@workerdeck/ui 0.17.0 → 0.18.0

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 (40) hide show
  1. package/build/{SessionPanel-CnNYEX80.d.mts → SessionPanel-CnU_IJ3-.d.mts} +24 -9
  2. package/build/{SessionPanel-DMPhsNlW.mjs → SessionPanel-DPx8Iz8a.mjs} +439 -74
  3. package/build/SessionPanel-DPx8Iz8a.mjs.map +1 -0
  4. package/build/{format-DfI_je9S.d.mts → format-ljc3lKpA.d.mts} +1 -1
  5. package/build/format.d.mts +16 -5
  6. package/build/format.mjs +2 -3
  7. package/build/index.d.mts +237 -8
  8. package/build/index.mjs +399 -167
  9. package/build/index.mjs.map +1 -1
  10. package/build/{format-DqR56Y8l.mjs → status-BE-zg88x.mjs} +154 -2
  11. package/build/status-BE-zg88x.mjs.map +1 -0
  12. package/build/workspace.d.mts +4 -1
  13. package/build/workspace.mjs +4 -3
  14. package/build/workspace.mjs.map +1 -1
  15. package/package.json +4 -4
  16. package/src/components/agent/ContextRing.tsx +41 -0
  17. package/src/components/agent/EngineIcon.tsx +40 -0
  18. package/src/components/agent/SessionBrowser.tsx +115 -22
  19. package/src/components/agent/SessionPanel.tsx +153 -3
  20. package/src/components/agent/SessionSteps.tsx +233 -0
  21. package/src/components/agent/SessionWorkspace.tsx +4 -0
  22. package/src/components/agent/StatusBar.tsx +4 -2
  23. package/src/components/agent/SubagentStrip.tsx +134 -0
  24. package/src/components/agent/Transcript.tsx +138 -19
  25. package/src/components/agent/transcript-rows.ts +9 -1
  26. package/src/components/terminal/TerminalTranscript.tsx +77 -3
  27. package/src/components/terminal/affordances.tsx +34 -0
  28. package/src/components/terminal/blocks.ts +28 -0
  29. package/src/components/terminal/height.ts +24 -0
  30. package/src/components/terminal/items.tsx +1 -1
  31. package/src/components/terminal/scrubber.tsx +28 -3
  32. package/src/components/terminal/tool-run.ts +49 -5
  33. package/src/index.ts +19 -1
  34. package/src/lib/status.ts +16 -3
  35. package/src/styles/terminal.css +10 -0
  36. package/src/styles/theme.css +42 -0
  37. package/build/SessionPanel-DMPhsNlW.mjs.map +0 -1
  38. package/build/format-DqR56Y8l.mjs.map +0 -1
  39. package/build/status-Ydzi7n6j.mjs +0 -143
  40. package/build/status-Ydzi7n6j.mjs.map +0 -1
@@ -117,14 +117,58 @@ const trimmed = (value: unknown): string | undefined =>
117
117
  * emptier than a plain tool row's.
118
118
  */
119
119
  export function taskLabel(task: ToolCallItem): string {
120
+ return `${task.name}(${taskIdentity(task)})`
121
+ }
122
+
123
+ /**
124
+ * The inner half of {@link taskLabel} — `Explore · find the auth check` — without
125
+ * the `Task(…)` wrapper.
126
+ *
127
+ * Split out for the sub-agent takeover's header, which names the agent it is
128
+ * showing and has no room (or reason) to repeat the tool's own name: the whole
129
+ * surface *is* that Task. Extracted rather than re-spelled so the header and the
130
+ * row it was opened from cannot drift, and so both keep matching protocol's
131
+ * `subagentLabel`, which reads the same two input fields for the sessions list.
132
+ */
133
+ export function taskIdentity(task: ToolCallItem): string {
120
134
  const input = task.input as { description?: unknown; subagent_type?: unknown } | null
121
135
  const description = trimmed(input?.description)
122
136
  const agent = trimmed(input?.subagent_type)
123
- const inner =
124
- agent && description
125
- ? `${agent} · ${clip(description)}`
126
- : (agent ?? (description ? clip(description) : toolInputPreview(task.input)))
127
- return `${task.name}(${inner})`
137
+ return agent && description
138
+ ? `${agent} · ${clip(description)}`
139
+ : (agent ?? (description ? clip(description) : toolInputPreview(task.input)))
140
+ }
141
+
142
+ /**
143
+ * **What this agent was actually asked** — the sub-agent's brief, or undefined
144
+ * when the engine did not give us one.
145
+ *
146
+ * Measured against a live session rather than assumed: the Agent SDK's call
147
+ * carries `{description, subagent_type, run_in_background, prompt}`, and
148
+ * `prompt` is the instruction.
149
+ *
150
+ * **Whether it also arrives in the stream depends on the agent.** A foreground
151
+ * `Task` forwards its brief as a real nested `user_message` (that is what
152
+ * `forwardSubagentText` buys and what the reducer stamps a `parentToolUseId`
153
+ * on), so `subagentItems` picks it up and the frame already has it. A
154
+ * **background** agent does not: measured on a session with eight of them,
155
+ * zero items carried a `parentToolUseId` on a `user` kind. Those runs are
156
+ * exactly the ones you open a takeover on — an agent working while you read
157
+ * something else — and their brief was nowhere at all.
158
+ *
159
+ * So this is the *fallback*, not the source: the callers splice it in only when
160
+ * the frame carries no brief of its own, or the same instruction would be drawn
161
+ * twice.
162
+ *
163
+ * `description` is deliberately not a fallback: it is the 3–5 word label the
164
+ * header already prints, and repeating it as a brief would claim we know the
165
+ * instruction when we do not. **Codex genuinely has none** — its `spawn_agent`
166
+ * message is an encrypted blob on the wire — so on that engine this is
167
+ * undefined and the row is not drawn, rather than drawn empty.
168
+ */
169
+ export function taskBrief(task: ToolCallItem): string | undefined {
170
+ const input = task.input as { prompt?: unknown } | null
171
+ return trimmed(input?.prompt)
128
172
  }
