@agimon-ai/po-team 0.0.0-alpha.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 (110) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +156 -0
  3. package/dist/agentDiscovery-Whwzc4dP.cjs +1637 -0
  4. package/dist/agentDiscovery-Whwzc4dP.cjs.map +1 -0
  5. package/dist/agentDiscovery-ZQlFkKvB.mjs +1529 -0
  6. package/dist/agentDiscovery-ZQlFkKvB.mjs.map +1 -0
  7. package/dist/apiContracts.cjs +162 -0
  8. package/dist/apiContracts.cjs.map +1 -0
  9. package/dist/apiContracts.d.mts +53 -0
  10. package/dist/apiContracts.d.mts.map +1 -0
  11. package/dist/apiContracts.mjs +157 -0
  12. package/dist/apiContracts.mjs.map +1 -0
  13. package/dist/atomicJson-D0346bt5.cjs +178 -0
  14. package/dist/atomicJson-D0346bt5.cjs.map +1 -0
  15. package/dist/atomicJson-D2t0CDh9.mjs +137 -0
  16. package/dist/atomicJson-D2t0CDh9.mjs.map +1 -0
  17. package/dist/capabilityCeiling-C-Y7H8oC.cjs +191 -0
  18. package/dist/capabilityCeiling-C-Y7H8oC.cjs.map +1 -0
  19. package/dist/capabilityCeiling-CF7kAS18.mjs +156 -0
  20. package/dist/capabilityCeiling-CF7kAS18.mjs.map +1 -0
  21. package/dist/capabilityCeiling.cjs +10 -0
  22. package/dist/capabilityCeiling.d.mts +46 -0
  23. package/dist/capabilityCeiling.d.mts.map +1 -0
  24. package/dist/capabilityCeiling.mjs +3 -0
  25. package/dist/delegation.cjs +49 -0
  26. package/dist/delegation.cjs.map +1 -0
  27. package/dist/delegation.d.mts +269 -0
  28. package/dist/delegation.d.mts.map +1 -0
  29. package/dist/delegation.mjs +34 -0
  30. package/dist/delegation.mjs.map +1 -0
  31. package/dist/delegationApi.cjs +92 -0
  32. package/dist/delegationApi.d.mts +2 -0
  33. package/dist/delegationApi.mjs +2 -0
  34. package/dist/env.cjs +62 -0
  35. package/dist/env.d.mts +2 -0
  36. package/dist/env.mjs +2 -0
  37. package/dist/environment-CaCW3SgT.cjs +320 -0
  38. package/dist/environment-CaCW3SgT.cjs.map +1 -0
  39. package/dist/environment-DrqhrxG_.mjs +75 -0
  40. package/dist/environment-DrqhrxG_.mjs.map +1 -0
  41. package/dist/environment-ma5oQ7uM.d.mts +63 -0
  42. package/dist/environment-ma5oQ7uM.d.mts.map +1 -0
  43. package/dist/extensions/server.cjs +6940 -0
  44. package/dist/extensions/server.cjs.map +1 -0
  45. package/dist/extensions/server.d.mts +5 -0
  46. package/dist/extensions/server.d.mts.map +1 -0
  47. package/dist/extensions/server.mjs +6932 -0
  48. package/dist/extensions/server.mjs.map +1 -0
  49. package/dist/extensions/web.mjs +2308 -0
  50. package/dist/extensions/web.mjs.map +1 -0
  51. package/dist/externalProcessIpc-C-15lJrB.mjs +584 -0
  52. package/dist/externalProcessIpc-C-15lJrB.mjs.map +1 -0
  53. package/dist/externalProcessIpc-ojE0pvw0.cjs +690 -0
  54. package/dist/externalProcessIpc-ojE0pvw0.cjs.map +1 -0
  55. package/dist/fablePlan.cjs +17 -0
  56. package/dist/fablePlan.d.mts +2 -0
  57. package/dist/fablePlan.mjs +2 -0
  58. package/dist/fablePlanApi-BsHhgVFV.mjs +118 -0
  59. package/dist/fablePlanApi-BsHhgVFV.mjs.map +1 -0
  60. package/dist/fablePlanApi-CZ3uUcMi.d.mts +97 -0
  61. package/dist/fablePlanApi-CZ3uUcMi.d.mts.map +1 -0
  62. package/dist/fablePlanApi-wBu9bRXU.cjs +207 -0
  63. package/dist/fablePlanApi-wBu9bRXU.cjs.map +1 -0
  64. package/dist/fablePlanApi.cjs +17 -0
  65. package/dist/fablePlanApi.d.mts +2 -0
  66. package/dist/fablePlanApi.mjs +2 -0
  67. package/dist/index.cjs +49 -0
  68. package/dist/index.cjs.map +1 -0
  69. package/dist/index.d.mts +160 -0
  70. package/dist/index.d.mts.map +1 -0
  71. package/dist/index.mjs +45 -0
  72. package/dist/index.mjs.map +1 -0
  73. package/dist/runs/background/cliRunnerEntry.cjs +539 -0
  74. package/dist/runs/background/cliRunnerEntry.cjs.map +1 -0
  75. package/dist/runs/background/cliRunnerEntry.d.mts +1 -0
  76. package/dist/runs/background/cliRunnerEntry.mjs +537 -0
  77. package/dist/runs/background/cliRunnerEntry.mjs.map +1 -0
  78. package/dist/runtimeSubagentPolicy.cjs +5 -0
  79. package/dist/runtimeSubagentPolicy.d.mts +2 -0
  80. package/dist/runtimeSubagentPolicy.mjs +2 -0
  81. package/dist/runtimeSubagentTool.cjs +11 -0
  82. package/dist/runtimeSubagentTool.d.mts +2 -0
  83. package/dist/runtimeSubagentTool.mjs +2 -0
  84. package/dist/subagentPolicy-ALJam4GN.d.mts +33 -0
  85. package/dist/subagentPolicy-ALJam4GN.d.mts.map +1 -0
  86. package/dist/subagentPolicy-DQRyKDH-.cjs +39 -0
  87. package/dist/subagentPolicy-DQRyKDH-.cjs.map +1 -0
  88. package/dist/subagentPolicy-DxjsKeyj.mjs +22 -0
  89. package/dist/subagentPolicy-DxjsKeyj.mjs.map +1 -0
  90. package/dist/subagentPolicy.cjs +5 -0
  91. package/dist/subagentPolicy.d.mts +2 -0
  92. package/dist/subagentPolicy.mjs +2 -0
  93. package/dist/subagentTool-BTssGLw8.d.mts +103 -0
  94. package/dist/subagentTool-BTssGLw8.d.mts.map +1 -0
  95. package/dist/subagentTool-Ca2zQXKw.cjs +227 -0
  96. package/dist/subagentTool-Ca2zQXKw.cjs.map +1 -0
  97. package/dist/subagentTool-WOYrjxgT.mjs +186 -0
  98. package/dist/subagentTool-WOYrjxgT.mjs.map +1 -0
  99. package/dist/subagentTool.cjs +10 -0
  100. package/dist/subagentTool.d.mts +2 -0
  101. package/dist/subagentTool.mjs +2 -0
  102. package/dist/teamSnapshot-DgaANhBs.mjs +24 -0
  103. package/dist/teamSnapshot-DgaANhBs.mjs.map +1 -0
  104. package/dist/teamSnapshot-oKuGFdYc.cjs +35 -0
  105. package/dist/teamSnapshot-oKuGFdYc.cjs.map +1 -0
  106. package/dist/teamSnapshot.cjs +4 -0
  107. package/dist/teamSnapshot.d.mts +59 -0
  108. package/dist/teamSnapshot.d.mts.map +1 -0
  109. package/dist/teamSnapshot.mjs +2 -0
  110. package/package.json +201 -0
