@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.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/docs/SUBAGENT_FLOW_AND_PARITY.md +138 -0
- package/docs/default-eliza-skills-and-agent-bridge-plan.md +225 -0
- package/docs/economics-goal-runbook.md +140 -0
- package/docs/multi-account-orchestration.md +76 -0
- package/docs/orchestrator-buildout-followups.md +124 -0
- package/docs/orchestrator-dashboard-task-widget-secrets-assessment.md +174 -0
- package/docs/orchestrator-dashboard-task-widget-secrets-design.md +301 -0
- package/docs/research/orchestrator-view-research-report.md +273 -0
- package/docs/sub-agent-routing.md +227 -0
- package/package.json +197 -38
- package/dist/index.d.ts +0 -21
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -21120
- package/dist/index.js.map +0 -109
- package/dist/src/actions/messaging.d.ts +0 -24
- package/dist/src/actions/messaging.d.ts.map +0 -1
- package/dist/src/actions/peek-subagent.d.ts +0 -3
- package/dist/src/actions/peek-subagent.d.ts.map +0 -1
- package/dist/src/actions/subagent-management.d.ts +0 -7
- package/dist/src/actions/subagent-management.d.ts.map +0 -1
- package/dist/src/actions/task-management.d.ts +0 -9
- package/dist/src/actions/task-management.d.ts.map +0 -1
- package/dist/src/config.d.ts +0 -4
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/providers/orchestrator-config.d.ts +0 -80
- package/dist/src/providers/orchestrator-config.d.ts.map +0 -1
- package/dist/src/providers/task-context.d.ts +0 -3
- package/dist/src/providers/task-context.d.ts.map +0 -1
- package/dist/src/services/agent-orchestrator-service.d.ts +0 -59
- package/dist/src/services/agent-orchestrator-service.d.ts.map +0 -1
- package/dist/src/services/messaging-service.d.ts +0 -111
- package/dist/src/services/messaging-service.d.ts.map +0 -1
- package/dist/src/services/sandbox-service.d.ts +0 -103
- package/dist/src/services/sandbox-service.d.ts.map +0 -1
- package/dist/src/services/subagent-service.d.ts +0 -140
- package/dist/src/services/subagent-service.d.ts.map +0 -1
- package/dist/src/sub-agents/adapter.d.ts +0 -13
- package/dist/src/sub-agents/adapter.d.ts.map +0 -1
- package/dist/src/sub-agents/claude-agent-sdk-sub-agent.d.ts +0 -18
- package/dist/src/sub-agents/claude-agent-sdk-sub-agent.d.ts.map +0 -1
- package/dist/src/sub-agents/codex-sdk-sub-agent.d.ts +0 -18
- package/dist/src/sub-agents/codex-sdk-sub-agent.d.ts.map +0 -1
- package/dist/src/sub-agents/eliza-sub-agent.d.ts +0 -27
- package/dist/src/sub-agents/eliza-sub-agent.d.ts.map +0 -1
- package/dist/src/sub-agents/elizaos-native-sub-agent.d.ts +0 -61
- package/dist/src/sub-agents/elizaos-native-sub-agent.d.ts.map +0 -1
- package/dist/src/sub-agents/index.d.ts +0 -10
- package/dist/src/sub-agents/index.d.ts.map +0 -1
- package/dist/src/sub-agents/opencode-sub-agent.d.ts +0 -44
- package/dist/src/sub-agents/opencode-sub-agent.d.ts.map +0 -1
- package/dist/src/sub-agents/registry.d.ts +0 -3
- package/dist/src/sub-agents/registry.d.ts.map +0 -1
- package/dist/src/sub-agents/sweagent-sub-agent.d.ts +0 -19
- package/dist/src/sub-agents/sweagent-sub-agent.d.ts.map +0 -1
- package/dist/src/sub-agents/tools.d.ts +0 -15
- package/dist/src/sub-agents/tools.d.ts.map +0 -1
- package/dist/src/sub-agents/types.d.ts +0 -170
- package/dist/src/sub-agents/types.d.ts.map +0 -1
- package/dist/src/types/index.d.ts +0 -12
- package/dist/src/types/index.d.ts.map +0 -1
- package/dist/src/types/messaging.d.ts +0 -202
- package/dist/src/types/messaging.d.ts.map +0 -1
- package/dist/src/types/sandbox.d.ts +0 -228
- package/dist/src/types/sandbox.d.ts.map +0 -1
- package/dist/src/types/subagent.d.ts +0 -232
- package/dist/src/types/subagent.d.ts.map +0 -1
- package/dist/src/types.d.ts +0 -138
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/utils/index.d.ts +0 -7
- package/dist/src/utils/index.d.ts.map +0 -1
- package/dist/src/utils/session.d.ts +0 -184
- 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.
|