@agimon-ai/po-team 0.0.0-alpha.0 → 0.0.0-alpha.2
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.
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
import { createApiClient, defineActivityGroup, defineApiRoutes, defineChannelFile, defineFill, defineNativePlugin, defineSessionStore, nativeApiTransport, sessionApiAddress } from "@agimon-ai/po-core/native";
|
|
2
|
+
import { Badge, Button, Dot, Input, ListRow, SegmentedControl, Sheet, SheetBody, SheetContent, SheetHeader, SheetTitle, Textarea, UsersIcon } from "@agimon-ai/po-native-components";
|
|
3
|
+
import { createElement, useCallback, useEffect, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { ScrollView } from "react-native-unistyles/components/native/ScrollView";
|
|
5
|
+
import { View } from "react-native-unistyles/components/native/View";
|
|
6
|
+
import { Text } from "react-native-unistyles/components/native/Text";
|
|
7
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/catalog.native.ts
|
|
10
|
+
const routes = defineApiRoutes({
|
|
11
|
+
catalog: {
|
|
12
|
+
method: "GET",
|
|
13
|
+
path: "/catalog"
|
|
14
|
+
},
|
|
15
|
+
run: {
|
|
16
|
+
method: "POST",
|
|
17
|
+
path: "/run"
|
|
18
|
+
},
|
|
19
|
+
steer: {
|
|
20
|
+
method: "POST",
|
|
21
|
+
path: "/steer"
|
|
22
|
+
},
|
|
23
|
+
stop: {
|
|
24
|
+
method: "POST",
|
|
25
|
+
path: "/stop"
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const api = createApiClient(routes, {
|
|
29
|
+
scopes: ["session"],
|
|
30
|
+
basePath: "team",
|
|
31
|
+
transport: nativeApiTransport,
|
|
32
|
+
sessionAddress: sessionApiAddress
|
|
33
|
+
});
|
|
34
|
+
function isRecord$2(value) {
|
|
35
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
function isStrings(value) {
|
|
38
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
39
|
+
}
|
|
40
|
+
function isAgent(value) {
|
|
41
|
+
return isRecord$2(value) && typeof value.name === "string" && typeof value.description === "string" && typeof value.filePath === "string" && [
|
|
42
|
+
"project",
|
|
43
|
+
"user",
|
|
44
|
+
"plugin"
|
|
45
|
+
].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");
|
|
46
|
+
}
|
|
47
|
+
/** Read the selected session's catalog through the host's authenticated HTTP transport. */
|
|
48
|
+
async function fetchCatalog(sessionId, signal) {
|
|
49
|
+
const response = await api.session(sessionId).catalog({ signal });
|
|
50
|
+
const body = response.data;
|
|
51
|
+
if (!response.ok) throw new Error(isRecord$2(body) && typeof body.error === "string" ? body.error : `Agent catalog could not be loaded (${response.status}).`);
|
|
52
|
+
if (!isRecord$2(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.");
|
|
53
|
+
return {
|
|
54
|
+
cwd: body.cwd,
|
|
55
|
+
agents: body.agents,
|
|
56
|
+
models: body.models
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** Launch on the selected session host, without submitting a parent prompt. */
|
|
60
|
+
async function launchAgent(sessionId, request) {
|
|
61
|
+
const response = await api.session(sessionId).run({ body: request });
|
|
62
|
+
const body = response.data;
|
|
63
|
+
if (!response.ok) throw new Error(isRecord$2(body) && typeof body.error === "string" ? body.error : `Agent could not be launched (${response.status}).`);
|
|
64
|
+
if (!isRecord$2(body) || typeof body.runId !== "string" || body.runId === "") throw new Error("The server returned an invalid run ID.");
|
|
65
|
+
return body.runId;
|
|
66
|
+
}
|
|
67
|
+
function isSteerState(value) {
|
|
68
|
+
return value === "delivered" || value === "failed" || value === "pending";
|
|
69
|
+
}
|
|
70
|
+
/** Steer one run on the selected session host, without submitting a parent prompt. */
|
|
71
|
+
async function steerRun(sessionId, runId, message) {
|
|
72
|
+
const response = await api.session(sessionId).steer({ body: {
|
|
73
|
+
runId,
|
|
74
|
+
message
|
|
75
|
+
} });
|
|
76
|
+
const body = response.data;
|
|
77
|
+
if (!response.ok) throw new Error(isRecord$2(body) && typeof body.error === "string" ? body.error : `Guidance could not be sent (${response.status}).`);
|
|
78
|
+
if (!isRecord$2(body) || !isSteerState(body.state) || typeof body.message !== "string") throw new Error("The server returned an invalid steer result.");
|
|
79
|
+
return {
|
|
80
|
+
state: body.state,
|
|
81
|
+
message: body.message
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** Ask the selected session host to stop one run; the run's own status reports when it has. */
|
|
85
|
+
async function stopRun(sessionId, runId) {
|
|
86
|
+
const response = await api.session(sessionId).stop({ body: { runId } });
|
|
87
|
+
const body = response.data;
|
|
88
|
+
if (!response.ok) throw new Error(isRecord$2(body) && typeof body.error === "string" ? body.error : `The run could not be stopped (${response.status}).`);
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/types/webSubagents.ts
|
|
92
|
+
const SUBAGENT_RUNS_TYPE = "subagent_runs";
|
|
93
|
+
const SUBAGENT_CATALOG_TYPE = "subagent_catalog";
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/catalogStore.native.ts
|
|
96
|
+
const catalog = defineSessionStore({
|
|
97
|
+
cwd: "",
|
|
98
|
+
agents: [],
|
|
99
|
+
models: [],
|
|
100
|
+
warning: void 0,
|
|
101
|
+
loading: false,
|
|
102
|
+
requestId: 0,
|
|
103
|
+
open: false,
|
|
104
|
+
filter: "",
|
|
105
|
+
task: "",
|
|
106
|
+
selected: void 0,
|
|
107
|
+
inspected: void 0,
|
|
108
|
+
launch: void 0
|
|
109
|
+
});
|
|
110
|
+
function isRecord$1(value) {
|
|
111
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
112
|
+
}
|
|
113
|
+
function openCatalog(sessionId, task = "") {
|
|
114
|
+
catalog.update(sessionId, (current) => ({
|
|
115
|
+
...current,
|
|
116
|
+
open: true,
|
|
117
|
+
task
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
function closeCatalog(sessionId) {
|
|
121
|
+
catalog.update(sessionId, (current) => ({
|
|
122
|
+
...current,
|
|
123
|
+
open: false,
|
|
124
|
+
task: "",
|
|
125
|
+
launch: void 0
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
/** Each drawer opening owns one request and cancels it when the drawer leaves. */
|
|
129
|
+
function loadCatalog(sessionId) {
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const requestId = catalog.select(catalog.store.state, sessionId).requestId + 1;
|
|
132
|
+
catalog.update(sessionId, (current) => ({
|
|
133
|
+
...current,
|
|
134
|
+
requestId,
|
|
135
|
+
loading: true,
|
|
136
|
+
warning: void 0
|
|
137
|
+
}));
|
|
138
|
+
const isCurrent = () => !controller.signal.aborted && catalog.store.state[sessionId]?.requestId === requestId;
|
|
139
|
+
fetchCatalog(sessionId, controller.signal).then((payload) => {
|
|
140
|
+
if (isCurrent()) subagentCatalogChannel.apply(sessionId, payload);
|
|
141
|
+
}).catch((error) => {
|
|
142
|
+
if (isCurrent()) catalog.update(sessionId, (current) => ({
|
|
143
|
+
...current,
|
|
144
|
+
warning: error instanceof Error ? error.message : String(error)
|
|
145
|
+
}));
|
|
146
|
+
}).finally(() => {
|
|
147
|
+
if (isCurrent()) catalog.update(sessionId, (current) => ({
|
|
148
|
+
...current,
|
|
149
|
+
loading: false
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
152
|
+
return () => {
|
|
153
|
+
if (isCurrent()) catalog.update(sessionId, (current) => ({
|
|
154
|
+
...current,
|
|
155
|
+
loading: false
|
|
156
|
+
}));
|
|
157
|
+
controller.abort();
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/** The plugin's catalog channel: 'subagent_catalog' payloads into the store; the drawer's own state survives a refresh. */
|
|
161
|
+
const subagentCatalogChannel = catalog.channel({
|
|
162
|
+
channel: SUBAGENT_CATALOG_TYPE,
|
|
163
|
+
parse(input) {
|
|
164
|
+
if (!isRecord$1(input) || typeof input.cwd !== "string" || !Array.isArray(input.agents)) return null;
|
|
165
|
+
const models = Array.isArray(input.models) ? input.models.filter((m) => typeof m === "string") : [];
|
|
166
|
+
return {
|
|
167
|
+
cwd: input.cwd,
|
|
168
|
+
agents: input.agents.filter(isRecord$1),
|
|
169
|
+
models,
|
|
170
|
+
...typeof input.warning === "string" ? { warning: input.warning } : {}
|
|
171
|
+
};
|
|
172
|
+
},
|
|
173
|
+
reduce(current, payload) {
|
|
174
|
+
const names = new Set(payload.agents.map((agent) => agent.name));
|
|
175
|
+
return {
|
|
176
|
+
...current,
|
|
177
|
+
cwd: payload.cwd,
|
|
178
|
+
agents: payload.agents,
|
|
179
|
+
models: payload.models,
|
|
180
|
+
warning: payload.warning,
|
|
181
|
+
selected: current.selected !== void 0 && names.has(current.selected) ? current.selected : void 0,
|
|
182
|
+
inspected: current.inspected !== void 0 && names.has(current.inspected) ? current.inspected : void 0,
|
|
183
|
+
launch: current.launch !== void 0 && names.has(current.launch.agent) ? current.launch : void 0
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/format.ts
|
|
189
|
+
/**
|
|
190
|
+
* Local copies of the host cockpit's pure formatters
|
|
191
|
+
* (po-web src/web/lib/sessionSummary.ts): this plugin ships zero host
|
|
192
|
+
* imports so the cockpit's bundler can compile it from any install.
|
|
193
|
+
*/
|
|
194
|
+
const MINUTE_MS = 6e4;
|
|
195
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
196
|
+
function formatRunDuration(elapsedMs) {
|
|
197
|
+
if (elapsedMs < MINUTE_MS) return "<1m";
|
|
198
|
+
if (elapsedMs < HOUR_MS) return `${Math.floor(elapsedMs / MINUTE_MS)}m`;
|
|
199
|
+
const hours = Math.floor(elapsedMs / HOUR_MS);
|
|
200
|
+
const minutes = Math.floor(elapsedMs % HOUR_MS / MINUTE_MS);
|
|
201
|
+
return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* What to call a run on screen.
|
|
205
|
+
*
|
|
206
|
+
* Prefers the generated identity, because a fan-out of one agent otherwise
|
|
207
|
+
* renders as N rows reading the same word. Falls back to the agent name for a
|
|
208
|
+
* run projected before identities existed. Duplicated rather than imported
|
|
209
|
+
* from `services/agentIdentity`, which reaches for `node:fs` and so cannot be
|
|
210
|
+
* pulled into the browser bundle; that module owns minting, this owns display.
|
|
211
|
+
*/
|
|
212
|
+
function agentRunLabel(run) {
|
|
213
|
+
const base = run.identity ?? run.agent;
|
|
214
|
+
return run.inline ? `${base} (inline)` : base;
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/launchCommand.ts
|
|
218
|
+
const META_TOOL_LIMIT = 3;
|
|
219
|
+
/** Rows whose name, source, package, description or a tool contains the filter, case-insensitively. */
|
|
220
|
+
function filterCatalog(agents, filter) {
|
|
221
|
+
const needle = filter.trim().toLowerCase();
|
|
222
|
+
if (needle === "") return [...agents];
|
|
223
|
+
return agents.filter((agent) => [
|
|
224
|
+
agent.name,
|
|
225
|
+
agent.source,
|
|
226
|
+
agent.packageName ?? "",
|
|
227
|
+
agent.description,
|
|
228
|
+
...agent.tools
|
|
229
|
+
].some((field) => field.toLowerCase().includes(needle)));
|
|
230
|
+
}
|
|
231
|
+
/** What the picker offers: the agent's own model and fallbacks first, then the team's, without repeats. */
|
|
232
|
+
function modelChoices(agent, models) {
|
|
233
|
+
const seen = /* @__PURE__ */ new Set();
|
|
234
|
+
const choices = [];
|
|
235
|
+
for (const model of [
|
|
236
|
+
agent.model ?? "",
|
|
237
|
+
...agent.fallbackModels,
|
|
238
|
+
...models
|
|
239
|
+
]) {
|
|
240
|
+
if (model === "" || seen.has(model)) continue;
|
|
241
|
+
seen.add(model);
|
|
242
|
+
choices.push(model);
|
|
243
|
+
}
|
|
244
|
+
return choices;
|
|
245
|
+
}
|
|
246
|
+
/** The row's one-liner: what tools it may use, how many skills, and how it starts. */
|
|
247
|
+
function agentMeta(agent) {
|
|
248
|
+
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"}`;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_lib/subagentsStore.native.ts
|
|
252
|
+
const TERMINAL_STATES = /* @__PURE__ */ new Set([
|
|
253
|
+
"done",
|
|
254
|
+
"failed",
|
|
255
|
+
"stopped"
|
|
256
|
+
]);
|
|
257
|
+
function isTerminalRun(run) {
|
|
258
|
+
return TERMINAL_STATES.has(run.state);
|
|
259
|
+
}
|
|
260
|
+
const subagents = defineSessionStore({
|
|
261
|
+
runs: [],
|
|
262
|
+
dismissed: [],
|
|
263
|
+
stopRequested: [],
|
|
264
|
+
openRunId: void 0,
|
|
265
|
+
pendingLaunch: void 0,
|
|
266
|
+
autoOpenRunId: void 0
|
|
267
|
+
});
|
|
268
|
+
function isRecord(value) {
|
|
269
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
270
|
+
}
|
|
271
|
+
function without(list, runId) {
|
|
272
|
+
return list.filter((id) => id !== runId);
|
|
273
|
+
}
|
|
274
|
+
/** The full Subagents tab's runs minus the ones the reader dismissed. */
|
|
275
|
+
function visibleRuns(session) {
|
|
276
|
+
return session.dismissed.length === 0 ? session.runs : session.runs.filter((run) => !session.dismissed.includes(run.runId));
|
|
277
|
+
}
|
|
278
|
+
/** Only runs that can still be doing work belong in the activity group. */
|
|
279
|
+
function activityRuns(session) {
|
|
280
|
+
return visibleRuns(session).filter((run) => run.state === "queued" || run.state === "running");
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Asks the session host to stop a run over the Team API, never as a parent
|
|
284
|
+
* prompt: the web host does not know Team's slash commands and refuses
|
|
285
|
+
* prompts mid-turn. Marked at once so the control reads "stopping"; rolled
|
|
286
|
+
* back and rethrown if the host refuses. Once accepted, the request stands
|
|
287
|
+
* until the run reports a final state.
|
|
288
|
+
*/
|
|
289
|
+
async function requestRunStop(sessionId, runId) {
|
|
290
|
+
subagents.update(sessionId, (current) => ({
|
|
291
|
+
...current,
|
|
292
|
+
stopRequested: [...without(current.stopRequested, runId), runId]
|
|
293
|
+
}));
|
|
294
|
+
try {
|
|
295
|
+
await stopRun(sessionId, runId);
|
|
296
|
+
} catch (error) {
|
|
297
|
+
subagents.update(sessionId, (current) => ({
|
|
298
|
+
...current,
|
|
299
|
+
stopRequested: without(current.stopRequested, runId)
|
|
300
|
+
}));
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/** Hides a finished run from this page; nothing is deleted. */
|
|
305
|
+
function dismissRun(sessionId, runId) {
|
|
306
|
+
subagents.update(sessionId, (current) => ({
|
|
307
|
+
...current,
|
|
308
|
+
dismissed: [...without(current.dismissed, runId), runId],
|
|
309
|
+
openRunId: current.openRunId === runId ? void 0 : current.openRunId
|
|
310
|
+
}));
|
|
311
|
+
}
|
|
312
|
+
/** Remember the fleet before the server launch so its first update can open the new run. */
|
|
313
|
+
function requestLaunch(sessionId, agent) {
|
|
314
|
+
subagents.update(sessionId, (current) => ({
|
|
315
|
+
...current,
|
|
316
|
+
pendingLaunch: {
|
|
317
|
+
agent,
|
|
318
|
+
knownRunIds: current.runs.map((run) => run.runId)
|
|
319
|
+
}
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
function clearPendingLaunch(sessionId) {
|
|
323
|
+
subagents.update(sessionId, (current) => ({
|
|
324
|
+
...current,
|
|
325
|
+
pendingLaunch: void 0
|
|
326
|
+
}));
|
|
327
|
+
}
|
|
328
|
+
/** The plugin's session data channel: 'subagent_runs' payloads into the store. */
|
|
329
|
+
const subagentRunsChannel = subagents.channel({
|
|
330
|
+
channel: SUBAGENT_RUNS_TYPE,
|
|
331
|
+
parse(input) {
|
|
332
|
+
if (!isRecord(input) || !Array.isArray(input.runs)) return null;
|
|
333
|
+
return { runs: input.runs.filter(isRecord) };
|
|
334
|
+
},
|
|
335
|
+
reduce(current, { runs }) {
|
|
336
|
+
const present = new Map(runs.map((run) => [run.runId, run]));
|
|
337
|
+
const stopRequested = current.stopRequested.filter((runId) => {
|
|
338
|
+
const run = present.get(runId);
|
|
339
|
+
return run !== void 0 && !isTerminalRun(run);
|
|
340
|
+
});
|
|
341
|
+
const dismissed = current.dismissed.filter((runId) => present.has(runId));
|
|
342
|
+
const arrived = current.pendingLaunch === void 0 ? void 0 : runs.find((run) => run.agent === current.pendingLaunch?.agent && !current.pendingLaunch.knownRunIds.includes(run.runId));
|
|
343
|
+
return {
|
|
344
|
+
...current,
|
|
345
|
+
runs,
|
|
346
|
+
stopRequested,
|
|
347
|
+
dismissed,
|
|
348
|
+
pendingLaunch: arrived === void 0 ? current.pendingLaunch : void 0,
|
|
349
|
+
autoOpenRunId: arrived === void 0 ? current.autoOpenRunId : arrived.runId
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
//#endregion
|
|
354
|
+
//#region src/extensions/workspaces/sessions/(frontend)/_components/SubagentsPanel.native.tsx
|
|
355
|
+
function useSubagents(sessionId) {
|
|
356
|
+
const subscribe = useCallback((listener) => {
|
|
357
|
+
const sub = subagents.store.subscribe(listener);
|
|
358
|
+
return () => sub.unsubscribe();
|
|
359
|
+
}, []);
|
|
360
|
+
return useSyncExternalStore(subscribe, () => subagents.select(subagents.store.state, sessionId));
|
|
361
|
+
}
|
|
362
|
+
function subagentsTab() {
|
|
363
|
+
return {
|
|
364
|
+
id: "subagents",
|
|
365
|
+
label: "agents",
|
|
366
|
+
panel: SubagentsPanel
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function agentThreadTab(run) {
|
|
370
|
+
const runId = run.runId;
|
|
371
|
+
return {
|
|
372
|
+
id: `subagents-run-${runId}`,
|
|
373
|
+
label: agentRunLabel(run),
|
|
374
|
+
panel: (props) => /* @__PURE__ */ jsx(AgentThreadPanel, {
|
|
375
|
+
...props,
|
|
376
|
+
runId
|
|
377
|
+
})
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function AgentRunRow({ run, onPress }) {
|
|
381
|
+
const detail = run.error ?? run.summary?.split("\n").find((line) => line.trim() !== "") ?? run.currentTool ?? run.tail.at(-1) ?? run.state;
|
|
382
|
+
return /* @__PURE__ */ jsx(ListRow, {
|
|
383
|
+
leading: /* @__PURE__ */ jsx(Dot, { tone: run.state === "failed" ? "red" : run.state === "running" ? "yellow" : run.state === "done" ? "green" : "muted" }),
|
|
384
|
+
title: /* @__PURE__ */ jsx(Button, {
|
|
385
|
+
variant: "ghost",
|
|
386
|
+
onPress,
|
|
387
|
+
testID: `activity-run-${run.runId}`,
|
|
388
|
+
children: agentRunLabel(run)
|
|
389
|
+
}),
|
|
390
|
+
description: detail,
|
|
391
|
+
trailing: /* @__PURE__ */ jsx(Badge, { children: run.state })
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
function SubagentsPanel(props) {
|
|
395
|
+
const state = useSubagents(props.sessionId);
|
|
396
|
+
return /* @__PURE__ */ jsxs(ScrollView, {
|
|
397
|
+
contentContainerStyle: styles.body,
|
|
398
|
+
children: [
|
|
399
|
+
/* @__PURE__ */ jsx(Button, {
|
|
400
|
+
testID: "activity-agents-launch",
|
|
401
|
+
onPress: () => {
|
|
402
|
+
if (props.sessionId !== null) openCatalog(props.sessionId);
|
|
403
|
+
},
|
|
404
|
+
children: "Launch an agent"
|
|
405
|
+
}),
|
|
406
|
+
visibleRuns(state).map((run) => /* @__PURE__ */ jsx(AgentRunRow, {
|
|
407
|
+
run,
|
|
408
|
+
onPress: () => props.openTransientTab(agentThreadTab(run))
|
|
409
|
+
}, run.runId)),
|
|
410
|
+
/* @__PURE__ */ jsx(AgentCatalog, { ...props })
|
|
411
|
+
]
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
function AgentCatalog({ sessionId }) {
|
|
415
|
+
const subscribe = useCallback((listener) => {
|
|
416
|
+
const sub = catalog.store.subscribe(listener);
|
|
417
|
+
return () => sub.unsubscribe();
|
|
418
|
+
}, []);
|
|
419
|
+
const state = useSyncExternalStore(subscribe, () => catalog.select(catalog.store.state, sessionId));
|
|
420
|
+
const [filter, setFilter] = useState("");
|
|
421
|
+
const [agent, setAgent] = useState();
|
|
422
|
+
useEffect(() => {
|
|
423
|
+
if (sessionId !== null && state.open) return loadCatalog(sessionId);
|
|
424
|
+
}, [sessionId, state.open]);
|
|
425
|
+
if (sessionId === null || !state.open) return null;
|
|
426
|
+
return /* @__PURE__ */ jsx(Sheet, {
|
|
427
|
+
open: true,
|
|
428
|
+
onOpenChange: (open) => {
|
|
429
|
+
if (!open) {
|
|
430
|
+
closeCatalog(sessionId);
|
|
431
|
+
setAgent(void 0);
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
children: /* @__PURE__ */ jsxs(SheetContent, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: agent ? `Launch ${agent.name}` : "Launch an agent" }) }), /* @__PURE__ */ jsx(SheetBody, { children: agent ? /* @__PURE__ */ jsx(AgentLaunch, {
|
|
435
|
+
sessionId,
|
|
436
|
+
agent,
|
|
437
|
+
models: state.models,
|
|
438
|
+
initialTask: state.task,
|
|
439
|
+
onClose: () => {
|
|
440
|
+
closeCatalog(sessionId);
|
|
441
|
+
setAgent(void 0);
|
|
442
|
+
}
|
|
443
|
+
}, agent.name) : /* @__PURE__ */ jsxs(View, {
|
|
444
|
+
style: styles.body,
|
|
445
|
+
children: [
|
|
446
|
+
/* @__PURE__ */ jsx(Input, {
|
|
447
|
+
accessibilityLabel: "Filter agents",
|
|
448
|
+
placeholder: "Find an agent",
|
|
449
|
+
value: filter,
|
|
450
|
+
onChangeText: setFilter
|
|
451
|
+
}),
|
|
452
|
+
state.loading ? /* @__PURE__ */ jsx(Text, {
|
|
453
|
+
style: styles.meta,
|
|
454
|
+
children: "Loading agents…"
|
|
455
|
+
}) : null,
|
|
456
|
+
state.warning ? /* @__PURE__ */ jsx(Text, {
|
|
457
|
+
style: styles.error,
|
|
458
|
+
children: state.warning
|
|
459
|
+
}) : null,
|
|
460
|
+
filterCatalog(state.agents, filter).map((item) => /* @__PURE__ */ jsx(ListRow, {
|
|
461
|
+
title: /* @__PURE__ */ jsx(Button, {
|
|
462
|
+
variant: "ghost",
|
|
463
|
+
onPress: () => setAgent(item),
|
|
464
|
+
children: item.name
|
|
465
|
+
}),
|
|
466
|
+
description: `${item.description}\n${agentMeta(item)}`
|
|
467
|
+
}, item.name))
|
|
468
|
+
]
|
|
469
|
+
}) })] })
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
function AgentLaunch({ sessionId, agent, models, initialTask, onClose }) {
|
|
473
|
+
const [task, setTask] = useState(initialTask);
|
|
474
|
+
const [fork, setFork] = useState(agent.defaultContext === "fork");
|
|
475
|
+
const [model, setModel] = useState("");
|
|
476
|
+
const [busy, setBusy] = useState(false);
|
|
477
|
+
const [error, setError] = useState();
|
|
478
|
+
const launch = async () => {
|
|
479
|
+
if (busy) return;
|
|
480
|
+
setBusy(true);
|
|
481
|
+
setError(void 0);
|
|
482
|
+
requestLaunch(sessionId, agent.name);
|
|
483
|
+
try {
|
|
484
|
+
await launchAgent(sessionId, {
|
|
485
|
+
agent: agent.name,
|
|
486
|
+
task,
|
|
487
|
+
fork,
|
|
488
|
+
...model === "" ? {} : { model }
|
|
489
|
+
});
|
|
490
|
+
onClose();
|
|
491
|
+
} catch (cause) {
|
|
492
|
+
clearPendingLaunch(sessionId);
|
|
493
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
494
|
+
} finally {
|
|
495
|
+
setBusy(false);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
499
|
+
style: styles.body,
|
|
500
|
+
children: [
|
|
501
|
+
/* @__PURE__ */ jsx(Text, {
|
|
502
|
+
style: styles.meta,
|
|
503
|
+
children: agent.description
|
|
504
|
+
}),
|
|
505
|
+
/* @__PURE__ */ jsx(Textarea, {
|
|
506
|
+
accessibilityLabel: "Task",
|
|
507
|
+
value: task,
|
|
508
|
+
onChangeText: setTask,
|
|
509
|
+
placeholder: "What should the agent do? Empty runs its own prompt"
|
|
510
|
+
}),
|
|
511
|
+
/* @__PURE__ */ jsx(SegmentedControl, {
|
|
512
|
+
value: fork ? "fork" : "fresh",
|
|
513
|
+
onValueChange: (value) => setFork(value === "fork"),
|
|
514
|
+
options: [{
|
|
515
|
+
value: "fresh",
|
|
516
|
+
label: "Fresh"
|
|
517
|
+
}, {
|
|
518
|
+
value: "fork",
|
|
519
|
+
label: "Fork this session"
|
|
520
|
+
}]
|
|
521
|
+
}),
|
|
522
|
+
/* @__PURE__ */ jsx(Text, {
|
|
523
|
+
style: styles.meta,
|
|
524
|
+
children: "Model"
|
|
525
|
+
}),
|
|
526
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
527
|
+
variant: model === "" ? "primary" : "outline",
|
|
528
|
+
onPress: () => setModel(""),
|
|
529
|
+
children: ["Agent default", agent.model ? ` · ${agent.model}` : ""]
|
|
530
|
+
}),
|
|
531
|
+
modelChoices(agent, models).map((choice) => /* @__PURE__ */ jsx(Button, {
|
|
532
|
+
variant: "outline",
|
|
533
|
+
onPress: () => setModel(choice),
|
|
534
|
+
accessibilityState: { selected: model === choice },
|
|
535
|
+
children: choice
|
|
536
|
+
}, choice)),
|
|
537
|
+
error ? /* @__PURE__ */ jsx(Text, {
|
|
538
|
+
style: styles.error,
|
|
539
|
+
children: error
|
|
540
|
+
}) : null,
|
|
541
|
+
/* @__PURE__ */ jsx(Button, {
|
|
542
|
+
testID: "launch-agent-submit",
|
|
543
|
+
disabled: busy,
|
|
544
|
+
onPress: () => void launch(),
|
|
545
|
+
children: busy ? "Launching…" : "Launch"
|
|
546
|
+
})
|
|
547
|
+
]
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
function AgentThreadPanel({ sessionId, runId, renderThread }) {
|
|
551
|
+
const state = useSubagents(sessionId);
|
|
552
|
+
const run = state.runs.find((item) => item.runId === runId);
|
|
553
|
+
const [guidance, setGuidance] = useState("");
|
|
554
|
+
const [busy, setBusy] = useState(false);
|
|
555
|
+
const [notice, setNotice] = useState();
|
|
556
|
+
const [confirmClear, setConfirmClear] = useState(false);
|
|
557
|
+
const act = async (action) => {
|
|
558
|
+
setBusy(true);
|
|
559
|
+
setNotice(void 0);
|
|
560
|
+
try {
|
|
561
|
+
await action();
|
|
562
|
+
} catch (cause) {
|
|
563
|
+
setNotice(cause instanceof Error ? cause.message : String(cause));
|
|
564
|
+
} finally {
|
|
565
|
+
setBusy(false);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
return /* @__PURE__ */ jsxs(View, {
|
|
569
|
+
style: styles.screen,
|
|
570
|
+
children: [
|
|
571
|
+
run ? /* @__PURE__ */ jsxs(View, {
|
|
572
|
+
style: styles.body,
|
|
573
|
+
children: [
|
|
574
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
575
|
+
style: styles.title,
|
|
576
|
+
children: [
|
|
577
|
+
agentRunLabel(run),
|
|
578
|
+
" · ",
|
|
579
|
+
run.state
|
|
580
|
+
]
|
|
581
|
+
}),
|
|
582
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
583
|
+
style: styles.meta,
|
|
584
|
+
children: [
|
|
585
|
+
run.model,
|
|
586
|
+
"\\n",
|
|
587
|
+
run.task,
|
|
588
|
+
"\\n",
|
|
589
|
+
formatRunDuration(Math.max(0, (run.endedAt ?? run.lastUpdate) - run.startedAt))
|
|
590
|
+
]
|
|
591
|
+
}),
|
|
592
|
+
sessionId !== null ? isTerminalRun(run) ? /* @__PURE__ */ jsx(Button, {
|
|
593
|
+
variant: "outline",
|
|
594
|
+
onPress: () => setConfirmClear(true),
|
|
595
|
+
children: "Clear run"
|
|
596
|
+
}) : /* @__PURE__ */ jsx(Button, {
|
|
597
|
+
variant: "danger-outline",
|
|
598
|
+
disabled: busy || state.stopRequested.includes(runId),
|
|
599
|
+
onPress: () => void act(() => requestRunStop(sessionId, runId)),
|
|
600
|
+
children: state.stopRequested.includes(runId) ? "Stopping…" : "Stop"
|
|
601
|
+
}) : null
|
|
602
|
+
]
|
|
603
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
604
|
+
style: styles.meta,
|
|
605
|
+
children: "This run is no longer listed; its transcript stays readable."
|
|
606
|
+
}),
|
|
607
|
+
/* @__PURE__ */ jsx(View, {
|
|
608
|
+
style: styles.transcript,
|
|
609
|
+
children: renderThread(runId)
|
|
610
|
+
}),
|
|
611
|
+
run && sessionId !== null && !isTerminalRun(run) ? /* @__PURE__ */ jsxs(View, {
|
|
612
|
+
style: styles.body,
|
|
613
|
+
children: [/* @__PURE__ */ jsx(Textarea, {
|
|
614
|
+
accessibilityLabel: "Steering guidance",
|
|
615
|
+
value: guidance,
|
|
616
|
+
onChangeText: setGuidance,
|
|
617
|
+
placeholder: "Guide this agent…"
|
|
618
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
619
|
+
disabled: busy || guidance.trim() === "",
|
|
620
|
+
onPress: () => void act(async () => {
|
|
621
|
+
const result = await steerRun(sessionId, runId, guidance.trim());
|
|
622
|
+
setNotice(result.message);
|
|
623
|
+
if (result.state !== "failed") setGuidance("");
|
|
624
|
+
}),
|
|
625
|
+
children: "Send guidance"
|
|
626
|
+
})]
|
|
627
|
+
}) : null,
|
|
628
|
+
notice ? /* @__PURE__ */ jsx(Text, {
|
|
629
|
+
style: styles.meta,
|
|
630
|
+
children: notice
|
|
631
|
+
}) : null,
|
|
632
|
+
/* @__PURE__ */ jsx(Sheet, {
|
|
633
|
+
open: confirmClear,
|
|
634
|
+
onOpenChange: setConfirmClear,
|
|
635
|
+
children: /* @__PURE__ */ jsxs(SheetContent, { children: [/* @__PURE__ */ jsx(SheetHeader, { children: /* @__PURE__ */ jsx(SheetTitle, { children: "Clear this run?" }) }), /* @__PURE__ */ jsxs(SheetBody, { children: [/* @__PURE__ */ jsx(Text, {
|
|
636
|
+
style: styles.meta,
|
|
637
|
+
children: "This hides the finished run. Its transcript stays on disk."
|
|
638
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
639
|
+
variant: "danger-outline",
|
|
640
|
+
onPress: () => {
|
|
641
|
+
if (sessionId !== null) dismissRun(sessionId, runId);
|
|
642
|
+
setConfirmClear(false);
|
|
643
|
+
},
|
|
644
|
+
children: "Clear"
|
|
645
|
+
})] })] })
|
|
646
|
+
})
|
|
647
|
+
]
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
const styles = StyleSheet.create((theme) => ({
|
|
651
|
+
screen: {
|
|
652
|
+
flex: 1,
|
|
653
|
+
backgroundColor: theme.colors.bg,
|
|
654
|
+
uni__dependencies: [0]
|
|
655
|
+
},
|
|
656
|
+
transcript: { flex: 1 },
|
|
657
|
+
body: {
|
|
658
|
+
padding: theme.spacing(3),
|
|
659
|
+
gap: theme.spacing(2),
|
|
660
|
+
uni__dependencies: [0]
|
|
661
|
+
},
|
|
662
|
+
title: {
|
|
663
|
+
color: theme.colors.hi,
|
|
664
|
+
fontSize: theme.fontSize.base,
|
|
665
|
+
fontWeight: "600",
|
|
666
|
+
uni__dependencies: [0]
|
|
667
|
+
},
|
|
668
|
+
meta: {
|
|
669
|
+
color: theme.colors.dim,
|
|
670
|
+
fontSize: theme.fontSize.sm,
|
|
671
|
+
uni__dependencies: [0]
|
|
672
|
+
},
|
|
673
|
+
error: {
|
|
674
|
+
color: theme.colors.red,
|
|
675
|
+
fontSize: theme.fontSize.sm,
|
|
676
|
+
uni__dependencies: [0]
|
|
677
|
+
}
|
|
678
|
+
}));
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region src/extensions/workspaces/sessions/(frontend)/activity-group/agents.native.ts
|
|
681
|
+
var agents_native_default = defineActivityGroup({
|
|
682
|
+
keys: "a r",
|
|
683
|
+
icon: () => createElement(UsersIcon),
|
|
684
|
+
statusKey: "doom-team-agents",
|
|
685
|
+
order: 10,
|
|
686
|
+
transientTab: subagentsTab,
|
|
687
|
+
activeSource: {
|
|
688
|
+
subscribe(listener) {
|
|
689
|
+
const subscription = subagents.store.subscribe(listener);
|
|
690
|
+
return () => subscription.unsubscribe();
|
|
691
|
+
},
|
|
692
|
+
isActive(sessionId) {
|
|
693
|
+
return activityRuns(subagents.select(subagents.store.state, sessionId)).length > 0;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
//#endregion
|
|
698
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/subagent-catalog.native.ts
|
|
699
|
+
var subagent_catalog_native_default = defineChannelFile(subagentCatalogChannel);
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/extensions/workspaces/sessions/(frontend)/channel/subagent-runs.native.ts
|
|
702
|
+
var subagent_runs_native_default = defineChannelFile(subagentRunsChannel);
|
|
703
|
+
//#endregion
|
|
704
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/_components/AgentsActivitySection.native.tsx
|
|
705
|
+
function AgentsActivitySection({ sessionId, openTransientTab }) {
|
|
706
|
+
const runs = activityRuns(useSubagents(sessionId));
|
|
707
|
+
return /* @__PURE__ */ jsxs(View, { children: [runs.map((run) => /* @__PURE__ */ jsx(AgentRunRow, {
|
|
708
|
+
run,
|
|
709
|
+
onPress: () => openTransientTab(agentThreadTab(run))
|
|
710
|
+
}, run.runId)), runs.length === 0 ? /* @__PURE__ */ jsx(Button, {
|
|
711
|
+
variant: "ghost",
|
|
712
|
+
testID: "activity-agents-launch",
|
|
713
|
+
disabled: sessionId === null,
|
|
714
|
+
onPress: () => {
|
|
715
|
+
if (sessionId !== null) {
|
|
716
|
+
openCatalog(sessionId);
|
|
717
|
+
openTransientTab(subagentsTab());
|
|
718
|
+
}
|
|
719
|
+
},
|
|
720
|
+
children: "Launch an agent"
|
|
721
|
+
}) : null] });
|
|
722
|
+
}
|
|
723
|
+
//#endregion
|
|
724
|
+
//#region src/extensions/workspaces/sessions/(frontend)/fill/Agents.activity.agents.native.tsx
|
|
725
|
+
var Agents_activity_agents_native_default = defineFill({ component: AgentsActivitySection });
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region generated/native.ts
|
|
728
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
729
|
+
...identity,
|
|
730
|
+
...value
|
|
731
|
+
};
|
|
732
|
+
const nativePlugin = defineNativePlugin({
|
|
733
|
+
id: "subagents",
|
|
734
|
+
session: {
|
|
735
|
+
get activityGroups() {
|
|
736
|
+
return [via({ name: "agents" }, agents_native_default)];
|
|
737
|
+
},
|
|
738
|
+
get channels() {
|
|
739
|
+
return [via({ channel: "subagent_catalog" }, subagent_catalog_native_default), via({ channel: "subagent_runs" }, subagent_runs_native_default)];
|
|
740
|
+
},
|
|
741
|
+
get fills() {
|
|
742
|
+
return [via({
|
|
743
|
+
slot: "activity.agents",
|
|
744
|
+
id: "agents"
|
|
745
|
+
}, Agents_activity_agents_native_default)];
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
//#endregion
|
|
750
|
+
export { nativePlugin };
|
|
751
|
+
|
|
752
|
+
//# sourceMappingURL=native.mjs.map
|