Skip to content

Commit e92caa3

Browse files
Mikarina13claude
andcommitted
feat(ux): 2026 overhaul — continuity, voice in/out, live preview, calmer palette
User feedback after first Project-mode run was blunt and right: "too bright, messy, Windows 2001, CODEC doesn't speak, scary, not 2026." This is a multi-front fix. #1 — Conversational continuity (chat → running agent routing) ───────────────────────────────────────────────────────────── Before: every Project-mode send drafted a fresh agent. Typing "how's it going?" while one was running spawned a *second* project for that question, exactly as the user reported in his screenshot. Now: • `_activeAgentId` binds a Project-mode chat thread to the first agent drafted from it. Survives reloads via localStorage. • Next sends route to POST /api/agents/{id}/messages (existing reply queue) instead of drafting a new project. • Pulsing chip above the composer: "Talking to agent_09aab9…" × exit • Auto-clears when the bound agent reaches done/failed/aborted (poll piggybacks on existing 5s status-pill tick). • Brief 2s reply-poll surfaces the agent's response in-thread. #2 — Auto-grant paths the user typed ──────────────────────────────────── Before: the CONSCRIPTED prompt mentioned ~/conscripted-2026/ four times, agent still blocked on read_path_not_authorized. Now: codec_agent_plan.merge_user_paths_into_manifest() runs after draft_plan returns. Extracts ~/foo, $HOME/foo, /Users/<n>/foo from the description, injects them into the manifest: • write verbs (save/write/download/output/generate/export/log to) within ±60 chars → write_paths (which also implies readable) • otherwise → read_paths • _PATH_BLOCKLIST_SUBSTRINGS drops ~/.ssh, ~/.aws, ~/.gnupg, /etc, /var, /System, ~/.codec/secrets etc. — these stay behind the approval wall regardless of what the user typed. • Idempotent: re-merge adds 0. Verified on the actual CONSCRIPTED description — 5 paths injected, including the lookbook output dir and log path. ~/.ssh + ~/.aws correctly excluded by blocklist. URLs ignored. #3 — Voice in / Voice out (the "tongue") ───────────────────────────────────────── Before: voiceReplyEnabled toggle existed in the side panel but was never read. CODEC literally never spoke. Now: • addMessage('assistant', …) → if voiceReplyEnabled, plays Kokoro via /api/tts. New Speak button per assistant message for manual playback regardless of toggle. • Speak toggle promoted into the composer mode-bar (was buried in side panel) — same toggleVoiceReply() backend so state stays in sync between both controls. • Pilot tab gets its own 🎤 mic + 🔇/🔊 speak buttons. Voice input via Web Speech API populates pilotTaskInput. Pilot speaks "Agent started", "Run complete", "Replay completed N of M steps" via pilotSay() → Kokoro. • Per-run terminal-state transitions tracked in _pilotLastSpokenStatus to avoid repeating completion announcements. #4 — Live preview panel (see what the agent is doing) ────────────────────────────────────────────────────── Before: while an agent was working, the user saw status pills and nothing else. Had to leave CODEC and open the project folder in an IDE to see what was happening. Now: • New GET /api/agents/{id}/files — walks project_dir up to 3 levels, returns most-recently-modified files (mtime-sorted, capped at 2K scanned, skips .git / node_modules / .venv etc.). • Live-preview slide-out anchored under the activeAgentChip. Polls every 5s while open. Shows rel path, size, "Ns/m/h ago" per file. • "👁 preview" toggle on the chip; "📂 open folder" calls existing open_folder endpoint to surface the dir in Finder. • Toggle state persisted in localStorage. #5 — 2026 palette (calmer, less "Windows 2001") ──────────────────────────────────────────────── The visible-on-bright-cream-light-mode pure orange (#E8711A) was the loudest offender. Coupled with high-saturation green/yellow/ red/blue/purple node colors on the Cortex map, the whole thing hit the "Windows XP control panel" register. Now (all three product HTMLs): • Dark mode accent: #E8711A → #d97757 (warmer coral, kept the brand orange family, dropped saturation) • Light mode accent: #E8711A → #b85a3a (was *jarring* on the cream bg — light mode now has its own calmer accent) • Light mode surfaces: #f7f5f2 → #faf8f5, borders softened • Dark mode surfaces & borders: tightened to the zinc family • Cortex node palette swap (111 occurrences via codepath replace): #ffd740 → #fbbf24 #00e676 → #34d399 #448aff → #60a5fa #b388ff → #a78bfa #ff9544 → #f59e0b #ff3d3d → #f87171 #34c759 → #22c55e #29b6f6 → #38bdf8 #E8711A → #d97757 Same hues, dropped saturation, modern Tailwind-500 family. Brand recognition intact — just stops shouting. Files ───── codec_agent_plan.py +109 lines: path extraction + merge helper, wired into create_agent codec_chat.html +292 lines: continuity binding, reply-poll, speak-on-assistant, Speak mode-btn, live-preview panel, palette codec_cortex.html ±222 lines: 111 color tone-downs, root palette codec_tasks.html +85 lines: pilotMic/Speak/Say, palette, run lifecycle TTS announcements routes/agents.py +61 lines: /api/agents/{id}/files endpoint Live-tested ─────────── • extract_user_paths on the actual CONSCRIPTED description: 5 paths injected (3 read, 2 write), ~/.ssh + ~/.aws blocked, URLs ignored, idempotent re-merge. • /api/tts reachable, Kokoro :8085 healthy. • /api/agents/{id}/files registered (auth-gated like /messages). • All services online after restart, dashboard responds 200. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 9ed6dd8 commit e92caa3

