@narumitw/pi-subagents 3.3.3 → 3.4.1
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/README.md +74 -25
- package/dist/child-communication-bridge.ts +3 -2
- package/dist/child-communication-bridge.ts.map +1 -1
- package/dist/child-readiness-probe.ts +114 -17
- package/dist/child-readiness-probe.ts.map +2 -2
- package/dist/chunks/{chunk-FY5HIU6Y.ts → chunk-5OXDVXXO.ts} +1 -1
- package/dist/chunks/{chunk-FY5HIU6Y.ts.map → chunk-5OXDVXXO.ts.map} +1 -1
- package/dist/chunks/chunk-B3RWNGDX.ts +192 -0
- package/dist/chunks/chunk-B3RWNGDX.ts.map +7 -0
- package/dist/chunks/{chunk-ZOAFWLDJ.ts → chunk-PRIDTAJL.ts} +4 -16
- package/dist/chunks/chunk-PRIDTAJL.ts.map +7 -0
- package/dist/chunks/{chunk-YMVSJ5GE.ts → chunk-YV2P6XJX.ts} +46 -17
- package/dist/chunks/chunk-YV2P6XJX.ts.map +7 -0
- package/dist/chunks/{menu-KAIUEWL7.ts → menu-2UH4ICLL.ts} +74 -3
- package/dist/chunks/menu-2UH4ICLL.ts.map +7 -0
- package/dist/index.ts +554 -293
- package/dist/index.ts.map +3 -3
- package/docs/attachments.md +9 -3
- package/docs/design-principles.md +3 -1
- package/docs/messaging.md +3 -1
- package/docs/tools.md +15 -1
- package/package.json +3 -3
- package/src/broker-credentials.ts +44 -13
- package/src/child-communication-tools.ts +2 -19
- package/src/child-readiness-probe.ts +144 -18
- package/src/child-readiness.ts +118 -0
- package/src/menu.ts +82 -3
- package/src/message-broker.ts +23 -10
- package/src/process.ts +59 -167
- package/src/resource-attachments.ts +1 -3
- package/src/rpc-line-decoder.ts +61 -0
- package/src/runtime.ts +22 -10
- package/src/settings.ts +2 -2
- package/src/subagents.ts +110 -18
- package/src/tools.ts +83 -37
- package/src/types.ts +1 -0
- package/src/widget-renderer.ts +119 -0
- package/src/widget.ts +245 -121
- package/dist/chunks/chunk-3FCHXUKZ.ts +0 -98
- package/dist/chunks/chunk-3FCHXUKZ.ts.map +0 -7
- package/dist/chunks/chunk-YMVSJ5GE.ts.map +0 -7
- package/dist/chunks/chunk-ZOAFWLDJ.ts.map +0 -7
- package/dist/chunks/menu-KAIUEWL7.ts.map +0 -7
package/dist/index.ts
CHANGED
|
@@ -4,37 +4,43 @@ import {
|
|
|
4
4
|
CHILD_COMMUNICATION_TOOL_NAMES,
|
|
5
5
|
prepareTimeoutArguments,
|
|
6
6
|
resolveTimeoutMs
|
|
7
|
-
} from "./chunks/chunk-
|
|
7
|
+
} from "./chunks/chunk-PRIDTAJL.ts";
|
|
8
8
|
import {
|
|
9
9
|
addAttachmentIgnoreRules,
|
|
10
|
+
assertChildModelPair,
|
|
11
|
+
assertChildModelState,
|
|
12
|
+
assertTrustedChildResources,
|
|
10
13
|
isAbortError,
|
|
11
14
|
isRecord,
|
|
12
15
|
isWithin,
|
|
16
|
+
parseReadinessFrame,
|
|
13
17
|
realpath,
|
|
14
18
|
throwIfAttachmentAborted,
|
|
15
19
|
toPosixPath,
|
|
16
20
|
toolSourceId
|
|
17
|
-
} from "./chunks/chunk-
|
|
21
|
+
} from "./chunks/chunk-B3RWNGDX.ts";
|
|
18
22
|
import {
|
|
19
23
|
BROKER_CREDENTIAL_FD,
|
|
20
24
|
CHILD_READINESS_FD,
|
|
21
|
-
|
|
25
|
+
MAX_BOOTSTRAP_BYTES,
|
|
22
26
|
MAX_IDENTIFIER_LENGTH,
|
|
23
27
|
MAX_MESSAGE_BYTES,
|
|
24
28
|
MAX_READINESS_FRAME_BYTES,
|
|
25
29
|
MessageBroker,
|
|
26
30
|
assertChildBootstrapCapacity,
|
|
27
31
|
childBootstrapEnvironment,
|
|
32
|
+
optionalIdentifier,
|
|
33
|
+
requiredIdentifier,
|
|
28
34
|
sanitizeTerminalText,
|
|
29
35
|
serializeChildBootstrap,
|
|
30
36
|
validateMessage
|
|
31
|
-
} from "./chunks/chunk-
|
|
37
|
+
} from "./chunks/chunk-YV2P6XJX.ts";
|
|
32
38
|
import {
|
|
33
39
|
CHILD_CORE_TOOL_NAMES,
|
|
34
40
|
DEFAULT_SUBAGENT_TOOLS,
|
|
35
41
|
SUBAGENT_THINKING_LEVELS,
|
|
36
42
|
TERMINAL_JOB_STATES
|
|
37
|
-
} from "./chunks/chunk-
|
|
43
|
+
} from "./chunks/chunk-5OXDVXXO.ts";
|
|
38
44
|
|
|
39
45
|
// src/display-text.ts
|
|
40
46
|
function sanitizeSubagentDisplayText(value) {
|
|
@@ -102,8 +108,8 @@ import { dirname, join } from "node:path";
|
|
|
102
108
|
import { getAgentDir, withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
103
109
|
var DEFAULT_SUBAGENT_SETTINGS = Object.freeze({
|
|
104
110
|
showWidget: true,
|
|
105
|
-
showJobId:
|
|
106
|
-
showTools:
|
|
111
|
+
showJobId: false,
|
|
112
|
+
showTools: false,
|
|
107
113
|
showTiming: true
|
|
108
114
|
});
|
|
109
115
|
var SETTINGS_KEYS = Object.keys(DEFAULT_SUBAGENT_SETTINGS);
|
|
@@ -263,10 +269,68 @@ import * as path from "node:path";
|
|
|
263
269
|
import { StringDecoder } from "node:string_decoder";
|
|
264
270
|
import { fileURLToPath } from "node:url";
|
|
265
271
|
import { getPackageDir } from "@earendil-works/pi-coding-agent";
|
|
272
|
+
|
|
273
|
+
// src/rpc-line-decoder.ts
|
|
274
|
+
var MAX_EVENT_LINE_BYTES = 256 * 1024;
|
|
275
|
+
var JsonLineDecoder = class {
|
|
276
|
+
constructor(onValue, onMalformed) {
|
|
277
|
+
this.onValue = onValue;
|
|
278
|
+
this.onMalformed = onMalformed;
|
|
279
|
+
}
|
|
280
|
+
onValue;
|
|
281
|
+
onMalformed;
|
|
282
|
+
fragments = [];
|
|
283
|
+
bytes = 0;
|
|
284
|
+
dropping = false;
|
|
285
|
+
decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
286
|
+
push(chunk) {
|
|
287
|
+
const data = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
288
|
+
let offset = 0;
|
|
289
|
+
while (offset < data.length) {
|
|
290
|
+
const newline = data.indexOf(10, offset);
|
|
291
|
+
const end = newline < 0 ? data.length : newline;
|
|
292
|
+
const fragment = data.subarray(offset, end);
|
|
293
|
+
if (!this.dropping) {
|
|
294
|
+
if (this.bytes + fragment.length > MAX_EVENT_LINE_BYTES) {
|
|
295
|
+
this.clear();
|
|
296
|
+
this.dropping = true;
|
|
297
|
+
this.onMalformed();
|
|
298
|
+
} else if (fragment.length > 0) {
|
|
299
|
+
this.fragments.push(Buffer.from(fragment));
|
|
300
|
+
this.bytes += fragment.length;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (newline < 0) return;
|
|
304
|
+
this.complete();
|
|
305
|
+
offset = newline + 1;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
finish() {
|
|
309
|
+
this.complete();
|
|
310
|
+
}
|
|
311
|
+
complete() {
|
|
312
|
+
const dropping = this.dropping;
|
|
313
|
+
const line = Buffer.concat(this.fragments, this.bytes);
|
|
314
|
+
this.clear();
|
|
315
|
+
this.dropping = false;
|
|
316
|
+
if (dropping || line.length === 0) return;
|
|
317
|
+
try {
|
|
318
|
+
const text = this.decoder.decode(line);
|
|
319
|
+
if (text.trim()) this.onValue(JSON.parse(text));
|
|
320
|
+
} catch {
|
|
321
|
+
this.onMalformed();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
clear() {
|
|
325
|
+
this.fragments = [];
|
|
326
|
+
this.bytes = 0;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/process.ts
|
|
266
331
|
var CORE_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
|
|
267
332
|
var MAX_OUTPUT_BYTES = 32 * 1024;
|
|
268
333
|
var MAX_ERROR_BYTES = 8 * 1024;
|
|
269
|
-
var MAX_EVENT_LINE_BYTES = 256 * 1024;
|
|
270
334
|
var RPC_RESPONSE_TIMEOUT_MS = 3e4;
|
|
271
335
|
var KILL_GRACE_MS = 1e3;
|
|
272
336
|
var MAX_WINDOWS_COMMAND_LINE_CODE_UNITS = 32767;
|
|
@@ -302,15 +366,9 @@ function buildPiArgs(request) {
|
|
|
302
366
|
];
|
|
303
367
|
if (request.tools.includes("codemode")) args.push("-e", "builtin:codemode");
|
|
304
368
|
for (const extension of request.extensions) args.push("-e", extension.path);
|
|
305
|
-
|
|
369
|
+
args.push("-e", childReadinessProbePath());
|
|
306
370
|
for (const skill of request.skills) args.push("--skill", skill);
|
|
307
|
-
args.push(
|
|
308
|
-
"--model",
|
|
309
|
-
request.model,
|
|
310
|
-
"--thinking",
|
|
311
|
-
request.thinkingLevel,
|
|
312
|
-
request.projectTrusted ? "--approve" : "--no-approve"
|
|
313
|
-
);
|
|
371
|
+
args.push("--thinking", request.thinkingLevel, request.projectTrusted ? "--approve" : "--no-approve");
|
|
314
372
|
args.push("--tools", selectedChildTools(request).join(","));
|
|
315
373
|
return args;
|
|
316
374
|
}
|
|
@@ -346,9 +404,6 @@ function childCommunicationBridgePath() {
|
|
|
346
404
|
function childReadinessProbePath() {
|
|
347
405
|
return fileURLToPath(new URL("./child-readiness-probe.ts", import.meta.url));
|
|
348
406
|
}
|
|
349
|
-
function requiresReadinessAttestation(request) {
|
|
350
|
-
return request.tools.includes("codemode") || request.extensions.length > 0;
|
|
351
|
-
}
|
|
352
407
|
function selectedChildTools(request) {
|
|
353
408
|
return [
|
|
354
409
|
.../* @__PURE__ */ new Set([
|
|
@@ -361,8 +416,8 @@ function selectedChildTools(request) {
|
|
|
361
416
|
async function executeProcess(invocation, request) {
|
|
362
417
|
const timeoutMs = resolveTimeoutMs(request.timeout);
|
|
363
418
|
const expectedTools = selectedChildTools(request);
|
|
364
|
-
const
|
|
365
|
-
|
|
419
|
+
const selection = { provider: request.provider, model: request.model, thinkingLevel: request.thinkingLevel };
|
|
420
|
+
assertChildBootstrapCapacity(expectedTools, selection);
|
|
366
421
|
let latestOutput = "";
|
|
367
422
|
let terminalOutput;
|
|
368
423
|
let terminalStopReason;
|
|
@@ -397,7 +452,7 @@ async function executeProcess(invocation, request) {
|
|
|
397
452
|
};
|
|
398
453
|
let malformedEvents = 0;
|
|
399
454
|
let rpcCounter = 0;
|
|
400
|
-
let
|
|
455
|
+
let attachmentReady = false;
|
|
401
456
|
let attachmentStartupError;
|
|
402
457
|
const pendingCommands = /* @__PURE__ */ new Map();
|
|
403
458
|
let rpcInputError;
|
|
@@ -436,7 +491,9 @@ async function executeProcess(invocation, request) {
|
|
|
436
491
|
const decoder = new JsonLineDecoder(
|
|
437
492
|
(value) => {
|
|
438
493
|
const event = value;
|
|
439
|
-
if (
|
|
494
|
+
if (event.type === "extension_error" && ["session_start", "resources_discover", "model_select", "thinking_level_select"].includes(
|
|
495
|
+
String(event.event)
|
|
496
|
+
) && !attachmentStartupError) {
|
|
440
497
|
const detail = typeof event.error === "string" && event.error ? redactAttachmentPaths(sanitizeTerminalText(event.error), request) : "Unknown error.";
|
|
441
498
|
attachmentStartupError = `Subagent attachment startup failed during ${event.event}: ${detail}`;
|
|
442
499
|
return;
|
|
@@ -444,7 +501,9 @@ async function executeProcess(invocation, request) {
|
|
|
444
501
|
if (event.type === "response" && typeof event.id === "string") {
|
|
445
502
|
const pending = pendingCommands.get(event.id);
|
|
446
503
|
if (!pending) return;
|
|
447
|
-
if (event.
|
|
504
|
+
if (event.command !== pending.command) {
|
|
505
|
+
rejectPendingCommand(event.id, new Error("Subagent RPC response returned an invalid command."));
|
|
506
|
+
} else if (event.success === true) {
|
|
448
507
|
resolvePendingCommand(event.id, event);
|
|
449
508
|
} else {
|
|
450
509
|
rejectPendingCommand(
|
|
@@ -483,8 +542,9 @@ async function executeProcess(invocation, request) {
|
|
|
483
542
|
},
|
|
484
543
|
() => {
|
|
485
544
|
malformedEvents++;
|
|
486
|
-
if (
|
|
545
|
+
if (!attachmentReady && !attachmentStartupError) {
|
|
487
546
|
attachmentStartupError = "Subagent attachment startup emitted malformed or oversized RPC output.";
|
|
547
|
+
failRpcInput(new Error(attachmentStartupError));
|
|
488
548
|
}
|
|
489
549
|
}
|
|
490
550
|
);
|
|
@@ -498,10 +558,10 @@ async function executeProcess(invocation, request) {
|
|
|
498
558
|
let timedOut = false;
|
|
499
559
|
let completed = false;
|
|
500
560
|
let ready = false;
|
|
501
|
-
let attachmentReady = !expectReadiness;
|
|
502
561
|
let promptStarted = false;
|
|
503
562
|
let readinessPipe;
|
|
504
563
|
let deadline;
|
|
564
|
+
let startupDeadline;
|
|
505
565
|
let forceClose;
|
|
506
566
|
let escalation;
|
|
507
567
|
let termination;
|
|
@@ -512,6 +572,7 @@ async function executeProcess(invocation, request) {
|
|
|
512
572
|
if (settled) return;
|
|
513
573
|
settled = true;
|
|
514
574
|
if (deadline) clearTimeout(deadline);
|
|
575
|
+
if (startupDeadline) clearTimeout(startupDeadline);
|
|
515
576
|
if (forceClose) clearTimeout(forceClose);
|
|
516
577
|
if (escalation) clearTimeout(escalation);
|
|
517
578
|
request.signal.removeEventListener("abort", onAbort);
|
|
@@ -526,6 +587,8 @@ async function executeProcess(invocation, request) {
|
|
|
526
587
|
const terminate = (code) => {
|
|
527
588
|
if (settled || terminating) return;
|
|
528
589
|
terminating = true;
|
|
590
|
+
rejectPendingCommands(new Error("Subagent RPC process is terminating."));
|
|
591
|
+
if (startupDeadline) clearTimeout(startupDeadline);
|
|
529
592
|
if (deadline) {
|
|
530
593
|
clearTimeout(deadline);
|
|
531
594
|
deadline = void 0;
|
|
@@ -561,15 +624,14 @@ async function executeProcess(invocation, request) {
|
|
|
561
624
|
try {
|
|
562
625
|
const environment = {
|
|
563
626
|
...globalThis.process.env,
|
|
564
|
-
...childBootstrapEnvironment(
|
|
627
|
+
...childBootstrapEnvironment(true),
|
|
565
628
|
PI_SUBAGENT_DEPTH: String((Number.parseInt(globalThis.process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0) + 1)
|
|
566
629
|
};
|
|
567
|
-
if (!expectReadiness) delete environment[CHILD_READINESS_FD_ENV];
|
|
568
630
|
process2 = spawn(invocation.command, invocation.args, {
|
|
569
631
|
cwd: request.cwd,
|
|
570
632
|
detached: globalThis.process.platform !== "win32",
|
|
571
633
|
shell: false,
|
|
572
|
-
stdio:
|
|
634
|
+
stdio: ["pipe", "pipe", "pipe", "pipe", "pipe"],
|
|
573
635
|
env: environment
|
|
574
636
|
});
|
|
575
637
|
} catch (error2) {
|
|
@@ -626,8 +688,10 @@ async function executeProcess(invocation, request) {
|
|
|
626
688
|
});
|
|
627
689
|
};
|
|
628
690
|
const startPrompt = () => {
|
|
629
|
-
if (!spawned || !attachmentReady || promptStarted || settled || cancelled || terminating)
|
|
691
|
+
if (!spawned || !attachmentReady || promptStarted || settled || cancelled || terminating || request.signal.aborted)
|
|
692
|
+
return;
|
|
630
693
|
promptStarted = true;
|
|
694
|
+
if (startupDeadline) clearTimeout(startupDeadline);
|
|
631
695
|
void sendCommand(
|
|
632
696
|
{ type: "prompt", message: `Task: ${request.task}` },
|
|
633
697
|
() => {
|
|
@@ -664,11 +728,18 @@ async function executeProcess(invocation, request) {
|
|
|
664
728
|
};
|
|
665
729
|
const failReadiness = (message) => {
|
|
666
730
|
if (settled || terminating || attachmentReady) return;
|
|
667
|
-
|
|
731
|
+
const limited = truncateText(redactAttachmentPaths(sanitizeTerminalText(message), request), MAX_ERROR_BYTES);
|
|
732
|
+
errorMessage = limited.text;
|
|
733
|
+
truncated ||= limited.truncated;
|
|
668
734
|
terminate(1);
|
|
669
735
|
};
|
|
670
736
|
const confirmAttachmentStartup = () => {
|
|
671
|
-
void sendCommand(
|
|
737
|
+
void sendCommand(
|
|
738
|
+
{ type: "get_state" },
|
|
739
|
+
(response) => assertChildModelState(response.data, request),
|
|
740
|
+
request.signal
|
|
741
|
+
).then(async () => {
|
|
742
|
+
if (settled || terminating || request.signal.aborted) return;
|
|
672
743
|
if (attachmentStartupError) throw new Error(attachmentStartupError);
|
|
673
744
|
if (!request.projectTrusted) {
|
|
674
745
|
await sendCommand(
|
|
@@ -677,8 +748,8 @@ async function executeProcess(invocation, request) {
|
|
|
677
748
|
request.signal
|
|
678
749
|
);
|
|
679
750
|
}
|
|
751
|
+
if (settled || terminating || request.signal.aborted) return;
|
|
680
752
|
if (attachmentStartupError) throw new Error(attachmentStartupError);
|
|
681
|
-
attachmentStartupPending = false;
|
|
682
753
|
attachmentReady = true;
|
|
683
754
|
startPrompt();
|
|
684
755
|
}).catch((error2) => {
|
|
@@ -691,7 +762,9 @@ async function executeProcess(invocation, request) {
|
|
|
691
762
|
spawned = true;
|
|
692
763
|
startPrompt();
|
|
693
764
|
});
|
|
694
|
-
|
|
765
|
+
{
|
|
766
|
+
startupDeadline = setTimeout(() => failReadiness("Subagent child readiness timed out."), RPC_RESPONSE_TIMEOUT_MS);
|
|
767
|
+
startupDeadline.unref();
|
|
695
768
|
const candidate = process2.stdio[CHILD_READINESS_FD];
|
|
696
769
|
if (!candidate || !("on" in candidate)) {
|
|
697
770
|
errorMessage = "Subagent readiness pipe is unavailable.";
|
|
@@ -714,6 +787,7 @@ async function executeProcess(invocation, request) {
|
|
|
714
787
|
let frame;
|
|
715
788
|
try {
|
|
716
789
|
frame = parseReadinessFrame(buffer, expectedTools.length);
|
|
790
|
+
if (frame.ok) assertChildModelPair(frame.model, request);
|
|
717
791
|
} catch (error2) {
|
|
718
792
|
failReadiness(error2 instanceof Error ? error2.message : String(error2));
|
|
719
793
|
return;
|
|
@@ -779,7 +853,8 @@ async function executeProcess(invocation, request) {
|
|
|
779
853
|
credentialPipe.end(
|
|
780
854
|
serializeChildBootstrap({
|
|
781
855
|
communication: request.communication,
|
|
782
|
-
expectedTools
|
|
856
|
+
expectedTools,
|
|
857
|
+
selection
|
|
783
858
|
})
|
|
784
859
|
);
|
|
785
860
|
} catch {
|
|
@@ -920,62 +995,6 @@ function killImmediateChild(process2) {
|
|
|
920
995
|
} catch {
|
|
921
996
|
}
|
|
922
997
|
}
|
|
923
|
-
function assertTrustedChildResources(value, cwd) {
|
|
924
|
-
const invalid = () => new Error("Subagent child resource attestation returned invalid commands.");
|
|
925
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalid();
|
|
926
|
-
const commands = value.commands;
|
|
927
|
-
if (!Array.isArray(commands)) throw invalid();
|
|
928
|
-
let canonicalCwd;
|
|
929
|
-
try {
|
|
930
|
-
canonicalCwd = fs2.realpathSync(cwd);
|
|
931
|
-
} catch {
|
|
932
|
-
throw invalid();
|
|
933
|
-
}
|
|
934
|
-
for (const command of commands) {
|
|
935
|
-
if (!command || typeof command !== "object" || Array.isArray(command)) throw invalid();
|
|
936
|
-
const entry = command;
|
|
937
|
-
if (entry.source !== "skill" && entry.source !== "prompt") continue;
|
|
938
|
-
const resourcePath = entry.sourceInfo?.path;
|
|
939
|
-
if (typeof resourcePath !== "string" || !resourcePath) throw invalid();
|
|
940
|
-
const lexicalPath = path.resolve(cwd, resourcePath);
|
|
941
|
-
let canonicalPath;
|
|
942
|
-
try {
|
|
943
|
-
canonicalPath = fs2.realpathSync(lexicalPath);
|
|
944
|
-
} catch {
|
|
945
|
-
throw invalid();
|
|
946
|
-
}
|
|
947
|
-
if (isWithin(cwd, lexicalPath) || isWithin(canonicalCwd, canonicalPath)) {
|
|
948
|
-
throw new Error("Subagent child cannot load project resources because the project is not trusted.");
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
function parseReadinessFrame(buffer, expectedToolCount) {
|
|
953
|
-
if (buffer.byteLength === 0) {
|
|
954
|
-
throw new Error("Subagent readiness pipe closed without a result.");
|
|
955
|
-
}
|
|
956
|
-
const text = buffer.toString("utf8");
|
|
957
|
-
const newline = text.indexOf("\n");
|
|
958
|
-
if (newline < 0 || newline !== text.length - 1) {
|
|
959
|
-
throw new Error("Subagent readiness pipe returned an invalid frame.");
|
|
960
|
-
}
|
|
961
|
-
let value;
|
|
962
|
-
try {
|
|
963
|
-
value = JSON.parse(text.slice(0, newline));
|
|
964
|
-
} catch {
|
|
965
|
-
throw new Error("Subagent readiness pipe returned malformed JSON.");
|
|
966
|
-
}
|
|
967
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
968
|
-
throw new Error("Subagent readiness pipe returned an invalid result.");
|
|
969
|
-
}
|
|
970
|
-
const record = value;
|
|
971
|
-
if (record.ok === true && Object.keys(record).length === 2 && Array.isArray(record.sources) && record.sources.length === expectedToolCount && record.sources.every((source) => typeof source === "string" && /^[a-f0-9]{64}$/u.test(source))) {
|
|
972
|
-
return { ok: true, sources: record.sources };
|
|
973
|
-
}
|
|
974
|
-
if (record.ok === false && Object.keys(record).length === 2 && typeof record.error === "string" && record.error.length > 0 && Buffer.byteLength(record.error, "utf8") <= MAX_ERROR_BYTES) {
|
|
975
|
-
return { ok: false, error: sanitizeTerminalText(record.error) };
|
|
976
|
-
}
|
|
977
|
-
throw new Error("Subagent readiness pipe returned an invalid result.");
|
|
978
|
-
}
|
|
979
998
|
function redactAttachmentPaths(value, request) {
|
|
980
999
|
return redactAttachmentPathVariants(value, attachmentPathVariants(request));
|
|
981
1000
|
}
|
|
@@ -1043,58 +1062,6 @@ ${bytes.subarray(bytes.length - Math.max(0, maxBytes - 18)).toString("utf8").rep
|
|
|
1043
1062
|
truncated: true
|
|
1044
1063
|
};
|
|
1045
1064
|
}
|
|
1046
|
-
var JsonLineDecoder = class {
|
|
1047
|
-
constructor(onValue, onMalformed) {
|
|
1048
|
-
this.onValue = onValue;
|
|
1049
|
-
this.onMalformed = onMalformed;
|
|
1050
|
-
}
|
|
1051
|
-
onValue;
|
|
1052
|
-
onMalformed;
|
|
1053
|
-
buffer = "";
|
|
1054
|
-
dropping = false;
|
|
1055
|
-
decoder = new StringDecoder("utf8");
|
|
1056
|
-
push(chunk) {
|
|
1057
|
-
this.buffer += typeof chunk === "string" ? chunk : this.decoder.write(chunk);
|
|
1058
|
-
this.drain(false);
|
|
1059
|
-
}
|
|
1060
|
-
finish() {
|
|
1061
|
-
this.buffer += this.decoder.end();
|
|
1062
|
-
this.drain(true);
|
|
1063
|
-
this.buffer = "";
|
|
1064
|
-
this.dropping = false;
|
|
1065
|
-
}
|
|
1066
|
-
drain(flush) {
|
|
1067
|
-
while (true) {
|
|
1068
|
-
const newline = this.buffer.indexOf("\n");
|
|
1069
|
-
if (newline < 0) break;
|
|
1070
|
-
const line = this.buffer.slice(0, newline).replace(/\r$/u, "");
|
|
1071
|
-
this.buffer = this.buffer.slice(newline + 1);
|
|
1072
|
-
if (this.dropping) {
|
|
1073
|
-
this.dropping = false;
|
|
1074
|
-
continue;
|
|
1075
|
-
}
|
|
1076
|
-
this.parse(line);
|
|
1077
|
-
}
|
|
1078
|
-
if (!flush && Buffer.byteLength(this.buffer, "utf8") > MAX_EVENT_LINE_BYTES) {
|
|
1079
|
-
this.onMalformed();
|
|
1080
|
-
this.buffer = "";
|
|
1081
|
-
this.dropping = true;
|
|
1082
|
-
}
|
|
1083
|
-
if (flush && this.buffer && !this.dropping) this.parse(this.buffer.replace(/\r$/u, ""));
|
|
1084
|
-
}
|
|
1085
|
-
parse(line) {
|
|
1086
|
-
if (!line.trim()) return;
|
|
1087
|
-
if (Buffer.byteLength(line, "utf8") > MAX_EVENT_LINE_BYTES) {
|
|
1088
|
-
this.onMalformed();
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
try {
|
|
1092
|
-
this.onValue(JSON.parse(line));
|
|
1093
|
-
} catch {
|
|
1094
|
-
this.onMalformed();
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
1065
|
|
|
1099
1066
|
// src/resource-attachments.ts
|
|
1100
1067
|
import { existsSync, statSync as statSync3 } from "node:fs";
|
|
@@ -1442,9 +1409,7 @@ function resolveResourcePath(value, kind, cwd, canonicalCwd, projectTrusted) {
|
|
|
1442
1409
|
if (!stats.isFile() && !stats.isDirectory()) {
|
|
1443
1410
|
throw new Error(`Subagent ${kind} path must reference a file or directory.`);
|
|
1444
1411
|
}
|
|
1445
|
-
|
|
1446
|
-
throw new Error(`Subagent ${kind} cannot load a project path because the project is not trusted.`);
|
|
1447
|
-
}
|
|
1412
|
+
assertProjectPathTrusted(lexicalPath, canonicalPath, kind, cwd, canonicalCwd, projectTrusted);
|
|
1448
1413
|
return canonicalPath;
|
|
1449
1414
|
}
|
|
1450
1415
|
async function assertResolvableExtensionAttachment(extensionPath, cwd, canonicalCwd, projectTrusted, signal) {
|
|
@@ -1863,7 +1828,6 @@ var SubagentRuntime = class {
|
|
|
1863
1828
|
now;
|
|
1864
1829
|
counter = 0;
|
|
1865
1830
|
generation = 0;
|
|
1866
|
-
deliveryEnabled = false;
|
|
1867
1831
|
sessionActive = false;
|
|
1868
1832
|
omittedJobs = 0;
|
|
1869
1833
|
jobListeners = /* @__PURE__ */ new Set();
|
|
@@ -1872,7 +1836,6 @@ var SubagentRuntime = class {
|
|
|
1872
1836
|
this.generation++;
|
|
1873
1837
|
this.jobs.clear();
|
|
1874
1838
|
this.omittedJobs = 0;
|
|
1875
|
-
this.deliveryEnabled = true;
|
|
1876
1839
|
this.sessionActive = true;
|
|
1877
1840
|
this.notifyJobsChanged();
|
|
1878
1841
|
}
|
|
@@ -1889,7 +1852,9 @@ var SubagentRuntime = class {
|
|
|
1889
1852
|
jobId: job.jobId,
|
|
1890
1853
|
task: job.taskText,
|
|
1891
1854
|
state: job.state,
|
|
1855
|
+
...job.modelOverrides,
|
|
1892
1856
|
thinkingLevel: job.thinkingLevel,
|
|
1857
|
+
defaultThinkingLevel: job.defaultThinkingLevel,
|
|
1893
1858
|
elapsedMs: Math.max(0, now - (job.startedAt ?? job.createdAt)),
|
|
1894
1859
|
...job.timeout !== void 0 ? { timeout: job.timeout } : {},
|
|
1895
1860
|
tools: [...job.tools]
|
|
@@ -1905,6 +1870,7 @@ var SubagentRuntime = class {
|
|
|
1905
1870
|
if (active >= MAX_ACTIVE_JOBS) {
|
|
1906
1871
|
throw new Error(`Active subagent job limit reached (${MAX_ACTIVE_JOBS}).`);
|
|
1907
1872
|
}
|
|
1873
|
+
const { provider, model } = input;
|
|
1908
1874
|
const jobId = `job_${this.now().toString(36)}_${(++this.counter).toString(36)}`;
|
|
1909
1875
|
const communication = this.broker.issueCredentials({
|
|
1910
1876
|
jobId,
|
|
@@ -1931,7 +1897,9 @@ var SubagentRuntime = class {
|
|
|
1931
1897
|
controller,
|
|
1932
1898
|
tools: effectiveTools,
|
|
1933
1899
|
taskText: input.task,
|
|
1900
|
+
modelOverrides: { ...input.modelOverrides },
|
|
1934
1901
|
thinkingLevel: input.thinkingLevel,
|
|
1902
|
+
defaultThinkingLevel: input.defaultThinkingLevel,
|
|
1935
1903
|
terminal,
|
|
1936
1904
|
resolveTerminal,
|
|
1937
1905
|
controlReady,
|
|
@@ -1939,7 +1907,6 @@ var SubagentRuntime = class {
|
|
|
1939
1907
|
rejectControl,
|
|
1940
1908
|
sendQueue: Promise.resolve(),
|
|
1941
1909
|
limitations: [],
|
|
1942
|
-
deliverySent: false,
|
|
1943
1910
|
generation: this.generation
|
|
1944
1911
|
};
|
|
1945
1912
|
this.jobs.set(jobId, job);
|
|
@@ -1966,7 +1933,8 @@ var SubagentRuntime = class {
|
|
|
1966
1933
|
toolSources: Object.fromEntries(
|
|
1967
1934
|
Object.entries(input.toolSources).map(([tool, sources]) => [tool, [...sources]])
|
|
1968
1935
|
),
|
|
1969
|
-
|
|
1936
|
+
provider,
|
|
1937
|
+
model,
|
|
1970
1938
|
thinkingLevel: input.thinkingLevel,
|
|
1971
1939
|
cwd: input.cwd,
|
|
1972
1940
|
timeout: input.timeout,
|
|
@@ -2100,7 +2068,6 @@ var SubagentRuntime = class {
|
|
|
2100
2068
|
}
|
|
2101
2069
|
async shutdown() {
|
|
2102
2070
|
if (!this.sessionActive) return;
|
|
2103
|
-
this.deliveryEnabled = false;
|
|
2104
2071
|
this.sessionActive = false;
|
|
2105
2072
|
const active = [...this.jobs.values()].filter((job) => !isTerminal(job.state));
|
|
2106
2073
|
await Promise.allSettled(
|
|
@@ -2154,8 +2121,7 @@ var SubagentRuntime = class {
|
|
|
2154
2121
|
this.prune();
|
|
2155
2122
|
}
|
|
2156
2123
|
deliver(job) {
|
|
2157
|
-
if (!this.
|
|
2158
|
-
job.deliverySent = true;
|
|
2124
|
+
if (!this.sessionActive || job.generation !== this.generation) return;
|
|
2159
2125
|
const payload = this.waitResult(job, false);
|
|
2160
2126
|
try {
|
|
2161
2127
|
this.pi.sendMessage(
|
|
@@ -2165,7 +2131,7 @@ var SubagentRuntime = class {
|
|
|
2165
2131
|
display: true,
|
|
2166
2132
|
details: payload
|
|
2167
2133
|
},
|
|
2168
|
-
{ deliverAs: "steer" }
|
|
2134
|
+
{ deliverAs: "steer", triggerTurn: true }
|
|
2169
2135
|
);
|
|
2170
2136
|
} catch {
|
|
2171
2137
|
}
|
|
@@ -2276,6 +2242,20 @@ var SpawnParameters = Type.Object(
|
|
|
2276
2242
|
description: "Self-contained task, constraints, and expected result. Maximum 50 KiB.",
|
|
2277
2243
|
maxLength: MAX_TASK_BYTES
|
|
2278
2244
|
}),
|
|
2245
|
+
provider: Type.Optional(
|
|
2246
|
+
Type.String({
|
|
2247
|
+
description: "Exact child provider ID. Defaults to the main agent's effective provider at spawn time.",
|
|
2248
|
+
minLength: 1,
|
|
2249
|
+
maxLength: MAX_BOOTSTRAP_BYTES
|
|
2250
|
+
})
|
|
2251
|
+
),
|
|
2252
|
+
model: Type.Optional(
|
|
2253
|
+
Type.String({
|
|
2254
|
+
description: "Exact model ID within the selected provider. Defaults independently to the main agent's effective model ID at spawn time. Unlisted models fail without fallback.",
|
|
2255
|
+
minLength: 1,
|
|
2256
|
+
maxLength: MAX_BOOTSTRAP_BYTES
|
|
2257
|
+
})
|
|
2258
|
+
),
|
|
2279
2259
|
tools: Type.Optional(
|
|
2280
2260
|
Type.Array(
|
|
2281
2261
|
StringEnum(CHILD_CORE_TOOL_NAMES, {
|
|
@@ -2410,23 +2390,25 @@ function registerSubagentTools(pi, dependencies = {}) {
|
|
|
2410
2390
|
if (ctx.cwd !== cwd || ctx.isProjectTrusted() !== projectTrusted) {
|
|
2411
2391
|
throw new Error("Subagent spawn context changed during attachment validation; retry the request.");
|
|
2412
2392
|
}
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
const model = resolveChildModel(ctx, attachments.extensions.length > 0);
|
|
2417
|
-
const thinkingLevel = resolveThinkingLevel(params.thinkingLevel ?? ctx.thinkingLevel ?? pi.getThinkingLevel());
|
|
2393
|
+
const { provider, model, modelOverrides } = resolveChildModel(ctx, params, attachments.extensions.length > 0);
|
|
2394
|
+
const defaultThinkingLevel = resolveThinkingLevel(ctx.thinkingLevel ?? pi.getThinkingLevel());
|
|
2395
|
+
const thinkingLevel = resolveThinkingLevel(params.thinkingLevel ?? defaultThinkingLevel);
|
|
2418
2396
|
resolveTimeoutMs(params.timeout);
|
|
2419
2397
|
assertChildCommandCapacity(
|
|
2420
2398
|
{
|
|
2421
2399
|
tools,
|
|
2422
2400
|
skills: attachments.skills,
|
|
2423
2401
|
extensions: attachments.extensions,
|
|
2424
|
-
model,
|
|
2425
2402
|
thinkingLevel,
|
|
2426
2403
|
projectTrusted
|
|
2427
2404
|
},
|
|
2428
2405
|
dependencies.platform
|
|
2429
2406
|
);
|
|
2407
|
+
assertChildBootstrapCapacity(selectedChildTools({ tools, extensions: attachments.extensions }), {
|
|
2408
|
+
provider,
|
|
2409
|
+
model,
|
|
2410
|
+
thinkingLevel
|
|
2411
|
+
});
|
|
2430
2412
|
throwIfAborted2(signal, "Subagent spawn was cancelled");
|
|
2431
2413
|
return toolResult(
|
|
2432
2414
|
runtime.start({
|
|
@@ -2435,8 +2417,11 @@ function registerSubagentTools(pi, dependencies = {}) {
|
|
|
2435
2417
|
skills: attachments.skills,
|
|
2436
2418
|
extensions: attachments.extensions,
|
|
2437
2419
|
toolSources: attachments.toolSources,
|
|
2420
|
+
provider,
|
|
2438
2421
|
model,
|
|
2422
|
+
modelOverrides,
|
|
2439
2423
|
thinkingLevel,
|
|
2424
|
+
defaultThinkingLevel,
|
|
2440
2425
|
cwd,
|
|
2441
2426
|
timeout: params.timeout,
|
|
2442
2427
|
projectTrusted
|
|
@@ -2573,19 +2558,47 @@ function resolveTools(value) {
|
|
|
2573
2558
|
}
|
|
2574
2559
|
return tools;
|
|
2575
2560
|
}
|
|
2576
|
-
function resolveChildModel(ctx, hasAttachedExtensions) {
|
|
2577
|
-
const
|
|
2578
|
-
if (!
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2561
|
+
function resolveChildModel(ctx, overrides, hasAttachedExtensions) {
|
|
2562
|
+
const parent = ctx.model;
|
|
2563
|
+
if (!parent && (overrides.provider === void 0 || overrides.model === void 0)) {
|
|
2564
|
+
throw new Error("Subagent model is unavailable because no main-agent model is selected.");
|
|
2565
|
+
}
|
|
2566
|
+
const provider = validateModelIdentifier(
|
|
2567
|
+
overrides.provider === void 0 ? parent?.provider : overrides.provider,
|
|
2568
|
+
"provider"
|
|
2569
|
+
);
|
|
2570
|
+
const model = validateModelIdentifier(overrides.model === void 0 ? parent?.id : overrides.model, "model");
|
|
2571
|
+
const displayProvider = sanitizeTerminalText(provider).slice(0, 128);
|
|
2572
|
+
if (!hasAttachedExtensions && ctx.modelRegistry.getRegisteredProviderIds().includes(provider)) {
|
|
2573
|
+
throw new Error(
|
|
2574
|
+
`Subagent model provider ${displayProvider} is unavailable because children disable parent extensions.`
|
|
2575
|
+
);
|
|
2582
2576
|
}
|
|
2583
|
-
if (ctx.modelRegistry.getProviderAuthStatus(
|
|
2577
|
+
if (ctx.modelRegistry.getProviderAuthStatus(provider).source === "runtime") {
|
|
2584
2578
|
throw new Error(
|
|
2585
|
-
`Subagent model provider ${
|
|
2579
|
+
`Subagent model provider ${displayProvider} uses a process-local runtime API key. Configure stored or environment credentials that child processes can read.`
|
|
2586
2580
|
);
|
|
2587
2581
|
}
|
|
2588
|
-
return
|
|
2582
|
+
return {
|
|
2583
|
+
provider,
|
|
2584
|
+
model,
|
|
2585
|
+
modelOverrides: {
|
|
2586
|
+
...provider !== parent?.provider ? { provider } : {},
|
|
2587
|
+
...model !== parent?.id ? { model } : {}
|
|
2588
|
+
}
|
|
2589
|
+
};
|
|
2590
|
+
}
|
|
2591
|
+
function validateModelIdentifier(value, field) {
|
|
2592
|
+
if (typeof value === "string" && (value.length > MAX_BOOTSTRAP_BYTES || Buffer.byteLength(value, "utf8") > MAX_BOOTSTRAP_BYTES)) {
|
|
2593
|
+
throw new Error(`Subagent ${field} exceeds the ${MAX_BOOTSTRAP_BYTES}-byte child bootstrap size limit.`);
|
|
2594
|
+
}
|
|
2595
|
+
if (typeof value !== "string" || !value.trim() || [...value].some((character) => {
|
|
2596
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
2597
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
2598
|
+
}) || sanitizeTerminalText(value) !== value) {
|
|
2599
|
+
throw new Error(`Subagent ${field} must be a non-empty exact ID without control characters.`);
|
|
2600
|
+
}
|
|
2601
|
+
return value;
|
|
2589
2602
|
}
|
|
2590
2603
|
function resolveThinkingLevel(value) {
|
|
2591
2604
|
if (typeof value !== "string" || !THINKING_LEVEL_SET.has(value)) {
|
|
@@ -2608,23 +2621,10 @@ function resolveMainSendArguments(params) {
|
|
|
2608
2621
|
}
|
|
2609
2622
|
return recipient !== void 0 ? { kind: "request", recipient, message: params.message } : { kind: "response", requestId: requestId ?? "", message: params.message };
|
|
2610
2623
|
}
|
|
2611
|
-
function optionalIdentifier(value, field) {
|
|
2612
|
-
return value === void 0 ? void 0 : requiredIdentifier(value, field);
|
|
2613
|
-
}
|
|
2614
2624
|
function requiredString(value, field) {
|
|
2615
2625
|
if (typeof value !== "string" || !value.trim()) throw new Error(`Subagent ${field} is required.`);
|
|
2616
2626
|
return value.trim();
|
|
2617
2627
|
}
|
|
2618
|
-
function requiredIdentifier(value, field) {
|
|
2619
|
-
const identifier = requiredString(value, field);
|
|
2620
|
-
if (identifier.length > MAX_IDENTIFIER_LENGTH || [...identifier].some((character) => {
|
|
2621
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
2622
|
-
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
2623
|
-
})) {
|
|
2624
|
-
throw new Error(`Subagent ${field} is invalid.`);
|
|
2625
|
-
}
|
|
2626
|
-
return identifier;
|
|
2627
|
-
}
|
|
2628
2628
|
function assertNotNested() {
|
|
2629
2629
|
if ((Number.parseInt(process.env.PI_SUBAGENT_DEPTH ?? "0", 10) || 0) > 0) {
|
|
2630
2630
|
throw new Error("Nested subagents are not supported by pi-subagents.");
|
|
@@ -2646,86 +2646,45 @@ function toolResult(value) {
|
|
|
2646
2646
|
}
|
|
2647
2647
|
|
|
2648
2648
|
// src/widget.ts
|
|
2649
|
+
import {
|
|
2650
|
+
createEditorStatusCollection
|
|
2651
|
+
} from "@narumitw/pi-tui-kit/editor-status-collection";
|
|
2652
|
+
import { createEditorStatusPublisher } from "@narumitw/pi-tui-kit/editor-status-publisher";
|
|
2653
|
+
import {
|
|
2654
|
+
createEditorStatusView
|
|
2655
|
+
} from "@narumitw/pi-tui-kit/editor-status-view";
|
|
2656
|
+
|
|
2657
|
+
// src/widget-renderer.ts
|
|
2649
2658
|
import { stripTerminalSequences, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
2650
2659
|
import { EditorStatusWidget } from "@narumitw/pi-tui-kit/editor-status-widget";
|
|
2651
|
-
var SUBAGENT_WIDGET_KEY = "subagents";
|
|
2652
|
-
var SUBAGENT_WIDGET_REFRESH_INTERVAL_MS = 1e3;
|
|
2653
|
-
var WIDGET_OPTIONS = { placement: "aboveEditor" };
|
|
2654
2660
|
var TASK_PREVIEW_WIDTH = 40;
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
(_tui, theme) => createSubagentWidget(snapshot, theme, preferences),
|
|
2683
|
-
WIDGET_OPTIONS
|
|
2684
|
-
);
|
|
2685
|
-
}
|
|
2686
|
-
publishedValue = value;
|
|
2687
|
-
};
|
|
2688
|
-
return {
|
|
2689
|
-
start(ctx, preferences = DEFAULT_SUBAGENT_SETTINGS) {
|
|
2690
|
-
stop();
|
|
2691
|
-
clearWidget?.();
|
|
2692
|
-
clearWidget = void 0;
|
|
2693
|
-
settings = { ...preferences };
|
|
2694
|
-
activeSession = ctx.sessionManager;
|
|
2695
|
-
publishedValue = void 0;
|
|
2696
|
-
if (ctx.mode !== "tui") return;
|
|
2697
|
-
clearWidget = () => ctx.ui.setWidget(SUBAGENT_WIDGET_KEY, void 0);
|
|
2698
|
-
clearWidget();
|
|
2699
|
-
unsubscribeJobs = runtime.subscribeJobs(() => publish(ctx));
|
|
2700
|
-
refreshTimer = setInterval(() => publish(ctx), SUBAGENT_WIDGET_REFRESH_INTERVAL_MS);
|
|
2701
|
-
refreshTimer.unref();
|
|
2702
|
-
publish(ctx);
|
|
2703
|
-
},
|
|
2704
|
-
update(ctx, preferences) {
|
|
2705
|
-
if (!ownsSession(ctx)) return;
|
|
2706
|
-
settings = { ...preferences };
|
|
2707
|
-
publish(ctx);
|
|
2708
|
-
},
|
|
2709
|
-
shutdown(ctx) {
|
|
2710
|
-
if (!ownsSession(ctx)) return;
|
|
2711
|
-
stop();
|
|
2712
|
-
clearWidget?.();
|
|
2713
|
-
clearWidget = void 0;
|
|
2714
|
-
publishedValue = void 0;
|
|
2715
|
-
activeSession = void 0;
|
|
2716
|
-
}
|
|
2717
|
-
};
|
|
2718
|
-
}
|
|
2719
|
-
function createSubagentWidget(jobs, theme, settings) {
|
|
2720
|
-
return new EditorStatusWidget({
|
|
2721
|
-
theme,
|
|
2722
|
-
renderBody: () => [
|
|
2723
|
-
theme.fg("muted", `Subagents \xB7 ${jobs.length} active`),
|
|
2724
|
-
...jobs.map((job) => renderJob(job, theme, settings))
|
|
2725
|
-
]
|
|
2726
|
-
});
|
|
2727
|
-
}
|
|
2728
|
-
function renderJob(job, theme, settings) {
|
|
2661
|
+
var SUBAGENT_WIDGET_RESERVED_ROWS = 8;
|
|
2662
|
+
var SUBAGENT_WIDGET_MAX_ROWS = 10;
|
|
2663
|
+
function subagentWidgetRowBudget(rows) {
|
|
2664
|
+
return Number.isFinite(rows) ? Math.min(SUBAGENT_WIDGET_MAX_ROWS, Math.max(0, Math.floor(rows) - SUBAGENT_WIDGET_RESERVED_ROWS)) : 0;
|
|
2665
|
+
}
|
|
2666
|
+
function renderSubagentWidgetSnapshot(snapshot, theme, width, maxRows) {
|
|
2667
|
+
const { jobs, items, settings, mode } = snapshot;
|
|
2668
|
+
const budget = Number.isFinite(maxRows) ? Math.max(0, Math.floor(maxRows)) : 0;
|
|
2669
|
+
if (!settings.showWidget || mode === "hidden" || jobs.length === 0 || items.length === 0 || budget < 2) return [];
|
|
2670
|
+
const running = jobs.filter((job) => job.state === "running").length;
|
|
2671
|
+
const counts = `Subagents \xB7 ${jobs.length} active \xB7 ${running} running \xB7 ${jobs.length - running} queued`;
|
|
2672
|
+
const removed = jobs.length - items.length;
|
|
2673
|
+
const summary = (shown) => `${counts}${jobs.length > shown ? ` \xB7 ${jobs.length - shown} not shown${removed ? ` (${removed} removed)` : ""}` : ""}`;
|
|
2674
|
+
const bodyRows = budget - 1;
|
|
2675
|
+
let body;
|
|
2676
|
+
if (mode === "compact" || bodyRows === 1) {
|
|
2677
|
+
body = [theme.fg("muted", summary(0))];
|
|
2678
|
+
} else {
|
|
2679
|
+
const shown = items.slice(0, bodyRows - 1);
|
|
2680
|
+
body = [
|
|
2681
|
+
theme.fg("muted", shown.length === jobs.length ? `Subagents \xB7 ${jobs.length} active` : summary(shown.length)),
|
|
2682
|
+
...shown.map(({ value, mode: itemMode }) => renderJob(value, theme, settings, itemMode === "compact"))
|
|
2683
|
+
];
|
|
2684
|
+
}
|
|
2685
|
+
return new EditorStatusWidget({ theme, renderBody: () => body }).render(width);
|
|
2686
|
+
}
|
|
2687
|
+
function renderJob(job, theme, settings, compact) {
|
|
2729
2688
|
const running = job.state === "running";
|
|
2730
2689
|
const symbol = theme.fg(running ? "accent" : "dim", running ? "\u25B6 " : "\u25CB ");
|
|
2731
2690
|
const state = theme.fg(running ? "accent" : "muted", job.state);
|
|
@@ -2733,27 +2692,25 @@ function renderJob(job, theme, settings) {
|
|
|
2733
2692
|
const taskText = sanitizeSubagentDisplayText(job.task);
|
|
2734
2693
|
const task = stripTerminalSequences(truncateToWidth(taskText, TASK_PREVIEW_WIDTH, "\u2026"));
|
|
2735
2694
|
const labels = [
|
|
2736
|
-
...settings.showJobId ? [theme.fg("text", jobId)] : [],
|
|
2737
|
-
...visibleWidth(task) > 0 ? [theme.fg("text", task)] : []
|
|
2738
|
-
state
|
|
2695
|
+
...settings.showJobId && visibleWidth(jobId) > 0 ? [theme.fg("text", jobId)] : [],
|
|
2696
|
+
...visibleWidth(task) > 0 ? [theme.fg("text", task)] : []
|
|
2739
2697
|
];
|
|
2698
|
+
const hasLabel = labels.length > 0;
|
|
2699
|
+
if (!hasLabel) labels.push(state);
|
|
2700
|
+
if (compact) return `${symbol}${labels.join(" \xB7 ")}${!running && hasLabel ? ` \xB7 ${state}` : ""}`;
|
|
2740
2701
|
const tools = job.tools.length > 0 ? job.tools.map(sanitizeSubagentDisplayText).join(", ") : "none";
|
|
2741
|
-
const timeout = job.timeout === void 0 ?
|
|
2702
|
+
const timeout = job.timeout === void 0 ? void 0 : formatSeconds(job.timeout);
|
|
2703
|
+
const elapsed = formatSeconds(Math.floor(job.elapsedMs / 1e3));
|
|
2704
|
+
const timing = running ? `${elapsed}${timeout === void 0 ? "" : ` / ${timeout}`}` : timeout === void 0 ? void 0 : `timeout: ${timeout}`;
|
|
2742
2705
|
const details = [
|
|
2743
|
-
`
|
|
2744
|
-
...
|
|
2706
|
+
...job.provider !== void 0 ? [`provider: ${sanitizeSubagentDisplayText(job.provider)}`] : [],
|
|
2707
|
+
...job.model !== void 0 ? [`model: ${sanitizeSubagentDisplayText(job.model)}`] : [],
|
|
2708
|
+
...job.thinkingLevel !== job.defaultThinkingLevel ? [`thinking: ${sanitizeSubagentDisplayText(job.thinkingLevel)}`] : [],
|
|
2709
|
+
...!running && hasLabel ? ["queued"] : [],
|
|
2710
|
+
...settings.showTiming && timing !== void 0 ? [timing] : [],
|
|
2745
2711
|
...settings.showTools ? [`tools: ${tools}`] : []
|
|
2746
2712
|
];
|
|
2747
|
-
|
|
2748
|
-
return `${symbol}${labels.join(" \xB7 ")}${detail}`;
|
|
2749
|
-
}
|
|
2750
|
-
function widgetValue(jobs) {
|
|
2751
|
-
return jobs.map(
|
|
2752
|
-
(job) => `${job.jobId}\0${job.task}\0${job.state}\0${job.thinkingLevel}\0${Math.floor(job.elapsedMs / 1e3)}\0${job.timeout ?? ""}\0${job.tools.join(",")}`
|
|
2753
|
-
).join("\n");
|
|
2754
|
-
}
|
|
2755
|
-
function cloneDisplayJob(job) {
|
|
2756
|
-
return { ...job, tools: [...job.tools] };
|
|
2713
|
+
return `${symbol}${labels.join(" \xB7 ")}${details.length > 0 ? theme.fg("muted", ` \xB7 ${details.join(" \xB7 ")}`) : ""}`;
|
|
2757
2714
|
}
|
|
2758
2715
|
function formatSeconds(value) {
|
|
2759
2716
|
if (value < 60) return `${formatNumber(value)}s`;
|
|
@@ -2768,6 +2725,225 @@ function formatNumber(value) {
|
|
|
2768
2725
|
return Number.isInteger(value) ? String(value) : value.toFixed(3).replace(/0+$/u, "").replace(/\.$/u, "");
|
|
2769
2726
|
}
|
|
2770
2727
|
|
|
2728
|
+
// src/widget.ts
|
|
2729
|
+
var SUBAGENT_WIDGET_KEY = "subagents";
|
|
2730
|
+
var SUBAGENT_WIDGET_REFRESH_INTERVAL_MS = 1e3;
|
|
2731
|
+
function createCollection() {
|
|
2732
|
+
return createEditorStatusCollection({
|
|
2733
|
+
maxItems: 8,
|
|
2734
|
+
// Runtime accepts at most eight active jobs.
|
|
2735
|
+
// 50 KiB task + 64 tool names (128 UTF-16 units each) + 16 KiB model selection, plus metadata.
|
|
2736
|
+
maxContentSize: 128 * 1024,
|
|
2737
|
+
measure: (job) => Buffer.byteLength(
|
|
2738
|
+
[job.jobId, job.task, job.provider ?? "", job.model ?? "", job.thinkingLevel, ...job.tools].join("\0"),
|
|
2739
|
+
"utf8"
|
|
2740
|
+
) + 128
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
function createSubagentWidgetController(runtime) {
|
|
2744
|
+
let activeSession;
|
|
2745
|
+
let generation = 0;
|
|
2746
|
+
let refreshTimer;
|
|
2747
|
+
let unsubscribeJobs;
|
|
2748
|
+
let publisher;
|
|
2749
|
+
let collection;
|
|
2750
|
+
let view;
|
|
2751
|
+
let controls;
|
|
2752
|
+
let jobs = [];
|
|
2753
|
+
let publishedValue;
|
|
2754
|
+
let cyclePhase = "idle";
|
|
2755
|
+
let lastOutcome;
|
|
2756
|
+
let settings = { ...DEFAULT_SUBAGENT_SETTINGS };
|
|
2757
|
+
const ownsSession = (ctx) => activeSession !== void 0 && ctx.sessionManager === activeSession;
|
|
2758
|
+
const stop = (errors = []) => {
|
|
2759
|
+
try {
|
|
2760
|
+
for (const release of [
|
|
2761
|
+
() => {
|
|
2762
|
+
if (refreshTimer) clearInterval(refreshTimer);
|
|
2763
|
+
},
|
|
2764
|
+
() => unsubscribeJobs?.(),
|
|
2765
|
+
() => view?.dispose(),
|
|
2766
|
+
() => collection?.dispose(),
|
|
2767
|
+
// The publisher must still own its key when it attempts to clear it.
|
|
2768
|
+
() => publisher?.dispose()
|
|
2769
|
+
]) {
|
|
2770
|
+
try {
|
|
2771
|
+
release();
|
|
2772
|
+
} catch (error) {
|
|
2773
|
+
errors.push(error);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
} finally {
|
|
2777
|
+
generation++;
|
|
2778
|
+
activeSession = void 0;
|
|
2779
|
+
refreshTimer = void 0;
|
|
2780
|
+
unsubscribeJobs = void 0;
|
|
2781
|
+
view = void 0;
|
|
2782
|
+
collection = void 0;
|
|
2783
|
+
publisher = void 0;
|
|
2784
|
+
controls = void 0;
|
|
2785
|
+
jobs = [];
|
|
2786
|
+
publishedValue = void 0;
|
|
2787
|
+
cyclePhase = "idle";
|
|
2788
|
+
lastOutcome = void 0;
|
|
2789
|
+
}
|
|
2790
|
+
if (errors.length === 1) throw errors[0];
|
|
2791
|
+
if (errors.length > 1) throw new AggregateError(errors, "Subagent widget cleanup failed");
|
|
2792
|
+
};
|
|
2793
|
+
const publish = () => {
|
|
2794
|
+
if (!view || !collection || !publisher) return;
|
|
2795
|
+
const items = collection.snapshot();
|
|
2796
|
+
const value = JSON.stringify([settings, view.mode, jobs, items]);
|
|
2797
|
+
if (value === publishedValue) return;
|
|
2798
|
+
publisher.publish(
|
|
2799
|
+
settings.showWidget && view.mode !== "hidden" && items.length > 0 ? { jobs, items, settings: { ...settings }, mode: view.mode } : void 0
|
|
2800
|
+
);
|
|
2801
|
+
publishedValue = value;
|
|
2802
|
+
};
|
|
2803
|
+
const refresh = () => {
|
|
2804
|
+
if (!view || !collection) return;
|
|
2805
|
+
const next = runtime.activeJobsForDisplay().map((job) => ({ ...job, tools: [...job.tools] }));
|
|
2806
|
+
const entries = next.map((job) => ({ id: job.jobId, value: job, revision: semanticRevision(job) }));
|
|
2807
|
+
collection.replace(entries);
|
|
2808
|
+
jobs = next;
|
|
2809
|
+
view.revision(JSON.stringify(entries.map(({ id, revision }) => [id, revision])));
|
|
2810
|
+
publish();
|
|
2811
|
+
};
|
|
2812
|
+
const finishCycle = (eligible) => {
|
|
2813
|
+
try {
|
|
2814
|
+
view?.settled(eligible);
|
|
2815
|
+
} finally {
|
|
2816
|
+
cyclePhase = "idle";
|
|
2817
|
+
lastOutcome = void 0;
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
const startCycle = (phase) => {
|
|
2821
|
+
if (cyclePhase === "idle" || cyclePhase === "maintenance") lastOutcome = void 0;
|
|
2822
|
+
cyclePhase = phase;
|
|
2823
|
+
view?.agentStarted();
|
|
2824
|
+
};
|
|
2825
|
+
const syncActiveCycle = (ctx) => {
|
|
2826
|
+
if (ctx.isIdle()) {
|
|
2827
|
+
if (cyclePhase !== "idle" && cyclePhase !== "running") finishCycle(false);
|
|
2828
|
+
} else if (cyclePhase === "idle" || cyclePhase === "maintenance") {
|
|
2829
|
+
startCycle(ctx.signal === void 0 ? "unconfirmed" : "running");
|
|
2830
|
+
}
|
|
2831
|
+
};
|
|
2832
|
+
return {
|
|
2833
|
+
start(ctx, preferences = DEFAULT_SUBAGENT_SETTINGS, phase = "idle") {
|
|
2834
|
+
stop();
|
|
2835
|
+
const token = generation;
|
|
2836
|
+
const isCurrent = () => token === generation && ownsSession(ctx);
|
|
2837
|
+
try {
|
|
2838
|
+
activeSession = ctx.sessionManager;
|
|
2839
|
+
settings = { ...preferences };
|
|
2840
|
+
if (ctx.mode !== "tui" || !ctx.hasUI) return;
|
|
2841
|
+
collection = createCollection();
|
|
2842
|
+
view = createEditorStatusView({ onChange: publish, canCompact: () => settings.showWidget && jobs.length > 0 });
|
|
2843
|
+
publisher = createEditorStatusPublisher({
|
|
2844
|
+
key: SUBAGENT_WIDGET_KEY,
|
|
2845
|
+
context: ctx,
|
|
2846
|
+
isCurrent,
|
|
2847
|
+
render: (snapshot, theme, width, rows) => renderSubagentWidgetSnapshot(snapshot, theme, width, subagentWidgetRowBudget(rows))
|
|
2848
|
+
});
|
|
2849
|
+
if (phase === "maintenance") cyclePhase = phase;
|
|
2850
|
+
else if (phase !== "idle") startCycle(phase);
|
|
2851
|
+
syncActiveCycle(ctx);
|
|
2852
|
+
const ownedView = view;
|
|
2853
|
+
controls = {
|
|
2854
|
+
state: () => ({ mode: ownedView.mode, pinned: ownedView.pinned, trigger: ownedView.trigger }),
|
|
2855
|
+
setMode: (mode) => {
|
|
2856
|
+
if (isCurrent()) ownedView.setMode(mode);
|
|
2857
|
+
},
|
|
2858
|
+
setPinned: (pinned) => {
|
|
2859
|
+
if (isCurrent()) ownedView.setPinned(pinned);
|
|
2860
|
+
},
|
|
2861
|
+
setTrigger: (trigger) => {
|
|
2862
|
+
if (!isCurrent()) return;
|
|
2863
|
+
syncActiveCycle(ctx);
|
|
2864
|
+
ownedView.setTrigger(trigger);
|
|
2865
|
+
},
|
|
2866
|
+
resetItems() {
|
|
2867
|
+
if (!isCurrent()) return;
|
|
2868
|
+
collection?.dispose();
|
|
2869
|
+
collection = createCollection();
|
|
2870
|
+
refresh();
|
|
2871
|
+
},
|
|
2872
|
+
item(jobId) {
|
|
2873
|
+
if (!isCurrent()) return void 0;
|
|
2874
|
+
const handle = collection?.item(jobId);
|
|
2875
|
+
if (!handle) return void 0;
|
|
2876
|
+
const apply = (action) => {
|
|
2877
|
+
if (!isCurrent() || !handle[action]()) return false;
|
|
2878
|
+
publish();
|
|
2879
|
+
return true;
|
|
2880
|
+
};
|
|
2881
|
+
return { compact: () => apply("compact"), expand: () => apply("expand"), remove: () => apply("remove") };
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
unsubscribeJobs = runtime.subscribeJobs(() => {
|
|
2885
|
+
if (isCurrent()) refresh();
|
|
2886
|
+
});
|
|
2887
|
+
refreshTimer = setInterval(() => {
|
|
2888
|
+
if (isCurrent()) refresh();
|
|
2889
|
+
}, SUBAGENT_WIDGET_REFRESH_INTERVAL_MS);
|
|
2890
|
+
refreshTimer.unref();
|
|
2891
|
+
refresh();
|
|
2892
|
+
} catch (error) {
|
|
2893
|
+
stop([error]);
|
|
2894
|
+
}
|
|
2895
|
+
},
|
|
2896
|
+
update(ctx, preferences) {
|
|
2897
|
+
if (!ownsSession(ctx)) return;
|
|
2898
|
+
settings = { ...preferences };
|
|
2899
|
+
if (ctx.mode === "tui") refresh();
|
|
2900
|
+
},
|
|
2901
|
+
shutdown(ctx) {
|
|
2902
|
+
if (!ownsSession(ctx)) return;
|
|
2903
|
+
stop();
|
|
2904
|
+
},
|
|
2905
|
+
controls(ctx) {
|
|
2906
|
+
return ownsSession(ctx) && ctx.mode === "tui" ? controls : void 0;
|
|
2907
|
+
},
|
|
2908
|
+
preparing(ctx) {
|
|
2909
|
+
if (!ownsSession(ctx) || !view) return;
|
|
2910
|
+
if (cyclePhase !== "auto-preparing" && cyclePhase !== "unconfirmed") finishCycle(false);
|
|
2911
|
+
startCycle("preparing");
|
|
2912
|
+
},
|
|
2913
|
+
automaticCompaction(ctx) {
|
|
2914
|
+
if (!ownsSession(ctx) || !view) return;
|
|
2915
|
+
if (cyclePhase === "preparing" || cyclePhase === "maintenance") finishCycle(false);
|
|
2916
|
+
if (cyclePhase === "idle" || cyclePhase === "unconfirmed") startCycle("auto-preparing");
|
|
2917
|
+
},
|
|
2918
|
+
clearCycle(ctx) {
|
|
2919
|
+
if (ownsSession(ctx)) finishCycle(false);
|
|
2920
|
+
},
|
|
2921
|
+
maintenance(ctx) {
|
|
2922
|
+
if (!ownsSession(ctx)) return;
|
|
2923
|
+
finishCycle(false);
|
|
2924
|
+
cyclePhase = "maintenance";
|
|
2925
|
+
},
|
|
2926
|
+
agentStarted(ctx) {
|
|
2927
|
+
if (ownsSession(ctx) && view) startCycle("running");
|
|
2928
|
+
},
|
|
2929
|
+
userMessage(ctx) {
|
|
2930
|
+
if (ownsSession(ctx)) view?.userMessage();
|
|
2931
|
+
},
|
|
2932
|
+
turnEnded(ctx, stopReason) {
|
|
2933
|
+
if (ownsSession(ctx) && cyclePhase !== "idle" && cyclePhase !== "maintenance") lastOutcome = stopReason;
|
|
2934
|
+
},
|
|
2935
|
+
settled(ctx, aborted) {
|
|
2936
|
+
if (!ownsSession(ctx) || !view) return;
|
|
2937
|
+
const eligible = !aborted && lastOutcome !== void 0 && lastOutcome !== "error" && lastOutcome !== "aborted";
|
|
2938
|
+
finishCycle(eligible);
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
}
|
|
2942
|
+
function semanticRevision(job) {
|
|
2943
|
+
const { elapsedMs: _elapsedMs, ...semantic } = job;
|
|
2944
|
+
return JSON.stringify(semantic);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2771
2947
|
// src/subagents.ts
|
|
2772
2948
|
function subagents(pi, dependencies = {}) {
|
|
2773
2949
|
registerMessageRenderers(pi);
|
|
@@ -2779,9 +2955,18 @@ function subagents(pi, dependencies = {}) {
|
|
|
2779
2955
|
let owner = new AbortController();
|
|
2780
2956
|
let ready = false;
|
|
2781
2957
|
let menuOpen = false;
|
|
2958
|
+
let cyclePhase = "idle";
|
|
2782
2959
|
let settings = { ...DEFAULT_SUBAGENT_SETTINGS };
|
|
2783
2960
|
const menuTasks = /* @__PURE__ */ new Set();
|
|
2784
|
-
const ownsSession = (ctx) => ctx.sessionManager === activeContext
|
|
2961
|
+
const ownsSession = (ctx) => activeContext !== void 0 && ctx.sessionManager === activeContext.sessionManager;
|
|
2962
|
+
const joinCleanup = async (steps, errors = []) => {
|
|
2963
|
+
const results = await Promise.allSettled(steps.map(async (step) => step()));
|
|
2964
|
+
for (const result of results) {
|
|
2965
|
+
if (result.status === "rejected") errors.push(result.reason);
|
|
2966
|
+
}
|
|
2967
|
+
if (errors.length === 1) throw errors[0];
|
|
2968
|
+
if (errors.length > 1) throw new AggregateError(errors, "Subagents session cleanup failed");
|
|
2969
|
+
};
|
|
2785
2970
|
const openSettings = async (ctx, signal, isCurrent) => {
|
|
2786
2971
|
if (signal.aborted || !isCurrent()) return "stale";
|
|
2787
2972
|
if (ctx.mode !== "tui") {
|
|
@@ -2825,13 +3010,13 @@ function subagents(pi, dependencies = {}) {
|
|
|
2825
3010
|
menuOpen = true;
|
|
2826
3011
|
const generation = sessionGeneration;
|
|
2827
3012
|
const signal = owner.signal;
|
|
2828
|
-
const isCurrent = () => generation === sessionGeneration && ownsSession(ctx)
|
|
3013
|
+
const isCurrent = () => generation === sessionGeneration && !signal.aborted && ownsSession(ctx);
|
|
2829
3014
|
const task = (async () => {
|
|
2830
3015
|
if (route === "settings") {
|
|
2831
3016
|
await openSettings(ctx, signal, isCurrent);
|
|
2832
3017
|
return;
|
|
2833
3018
|
}
|
|
2834
|
-
const { showSubagentsMenu } = await import("./chunks/menu-
|
|
3019
|
+
const { showSubagentsMenu } = await import("./chunks/menu-2UH4ICLL.ts");
|
|
2835
3020
|
if (!isCurrent()) return;
|
|
2836
3021
|
await showSubagentsMenu(ctx, {
|
|
2837
3022
|
signal,
|
|
@@ -2846,7 +3031,8 @@ function subagents(pi, dependencies = {}) {
|
|
|
2846
3031
|
if (!isCurrent()) throw new Error("The subagents session changed.");
|
|
2847
3032
|
await tools.runtime.cancel(jobId);
|
|
2848
3033
|
},
|
|
2849
|
-
showSettings: (actionSignal) => openSettings(ctx, actionSignal, isCurrent)
|
|
3034
|
+
showSettings: (actionSignal) => openSettings(ctx, actionSignal, isCurrent),
|
|
3035
|
+
widget: widget.controls(ctx)
|
|
2850
3036
|
});
|
|
2851
3037
|
})();
|
|
2852
3038
|
menuTasks.add(task);
|
|
@@ -2863,40 +3049,115 @@ function subagents(pi, dependencies = {}) {
|
|
|
2863
3049
|
}
|
|
2864
3050
|
});
|
|
2865
3051
|
pi.on("session_start", async (_event, ctx) => {
|
|
3052
|
+
const previousContext = activeContext;
|
|
3053
|
+
const generation = ++sessionGeneration;
|
|
3054
|
+
ready = false;
|
|
3055
|
+
menuOpen = false;
|
|
2866
3056
|
owner.abort();
|
|
2867
|
-
if (activeContext) widget.shutdown(activeContext);
|
|
2868
3057
|
owner = new AbortController();
|
|
3058
|
+
const signal = owner.signal;
|
|
2869
3059
|
activeContext = ctx;
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
const
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
3060
|
+
cyclePhase = ctx.signal === void 0 ? "idle" : "running";
|
|
3061
|
+
const isCurrent = () => generation === sessionGeneration && !signal.aborted && ownsSession(ctx);
|
|
3062
|
+
const errors = [];
|
|
3063
|
+
try {
|
|
3064
|
+
if (previousContext) widget.shutdown(previousContext);
|
|
3065
|
+
} catch (error) {
|
|
3066
|
+
errors.push(error);
|
|
3067
|
+
}
|
|
3068
|
+
const failedHandoff = errors.length > 0;
|
|
3069
|
+
await joinCleanup(
|
|
3070
|
+
[
|
|
3071
|
+
() => failedHandoff ? tools.shutdown() : tools.startSession(),
|
|
3072
|
+
...[...menuTasks].map((task) => () => task),
|
|
3073
|
+
...failedHandoff ? [() => store.flush()] : []
|
|
3074
|
+
],
|
|
3075
|
+
errors
|
|
3076
|
+
);
|
|
2877
3077
|
if (!isCurrent()) return;
|
|
2878
3078
|
const loaded = await store.load();
|
|
2879
3079
|
if (!isCurrent()) return;
|
|
2880
3080
|
settings = { ...loaded.settings };
|
|
3081
|
+
widget.start(ctx, settings, cyclePhase);
|
|
2881
3082
|
ready = true;
|
|
2882
|
-
widget.start(ctx, settings);
|
|
2883
3083
|
if (loaded.diagnostics.length > 0) {
|
|
2884
3084
|
const message = sanitizeSubagentDisplayText(`Subagent settings: ${loaded.diagnostics.join(" ")}`);
|
|
2885
3085
|
if (ctx.hasUI) ctx.ui.notify(message, "warning");
|
|
2886
3086
|
else throw new Error(message);
|
|
2887
3087
|
}
|
|
2888
3088
|
});
|
|
2889
|
-
pi.on("
|
|
3089
|
+
pi.on("before_agent_start", (_event, ctx) => {
|
|
3090
|
+
if (!ownsSession(ctx)) return;
|
|
3091
|
+
cyclePhase = "preparing";
|
|
3092
|
+
if (ready) widget.preparing(ctx);
|
|
3093
|
+
});
|
|
3094
|
+
pi.on("session_before_compact", (event, ctx) => {
|
|
3095
|
+
if (!ownsSession(ctx)) return;
|
|
3096
|
+
if (event.reason === "manual") {
|
|
3097
|
+
cyclePhase = "maintenance";
|
|
3098
|
+
if (ready) widget.maintenance(ctx);
|
|
3099
|
+
} else {
|
|
3100
|
+
if (cyclePhase !== "running") cyclePhase = "auto-preparing";
|
|
3101
|
+
if (ready) widget.automaticCompaction(ctx);
|
|
3102
|
+
}
|
|
3103
|
+
});
|
|
3104
|
+
pi.on("session_before_tree", (_event, ctx) => {
|
|
3105
|
+
if (!ownsSession(ctx)) return;
|
|
3106
|
+
cyclePhase = "maintenance";
|
|
3107
|
+
if (ready) widget.maintenance(ctx);
|
|
3108
|
+
});
|
|
3109
|
+
pi.on("input", (_event, ctx) => {
|
|
3110
|
+
if (!ownsSession(ctx) || !ctx.isIdle()) return;
|
|
3111
|
+
cyclePhase = "idle";
|
|
3112
|
+
if (ready) widget.clearCycle(ctx);
|
|
3113
|
+
});
|
|
3114
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
3115
|
+
if (!ownsSession(ctx)) return;
|
|
3116
|
+
cyclePhase = "running";
|
|
3117
|
+
if (ready) widget.agentStarted(ctx);
|
|
3118
|
+
});
|
|
3119
|
+
pi.on("message_start", (event, ctx) => {
|
|
3120
|
+
if (ready && ownsSession(ctx) && event.message.role === "user") widget.userMessage(ctx);
|
|
3121
|
+
});
|
|
3122
|
+
pi.on("turn_end", (event, ctx) => {
|
|
3123
|
+
if (ready && ownsSession(ctx) && event.message.role === "assistant")
|
|
3124
|
+
widget.turnEnded(ctx, event.message.stopReason);
|
|
3125
|
+
});
|
|
3126
|
+
pi.on("agent_settled", (event, ctx) => {
|
|
3127
|
+
if (!ownsSession(ctx)) return;
|
|
3128
|
+
cyclePhase = "idle";
|
|
3129
|
+
if (ready) widget.settled(ctx, event.aborted);
|
|
3130
|
+
});
|
|
3131
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
2890
3132
|
if (!ownsSession(ctx)) return;
|
|
3133
|
+
cyclePhase = "maintenance";
|
|
3134
|
+
if (!ready) return;
|
|
2891
3135
|
sessionGeneration++;
|
|
3136
|
+
ready = false;
|
|
3137
|
+
menuOpen = false;
|
|
2892
3138
|
owner.abort();
|
|
3139
|
+
owner = new AbortController();
|
|
3140
|
+
widget.start(ctx, settings, cyclePhase);
|
|
3141
|
+
ready = true;
|
|
3142
|
+
});
|
|
3143
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
3144
|
+
if (!ownsSession(ctx)) return;
|
|
3145
|
+
sessionGeneration++;
|
|
2893
3146
|
ready = false;
|
|
2894
3147
|
menuOpen = false;
|
|
2895
3148
|
activeContext = void 0;
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
const
|
|
2899
|
-
|
|
3149
|
+
cyclePhase = "idle";
|
|
3150
|
+
owner.abort();
|
|
3151
|
+
const errors = [];
|
|
3152
|
+
try {
|
|
3153
|
+
widget.shutdown(ctx);
|
|
3154
|
+
} catch (error) {
|
|
3155
|
+
errors.push(error);
|
|
3156
|
+
}
|
|
3157
|
+
await joinCleanup(
|
|
3158
|
+
[() => tools.shutdown(), ...[...menuTasks].map((task) => () => task), () => store.flush()],
|
|
3159
|
+
errors
|
|
3160
|
+
);
|
|
2900
3161
|
});
|
|
2901
3162
|
}
|
|
2902
3163
|
export {
|