@@ -0,0 +1,2308 @@
1
+ import { defineActivityGroup, defineChannelFile, defineContextAction, defineFill, defineLeaderBinding, defineSessionStore, defineSlot, defineSlotFile, defineToolRenderer, defineWebPlugin, pluginApiUrl, sessionApiAddress } from "@agimon-ai/po-core/web";
2
+ import { ArrowRightIcon, Badge, Button, CloseIcon, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Dot, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, EmptyState, Input, Kbd, KebabIcon, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, Panel, STATUS_EDGE, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle, StatusBadge, Textarea, UsersIcon, cn, toolTone } from "@agimon-ai/po-web-components";
3
+ import { useStore } from "@tanstack/react-store";
4
+ import { useEffect, useState } from "react";
5
+ import { sealedTransport } from "@agimon-ai/po-web-security/browser";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region src/types/webSubagents.ts
8
+ const SUBAGENT_RUNS_TYPE = "subagent_runs";
9
+ const SUBAGENT_CATALOG_TYPE = "subagent_catalog";
10
+ //#endregion
11
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/catalog.ts
12
+ /** The team API's address on a session, from the one builder every package API URL shares. */
13
+ function teamApiUrl(sessionId, path) {
14
+ return pluginApiUrl(sessionApiAddress(sessionId), "team", path);
15
+ }
16
+ function isRecord$3(value) {
17
+ return typeof value === "object" && value !== null && !Array.isArray(value);
18
+ }
19
+ function isStrings(value) {
20
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
21
+ }
22
+ function isAgent(value) {
23
+ return isRecord$3(value) && typeof value.name === "string" && typeof value.description === "string" && typeof value.filePath === "string" && [
24
+ "project",
25
+ "user",
26
+ "plugin"
27
+ ].includes(String(value.source)) && (value.packageName === void 0 || typeof value.packageName === "string") && (value.model === void 0 || typeof value.model === "string") && isStrings(value.fallbackModels) && isStrings(value.tools) && isStrings(value.skills) && isStrings(value.extensions) && (value.defaultContext === "fresh" || value.defaultContext === "fork");
28
+ }
29
+ /** Read the selected session's catalog through the host's authenticated HTTP transport. */
30
+ async function fetchCatalog(sessionId, signal) {
31
+ const response = await sealedTransport.fetch(teamApiUrl(sessionId, "/catalog"), {
32
+ signal,
33
+ cache: "no-store"
34
+ });
35
+ const body = await response.json();
36
+ if (!response.ok) throw new Error(isRecord$3(body) && typeof body.error === "string" ? body.error : `Agent catalog could not be loaded (${response.status}).`);
37
+ if (!isRecord$3(body) || typeof body.cwd !== "string" || !Array.isArray(body.agents) || !body.agents.every(isAgent) || !isStrings(body.models)) throw new Error("The server returned an invalid agent catalog.");
38
+ return {
39
+ cwd: body.cwd,
40
+ agents: body.agents,
41
+ models: body.models
42
+ };
43
+ }
44
+ /** Launch on the selected session host, without submitting a parent prompt. */
45
+ async function launchAgent(sessionId, request) {
46
+ const response = await sealedTransport.fetch(teamApiUrl(sessionId, "/run"), {
47
+ method: "POST",
48
+ headers: { "content-type": "application/json" },
49
+ body: JSON.stringify(request)
50
+ });
51
+ const body = await response.json();
52
+ if (!response.ok) throw new Error(isRecord$3(body) && typeof body.error === "string" ? body.error : `Agent could not be launched (${response.status}).`);
53
+ if (!isRecord$3(body) || typeof body.runId !== "string" || body.runId === "") throw new Error("The server returned an invalid run ID.");
54
+ return body.runId;
55
+ }
56
+ function isSteerState(value) {
57
+ return value === "delivered" || value === "failed" || value === "pending";
58
+ }
59
+ /** Steer one run on the selected session host, without submitting a parent prompt. */
60
+ async function steerRun(sessionId, runId, message) {
61
+ const response = await sealedTransport.fetch(teamApiUrl(sessionId, "/steer"), {
62
+ method: "POST",
63
+ headers: { "content-type": "application/json" },
64
+ body: JSON.stringify({
65
+ runId,
66
+ message
67
+ })
68
+ });
69
+ const body = await response.json();
70
+ if (!response.ok) throw new Error(isRecord$3(body) && typeof body.error === "string" ? body.error : `Guidance could not be sent (${response.status}).`);
71
+ if (!isRecord$3(body) || !isSteerState(body.state) || typeof body.message !== "string") throw new Error("The server returned an invalid steer result.");
72
+ return {
73
+ state: body.state,
74
+ message: body.message
75
+ };
76
+ }
77
+ /** Ask the selected session host to stop one run; the run's own status reports when it has. */
78
+ async function stopRun(sessionId, runId) {
79
+ const response = await sealedTransport.fetch(teamApiUrl(sessionId, "/stop"), {
80
+ method: "POST",
81
+ headers: { "content-type": "application/json" },
82
+ body: JSON.stringify({ runId })
83
+ });
84
+ const body = await response.json();
85
+ if (!response.ok) throw new Error(isRecord$3(body) && typeof body.error === "string" ? body.error : `The run could not be stopped (${response.status}).`);
86
+ }
87
+ //#endregion
88
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/catalogStore.ts
89
+ const catalog = defineSessionStore({
90
+ cwd: "",
91
+ agents: [],
92
+ models: [],
93
+ warning: void 0,
94
+ loading: false,
95
+ requestId: 0,
96
+ open: false,
97
+ filter: "",
98
+ task: "",
99
+ selected: void 0,
100
+ inspected: void 0,
101
+ launch: void 0
102
+ });
103
+ function isRecord$2(value) {
104
+ return typeof value === "object" && value !== null && !Array.isArray(value);
105
+ }
106
+ function openCatalog(sessionId, task = "") {
107
+ catalog.update(sessionId, (current) => ({
108
+ ...current,
109
+ open: true,
110
+ task
111
+ }));
112
+ }
113
+ /** Opens the Team picker with context supplied by another installed plugin. */
114
+ function openAgentCatalogForContext(context, tab) {
115
+ if (context.sessionId === null) return;
116
+ openCatalog(context.sessionId, context.item.content);
117
+ context.openTransientTab(tab());
118
+ }
119
+ function closeCatalog(sessionId) {
120
+ catalog.update(sessionId, (current) => ({
121
+ ...current,
122
+ open: false,
123
+ task: "",
124
+ launch: void 0
125
+ }));
126
+ }
127
+ function setCatalogFilter(sessionId, filter) {
128
+ catalog.update(sessionId, (current) => ({
129
+ ...current,
130
+ filter
131
+ }));
132
+ }
133
+ function selectAgent(sessionId, name) {
134
+ catalog.update(sessionId, (current) => ({
135
+ ...current,
136
+ selected: name
137
+ }));
138
+ }
139
+ function toggleInspect(sessionId, name) {
140
+ catalog.update(sessionId, (current) => ({
141
+ ...current,
142
+ inspected: current.inspected === name ? void 0 : name
143
+ }));
144
+ }
145
+ function openLaunch(sessionId, agent, fork) {
146
+ catalog.update(sessionId, (current) => ({
147
+ ...current,
148
+ selected: agent,
149
+ launch: {
150
+ agent,
151
+ fork
152
+ }
153
+ }));
154
+ }
155
+ function closeLaunch(sessionId) {
156
+ catalog.update(sessionId, (current) => ({
157
+ ...current,
158
+ launch: void 0
159
+ }));
160
+ }
161
+ /** Each drawer opening owns one request and cancels it when the drawer leaves. */
162
+ function loadCatalog(sessionId) {
163
+ const controller = new AbortController();
164
+ const requestId = catalog.select(catalog.store.state, sessionId).requestId + 1;
165
+ catalog.update(sessionId, (current) => ({
166
+ ...current,
167
+ requestId,
168
+ loading: true,
169
+ warning: void 0
170
+ }));
171
+ const isCurrent = () => !controller.signal.aborted && catalog.store.state[sessionId]?.requestId === requestId;
172
+ fetchCatalog(sessionId, controller.signal).then((payload) => {
173
+ if (isCurrent()) subagentCatalogChannel.apply(sessionId, payload);
174
+ }).catch((error) => {
175
+ if (isCurrent()) catalog.update(sessionId, (current) => ({
176
+ ...current,
177
+ warning: error instanceof Error ? error.message : String(error)
178
+ }));
179
+ }).finally(() => {
180
+ if (isCurrent()) catalog.update(sessionId, (current) => ({
181
+ ...current,
182
+ loading: false
183
+ }));
184
+ });
185
+ return () => {
186
+ if (isCurrent()) catalog.update(sessionId, (current) => ({
187
+ ...current,
188
+ loading: false
189
+ }));
190
+ controller.abort();
191
+ };
192
+ }
193
+ /** The plugin's catalog channel: 'subagent_catalog' payloads into the store; the drawer's own state survives a refresh. */
194
+ const subagentCatalogChannel = catalog.channel({
195
+ channel: SUBAGENT_CATALOG_TYPE,
196
+ parse(input) {
197
+ if (!isRecord$2(input) || typeof input.cwd !== "string" || !Array.isArray(input.agents)) return null;
198
+ const models = Array.isArray(input.models) ? input.models.filter((m) => typeof m === "string") : [];
199
+ return {
200
+ cwd: input.cwd,
201
+ agents: input.agents.filter(isRecord$2),
202
+ models,
203
+ ...typeof input.warning === "string" ? { warning: input.warning } : {}
204
+ };
205
+ },
206
+ reduce(current, payload) {
207
+ const names = new Set(payload.agents.map((agent) => agent.name));
208
+ return {
209
+ ...current,
210
+ cwd: payload.cwd,
211
+ agents: payload.agents,
212
+ models: payload.models,
213
+ warning: payload.warning,
214
+ selected: current.selected !== void 0 && names.has(current.selected) ? current.selected : void 0,
215
+ inspected: current.inspected !== void 0 && names.has(current.inspected) ? current.inspected : void 0,
216
+ launch: current.launch !== void 0 && names.has(current.launch.agent) ? current.launch : void 0
217
+ };
218
+ }
219
+ });
220
+ //#endregion
221
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/format.ts
222
+ /**
223
+ * Local copies of the host cockpit's pure formatters
224
+ * (po-web src/web/lib/sessionSummary.ts): this plugin ships zero host
225
+ * imports so the cockpit's bundler can compile it from any install.
226
+ */
227
+ const MINUTE_MS = 6e4;
228
+ const HOUR_MS = 60 * MINUTE_MS;
229
+ function formatRunDuration(elapsedMs) {
230
+ if (elapsedMs < MINUTE_MS) return "<1m";
231
+ if (elapsedMs < HOUR_MS) return `${Math.floor(elapsedMs / MINUTE_MS)}m`;
232
+ const hours = Math.floor(elapsedMs / HOUR_MS);
233
+ const minutes = Math.floor(elapsedMs % HOUR_MS / MINUTE_MS);
234
+ return `${hours}h ${String(minutes).padStart(2, "0")}m`;
235
+ }
236
+ function abbreviateCwd(cwd) {
237
+ const match = /^\/(?:Users|home)\/[^/]+/u.exec(cwd);
238
+ if (!match) return cwd;
239
+ return `~${cwd.slice(match[0].length)}`;
240
+ }
241
+ /**
242
+ * What to call a run on screen.
243
+ *
244
+ * Prefers the generated identity, because a fan-out of one agent otherwise
245
+ * renders as N rows reading the same word. Falls back to the agent name for a
246
+ * run projected before identities existed. Duplicated rather than imported
247
+ * from `services/agentIdentity`, which reaches for `node:fs` and so cannot be
248
+ * pulled into the browser bundle; that module owns minting, this owns display.
249
+ */
250
+ function agentRunLabel(run) {
251
+ const base = run.identity ?? run.agent;
252
+ return run.inline ? `${base} (inline)` : base;
253
+ }
254
+ //#endregion
255
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/runActionsSlot.ts
256
+ /**
257
+ * The slot the run detail sheet opens beside its stop control: an independent
258
+ * plugin may put its own action for the shown run there, without this
259
+ * package knowing it exists. Fills receive the panel's own props.
260
+ */
261
+ const RUN_ACTIONS_SLOT = defineSlot({ slot: "subagents.run-actions" });
262
+ //#endregion
263
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/subagentsStore.ts
264
+ const TERMINAL_STATES = /* @__PURE__ */ new Set([
265
+ "done",
266
+ "failed",
267
+ "stopped"
268
+ ]);
269
+ function isTerminalRun(run) {
270
+ return TERMINAL_STATES.has(run.state);
271
+ }
272
+ const subagents = defineSessionStore({
273
+ runs: [],
274
+ dismissed: [],
275
+ stopRequested: [],
276
+ openRunId: void 0,
277
+ pendingLaunch: void 0,
278
+ autoOpenRunId: void 0
279
+ });
280
+ function isRecord$1(value) {
281
+ return typeof value === "object" && value !== null && !Array.isArray(value);
282
+ }
283
+ function without(list, runId) {
284
+ return list.filter((id) => id !== runId);
285
+ }
286
+ /** The full Subagents tab's runs minus the ones the reader dismissed. */
287
+ function visibleRuns(session) {
288
+ return session.dismissed.length === 0 ? session.runs : session.runs.filter((run) => !session.dismissed.includes(run.runId));
289
+ }
290
+ /** Only runs that can still be doing work belong in the activity group. */
291
+ function activityRuns(session) {
292
+ return visibleRuns(session).filter((run) => run.state === "queued" || run.state === "running");
293
+ }
294
+ /**
295
+ * Asks the session host to stop a run over the Team API, never as a parent
296
+ * prompt: the web host does not know Team's slash commands and refuses
297
+ * prompts mid-turn. Marked at once so the control reads "stopping"; rolled
298
+ * back and rethrown if the host refuses. Once accepted, the request stands
299
+ * until the run reports a final state.
300
+ */
301
+ async function requestRunStop(sessionId, runId) {
302
+ subagents.update(sessionId, (current) => ({
303
+ ...current,
304
+ stopRequested: [...without(current.stopRequested, runId), runId]
305
+ }));
306
+ try {
307
+ await stopRun(sessionId, runId);
308
+ } catch (error) {
309
+ subagents.update(sessionId, (current) => ({
310
+ ...current,
311
+ stopRequested: without(current.stopRequested, runId)
312
+ }));
313
+ throw error;
314
+ }
315
+ }
316
+ /** Hides a finished run from this page; nothing is deleted. */
317
+ function dismissRun(sessionId, runId) {
318
+ subagents.update(sessionId, (current) => ({
319
+ ...current,
320
+ dismissed: [...without(current.dismissed, runId), runId],
321
+ openRunId: current.openRunId === runId ? void 0 : current.openRunId
322
+ }));
323
+ }
324
+ /** Remember the fleet before the server launch so its first update can open the new run. */
325
+ function requestLaunch(sessionId, agent) {
326
+ subagents.update(sessionId, (current) => ({
327
+ ...current,
328
+ pendingLaunch: {
329
+ agent,
330
+ knownRunIds: current.runs.map((run) => run.runId)
331
+ }
332
+ }));
333
+ }
334
+ function clearPendingLaunch(sessionId) {
335
+ subagents.update(sessionId, (current) => ({
336
+ ...current,
337
+ pendingLaunch: void 0
338
+ }));
339
+ }
340
+ function clearAutoOpen(sessionId) {
341
+ subagents.update(sessionId, (current) => ({
342
+ ...current,
343
+ autoOpenRunId: void 0
344
+ }));
345
+ }
346
+ function openRun(sessionId, runId) {
347
+ subagents.update(sessionId, (current) => ({
348
+ ...current,
349
+ openRunId: runId
350
+ }));
351
+ }
352
+ /** The plugin's session data channel: 'subagent_runs' payloads into the store. */
353
+ const subagentRunsChannel = subagents.channel({
354
+ channel: SUBAGENT_RUNS_TYPE,
355
+ parse(input) {
356
+ if (!isRecord$1(input) || !Array.isArray(input.runs)) return null;
357
+ return { runs: input.runs.filter(isRecord$1) };
358
+ },
359
+ reduce(current, { runs }) {
360
+ const present = new Map(runs.map((run) => [run.runId, run]));
361
+ const stopRequested = current.stopRequested.filter((runId) => {
362
+ const run = present.get(runId);
363
+ return run !== void 0 && !isTerminalRun(run);
364
+ });
365
+ const dismissed = current.dismissed.filter((runId) => present.has(runId));
366
+ const arrived = current.pendingLaunch === void 0 ? void 0 : runs.find((run) => run.agent === current.pendingLaunch?.agent && !current.pendingLaunch.knownRunIds.includes(run.runId));
367
+ return {
368
+ ...current,
369
+ runs,
370
+ stopRequested,
371
+ dismissed,
372
+ pendingLaunch: arrived === void 0 ? current.pendingLaunch : void 0,
373
+ autoOpenRunId: arrived === void 0 ? current.autoOpenRunId : arrived.runId
374
+ };
375
+ }
376
+ });
377
+ //#endregion
378
+ //#region src/extensions/workspaces/sessions/(frontend)/_lib/launchCommand.ts
379
+ const META_TOOL_LIMIT = 3;
380
+ /** Rows whose name, source, package, description or a tool contains the filter, case-insensitively. */
381
+ function filterCatalog(agents, filter) {
382
+ const needle = filter.trim().toLowerCase();
383
+ if (needle === "") return [...agents];
384
+ return agents.filter((agent) => [
385
+ agent.name,
386
+ agent.source,
387
+ agent.packageName ?? "",
388
+ agent.description,
389
+ ...agent.tools
390
+ ].some((field) => field.toLowerCase().includes(needle)));
391
+ }
392
+ const SECTION_LABEL = {
393
+ project: "PROJECT",
394
+ user: "USER",
395
+ plugin: "PACKAGES"
396
+ };
397
+ /** The catalog's sections, nearest source first, keeping the payload's order inside each; empty ones are left out. */
398
+ function groupCatalog(agents) {
399
+ const sections = [];
400
+ for (const source of [
401
+ "project",
402
+ "user",
403
+ "plugin"
404
+ ]) {
405
+ const members = agents.filter((agent) => agent.source === source);
406
+ if (members.length > 0) sections.push({
407
+ source,
408
+ label: SECTION_LABEL[source],
409
+ agents: members
410
+ });
411
+ }
412
+ return sections;
413
+ }
414
+ /** What the picker offers: the agent's own model and fallbacks first, then the team's, without repeats. */
415
+ function modelChoices(agent, models) {
416
+ const seen = /* @__PURE__ */ new Set();
417
+ const choices = [];
418
+ for (const model of [
419
+ agent.model ?? "",
420
+ ...agent.fallbackModels,
421
+ ...models
422
+ ]) {
423
+ if (model === "" || seen.has(model)) continue;
424
+ seen.add(model);
425
+ choices.push(model);
426
+ }
427
+ return choices;
428
+ }
429
+ /** The row's one-liner: what tools it may use, how many skills, and how it starts. */
430
+ function agentMeta(agent) {
431
+ return `${agent.tools.length === 0 ? "all tools" : `tools ${agent.tools.slice(0, META_TOOL_LIMIT).join(", ")}${agent.tools.length > META_TOOL_LIMIT ? ` +${agent.tools.length - META_TOOL_LIMIT}` : ""}`}${agent.skills.length > 0 ? ` · ${agent.skills.length} skill${agent.skills.length === 1 ? "" : "s"}` : ""} · ${agent.defaultContext === "fork" ? "forks the session" : "fresh context"}`;
432
+ }
433
+ //#endregion
434
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AgentCatalogDrawer.tsx
435
+ const INPUT_TAG = "INPUT";
436
+ function shortModel(model) {
437
+ return model.split("/").pop() ?? model;
438
+ }
439
+ /** The unfolded row: what the definition names, read-only, the way the TUI catalog's tabs show it. */
440
+ function Resources({ agent }) {
441
+ const rows = [
442
+ ["tools", agent.tools.length > 0 ? agent.tools.join(", ") : "every tool the session has"],
443
+ ["skills", agent.skills.length > 0 ? agent.skills.join(", ") : "none named"],
444
+ ["extensions", agent.extensions.length > 0 ? agent.extensions.join(", ") : "none named"],
445
+ ["model", agent.model ?? "runtime default"],
446
+ ["file", agent.filePath]
447
+ ];
448
+ return /* @__PURE__ */ jsx("dl", {
449
+ "data-testid": `catalog-resources-${agent.name}`,
450
+ className: "flex flex-col gap-1 pt-1 text-sm",
451
+ children: rows.map(([label, value]) => /* @__PURE__ */ jsxs("div", {
452
+ className: "flex gap-2",
453
+ children: [/* @__PURE__ */ jsx("dt", {
454
+ className: "w-16 shrink-0 text-doom-dim",
455
+ children: label
456
+ }), /* @__PURE__ */ jsx("dd", {
457
+ className: "min-w-0 break-words text-doom-dim",
458
+ children: value
459
+ })]
460
+ }, label))
461
+ });
462
+ }
463
+ function AgentRow({ agent, selected, inspected, onSelect, onLaunch, onInspect }) {
464
+ return /* @__PURE__ */ jsxs("div", {
465
+ role: "option",
466
+ "aria-selected": selected,
467
+ tabIndex: -1,
468
+ "data-testid": `catalog-agent-${agent.name}`,
469
+ "data-selected": selected,
470
+ onClick: onSelect,
471
+ onDoubleClick: () => onLaunch(false),
472
+ className: `flex cursor-pointer flex-col gap-1 border-l-2 px-4 py-2 ${selected ? "border-doom-blue bg-doom-panel" : "border-transparent hover:bg-doom-panel/60"}`,
473
+ children: [
474
+ /* @__PURE__ */ jsxs("div", {
475
+ className: "flex min-w-0 items-center gap-2",
476
+ children: [
477
+ /* @__PURE__ */ jsx("span", {
478
+ className: "truncate text-base font-semibold text-doom-hi",
479
+ children: agent.name
480
+ }),
481
+ /* @__PURE__ */ jsx(Badge, {
482
+ size: "xs",
483
+ className: "shrink-0",
484
+ children: agent.packageName ?? agent.source
485
+ }),
486
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
487
+ /* @__PURE__ */ jsx("span", {
488
+ className: `shrink-0 text-sm ${agent.model ? "text-doom-cyan" : "text-doom-dim"}`,
489
+ children: agent.model ? shortModel(agent.model) : "agent default"
490
+ })
491
+ ]
492
+ }),
493
+ /* @__PURE__ */ jsx("span", {
494
+ className: "truncate text-sm text-doom-dim",
495
+ children: agent.description
496
+ }),
497
+ /* @__PURE__ */ jsx("span", {
498
+ className: "truncate text-sm text-doom-dim",
499
+ children: agentMeta(agent)
500
+ }),
501
+ selected ? /* @__PURE__ */ jsxs("div", {
502
+ className: "flex items-center gap-3 pt-0.5",
503
+ children: [
504
+ /* @__PURE__ */ jsxs(Button, {
505
+ variant: "link",
506
+ size: "xs",
507
+ "data-testid": `catalog-launch-${agent.name}`,
508
+ className: "px-0",
509
+ onClick: (event) => {
510
+ event.stopPropagation();
511
+ onLaunch(false);
512
+ },
513
+ children: ["launch ", /* @__PURE__ */ jsx(Kbd, { children: "↵" })]
514
+ }),
515
+ /* @__PURE__ */ jsxs(Button, {
516
+ variant: "link",
517
+ size: "xs",
518
+ "data-testid": `catalog-fork-${agent.name}`,
519
+ className: "px-0 text-doom-dim",
520
+ onClick: (event) => {
521
+ event.stopPropagation();
522
+ onLaunch(true);
523
+ },
524
+ children: ["fork session ", /* @__PURE__ */ jsx(Kbd, { children: "f" })]
525
+ }),
526
+ /* @__PURE__ */ jsxs(Button, {
527
+ variant: "link",
528
+ size: "xs",
529
+ "data-testid": `catalog-inspect-${agent.name}`,
530
+ className: "px-0 text-doom-dim",
531
+ onClick: (event) => {
532
+ event.stopPropagation();
533
+ onInspect();
534
+ },
535
+ children: ["inspect ", /* @__PURE__ */ jsx(Kbd, { children: "i" })]
536
+ })
537
+ ]
538
+ }) : null,
539
+ selected && inspected ? /* @__PURE__ */ jsx(Resources, { agent }) : null
540
+ ]
541
+ });
542
+ }
543
+ /**
544
+ * The agent catalog, in the subagents tab's drawer: every agent the session's
545
+ * directory can launch, nearest definition first, with a filter and keys the
546
+ * TUI catalog also answers to. Launching hands the chosen agent to the dialog.
547
+ */
548
+ function AgentCatalogDrawer({ sessionId, onClose, onLaunch }) {
549
+ const state = useStore(catalog.store, (current) => catalog.select(current, sessionId));
550
+ const shown = filterCatalog(state.agents, state.filter);
551
+ const sections = groupCatalog(shown);
552
+ const selected = shown.find((agent) => agent.name === state.selected) ?? shown[0];
553
+ const move = (delta) => {
554
+ if (shown.length === 0) return;
555
+ const index = selected === void 0 ? -1 : shown.findIndex((agent) => agent.name === selected.name);
556
+ const next = shown[(index + delta + shown.length) % shown.length];
557
+ if (next) selectAgent(sessionId, next.name);
558
+ };
559
+ const onKeyDown = (event) => {
560
+ const typing = event.target.tagName === INPUT_TAG;
561
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
562
+ event.preventDefault();
563
+ move(event.key === "ArrowDown" ? 1 : -1);
564
+ return;
565
+ }
566
+ if (event.key === "Escape") {
567
+ event.preventDefault();
568
+ onClose();
569
+ return;
570
+ }
571
+ if (selected === void 0) return;
572
+ if (event.key === "Enter") {
573
+ event.preventDefault();
574
+ onLaunch(selected, false);
575
+ return;
576
+ }
577
+ if (typing) return;
578
+ if (event.key === "f") onLaunch(selected, true);
579
+ if (event.key === "i") toggleInspect(sessionId, selected.name);
580
+ };
581
+ return /* @__PURE__ */ jsxs("aside", {
582
+ "data-testid": "catalog-drawer",
583
+ tabIndex: 0,
584
+ onKeyDown,
585
+ className: "flex w-[min(440px,calc(100vw-24px))] shrink-0 flex-col overflow-hidden border-l border-doom-border bg-doom-rail outline-none",
586
+ children: [
587
+ /* @__PURE__ */ jsxs("div", {
588
+ className: "flex h-11 shrink-0 items-center gap-2.5 border-b border-doom-border px-4",
589
+ children: [
590
+ /* @__PURE__ */ jsx("span", {
591
+ className: "text-lg font-semibold text-doom-hi",
592
+ children: "agent catalog"
593
+ }),
594
+ /* @__PURE__ */ jsxs(Badge, {
595
+ size: "xs",
596
+ "data-testid": "catalog-count",
597
+ children: [
598
+ state.agents.length,
599
+ " agent",
600
+ state.agents.length === 1 ? "" : "s"
601
+ ]
602
+ }),
603
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
604
+ /* @__PURE__ */ jsx(Kbd, { children: "SPC a l" }),
605
+ /* @__PURE__ */ jsx(Button, {
606
+ variant: "ghost",
607
+ size: "icon",
608
+ "data-testid": "catalog-close",
609
+ title: "close the catalog",
610
+ onClick: onClose,
611
+ children: /* @__PURE__ */ jsx(CloseIcon, { className: "h-3 w-3" })
612
+ })
613
+ ]
614
+ }),
615
+ /* @__PURE__ */ jsx("div", {
616
+ className: "px-4 pt-2.5 pb-1.5",
617
+ children: /* @__PURE__ */ jsx(Input, {
618
+ "data-testid": "catalog-filter",
619
+ value: state.filter,
620
+ placeholder: "filter by name, source, or tool…",
621
+ autoFocus: true,
622
+ onChange: (event) => setCatalogFilter(sessionId, event.target.value)
623
+ })
624
+ }),
625
+ /* @__PURE__ */ jsxs("div", {
626
+ role: "listbox",
627
+ className: "flex min-h-0 flex-1 flex-col overflow-y-auto pb-2",
628
+ children: [
629
+ state.loading ? /* @__PURE__ */ jsx("p", {
630
+ role: "status",
631
+ className: "px-4 py-3 text-sm text-doom-dim",
632
+ children: "loading agents..."
633
+ }) : null,
634
+ state.warning ? /* @__PURE__ */ jsx("p", {
635
+ role: "alert",
636
+ className: "px-4 py-3 text-sm text-doom-red",
637
+ children: state.warning
638
+ }) : null,
639
+ shown.length === 0 && !state.loading && !state.warning ? /* @__PURE__ */ jsx("p", {
640
+ "data-testid": "catalog-empty",
641
+ className: "px-4 py-3 text-sm text-doom-dim",
642
+ children: state.agents.length === 0 ? "no agents found for this session; add one under .po/agents or ~/.po/agent/agents" : "nothing matches the filter"
643
+ }) : null,
644
+ sections.map((section) => /* @__PURE__ */ jsxs("div", {
645
+ className: "flex flex-col",
646
+ children: [/* @__PURE__ */ jsx("span", {
647
+ className: "px-4 pt-2.5 pb-1 text-lg font-semibold text-doom-hi",
648
+ children: section.label
649
+ }), section.agents.map((agent) => /* @__PURE__ */ jsx(AgentRow, {
650
+ agent,
651
+ selected: selected?.name === agent.name,
652
+ inspected: state.inspected === agent.name,
653
+ onSelect: () => selectAgent(sessionId, agent.name),
654
+ onLaunch: (fork) => onLaunch(agent, fork),
655
+ onInspect: () => toggleInspect(sessionId, agent.name)
656
+ }, agent.name))]
657
+ }, section.source))
658
+ ]
659
+ }),
660
+ /* @__PURE__ */ jsxs("div", {
661
+ className: "flex h-8 shrink-0 items-center justify-between border-t border-doom-border-soft bg-doom-deep px-4",
662
+ children: [/* @__PURE__ */ jsx("span", {
663
+ className: "text-sm text-doom-dim",
664
+ children: "↑↓ choose · enter launch · f fork · i inspect · esc close"
665
+ }), /* @__PURE__ */ jsxs("span", {
666
+ className: "text-sm text-doom-dim",
667
+ children: [
668
+ sections.length,
669
+ " source",
670
+ sections.length === 1 ? "" : "s"
671
+ ]
672
+ })]
673
+ })
674
+ ]
675
+ });
676
+ }
677
+ //#endregion
678
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/RunControl.tsx
679
+ /** One run state, in the host's shared outcome vocabulary. */
680
+ const RUN_BADGE = {
681
+ queued: {
682
+ label: "QUEUED",
683
+ tone: "neutral"
684
+ },
685
+ running: {
686
+ label: "RUNNING",
687
+ tone: "running"
688
+ },
689
+ done: {
690
+ label: "DONE",
691
+ tone: "ok"
692
+ },
693
+ failed: {
694
+ label: "FAILED",
695
+ tone: "error"
696
+ },
697
+ stopped: {
698
+ label: "STOPPED",
699
+ tone: "neutral"
700
+ }
701
+ };
702
+ /** How long the run has been going, or took; the clock only matters while it is active. */
703
+ function elapsedRun(run, now) {
704
+ const end = run.endedAt ?? (run.state === "running" || run.state === "queued" ? now : run.lastUpdate);
705
+ return formatRunDuration(Math.max(0, end - run.startedAt));
706
+ }
707
+ /**
708
+ * The one control a run offers: stop while it is active, clear once it is
709
+ * not. Stop is a request the runtime acknowledges in its own time, so the
710
+ * button reads "stopping" until the run's own status says otherwise; a
711
+ * refused request says why next to the button.
712
+ */
713
+ function RunControl({ sessionId, run, stopping }) {
714
+ const [error, setError] = useState();
715
+ const act = (event, action) => {
716
+ event.stopPropagation();
717
+ action();
718
+ };
719
+ const stop = () => {
720
+ setError(void 0);
721
+ requestRunStop(sessionId, run.runId).catch((cause) => setError(cause instanceof Error ? cause.message : String(cause)));
722
+ };
723
+ if (isTerminalRun(run)) return /* @__PURE__ */ jsx(Button, {
724
+ variant: "outline",
725
+ size: "xs",
726
+ "data-testid": `run-clear-${run.runId}`,
727
+ title: "clear this run from the grid",
728
+ onClick: (event) => act(event, () => dismissRun(sessionId, run.runId)),
729
+ children: "clear"
730
+ });
731
+ return /* @__PURE__ */ jsxs(Fragment, { children: [error ? /* @__PURE__ */ jsx("span", {
732
+ role: "alert",
733
+ "data-testid": `run-stop-error-${run.runId}`,
734
+ title: error,
735
+ className: "min-w-0 truncate text-sm text-doom-red",
736
+ children: error
737
+ }) : null, /* @__PURE__ */ jsx(Button, {
738
+ variant: stopping ? "outline" : "danger-outline",
739
+ size: "xs",
740
+ "data-testid": `run-stop-${run.runId}`,
741
+ "data-stopping": stopping,
742
+ disabled: stopping,
743
+ title: stopping ? "stop requested; the run reports its own final state" : "ask the runtime to stop this run",
744
+ onClick: (event) => act(event, stop),
745
+ children: stopping ? "stopping…" : "stop"
746
+ })] });
747
+ }
748
+ //#endregion
749
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/AgentThreadPanel.tsx
750
+ const TICK_MS$2 = 1e4;
751
+ /** The tab id doubles as the URL segment, so it stays plain and unique across plugins. */
752
+ const TAB_ID_PREFIX = "subagents-run-";
753
+ /** The temporary tab for one run; the host keeps this panel for as long as the tab is open. */
754
+ function agentThreadTab(run) {
755
+ const runId = run.runId;
756
+ return {
757
+ id: `${TAB_ID_PREFIX}${runId}`,
758
+ label: agentRunLabel(run),
759
+ panel: (props) => /* @__PURE__ */ jsx(AgentThreadPanel, {
760
+ ...props,
761
+ runId
762
+ })
763
+ };
764
+ }
765
+ /**
766
+ * A run's own conversation on the host's transcript, under a header with
767
+ * what the grid card knows: state, elapsed, model, the prompt's first line,
768
+ * and stop while the run is going. The run can leave the fleet feed before
769
+ * the reader is done; the thread stays, and the header says so.
770
+ */
771
+ function AgentThreadPanel({ sessionId, runId, renderThread }) {
772
+ const { runs, stopRequested } = useStore(subagents.store, (state) => subagents.select(state, sessionId));
773
+ const run = runs.find((candidate) => candidate.runId === runId);
774
+ const [now, setNow] = useState(() => Date.now());
775
+ const [guidance, setGuidance] = useState("");
776
+ const [sending, setSending] = useState(false);
777
+ const [error, setError] = useState();
778
+ const [notice, setNotice] = useState();
779
+ useEffect(() => {
780
+ const timer = setInterval(() => setNow(Date.now()), TICK_MS$2);
781
+ return () => clearInterval(timer);
782
+ }, []);
783
+ const firstLine = run?.task.split("\n").find((line) => line.trim() !== "") ?? "";
784
+ const canSteer = sessionId !== null && run !== void 0 && !isTerminalRun(run);
785
+ const submitGuidance = async (event) => {
786
+ event.preventDefault();
787
+ const message = guidance.trim();
788
+ if (!canSteer || !message || sending) return;
789
+ setSending(true);
790
+ setError(void 0);
791
+ setNotice(void 0);
792
+ try {
793
+ const result = await steerRun(sessionId, runId, message);
794
+ if (result.state === "failed") {
795
+ setError(result.message);
796
+ return;
797
+ }
798
+ setGuidance("");
799
+ if (result.state === "pending") setNotice(result.message);
800
+ } catch (cause) {
801
+ setError(cause instanceof Error ? cause.message : String(cause));
802
+ } finally {
803
+ setSending(false);
804
+ }
805
+ };
806
+ return /* @__PURE__ */ jsxs("div", {
807
+ "data-testid": "agent-thread-panel",
808
+ className: "flex min-h-0 flex-1 flex-col",
809
+ children: [
810
+ /* @__PURE__ */ jsx("div", {
811
+ "data-testid": "agent-thread-header",
812
+ className: "flex h-10 shrink-0 items-center gap-2.5 border-b border-doom-border-soft px-[26px]",
813
+ children: run ? /* @__PURE__ */ jsxs(Fragment, { children: [
814
+ /* @__PURE__ */ jsx("span", {
815
+ "data-testid": "agent-thread-agent",
816
+ className: "shrink-0 truncate text-base font-semibold text-doom-hi",
817
+ children: agentRunLabel(run)
818
+ }),
819
+ /* @__PURE__ */ jsx(StatusBadge, {
820
+ tone: RUN_BADGE[run.state].tone,
821
+ "data-testid": "agent-thread-state",
822
+ children: RUN_BADGE[run.state].label
823
+ }),
824
+ /* @__PURE__ */ jsx("span", {
825
+ className: "shrink-0 text-sm text-doom-dim",
826
+ children: elapsedRun(run, now)
827
+ }),
828
+ run.model ? /* @__PURE__ */ jsx("span", {
829
+ className: "shrink-0 text-sm text-doom-dim",
830
+ children: run.model.split("/").pop() ?? run.model
831
+ }) : null,
832
+ /* @__PURE__ */ jsx("span", {
833
+ "data-testid": "agent-thread-task",
834
+ className: "min-w-0 flex-1 truncate text-sm text-doom-dim",
835
+ children: firstLine
836
+ }),
837
+ sessionId !== null && !isTerminalRun(run) ? /* @__PURE__ */ jsx(RunControl, {
838
+ sessionId,
839
+ run,
840
+ stopping: stopRequested.includes(run.runId)
841
+ }) : null
842
+ ] }) : /* @__PURE__ */ jsxs("span", {
843
+ "data-testid": "agent-thread-gone",
844
+ className: "text-sm text-doom-dim",
845
+ children: [
846
+ "run ",
847
+ runId.slice(0, 8),
848
+ " is no longer listed; its transcript stays readable"
849
+ ]
850
+ })
851
+ }),
852
+ renderThread(runId),
853
+ canSteer ? /* @__PURE__ */ jsx("form", {
854
+ "data-testid": "agent-steer-composer",
855
+ className: "shrink-0 border-t border-doom-border-soft bg-doom-rail px-[26px] pt-3 pb-2.5",
856
+ onSubmit: (event) => void submitGuidance(event),
857
+ children: /* @__PURE__ */ jsxs("div", {
858
+ className: "rounded-lg border border-doom-border bg-doom-deep transition-colors focus-within:border-doom-blue/60",
859
+ children: [
860
+ /* @__PURE__ */ jsxs("div", {
861
+ className: "flex min-w-0 items-start gap-2.5 px-3.5 pt-3",
862
+ children: [/* @__PURE__ */ jsx("span", {
863
+ className: "mt-[3px] shrink-0 select-none text-base leading-none text-doom-green",
864
+ children: ">"
865
+ }), /* @__PURE__ */ jsx(Textarea, {
866
+ variant: "bare",
867
+ "data-testid": "agent-steer-input",
868
+ "aria-label": "Steering guidance",
869
+ rows: 2,
870
+ value: guidance,
871
+ readOnly: sending,
872
+ placeholder: "Guide this agent…",
873
+ className: "min-w-0 flex-1 text-base leading-relaxed",
874
+ onChange: (event) => setGuidance(event.target.value)
875
+ })]
876
+ }),
877
+ error ? /* @__PURE__ */ jsx("p", {
878
+ role: "alert",
879
+ "data-testid": "agent-steer-error",
880
+ className: "px-3.5 pt-2 text-sm text-doom-red",
881
+ children: error
882
+ }) : null,
883
+ /* @__PURE__ */ jsxs("div", {
884
+ className: "flex items-center gap-2 px-3.5 pt-2 pb-2.5",
885
+ children: [
886
+ /* @__PURE__ */ jsx("span", {
887
+ role: "status",
888
+ "data-testid": "agent-steer-hint",
889
+ className: "min-w-0 truncate text-sm text-doom-dim",
890
+ children: notice ?? "guidance reaches this run while it is still working"
891
+ }),
892
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
893
+ /* @__PURE__ */ jsx(Button, {
894
+ "data-testid": "agent-steer-submit",
895
+ type: "submit",
896
+ variant: "primary",
897
+ size: "md",
898
+ className: "px-3.5",
899
+ disabled: sending || guidance.trim() === "",
900
+ children: sending ? "sending…" : "send"
901
+ })
902
+ ]
903
+ })
904
+ ]
905
+ })
906
+ }) : null
907
+ ]
908
+ });
909
+ }
910
+ //#endregion
911
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/LaunchAgentDialog.tsx
912
+ /** The picker's stand-in for "no override"; a Radix item cannot carry an empty value. */
913
+ const AGENT_DEFAULT = "agent-default";
914
+ const TASK_ROWS = 4;
915
+ function FieldLabel({ children }) {
916
+ return /* @__PURE__ */ jsx("span", {
917
+ className: "text-base font-medium st text-doom-dim",
918
+ children
919
+ });
920
+ }
921
+ /**
922
+ * Launches one agent from the catalog: the task, whether the child forks
923
+ * this session, and a model override. The session server starts the child.
924
+ */
925
+ function LaunchAgentDialog({ sessionId, agent, cwd, models, fork: initialFork, initialTask, onClose, onLaunched }) {
926
+ const [task, setTask] = useState(initialTask);
927
+ const [fork, setFork] = useState(initialFork);
928
+ const [model, setModel] = useState(AGENT_DEFAULT);
929
+ const [busy, setBusy] = useState(false);
930
+ const [error, setError] = useState();
931
+ const request = {
932
+ agent: agent.name,
933
+ task,
934
+ fork,
935
+ ...model === AGENT_DEFAULT ? {} : { model }
936
+ };
937
+ const launch = async () => {
938
+ if (busy) return;
939
+ setBusy(true);
940
+ setError(void 0);
941
+ requestLaunch(sessionId, agent.name);
942
+ try {
943
+ await launchAgent(sessionId, request);
944
+ onLaunched();
945
+ } catch (cause) {
946
+ clearPendingLaunch(sessionId);
947
+ setError(cause instanceof Error ? cause.message : String(cause));
948
+ } finally {
949
+ setBusy(false);
950
+ }
951
+ };
952
+ const onTaskKeyDown = (event) => {
953
+ if (event.key === "Enter" && !event.shiftKey) {
954
+ event.preventDefault();
955
+ launch();
956
+ }
957
+ };
958
+ return /* @__PURE__ */ jsx(Dialog, {
959
+ open: true,
960
+ onOpenChange: (open) => {
961
+ if (!open) onClose();
962
+ },
963
+ children: /* @__PURE__ */ jsxs(DialogContent, {
964
+ width: "lg",
965
+ "data-testid": "launch-dialog",
966
+ "aria-describedby": void 0,
967
+ children: [/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsxs("div", {
968
+ className: "flex min-w-0 flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2.5",
969
+ children: [
970
+ /* @__PURE__ */ jsx("span", {
971
+ className: "text-sm text-doom-dim",
972
+ children: "launch subagent"
973
+ }),
974
+ /* @__PURE__ */ jsx(DialogTitle, {
975
+ "data-testid": "launch-agent",
976
+ className: "max-w-full break-words",
977
+ children: agent.name
978
+ }),
979
+ /* @__PURE__ */ jsx(Badge, {
980
+ size: "xs",
981
+ className: "max-w-full truncate",
982
+ children: agent.packageName ?? agent.source
983
+ })
984
+ ]
985
+ }) }), /* @__PURE__ */ jsxs(DialogBody, { children: [
986
+ /* @__PURE__ */ jsx("p", {
987
+ className: "text-base leading-relaxed text-doom-dim",
988
+ children: agent.description
989
+ }),
990
+ /* @__PURE__ */ jsxs("div", {
991
+ className: "flex flex-col gap-1.5",
992
+ children: [
993
+ /* @__PURE__ */ jsx(FieldLabel, { children: "TASK" }),
994
+ /* @__PURE__ */ jsx(Textarea, {
995
+ "data-testid": "launch-task",
996
+ autoFocus: true,
997
+ rows: TASK_ROWS,
998
+ value: task,
999
+ placeholder: "what should the agent do? empty runs its own prompt",
1000
+ onChange: (event) => setTask(event.target.value),
1001
+ onKeyDown: onTaskKeyDown
1002
+ }),
1003
+ /* @__PURE__ */ jsx("span", {
1004
+ className: "text-sm text-doom-dim",
1005
+ children: "enter launches · shift+enter for a new line · @ for files"
1006
+ })
1007
+ ]
1008
+ }),
1009
+ /* @__PURE__ */ jsxs("div", {
1010
+ className: "flex flex-wrap gap-5",
1011
+ children: [
1012
+ /* @__PURE__ */ jsxs("div", {
1013
+ className: "flex flex-col gap-1.5",
1014
+ children: [
1015
+ /* @__PURE__ */ jsx(FieldLabel, { children: "CONTEXT" }),
1016
+ /* @__PURE__ */ jsxs("div", {
1017
+ className: "flex flex-wrap gap-1.5",
1018
+ children: [/* @__PURE__ */ jsx(Button, {
1019
+ variant: fork ? "outline" : "primary",
1020
+ size: "xs",
1021
+ "data-testid": "launch-fresh",
1022
+ "data-active": !fork,
1023
+ onClick: () => setFork(false),
1024
+ children: "fresh"
1025
+ }), /* @__PURE__ */ jsx(Button, {
1026
+ variant: fork ? "primary" : "outline",
1027
+ size: "xs",
1028
+ "data-testid": "launch-fork",
1029
+ "data-active": fork,
1030
+ onClick: () => setFork(true),
1031
+ children: "fork this session"
1032
+ })]
1033
+ }),
1034
+ /* @__PURE__ */ jsx("span", {
1035
+ className: "text-sm text-doom-dim",
1036
+ children: "fork shares the conversation so far"
1037
+ })
1038
+ ]
1039
+ }),
1040
+ /* @__PURE__ */ jsxs("div", {
1041
+ className: "flex flex-col gap-1.5",
1042
+ children: [
1043
+ /* @__PURE__ */ jsx(FieldLabel, { children: "MODEL" }),
1044
+ /* @__PURE__ */ jsxs(Select, {
1045
+ value: model,
1046
+ onValueChange: setModel,
1047
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
1048
+ "data-testid": "launch-model",
1049
+ className: "h-7 w-full min-w-0 sm:min-w-[200px]",
1050
+ children: /* @__PURE__ */ jsx(SelectValue, {})
1051
+ }), /* @__PURE__ */ jsxs(SelectContent, { children: [/* @__PURE__ */ jsxs(SelectItem, {
1052
+ value: AGENT_DEFAULT,
1053
+ children: ["agent default", agent.model ? ` · ${agent.model}` : ""]
1054
+ }), modelChoices(agent, models).map((choice) => /* @__PURE__ */ jsx(SelectItem, {
1055
+ value: choice,
1056
+ children: choice
1057
+ }, choice))] })]
1058
+ }),
1059
+ /* @__PURE__ */ jsx("span", {
1060
+ className: "text-sm text-doom-dim",
1061
+ children: "leave unset to use the agent's default"
1062
+ })
1063
+ ]
1064
+ }),
1065
+ /* @__PURE__ */ jsxs("div", {
1066
+ className: "flex flex-col gap-1.5",
1067
+ children: [
1068
+ /* @__PURE__ */ jsx(FieldLabel, { children: "CWD" }),
1069
+ /* @__PURE__ */ jsx("span", {
1070
+ className: "flex h-7 items-center text-sm text-doom-dim",
1071
+ children: abbreviateCwd(cwd)
1072
+ }),
1073
+ /* @__PURE__ */ jsx("span", {
1074
+ className: "text-sm text-doom-dim",
1075
+ children: "the session's directory"
1076
+ })
1077
+ ]
1078
+ })
1079
+ ]
1080
+ }),
1081
+ error ? /* @__PURE__ */ jsx("p", {
1082
+ role: "alert",
1083
+ className: "text-sm text-doom-red",
1084
+ children: error
1085
+ }) : null,
1086
+ /* @__PURE__ */ jsxs(DialogFooter, {
1087
+ className: "flex-wrap sm:flex-nowrap",
1088
+ children: [
1089
+ /* @__PURE__ */ jsx("span", {
1090
+ className: "w-full text-sm text-doom-dim sm:w-auto",
1091
+ children: "the run opens in its own tab once it starts"
1092
+ }),
1093
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1094
+ /* @__PURE__ */ jsx(Button, {
1095
+ variant: "outline",
1096
+ size: "xs",
1097
+ "data-testid": "launch-cancel",
1098
+ onClick: onClose,
1099
+ children: "cancel"
1100
+ }),
1101
+ /* @__PURE__ */ jsx(Button, {
1102
+ variant: "primary",
1103
+ size: "xs",
1104
+ "data-testid": "launch-submit",
1105
+ disabled: busy,
1106
+ onClick: () => void launch(),
1107
+ children: busy ? "launching…" : "launch"
1108
+ })
1109
+ ]
1110
+ })
1111
+ ] })]
1112
+ })
1113
+ });
1114
+ }
1115
+ //#endregion
1116
+ //#region src/extensions/workspaces/sessions/(frontend)/_components/SubagentsPanel.tsx
1117
+ /** One fleet tab per session; the fleet is singular, so the id needs nothing else. */
1118
+ const SUBAGENTS_TAB_ID = "subagents-fleet";
1119
+ const TICK_MS$1 = 1e4;
1120
+ /**
1121
+ * How much of a run's own conversation a card carries: enough to read what it
1122
+ * is doing right now, not enough to read the run. The whole thread is one
1123
+ * click away in the run's tab.
1124
+ */
1125
+ const CARD_THREAD_ENTRIES = 8;
1126
+ /** The card's work line: a task binding wins; otherwise the delegation prompt. */
1127
+ function WorkLine({ run }) {
1128
+ const firstLine = run.task.split("\n").find((line) => line.trim() !== "") ?? "";
1129
+ if (run.taskRef) return /* @__PURE__ */ jsxs("div", {
1130
+ className: "flex items-center gap-2 px-3 pb-2.5",
1131
+ children: [/* @__PURE__ */ jsxs(Badge, {
1132
+ tone: "violet",
1133
+ size: "xs",
1134
+ className: "shrink-0 rounded-sm bg-doom-violet/10",
1135
+ children: ["TASK ", run.taskRef]
1136
+ }), /* @__PURE__ */ jsx("span", {
1137
+ "data-testid": "run-work",
1138
+ className: "min-w-0 truncate text-base text-doom-hi",
1139
+ children: firstLine
1140
+ })]
1141
+ });
1142
+ return /* @__PURE__ */ jsxs("div", {
1143
+ className: "flex items-center gap-2 px-3 pb-2.5",
1144
+ children: [/* @__PURE__ */ jsx("span", {
1145
+ className: "shrink-0 text-base font-medium text-doom-dim",
1146
+ children: "Prompt"
1147
+ }), /* @__PURE__ */ jsx("span", {
1148
+ "data-testid": "run-work",
1149
+ className: "min-w-0 truncate text-base text-doom-dim",
1150
+ children: firstLine
1151
+ })]
1152
+ });
1153
+ }
1154
+ /**
1155
+ * Everything a card can do, behind one control.
1156
+ *
1157
+ * A card already spends its width on the run's own stream, and stop, clear,
1158
+ * detail and the run's tab are four verbs: as buttons they crowd the footer
1159
+ * and each one has to defend itself from the card's own click.
1160
+ */
1161
+ function RunMenu({ sessionId, run, stopping, onOpenThread, onOpenDetail }) {
1162
+ const [error, setError] = useState();
1163
+ const stop = () => {
1164
+ setError(void 0);
1165
+ requestRunStop(sessionId, run.runId).catch((cause) => setError(cause instanceof Error ? cause.message : String(cause)));
1166
+ };
1167
+ return /* @__PURE__ */ jsxs(Fragment, { children: [error ? /* @__PURE__ */ jsx("span", {
1168
+ role: "alert",
1169
+ "data-testid": `run-menu-stop-error-${run.runId}`,
1170
+ title: error,
1171
+ className: "min-w-0 truncate text-sm text-doom-red",
1172
+ children: error
1173
+ }) : null, /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
1174
+ asChild: true,
1175
+ children: /* @__PURE__ */ jsx(Button, {
1176
+ variant: "ghost",
1177
+ size: "icon",
1178
+ "data-testid": `run-menu-${run.runId}`,
1179
+ title: "what this run can do",
1180
+ "aria-label": "run actions",
1181
+ onClick: (event) => event.stopPropagation(),
1182
+ children: /* @__PURE__ */ jsx(KebabIcon, { className: "h-3 w-3" })
1183
+ })
1184
+ }), /* @__PURE__ */ jsxs(DropdownMenuContent, {
1185
+ "data-testid": `run-menu-content-${run.runId}`,
1186
+ children: [
1187
+ /* @__PURE__ */ jsx(DropdownMenuItem, {
1188
+ "data-testid": `run-open-${run.runId}`,
1189
+ onSelect: onOpenThread,
1190
+ children: "open thread"
1191
+ }),
1192
+ /* @__PURE__ */ jsx(DropdownMenuItem, {
1193
+ "data-testid": `run-detail-${run.runId}`,
1194
+ onSelect: onOpenDetail,
1195
+ children: "details"
1196
+ }),
1197
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
1198
+ isTerminalRun(run) ? /* @__PURE__ */ jsx(DropdownMenuItem, {
1199
+ "data-testid": `run-clear-${run.runId}`,
1200
+ onSelect: () => dismissRun(sessionId, run.runId),
1201
+ children: "clear from the grid"
1202
+ }) : /* @__PURE__ */ jsx(DropdownMenuItem, {
1203
+ variant: "destructive",
1204
+ "data-testid": `run-stop-${run.runId}`,
1205
+ "data-stopping": stopping,
1206
+ disabled: stopping,
1207
+ onSelect: stop,
1208
+ children: stopping ? "stopping…" : "stop"
1209
+ })
1210
+ ]
1211
+ })] })] });
1212
+ }
1213
+ /**
1214
+ * One run in the grid: its live conversation under a header, on the host's own
1215
+ * transcript so a tool call reads the same here as in the session.
1216
+ *
1217
+ * The body is a glance, so it takes the newest entries only and does not take
1218
+ * the pointer: the whole card is the way into the run's tab.
1219
+ */
1220
+ function RunCard({ sessionId, run, now, stopping, renderThread, onOpenThread, onOpenDetail }) {
1221
+ const badge = RUN_BADGE[run.state];
1222
+ const onKeyDown = (event) => {
1223
+ if (event.target !== event.currentTarget) return;
1224
+ if (event.key === "Enter" || event.key === " ") {
1225
+ event.preventDefault();
1226
+ onOpenThread();
1227
+ }
1228
+ };
1229
+ return /* @__PURE__ */ jsx(Panel, {
1230
+ asChild: true,
1231
+ className: cn("flex h-[280px] min-w-0 flex-col text-left transition-colors outline-none hover:border-doom-blue/50 focus-within:border-doom-blue", STATUS_EDGE[badge.tone]),
1232
+ children: /* @__PURE__ */ jsxs("div", {
1233
+ "data-testid": `run-card-${run.runId}`,
1234
+ "data-run-state": run.state,
1235
+ children: [/* @__PURE__ */ jsxs("div", {
1236
+ role: "button",
1237
+ tabIndex: 0,
1238
+ "data-testid": `run-open-card-${run.runId}`,
1239
+ className: "flex min-h-0 flex-1 cursor-pointer flex-col outline-none",
1240
+ title: "open this run's own thread in a tab",
1241
+ onClick: onOpenThread,
1242
+ onKeyDown,
1243
+ children: [
1244
+ /* @__PURE__ */ jsxs("div", {
1245
+ className: "flex items-center gap-2 px-3 pt-2.5 pb-1.5",
1246
+ children: [
1247
+ /* @__PURE__ */ jsx("span", {
1248
+ "data-testid": "run-agent",
1249
+ className: "truncate text-base font-semibold text-doom-hi",
1250
+ children: agentRunLabel(run)
1251
+ }),
1252
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1253
+ /* @__PURE__ */ jsx("span", {
1254
+ className: "shrink-0 text-sm text-doom-dim",
1255
+ children: elapsedRun(run, now)
1256
+ }),
1257
+ /* @__PURE__ */ jsx(StatusBadge, {
1258
+ tone: badge.tone,
1259
+ "data-testid": "run-state",
1260
+ children: badge.label
1261
+ })
1262
+ ]
1263
+ }),
1264
+ /* @__PURE__ */ jsx(WorkLine, { run }),
1265
+ /* @__PURE__ */ jsx("div", {
1266
+ "data-testid": "run-stream",
1267
+ className: "pointer-events-none flex min-h-0 flex-1 flex-col overflow-hidden border-t border-doom-border-soft bg-doom-deep",
1268
+ children: renderThread(run.runId, {
1269
+ limit: CARD_THREAD_ENTRIES,
1270
+ compact: true
1271
+ })
1272
+ }),
1273
+ run.state === "failed" && run.error ? /* @__PURE__ */ jsx("span", {
1274
+ "data-testid": "run-error",
1275
+ className: "truncate border-t border-doom-border-soft px-3 py-1.5 text-sm text-doom-red",
1276
+ children: run.error
1277
+ }) : null
1278
+ ]
1279
+ }), /* @__PURE__ */ jsxs("div", {
1280
+ className: "flex shrink-0 items-center gap-3 border-t border-doom-border-soft px-3 py-1.5",
1281
+ children: [
1282
+ /* @__PURE__ */ jsxs("span", {
1283
+ className: "truncate text-sm text-doom-dim",
1284
+ children: [
1285
+ run.toolCount !== void 0 ? `${run.toolCount} tools` : "—",
1286
+ run.tokens !== void 0 ? ` · ${run.tokens.toLocaleString()} tk` : "",
1287
+ run.model ? ` · ${run.model.split("/").pop() ?? run.model}` : ""
1288
+ ]
1289
+ }),
1290
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1291
+ /* @__PURE__ */ jsx(RunMenu, {
1292
+ sessionId,
1293
+ run,
1294
+ stopping,
1295
+ onOpenThread,
1296
+ onOpenDetail
1297
+ })
1298
+ ]
1299
+ })]
1300
+ })
1301
+ });
1302
+ }
1303
+ function SheetRow({ label, value, tone = "text-doom-dim" }) {
1304
+ return /* @__PURE__ */ jsxs("div", {
1305
+ className: "flex gap-2.5",
1306
+ children: [/* @__PURE__ */ jsx("span", {
1307
+ className: "w-11 shrink-0 text-sm text-doom-dim",
1308
+ children: label
1309
+ }), /* @__PURE__ */ jsx("span", {
1310
+ className: `min-w-0 break-words text-base ${tone}`,
1311
+ children: value
1312
+ })]
1313
+ });
1314
+ }
1315
+ function SheetBlock({ label, text, testId, tone }) {
1316
+ return /* @__PURE__ */ jsxs("div", {
1317
+ className: "flex flex-col gap-1 rounded-md border border-doom-border bg-doom-deep px-3 py-2.5",
1318
+ children: [/* @__PURE__ */ jsx("span", {
1319
+ className: "text-base font-medium text-doom-dim",
1320
+ children: label
1321
+ }), /* @__PURE__ */ jsx("pre", {
1322
+ "data-testid": testId,
1323
+ className: `overflow-x-auto whitespace-pre-wrap break-words text-base leading-relaxed ${tone ?? "text-doom-text"}`,
1324
+ children: text
1325
+ })]
1326
+ });
1327
+ }
1328
+ /**
1329
+ * What the run was asked to do and what it was given to do it with, over the
1330
+ * grid rather than beside it: the conversation is on the card and in the run's
1331
+ * tab, so this reads as a definition and takes no width from the fleet.
1332
+ */
1333
+ function RunDetailSheet({ sessionId, run, now, stopping, renderSlot, onClose }) {
1334
+ const badge = RUN_BADGE[run.state];
1335
+ return /* @__PURE__ */ jsx(Sheet, {
1336
+ open: true,
1337
+ onOpenChange: (open) => {
1338
+ if (!open) onClose();
1339
+ },
1340
+ children: /* @__PURE__ */ jsxs(SheetContent, {
1341
+ "data-testid": "run-sheet",
1342
+ "aria-describedby": void 0,
1343
+ children: [/* @__PURE__ */ jsxs(SheetHeader, {
1344
+ closeLabel: "close the run detail",
1345
+ children: [
1346
+ /* @__PURE__ */ jsx(SheetTitle, {
1347
+ "data-testid": "sheet-agent",
1348
+ children: agentRunLabel(run)
1349
+ }),
1350
+ /* @__PURE__ */ jsx(StatusBadge, {
1351
+ tone: badge.tone,
1352
+ children: badge.label
1353
+ }),
1354
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1" }),
1355
+ /* @__PURE__ */ jsx("span", {
1356
+ className: "text-sm text-doom-dim",
1357
+ children: elapsedRun(run, now)
1358
+ }),
1359
+ renderSlot(RUN_ACTIONS_SLOT.slot),
1360
+ /* @__PURE__ */ jsx(RunControl, {
1361
+ sessionId,
1362
+ run,
1363
+ stopping
1364
+ })
1365
+ ]
1366
+ }), /* @__PURE__ */ jsxs(SheetBody, { children: [
1367
+ /* @__PURE__ */ jsxs("div", {
1368
+ className: "flex flex-col gap-1.5",
1369
+ children: [
1370
+ /* @__PURE__ */ jsx(SheetRow, {
1371
+ label: "run",
1372
+ value: run.runId
1373
+ }),
1374
+ run.taskRef ? /* @__PURE__ */ jsx(SheetRow, {
1375
+ label: "task",
1376
+ value: run.taskRef,
1377
+ tone: "text-doom-violet"
1378
+ }) : null,
1379
+ /* @__PURE__ */ jsx(SheetRow, {
1380
+ label: "model",
1381
+ value: run.model ?? "the runtime default"
1382
+ }),
1383
+ /* @__PURE__ */ jsx(SheetRow, {
1384
+ label: "cwd",
1385
+ value: abbreviateCwd(run.cwd)
1386
+ }),
1387
+ /* @__PURE__ */ jsx(SheetRow, {
1388
+ label: "work",
1389
+ value: `${run.toolCount === void 0 ? "no" : String(run.toolCount)} tools${run.tokens === void 0 ? "" : ` · ${run.tokens.toLocaleString()} tk`}`
1390
+ })
1391
+ ]
1392
+ }),
1393
+ /* @__PURE__ */ jsx(SheetBlock, {
1394
+ label: "PROMPT FROM THE MAIN AGENT",
1395
+ testId: "sheet-task",
1396
+ text: run.task || "(no prompt recorded)"
1397
+ }),
1398
+ run.summary ? /* @__PURE__ */ jsx(SheetBlock, {
1399
+ label: "REPORT",
1400
+ testId: "sheet-summary",
1401
+ text: run.summary
1402
+ }) : null,
1403
+ run.error ? /* @__PURE__ */ jsx(SheetBlock, {
1404
+ label: "FAILURE",
1405
+ testId: "sheet-error",
1406
+ text: run.error,
1407
+ tone: "text-doom-red"
1408
+ }) : null
1409
+ ] })]
1410
+ })
1411
+ });
1412
+ }
1413
+ /**
1414
+ * The subagents tab: the focused session's fleet as a computed grid.
1415
+ *
1416
+ * Columns come from the available width (auto-fill over a 420px minimum), and
1417
+ * every card is the same height with the run's own live conversation
1418
+ * absorbing the slack, so the grid reads as a fleet rather than a ragged list.
1419
+ * A card opens the run's tab; its menu opens the detail sheet over the grid.
1420
+ * The catalog to launch from still takes the right rail, because picking an
1421
+ * agent is a list beside the fleet rather than a thing about one run.
1422
+ */
1423
+ function SubagentsPanel({ sessionId, renderSlot, renderThread, openTransientTab }) {
1424
+ const runs = useStore(subagents.store, (state) => visibleRuns(subagents.select(state, sessionId)));
1425
+ const { openRunId, stopRequested, autoOpenRunId } = useStore(subagents.store, (state) => subagents.select(state, sessionId));
1426
+ const shelf = useStore(catalog.store, (state) => catalog.select(state, sessionId));
1427
+ const [now, setNow] = useState(() => Date.now());
1428
+ useEffect(() => {
1429
+ if (sessionId === null || !shelf.open) return;
1430
+ return loadCatalog(sessionId);
1431
+ }, [sessionId, shelf.open]);
1432
+ useEffect(() => {
1433
+ const timer = setInterval(() => setNow(Date.now()), TICK_MS$1);
1434
+ return () => clearInterval(timer);
1435
+ }, []);
1436
+ useEffect(() => {
1437
+ if (autoOpenRunId === void 0 || sessionId === null) return;
1438
+ const run = runs.find((candidate) => candidate.runId === autoOpenRunId);
1439
+ if (run) openTransientTab(agentThreadTab(run));
1440
+ clearAutoOpen(sessionId);
1441
+ }, [
1442
+ autoOpenRunId,
1443
+ runs,
1444
+ sessionId,
1445
+ openTransientTab
1446
+ ]);
1447
+ const shownRun = openRunId === void 0 ? void 0 : runs.find((run) => run.runId === openRunId);
1448
+ const launching = shelf.launch === void 0 ? void 0 : shelf.agents.find((agent) => agent.name === shelf.launch?.agent);
1449
+ const stopping = (run) => stopRequested.includes(run.runId);
1450
+ const runningTally = runs.filter((run) => run.state === "running" || run.state === "queued").length;
1451
+ const doneTally = runs.filter((run) => run.state === "done").length;
1452
+ const failedTally = runs.filter((run) => run.state === "failed" || run.state === "stopped").length;
1453
+ const showCatalog = () => {
1454
+ if (sessionId === null) return;
1455
+ openRun(sessionId, void 0);
1456
+ openCatalog(sessionId);
1457
+ };
1458
+ return /* @__PURE__ */ jsxs("div", {
1459
+ "data-testid": "subagents-panel",
1460
+ className: "flex min-h-0 min-w-0 flex-1 overflow-hidden",
1461
+ children: [
1462
+ /* @__PURE__ */ jsxs("div", {
1463
+ className: "flex min-w-0 flex-1 flex-col overflow-y-auto px-3 py-3 sm:px-[26px] sm:py-[18px]",
1464
+ children: [/* @__PURE__ */ jsxs("div", {
1465
+ className: "flex flex-wrap items-center gap-x-3 gap-y-2 pb-3",
1466
+ children: [
1467
+ /* @__PURE__ */ jsx("span", {
1468
+ className: "text-lg font-semibold text-doom-hi",
1469
+ children: "Subagents · this session"
1470
+ }),
1471
+ /* @__PURE__ */ jsx("span", { className: "hidden min-w-0 flex-1 sm:block" }),
1472
+ /* @__PURE__ */ jsx("span", {
1473
+ "data-testid": "subagents-tally",
1474
+ className: "order-3 w-full text-sm text-doom-dim sm:order-none sm:w-auto",
1475
+ children: runs.length === 0 ? "no runs yet" : `${runningTally} running · ${doneTally} done · ${failedTally} failed`
1476
+ }),
1477
+ /* @__PURE__ */ jsxs(Button, {
1478
+ variant: "outline",
1479
+ size: "xs",
1480
+ className: "ml-auto sm:ml-0",
1481
+ "data-testid": "subagents-launch",
1482
+ title: "pick an agent from the catalog and launch it",
1483
+ onClick: showCatalog,
1484
+ children: ["launch agent ", /* @__PURE__ */ jsx(Kbd, { children: "a l" })]
1485
+ })
1486
+ ]
1487
+ }), runs.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
1488
+ "data-testid": "subagents-empty",
1489
+ title: "no subagent runs yet",
1490
+ description: "pick an agent from the catalog to launch one yourself, or let the main agent delegate with its subagent tool."
1491
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
1492
+ "data-testid": "subagents-grid",
1493
+ className: "grid grid-cols-[repeat(auto-fill,minmax(min(420px,100%),1fr))] gap-4",
1494
+ children: sessionId === null ? null : runs.map((run) => /* @__PURE__ */ jsx(RunCard, {
1495
+ sessionId,
1496
+ run,
1497
+ now,
1498
+ stopping: stopping(run),
1499
+ renderThread,
1500
+ onOpenThread: () => {
1501
+ closeCatalog(sessionId);
1502
+ openTransientTab(agentThreadTab(run));
1503
+ },
1504
+ onOpenDetail: () => {
1505
+ closeCatalog(sessionId);
1506
+ openRun(sessionId, run.runId);
1507
+ }
1508
+ }, run.runId))
1509
+ }), /* @__PURE__ */ jsx("span", {
1510
+ className: "pt-3 text-sm text-doom-dim",
1511
+ children: "click a card to open the run's thread · the card menu holds details, stop and clear · finished runs leave the grid after 10m"
1512
+ })] })]
1513
+ }),
1514
+ sessionId !== null && shelf.open ? /* @__PURE__ */ jsx(AgentCatalogDrawer, {
1515
+ sessionId,
1516
+ onClose: () => closeCatalog(sessionId),
1517
+ onLaunch: (agent, fork) => openLaunch(sessionId, agent.name, fork)
1518
+ }) : null,
1519
+ shownRun && sessionId !== null ? /* @__PURE__ */ jsx(RunDetailSheet, {
1520
+ sessionId,
1521
+ run: shownRun,
1522
+ now,
1523
+ stopping: stopping(shownRun),
1524
+ renderSlot,
1525
+ onClose: () => openRun(sessionId, void 0)
1526
+ }) : null,
1527
+ sessionId !== null && launching !== void 0 && shelf.launch !== void 0 ? /* @__PURE__ */ jsx(LaunchAgentDialog, {
1528
+ sessionId,
1529
+ agent: launching,
1530
+ cwd: shelf.cwd,
1531
+ models: shelf.models,
1532
+ fork: shelf.launch.fork,
1533
+ initialTask: shelf.task,
1534
+ onClose: () => closeLaunch(sessionId),
1535
+ onLaunched: () => closeCatalog(sessionId)
1536
+ }, launching.name) : null
1537
+ ]
1538
+ });
1539
+ }
1540
+ /** The temporary tab the fleet opens in; opening it again only focuses it. */
1541
+ function subagentsTab() {
1542
+ return {
1543
+ id: SUBAGENTS_TAB_ID,
1544
+ label: "subagents",
1545
+ panel: SubagentsPanel
1546
+ };
1547
+ }
1548
+ //#endregion
1549
+ //#region src/extensions/workspaces/sessions/(frontend)/action/launch-agent.web.ts
1550
+ /** Offered on any work item another plugin presents. The filename is the action id. */
1551
+ var launch_agent_web_default = defineContextAction({
1552
+ label: "Launch Agent",
1553
+ detail: "Choose an agent, review the task, then launch it.",
1554
+ kinds: ["work-item"],
1555
+ order: 10,
1556
+ run: (context) => openAgentCatalogForContext(context, subagentsTab)
1557
+ });
1558
+ //#endregion
1559
+ //#region src/extensions/workspaces/sessions/(frontend)/activity-group/agents.web.ts
1560
+ /**
1561
+ * The dock group this package owns. The filename is the group name, which is
1562
+ * also the slot other plugins fill: `activity.agents`.
1563
+ *
1564
+ * It stays visible while the session is idle, because the group is the only
1565
+ * way into the fleet and a launcher that appears only once something runs
1566
+ * cannot be used to start anything.
1567
+ */
1568
+ var agents_web_default = defineActivityGroup({
1569
+ keys: "a r",
1570
+ icon: UsersIcon,
1571
+ statusKey: "doom-team-agents",
1572
+ activeSource: {
1573
+ subscribe(listener) {
1574
+ const subscription = subagents.store.subscribe(listener);
1575
+ return () => subscription.unsubscribe();
1576
+ },
1577
+ isActive(sessionId) {
1578
+ return activityRuns(subagents.select(subagents.store.state, sessionId)).some((run) => !isTerminalRun(run));
1579
+ }
1580
+ },
1581
+ transientTab: subagentsTab,
1582
+ order: 10
1583
+ });
1584
+ //#endregion
1585
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/subagent-catalog.web.ts
1586
+ /** The agent catalog the session facet publishes. The filename is the frame type. */
1587
+ var subagent_catalog_web_default = defineChannelFile(subagentCatalogChannel);
1588
+ //#endregion
1589
+ //#region src/extensions/workspaces/sessions/(frontend)/channel/subagent-runs.web.ts
1590
+ /** The run graph the session facet publishes. The filename is the frame type. */
1591
+ var subagent_runs_web_default = defineChannelFile(subagentRunsChannel);
1592
+ //#endregion
1593
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/_components/AgentsActivitySection.tsx
1594
+ const TICK_MS = 1e4;
1595
+ const STATE_TONE = {
1596
+ queued: "muted",
1597
+ running: "yellow",
1598
+ done: "green",
1599
+ failed: "red",
1600
+ stopped: "muted"
1601
+ };
1602
+ function elapsed(run, now) {
1603
+ const end = run.endedAt ?? (isTerminalRun(run) ? run.lastUpdate : now);
1604
+ return formatRunDuration(Math.max(0, end - run.startedAt));
1605
+ }
1606
+ /** The one-liner under a row: what the run is doing, or how it ended. */
1607
+ function detail(run) {
1608
+ if (run.state === "failed" && run.error) return run.error;
1609
+ if (run.summary) return run.summary.split("\n").find((line) => line.trim() !== "") ?? "";
1610
+ return run.currentTool ?? run.tail.at(-1) ?? run.state;
1611
+ }
1612
+ /**
1613
+ * The agents group's body in the activity dock: the session's runs, each a
1614
+ * row that opens the run's own conversation in a temporary tab. This replaces
1615
+ * the runtime's footer one-liner, which only says whether anything is running.
1616
+ */
1617
+ function AgentsActivitySection({ sessionId, openTransientTab }) {
1618
+ const runs = useStore(subagents.store, (state) => activityRuns(subagents.select(state, sessionId)));
1619
+ const [now, setNow] = useState(() => Date.now());
1620
+ useEffect(() => {
1621
+ const timer = setInterval(() => setNow(Date.now()), TICK_MS);
1622
+ return () => clearInterval(timer);
1623
+ }, []);
1624
+ if (runs.length === 0) return /* @__PURE__ */ jsxs("div", {
1625
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1",
1626
+ children: [/* @__PURE__ */ jsx("p", {
1627
+ "data-testid": "activity-summary-agents",
1628
+ className: "text-sm text-doom-dim",
1629
+ children: "Idle"
1630
+ }), /* @__PURE__ */ jsxs(Button, {
1631
+ variant: "link",
1632
+ size: "sm",
1633
+ "data-testid": "activity-agents-launch",
1634
+ className: "h-auto gap-1 px-0 text-sm text-doom-green",
1635
+ onClick: () => {
1636
+ if (sessionId === null) return;
1637
+ openCatalog(sessionId);
1638
+ openTransientTab(subagentsTab());
1639
+ },
1640
+ children: ["Launch an agent", /* @__PURE__ */ jsx(ArrowRightIcon, {
1641
+ "aria-hidden": true,
1642
+ className: "size-3.5"
1643
+ })]
1644
+ })]
1645
+ });
1646
+ return /* @__PURE__ */ jsx("div", {
1647
+ "data-testid": "activity-agent-runs",
1648
+ className: "flex flex-col gap-0.5",
1649
+ children: runs.map((run) => /* @__PURE__ */ jsxs(Button, {
1650
+ variant: "ghost",
1651
+ size: "card",
1652
+ "data-testid": `activity-run-${run.runId}`,
1653
+ "data-run-state": run.state,
1654
+ title: "open this run's conversation",
1655
+ onClick: () => {
1656
+ if (sessionId === null) return;
1657
+ openTransientTab(agentThreadTab(run));
1658
+ },
1659
+ className: "min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel",
1660
+ children: [/* @__PURE__ */ jsxs("span", {
1661
+ className: "flex min-w-0 items-center gap-1.5",
1662
+ children: [
1663
+ /* @__PURE__ */ jsx(Dot, {
1664
+ tone: STATE_TONE[run.state],
1665
+ pulse: run.state === "running"
1666
+ }),
1667
+ /* @__PURE__ */ jsx("span", {
1668
+ className: `min-w-0 flex-1 truncate text-base font-semibold ${isTerminalRun(run) ? "text-doom-hi" : "text-doom-hi"}`,
1669
+ children: agentRunLabel(run)
1670
+ }),
1671
+ /* @__PURE__ */ jsxs("span", {
1672
+ className: "shrink-0 text-sm text-doom-dim",
1673
+ children: [elapsed(run, now), run.toolCount !== void 0 ? ` · ${run.toolCount} tools` : ""]
1674
+ })
1675
+ ]
1676
+ }), /* @__PURE__ */ jsx("span", {
1677
+ className: `truncate pl-3 text-sm ${run.state === "failed" ? "text-doom-red" : "text-doom-dim"}`,
1678
+ children: detail(run)
1679
+ })]
1680
+ }, run.runId))
1681
+ });
1682
+ }
1683
+ //#endregion
1684
+ //#region src/extensions/workspaces/sessions/(frontend)/fill/Agents.activity.agents.web.tsx
1685
+ /**
1686
+ * Renders inside the `agents` activity group rather than after the groups.
1687
+ *
1688
+ * The target in the filename is the slot. `activity.agents` is the group this
1689
+ * package also declares, and the dock puts this in place of its one-line
1690
+ * footer summary.
1691
+ */
1692
+ var Agents_activity_agents_web_default = defineFill({ component: AgentsActivitySection });
1693
+ //#endregion
1694
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/_lib/leaderGroups.ts
1695
+ /** The Leader Space group both of this package's bindings hang from. */
1696
+ const AGENTS_GROUP = {
1697
+ key: "a",
1698
+ label: "agents",
1699
+ detail: "subagent resources and runs"
1700
+ };
1701
+ //#endregion
1702
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/catalog.web.ts
1703
+ /** SPC a l: pick an agent and launch it. The filename is the binding's local name. */
1704
+ var catalog_web_default = defineLeaderBinding({
1705
+ path: [AGENTS_GROUP, {
1706
+ key: "l",
1707
+ label: "launch",
1708
+ detail: "pick an agent and launch it"
1709
+ }],
1710
+ run: (context) => {
1711
+ if (context.sessionId !== null) openCatalog(context.sessionId);
1712
+ context.openTransientTab(subagentsTab());
1713
+ }
1714
+ });
1715
+ //#endregion
1716
+ //#region src/extensions/workspaces/sessions/(frontend)/leader/fleet.web.ts
1717
+ /** SPC a r: the runs in this session. The filename is the binding's local name. */
1718
+ var fleet_web_default = defineLeaderBinding({
1719
+ path: [AGENTS_GROUP, {
1720
+ key: "r",
1721
+ label: "runs",
1722
+ detail: "runs in this session"
1723
+ }],
1724
+ run: (context) => context.openTransientTab(subagentsTab())
1725
+ });
1726
+ //#endregion
1727
+ //#region src/extensions/workspaces/sessions/(frontend)/slot/run-actions.web.ts
1728
+ /**
1729
+ * The run detail sheet's action row, open to independent plugins.
1730
+ *
1731
+ * The filename is the local name; the path namespaces it as
1732
+ * `subagents.run-actions`, so no filler has to spell that prefix.
1733
+ */
1734
+ var run_actions_web_default = defineSlotFile({});
1735
+ const MESSAGE_PREVIEW_CHARS = 72;
1736
+ const ELLIPSIS = "…";
1737
+ function isRecord(value) {
1738
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1739
+ }
1740
+ function asString(value) {
1741
+ return typeof value === "string" ? value : "";
1742
+ }
1743
+ /** The detail beside the subagent action, as the TUI's callDetails words it. */
1744
+ function subagentCallDetail(args) {
1745
+ switch (args.action) {
1746
+ case "agents": return asString(args.name);
1747
+ case "run": {
1748
+ const requests = Array.isArray(args.requests) ? args.requests : [];
1749
+ const agents = requests.map((request) => isRecord(request) ? asString(request.agent) : "").filter(Boolean);
1750
+ const count = `${requests.length} agent${requests.length === 1 ? "" : "s"}`;
1751
+ return agents.length > 0 ? `${count} · ${agents.join(", ")}` : count;
1752
+ }
1753
+ case "status": return asString(args.id) || "fleet";
1754
+ case "steer":
1755
+ case "stop":
1756
+ case "restore": return asString(args.id);
1757
+ case "suspended": return "runs";
1758
+ default: return "";
1759
+ }
1760
+ }
1761
+ /** Result text as lines, trailing blank lines dropped the way the TUI does. */
1762
+ function resultLines(output) {
1763
+ const lines = output.split("\n");
1764
+ while (lines.length > 0 && lines.at(-1)?.trim() === "") lines.pop();
1765
+ return lines.length === 1 && lines[0] === "" ? [] : lines;
1766
+ }
1767
+ /**
1768
+ * The subagent result body: a tail while running, then the head collapsed to
1769
+ * the TUI budget, with the closing glyph line the TUI appends.
1770
+ */
1771
+ function shapeResult(output, options) {
1772
+ const all = resultLines(output);
1773
+ if (options.isPartial) return {
1774
+ lines: all.slice(-12),
1775
+ glyph: "running",
1776
+ hidden: 0
1777
+ };
1778
+ const lines = options.expanded ? all : all.slice(0, 12);
1779
+ const hidden = all.length - lines.length;
1780
+ if (options.isError) return {
1781
+ lines,
1782
+ glyph: "failed",
1783
+ hidden
1784
+ };
1785
+ if (hidden > 0) return {
1786
+ lines,
1787
+ glyph: "more",
1788
+ hidden
1789
+ };
1790
+ if (lines.length === 0) return {
1791
+ lines,
1792
+ glyph: "done",
1793
+ hidden: 0
1794
+ };
1795
+ return {
1796
+ lines,
1797
+ glyph: "none",
1798
+ hidden: 0
1799
+ };
1800
+ }
1801
+ function preview(message) {
1802
+ const flat = message.replace(/\s+/g, " ").trim();
1803
+ return flat.length > MESSAGE_PREVIEW_CHARS ? `${flat.slice(0, 71)}${ELLIPSIS}` : flat;
1804
+ }
1805
+ function records(value) {
1806
+ return Array.isArray(value) ? value.filter(isRecord) : [];
1807
+ }
1808
+ function parts(values) {
1809
+ return values.filter((value) => typeof value === "string" && value.trim() !== "").join(" · ");
1810
+ }
1811
+ function firstLine(value) {
1812
+ return asString(value).split("\n")[0] ?? "";
1813
+ }
1814
+ function stateTone(state) {
1815
+ if (state === "failed" || state === "error") return "error";
1816
+ if (state === "complete" || state === "completed") return "ok";
1817
+ if (state === "running" || state === "starting") return "running";
1818
+ return "idle";
1819
+ }
1820
+ function countSummary(count, noun) {
1821
+ return count === 0 ? `no ${noun}s` : `${count} ${noun}${count === 1 ? "" : "s"}`;
1822
+ }
1823
+ function agentsView(value) {
1824
+ const rows = records(value).map((agent, index) => ({
1825
+ key: `${asString(agent.name)}-${index}`,
1826
+ label: asString(agent.name),
1827
+ detail: preview(asString(agent.description)),
1828
+ state: asString(agent.source),
1829
+ tone: "idle"
1830
+ }));
1831
+ return {
1832
+ kind: "agents",
1833
+ rows,
1834
+ summary: countSummary(rows.length, "agent"),
1835
+ tone: "idle"
1836
+ };
1837
+ }
1838
+ function runView(value) {
1839
+ const rows = records(value).map((outcome, index) => {
1840
+ const runId = asString(outcome.runId);
1841
+ return {
1842
+ key: runId || `${asString(outcome.agent)}-${index}`,
1843
+ label: asString(outcome.agent),
1844
+ detail: runId || preview(asString(outcome.error)) || "unknown error",
1845
+ state: runId ? "started" : "failed",
1846
+ tone: runId ? "ok" : "error"
1847
+ };
1848
+ });
1849
+ const started = rows.filter((row) => row.state === "started").length;
1850
+ const failed = rows.length - started;
1851
+ return {
1852
+ kind: "run",
1853
+ rows,
1854
+ summary: failed === 0 ? `${started} started` : `${started} started · ${failed} failed`,
1855
+ tone: failed === 0 ? "ok" : "error"
1856
+ };
1857
+ }
1858
+ function fleetView(value) {
1859
+ const rows = records(value).map((job, index) => {
1860
+ const state = asString(job.status) || "starting";
1861
+ return {
1862
+ key: asString(job.runId) || `run-${index}`,
1863
+ label: asString(job.runId),
1864
+ detail: preview(parts([
1865
+ job.identity ? job.inline ? `${asString(job.identity)} (inline)` : job.identity : void 0,
1866
+ job.agent,
1867
+ job.currentTool,
1868
+ job.activityState,
1869
+ job.attentionReason,
1870
+ job.error
1871
+ ])),
1872
+ state,
1873
+ tone: stateTone(state)
1874
+ };
1875
+ });
1876
+ return {
1877
+ kind: "fleet",
1878
+ rows,
1879
+ summary: countSummary(rows.length, "run"),
1880
+ tone: "idle"
1881
+ };
1882
+ }
1883
+ function suspendedView(value) {
1884
+ const rows = (isRecord(value) ? [value] : records(value)).map((run, index) => {
1885
+ const resumable = run.resumable;
1886
+ return {
1887
+ key: asString(run.runId) || `suspended-${index}`,
1888
+ label: asString(run.runId),
1889
+ detail: preview(parts([run.agent, firstLine(run.task)])),
1890
+ state: resumable === false ? "not resumable" : resumable === true ? "resumable" : "suspended",
1891
+ tone: resumable === false ? "error" : "idle"
1892
+ };
1893
+ });
1894
+ return {
1895
+ kind: "suspended",
1896
+ rows,
1897
+ summary: countSummary(rows.length, "suspended run"),
1898
+ tone: "idle"
1899
+ };
1900
+ }
1901
+ function statusView(details) {
1902
+ const runId = asString(details.runId);
1903
+ const status = isRecord(details.status) ? details.status : void 0;
1904
+ const state = status ? asString(status.state) : "";
1905
+ if (!state) return {
1906
+ kind: "status",
1907
+ rows: [],
1908
+ summary: `${runId} has no status yet`,
1909
+ tone: "idle"
1910
+ };
1911
+ return {
1912
+ kind: "status",
1913
+ rows: [{
1914
+ key: runId,
1915
+ label: runId,
1916
+ detail: preview(parts([
1917
+ status?.agent,
1918
+ status?.activityState,
1919
+ status?.summary,
1920
+ status?.error
1921
+ ])),
1922
+ state,
1923
+ tone: stateTone(state)
1924
+ }],
1925
+ summary: "",
1926
+ tone: stateTone(state)
1927
+ };
1928
+ }
1929
+ function controlView(summary, tone) {
1930
+ return {
1931
+ kind: "control",
1932
+ rows: [],
1933
+ summary,
1934
+ tone
1935
+ };
1936
+ }
1937
+ /**
1938
+ * Narrows the wire `details` of a subagent result to the rows the card draws.
1939
+ * Null when they are not a finished subagent result at all, which is what a
1940
+ * partial update or an error message is: the card then falls back to the text.
1941
+ */
1942
+ function subagentResultView(details) {
1943
+ if (!isRecord(details) || details.partial === true) return null;
1944
+ if (Array.isArray(details.agents)) return agentsView(details.agents);
1945
+ if (Array.isArray(details.outcomes)) return runView(details.outcomes);
1946
+ if (isRecord(details.spawn) && Array.isArray(details.spawn.outcomes)) return runView(details.spawn.outcomes);
1947
+ if (Array.isArray(details.runs)) return fleetView(details.runs);
1948
+ if (Array.isArray(details.fleet)) return fleetView(details.fleet);
1949
+ if (details.suspended !== void 0) return suspendedView(details.suspended);
1950
+ if (isRecord(details.control)) return controlView(`stop requested for ${asString(details.runId)}`, "ok");
1951
+ if (isRecord(details.steer)) {
1952
+ const state = asString(details.steer.state);
1953
+ return controlView(`steer ${state}${details.steer.message ? `: ${preview(asString(details.steer.message))}` : ""}`, state === "failed" ? "error" : "ok");
1954
+ }
1955
+ if (isRecord(details.restore)) {
1956
+ const restore = details.restore;
1957
+ return controlView(`restored ${asString(restore.restoredFrom)} as ${asString(restore.runId)}`, "ok");
1958
+ }
1959
+ if (typeof details.runId === "string") return statusView(details);
1960
+ return null;
1961
+ }
1962
+ /** The intercom call header: action, its target, and what is being said. */
1963
+ function intercomCallSummary(args) {
1964
+ const action = asString(args.action);
1965
+ switch (action) {
1966
+ case "send":
1967
+ case "ask": return {
1968
+ action,
1969
+ target: asString(args.to),
1970
+ message: preview(asString(args.message))
1971
+ };
1972
+ case "reply": return {
1973
+ action,
1974
+ target: asString(args.requestId),
1975
+ message: preview(asString(args.message))
1976
+ };
1977
+ default: return {
1978
+ action,
1979
+ target: "",
1980
+ message: ""
1981
+ };
1982
+ }
1983
+ }
1984
+ /**
1985
+ * The outcome line for a finished intercom call, read from the details the
1986
+ * tool attaches: a send is delivered or only queued, a reply confirms its
1987
+ * request, an ask carries the answer's sender.
1988
+ */
1989
+ function intercomOutcome(details) {
1990
+ if (!isRecord(details)) return {
1991
+ outcome: "none",
1992
+ who: ""
1993
+ };
1994
+ if (details.delivered === true) return {
1995
+ outcome: "delivered",
1996
+ who: asString(details.to)
1997
+ };
1998
+ if (details.state === "queued") return {
1999
+ outcome: "queued",
2000
+ who: asString(details.to)
2001
+ };
2002
+ if (typeof details.reply === "string") return {
2003
+ outcome: "answered",
2004
+ who: asString(details.from)
2005
+ };
2006
+ if (typeof details.requestId === "string" && typeof details.to === "string") return {
2007
+ outcome: "replied",
2008
+ who: details.to
2009
+ };
2010
+ return {
2011
+ outcome: "none",
2012
+ who: ""
2013
+ };
2014
+ }
2015
+ //#endregion
2016
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/IntercomToolMessage.tsx
2017
+ const OUTCOME_LINE = {
2018
+ delivered: {
2019
+ tone: "ok",
2020
+ text: "delivered to"
2021
+ },
2022
+ queued: {
2023
+ tone: "running",
2024
+ text: "queued, delivery unconfirmed, for"
2025
+ },
2026
+ replied: {
2027
+ tone: "ok",
2028
+ text: "replied to"
2029
+ },
2030
+ answered: {
2031
+ tone: "ok",
2032
+ text: "answered by"
2033
+ }
2034
+ };
2035
+ /**
2036
+ * The intercom tool's timeline item. Intercom has no TUI renderer; the
2037
+ * header names the action, its member or request, and the message, and the
2038
+ * body shows the result text with the outcome the details report.
2039
+ */
2040
+ function IntercomToolMessage({ args, result, output, running, isError }) {
2041
+ const summary = intercomCallSummary(args);
2042
+ const collapsed = shapeResult(output, {
2043
+ expanded: false,
2044
+ isPartial: running,
2045
+ isError
2046
+ });
2047
+ const outcome = running || isError ? {
2048
+ outcome: "none",
2049
+ who: ""
2050
+ } : intercomOutcome(result?.details);
2051
+ const line = outcome.outcome === "none" ? null : OUTCOME_LINE[outcome.outcome];
2052
+ return /* @__PURE__ */ jsx(MessageItem, {
2053
+ tone: toolTone({
2054
+ running,
2055
+ isError
2056
+ }),
2057
+ expandable: collapsed.glyph === "more",
2058
+ children: ({ expanded }) => {
2059
+ const shaped = expanded ? shapeResult(output, {
2060
+ expanded: true,
2061
+ isPartial: running,
2062
+ isError
2063
+ }) : collapsed;
2064
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
2065
+ title: "intercom",
2066
+ children: /* @__PURE__ */ jsxs("span", {
2067
+ "data-testid": "tool-call-intercom",
2068
+ className: "flex min-w-0 flex-1 items-center gap-2",
2069
+ children: [
2070
+ /* @__PURE__ */ jsx("span", {
2071
+ className: "text-doom-hi",
2072
+ children: summary.action
2073
+ }),
2074
+ summary.target ? /* @__PURE__ */ jsxs("span", {
2075
+ className: "shrink-0 text-doom-cyan",
2076
+ children: ["→ ", summary.target]
2077
+ }) : null,
2078
+ summary.message ? /* @__PURE__ */ jsx("span", {
2079
+ className: "min-w-0 truncate text-doom-dim",
2080
+ children: summary.message
2081
+ }) : null
2082
+ ]
2083
+ })
2084
+ }), /* @__PURE__ */ jsxs(MessageItemBody, {
2085
+ "data-testid": "tool-result-intercom",
2086
+ className: "flex flex-col gap-1",
2087
+ children: [
2088
+ shaped.lines.length > 0 ? /* @__PURE__ */ jsx("pre", {
2089
+ className: `${expanded ? "whitespace-pre-wrap break-words" : "truncate"} text-doom-dim`,
2090
+ children: shaped.lines.join("\n")
2091
+ }) : null,
2092
+ running ? /* @__PURE__ */ jsx(MessageItemStatus, {
2093
+ tone: "running",
2094
+ children: "waiting"
2095
+ }) : null,
2096
+ isError ? /* @__PURE__ */ jsx(MessageItemStatus, {
2097
+ tone: "error",
2098
+ children: "failed"
2099
+ }) : null,
2100
+ line ? /* @__PURE__ */ jsxs(MessageItemStatus, {
2101
+ tone: line.tone,
2102
+ children: [
2103
+ line.text,
2104
+ " ",
2105
+ /* @__PURE__ */ jsx("span", {
2106
+ className: "text-doom-cyan",
2107
+ children: outcome.who
2108
+ })
2109
+ ]
2110
+ }) : null,
2111
+ shaped.glyph === "more" ? /* @__PURE__ */ jsxs(MessageItemStatus, {
2112
+ expands: true,
2113
+ children: [shaped.hidden, " more line(s)"]
2114
+ }) : null
2115
+ ]
2116
+ })] });
2117
+ }
2118
+ });
2119
+ }
2120
+ //#endregion
2121
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/intercom.web.tsx
2122
+ /** The intercom tool's timeline card. The filename names the tool it renders. */
2123
+ var intercom_web_default = defineToolRenderer({ message: IntercomToolMessage });
2124
+ //#endregion
2125
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/_components/SubagentToolMessage.tsx
2126
+ const CLOSING = {
2127
+ running: {
2128
+ tone: "running",
2129
+ text: "Running"
2130
+ },
2131
+ failed: {
2132
+ tone: "error",
2133
+ text: "Failed"
2134
+ },
2135
+ done: {
2136
+ tone: "ok",
2137
+ text: "Done"
2138
+ }
2139
+ };
2140
+ const ROW_BADGE = {
2141
+ ok: "ok",
2142
+ error: "error",
2143
+ running: "running",
2144
+ idle: "neutral"
2145
+ };
2146
+ const STATUS_TONE = {
2147
+ ok: "ok",
2148
+ error: "error",
2149
+ running: "running",
2150
+ idle: "ok"
2151
+ };
2152
+ /** One structured row: the run or agent, what it is doing, and its state flushed right. */
2153
+ function SubagentRowLine({ row }) {
2154
+ return /* @__PURE__ */ jsxs("li", {
2155
+ "data-testid": "tool-result-subagent-row",
2156
+ className: "flex items-center gap-3 rounded-md px-2 py-1.5",
2157
+ children: [
2158
+ /* @__PURE__ */ jsx("span", {
2159
+ className: "shrink-0 text-base font-semibold text-doom-hi",
2160
+ children: row.label
2161
+ }),
2162
+ /* @__PURE__ */ jsx("span", {
2163
+ className: "min-w-0 flex-1 truncate text-doom-dim",
2164
+ children: row.detail
2165
+ }),
2166
+ row.state ? /* @__PURE__ */ jsx(StatusBadge, {
2167
+ tone: ROW_BADGE[row.tone],
2168
+ size: "md",
2169
+ className: ROW_BADGE[row.tone] === "neutral" ? "bg-doom-rail" : void 0,
2170
+ children: row.state
2171
+ }) : null
2172
+ ]
2173
+ });
2174
+ }
2175
+ /**
2176
+ * The subagent tool's timeline item, the web half of renderSubagentCall and
2177
+ * renderSubagentResult: the action and its per-action detail in the header,
2178
+ * the rows the result reported in the body, and the subagents tab one click
2179
+ * away. A result the narrower does not recognise, a partial one included,
2180
+ * falls back to the text the model was given.
2181
+ */
2182
+ function SubagentToolMessage({ args, result, output, running, isError, openTransientTab }) {
2183
+ const action = typeof args.action === "string" ? args.action : "";
2184
+ const detail = subagentCallDetail(args);
2185
+ const view = running || isError ? null : subagentResultView(result?.details);
2186
+ const hiddenRows = view ? Math.max(0, view.rows.length - 8) : 0;
2187
+ const collapsedText = view ? null : shapeResult(output, {
2188
+ expanded: false,
2189
+ isPartial: running,
2190
+ isError
2191
+ });
2192
+ return /* @__PURE__ */ jsx(MessageItem, {
2193
+ tone: toolTone({
2194
+ running,
2195
+ isError
2196
+ }),
2197
+ expandable: view ? hiddenRows > 0 : collapsedText?.glyph === "more",
2198
+ children: ({ expanded }) => {
2199
+ const shaped = view || !collapsedText ? null : expanded ? shapeResult(output, {
2200
+ expanded: true,
2201
+ isPartial: running,
2202
+ isError
2203
+ }) : collapsedText;
2204
+ const closing = !shaped || shaped.glyph === "none" || shaped.glyph === "more" ? null : CLOSING[shaped.glyph];
2205
+ const rows = view ? expanded ? view.rows : view.rows.slice(0, 8) : [];
2206
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MessageItemHeader, {
2207
+ title: "subagent",
2208
+ children: /* @__PURE__ */ jsxs("span", {
2209
+ "data-testid": "tool-call-subagent",
2210
+ className: "flex min-w-0 flex-1 items-center gap-2",
2211
+ children: [/* @__PURE__ */ jsx("span", {
2212
+ className: "text-doom-dim",
2213
+ children: action
2214
+ }), detail ? /* @__PURE__ */ jsx("span", {
2215
+ className: "min-w-0 truncate text-doom-dim",
2216
+ children: detail
2217
+ }) : null]
2218
+ })
2219
+ }), /* @__PURE__ */ jsxs(MessageItemBody, {
2220
+ "data-testid": "tool-result-subagent",
2221
+ className: "flex flex-col gap-1",
2222
+ children: [
2223
+ rows.length > 0 ? /* @__PURE__ */ jsx("ul", {
2224
+ className: "-mx-2 flex flex-col",
2225
+ children: rows.map((row) => /* @__PURE__ */ jsx(SubagentRowLine, { row }, row.key))
2226
+ }) : null,
2227
+ shaped && shaped.lines.length > 0 ? /* @__PURE__ */ jsx("pre", {
2228
+ className: `${expanded ? "whitespace-pre-wrap break-words" : "truncate"} text-doom-dim`,
2229
+ children: shaped.lines.join("\n")
2230
+ }) : null,
2231
+ !expanded && hiddenRows > 0 ? /* @__PURE__ */ jsxs(MessageItemStatus, {
2232
+ expands: true,
2233
+ children: [hiddenRows, " more"]
2234
+ }) : null,
2235
+ closing ? /* @__PURE__ */ jsx(MessageItemStatus, {
2236
+ tone: closing.tone,
2237
+ children: closing.text
2238
+ }) : null,
2239
+ shaped?.glyph === "more" ? /* @__PURE__ */ jsxs(MessageItemStatus, {
2240
+ expands: true,
2241
+ children: [shaped.hidden, " more line(s)"]
2242
+ }) : null,
2243
+ /* @__PURE__ */ jsxs("div", {
2244
+ className: `flex flex-wrap items-center gap-x-4 gap-y-1 ${rows.length > 0 ? "mt-1 border-t border-doom-border-soft pt-2.5" : ""}`,
2245
+ children: [view && view.summary ? /* @__PURE__ */ jsx(MessageItemStatus, {
2246
+ "data-testid": "tool-result-subagent-summary",
2247
+ tone: STATUS_TONE[view.tone],
2248
+ children: view.summary
2249
+ }) : null, /* @__PURE__ */ jsxs(Button, {
2250
+ variant: "link",
2251
+ size: "sm",
2252
+ "data-testid": "tool-result-subagent-open",
2253
+ onClick: () => openTransientTab(subagentsTab()),
2254
+ className: "h-auto gap-1 px-0 text-sm text-doom-green",
2255
+ children: ["Open subagents", /* @__PURE__ */ jsx(ArrowRightIcon, {
2256
+ "aria-hidden": true,
2257
+ className: "size-3.5"
2258
+ })]
2259
+ })]
2260
+ })
2261
+ ]
2262
+ })] });
2263
+ }
2264
+ });
2265
+ }
2266
+ //#endregion
2267
+ //#region src/extensions/workspaces/sessions/(frontend)/tool/subagent.web.tsx
2268
+ /** The subagent tool's timeline card. The filename names the tool it renders. */
2269
+ var subagent_web_default = defineToolRenderer({ message: SubagentToolMessage });
2270
+ //#endregion
2271
+ //#region generated/web.ts
2272
+ const via = (identity, value) => value === void 0 ? void 0 : {
2273
+ ...identity,
2274
+ ...value
2275
+ };
2276
+ const webPlugin = defineWebPlugin({
2277
+ id: "subagents",
2278
+ session: {
2279
+ get activityGroups() {
2280
+ return [via({ name: "agents" }, agents_web_default)];
2281
+ },
2282
+ get channels() {
2283
+ return [via({ channel: "subagent_catalog" }, subagent_catalog_web_default), via({ channel: "subagent_runs" }, subagent_runs_web_default)];
2284
+ },
2285
+ get contextActions() {
2286
+ return [via({ id: "launch-agent" }, launch_agent_web_default)];
2287
+ },
2288
+ get fills() {
2289
+ return [via({
2290
+ slot: "activity.agents",
2291
+ id: "agents"
2292
+ }, Agents_activity_agents_web_default)];
2293
+ },
2294
+ get leaderBindings() {
2295
+ return [via({ id: "subagents.catalog" }, catalog_web_default), via({ id: "subagents.fleet" }, fleet_web_default)];
2296
+ },
2297
+ get slots() {
2298
+ return [via({ slot: "subagents.run-actions" }, run_actions_web_default)];
2299
+ },
2300
+ get toolRenderers() {
2301
+ return [via({ tools: ["intercom"] }, intercom_web_default), via({ tools: ["subagent"] }, subagent_web_default)];
2302
+ }
2303
+ }
2304
+ });
2305
+ //#endregion
2306
+ export { webPlugin };
2307
+
2308
+ //# sourceMappingURL=web.mjs.map