@elizaos/plugin-agent-orchestrator 2.0.0-alpha.9 → 2.0.11-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +232 -0
  4. package/docs/SUBAGENT_FLOW_AND_PARITY.md +138 -0
  5. package/docs/default-eliza-skills-and-agent-bridge-plan.md +225 -0
  6. package/docs/economics-goal-runbook.md +140 -0
  7. package/docs/multi-account-orchestration.md +76 -0
  8. package/docs/orchestrator-buildout-followups.md +124 -0
  9. package/docs/orchestrator-dashboard-task-widget-secrets-assessment.md +174 -0
  10. package/docs/orchestrator-dashboard-task-widget-secrets-design.md +301 -0
  11. package/docs/research/orchestrator-view-research-report.md +273 -0
  12. package/docs/sub-agent-routing.md +227 -0
  13. package/package.json +197 -38
  14. package/dist/index.d.ts +0 -21
  15. package/dist/index.d.ts.map +0 -1
  16. package/dist/index.js +0 -21120
  17. package/dist/index.js.map +0 -109
  18. package/dist/src/actions/messaging.d.ts +0 -24
  19. package/dist/src/actions/messaging.d.ts.map +0 -1
  20. package/dist/src/actions/peek-subagent.d.ts +0 -3
  21. package/dist/src/actions/peek-subagent.d.ts.map +0 -1
  22. package/dist/src/actions/subagent-management.d.ts +0 -7
  23. package/dist/src/actions/subagent-management.d.ts.map +0 -1
  24. package/dist/src/actions/task-management.d.ts +0 -9
  25. package/dist/src/actions/task-management.d.ts.map +0 -1
  26. package/dist/src/config.d.ts +0 -4
  27. package/dist/src/config.d.ts.map +0 -1
  28. package/dist/src/providers/orchestrator-config.d.ts +0 -80
  29. package/dist/src/providers/orchestrator-config.d.ts.map +0 -1
  30. package/dist/src/providers/task-context.d.ts +0 -3
  31. package/dist/src/providers/task-context.d.ts.map +0 -1
  32. package/dist/src/services/agent-orchestrator-service.d.ts +0 -59
  33. package/dist/src/services/agent-orchestrator-service.d.ts.map +0 -1
  34. package/dist/src/services/messaging-service.d.ts +0 -111
  35. package/dist/src/services/messaging-service.d.ts.map +0 -1
  36. package/dist/src/services/sandbox-service.d.ts +0 -103
  37. package/dist/src/services/sandbox-service.d.ts.map +0 -1
  38. package/dist/src/services/subagent-service.d.ts +0 -140
  39. package/dist/src/services/subagent-service.d.ts.map +0 -1
  40. package/dist/src/sub-agents/adapter.d.ts +0 -13
  41. package/dist/src/sub-agents/adapter.d.ts.map +0 -1
  42. package/dist/src/sub-agents/claude-agent-sdk-sub-agent.d.ts +0 -18
  43. package/dist/src/sub-agents/claude-agent-sdk-sub-agent.d.ts.map +0 -1
  44. package/dist/src/sub-agents/codex-sdk-sub-agent.d.ts +0 -18
  45. package/dist/src/sub-agents/codex-sdk-sub-agent.d.ts.map +0 -1
  46. package/dist/src/sub-agents/eliza-sub-agent.d.ts +0 -27
  47. package/dist/src/sub-agents/eliza-sub-agent.d.ts.map +0 -1
  48. package/dist/src/sub-agents/elizaos-native-sub-agent.d.ts +0 -61
  49. package/dist/src/sub-agents/elizaos-native-sub-agent.d.ts.map +0 -1
  50. package/dist/src/sub-agents/index.d.ts +0 -10
  51. package/dist/src/sub-agents/index.d.ts.map +0 -1
  52. package/dist/src/sub-agents/opencode-sub-agent.d.ts +0 -44
  53. package/dist/src/sub-agents/opencode-sub-agent.d.ts.map +0 -1
  54. package/dist/src/sub-agents/registry.d.ts +0 -3
  55. package/dist/src/sub-agents/registry.d.ts.map +0 -1
  56. package/dist/src/sub-agents/sweagent-sub-agent.d.ts +0 -19
  57. package/dist/src/sub-agents/sweagent-sub-agent.d.ts.map +0 -1
  58. package/dist/src/sub-agents/tools.d.ts +0 -15
  59. package/dist/src/sub-agents/tools.d.ts.map +0 -1
  60. package/dist/src/sub-agents/types.d.ts +0 -170
  61. package/dist/src/sub-agents/types.d.ts.map +0 -1
  62. package/dist/src/types/index.d.ts +0 -12
  63. package/dist/src/types/index.d.ts.map +0 -1
  64. package/dist/src/types/messaging.d.ts +0 -202
  65. package/dist/src/types/messaging.d.ts.map +0 -1
  66. package/dist/src/types/sandbox.d.ts +0 -228
  67. package/dist/src/types/sandbox.d.ts.map +0 -1
  68. package/dist/src/types/subagent.d.ts +0 -232
  69. package/dist/src/types/subagent.d.ts.map +0 -1
  70. package/dist/src/types.d.ts +0 -138
  71. package/dist/src/types.d.ts.map +0 -1
  72. package/dist/src/utils/index.d.ts +0 -7
  73. package/dist/src/utils/index.d.ts.map +0 -1
  74. package/dist/src/utils/session.d.ts +0 -184
  75. package/dist/src/utils/session.d.ts.map +0 -1
