@aparte/plugin-subagents 0.0.0-stage → 1.6.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 +21 -0
- package/README.md +89 -2
- package/dist/aparte-subagent-view.d.ts +129 -0
- package/dist/aparte-subagent-view.d.ts.map +1 -0
- package/dist/custom-elements.json +1438 -0
- package/dist/drawing.d.ts +44 -0
- package/dist/drawing.d.ts.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/index.node.d.ts +16 -0
- package/dist/index.node.d.ts.map +1 -0
- package/dist/index.node.js +9 -0
- package/dist/index.node.js.map +1 -0
- package/dist/lanes.d.ts +42 -0
- package/dist/lanes.d.ts.map +1 -0
- package/dist/line.d.ts +24 -0
- package/dist/line.d.ts.map +1 -0
- package/dist/segment.d.ts +20 -0
- package/dist/segment.d.ts.map +1 -0
- package/dist/setup-CaTzFtu8.js +1164 -0
- package/dist/setup-CaTzFtu8.js.map +1 -0
- package/dist/setup.d.ts +23 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/status.d.ts +24 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/styles.d.ts +5 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/transcript.d.ts +28 -0
- package/dist/transcript.d.ts.map +1 -0
- package/dist/types.d.ts +103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/words.d.ts +62 -0
- package/dist/words.d.ts.map +1 -0
- package/package.json +92 -3
|
@@ -0,0 +1,1164 @@
|
|
|
1
|
+
import { createSegmentList, createStreamAdapter, runWithConfig, getSegmentRenderer, installDefaultRenderersOnce, deferHydrationOf, subscribeConfigChange, resolveConfig, contextConfig, pluralize, formatDuration, segmentTiming, aparteGlobalConfig, registerSegmentRenderer, unregisterSegmentRenderer } from "@aparte/core";
|
|
2
|
+
const SUBAGENT_SEGMENT_TYPE = "subagent";
|
|
3
|
+
const STATUSES = /* @__PURE__ */ new Set(["running", "waiting", "done", "failed", "stopped"]);
|
|
4
|
+
function subagentOf(segment) {
|
|
5
|
+
const value = segment.subagent;
|
|
6
|
+
if (!value || typeof value !== "object") return void 0;
|
|
7
|
+
const sub = value;
|
|
8
|
+
if (typeof sub.id !== "string") return void 0;
|
|
9
|
+
if (segment.type !== "tool_call" && segment.type !== SUBAGENT_SEGMENT_TYPE) return void 0;
|
|
10
|
+
return {
|
|
11
|
+
...sub,
|
|
12
|
+
id: sub.id,
|
|
13
|
+
status: STATUSES.has(sub.status) ? sub.status : "stopped",
|
|
14
|
+
segments: Array.isArray(sub.segments) ? sub.segments : []
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function anchorSettled(segment) {
|
|
18
|
+
if (segment.type === "tool_call") {
|
|
19
|
+
const status = segment.status;
|
|
20
|
+
return status !== "pending" && status !== "awaiting-approval";
|
|
21
|
+
}
|
|
22
|
+
return segment.isStreaming !== true;
|
|
23
|
+
}
|
|
24
|
+
function subagentStatus(segment) {
|
|
25
|
+
const sub = subagentOf(segment);
|
|
26
|
+
if (!sub) return "stopped";
|
|
27
|
+
if ((sub.status === "running" || sub.status === "waiting") && anchorSettled(segment)) return "stopped";
|
|
28
|
+
return sub.status;
|
|
29
|
+
}
|
|
30
|
+
function statusOfCall(status) {
|
|
31
|
+
switch (status ?? "pending") {
|
|
32
|
+
case "pending":
|
|
33
|
+
return "running";
|
|
34
|
+
case "awaiting-approval":
|
|
35
|
+
return "waiting";
|
|
36
|
+
case "resolved":
|
|
37
|
+
return "done";
|
|
38
|
+
case "failed":
|
|
39
|
+
return "failed";
|
|
40
|
+
default:
|
|
41
|
+
return "stopped";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function callStatusOf(status) {
|
|
45
|
+
switch (status) {
|
|
46
|
+
case "running":
|
|
47
|
+
return "pending";
|
|
48
|
+
case "waiting":
|
|
49
|
+
return "awaiting-approval";
|
|
50
|
+
case "done":
|
|
51
|
+
return "resolved";
|
|
52
|
+
case "failed":
|
|
53
|
+
return "failed";
|
|
54
|
+
default:
|
|
55
|
+
return "aborted";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const MAX_RUNS = 64;
|
|
59
|
+
const MAX_LANES = 512;
|
|
60
|
+
const MAX_EARLY = 2e3;
|
|
61
|
+
const COALESCED = /* @__PURE__ */ new Set(["text-delta", "thinking-delta", "sources", "text-flush", "turn-start"]);
|
|
62
|
+
const isOpen = (status) => (status ?? "pending") === "pending" || status === "awaiting-approval";
|
|
63
|
+
const laneWatchers = /* @__PURE__ */ new Set();
|
|
64
|
+
function watchLaneWrites(fn) {
|
|
65
|
+
laneWatchers.add(fn);
|
|
66
|
+
return () => {
|
|
67
|
+
laneWatchers.delete(fn);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const claims = /* @__PURE__ */ new WeakMap();
|
|
71
|
+
function claim(target, messageId, owner) {
|
|
72
|
+
let byMessage = claims.get(target);
|
|
73
|
+
if (!byMessage) claims.set(target, byMessage = /* @__PURE__ */ new Map());
|
|
74
|
+
const holder2 = byMessage.get(messageId);
|
|
75
|
+
if (holder2) return holder2 === owner;
|
|
76
|
+
byMessage.set(messageId, owner);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
function release(target, messageId, owner) {
|
|
80
|
+
const byMessage = claims.get(target);
|
|
81
|
+
if (byMessage?.get(messageId) === owner) byMessage.delete(messageId);
|
|
82
|
+
}
|
|
83
|
+
function nextFrame(fn) {
|
|
84
|
+
if (typeof requestAnimationFrame === "function") {
|
|
85
|
+
const handle2 = requestAnimationFrame(fn);
|
|
86
|
+
return () => cancelAnimationFrame(handle2);
|
|
87
|
+
}
|
|
88
|
+
const handle = setTimeout(fn, 16);
|
|
89
|
+
return () => clearTimeout(handle);
|
|
90
|
+
}
|
|
91
|
+
function stopFrame(run) {
|
|
92
|
+
const cancel = run.cancelFrame;
|
|
93
|
+
run.cancelFrame = null;
|
|
94
|
+
cancel?.();
|
|
95
|
+
}
|
|
96
|
+
const hostKey = (host, toolCallId) => `${host?.id ?? ""}\0${toolCallId}`;
|
|
97
|
+
const ownSegmentId = (agentId) => `subagent-${agentId}`;
|
|
98
|
+
function freeOwnId(segments, agentId) {
|
|
99
|
+
const base = ownSegmentId(agentId);
|
|
100
|
+
let id = base;
|
|
101
|
+
for (let n = 2; segments?.some((s) => s.id === id); n += 1) id = `${base}-${n}`;
|
|
102
|
+
return id;
|
|
103
|
+
}
|
|
104
|
+
function createLanes() {
|
|
105
|
+
const owner = {};
|
|
106
|
+
const runs = /* @__PURE__ */ new Map();
|
|
107
|
+
const over = /* @__PURE__ */ new Set();
|
|
108
|
+
const replySegments = (run) => {
|
|
109
|
+
const t = run.target;
|
|
110
|
+
try {
|
|
111
|
+
const message = t.getMessage?.(run.messageId) ?? t.getMessages?.()?.find((m) => m.id === run.messageId);
|
|
112
|
+
return message ? message.segments ?? [] : void 0;
|
|
113
|
+
} catch {
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const hostSegments = (run, host) => host ? host.list.segments : replySegments(run);
|
|
118
|
+
const anchorOf = (run, lane) => hostSegments(run, lane.host)?.find((s) => s.id === lane.anchorId);
|
|
119
|
+
const isWaiting = (run, lane) => {
|
|
120
|
+
if (lane.status === "waiting" || lane.asking.size > 0) return true;
|
|
121
|
+
for (const other of run.lanes.values()) {
|
|
122
|
+
if (other.host === lane && (other.status === "running" || other.status === "waiting") && isWaiting(run, other)) return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
};
|
|
126
|
+
const snapshot = (run, lane) => ({
|
|
127
|
+
id: lane.id,
|
|
128
|
+
...lane.name !== void 0 ? { name: lane.name } : {},
|
|
129
|
+
...lane.description !== void 0 ? { description: lane.description } : {},
|
|
130
|
+
status: lane.status === "running" && isWaiting(run, lane) ? "waiting" : lane.status,
|
|
131
|
+
...lane.result !== void 0 ? { result: lane.result } : {},
|
|
132
|
+
...lane.error !== void 0 ? { error: lane.error } : {},
|
|
133
|
+
...lane.parentId !== void 0 ? { parentId: lane.parentId } : {},
|
|
134
|
+
segments: lane.list.segments
|
|
135
|
+
});
|
|
136
|
+
const write = (run, host, segmentId, updates) => {
|
|
137
|
+
if (host) {
|
|
138
|
+
host.list.updateSegment?.(segmentId, updates);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const segments = replySegments(run);
|
|
142
|
+
if (segments && !segments.some((s) => s.id === segmentId)) return;
|
|
143
|
+
run.target.updateSegment?.(segmentId, updates);
|
|
144
|
+
};
|
|
145
|
+
const writeAnchor = (run, lane) => {
|
|
146
|
+
const updates = { subagent: snapshot(run, lane) };
|
|
147
|
+
if (lane.anchorKind === "own") {
|
|
148
|
+
updates["isStreaming"] = lane.streaming;
|
|
149
|
+
} else {
|
|
150
|
+
const call = anchorOf(run, lane);
|
|
151
|
+
if (call && isOpen(call.status)) {
|
|
152
|
+
const waiting = isWaiting(run, lane);
|
|
153
|
+
if (waiting && call.status !== "awaiting-approval") {
|
|
154
|
+
updates["status"] = "awaiting-approval";
|
|
155
|
+
lane.holding = true;
|
|
156
|
+
} else if (!waiting && lane.holding && call.status === "awaiting-approval") {
|
|
157
|
+
updates["status"] = "pending";
|
|
158
|
+
lane.holding = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
write(run, lane.host, lane.anchorId, updates);
|
|
163
|
+
if (laneWatchers.size === 0 || run.closed) return;
|
|
164
|
+
const anchor = anchorOf(run, lane) ?? {
|
|
165
|
+
id: lane.anchorId,
|
|
166
|
+
type: SUBAGENT_SEGMENT_TYPE,
|
|
167
|
+
subagent: updates["subagent"],
|
|
168
|
+
isStreaming: lane.streaming
|
|
169
|
+
};
|
|
170
|
+
const note = {
|
|
171
|
+
target: run.target,
|
|
172
|
+
...run.targetId !== void 0 ? { targetId: run.targetId } : {},
|
|
173
|
+
messageId: run.messageId,
|
|
174
|
+
agentId: lane.id,
|
|
175
|
+
anchor
|
|
176
|
+
};
|
|
177
|
+
for (const fn of [...laneWatchers]) {
|
|
178
|
+
try {
|
|
179
|
+
fn(note);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
console.error("[aparte] a sub-agent view threw", err);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const flush = (run) => {
|
|
186
|
+
if (run.closed) return;
|
|
187
|
+
stopFrame(run);
|
|
188
|
+
for (let guard = 0; run.dirty.size > 0 && guard < 1e4; guard += 1) {
|
|
189
|
+
let deepest;
|
|
190
|
+
for (const lane of run.dirty) if (!deepest || lane.depth > deepest.depth) deepest = lane;
|
|
191
|
+
run.dirty.delete(deepest);
|
|
192
|
+
if (deepest.ready) writeAnchor(run, deepest);
|
|
193
|
+
}
|
|
194
|
+
stopFrame(run);
|
|
195
|
+
};
|
|
196
|
+
const schedule = (run) => {
|
|
197
|
+
if (run.cancelFrame || run.closed) return;
|
|
198
|
+
run.cancelFrame = nextFrame(() => {
|
|
199
|
+
run.cancelFrame = null;
|
|
200
|
+
flush(run);
|
|
201
|
+
});
|
|
202
|
+
};
|
|
203
|
+
const dispatchLifecycleEvent = (run, name, detail) => {
|
|
204
|
+
if (typeof CustomEvent !== "function") return;
|
|
205
|
+
const id = run.targetId ?? (run.target.id || void 0);
|
|
206
|
+
try {
|
|
207
|
+
run.target.dispatchEvent(new CustomEvent(name, { bubbles: true, composed: true, detail: { targetId: id, ...detail } }));
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const dispatchEnd = (run, lane, status) => dispatchLifecycleEvent(run, "aparte-subagent-end", { messageId: run.messageId, agentId: lane.id, status });
|
|
212
|
+
const settleLane = (lane, calls) => {
|
|
213
|
+
for (const segment of lane.list.segments) {
|
|
214
|
+
if (segment.type === "tool_call") {
|
|
215
|
+
if (calls && isOpen(segment.status)) {
|
|
216
|
+
lane.list.updateSegment?.(segment.id, { status: "aborted" });
|
|
217
|
+
}
|
|
218
|
+
} else if (segment.isStreaming === true) {
|
|
219
|
+
lane.list.updateSegment?.(segment.id, { isStreaming: false });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
const close = (run) => {
|
|
224
|
+
if (run.closed) return;
|
|
225
|
+
for (const lane of run.lanes.values()) {
|
|
226
|
+
const working = lane.status === "running" || lane.status === "waiting";
|
|
227
|
+
settleLane(lane, true);
|
|
228
|
+
lane.streaming = false;
|
|
229
|
+
run.dirty.add(lane);
|
|
230
|
+
if (working) dispatchEnd(run, lane, "stopped");
|
|
231
|
+
}
|
|
232
|
+
flush(run);
|
|
233
|
+
forget(run);
|
|
234
|
+
};
|
|
235
|
+
const forget = (run) => {
|
|
236
|
+
run.closed = true;
|
|
237
|
+
stopFrame(run);
|
|
238
|
+
run.unlisten();
|
|
239
|
+
release(run.target, run.messageId, owner);
|
|
240
|
+
if (runs.get(run.messageId) === run) runs.delete(run.messageId);
|
|
241
|
+
over.add(run.messageId);
|
|
242
|
+
if (over.size > MAX_RUNS) over.delete(over.values().next().value);
|
|
243
|
+
};
|
|
244
|
+
const markDirty = (run, lane, now) => {
|
|
245
|
+
run.dirty.add(lane);
|
|
246
|
+
if (now) flush(run);
|
|
247
|
+
else schedule(run);
|
|
248
|
+
};
|
|
249
|
+
const settleDueRows = (run) => {
|
|
250
|
+
if (run.rowsDue.length === 0) return;
|
|
251
|
+
const due = run.rowsDue;
|
|
252
|
+
run.rowsDue = [];
|
|
253
|
+
for (const lane of due) {
|
|
254
|
+
lane.ready = true;
|
|
255
|
+
run.dirty.add(lane);
|
|
256
|
+
}
|
|
257
|
+
flush(run);
|
|
258
|
+
};
|
|
259
|
+
const callStarted = (run, host, toolCallId) => {
|
|
260
|
+
const key = hostKey(host, toolCallId);
|
|
261
|
+
const held = run.awaitingRow.get(key);
|
|
262
|
+
if (!held) return;
|
|
263
|
+
run.awaitingRow.delete(key);
|
|
264
|
+
if (host) {
|
|
265
|
+
for (const lane of held) {
|
|
266
|
+
lane.ready = true;
|
|
267
|
+
run.dirty.add(lane);
|
|
268
|
+
}
|
|
269
|
+
flush(run);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
run.rowsDue.push(...held);
|
|
273
|
+
queueMicrotask(() => settleDueRows(run));
|
|
274
|
+
};
|
|
275
|
+
const listen = (run) => {
|
|
276
|
+
const target = run.target;
|
|
277
|
+
if (typeof target.addEventListener !== "function" || typeof target.removeEventListener !== "function") return () => {
|
|
278
|
+
};
|
|
279
|
+
const mine = (e) => e.detail?.messageId === run.messageId;
|
|
280
|
+
const onEnd = (e) => {
|
|
281
|
+
if (mine(e)) close(run);
|
|
282
|
+
};
|
|
283
|
+
const onPause = (e) => {
|
|
284
|
+
if (!mine(e)) return;
|
|
285
|
+
for (const lane of run.lanes.values()) run.dirty.add(lane);
|
|
286
|
+
flush(run);
|
|
287
|
+
};
|
|
288
|
+
const ends = ["aparte-message-done", "aparte-message-aborted", "aparte-message-error"];
|
|
289
|
+
for (const name of ends) target.addEventListener(name, onEnd, true);
|
|
290
|
+
target.addEventListener("aparte-message-paused", onPause, true);
|
|
291
|
+
return () => {
|
|
292
|
+
for (const name of ends) target.removeEventListener(name, onEnd, true);
|
|
293
|
+
target.removeEventListener("aparte-message-paused", onPause, true);
|
|
294
|
+
};
|
|
295
|
+
};
|
|
296
|
+
const openRun = (ctx) => {
|
|
297
|
+
if (!claim(ctx.target, ctx.messageId, owner)) return void 0;
|
|
298
|
+
if (runs.size >= MAX_RUNS) {
|
|
299
|
+
const oldest = runs.values().next().value;
|
|
300
|
+
if (oldest) forget(oldest);
|
|
301
|
+
}
|
|
302
|
+
const run = {
|
|
303
|
+
messageId: ctx.messageId,
|
|
304
|
+
target: ctx.target,
|
|
305
|
+
...ctx.targetId !== void 0 ? { targetId: ctx.targetId } : {},
|
|
306
|
+
lanes: /* @__PURE__ */ new Map(),
|
|
307
|
+
callAgents: /* @__PURE__ */ new Map(),
|
|
308
|
+
early: /* @__PURE__ */ new Map(),
|
|
309
|
+
earlyCount: 0,
|
|
310
|
+
awaitingRow: /* @__PURE__ */ new Map(),
|
|
311
|
+
rowsDue: [],
|
|
312
|
+
dirty: /* @__PURE__ */ new Set(),
|
|
313
|
+
cancelFrame: null,
|
|
314
|
+
closed: false,
|
|
315
|
+
unlisten: () => {
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
run.unlisten = listen(run);
|
|
319
|
+
runs.set(ctx.messageId, run);
|
|
320
|
+
return run;
|
|
321
|
+
};
|
|
322
|
+
const savedAnchor = (segments, agentId, toolCallId) => {
|
|
323
|
+
if (!segments) return void 0;
|
|
324
|
+
return segments.find((s) => s.id === ownSegmentId(agentId) && subagentOf(s)?.id === agentId) ?? (toolCallId ? segments.find((s) => s.id === `tool-${toolCallId}` && subagentOf(s)?.id === agentId) : void 0);
|
|
325
|
+
};
|
|
326
|
+
const retire = (run, lane) => {
|
|
327
|
+
run.lanes.delete(lane.id);
|
|
328
|
+
if (lane.status === "running" || lane.status === "waiting") {
|
|
329
|
+
lane.status = "stopped";
|
|
330
|
+
settleLane(lane, true);
|
|
331
|
+
lane.streaming = false;
|
|
332
|
+
dispatchEnd(run, lane, "stopped");
|
|
333
|
+
}
|
|
334
|
+
markDirty(run, lane, true);
|
|
335
|
+
};
|
|
336
|
+
const startAgent = (run, e, ctx) => {
|
|
337
|
+
const known = run.lanes.get(e.agentId);
|
|
338
|
+
const reused = known !== void 0 && e.toolCallId !== void 0 && e.toolCallId !== known.toolCallId;
|
|
339
|
+
if (known && reused) retire(run, known);
|
|
340
|
+
if (known && !reused) {
|
|
341
|
+
known.status = "running";
|
|
342
|
+
delete known.result;
|
|
343
|
+
delete known.error;
|
|
344
|
+
if (e.name !== void 0) known.name = e.name;
|
|
345
|
+
if (e.description !== void 0) known.description = e.description;
|
|
346
|
+
known.streaming = true;
|
|
347
|
+
announce(run, known, e.toolCallId);
|
|
348
|
+
markDirty(run, known, true);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (run.lanes.size >= MAX_LANES) return;
|
|
352
|
+
const host = e.parentAgentId ? run.lanes.get(e.parentAgentId) ?? null : null;
|
|
353
|
+
const segments = hostSegments(run, host);
|
|
354
|
+
const saved = reused ? void 0 : savedAnchor(segments, e.agentId, e.toolCallId);
|
|
355
|
+
const callKey = e.toolCallId ? hostKey(host, e.toolCallId) : "";
|
|
356
|
+
const callId = e.toolCallId ? `tool-${e.toolCallId}` : "";
|
|
357
|
+
const callTaken = callKey !== "" && (run.callAgents.has(callKey) || !!segments?.some((s) => s.id === callId && subagentOf(s) && subagentOf(s).id !== e.agentId));
|
|
358
|
+
const onCall = saved ? saved.type === "tool_call" : callKey !== "" && !callTaken;
|
|
359
|
+
if (onCall) run.callAgents.set(callKey, e.agentId);
|
|
360
|
+
const previous = saved ? subagentOf(saved) : void 0;
|
|
361
|
+
const list = createSegmentList({ messageId: run.messageId, ...previous ? { segments: previous.segments } : {}, config: ctx.config });
|
|
362
|
+
const lane = {
|
|
363
|
+
id: e.agentId,
|
|
364
|
+
...e.name !== void 0 ? { name: e.name } : previous?.name !== void 0 ? { name: previous.name } : {},
|
|
365
|
+
...e.description !== void 0 ? { description: e.description } : previous?.description !== void 0 ? { description: previous.description } : {},
|
|
366
|
+
...e.parentAgentId !== void 0 ? { parentId: e.parentAgentId } : {},
|
|
367
|
+
...e.toolCallId !== void 0 ? { toolCallId: e.toolCallId } : {},
|
|
368
|
+
status: "running",
|
|
369
|
+
list,
|
|
370
|
+
// Not observed: every observer heard these events from the reply's adapter already.
|
|
371
|
+
emit: createStreamAdapter({ target: list, config: ctx.config, messageId: run.messageId, observe: false }),
|
|
372
|
+
host,
|
|
373
|
+
anchorId: onCall ? callId : saved ? saved.id : freeOwnId(segments, e.agentId),
|
|
374
|
+
anchorKind: onCall ? "call" : "own",
|
|
375
|
+
ready: true,
|
|
376
|
+
streaming: true,
|
|
377
|
+
asking: /* @__PURE__ */ new Set(),
|
|
378
|
+
holding: false,
|
|
379
|
+
depth: host ? host.depth + 1 : 0
|
|
380
|
+
};
|
|
381
|
+
run.lanes.set(lane.id, lane);
|
|
382
|
+
list.onChange(() => {
|
|
383
|
+
if (!run.closed) {
|
|
384
|
+
run.dirty.add(lane);
|
|
385
|
+
schedule(run);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
if (lane.anchorKind === "call" && !saved && segments && !segments.some((s) => s.id === lane.anchorId)) {
|
|
389
|
+
lane.ready = false;
|
|
390
|
+
const key = callKey;
|
|
391
|
+
run.awaitingRow.set(key, [...run.awaitingRow.get(key) ?? [], lane]);
|
|
392
|
+
} else if (lane.anchorKind === "own" && !saved && !run.closed) {
|
|
393
|
+
const segment = {
|
|
394
|
+
id: lane.anchorId,
|
|
395
|
+
type: SUBAGENT_SEGMENT_TYPE,
|
|
396
|
+
subagent: snapshot(run, lane),
|
|
397
|
+
isStreaming: true
|
|
398
|
+
};
|
|
399
|
+
if (host) host.list.addSegment?.(segment);
|
|
400
|
+
else run.target.addSegment?.(segment);
|
|
401
|
+
}
|
|
402
|
+
announce(run, lane, e.toolCallId);
|
|
403
|
+
const early = run.early.get(lane.id);
|
|
404
|
+
if (early) {
|
|
405
|
+
run.early.delete(lane.id);
|
|
406
|
+
run.earlyCount -= early.length;
|
|
407
|
+
for (const event of early) feed(run, lane, event);
|
|
408
|
+
}
|
|
409
|
+
markDirty(run, lane, true);
|
|
410
|
+
};
|
|
411
|
+
const announce = (run, lane, toolCallId) => dispatchLifecycleEvent(run, "aparte-subagent-start", {
|
|
412
|
+
messageId: run.messageId,
|
|
413
|
+
agentId: lane.id,
|
|
414
|
+
...lane.name !== void 0 ? { name: lane.name } : {},
|
|
415
|
+
...lane.description !== void 0 ? { description: lane.description } : {},
|
|
416
|
+
...toolCallId !== void 0 ? { toolCallId } : {},
|
|
417
|
+
...lane.parentId !== void 0 ? { parentId: lane.parentId } : {}
|
|
418
|
+
});
|
|
419
|
+
const endAgent = (run, e) => {
|
|
420
|
+
const lane = run.lanes.get(e.agentId);
|
|
421
|
+
if (!lane) return;
|
|
422
|
+
lane.status = e.status;
|
|
423
|
+
if (e.result !== void 0) lane.result = e.result;
|
|
424
|
+
if (e.error !== void 0) lane.error = e.error;
|
|
425
|
+
if (e.status !== "waiting") {
|
|
426
|
+
settleLane(lane, false);
|
|
427
|
+
lane.streaming = false;
|
|
428
|
+
}
|
|
429
|
+
dispatchEnd(run, lane, e.status);
|
|
430
|
+
markDirty(run, lane, true);
|
|
431
|
+
};
|
|
432
|
+
const feed = (run, lane, e) => {
|
|
433
|
+
const { agentId: _owner, ...event } = e;
|
|
434
|
+
if ("toolCallId" in event && typeof event.toolCallId === "string") {
|
|
435
|
+
if (event.type === "tool-awaiting-approval") lane.asking.add(event.toolCallId);
|
|
436
|
+
else if (event.type !== "tool-start") lane.asking.delete(event.toolCallId);
|
|
437
|
+
}
|
|
438
|
+
const before = lane.list.segments.length;
|
|
439
|
+
lane.emit(event);
|
|
440
|
+
if (event.type === "tool-start") callStarted(run, lane, event.toolCallId);
|
|
441
|
+
const now = !COALESCED.has(event.type) || lane.list.segments.length !== before;
|
|
442
|
+
markDirty(run, lane, now);
|
|
443
|
+
};
|
|
444
|
+
const observe = (event, ctx) => {
|
|
445
|
+
const agentId = event.agentId;
|
|
446
|
+
const attributed = typeof agentId === "string" || event.type === "agent-start" || event.type === "agent-end";
|
|
447
|
+
let run = runs.get(ctx.messageId);
|
|
448
|
+
if (run && run.target !== ctx.target) return;
|
|
449
|
+
if (!run) {
|
|
450
|
+
if (!attributed || over.has(ctx.messageId)) return;
|
|
451
|
+
run = openRun(ctx);
|
|
452
|
+
if (!run) return;
|
|
453
|
+
}
|
|
454
|
+
settleDueRows(run);
|
|
455
|
+
if (!attributed) {
|
|
456
|
+
switch (event.type) {
|
|
457
|
+
case "tool-start":
|
|
458
|
+
callStarted(run, null, event.toolCallId);
|
|
459
|
+
break;
|
|
460
|
+
case "run-suspended":
|
|
461
|
+
for (const lane2 of run.lanes.values()) run.dirty.add(lane2);
|
|
462
|
+
flush(run);
|
|
463
|
+
break;
|
|
464
|
+
case "run-done":
|
|
465
|
+
case "run-aborted":
|
|
466
|
+
close(run);
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (event.type === "agent-start") {
|
|
472
|
+
startAgent(run, event, ctx);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (event.type === "agent-end") {
|
|
476
|
+
endAgent(run, event);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (event.type === "run-start" || event.type === "run-done" || event.type === "run-aborted" || event.type === "run-suspended") return;
|
|
480
|
+
const lane = run.lanes.get(agentId);
|
|
481
|
+
if (lane) {
|
|
482
|
+
feed(run, lane, event);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (run.earlyCount >= MAX_EARLY) return;
|
|
486
|
+
run.early.set(agentId, [...run.early.get(agentId) ?? [], event]);
|
|
487
|
+
run.earlyCount += 1;
|
|
488
|
+
};
|
|
489
|
+
return {
|
|
490
|
+
observe,
|
|
491
|
+
dispose: () => {
|
|
492
|
+
for (const run of [...runs.values()]) forget(run);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
function rendererFor(type, config) {
|
|
497
|
+
const renderer = getSegmentRenderer(type, config);
|
|
498
|
+
if (renderer) return renderer;
|
|
499
|
+
installDefaultRenderersOnce(config);
|
|
500
|
+
return getSegmentRenderer(type, config);
|
|
501
|
+
}
|
|
502
|
+
function toElement(out, segment) {
|
|
503
|
+
let el;
|
|
504
|
+
if (typeof out === "string") {
|
|
505
|
+
const tpl = document.createElement("template");
|
|
506
|
+
tpl.innerHTML = out.trim();
|
|
507
|
+
el = tpl.content.firstElementChild;
|
|
508
|
+
} else {
|
|
509
|
+
el = out;
|
|
510
|
+
}
|
|
511
|
+
if (el && !el.hasAttribute("data-segment-id")) el.setAttribute("data-segment-id", segment.id);
|
|
512
|
+
return el;
|
|
513
|
+
}
|
|
514
|
+
function fallbackOf(segment) {
|
|
515
|
+
const text = segment.fallback;
|
|
516
|
+
if (typeof text !== "string" || !text.trim()) return null;
|
|
517
|
+
const el = document.createElement("div");
|
|
518
|
+
el.className = "aparte-segment aparte-segment-fallback";
|
|
519
|
+
el.setAttribute("data-segment-id", segment.id);
|
|
520
|
+
el.textContent = text;
|
|
521
|
+
return el;
|
|
522
|
+
}
|
|
523
|
+
function drawnAs(segment, live) {
|
|
524
|
+
if (!live || segment.type !== "text" && segment.type !== "thinking") return segment;
|
|
525
|
+
if (segment.isStreaming !== void 0) return segment;
|
|
526
|
+
return { ...segment, isStreaming: true };
|
|
527
|
+
}
|
|
528
|
+
class LaneDrawing {
|
|
529
|
+
constructor(container) {
|
|
530
|
+
this.container = container;
|
|
531
|
+
}
|
|
532
|
+
rows = /* @__PURE__ */ new Map();
|
|
533
|
+
/** How many rows are drawn. */
|
|
534
|
+
get size() {
|
|
535
|
+
return this.rows.size;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Draw `segments`, in order: a new segment gets a row, a changed one is patched, one that
|
|
539
|
+
* left loses its row. `live`: the sub-agent still works, so what it adds is arriving.
|
|
540
|
+
*/
|
|
541
|
+
sync(segments, config, live) {
|
|
542
|
+
const seen = /* @__PURE__ */ new Set();
|
|
543
|
+
let previous = null;
|
|
544
|
+
for (const segment of segments) {
|
|
545
|
+
if (!segment || typeof segment.id !== "string" || seen.has(segment.id)) continue;
|
|
546
|
+
seen.add(segment.id);
|
|
547
|
+
const renderer = rendererFor(segment.type, config);
|
|
548
|
+
let row = this.rows.get(segment.id);
|
|
549
|
+
if (row && row.renderer !== renderer) {
|
|
550
|
+
this.release(segment.id, config);
|
|
551
|
+
row.el.remove();
|
|
552
|
+
row = void 0;
|
|
553
|
+
}
|
|
554
|
+
if (!row) row = this.draw(segment, renderer, config, live);
|
|
555
|
+
else if (row.segment !== segment) this.patch(row, segment, config);
|
|
556
|
+
if (!row) continue;
|
|
557
|
+
const expected = previous ? previous.nextSibling : this.container.firstChild;
|
|
558
|
+
if (row.el !== expected) this.container.insertBefore(row.el, expected);
|
|
559
|
+
previous = row.el;
|
|
560
|
+
}
|
|
561
|
+
for (const [id, row] of [...this.rows]) {
|
|
562
|
+
if (seen.has(id)) continue;
|
|
563
|
+
this.release(id, config);
|
|
564
|
+
row.el.remove();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
/** Every row re-reads what it takes from the config (a language switch). */
|
|
568
|
+
relabel(config) {
|
|
569
|
+
for (const row of this.rows.values()) {
|
|
570
|
+
const relabel = row.renderer?.relabel;
|
|
571
|
+
if (!relabel) continue;
|
|
572
|
+
runWithConfig(config, () => relabel.call(row.renderer, row.el, row.segment));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
/** Every row goes: each renderer cleans up what it drew. */
|
|
576
|
+
clear(config) {
|
|
577
|
+
for (const [id, row] of [...this.rows]) {
|
|
578
|
+
this.release(id, config);
|
|
579
|
+
row.el.remove();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
draw(segment, renderer, config, live) {
|
|
583
|
+
const el = renderer ? toElement(runWithConfig(config, () => renderer.render(segment)), segment) : fallbackOf(segment);
|
|
584
|
+
if (!el) return void 0;
|
|
585
|
+
const row = { el, renderer, segment };
|
|
586
|
+
this.rows.set(segment.id, row);
|
|
587
|
+
if (renderer?.setup) {
|
|
588
|
+
const as = drawnAs(segment, live);
|
|
589
|
+
runWithConfig(config, () => renderer.setup(el, as));
|
|
590
|
+
}
|
|
591
|
+
return row;
|
|
592
|
+
}
|
|
593
|
+
patch(row, segment, config) {
|
|
594
|
+
const renderer = row.renderer;
|
|
595
|
+
row.segment = segment;
|
|
596
|
+
if (!renderer) {
|
|
597
|
+
const next2 = fallbackOf(segment);
|
|
598
|
+
if (next2) {
|
|
599
|
+
row.el.replaceWith(next2);
|
|
600
|
+
row.el = next2;
|
|
601
|
+
}
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (renderer.update) {
|
|
605
|
+
const before = row.el;
|
|
606
|
+
runWithConfig(config, () => renderer.update(before, segment));
|
|
607
|
+
if (before.parentNode !== this.container) {
|
|
608
|
+
const now = this.childOf(segment.id);
|
|
609
|
+
if (now) row.el = now;
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const next = toElement(runWithConfig(config, () => renderer.render(segment)), segment);
|
|
614
|
+
if (!next) return;
|
|
615
|
+
this.cleanup(row, config);
|
|
616
|
+
row.el.replaceWith(next);
|
|
617
|
+
row.el = next;
|
|
618
|
+
if (renderer.setup) runWithConfig(config, () => renderer.setup(next, segment));
|
|
619
|
+
}
|
|
620
|
+
/** The row for `id` among the container's own children — never a decoy in a row's prose. */
|
|
621
|
+
childOf(id) {
|
|
622
|
+
for (const child of this.container.children) {
|
|
623
|
+
if (child.getAttribute("data-segment-id") === id) return child;
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
release(id, config) {
|
|
628
|
+
const row = this.rows.get(id);
|
|
629
|
+
if (!row) return;
|
|
630
|
+
this.rows.delete(id);
|
|
631
|
+
this.cleanup(row, config);
|
|
632
|
+
}
|
|
633
|
+
cleanup(row, config) {
|
|
634
|
+
const cleanup = row.renderer?.cleanup;
|
|
635
|
+
if (!cleanup) return;
|
|
636
|
+
try {
|
|
637
|
+
runWithConfig(config, () => cleanup.call(row.renderer, row.el, row.segment));
|
|
638
|
+
} catch (err) {
|
|
639
|
+
console.error("[aparte] a segment renderer's cleanup threw", err);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
const styles = "/*\n * @aparte/plugin-subagents — a sub-agent's conversation.\n *\n * Only what is the plugin's own. The rows inside are core's (text, reasoning, tool rows,\n * a nested sub-agent's line) and wear core's sheets; under a line, the transcript sits in\n * core's tool detail, which already draws the rule and the indent a disclosure has. So\n * this sheet draws the task line, the spacing of the rows, and — for the standalone view\n * only — the same quiet rule down the leading edge, since nothing of core's is around it.\n *\n * Logical properties throughout, so a right-to-left locale puts the rule on its own\n * leading edge. No colour of its own: core's tokens. Nothing moves, so there is no motion\n * to reduce.\n */\naparte-subagent-view {\n display: block;\n min-inline-size: 0;\n}\n\n/* Standalone (a canvas, a pane, a dialog): outside any message, so the conversation's\n type is set here, as core's segment container sets it in a reply. */\naparte-subagent-view:not([data-inline]) {\n font-size: var(--aparte-content-font-size);\n line-height: var(--aparte-content-line-height);\n color: var(--aparte-content-color);\n font-family: var(--aparte-font-family);\n}\n\n/* Nothing to show (an unknown sub-agent, or one not started yet): nothing drawn. */\naparte-subagent-view[data-empty] {\n display: none;\n}\n\n.aparte-subagent-task {\n margin: 0 0 var(--aparte-space-3);\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-sm);\n}\n\n.aparte-subagent-transcript {\n display: flex;\n flex-direction: column;\n gap: var(--aparte-space-4);\n min-inline-size: 0;\n}\n\n.aparte-subagent-transcript:empty {\n display: none;\n}\n\naparte-subagent-view:not([data-inline]) > .aparte-subagent-transcript {\n padding-inline-start: var(--aparte-subagent-indent, var(--aparte-space-6));\n border-inline-start: var(--aparte-border-width) solid var(--aparte-border);\n}\n";
|
|
644
|
+
const STYLE_ID = "aparte-subagent-styles";
|
|
645
|
+
const MARK = ".aparte-subagent-transcript";
|
|
646
|
+
let holder = null;
|
|
647
|
+
function onPage() {
|
|
648
|
+
if (holder?.isConnected) return true;
|
|
649
|
+
holder = document.getElementById(STYLE_ID) ?? [...document.querySelectorAll("style")].find((sheet) => sheet.textContent?.includes(MARK)) ?? null;
|
|
650
|
+
return holder !== null;
|
|
651
|
+
}
|
|
652
|
+
function rendererStyles() {
|
|
653
|
+
return typeof document !== "undefined" && document.getElementById(STYLE_ID) ? "" : styles;
|
|
654
|
+
}
|
|
655
|
+
function ensureStyles() {
|
|
656
|
+
if (typeof document === "undefined" || onPage()) return;
|
|
657
|
+
const style = document.createElement("style");
|
|
658
|
+
style.id = STYLE_ID;
|
|
659
|
+
style.textContent = styles;
|
|
660
|
+
document.head.appendChild(style);
|
|
661
|
+
holder = style;
|
|
662
|
+
}
|
|
663
|
+
const CONVERSATION_EVENTS = [
|
|
664
|
+
"aparte-subagent-start",
|
|
665
|
+
"aparte-subagent-end",
|
|
666
|
+
"aparte-segment-update",
|
|
667
|
+
"aparte-message-done",
|
|
668
|
+
"aparte-message-aborted",
|
|
669
|
+
"aparte-message-error",
|
|
670
|
+
"aparte-message-paused",
|
|
671
|
+
"aparte-path-changed",
|
|
672
|
+
"aparte-reset-done",
|
|
673
|
+
"aparte-conversation-loaded"
|
|
674
|
+
];
|
|
675
|
+
const MAX_DEPTH = 16;
|
|
676
|
+
const adopted = /* @__PURE__ */ new WeakMap();
|
|
677
|
+
function laneOf(anchor) {
|
|
678
|
+
const sub = subagentOf(anchor);
|
|
679
|
+
if (!sub) return { segments: [], live: false };
|
|
680
|
+
const status = subagentStatus(anchor);
|
|
681
|
+
if (status === "running" || status === "waiting") return { segments: sub.segments, live: true };
|
|
682
|
+
let settled = adopted.get(sub.segments);
|
|
683
|
+
if (!settled) {
|
|
684
|
+
settled = createSegmentList({ messageId: anchor.messageId ?? "", segments: sub.segments }).segments;
|
|
685
|
+
adopted.set(sub.segments, settled);
|
|
686
|
+
}
|
|
687
|
+
return { segments: settled, live: false };
|
|
688
|
+
}
|
|
689
|
+
function findSubagent(segments, agentId, depth = 0) {
|
|
690
|
+
for (const segment of segments) {
|
|
691
|
+
const sub = subagentOf(segment);
|
|
692
|
+
if (!sub) continue;
|
|
693
|
+
if (sub.id === agentId) return segment;
|
|
694
|
+
if (depth >= MAX_DEPTH) continue;
|
|
695
|
+
const found = findSubagent(laneOf(segment).segments, agentId, depth + 1);
|
|
696
|
+
if (found) return found;
|
|
697
|
+
}
|
|
698
|
+
return void 0;
|
|
699
|
+
}
|
|
700
|
+
const fed = /* @__PURE__ */ new WeakMap();
|
|
701
|
+
const ElementBase = typeof HTMLElement === "undefined" ? class {
|
|
702
|
+
} : HTMLElement;
|
|
703
|
+
class AparteSubagentView extends ElementBase {
|
|
704
|
+
static get observedAttributes() {
|
|
705
|
+
return ["for", "message-id", "agent-id"];
|
|
706
|
+
}
|
|
707
|
+
_chat = null;
|
|
708
|
+
_task = null;
|
|
709
|
+
_drawing = null;
|
|
710
|
+
/** The config the rows were drawn with, and the segment they were drawn from. */
|
|
711
|
+
_config = null;
|
|
712
|
+
_anchor = null;
|
|
713
|
+
_listening = null;
|
|
714
|
+
_stopConfig = null;
|
|
715
|
+
_stopWatching = null;
|
|
716
|
+
_queued = false;
|
|
717
|
+
_releaseQueued = false;
|
|
718
|
+
_warned = "";
|
|
719
|
+
_onConversation = () => this._schedule();
|
|
720
|
+
_onWrite = (write) => {
|
|
721
|
+
if (fed.has(this) || !this.isConnected) return;
|
|
722
|
+
if (write.messageId !== this.getAttribute("message-id") || write.agentId !== this.getAttribute("agent-id")) return;
|
|
723
|
+
if (!this._matches(write)) return;
|
|
724
|
+
this._draw(write.anchor, this._sourceConfig());
|
|
725
|
+
};
|
|
726
|
+
// ── What the app sets ──────────────────────────────────────────────────
|
|
727
|
+
/** The chat, when it has no `id` for the `for` attribute: an element, or anything with `getMessages()`. */
|
|
728
|
+
get chat() {
|
|
729
|
+
return this._chat;
|
|
730
|
+
}
|
|
731
|
+
set chat(value) {
|
|
732
|
+
this._chat = value;
|
|
733
|
+
if (!this.isConnected) return;
|
|
734
|
+
this._listen();
|
|
735
|
+
this.refresh();
|
|
736
|
+
}
|
|
737
|
+
/** Read the sub-agent again and draw it: what the element does on every change it hears. */
|
|
738
|
+
refresh() {
|
|
739
|
+
const feed = fed.get(this);
|
|
740
|
+
if (!feed && !this.isConnected) return;
|
|
741
|
+
const anchor = feed ? feed.anchor : this._find();
|
|
742
|
+
this._draw(anchor === null ? this._anchor : anchor, feed ? feed.config : this._sourceConfig());
|
|
743
|
+
}
|
|
744
|
+
// ── Lifecycle ──────────────────────────────────────────────────────────
|
|
745
|
+
connectedCallback() {
|
|
746
|
+
ensureStyles();
|
|
747
|
+
this._build();
|
|
748
|
+
if (!fed.has(this)) {
|
|
749
|
+
this._listen();
|
|
750
|
+
this._stopWatching ??= watchLaneWrites(this._onWrite);
|
|
751
|
+
} else {
|
|
752
|
+
this._stopConfig?.();
|
|
753
|
+
this._stopConfig = subscribeConfigChange(this, () => this._onConfigChange());
|
|
754
|
+
}
|
|
755
|
+
this.refresh();
|
|
756
|
+
}
|
|
757
|
+
disconnectedCallback() {
|
|
758
|
+
this._unlisten();
|
|
759
|
+
this._stopWatching?.();
|
|
760
|
+
this._stopWatching = null;
|
|
761
|
+
this._stopConfig?.();
|
|
762
|
+
this._stopConfig = null;
|
|
763
|
+
if (this._releaseQueued) return;
|
|
764
|
+
this._releaseQueued = true;
|
|
765
|
+
queueMicrotask(() => {
|
|
766
|
+
this._releaseQueued = false;
|
|
767
|
+
if (this.isConnected) return;
|
|
768
|
+
this._release();
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
attributeChangedCallback(_name, previous, value) {
|
|
772
|
+
if (previous === value || !this.isConnected || fed.has(this)) return;
|
|
773
|
+
if (_name === "for") this._listen();
|
|
774
|
+
this.refresh();
|
|
775
|
+
}
|
|
776
|
+
// ── Reading the conversation ───────────────────────────────────────────
|
|
777
|
+
_source() {
|
|
778
|
+
if (this._chat) return this._chat;
|
|
779
|
+
const id = this.getAttribute("for");
|
|
780
|
+
return id ? this.ownerDocument.getElementById(id) : null;
|
|
781
|
+
}
|
|
782
|
+
_sourceConfig() {
|
|
783
|
+
const source = this._source();
|
|
784
|
+
return resolveConfig(typeof Element !== "undefined" && source instanceof Element ? source : this);
|
|
785
|
+
}
|
|
786
|
+
/** A write from this view's chat: the run's target is the chat, inside it, or named by its id. */
|
|
787
|
+
_matches(write) {
|
|
788
|
+
const source = this._source();
|
|
789
|
+
if (!source) return false;
|
|
790
|
+
if (source === write.target) return true;
|
|
791
|
+
if (typeof Node !== "undefined" && source instanceof Node && write.target instanceof Node && (source.contains(write.target) || write.target.contains(source))) return true;
|
|
792
|
+
return write.targetId !== void 0 && source.id === write.targetId;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* The segment that stands for the sub-agent in the chat's reply; `undefined` when there is
|
|
796
|
+
* none, `null` when the chat cannot be read at all.
|
|
797
|
+
*/
|
|
798
|
+
_find() {
|
|
799
|
+
const messageId = this.getAttribute("message-id");
|
|
800
|
+
const agentId = this.getAttribute("agent-id");
|
|
801
|
+
const source = this._source();
|
|
802
|
+
if (!messageId || !agentId || !source) return void 0;
|
|
803
|
+
const own = source;
|
|
804
|
+
const reader = typeof own.getMessage === "function" || typeof own.getMessages === "function" ? own : own.querySelector?.("aparte-chat-viewport") ?? null;
|
|
805
|
+
if (!reader || typeof reader.getMessage !== "function" && typeof reader.getMessages !== "function") return null;
|
|
806
|
+
let message;
|
|
807
|
+
try {
|
|
808
|
+
message = reader.getMessage?.(messageId) ?? reader.getMessages?.()?.find((m) => m.id === messageId);
|
|
809
|
+
} catch {
|
|
810
|
+
return null;
|
|
811
|
+
}
|
|
812
|
+
const anchor = message ? findSubagent(message.segments ?? [], agentId) : void 0;
|
|
813
|
+
const key = `${messageId}\0${agentId}`;
|
|
814
|
+
if (message && !anchor && !message.isStreaming && message.status !== "pending" && message.status !== "streaming" && this._warned !== key) {
|
|
815
|
+
this._warned = key;
|
|
816
|
+
console.warn(`[aparte] <aparte-subagent-view>: the reply "${messageId}" holds no sub-agent "${agentId}".`);
|
|
817
|
+
}
|
|
818
|
+
return anchor;
|
|
819
|
+
}
|
|
820
|
+
_listen() {
|
|
821
|
+
const source = this._source();
|
|
822
|
+
const target = source && typeof source.addEventListener === "function" ? source : null;
|
|
823
|
+
if (target !== this._listening) {
|
|
824
|
+
this._unlisten();
|
|
825
|
+
if (target) for (const name of CONVERSATION_EVENTS) target.addEventListener(name, this._onConversation);
|
|
826
|
+
this._listening = target;
|
|
827
|
+
}
|
|
828
|
+
this._stopConfig?.();
|
|
829
|
+
const holder2 = typeof Element !== "undefined" && source instanceof Element ? source : this;
|
|
830
|
+
this._stopConfig = subscribeConfigChange(holder2, () => this._onConfigChange());
|
|
831
|
+
}
|
|
832
|
+
_unlisten() {
|
|
833
|
+
if (this._listening) for (const name of CONVERSATION_EVENTS) this._listening.removeEventListener(name, this._onConversation);
|
|
834
|
+
this._listening = null;
|
|
835
|
+
}
|
|
836
|
+
/** Folds a burst of events (a whole run's end) into one read. */
|
|
837
|
+
_schedule() {
|
|
838
|
+
if (this._queued) return;
|
|
839
|
+
this._queued = true;
|
|
840
|
+
queueMicrotask(() => {
|
|
841
|
+
this._queued = false;
|
|
842
|
+
this.refresh();
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
_onConfigChange() {
|
|
846
|
+
this.refresh();
|
|
847
|
+
if (this._config) this._drawing?.relabel(this._config);
|
|
848
|
+
}
|
|
849
|
+
// ── Drawing ────────────────────────────────────────────────────────────
|
|
850
|
+
_build() {
|
|
851
|
+
if (this._drawing) {
|
|
852
|
+
if (this._task.parentNode !== this) this.prepend(this._task);
|
|
853
|
+
if (this._drawing.container.parentNode !== this) this.append(this._drawing.container);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
const task = this.ownerDocument.createElement("p");
|
|
857
|
+
task.className = "aparte-subagent-task";
|
|
858
|
+
task.hidden = true;
|
|
859
|
+
const list = this.ownerDocument.createElement("div");
|
|
860
|
+
list.className = "aparte-subagent-transcript";
|
|
861
|
+
this.append(task, list);
|
|
862
|
+
this._task = task;
|
|
863
|
+
this._drawing = new LaneDrawing(list);
|
|
864
|
+
}
|
|
865
|
+
/** Draw `anchor`'s conversation with `config`. */
|
|
866
|
+
_draw(anchor, config) {
|
|
867
|
+
this._build();
|
|
868
|
+
const drawing = this._drawing;
|
|
869
|
+
if (this._config && this._config !== config) drawing.clear(this._config);
|
|
870
|
+
this._config = config;
|
|
871
|
+
this._anchor = anchor ?? null;
|
|
872
|
+
this.toggleAttribute("data-inline", fed.has(this));
|
|
873
|
+
this.toggleAttribute("data-empty", !anchor);
|
|
874
|
+
const task = (anchor ? subagentOf(anchor)?.description?.trim() : "") ?? "";
|
|
875
|
+
if (this._task.textContent !== task) this._task.textContent = task;
|
|
876
|
+
this._task.hidden = !task;
|
|
877
|
+
const lane = anchor ? laneOf(anchor) : { segments: [], live: false };
|
|
878
|
+
runWithConfig(config, () => drawing.sync(lane.segments, config, lane.live));
|
|
879
|
+
}
|
|
880
|
+
/** Out of the page for good: every row's renderer cleans up, and the drawing goes. */
|
|
881
|
+
_release() {
|
|
882
|
+
if (this._config) this._drawing?.clear(this._config);
|
|
883
|
+
const feed = fed.get(this);
|
|
884
|
+
if (feed) {
|
|
885
|
+
fed.delete(this);
|
|
886
|
+
feed.onRelease?.();
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function feedView(view, anchor, config, onRelease) {
|
|
891
|
+
const release2 = onRelease ?? fed.get(view)?.onRelease;
|
|
892
|
+
fed.set(view, { anchor, config, ...release2 ? { onRelease: release2 } : {} });
|
|
893
|
+
view._draw(anchor, config);
|
|
894
|
+
}
|
|
895
|
+
if (typeof customElements !== "undefined" && !customElements.get("aparte-subagent-view")) {
|
|
896
|
+
deferHydrationOf(AparteSubagentView);
|
|
897
|
+
customElements.define("aparte-subagent-view", AparteSubagentView);
|
|
898
|
+
}
|
|
899
|
+
const views = /* @__PURE__ */ new WeakMap();
|
|
900
|
+
const MAX_VIEWS = 500;
|
|
901
|
+
const subagentTranscript = (segment) => {
|
|
902
|
+
if (typeof document === "undefined") return "";
|
|
903
|
+
const sub = subagentOf(segment);
|
|
904
|
+
if (!sub) return "";
|
|
905
|
+
if (!sub.description?.trim() && sub.segments.length === 0) return "";
|
|
906
|
+
const config = contextConfig();
|
|
907
|
+
let byKey = views.get(config);
|
|
908
|
+
if (!byKey) views.set(config, byKey = /* @__PURE__ */ new Map());
|
|
909
|
+
const key = `${segment.messageId ?? ""}\0${segment.id}\0${sub.id}`;
|
|
910
|
+
let view = byKey.get(key);
|
|
911
|
+
if (!view) {
|
|
912
|
+
view = document.createElement("aparte-subagent-view");
|
|
913
|
+
const held = view;
|
|
914
|
+
const map = byKey;
|
|
915
|
+
feedView(view, segment, config, () => {
|
|
916
|
+
if (map.get(key) === held) map.delete(key);
|
|
917
|
+
});
|
|
918
|
+
map.set(key, view);
|
|
919
|
+
if (map.size > MAX_VIEWS) {
|
|
920
|
+
for (const [k, v] of map) {
|
|
921
|
+
if (map.size <= MAX_VIEWS) break;
|
|
922
|
+
if (!v.isConnected) map.delete(k);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
return view;
|
|
926
|
+
}
|
|
927
|
+
feedView(view, segment, config);
|
|
928
|
+
return view;
|
|
929
|
+
};
|
|
930
|
+
const WORDS = {
|
|
931
|
+
/** How many tool calls a sub-agent made, on its line: "3 tools". */
|
|
932
|
+
subagentsTools: {
|
|
933
|
+
en: "one:{n} tool|other:{n} tools",
|
|
934
|
+
fr: "one:{n} outil|other:{n} outils",
|
|
935
|
+
de: "one:{n} Tool|other:{n} Tools",
|
|
936
|
+
es: "one:{n} herramienta|other:{n} herramientas",
|
|
937
|
+
it: "one:{n} strumento|other:{n} strumenti",
|
|
938
|
+
ja: "other:{n} 個のツール",
|
|
939
|
+
ko: "other:도구 {n}개",
|
|
940
|
+
pt: "one:{n} ferramenta|other:{n} ferramentas",
|
|
941
|
+
ru: "one:{n} инструмент|few:{n} инструмента|many:{n} инструментов|other:{n} инструмента",
|
|
942
|
+
zh: "other:{n} 个工具"
|
|
943
|
+
},
|
|
944
|
+
/** What a sub-agent's second line says before its first call. */
|
|
945
|
+
subagentsStarting: {
|
|
946
|
+
en: "Starting…",
|
|
947
|
+
fr: "Démarrage…",
|
|
948
|
+
de: "Wird gestartet…",
|
|
949
|
+
es: "Iniciando…",
|
|
950
|
+
it: "Avvio…",
|
|
951
|
+
ja: "開始しています…",
|
|
952
|
+
ko: "시작하는 중…",
|
|
953
|
+
pt: "Iniciando…",
|
|
954
|
+
ru: "Запуск…",
|
|
955
|
+
zh: "正在启动…"
|
|
956
|
+
},
|
|
957
|
+
/** The name of a sub-agent that was given none, when no call names its line either. */
|
|
958
|
+
subagentsUnnamed: {
|
|
959
|
+
en: "Sub-agent",
|
|
960
|
+
fr: "Sous-agent",
|
|
961
|
+
de: "Subagent",
|
|
962
|
+
es: "Subagente",
|
|
963
|
+
it: "Sottoagente",
|
|
964
|
+
ja: "サブエージェント",
|
|
965
|
+
ko: "하위 에이전트",
|
|
966
|
+
pt: "Subagente",
|
|
967
|
+
ru: "Субагент",
|
|
968
|
+
zh: "子智能体"
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
function languageOf(config) {
|
|
972
|
+
const tag = config.getLocale().tag;
|
|
973
|
+
return typeof tag === "string" && tag ? tag.split("-")[0].toLowerCase() : "en";
|
|
974
|
+
}
|
|
975
|
+
function word(config, key) {
|
|
976
|
+
const fromApp = config.getLocale()[key];
|
|
977
|
+
const table = WORDS[key];
|
|
978
|
+
return typeof fromApp === "string" && fromApp || table[languageOf(config)] || table.en;
|
|
979
|
+
}
|
|
980
|
+
function toolCount(config, n) {
|
|
981
|
+
const tag = config.getLocale().tag;
|
|
982
|
+
return pluralize(word(config, "subagentsTools"), n, typeof tag === "string" && tag ? tag : void 0);
|
|
983
|
+
}
|
|
984
|
+
const standIns = /* @__PURE__ */ new WeakSet();
|
|
985
|
+
function markStandIn(segment) {
|
|
986
|
+
standIns.add(segment);
|
|
987
|
+
return segment;
|
|
988
|
+
}
|
|
989
|
+
const isOpenCall = (segment) => segment.type === "tool_call" && ["pending", "awaiting-approval"].includes(segment.status ?? "pending");
|
|
990
|
+
const textOf = (read) => {
|
|
991
|
+
try {
|
|
992
|
+
const value = read();
|
|
993
|
+
return typeof value === "string" ? value.trim() : "";
|
|
994
|
+
} catch {
|
|
995
|
+
return "";
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
function callLine(call, config) {
|
|
999
|
+
const name = call.toolCall?.name ?? "";
|
|
1000
|
+
const nested = subagentOf(call);
|
|
1001
|
+
const label = config.getToolDisplay(name)?.label;
|
|
1002
|
+
const shown = (label !== void 0 ? textOf(() => typeof label === "function" ? label(call) : label) : "") || nested?.name?.trim() || name;
|
|
1003
|
+
const summary = config.getToolSummary(name);
|
|
1004
|
+
const target = (summary ? textOf(() => summary(call)) : "") || nested?.description?.trim() || "";
|
|
1005
|
+
return [shown, target].filter(Boolean).join(" ");
|
|
1006
|
+
}
|
|
1007
|
+
function activityOf(sub, config) {
|
|
1008
|
+
let latest;
|
|
1009
|
+
for (let i = sub.segments.length - 1; i >= 0; i -= 1) {
|
|
1010
|
+
const segment = sub.segments[i];
|
|
1011
|
+
if (segment.type !== "tool_call") continue;
|
|
1012
|
+
if (isOpenCall(segment)) return callLine(segment, config);
|
|
1013
|
+
latest ??= segment;
|
|
1014
|
+
}
|
|
1015
|
+
return latest ? callLine(latest, config) : "";
|
|
1016
|
+
}
|
|
1017
|
+
function durationOf(anchor, sub) {
|
|
1018
|
+
const start = segmentTiming(anchor)?.startedAt;
|
|
1019
|
+
if (start === void 0) return void 0;
|
|
1020
|
+
let end;
|
|
1021
|
+
for (const segment of sub.segments) {
|
|
1022
|
+
const timing = segmentTiming(segment);
|
|
1023
|
+
const at = timing?.endedAt ?? timing?.startedAt;
|
|
1024
|
+
if (at !== void 0 && (end === void 0 || at > end)) end = at;
|
|
1025
|
+
}
|
|
1026
|
+
end ??= segmentTiming(anchor)?.endedAt;
|
|
1027
|
+
return end === void 0 || end < start ? void 0 : end - start;
|
|
1028
|
+
}
|
|
1029
|
+
function stateWord(config, status) {
|
|
1030
|
+
switch (status) {
|
|
1031
|
+
case "running":
|
|
1032
|
+
return config.t("toolRunning");
|
|
1033
|
+
case "waiting":
|
|
1034
|
+
return config.t("approvalWaiting");
|
|
1035
|
+
case "done":
|
|
1036
|
+
return config.t("toolCompleted");
|
|
1037
|
+
case "failed":
|
|
1038
|
+
return config.t("toolFailed");
|
|
1039
|
+
default:
|
|
1040
|
+
return config.t("toolStopped");
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
function createLineProvider(options = {}) {
|
|
1044
|
+
return (segment) => {
|
|
1045
|
+
const sub = subagentOf(segment);
|
|
1046
|
+
if (!sub) return void 0;
|
|
1047
|
+
const config = contextConfig();
|
|
1048
|
+
const standIn = standIns.has(segment);
|
|
1049
|
+
const status = subagentStatus(segment);
|
|
1050
|
+
const live = status === "running" || status === "waiting";
|
|
1051
|
+
const line = {};
|
|
1052
|
+
const name = sub.name?.trim();
|
|
1053
|
+
if (name) line.label = name;
|
|
1054
|
+
else if (standIn) line.label = word(config, "subagentsUnnamed");
|
|
1055
|
+
const task = sub.description?.trim();
|
|
1056
|
+
if (task) line.target = task;
|
|
1057
|
+
const summary = () => {
|
|
1058
|
+
if (options.counts === false) return "";
|
|
1059
|
+
const parts = [];
|
|
1060
|
+
const tools = sub.segments.filter((s) => s.type === "tool_call").length;
|
|
1061
|
+
if (tools > 0) parts.push(toolCount(config, tools));
|
|
1062
|
+
const ms = durationOf(segment, sub);
|
|
1063
|
+
if (ms !== void 0) {
|
|
1064
|
+
const tag = config.getLocale().tag;
|
|
1065
|
+
parts.push(formatDuration(ms, typeof tag === "string" && tag ? tag : void 0));
|
|
1066
|
+
}
|
|
1067
|
+
return parts.join(" · ");
|
|
1068
|
+
};
|
|
1069
|
+
const note = [];
|
|
1070
|
+
if (!standIn && statusOfCall(segment.status) !== status) note.push(stateWord(config, status));
|
|
1071
|
+
if (options.activity !== false) {
|
|
1072
|
+
line.activity = live ? activityOf(sub, config) || word(config, "subagentsStarting") : summary() || stateWord(config, status);
|
|
1073
|
+
} else if (!live) {
|
|
1074
|
+
const done = summary();
|
|
1075
|
+
if (done) note.push(done);
|
|
1076
|
+
}
|
|
1077
|
+
if (note.length > 0) line.note = note.join(" · ");
|
|
1078
|
+
const details = options.details;
|
|
1079
|
+
line.details = typeof details === "function" ? details : details === "transcript" ? subagentTranscript : false;
|
|
1080
|
+
return line;
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
const UNNAMED_AGENT = "subagent";
|
|
1084
|
+
function standInCall(segment) {
|
|
1085
|
+
const sub = subagentOf(segment);
|
|
1086
|
+
const status = subagentStatus(segment);
|
|
1087
|
+
const call = {
|
|
1088
|
+
id: segment.id,
|
|
1089
|
+
type: "tool_call",
|
|
1090
|
+
...segment.messageId !== void 0 ? { messageId: segment.messageId } : {},
|
|
1091
|
+
...segment.index !== void 0 ? { index: segment.index } : {},
|
|
1092
|
+
// Its timing is the segment's: the line's duration reads it.
|
|
1093
|
+
...segment.meta !== void 0 ? { meta: segment.meta } : {},
|
|
1094
|
+
...segment.collapsed !== void 0 ? { collapsed: segment.collapsed } : {},
|
|
1095
|
+
toolCall: { id: sub?.id ?? segment.id, name: sub?.name?.trim() || UNNAMED_AGENT, input: {} },
|
|
1096
|
+
status: callStatusOf(status),
|
|
1097
|
+
// A failure reads as a failed call's does.
|
|
1098
|
+
...status === "failed" && sub?.error ? { result: sub.error } : {},
|
|
1099
|
+
subagent: segment.subagent
|
|
1100
|
+
};
|
|
1101
|
+
return markStandIn(call);
|
|
1102
|
+
}
|
|
1103
|
+
const toolRow = () => getSegmentRenderer("tool_call", contextConfig());
|
|
1104
|
+
function replace(element, out, segment) {
|
|
1105
|
+
const tpl = document.createElement("template");
|
|
1106
|
+
tpl.innerHTML = typeof out === "string" ? out.trim() : "";
|
|
1107
|
+
const next = typeof out === "string" ? tpl.content.firstElementChild : out;
|
|
1108
|
+
if (!next) return null;
|
|
1109
|
+
if (!next.hasAttribute("data-segment-id")) next.setAttribute("data-segment-id", segment.id);
|
|
1110
|
+
element.replaceWith(next);
|
|
1111
|
+
return next;
|
|
1112
|
+
}
|
|
1113
|
+
const subagentRenderer = {
|
|
1114
|
+
type: SUBAGENT_SEGMENT_TYPE,
|
|
1115
|
+
render: (segment) => toolRow()?.render(standInCall(segment)) ?? "",
|
|
1116
|
+
setup: (element, segment) => toolRow()?.setup?.(element, standInCall(segment)),
|
|
1117
|
+
update: (element, segment) => {
|
|
1118
|
+
const row = toolRow();
|
|
1119
|
+
if (!row) return;
|
|
1120
|
+
const call = standInCall(segment);
|
|
1121
|
+
if (row.update) {
|
|
1122
|
+
row.update(element, call);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
row.cleanup?.(element, call);
|
|
1126
|
+
const next = replace(element, row.render(call), segment);
|
|
1127
|
+
if (next) row.setup?.(next, call);
|
|
1128
|
+
},
|
|
1129
|
+
relabel: (element, segment) => toolRow()?.relabel?.(element, standInCall(segment)),
|
|
1130
|
+
cleanup: (element, segment) => toolRow()?.cleanup?.(element, standInCall(segment)),
|
|
1131
|
+
// The plugin's sheet (the transcript's, the view's): core puts it on the page with the
|
|
1132
|
+
// renderers' own, once, unless a view already did.
|
|
1133
|
+
getStyles: rendererStyles
|
|
1134
|
+
};
|
|
1135
|
+
const installed = /* @__PURE__ */ new WeakMap();
|
|
1136
|
+
function setupSubagents(options = {}, config = aparteGlobalConfig) {
|
|
1137
|
+
installed.get(config)?.();
|
|
1138
|
+
const lanes = createLanes();
|
|
1139
|
+
const provider = createLineProvider({ ...options });
|
|
1140
|
+
const stopObserving = config.observeRunEvents(lanes.observe);
|
|
1141
|
+
config.setToolLineProvider(provider);
|
|
1142
|
+
registerSegmentRenderer(subagentRenderer, config);
|
|
1143
|
+
let disposed = false;
|
|
1144
|
+
const dispose = () => {
|
|
1145
|
+
if (disposed) return;
|
|
1146
|
+
disposed = true;
|
|
1147
|
+
stopObserving();
|
|
1148
|
+
lanes.dispose();
|
|
1149
|
+
if (config.getToolLineProvider() === provider) config.setToolLineProvider(null);
|
|
1150
|
+
if (getSegmentRenderer(SUBAGENT_SEGMENT_TYPE, config) === subagentRenderer) unregisterSegmentRenderer(SUBAGENT_SEGMENT_TYPE, config);
|
|
1151
|
+
if (installed.get(config) === dispose) installed.delete(config);
|
|
1152
|
+
};
|
|
1153
|
+
installed.set(config, dispose);
|
|
1154
|
+
return dispose;
|
|
1155
|
+
}
|
|
1156
|
+
export {
|
|
1157
|
+
AparteSubagentView as A,
|
|
1158
|
+
SUBAGENT_SEGMENT_TYPE as S,
|
|
1159
|
+
subagentOf as a,
|
|
1160
|
+
subagentStatus as b,
|
|
1161
|
+
subagentTranscript as c,
|
|
1162
|
+
setupSubagents as s
|
|
1163
|
+
};
|
|
1164
|
+
//# sourceMappingURL=setup-CaTzFtu8.js.map
|