5 files changed

Lines changed: 640 additions & 129 deletions

File tree

codec_agent_plan.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,104 @@ def _new_agent_id() -> str:
733733
return f"agent_{secrets.token_hex(6)}"
734734

735735

736+
# ── User-typed path extraction (auto-grant) ───────────────────────────────────
737+
#
738+
# When the user explicitly types a path like ~/conscripted-2026/ in their project
739+
# description, they're effectively authorizing the agent to read/write there.
740+
# We extract those paths post-draft and inject them into the manifest so the
741+
# plan approval flow grants them in one click instead of blocking mid-run.
742+
743+
import re as _re_path
744+
745+
# Match ~/foo/bar, $HOME/foo/bar, /Users/<name>/foo/bar
746+
_PATH_TOKEN = _re_path.compile(
747+
r"(?P<path>"
748+
r"(?:~|\$HOME|/Users/[A-Za-z0-9_.-]+)"
749+
r"/[A-Za-z0-9_.\-/]+"
750+
r")"
751+
)
752+
753+
# Never auto-grant these — keep approval friction
754+
_PATH_BLOCKLIST_SUBSTRINGS = (
755+
"/.ssh", "/.aws", "/.gnupg", "/.config/gh", "/Library/Keychains",
756+
"/Library/Application Support/com.apple",
757+
"/.codec/secrets", "/.codec/auth",
758+
"/etc/", "/var/", "/private/", "/System/", "/usr/local/etc",
759+
)
760+
761+
762+
def _normalize_path(p: str) -> str:
763+
p = p.rstrip(".,;:!?\"')\n")
764+
if p.endswith("/"):
765+
p = p[:-1]
766+
return p
767+
768+
769+
def extract_user_paths(description: str) -> tuple[list[str], list[str]]:
770+
"""
771+
Pull paths the user typed in their description. Returns (read_globs, write_globs).
772+
Each path is converted to a recursive glob ('foo/**') for the manifest.
773+
774+
Heuristic for read vs write — write verbs in the surrounding ±60 chars
775+
of the path token bump it into write_globs (which also implies readable).
776+
Paths matching _PATH_BLOCKLIST_SUBSTRINGS are dropped entirely.
777+
"""
778+
if not description:
779+
return [], []
780+
seen: set[str] = set()
781+
reads: list[str] = []
782+
writes: list[str] = []
783+
write_verbs = ("save", "write", "download", "output", "generate", "export",
784+
"store", "dump", "log to", "create at", "into")
785+
786+
desc_lower = description.lower()
787+
for m in _PATH_TOKEN.finditer(description):
788+
raw = _normalize_path(m.group("path"))
789+
if not raw or raw in seen:
790+
continue
791+
if any(b in raw for b in _PATH_BLOCKLIST_SUBSTRINGS):
792+
continue
793+
seen.add(raw)
794+
795+
start = max(0, m.start() - 60)
796+
end = min(len(description), m.end() + 60)
797+
window = desc_lower[start:end]
798+
looks_writey = any(v in window for v in write_verbs)
799+
800+
glob = raw + "/**"
801+
if looks_writey:
802+
writes.append(glob)
803+
reads.append(glob) # writable implies readable
804+
else:
805+
reads.append(glob)
806+
return reads, writes
807+
808+
809+
def merge_user_paths_into_manifest(plan, description: str) -> int:
810+
"""
811+
Inject paths the user typed into the plan's permission_manifest.
812+
Returns the number of paths added. Idempotent — won't re-add duplicates.
813+
"""
814+
reads, writes = extract_user_paths(description)
815+
if not reads and not writes:
816+
return 0
817+
pm = plan.permission_manifest
818+
existing_reads = set(pm.read_paths or [])
819+
existing_writes = set(pm.write_paths or [])
820+
added = 0
821+
for r in reads:
822+
if r not in existing_reads:
823+
pm.read_paths.append(r)
824+
existing_reads.add(r)
825+
added += 1
826+
for w in writes:
827+
if w not in existing_writes:
828+
pm.write_paths.append(w)
829+
existing_writes.add(w)
830+
added += 1
831+
return added
832+
833+
736834
def create_agent(title: str, description: str,
737835
registry=None,
738836
notification_channels: Optional[List[str]] = None) -> str:
@@ -788,6 +886,17 @@ def create_agent(title: str, description: str,
788886
extra={"agent_id": agent_id, "reason": str(e)[:200]})
789887
raise
790888

889+
# Auto-grant: paths the user explicitly typed in their description go
890+
# into the manifest before approval. Reduces "blocked_on_permission"
891+
# mid-run when the user's own message already authorized them.
892+
try:
893+
injected = merge_user_paths_into_manifest(plan, description)
894+
if injected:
895+
log.info("[%s] auto-injected %d user-typed path(s) into manifest",
896+
agent_id, injected)
897+
except Exception as e:
898+
log.warning("[%s] user-path extraction failed: %s", agent_id, e)
899+
791900
# Persist plan + transition status
792901
save_plan(plan)
793902
set_status(agent_id, "awaiting_approval")

0 commit comments

Comments
 (0)