@narumitw/pi-subagents 0.30.1 → 0.33.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/README.md +31 -15
- package/package.json +7 -7
- package/src/agents.ts +20 -15
- package/src/config-ui.ts +368 -66
- package/src/context.ts +13 -16
- package/src/execution.ts +360 -337
- package/src/limits.ts +8 -2
- package/src/params.ts +26 -9
- package/src/render.ts +2 -4
- package/src/settings.ts +84 -2
- package/src/stateful-prompt.ts +2 -5
- package/src/stateful.ts +25 -47
- package/src/subagents.ts +30 -12
- package/src/workspace.ts +7 -6
package/src/config-ui.ts
CHANGED
|
@@ -16,23 +16,27 @@ import {
|
|
|
16
16
|
import { type CompletionDelivery, discoverAgents } from "./agents.js";
|
|
17
17
|
import type { ManagedAgent } from "./registry.js";
|
|
18
18
|
import {
|
|
19
|
+
type DelegationWorkflow,
|
|
19
20
|
hasOwn,
|
|
20
21
|
inspectCompletionDeliverySettings,
|
|
22
|
+
inspectDelegationWorkflowSettings,
|
|
21
23
|
readSubagentSettings,
|
|
22
24
|
sameToolSet,
|
|
23
25
|
uniqueToolNames,
|
|
24
26
|
updateAgentToolsSetting,
|
|
25
27
|
updateCompletionDeliverySetting,
|
|
28
|
+
updateDelegationWorkflowSetting,
|
|
26
29
|
} from "./settings.js";
|
|
27
30
|
import { formatStatefulAgentLine, type StatefulSubagentRuntimeStatus } from "./stateful.js";
|
|
28
31
|
|
|
29
32
|
const SUBCOMMANDS: AutocompleteItem[] = [
|
|
30
|
-
{ value: "settings", label: "settings", description: "Configure completion
|
|
33
|
+
{ value: "settings", label: "settings", description: "Configure completion behavior" },
|
|
31
34
|
{ value: "status", label: "status", description: "Show effective subagent settings" },
|
|
32
35
|
{ value: "help", label: "help", description: "Show subagent settings help" },
|
|
33
36
|
];
|
|
34
37
|
|
|
35
38
|
export interface SubagentSettingsRuntime {
|
|
39
|
+
getBlockingEnabled(): boolean;
|
|
36
40
|
getCompletionDelivery(): CompletionDelivery;
|
|
37
41
|
setCompletionDelivery(value: CompletionDelivery): void;
|
|
38
42
|
getRuntimeStatus(): StatefulSubagentRuntimeStatus;
|
|
@@ -102,17 +106,11 @@ export class ToolToggleList {
|
|
|
102
106
|
|
|
103
107
|
export function registerSubagentConfigCommand(pi: ExtensionAPI, runtime: SubagentSettingsRuntime) {
|
|
104
108
|
registerSubagentPrimaryCommand(pi, runtime);
|
|
105
|
-
pi.registerCommand("subagents:config", {
|
|
106
|
-
description: "Configure user tool settings for each subagent",
|
|
107
|
-
handler: async (_args, ctx) => {
|
|
108
|
-
await showSubagentToolSettings(pi, ctx);
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
109
|
}
|
|
112
110
|
|
|
113
111
|
async function showSubagentToolSettings(pi: ExtensionAPI, ctx: ExtensionCommandContext) {
|
|
114
112
|
if (ctx.mode !== "tui") {
|
|
115
|
-
if (ctx.hasUI) ctx.ui.notify("
|
|
113
|
+
if (ctx.hasUI) ctx.ui.notify("Agent tool settings require TUI mode", "info");
|
|
116
114
|
return;
|
|
117
115
|
}
|
|
118
116
|
|
|
@@ -292,7 +290,8 @@ async function showSubagentToolSettings(pi: ExtensionAPI, ctx: ExtensionCommandC
|
|
|
292
290
|
}
|
|
293
291
|
}
|
|
294
292
|
|
|
295
|
-
type ManagerAction = "
|
|
293
|
+
type ManagerAction = "workflow" | "agents" | "completion" | "advanced" | "help";
|
|
294
|
+
type AdvancedAction = "agent-tools" | "status" | "back";
|
|
296
295
|
type AgentManagerAction = "back" | "clear";
|
|
297
296
|
|
|
298
297
|
function registerSubagentPrimaryCommand(pi: ExtensionAPI, runtime: SubagentSettingsRuntime) {
|
|
@@ -317,7 +316,7 @@ function registerSubagentPrimaryCommand(pi: ExtensionAPI, runtime: SubagentSetti
|
|
|
317
316
|
showSubagentStatus(ctx, runtime);
|
|
318
317
|
return;
|
|
319
318
|
case "help":
|
|
320
|
-
showSubagentHelp(ctx
|
|
319
|
+
showSubagentHelp(ctx);
|
|
321
320
|
return;
|
|
322
321
|
default:
|
|
323
322
|
if (ctx.mode === "tui" || ctx.hasUI) {
|
|
@@ -341,20 +340,20 @@ async function showSubagentManager(
|
|
|
341
340
|
const action = await selectManagerAction(ctx, runtime);
|
|
342
341
|
if (!action) return;
|
|
343
342
|
switch (action) {
|
|
344
|
-
case "
|
|
345
|
-
await
|
|
346
|
-
break;
|
|
347
|
-
case "agent-tools":
|
|
348
|
-
await showSubagentToolSettings(pi, ctx);
|
|
343
|
+
case "workflow":
|
|
344
|
+
if (await showDelegationWorkflow(ctx, runtime)) return;
|
|
349
345
|
break;
|
|
350
346
|
case "agents":
|
|
351
347
|
await showCurrentSessionAgents(ctx, runtime);
|
|
352
348
|
break;
|
|
353
|
-
case "
|
|
354
|
-
|
|
349
|
+
case "completion":
|
|
350
|
+
await showSubagentSettings(ctx, runtime);
|
|
351
|
+
break;
|
|
352
|
+
case "advanced":
|
|
353
|
+
await showAdvancedSettings(pi, ctx, runtime);
|
|
355
354
|
break;
|
|
356
355
|
case "help":
|
|
357
|
-
showSubagentHelp(ctx
|
|
356
|
+
showSubagentHelp(ctx);
|
|
358
357
|
break;
|
|
359
358
|
}
|
|
360
359
|
}
|
|
@@ -365,24 +364,28 @@ async function selectManagerAction(
|
|
|
365
364
|
runtime: SubagentSettingsRuntime,
|
|
366
365
|
): Promise<ManagerAction | null> {
|
|
367
366
|
const status = runtime.getRuntimeStatus();
|
|
368
|
-
const
|
|
367
|
+
const workflow = inspectDelegationWorkflowSettings();
|
|
369
368
|
const items: SelectItem[] = [
|
|
370
369
|
{
|
|
371
|
-
value: "
|
|
372
|
-
label: "
|
|
373
|
-
description: "
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
value: "agent-tools",
|
|
377
|
-
label: "Agent tool settings",
|
|
378
|
-
description: "Configure persistent per-agent tool allow-lists",
|
|
370
|
+
value: "workflow",
|
|
371
|
+
label: "Change delegation",
|
|
372
|
+
description: "Choose all methods, async only, or blocking only",
|
|
379
373
|
},
|
|
380
374
|
{
|
|
381
375
|
value: "agents",
|
|
382
|
-
label: "Current
|
|
376
|
+
label: "Current agents",
|
|
383
377
|
description: `${status.activeAgents} active · ${status.retainedAgents} retained`,
|
|
384
378
|
},
|
|
385
|
-
{
|
|
379
|
+
{
|
|
380
|
+
value: "completion",
|
|
381
|
+
label: "Completion behavior",
|
|
382
|
+
description: "Choose whether async completion waits or resumes automatically",
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
value: "advanced",
|
|
386
|
+
label: "Advanced settings",
|
|
387
|
+
description: "Agent permissions, runtime details, and settings path",
|
|
388
|
+
},
|
|
386
389
|
{ value: "help", label: "Help", description: "Show commands and manual configuration" },
|
|
387
390
|
];
|
|
388
391
|
return ctx.ui.custom<ManagerAction | null>((tui, theme, _keybindings, done) => {
|
|
@@ -390,7 +393,9 @@ async function selectManagerAction(
|
|
|
390
393
|
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
391
394
|
container.addChild(new Text(theme.fg("accent", theme.bold("Subagents")), 1, 0));
|
|
392
395
|
container.addChild(new Spacer(1));
|
|
393
|
-
container.addChild(
|
|
396
|
+
container.addChild(
|
|
397
|
+
new Text(theme.fg("muted", formatManagerSummary(runtime, status, workflow)), 1, 0),
|
|
398
|
+
);
|
|
394
399
|
container.addChild(new Spacer(1));
|
|
395
400
|
const selectList = new SelectList(items, Math.min(items.length + 2, 15), {
|
|
396
401
|
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
@@ -415,6 +420,257 @@ async function selectManagerAction(
|
|
|
415
420
|
});
|
|
416
421
|
}
|
|
417
422
|
|
|
423
|
+
async function showDelegationWorkflow(
|
|
424
|
+
ctx: ExtensionCommandContext,
|
|
425
|
+
runtime: SubagentSettingsRuntime,
|
|
426
|
+
): Promise<boolean> {
|
|
427
|
+
const snapshot = inspectDelegationWorkflowSettings();
|
|
428
|
+
if (ctx.mode !== "tui") {
|
|
429
|
+
if (ctx.hasUI) {
|
|
430
|
+
ctx.ui.notify(
|
|
431
|
+
`Edit delegation settings manually: ${safeTerminalText(snapshot.path)}`,
|
|
432
|
+
"info",
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
if (snapshot.error) {
|
|
438
|
+
ctx.ui.notify(
|
|
439
|
+
`Delegation settings cannot be edited: ${safeTerminalText(snapshot.error)}. Repair ${safeTerminalText(snapshot.path)} and retry.`,
|
|
440
|
+
"error",
|
|
441
|
+
);
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
const activeWorkflow = currentWorkflow(runtime, runtime.getRuntimeStatus());
|
|
445
|
+
const choices: SelectItem[] = [
|
|
446
|
+
{
|
|
447
|
+
value: "all",
|
|
448
|
+
label: "All delegation methods",
|
|
449
|
+
description: "Allow blocking batches and reusable async agents",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
value: "async-only",
|
|
453
|
+
label: "Async only",
|
|
454
|
+
description: "Keep the root responsive; remove blocking subagent",
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
value: "blocking-only",
|
|
458
|
+
label: "Blocking only",
|
|
459
|
+
description: "Keep blocking batches; remove reusable async agents",
|
|
460
|
+
},
|
|
461
|
+
];
|
|
462
|
+
const selected = await ctx.ui.custom<DelegationWorkflow | null>(
|
|
463
|
+
(tui, theme, _keybindings, done) => {
|
|
464
|
+
const container = new Container();
|
|
465
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
466
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Change Delegation")), 1, 0));
|
|
467
|
+
container.addChild(
|
|
468
|
+
new Text(
|
|
469
|
+
theme.fg(
|
|
470
|
+
"muted",
|
|
471
|
+
[
|
|
472
|
+
`Current: ${workflowLabel(activeWorkflow)}`,
|
|
473
|
+
...(snapshot.value !== activeWorkflow
|
|
474
|
+
? [`Configured after reload: ${workflowLabel(snapshot.value)}`]
|
|
475
|
+
: []),
|
|
476
|
+
].join("\n"),
|
|
477
|
+
),
|
|
478
|
+
1,
|
|
479
|
+
0,
|
|
480
|
+
),
|
|
481
|
+
);
|
|
482
|
+
container.addChild(new Spacer(1));
|
|
483
|
+
const selectList = new SelectList(choices, Math.min(choices.length + 2, 10), {
|
|
484
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
485
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
486
|
+
description: (text: string) => theme.fg("muted", text),
|
|
487
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
488
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
489
|
+
});
|
|
490
|
+
selectList.setSelectedIndex(
|
|
491
|
+
Math.max(
|
|
492
|
+
0,
|
|
493
|
+
choices.findIndex((item) => item.value === snapshot.value),
|
|
494
|
+
),
|
|
495
|
+
);
|
|
496
|
+
selectList.onSelect = (item) => done(item.value as DelegationWorkflow);
|
|
497
|
+
selectList.onCancel = () => done(null);
|
|
498
|
+
container.addChild(selectList);
|
|
499
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate · enter preview · esc back"), 1, 0));
|
|
500
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
501
|
+
return {
|
|
502
|
+
render: (width: number) => container.render(width),
|
|
503
|
+
invalidate: () => container.invalidate(),
|
|
504
|
+
handleInput(data: string) {
|
|
505
|
+
selectList.handleInput(data);
|
|
506
|
+
tui.requestRender();
|
|
507
|
+
},
|
|
508
|
+
};
|
|
509
|
+
},
|
|
510
|
+
);
|
|
511
|
+
if (!selected) return false;
|
|
512
|
+
if (selected === activeWorkflow && selected === snapshot.value) {
|
|
513
|
+
ctx.ui.notify(`Delegation already uses ${workflowLabel(selected)}.`, "info");
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
const requiresReload = selected !== activeWorkflow;
|
|
517
|
+
if (requiresReload && blockReloadWithRetainedAgents(ctx, runtime)) return false;
|
|
518
|
+
|
|
519
|
+
const confirmed = await showWorkflowPreview(ctx, activeWorkflow, selected, requiresReload);
|
|
520
|
+
if (!confirmed) return false;
|
|
521
|
+
if (requiresReload && blockReloadWithRetainedAgents(ctx, runtime)) return false;
|
|
522
|
+
try {
|
|
523
|
+
updateDelegationWorkflowSetting(selected as Exclude<DelegationWorkflow, "disabled">);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
ctx.ui.notify(
|
|
526
|
+
`Delegation settings were not saved: ${formatError(error)}. The current workflow is unchanged.`,
|
|
527
|
+
"error",
|
|
528
|
+
);
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
if (!requiresReload) {
|
|
532
|
+
ctx.ui.notify(
|
|
533
|
+
`Saved ${workflowLabel(selected)}. The current tool surface already matches.`,
|
|
534
|
+
"info",
|
|
535
|
+
);
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
ctx.ui.notify(
|
|
539
|
+
`Saved ${workflowLabel(selected)}. Reloading subagent tools… If the tool surface does not refresh, run /reload.`,
|
|
540
|
+
"info",
|
|
541
|
+
);
|
|
542
|
+
await ctx.reload();
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function blockReloadWithRetainedAgents(
|
|
547
|
+
ctx: ExtensionCommandContext,
|
|
548
|
+
runtime: SubagentSettingsRuntime,
|
|
549
|
+
): boolean {
|
|
550
|
+
const status = runtime.getRuntimeStatus();
|
|
551
|
+
if (status.retainedAgents === 0) return false;
|
|
552
|
+
ctx.ui.notify(
|
|
553
|
+
`Cannot reload while ${status.retainedAgents} detached subagent${status.retainedAgents === 1 ? " is" : "s are"} retained (${status.activeAgents} active). Open Current agents and clear them after their work is safe to discard, then change delegation.`,
|
|
554
|
+
"warning",
|
|
555
|
+
);
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function showWorkflowPreview(
|
|
560
|
+
ctx: ExtensionCommandContext,
|
|
561
|
+
current: DelegationWorkflow,
|
|
562
|
+
next: DelegationWorkflow,
|
|
563
|
+
requiresReload: boolean,
|
|
564
|
+
): Promise<boolean> {
|
|
565
|
+
const workflowChanges = workflowEffects(current, next);
|
|
566
|
+
const effects = (
|
|
567
|
+
workflowChanges.length > 0
|
|
568
|
+
? workflowChanges
|
|
569
|
+
: ["Keep the current registered tools and cancel the pending workflow change"]
|
|
570
|
+
)
|
|
571
|
+
.map((effect) => `- ${effect}`)
|
|
572
|
+
.join("\n");
|
|
573
|
+
return ctx.ui.custom<boolean>((tui, theme, _keybindings, done) => {
|
|
574
|
+
const container = new Container();
|
|
575
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
576
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Review Delegation Change")), 1, 0));
|
|
577
|
+
container.addChild(
|
|
578
|
+
new Text(
|
|
579
|
+
theme.fg(
|
|
580
|
+
"muted",
|
|
581
|
+
`Current: ${workflowLabel(current)}\nNew: ${workflowLabel(next)}\n\nEffect:\n${effects}\n- ${requiresReload ? "Reload the extension to apply this tool surface" : "No reload is needed because the active tools already match"}`,
|
|
582
|
+
),
|
|
583
|
+
1,
|
|
584
|
+
0,
|
|
585
|
+
),
|
|
586
|
+
);
|
|
587
|
+
container.addChild(new Spacer(1));
|
|
588
|
+
const actions: SelectItem[] = [
|
|
589
|
+
{
|
|
590
|
+
value: "save",
|
|
591
|
+
label: requiresReload ? "Save and reload" : "Save",
|
|
592
|
+
description: requiresReload
|
|
593
|
+
? "Persist and apply this workflow"
|
|
594
|
+
: "Persist the workflow that is already active",
|
|
595
|
+
},
|
|
596
|
+
{ value: "cancel", label: "Cancel", description: "Leave settings and tools unchanged" },
|
|
597
|
+
];
|
|
598
|
+
const selectList = new SelectList(actions, 4, {
|
|
599
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
600
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
601
|
+
description: (text: string) => theme.fg("muted", text),
|
|
602
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
603
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
604
|
+
});
|
|
605
|
+
selectList.onSelect = (item) => done(item.value === "save");
|
|
606
|
+
selectList.onCancel = () => done(false);
|
|
607
|
+
container.addChild(selectList);
|
|
608
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate · enter choose · esc cancel"), 1, 0));
|
|
609
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
610
|
+
return {
|
|
611
|
+
render: (width: number) => container.render(width),
|
|
612
|
+
invalidate: () => container.invalidate(),
|
|
613
|
+
handleInput(data: string) {
|
|
614
|
+
selectList.handleInput(data);
|
|
615
|
+
tui.requestRender();
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function showAdvancedSettings(
|
|
622
|
+
pi: ExtensionAPI,
|
|
623
|
+
ctx: ExtensionCommandContext,
|
|
624
|
+
runtime: SubagentSettingsRuntime,
|
|
625
|
+
) {
|
|
626
|
+
while (true) {
|
|
627
|
+
const action = await ctx.ui.custom<AdvancedAction | null>((tui, theme, _keybindings, done) => {
|
|
628
|
+
const container = new Container();
|
|
629
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
630
|
+
container.addChild(
|
|
631
|
+
new Text(theme.fg("accent", theme.bold("Advanced Subagent Settings")), 1, 0),
|
|
632
|
+
);
|
|
633
|
+
container.addChild(new Spacer(1));
|
|
634
|
+
const items: SelectItem[] = [
|
|
635
|
+
{
|
|
636
|
+
value: "agent-tools",
|
|
637
|
+
label: "Agent tool permissions",
|
|
638
|
+
description: "Customize persistent per-agent tool allow-lists",
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
value: "status",
|
|
642
|
+
label: "Runtime details",
|
|
643
|
+
description: "Show transport, configured source, and settings path",
|
|
644
|
+
},
|
|
645
|
+
{ value: "back", label: "Back", description: "Return to the Subagents manager" },
|
|
646
|
+
];
|
|
647
|
+
const selectList = new SelectList(items, 5, {
|
|
648
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
649
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
650
|
+
description: (text: string) => theme.fg("muted", text),
|
|
651
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
652
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
653
|
+
});
|
|
654
|
+
selectList.onSelect = (item) => done(item.value as AdvancedAction);
|
|
655
|
+
selectList.onCancel = () => done(null);
|
|
656
|
+
container.addChild(selectList);
|
|
657
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate · enter select · esc back"), 1, 0));
|
|
658
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
659
|
+
return {
|
|
660
|
+
render: (width: number) => container.render(width),
|
|
661
|
+
invalidate: () => container.invalidate(),
|
|
662
|
+
handleInput(data: string) {
|
|
663
|
+
selectList.handleInput(data);
|
|
664
|
+
tui.requestRender();
|
|
665
|
+
},
|
|
666
|
+
};
|
|
667
|
+
});
|
|
668
|
+
if (!action || action === "back") return;
|
|
669
|
+
if (action === "agent-tools") await showSubagentToolSettings(pi, ctx);
|
|
670
|
+
else showSubagentStatus(ctx, runtime);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
418
674
|
async function showCurrentSessionAgents(
|
|
419
675
|
ctx: ExtensionCommandContext,
|
|
420
676
|
runtime: SubagentSettingsRuntime,
|
|
@@ -516,11 +772,11 @@ async function showSubagentSettings(
|
|
|
516
772
|
const items: SettingItem[] = [
|
|
517
773
|
{
|
|
518
774
|
id: "completionDelivery",
|
|
519
|
-
label: "
|
|
775
|
+
label: "When async work finishes",
|
|
520
776
|
description:
|
|
521
|
-
"
|
|
522
|
-
currentValue,
|
|
523
|
-
values: ["next
|
|
777
|
+
"Wait for your next turn, or request one synthesis turn after the root settles.",
|
|
778
|
+
currentValue: completionLabel(currentValue),
|
|
779
|
+
values: ["Wait until my next turn", "Resume automatically when finished"],
|
|
524
780
|
},
|
|
525
781
|
];
|
|
526
782
|
const container = new Container();
|
|
@@ -541,17 +797,15 @@ async function showSubagentSettings(
|
|
|
541
797
|
(id, newValue) => {
|
|
542
798
|
if (id !== "completionDelivery") return;
|
|
543
799
|
const previous = currentValue;
|
|
544
|
-
const next =
|
|
800
|
+
const next: CompletionDelivery =
|
|
801
|
+
newValue === "Resume automatically when finished" ? "auto-resume" : "next-turn";
|
|
545
802
|
try {
|
|
546
803
|
updateCompletionDeliverySetting(next);
|
|
547
804
|
runtime.setCompletionDelivery(next);
|
|
548
805
|
currentValue = next;
|
|
549
|
-
ctx.ui.notify(
|
|
550
|
-
`User completion delivery set to ${next} for this and future sessions.`,
|
|
551
|
-
"info",
|
|
552
|
-
);
|
|
806
|
+
ctx.ui.notify(`Saved and applied: ${completionLabel(next)}.`, "info");
|
|
553
807
|
} catch (error) {
|
|
554
|
-
settingsList.updateValue(id, previous);
|
|
808
|
+
settingsList.updateValue(id, completionLabel(previous));
|
|
555
809
|
ctx.ui.notify(`Subagent settings were not saved: ${formatError(error)}`, "error");
|
|
556
810
|
}
|
|
557
811
|
tui.requestRender();
|
|
@@ -574,25 +828,20 @@ function showSubagentStatus(ctx: ExtensionCommandContext, runtime: SubagentSetti
|
|
|
574
828
|
if (ctx.mode !== "tui" && !ctx.hasUI) return;
|
|
575
829
|
const snapshot = inspectCompletionDeliverySettings();
|
|
576
830
|
ctx.ui.notify(
|
|
577
|
-
formatStatus(runtime.getRuntimeStatus(), snapshot),
|
|
831
|
+
formatStatus(runtime.getRuntimeStatus(), snapshot, runtime),
|
|
578
832
|
snapshot.error ? "warning" : "info",
|
|
579
833
|
);
|
|
580
834
|
}
|
|
581
835
|
|
|
582
|
-
function showSubagentHelp(ctx: ExtensionCommandContext
|
|
836
|
+
function showSubagentHelp(ctx: ExtensionCommandContext) {
|
|
583
837
|
if (ctx.mode !== "tui" && !ctx.hasUI) return;
|
|
584
838
|
const snapshot = inspectCompletionDeliverySettings();
|
|
585
|
-
const runtimeStatus = runtime.getRuntimeStatus();
|
|
586
839
|
ctx.ui.notify(
|
|
587
840
|
[
|
|
588
|
-
"/subagents —
|
|
589
|
-
"/subagents settings — configure
|
|
841
|
+
"/subagents — choose delegation workflow, manage current agents, and configure agent tools",
|
|
842
|
+
"/subagents settings — configure async completion behavior",
|
|
590
843
|
"/subagents status — show current-session and user-setting values",
|
|
591
844
|
"/subagents help — show this help",
|
|
592
|
-
"/subagents:config — compatibility route for per-agent user tool settings",
|
|
593
|
-
runtimeStatus.enabled
|
|
594
|
-
? "/subagents:agents list|clear — compatibility route for current-session agents"
|
|
595
|
-
: "/subagents:agents — unavailable while stateful lifecycle tools are disabled",
|
|
596
845
|
`User settings: ${safeTerminalText(snapshot.path)}`,
|
|
597
846
|
].join("\n"),
|
|
598
847
|
"info",
|
|
@@ -600,41 +849,48 @@ function showSubagentHelp(ctx: ExtensionCommandContext, runtime: SubagentSetting
|
|
|
600
849
|
}
|
|
601
850
|
|
|
602
851
|
function formatManagerSummary(
|
|
852
|
+
runtime: SubagentSettingsRuntime,
|
|
603
853
|
status: StatefulSubagentRuntimeStatus,
|
|
604
|
-
|
|
854
|
+
configured: ReturnType<typeof inspectDelegationWorkflowSettings>,
|
|
605
855
|
): string {
|
|
856
|
+
const current = currentWorkflow(runtime, status);
|
|
606
857
|
return [
|
|
607
|
-
|
|
608
|
-
`
|
|
609
|
-
`Transport: ${status.transport}`,
|
|
610
|
-
`Completion delivery: ${status.completionDelivery}`,
|
|
858
|
+
`Delegation: ${workflowLabel(current)}`,
|
|
859
|
+
`Completion: ${completionLabel(status.completionDelivery)}`,
|
|
611
860
|
`Agents: ${status.activeAgents} active · ${status.retainedAgents} retained`,
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
...(settings.error ? [`Warning: ${safeTerminalText(settings.error)}`] : []),
|
|
861
|
+
...(configured.value !== current
|
|
862
|
+
? [`Configured after reload: ${workflowLabel(configured.value)}`]
|
|
863
|
+
: []),
|
|
864
|
+
...(configured.error ? ["Settings need repair; open Advanced settings for details."] : []),
|
|
617
865
|
].join("\n");
|
|
618
866
|
}
|
|
619
867
|
|
|
620
868
|
function formatStatus(
|
|
621
869
|
status: StatefulSubagentRuntimeStatus,
|
|
622
870
|
snapshot: ReturnType<typeof inspectCompletionDeliverySettings>,
|
|
871
|
+
runtime?: SubagentSettingsRuntime,
|
|
623
872
|
): string {
|
|
873
|
+
const configuredWorkflow = inspectDelegationWorkflowSettings();
|
|
874
|
+
const current = runtime ? currentWorkflow(runtime, status) : configuredWorkflow.value;
|
|
624
875
|
return [
|
|
625
876
|
"Current session",
|
|
626
|
-
`
|
|
627
|
-
`
|
|
877
|
+
` Delegation: ${workflowLabel(current)}`,
|
|
878
|
+
` Async runtime: ${status.initialized ? "initialized" : status.enabled ? "not initialized" : "disabled"}`,
|
|
628
879
|
` Transport: ${status.transport}`,
|
|
629
|
-
` Completion
|
|
880
|
+
` Completion: ${completionLabel(status.completionDelivery)}`,
|
|
630
881
|
` Agents: ${status.activeAgents} active, ${status.retainedAgents} retained`,
|
|
631
882
|
"User settings",
|
|
883
|
+
` Delegation source: ${configuredWorkflow.source}`,
|
|
884
|
+
` Configured delegation: ${workflowLabel(configuredWorkflow.value)}`,
|
|
632
885
|
` Completion source: ${snapshot.source}`,
|
|
886
|
+
` Configured completion: ${completionLabel(snapshot.value)}`,
|
|
633
887
|
` Path: ${safeTerminalText(snapshot.path)}`,
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
888
|
+
configuredWorkflow.error || snapshot.error
|
|
889
|
+
? ` Warning: ${safeTerminalText(configuredWorkflow.error ?? snapshot.error ?? "invalid settings")}`
|
|
890
|
+
: " Warning: none",
|
|
891
|
+
configuredWorkflow.value !== current
|
|
892
|
+
? "Configured delegation differs from this session. Run /reload to apply it."
|
|
893
|
+
: "Manual file changes require /reload.",
|
|
638
894
|
].join("\n");
|
|
639
895
|
}
|
|
640
896
|
|
|
@@ -644,6 +900,52 @@ function formatEmptyRuntime(status: StatefulSubagentRuntimeStatus): string {
|
|
|
644
900
|
return "No current-session subagents.";
|
|
645
901
|
}
|
|
646
902
|
|
|
903
|
+
function currentWorkflow(
|
|
904
|
+
runtime: SubagentSettingsRuntime,
|
|
905
|
+
status: StatefulSubagentRuntimeStatus,
|
|
906
|
+
): DelegationWorkflow {
|
|
907
|
+
const blocking = runtime.getBlockingEnabled();
|
|
908
|
+
if (blocking && status.enabled) return "all";
|
|
909
|
+
if (status.enabled) return "async-only";
|
|
910
|
+
if (blocking) return "blocking-only";
|
|
911
|
+
return "disabled";
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
function workflowLabel(value: DelegationWorkflow): string {
|
|
915
|
+
switch (value) {
|
|
916
|
+
case "all":
|
|
917
|
+
return "All delegation methods";
|
|
918
|
+
case "async-only":
|
|
919
|
+
return "Async only";
|
|
920
|
+
case "blocking-only":
|
|
921
|
+
return "Blocking only";
|
|
922
|
+
case "disabled":
|
|
923
|
+
return "Delegation disabled";
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function completionLabel(value: CompletionDelivery): string {
|
|
928
|
+
return value === "auto-resume" ? "Resume automatically when finished" : "Wait until my next turn";
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function workflowEffects(current: DelegationWorkflow, next: DelegationWorkflow): string[] {
|
|
932
|
+
const blockingEnabled = (value: DelegationWorkflow) =>
|
|
933
|
+
value === "all" || value === "blocking-only";
|
|
934
|
+
const asyncEnabled = (value: DelegationWorkflow) => value === "all" || value === "async-only";
|
|
935
|
+
const effects: string[] = [];
|
|
936
|
+
if (blockingEnabled(current) !== blockingEnabled(next)) {
|
|
937
|
+
effects.push(blockingEnabled(next) ? "Add blocking `subagent`" : "Remove blocking `subagent`");
|
|
938
|
+
}
|
|
939
|
+
if (asyncEnabled(current) !== asyncEnabled(next)) {
|
|
940
|
+
effects.push(
|
|
941
|
+
asyncEnabled(next)
|
|
942
|
+
? "Add reusable async lifecycle tools"
|
|
943
|
+
: "Remove reusable async lifecycle tools",
|
|
944
|
+
);
|
|
945
|
+
}
|
|
946
|
+
return effects;
|
|
947
|
+
}
|
|
948
|
+
|
|
647
949
|
function safeTerminalText(value: string): string {
|
|
648
950
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
|
|
649
951
|
return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
|
package/src/context.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_MAX_CONTEXT_BYTES,
|
|
4
|
-
truncateUtf8,
|
|
5
|
-
truncateUtf8Tail,
|
|
6
|
-
} from "./limits.js";
|
|
2
|
+
import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8, truncateUtf8Tail } from "./limits.js";
|
|
7
3
|
|
|
8
4
|
export type ContextMode = "none" | "all" | "summary" | number;
|
|
9
5
|
|
|
@@ -107,19 +103,20 @@ export function buildContextSnapshot(
|
|
|
107
103
|
const raw =
|
|
108
104
|
mode === "summary" && selected.length > 4
|
|
109
105
|
? [
|
|
110
|
-
`## Earlier context checkpoint\n${
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
106
|
+
`## Earlier context checkpoint\n${
|
|
107
|
+
truncateUtf8(
|
|
108
|
+
selected
|
|
109
|
+
.slice(0, -4)
|
|
110
|
+
.map((message) => `${message.role}: ${message.text}`)
|
|
111
|
+
.join("\n"),
|
|
112
|
+
Math.floor(maxBytes / 3),
|
|
113
|
+
).text
|
|
114
|
+
}`,
|
|
115
|
+
...selected.slice(-4).map((message) => `## ${message.role}\n${message.text}`),
|
|
120
116
|
].join("\n\n")
|
|
121
117
|
: selected.map((message) => `## ${message.role}\n${message.text}`).join("\n\n");
|
|
122
|
-
const bounded =
|
|
118
|
+
const bounded =
|
|
119
|
+
mode === "summary" ? truncateUtf8Tail(raw, maxBytes) : truncateUtf8(raw, maxBytes);
|
|
123
120
|
return {
|
|
124
121
|
text: bounded.text,
|
|
125
122
|
turns: selected.filter((message) => message.role === "user").length,
|