diff --git a/coworker/personas/registry.py b/coworker/personas/registry.py index 06ccfb2b..14ce375b 100644 --- a/coworker/personas/registry.py +++ b/coworker/personas/registry.py @@ -50,6 +50,10 @@ class PersonaEntry: default_surfaced: bool = ( True # whether it shows in the picker before any user choice ) + # Whether it ships enabled before any user choice. Builtins default on (UX-029: the + # composer picker is their front door) — except retired ones (Chat: Coworker covers + # quick Q&A). Installed third-party personas always start disabled pending consent. + default_enabled: bool = True _builder: Optional[Callable[[], Agent]] = None manifest: Optional[PersonaManifest] = None @@ -101,6 +105,7 @@ class PersonaRegistry: tools, workspace="deliverable", default_surfaced=True, + default_enabled=True, ) -> None: self._entries[id] = PersonaEntry( id=id, @@ -113,13 +118,14 @@ class PersonaRegistry: workspace=workspace, tools=list(tools), default_surfaced=default_surfaced, + default_enabled=default_enabled, _builder=builder, ) def _load_builtin(self, builtin_dir: Optional[str | Path]) -> None: # Core surfaces keep their exact prompts via the existing builders. Cowork (the default) - # leads; Chat is hidden from the picker by default (Cowork covers quick Q&A) — recoverable - # from the Personas tab. + # leads; Chat is RETIRED (owner call 2026-08-11: Coworker covers quick Q&A) — it ships + # disabled and unsurfaced, recoverable from Settings ▸ Coworkers. self._register_builder( "cowork", "OpenWorker", @@ -153,6 +159,7 @@ class PersonaRegistry: [], workspace="none", default_surfaced=False, + default_enabled=False, ) # Markdown-backed built-ins (Ops, …) — dogfood the manifest path. d = Path(builtin_dir) if builtin_dir else Path(__file__).parent / "builtin" @@ -219,15 +226,16 @@ class PersonaRegistry: return self._entries.get(persona_id) def is_enabled(self, persona_id: str) -> bool: - # Explicit state (either way) always wins. Absent a user choice, BUILT-IN personas - # ship enabled — the composer picker is their front door (UX-029, supersedes the - # 2026-07-09 Coworker-only default that fit the old hidden ▾ menu). Installed - # third-party personas stay disabled until the user consents from the risk screen. + # Explicit state (either way) always wins. Absent a user choice, the entry's + # default applies: builtins ship enabled — the composer picker is their front door + # (UX-029, supersedes the 2026-07-09 Coworker-only default that fit the old hidden + # ▾ menu) — except retired ones (Chat). Installed third-party personas stay + # disabled until the user consents from the risk screen. if persona_id in self._enabled: return bool(self._enabled[persona_id]) entry = self._entries.get(persona_id) if entry is not None and entry.builtin: - return True + return entry.default_enabled return persona_id == self._default or persona_id == DEFAULT_PERSONA_ID def is_surfaced(self, persona_id: str) -> bool: diff --git a/surfaces/gui/e2e/fixtures.ts b/surfaces/gui/e2e/fixtures.ts index 48b3e5c7..6c44654a 100644 --- a/surfaces/gui/e2e/fixtures.ts +++ b/surfaces/gui/e2e/fixtures.ts @@ -57,7 +57,7 @@ const PERSONAS = { personas: [ { id: "cowork", name: "OpenWorker", icon: "cowork", tagline: "Produce a deliverable — research, analysis, scripts", needs_workspace: true, builtin: true, family: "knowledge", workspace: "deliverable", tools: ["files", "search"], enabled: true, surfaced: true, default: true }, { id: "code", name: "Code", icon: "code", tagline: "Work in a codebase — files, git, shell", needs_workspace: true, builtin: true, family: "code", workspace: "git", tools: ["code_files", "git"], enabled: true, surfaced: true, default: false }, - { id: "chat", name: "Chat", icon: "chat", tagline: "Quick questions — no workspace", needs_workspace: false, builtin: true, family: "knowledge", workspace: "none", tools: [], enabled: true, surfaced: false, default: false }, + { id: "chat", name: "Chat", icon: "chat", tagline: "Quick questions — no workspace", needs_workspace: false, builtin: true, family: "knowledge", workspace: "none", tools: [], enabled: false, surfaced: false, default: false }, { id: "ops", name: "Ops Coworker", icon: "wrench", tagline: "Operate and investigate — runbooks, logs, infrastructure", needs_workspace: true, builtin: true, family: "knowledge", workspace: "deliverable", tools: ["files", "shell"], enabled: true, surfaced: true, default: false }, // A non-builtin install (disabled pending consent — invisible to picker specs) so the // Personas page's delete/enable affordances have a target. diff --git a/surfaces/gui/src/components/SendFolderDialog.tsx b/surfaces/gui/src/components/SendFolderDialog.tsx index 61d49880..479121a5 100644 --- a/surfaces/gui/src/components/SendFolderDialog.tsx +++ b/surfaces/gui/src/components/SendFolderDialog.tsx @@ -90,7 +90,7 @@ export function SendFolderDialog({ coworkerName, onPick, onTemp, onCancel }: Pro }} disabled={busy} > - Start in a temporary folder + Use temporary folder {error &&