@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.
- package/LICENSE +25 -0
- package/README.md +156 -0
- package/dist/agentDiscovery-Whwzc4dP.cjs +1637 -0
- package/dist/agentDiscovery-Whwzc4dP.cjs.map +1 -0
- package/dist/agentDiscovery-ZQlFkKvB.mjs +1529 -0
- package/dist/agentDiscovery-ZQlFkKvB.mjs.map +1 -0
- package/dist/apiContracts.cjs +162 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +157 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/atomicJson-D0346bt5.cjs +178 -0
- package/dist/atomicJson-D0346bt5.cjs.map +1 -0
- package/dist/atomicJson-D2t0CDh9.mjs +137 -0
- package/dist/atomicJson-D2t0CDh9.mjs.map +1 -0
- package/dist/capabilityCeiling-C-Y7H8oC.cjs +191 -0
- package/dist/capabilityCeiling-C-Y7H8oC.cjs.map +1 -0
- package/dist/capabilityCeiling-CF7kAS18.mjs +156 -0
- package/dist/capabilityCeiling-CF7kAS18.mjs.map +1 -0
- package/dist/capabilityCeiling.cjs +10 -0
- package/dist/capabilityCeiling.d.mts +46 -0
- package/dist/capabilityCeiling.d.mts.map +1 -0
- package/dist/capabilityCeiling.mjs +3 -0
- package/dist/delegation.cjs +49 -0
- package/dist/delegation.cjs.map +1 -0
- package/dist/delegation.d.mts +269 -0
- package/dist/delegation.d.mts.map +1 -0
- package/dist/delegation.mjs +34 -0
- package/dist/delegation.mjs.map +1 -0
- package/dist/delegationApi.cjs +92 -0
- package/dist/delegationApi.d.mts +2 -0
- package/dist/delegationApi.mjs +2 -0
- package/dist/env.cjs +62 -0
- package/dist/env.d.mts +2 -0
- package/dist/env.mjs +2 -0
- package/dist/environment-CaCW3SgT.cjs +320 -0
- package/dist/environment-CaCW3SgT.cjs.map +1 -0
- package/dist/environment-DrqhrxG_.mjs +75 -0
- package/dist/environment-DrqhrxG_.mjs.map +1 -0
- package/dist/environment-ma5oQ7uM.d.mts +63 -0
- package/dist/environment-ma5oQ7uM.d.mts.map +1 -0
- package/dist/extensions/server.cjs +6940 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +6932 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +2308 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/externalProcessIpc-C-15lJrB.mjs +584 -0
- package/dist/externalProcessIpc-C-15lJrB.mjs.map +1 -0
- package/dist/externalProcessIpc-ojE0pvw0.cjs +690 -0
- package/dist/externalProcessIpc-ojE0pvw0.cjs.map +1 -0
- package/dist/fablePlan.cjs +17 -0
- package/dist/fablePlan.d.mts +2 -0
- package/dist/fablePlan.mjs +2 -0
- package/dist/fablePlanApi-BsHhgVFV.mjs +118 -0
- package/dist/fablePlanApi-BsHhgVFV.mjs.map +1 -0
- package/dist/fablePlanApi-CZ3uUcMi.d.mts +97 -0
- package/dist/fablePlanApi-CZ3uUcMi.d.mts.map +1 -0
- package/dist/fablePlanApi-wBu9bRXU.cjs +207 -0
- package/dist/fablePlanApi-wBu9bRXU.cjs.map +1 -0
- package/dist/fablePlanApi.cjs +17 -0
- package/dist/fablePlanApi.d.mts +2 -0
- package/dist/fablePlanApi.mjs +2 -0
- package/dist/index.cjs +49 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +160 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +45 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.cjs +539 -0
- package/dist/runs/background/cliRunnerEntry.cjs.map +1 -0
- package/dist/runs/background/cliRunnerEntry.d.mts +1 -0
- package/dist/runs/background/cliRunnerEntry.mjs +537 -0
- package/dist/runs/background/cliRunnerEntry.mjs.map +1 -0
- package/dist/runtimeSubagentPolicy.cjs +5 -0
- package/dist/runtimeSubagentPolicy.d.mts +2 -0
- package/dist/runtimeSubagentPolicy.mjs +2 -0
- package/dist/runtimeSubagentTool.cjs +11 -0
- package/dist/runtimeSubagentTool.d.mts +2 -0
- package/dist/runtimeSubagentTool.mjs +2 -0
- package/dist/subagentPolicy-ALJam4GN.d.mts +33 -0
- package/dist/subagentPolicy-ALJam4GN.d.mts.map +1 -0
- package/dist/subagentPolicy-DQRyKDH-.cjs +39 -0
- package/dist/subagentPolicy-DQRyKDH-.cjs.map +1 -0
- package/dist/subagentPolicy-DxjsKeyj.mjs +22 -0
- package/dist/subagentPolicy-DxjsKeyj.mjs.map +1 -0
- package/dist/subagentPolicy.cjs +5 -0
- package/dist/subagentPolicy.d.mts +2 -0
- package/dist/subagentPolicy.mjs +2 -0
- package/dist/subagentTool-BTssGLw8.d.mts +103 -0
- package/dist/subagentTool-BTssGLw8.d.mts.map +1 -0
- package/dist/subagentTool-Ca2zQXKw.cjs +227 -0
- package/dist/subagentTool-Ca2zQXKw.cjs.map +1 -0
- package/dist/subagentTool-WOYrjxgT.mjs +186 -0
- package/dist/subagentTool-WOYrjxgT.mjs.map +1 -0
- package/dist/subagentTool.cjs +10 -0
- package/dist/subagentTool.d.mts +2 -0
- package/dist/subagentTool.mjs +2 -0
- package/dist/teamSnapshot-DgaANhBs.mjs +24 -0
- package/dist/teamSnapshot-DgaANhBs.mjs.map +1 -0
- package/dist/teamSnapshot-oKuGFdYc.cjs +35 -0
- package/dist/teamSnapshot-oKuGFdYc.cjs.map +1 -0
- package/dist/teamSnapshot.cjs +4 -0
- package/dist/teamSnapshot.d.mts +59 -0
- package/dist/teamSnapshot.d.mts.map +1 -0
- package/dist/teamSnapshot.mjs +2 -0
- 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
|