@@ -0,0 +1,174 @@
1
+ # Slice 1 critical assessment — chat task widget + OAuth secrets widget
2
+
3
+ Companion to
4
+ [`orchestrator-dashboard-task-widget-secrets-design.md`](./orchestrator-dashboard-task-widget-secrets-design.md).
5
+ Recorded after the first implementation pass, before any UI screenshot.
6
+
7
+ ## What landed
8
+
9
+ | Area | Files | Net change |
10
+ |---|---|---|
11
+ | `[TASK:id]title[/TASK]` parser | `packages/ui/src/components/chat/message-task-parser.ts` (new) + test (new) | Pure parser, strict UUID-shaped threadId, truncates titles, ignores unterminated tags. 9/9 tests green. |
12
+ | `TaskWidget` component | `packages/ui/src/components/chat/widgets/task-widget.tsx` (new) + test (new) | One-line title + one-line status (status · agents · relative · tokens). Polls `getCodingAgentTaskThread` every 5s, stops on terminal status, silent on 404 ("Task removed."), click dispatches `eliza:navigate:view` → `/orchestrator?taskId=…`. 6/6 tests green. |
13
+ | Wire-up in chat | `packages/ui/src/components/chat/MessageContent.tsx` (edit) + integration test (new) | New `kind: "task-widget"` segment + dispatch. Prose around the block continues to render. 4/4 integration tests green. |
14
+ | Sensitive-request OAuth | `packages/ui/src/components/chat/MessageContent.tsx` (edit) + extended test | New `OAuthRequestPanel` rendered when `form.kind === "oauth"`. Opens authorization URL in a popup; on popup block, surfaces "Pop-up blocked" without falling back to the chat stream. Scopes are visible; the URL itself is never substituted into chat text. The secret form keeps its current behavior. |
15
+ | Type widening | `packages/ui/src/api/client-types-chat.ts` (edit) | `SensitiveRequestForm.kind` widened to `"secret" \| "oauth"` with three optional fields: `provider`, `scopes`, `authorizationUrl`. |
16
+ | Tests added | 4 vitest files | Parser (9), Widget (6), MessageContent integration (4), OAuth in SensitiveRequestBlock (3 new). Whole `src/components/chat` suite: 49/49 green. |
17
+ | Lint / typecheck | — | `bun run --cwd packages/ui lint` clean. `bun run --cwd packages/ui typecheck` clean. |
18
+
19
+ ## Honest gaps
20
+
21
+ These are not "in the design doc but I forgot" — they were called out as
22
+ follow-ups in the doc itself. Listing here so the next pass knows the
23
+ priority order:
24
+
25
+ 1. **No emission point in `TASKS_CREATE`.** The widget RENDERS, but no
26
+ production action yet emits `[TASK:<id>]<title>[/TASK]` after a successful
27
+ durable-task creation. `runCreate` in
28
+ [`tasks.ts`](../src/actions/tasks.ts) spawns ACP sessions, not durable
29
+ task threads — the link between an action callback and a durable
30
+ `taskId` is not in place. Without that, the widget only renders if
31
+ the agent text already contains the block, which is the e2e seam used
32
+ in tests. The natural next step is wiring `OrchestratorTaskService`'s
33
+ `createTask` (or a separate `TASKS_CREATE_THREAD` sub-action) so that
34
+ on success it returns a `taskId` and the callback text concatenates the
35
+ block. That is a one-line emission change + a unit test in the plugin,
36
+ but it touches the create flow's wiring and was scoped as a separate
37
+ slice.
38
+ 2. **No OAuth backend adapter yet.** The UI widget works, but no
39
+ `SensitiveRequest` with `target.kind === "oauth"` is produced by the
40
+ policy classifier today; the inline adapter rejects non-secret. A
41
+ sibling `owner-app-oauth-adapter.ts` (and a `target.kind: "oauth"`) is
42
+ the next backend step. See `orchestrator-buildout-followups.md` section B.
43
+ 3. **No Playwright spec yet for the chat path.** Component-level tests cover
44
+ the rendering, dispatch, and event contract; an end-to-end "send chat
45
+ → assistant emits block → widget appears → click → workbench task
46
+ detail visible" would need MSW-style routes for the chat backend plus a
47
+ way to inject the assistant turn. The existing
48
+ `orchestrator-gui-workbench.spec.ts` already covers the create-flow and
49
+ the rail → inspector path. The chat-side spec is in slice 4 of the
50
+ design doc; it didn't land in this pass.
51
+ 4. **`OrchestratorGlanceStrip` was descoped.** On second read of
52
+ [`WorkbenchHeader`](../../../plugins/plugin-task-coordinator/src/OrchestratorWorkbench.tsx#L597),
53
+ the header already reads "12 tasks · 1 active · 3 done" with the same
54
+ minimal language the design called for, and adds a usage chip on the
55
+ right. A separate strip would have been redundant and added a second
56
+ row that competes with the same information. The right follow-up if a
57
+ user calls this out specifically is making the inline counts
58
+ click-to-filter, NOT introducing a new pinned tile.
59
+ 5. **Inspector terminal-task cleanup (slice 5) was descoped.** It's a
60
+ handful of `if (TERMINAL_STATUSES.has(status)) return null;` guards in
61
+ `TaskInspector`; not landed because the inspector is 4142 LOC and I
62
+ prioritized the user-visible chat gap. Worth a follow-up.
63
+
64
+ ## Security audit of what landed
65
+
66
+ - **Secret form (`SensitiveRequestBlock`)** — submission still goes
67
+ through `client.updateSecrets`, never through the chat message stream.
68
+ Existing test asserts the raw secret never appears anywhere in the
69
+ rendered DOM. Added a `data-testid="sensitive-request-submit"` so
70
+ Playwright can target the submit cleanly; behavior unchanged.
71
+ - **OAuth widget (`OAuthRequestPanel`)** — three guards:
72
+ 1. The authorization URL is only consumed inside `window.open()`. It is
73
+ never substituted into the visible chat text. The new test asserts
74
+ the URL substring is not present anywhere in `container.textContent`.
75
+ 2. Popup is opened with `noopener,noreferrer` so the consent page can't
76
+ `window.opener.*` back to chat.
77
+ 3. A blocked popup surfaces "Pop-up blocked. Allow pop-ups for this
78
+ site to continue." rather than silently falling back to a chat
79
+ message that would re-render the URL.
80
+ - **TaskWidget** — only reads structured status fields (`status`,
81
+ `sessionCount`, `activeSessionCount`, `latestActivityAt`,
82
+ `usage.totalTokens`). It does not render `metadata` or `messages`. A
83
+ regression test asserts secret-shaped strings smuggled through `metadata`
84
+ do not appear in the rendered DOM.
85
+
86
+ ## Test coverage table
87
+
88
+ | File | Suite | Count | Status |
89
+ |---|---|---|---|
90
+ | `message-task-parser.test.ts` | parser unit | 9 | green |
91
+ | `widgets/task-widget.test.tsx` | component unit | 6 | green |
92
+ | `MessageContent.task-widget.test.tsx` | integration | 4 | green |
93
+ | `MessageContent.sensitive-request.test.tsx` | sensitive widget (extended) | 7 | green |
94
+ | Whole `src/components/chat` suite | — | 49 | green |
95
+
96
+ `bun run --cwd packages/ui lint` clean. `bun run --cwd packages/ui typecheck` clean.
97
+ `bun run --cwd packages/app-core typecheck` shows two pre-existing
98
+ `@elizaos/plugin-commands` resolution errors unrelated to this change.
99
+
100
+ ## What a screenshot pass would look for (next session)
101
+
102
+ Not run in this session because there is no live UI to capture. The
103
+ checklist for that pass:
104
+
105
+ - **Chat surface, message with a task block** —
106
+ - Title line truncates without ellipsis-jitter at 320px width.
107
+ - Status line wraps gracefully; the dot/label/agents/tokens chips
108
+ never wrap mid-chip.
109
+ - Pulse animation is visible on `active` and `validating`, absent
110
+ on terminal states.
111
+ - Click → orchestrator workbench shows the matching task selected.
112
+ - **Chat surface, OAuth request** —
113
+ - "Connect <provider>" button reads exactly like the surrounding chat
114
+ accent (no accidental blue).
115
+ - Scopes line truncates at small widths.
116
+ - Pop-up blocked state surfaces inline, not modal.
117
+ - **Chat surface, secret request** — unchanged, but confirm the new
118
+ `data-testid` didn't shift any spacing.
119
+ - **Orchestrator workbench, `?taskId=…` arrival** — task is selected,
120
+ inspector visible without scrolling.
121
+
122
+ ## Post-review fixes (second pass)
123
+
124
+ After fanning out independent code-review + aesthetic + verification agents, four
125
+ findings were addressed in this same change-set:
126
+
127
+ 1. **`window.open(..., "noopener,noreferrer")` bug** — per the HTML spec
128
+ `noopener` forces `window.open` to return `null`, so the popup-blocked
129
+ detection misfired in every real browser. Removed `noopener` from the
130
+ features string, kept `noreferrer`, and added an explicit
131
+ `popup.opener = null` immediately after open as a belt-and-suspenders
132
+ measure. Test asserts `noreferrer` is present and `noopener` is not, and
133
+ that `opener` is nulled.
134
+ 2. **`TaskWidget` infinite poll on auth error** — added a
135
+ `MAX_CONSECUTIVE_ERRORS = 3` cap on the silent-error counter; after three
136
+ consecutive 401/403/etc the widget freezes on the last good state and
137
+ stops polling. Counter resets on the next successful fetch.
138
+ 3. **Slop pass — `ExternalLink` decorative icon removed** from `TaskWidget`.
139
+ The whole card is already a `<button>` with `hover:bg-bg-hover`; the icon
140
+ added zero info at a glance.
141
+ 4. **Slop pass — "The value will not be sent as a chat message." removed**
142
+ from the secret-form widget. A `type="password"` input is its own
143
+ signal; the trust copy stays on the OAuth panel where the user is
144
+ genuinely navigating to a third-party origin.
145
+ 5. **OAuth panel testid** — added `data-testid="sensitive-request-oauth"`
146
+ to the panel wrapper for parity with `sensitive-request`.
147
+
148
+ Reopen-on-terminal was reconsidered and intentionally left as-is: done/failed
149
+ tasks have an Archive button that transitions them to `archived`, which
150
+ exposes Reopen. The path exists — it just isn't a single-click escape from
151
+ done → reopened. Acceptable.
152
+
153
+ The legacy `SensitiveRequestOauthTarget` (lowercase `a`) coexists with the
154
+ new `SensitiveRequestOAuthTarget` (capital `A`) in the
155
+ `SensitiveRequestTarget` union. The new OAuth adapter defends with an
156
+ `isOAuthTarget` runtime narrower, so downstream consumers that already
157
+ relied on the loose shape are unaffected. Consolidating these two into a
158
+ single tight type is a follow-up (it widens the blast radius beyond this
159
+ change-set).
160
+
161
+ ## What to revisit if a user looks at this and says "still too busy"
162
+
163
+ - The header's `12 tasks · 1 active · 3 done` line already aims at
164
+ minimal/dense. If that still reads as cluttered, the next reduction
165
+ is dropping `tasks` (just show counts and labels) and making the
166
+ divider thinner.
167
+ - The widget's status line is four chips by default. If three of the
168
+ four are zero (e.g. fresh task, no agents, no tokens), only `status`
169
+ + `relative` render — that's already the case via the existing
170
+ conditional renders.
171
+ - The OAuth panel intentionally explains *why* it's safe ("token is
172
+ stored securely and never shown in chat"). That sentence is the only
173
+ prose in the panel and it's important for trust; it should not be
174
+ cut.
@@ -0,0 +1,301 @@
1
+ # Orchestrator dashboard, in-chat task widget, and secrets widgets — design
2
+
3
+ Date: 2026-06-05. Companion to
4
+ [`orchestrator-buildout-followups.md`](./orchestrator-buildout-followups.md).
5
+
6
+ This is a code-grounded, intentionally small design for three connected gaps the
7
+ goal hook calls out, in priority order:
8
+
9
+ 1. **Orchestrator view should read as a *dashboard*** of ongoing tasks — quick
10
+ active/finished/needs-attention counts at the top, a minimal card list
11
+ underneath, click a card to drill into the existing full inspector.
12
+ 2. **Task creation should drop a live `TaskWidget` into the chat** that shows
13
+ status and is clickable. The chat conversation continues; the agent keeps
14
+ task context; the orchestrator view's action bar stays view-dependent on the
15
+ currently-selected task.
16
+ 3. **Sensitive-request widgets in chat (form / OAuth / secret) must
17
+ actually work and be tested**, with no secret material echoed to chat.
18
+
19
+ Scope is intentionally narrow. The existing
20
+ [`OrchestratorWorkbench.tsx`](../src/OrchestratorWorkbench.tsx) (4142 LOC) is
21
+ mature — rail, inspector, action bar, timeline, operator drawer, plan editor.
22
+ We do not redesign it. We add a thin glance strip, fix the gaps, and lock
23
+ behavior in tests.
24
+
25
+ ## What exists today (ground truth)
26
+
27
+ - `OrchestratorWorkbench` renders `/orchestrator`. Header has Pause All / Resume
28
+ All / New Task; a filter dropdown shows status + count; rail renders
29
+ `TaskCardList` cards. Selecting a card swaps the rail for a full-pane task
30
+ room with timeline + inspector (`data-testid="orchestrator-inspector"`).
31
+ Inspector action bar is already view-dependent: validating → Approve/Reject,
32
+ archived → Reopen, active → Pause/Resume, plus Fork/Restart/Add Agent.
33
+ - `MessageContent.tsx` dispatches 7 segment kinds: `text`, `config`, `ui-spec`,
34
+ `choice`, `followups`, `form`, `permission`, `analysis-xml`. **There is no
35
+ `task-widget` kind**. A `[TASK:id]title[/TASK]` block in an assistant message
36
+ renders as plain text today.
37
+ - `SensitiveRequestBlock` (in `MessageContent.tsx`) renders inline-secret form
38
+ widgets driven by `message.secretRequest`. It is tested
39
+ (`MessageContent.sensitive-request.test.tsx`). It supports `form.kind ===
40
+ "secret"` only; **OAuth is not wired** — `owner-app-inline-adapter.ts`
41
+ explicitly rejects any non-secret kind.
42
+ - `task-coordinator-gui-interactions.spec.ts` covers list, search, open detail,
43
+ archive, reopen. `orchestrator-gui-workbench.spec.ts` covers the rich rail
44
+ and inspector controls (priority change, restart-with-plan, add agent,
45
+ pause/resume, retry/rerun). **Neither covers a chat task widget or any
46
+ sensitive-request widget.**
47
+
48
+ ## Design
49
+
50
+ ### 1. `OrchestratorGlanceStrip` — at-a-glance dashboard tile
51
+
52
+ A single pinned row above the rail, four count tiles + the existing usage chip.
53
+ No new state — derived from the same `/api/orchestrator/status` payload the
54
+ header reads. Each tile is a button that sets the filter and scrolls the rail
55
+ to the top.
56
+
57
+ ```
58
+ ┌─────────────────────────────────────────────────────────────────┐
59
+ │ ● 3 Active ◐ 1 Validating ⚠ 0 Blocked ✓ 12 Done 12.3K · $0.42 │
60
+ └─────────────────────────────────────────────────────────────────┘
61
+ ```
62
+
63
+ Rules:
64
+
65
+ - **One row, fixed height.** ~32px. No subtitle text under counts.
66
+ - **Color = state, not decoration.** `ok` for Active, `accent` for Validating,
67
+ `warn` for Blocked, `muted` for Done.
68
+ - **Zero = `muted` text + de-emphasized tile.** Zero counts never animate.
69
+ - **Click filters.** Click "Active" → filter "active". Click "Blocked" →
70
+ filter "blocked" (rolls up `waiting_on_user` per existing status fan-out).
71
+ - **Hidden when there are zero tasks total** — the empty state already covers
72
+ this case.
73
+
74
+ Test ids: `orchestrator-glance`, `orchestrator-glance-active`,
75
+ `orchestrator-glance-validating`, `orchestrator-glance-blocked`,
76
+ `orchestrator-glance-done`.
77
+
78
+ Implementation: new component `OrchestratorGlanceStrip.tsx` rendered in
79
+ `OrchestratorWorkbench.tsx` directly above the rail. It reads
80
+ `taskCount`/`activeTaskCount`/`blockedTaskCount`/`validatingTaskCount` from
81
+ the same status payload `WorkbenchHeader` already consumes; we promote a small
82
+ counts derivation out so both call sites stay in sync.
83
+
84
+ ### 2. `TaskWidget` — inline chat widget for an orchestrator task
85
+
86
+ A new segment kind in `MessageContent`. The agent emits
87
+ `[TASK:<threadId>]<title>[/TASK]` after a successful `TASKS_CREATE`, and the
88
+ chat renderer replaces it with a live widget.
89
+
90
+ Anatomy (one compact card, ~64px tall):
91
+
92
+ ```
93
+ ┌────────────────────────────────────────────────────────┐
94
+ │ ▶ Build Kanban planner app [Open] │
95
+ │ ● active · 2/2 agents · 3m ago · ~12.3K │
96
+ └────────────────────────────────────────────────────────┘
97
+ ```
98
+
99
+ Rules:
100
+
101
+ - **One title line + one status line.** No goal, no acceptance criteria, no
102
+ artifact list. Those live in the orchestrator inspector.
103
+ - **Status line is structured, not prose.** Status dot + label, agents
104
+ active/total, relative last activity, token total. Tokens hidden when
105
+ `usageState === "unavailable"`.
106
+ - **Click → navigate `/orchestrator?taskId=<id>`** (the workbench already
107
+ reads `?task=` via `readInitialTaskId`; we alias `?taskId=` to it).
108
+ - **Live updates by polling**, not WebSocket. Reuses the workbench's
109
+ `POLL_INTERVAL_MS = 5000` (kept in one place — exported, not duplicated). On
110
+ unmount, polling stops. When the underlying task is terminal
111
+ (`done`/`failed`/`archived`/`closed`), polling stops and the status freezes.
112
+ - **One outstanding fetch per widget.** Concurrent renders share a tiny
113
+ per-thread cache so two widgets in scrollback don't quadruple traffic.
114
+ - **No action buttons inline.** Action belongs to the workbench's
115
+ view-dependent action bar (we are explicit about this — the goal asks the
116
+ workbench to own actions when on a task).
117
+ - **Failure mode is silent.** If the task 404s (deleted), the widget shrinks
118
+ to "Task removed." in muted text; it never throws into the chat.
119
+
120
+ Backend hook: after `TASKS_CREATE` succeeds, the action's response text gets
121
+ `[TASK:${task.id}]${task.title}[/TASK]` appended. This is a one-line change in
122
+ `tasks.ts` (the `create` runner already returns a callback with prose).
123
+
124
+ Segment parser: extend the existing regex sweep in
125
+ `MessageContent.parseSegments` with a `[TASK:...]...[/TASK]` pattern, ordered
126
+ after `[FORM]` and before plain text. The matcher requires `threadId` to be a
127
+ UUID-shaped string (lowercase a–f0-9-) to avoid trivial injection from
128
+ unrelated text.
129
+
130
+ Test ids: `task-widget`, `task-widget-status`, `task-widget-open`.
131
+
132
+ ### 3. View-dependent action bar in the workbench
133
+
134
+ Already exists in `TaskInspector` (`OrchestratorWorkbench.tsx` lines
135
+ 2007–2187). It is correctly state-conditional but every button is always
136
+ rendered (just disabled when N/A). The minimal change is grouping and hiding:
137
+
138
+ - **Hide the entire "Edit" group** (Fork, Restart, Add Agent, Edit Plan) when
139
+ `task.status` is in {`done`, `failed`, `archived`, `closed`}. Terminal tasks
140
+ should only expose Reopen (and Delete).
141
+ - **Hide priority dropdown for terminal tasks.** Priority is meaningless once
142
+ closed.
143
+ - **Keep validating-task Approve/Reject as the only primary affordance.**
144
+ Other buttons collapse into an overflow.
145
+
146
+ This is small — three `if` guards. Locked in by an inspector test that
147
+ toggles status and asserts visibility.
148
+
149
+ ### 4. Sensitive-request widgets — what's working and what we add
150
+
151
+ Working today: inline form for `kind: "secret"`. We tighten and extend.
152
+
153
+ **a. `SensitiveRequestBlock` polish (existing component, small fix)**
154
+
155
+ - Distinguish `pending` (gray "Pending"), `saving` (spinner + "Saving…"),
156
+ `saved` (green "Saved" + redacted preview "•••• (saved)"),
157
+ `failed` (red "Failed — retry"), `expired` (muted "Expired").
158
+ - On `saved`, the form **never re-renders the password input** for the same
159
+ request id, even if the message re-mounts. Backed by a tiny per-requestId
160
+ saved-state cache in component scope.
161
+ - Reason and instruction text already render; we make sure they never include
162
+ the secret value (server-side sanitization is the actual guarantee; the
163
+ widget asserts `value` is never substituted into `instruction` or `reason`
164
+ via `MessageContent.sensitive-request.test.tsx`).
165
+
166
+ **b. `OAuthRequestBlock` (new, in `MessageContent.tsx`)**
167
+
168
+ Renders when `message.secretRequest.form.kind === "oauth"`. Shape:
169
+
170
+ ```
171
+ ┌────────────────────────────────────────────┐
172
+ │ Connect GitHub │
173
+ │ Needed for: managing PRs on this task │
174
+ │ │
175
+ │ [ Connect with GitHub → ] Pending │
176
+ └────────────────────────────────────────────┘
177
+ ```
178
+
179
+ Rules:
180
+
181
+ - **One button.** It calls `client.startSensitiveRequestOAuth(requestId)`
182
+ which returns `{ authorizationUrl, state }` and opens it in a popup
183
+ (`window.open` with a sized rect). Same-origin fallback for embedded
184
+ environments where popups are blocked.
185
+ - **No token ever passed through chat.** The OAuth callback fulfills the
186
+ request server-side; the widget polls
187
+ `GET /api/sensitive-requests/:id/status` every 2s, up to `expiresAt`, and
188
+ stops on terminal states.
189
+ - **Visible state machine:** `Pending` → `Authorizing…` → `Saved` |
190
+ `Failed — retry` | `Expired`.
191
+ - **Cancel.** A small `Cancel` link sends `DELETE
192
+ /api/sensitive-requests/:id` and flips to `Cancelled`.
193
+
194
+ Backend wiring is a sibling of the inline adapter:
195
+ `packages/app-core/src/services/sensitive-requests/owner-app-oauth-adapter.ts`.
196
+ The adapter's `deliver()` accepts `request.target.kind === "oauth"` requests
197
+ (new target kind), builds an envelope with `form: { kind: "oauth", provider,
198
+ scopes, label, submitLabel: "Connect …" }`, and sends the inline chat content.
199
+ The OAuth callback handler lands tokens in `sharedVault` exactly like the
200
+ existing GitHub OAuth flow; only the *trigger* differs.
201
+
202
+ We do **not** ship a generic OAuth backend in this slice. We ship the widget +
203
+ its parser + a fixture-backed e2e + a unit test for envelope construction.
204
+ A follow-up wires production providers (per
205
+ [`orchestrator-buildout-followups.md`](./orchestrator-buildout-followups.md)
206
+ section B).
207
+
208
+ **c. `FormRequest` (existing `[FORM]` widget) — unchanged**
209
+
210
+ The non-sensitive `[FORM]` widget already works
211
+ (`form-request.tsx`). It is referenced here only to draw the line: that
212
+ widget's submission **is echoed to chat** because it carries non-secret data.
213
+ Sensitive requests must never reuse it.
214
+
215
+ ### Security guarantees we explicitly maintain
216
+
217
+ - The secret/OAuth widgets must never push the value or token into the chat
218
+ message stream. The current inline adapter calls
219
+ `client.updateSecrets(secrets)` directly and ignores its message channel
220
+ for the value; we keep that, and add an integration test that scrapes the
221
+ recorded chat-message stream after a successful save and asserts no field
222
+ value appears.
223
+ - OAuth callbacks must never include tokens in URL fragments delivered to the
224
+ chat surface. The popup posts the result to the server; the chat surface
225
+ only sees `status` transitions.
226
+ - Widget rendering is gated on `delivery.canCollectValueInCurrentChannel ===
227
+ true`. Public/group surfaces continue to render the existing
228
+ status-only card pointing at the owner app.
229
+
230
+ ## Tests
231
+
232
+ All under existing infra (`packages/ui` vitest for components,
233
+ `packages/app/test/ui-smoke` Playwright for the route).
234
+
235
+ 1. **Component (vitest, packages/ui):**
236
+ - `MessageContent.task-widget.test.tsx` — `[TASK:id]title[/TASK]` parses to a
237
+ `TaskWidget`; renders title; polls once and renders status; click
238
+ dispatches navigation.
239
+ - `MessageContent.sensitive-request.test.tsx` (extend) — status transitions;
240
+ saved-state cache survives remount; instruction never contains the
241
+ entered value.
242
+ - `MessageContent.oauth-request.test.tsx` — OAuth widget shows Connect,
243
+ opens popup (mocked `window.open`), polls status, lands on `Saved`.
244
+
245
+ 2. **Plugin (vitest, plugin-agent-orchestrator):**
246
+ - `create-task-emits-task-block.test.ts` — `TASKS_CREATE` runner output
247
+ contains `[TASK:${id}]${title}[/TASK]`.
248
+ - `owner-app-oauth-adapter.test.ts` — envelope shape; rejects non-OAuth
249
+ kind; emits an inline content with `form.kind === "oauth"`.
250
+
251
+ 3. **Playwright (packages/app):**
252
+ - Extend `orchestrator-gui-workbench.spec.ts` with a `glance strip` test —
253
+ fixture seeds 3 active / 1 validating / 12 done; assert tile counts;
254
+ click Validating → filter switches; assert rail shows only the validating
255
+ task.
256
+ - New `task-widget-in-chat.spec.ts` — boot the chat route, send a
257
+ "create task X" prompt, mock the orchestrator POST to return a fixture
258
+ task, assert a `task-widget` appears, status flips to active when the
259
+ mocked poll returns active, click `task-widget-open` lands on
260
+ `/orchestrator?taskId=…` with that task selected and its inspector
261
+ visible.
262
+ - New `sensitive-request-in-chat.spec.ts` —
263
+ (a) seed a secret request, fill the password, submit, assert
264
+ `client.updateSecrets` was called with that field, assert the password
265
+ is **never** posted to any chat-message endpoint;
266
+ (b) seed an OAuth request, click Connect, mock popup `postMessage`
267
+ completion, assert status flips to Saved without any token appearing in
268
+ the chat-message stream.
269
+
270
+ 4. **Scenario runner (`packages/scenario-runner`):** add a `task-creation`
271
+ scenario that drives the e2e create → widget → click → workbench path
272
+ against the real orchestrator routes, using the fake-ACP transport already
273
+ used by the orchestrator unit tests. Gated under `test:e2e:manual` because
274
+ it needs the ACP transport pinned, matching the existing convention.
275
+
276
+ ## Out of scope (explicitly)
277
+
278
+ - Replacing the 5s poll with SSE/WS — that's
279
+ [`orchestrator-buildout-followups.md`](./orchestrator-buildout-followups.md)
280
+ section D and design-touching.
281
+ - A general "remote/mobile orchestrator" — same followups section D.
282
+ - Re-anchoring planner follow-ups to durable goal — section F.
283
+ - Real production OAuth providers beyond the widget — see followups B.
284
+
285
+ ## Rollout
286
+
287
+ One PR per slice keeps blast radius small and lets the visual review run on
288
+ each:
289
+
290
+ 1. Slice 1: `OrchestratorGlanceStrip` + counts derivation + tests.
291
+ 2. Slice 2: `task-widget` segment + `TaskWidget` component +
292
+ `TASKS_CREATE` emission + unit tests.
293
+ 3. Slice 3: `task-widget-in-chat.spec.ts`.
294
+ 4. Slice 4: Sensitive-request polish + OAuth widget + adapter +
295
+ `sensitive-request-in-chat.spec.ts`.
296
+ 5. Slice 5: View-dependent inspector cleanup (hide Edit group on terminal,
297
+ hide priority on terminal) + inspector test.
298
+
299
+ Each slice ships and gets an audit pass. No slice is "done" until the
300
+ relevant Playwright spec is green locally and the chat / `/orchestrator`
301
+ route renders cleanly under the workbench audit pass.