coworker picker: 'Use temporary folder' copy; retire Chat persona

Chat ships disabled+unsurfaced (Coworker covers quick Q&A); recoverable from Settings.
This commit is contained in:
Rohit C Prasad
2026-08-10 22:08:03 -07:00
committed by Rohit P
parent 3d13c7d699
commit 4908c8402e
4 changed files with 24 additions and 14 deletions
+15 -7
View File
@@ -50,6 +50,10 @@ class PersonaEntry:
default_surfaced: bool = ( default_surfaced: bool = (
True # whether it shows in the picker before any user choice 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 _builder: Optional[Callable[[], Agent]] = None
manifest: Optional[PersonaManifest] = None manifest: Optional[PersonaManifest] = None
@@ -101,6 +105,7 @@ class PersonaRegistry:
tools, tools,
workspace="deliverable", workspace="deliverable",
default_surfaced=True, default_surfaced=True,
default_enabled=True,
) -> None: ) -> None:
self._entries[id] = PersonaEntry( self._entries[id] = PersonaEntry(
id=id, id=id,
@@ -113,13 +118,14 @@ class PersonaRegistry:
workspace=workspace, workspace=workspace,
tools=list(tools), tools=list(tools),
default_surfaced=default_surfaced, default_surfaced=default_surfaced,
default_enabled=default_enabled,
_builder=builder, _builder=builder,
) )
def _load_builtin(self, builtin_dir: Optional[str | Path]) -> None: def _load_builtin(self, builtin_dir: Optional[str | Path]) -> None:
# Core surfaces keep their exact prompts via the existing builders. Cowork (the default) # 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 # leads; Chat is RETIRED (owner call 2026-08-11: Coworker covers quick Q&A) — it ships
# from the Personas tab. # disabled and unsurfaced, recoverable from Settings ▸ Coworkers.
self._register_builder( self._register_builder(
"cowork", "cowork",
"OpenWorker", "OpenWorker",
@@ -153,6 +159,7 @@ class PersonaRegistry:
[], [],
workspace="none", workspace="none",
default_surfaced=False, default_surfaced=False,
default_enabled=False,
) )
# Markdown-backed built-ins (Ops, …) — dogfood the manifest path. # Markdown-backed built-ins (Ops, …) — dogfood the manifest path.
d = Path(builtin_dir) if builtin_dir else Path(__file__).parent / "builtin" d = Path(builtin_dir) if builtin_dir else Path(__file__).parent / "builtin"
@@ -219,15 +226,16 @@ class PersonaRegistry:
return self._entries.get(persona_id) return self._entries.get(persona_id)
def is_enabled(self, persona_id: str) -> bool: def is_enabled(self, persona_id: str) -> bool:
# Explicit state (either way) always wins. Absent a user choice, BUILT-IN personas # Explicit state (either way) always wins. Absent a user choice, the entry's
# ship enabled — the composer picker is their front door (UX-029, supersedes the # default applies: builtins ship enabled — the composer picker is their front door
# 2026-07-09 Coworker-only default that fit the old hidden ▾ menu). Installed # (UX-029, supersedes the 2026-07-09 Coworker-only default that fit the old hidden
# third-party personas stay disabled until the user consents from the risk screen. # ▾ 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: if persona_id in self._enabled:
return bool(self._enabled[persona_id]) return bool(self._enabled[persona_id])
entry = self._entries.get(persona_id) entry = self._entries.get(persona_id)
if entry is not None and entry.builtin: 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 return persona_id == self._default or persona_id == DEFAULT_PERSONA_ID
def is_surfaced(self, persona_id: str) -> bool: def is_surfaced(self, persona_id: str) -> bool:
+1 -1
View File
@@ -57,7 +57,7 @@ const PERSONAS = {
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: "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: "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 }, { 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 // A non-builtin install (disabled pending consent — invisible to picker specs) so the
// Personas page's delete/enable affordances have a target. // Personas page's delete/enable affordances have a target.
@@ -90,7 +90,7 @@ export function SendFolderDialog({ coworkerName, onPick, onTemp, onCancel }: Pro
}} }}
disabled={busy} disabled={busy}
> >
Start in a temporary folder Use temporary folder
</button> </button>
</div> </div>
{error && <div className="mt-2 text-[11.5px] text-warnInk">{error}</div>} {error && <div className="mt-2 text-[11.5px] text-warnInk">{error}</div>}
+7 -5
View File
@@ -34,13 +34,15 @@ def test_sidebar_defaults_to_surfaced_builtins(tmp_path):
assert "code" not in [e["name"] for e in reg.sidebar()] assert "code" not in [e["name"] for e in reg.sidebar()]
def test_chat_hidden_by_default_but_resolvable(tmp_path): def test_chat_retired_by_default_but_resolvable(tmp_path):
reg = _reg(tmp_path) reg = _reg(tmp_path)
assert reg.is_surfaced("chat") is False # default-hidden from the grouped nav # Chat is retired (owner call 2026-08-11): disabled + unsurfaced out of the box,
assert reg.is_enabled("chat") is True # builtins ship enabled (UX-029) # unlike the other builtins — Coworker covers quick Q&A.
assert reg.is_enabled("chat") is False
assert reg.is_surfaced("chat") is False
assert reg.agent("chat").name == "chat" # live sessions keep resolving assert reg.agent("chat").name == "chat" # live sessions keep resolving
# Surfacing it adds it to the sidebar picker too. # Still recoverable from Settings ▸ Coworkers (enable implies surface).
reg.set_surfaced("chat", True) reg.set_enabled("chat", True)
assert "chat" in [e["name"] for e in reg.sidebar()] assert "chat" in [e["name"] for e in reg.sidebar()]