129
173
 
130
174
  const callBusy = (call: ToolCallItem): boolean =>
package/src/index.ts CHANGED
@@ -180,7 +180,24 @@ export {
180
180
  export { SessionStatusIcon } from './components/agent/SessionBrowser.tsx'
181
181
  // Lifted out of the VS Code sidebar once the dashboard grew a collapsed rail
182
182
  // that needs the same glyph — two copies of a trademark set is one too many.
183
- export { EngineIcon, engineMark } from './components/agent/EngineIcon.tsx'
183
+ export {
184
+ EngineIcon,
185
+ engineMark,
186
+ vendorMarkClass,
187
+ vendorTextClass,
188
+ } from './components/agent/EngineIcon.tsx'
189
+ // Lifted out of the sidebar for the same reason: the work under a row is a
190
+ // protocol fact, so every list annotates it with one component.
191
+ export {
192
+ type Step,
193
+ StepRow,
194
+ StepToggle,
195
+ runningSteps,
196
+ sessionSteps,
197
+ } from './components/agent/SessionSteps.tsx'
198
+ // The sub-agent takeover's one line. Exported for hosts that draw their own
199
+ // panel chrome; the panel raises it itself.
200
+ export { SubagentStrip } from './components/agent/SubagentStrip.tsx'
184
201
  export { ProjectIcon } from './components/agent/ProjectIcon.tsx'
185
202
  export {
186
203
  SessionEmptyState,
@@ -188,6 +205,7 @@ export {
188
205
  } from './components/agent/SessionEmptyState.tsx'
189
206
  export { PromptTokenText } from './components/agent/PromptTokenText.tsx'
190
207
  export { STATUS_META } from './components/agent/status.ts'
208
+ export { ContextRing } from './components/agent/ContextRing.tsx'
191
209
 
192
210
  // Utilities
193
211
  export { cn } from './lib/utils.ts'
package/src/lib/status.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ContextUsage, ModelOption, RateLimitInfo, SessionStatus } from '@workerdeck/protocol'
1
+ import type { ModelOption, RateLimitInfo, SessionStatus } from '@workerdeck/protocol'
2
2
 
3
3
  /**
4
4
  * How a session's live readings become a status line — the pure half, so every
@@ -174,7 +174,20 @@ export function modelLabel(vitals: ModelReadings | undefined): string {
174
174
  }
175
175
 
176
176
  /** Context percentage as its meter severity — the reading and the colour come
177
- * from one place so a panel and a status bar never disagree. */
178
- export function contextSeverity(usage: ContextUsage | undefined): StatusSeverity {
177
+ * from one place so a panel and a status bar never disagree.
178
+ *
179
+ * Takes only the number it reads, so the compact `ContextReading` that rides
180
+ * the sessions list and the full `ContextUsage` on the event stream are coloured
181
+ * by one rule rather than two. */
182
+ export function contextSeverity(usage: { percentage: number } | undefined): StatusSeverity {
179
183
  return meterSeverity(usage?.percentage)
180
184
  }
185
+
186
+ /** {@link meterSeverity} as a text colour class. Here rather than beside each
187
+ * meter because the status bar, the context dialog and a sessions-list row all
188
+ * paint the same reading, and a fourth copy of the thresholds is a fourth
189
+ * chance for one surface to call 81% orange and another call it grey. */
190
+ export function meterColorClass(pct: number | undefined): string {
191
+ const severity = meterSeverity(pct)
192
+ return severity === 'error' ? 'text-danger' : severity === 'warning' ? 'text-warning' : 'text-fg-3'
193
+ }
@@ -1032,6 +1032,16 @@
1032
1032
  pointer-events: none;
1033
1033
  z-index: 11;
1034
1034
  }
1035
+ /* The sub-agent's brief, collapsed: clipped on the same wrapped lines
1036
+ `briefPx` counts, so the height the virtualizer reserved and the height the
1037
+ row draws cannot disagree. The count itself comes from the component
1038
+ (`BRIEF_LINES`), one number rather than one here and one there. */
1039
+ .term-brief-clip {
1040
+ display: -webkit-box;
1041
+ -webkit-box-orient: vertical;
1042
+ overflow: hidden;
1043
+ }
1044
+
1035
1045
  .term-scrubber .term-scrub-ex {
1036
1046
  display: -webkit-box;
1037
1047
  -webkit-line-clamp: 4;
@@ -51,6 +51,37 @@
51
51
  --fg-4: #a1a1a1;
52
52
  --fg-disabled: #c9c9c9;
53
53
 
54
+ /* ---------- Vendor ----------
55
+ * The engine's own colour, worn by its mark and (where the brand allows) by
56
+ * the model name beside it in a sessions list. Nowhere else.
57
+ *
58
+ * This is the one place a brand colour is allowed in the product, and it earns
59
+ * it by doing a job nothing else on the row does: the list already spends blue
60
+ * on running, amber on waiting, red on failed and muted on idle, so none of
61
+ * those can also say *whose engine this is* — and the mark alone is a 12px
62
+ * silhouette.
63
+ *
64
+ * Two values per vendor, because that glyph has to hold against both grounds:
65
+ * the dark-ground value is lifted and the light-ground one darkened.
66
+ *
67
+ * **OpenAI's is monochrome, and that is their rule rather than our taste**:
68
+ * their guidelines forbid adding colour to the mark, so it is pure white on
69
+ * dark and a near-black on light — both sanctioned, and the only kind of pair
70
+ * legible on both grounds. A green was tried first and is wrong, however well
71
+ * it reads. Near-black rather than `#000` because pure black would sit *harder*
72
+ * than the session title above it, so the mark would out-weigh the name it is
73
+ * labelling.
74
+ *
75
+ * That makes the two vendors asymmetric, which is the honest outcome: coral is
76
+ * Anthropic's brand and monochrome is OpenAI's, so a rule that gave both an
77
+ * accent would misrepresent one of them. It also means OpenAI's token is at
78
+ * full contrast, which is why only the **mark** wears it — see
79
+ * `vendorTextClass` in `EngineIcon.tsx`.
80
+ *
81
+ * A third vendor is two declarations here and one map entry there. */
82
+ --vendor-claude: #b3573a;
83
+ --vendor-openai: #373737;
84
+
54
85
  /* ---------- Accent ----------
55
86
  * VS Code blue (`#0078d4`) — the product's primary. It is the *same* hue in
56
87
  * both themes rather than the inverse of the canvas: a primary that flips
@@ -122,6 +153,10 @@
122
153
  --fg-4: #6e6e6e;
123
154
  --fg-disabled: #454545;
124
155
 
156
+ /* ---------- Vendor ---------- (see the light block for the whole argument) */
157
+ --vendor-claude: #cc7c5e;
158
+ --vendor-openai: #ffffff;
159
+
125
160
  /* ---------- Accent ----------
126
161
  * The same blue as light. `--accent` stays `#0078d4` because it is a *fill*
127
162
  * carrying white text (4.6:1) — the brighter `#3794ff` would drop that to
@@ -275,6 +310,13 @@
275
310
  --color-info: var(--info);
276
311
  --color-info-bg: var(--info-bg);
277
312
 
313
+ /* Vendor colours as real utilities (`text-vendor-claude`), not hand-written
314
+ classes: `cn`'s tailwind-merge only knows to *replace* a colour it can parse,
315
+ and `EngineIcon` ships its own `text-fg-3` that the vendor class has to win
316
+ against rather than merely follow in source order. */
317
+ --color-vendor-claude: var(--vendor-claude);
318
+ --color-vendor-openai: var(--vendor-openai);
319
+
278
320
  /* Stock-name bridge → utilities (bg-primary / text-muted-foreground / ...). */
279
321
  --color-primary: var(--primary);
280
322
  --color-primary-foreground: var(--primary-foreground);