@narumitw/pi-subagents 0.42.0 → 0.43.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 +142 -32
- package/package.json +1 -1
- package/src/agents.ts +49 -8
- package/src/config-ui.ts +223 -28
- package/src/consult-policy.ts +15 -0
- package/src/consult-render.ts +194 -0
- package/src/consult.ts +815 -0
- package/src/cwd-policy.ts +183 -0
- package/src/execution.ts +135 -66
- package/src/in-process-transport.ts +3 -3
- package/src/inspect-render.ts +234 -0
- package/src/inspect.ts +453 -0
- package/src/limits.ts +1 -0
- package/src/params.ts +2 -0
- package/src/persistence.ts +29 -0
- package/src/registry.ts +87 -0
- package/src/render-common.ts +252 -0
- package/src/render.ts +134 -99
- package/src/runner.ts +162 -22
- package/src/safe-text.ts +67 -0
- package/src/settings.ts +199 -12
- package/src/stateful-guidance.ts +35 -0
- package/src/stateful-lifecycle.ts +31 -0
- package/src/stateful-render.ts +249 -0
- package/src/stateful-safety.ts +91 -0
- package/src/stateful.ts +254 -225
- package/src/subagents.ts +100 -19
- package/src/subprocess-transport.ts +19 -2
package/src/config-ui.ts
CHANGED
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type CompletionDelivery,
|
|
5
|
+
type ConsultationCwdPolicy,
|
|
6
|
+
type ConsultResourcePolicy,
|
|
7
|
+
type DelegationCwdPolicy,
|
|
8
|
+
discoverAgents,
|
|
9
|
+
} from "./agents.js";
|
|
4
10
|
import type { ManagedAgent } from "./registry.js";
|
|
5
11
|
import {
|
|
6
12
|
type DelegationWorkflow,
|
|
7
13
|
hasOwn,
|
|
8
14
|
inspectCompletionDeliverySettings,
|
|
15
|
+
inspectConsultResourceSettings,
|
|
16
|
+
inspectCwdPolicySettings,
|
|
9
17
|
inspectDelegationWorkflowSettings,
|
|
10
18
|
readSubagentSettings,
|
|
11
19
|
sameToolSet,
|
|
12
20
|
uniqueToolNames,
|
|
13
21
|
updateAgentToolsSetting,
|
|
14
22
|
updateCompletionDeliverySetting,
|
|
23
|
+
updateConsultResourceSetting,
|
|
24
|
+
updateCwdPolicySetting,
|
|
15
25
|
updateDelegationWorkflowSetting,
|
|
16
26
|
} from "./settings.js";
|
|
17
27
|
import { formatStatefulAgentLine, type StatefulSubagentRuntimeStatus } from "./stateful.js";
|
|
18
28
|
|
|
19
29
|
const SUBCOMMANDS = [
|
|
20
|
-
{ value: "settings", label: "settings", description: "Configure
|
|
30
|
+
{ value: "settings", label: "settings", description: "Configure subagent user settings" },
|
|
21
31
|
{ value: "status", label: "status", description: "Show effective subagent settings" },
|
|
22
32
|
{ value: "help", label: "help", description: "Show subagent settings help" },
|
|
23
33
|
];
|
|
@@ -26,7 +36,13 @@ const TOOL_VIEWPORT_SIZE = 10;
|
|
|
26
36
|
export interface SubagentSettingsRuntime {
|
|
27
37
|
getBlockingEnabled(): boolean;
|
|
28
38
|
getCompletionDelivery(): CompletionDelivery;
|
|
39
|
+
getConsultResourcePolicy(): ConsultResourcePolicy;
|
|
40
|
+
getConsultationCwdPolicy(): ConsultationCwdPolicy;
|
|
41
|
+
getDelegationCwdPolicy(): DelegationCwdPolicy;
|
|
29
42
|
setCompletionDelivery(value: CompletionDelivery): void;
|
|
43
|
+
setConsultResourcePolicy(value: ConsultResourcePolicy): void;
|
|
44
|
+
setConsultationCwdPolicy(value: ConsultationCwdPolicy): void;
|
|
45
|
+
setDelegationCwdPolicy(value: DelegationCwdPolicy): void;
|
|
30
46
|
getRuntimeStatus(): StatefulSubagentRuntimeStatus;
|
|
31
47
|
listAgents(includeClosed?: boolean): ManagedAgent[];
|
|
32
48
|
clearAgents(): Promise<number>;
|
|
@@ -86,7 +102,7 @@ function registerSubagentPrimaryCommand(
|
|
|
86
102
|
showSubagentStatus(ctx, runtime);
|
|
87
103
|
return;
|
|
88
104
|
case "help":
|
|
89
|
-
showSubagentHelp(ctx);
|
|
105
|
+
showSubagentHelp(ctx, runtime);
|
|
90
106
|
return;
|
|
91
107
|
default:
|
|
92
108
|
if (ctx.mode === "tui" || ctx.hasUI) {
|
|
@@ -114,7 +130,7 @@ async function showSubagentManager(
|
|
|
114
130
|
| "main"
|
|
115
131
|
| "workflow"
|
|
116
132
|
| "agents"
|
|
117
|
-
| "
|
|
133
|
+
| "settings"
|
|
118
134
|
| "advanced"
|
|
119
135
|
| "status"
|
|
120
136
|
| "help"
|
|
@@ -124,6 +140,9 @@ async function showSubagentManager(
|
|
|
124
140
|
| "set-workflow"
|
|
125
141
|
| "clear-agents"
|
|
126
142
|
| "set-completion"
|
|
143
|
+
| "set-consult-resources"
|
|
144
|
+
| "set-consultation-cwd"
|
|
145
|
+
| "set-delegation-cwd"
|
|
127
146
|
| "load-agent-picker"
|
|
128
147
|
| "pick-agent"
|
|
129
148
|
| "toggle-tool"
|
|
@@ -154,10 +173,10 @@ async function showSubagentManager(
|
|
|
154
173
|
to: "agents",
|
|
155
174
|
},
|
|
156
175
|
{
|
|
157
|
-
id: "
|
|
158
|
-
label: "
|
|
159
|
-
description: "
|
|
160
|
-
to: "
|
|
176
|
+
id: "settings",
|
|
177
|
+
label: "Settings",
|
|
178
|
+
description: "Configure targets, trusted resources, and async completion",
|
|
179
|
+
to: "settings",
|
|
161
180
|
},
|
|
162
181
|
{
|
|
163
182
|
id: "advanced",
|
|
@@ -236,7 +255,7 @@ async function showSubagentManager(
|
|
|
236
255
|
hint: "back",
|
|
237
256
|
};
|
|
238
257
|
},
|
|
239
|
-
|
|
258
|
+
settings: () => subagentSettingsScreen(runtime),
|
|
240
259
|
advanced: () => ({
|
|
241
260
|
kind: "actions",
|
|
242
261
|
title: "Advanced Subagent Settings",
|
|
@@ -266,7 +285,7 @@ async function showSubagentManager(
|
|
|
266
285
|
help: () => ({
|
|
267
286
|
kind: "detail",
|
|
268
287
|
title: "Subagents help",
|
|
269
|
-
lines: helpLines(),
|
|
288
|
+
lines: helpLines(runtime),
|
|
270
289
|
hint: "back",
|
|
271
290
|
}),
|
|
272
291
|
"agent-picker": () => {
|
|
@@ -388,6 +407,10 @@ async function showSubagentManager(
|
|
|
388
407
|
return { kind: "stay" };
|
|
389
408
|
},
|
|
390
409
|
"set-completion": async ({ value }) => applyCompletionSetting(value, ctx, runtime),
|
|
410
|
+
"set-consult-resources": async ({ value }) =>
|
|
411
|
+
applyConsultResourceSetting(value, ctx, runtime),
|
|
412
|
+
"set-consultation-cwd": async ({ value }) => applyConsultationCwdSetting(value, ctx, runtime),
|
|
413
|
+
"set-delegation-cwd": async ({ value }) => applyDelegationCwdSetting(value, ctx, runtime),
|
|
391
414
|
"load-agent-picker": async () => {
|
|
392
415
|
availableAgents = discoverAgents(ctx.cwd, "user", readSubagentSettings() ?? {}).agents;
|
|
393
416
|
if (availableAgents.length === 0) {
|
|
@@ -468,7 +491,7 @@ async function showSubagentSettings(
|
|
|
468
491
|
runtime: SubagentSettingsRuntime,
|
|
469
492
|
owner: MenuOwner,
|
|
470
493
|
) {
|
|
471
|
-
const snapshot =
|
|
494
|
+
const snapshot = inspectConsultResourceSettings();
|
|
472
495
|
if (ctx.mode !== "tui") {
|
|
473
496
|
if (ctx.hasUI) {
|
|
474
497
|
ctx.ui.notify(
|
|
@@ -479,11 +502,20 @@ async function showSubagentSettings(
|
|
|
479
502
|
return;
|
|
480
503
|
}
|
|
481
504
|
const generation = owner.generation;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
505
|
+
type SettingsAction =
|
|
506
|
+
| "set-completion"
|
|
507
|
+
| "set-consult-resources"
|
|
508
|
+
| "set-consultation-cwd"
|
|
509
|
+
| "set-delegation-cwd";
|
|
510
|
+
const menu = defineMenu<undefined, "settings", SettingsAction, ExtensionCommandContext>({
|
|
511
|
+
start: "settings",
|
|
512
|
+
screens: { settings: () => subagentSettingsScreen(runtime) },
|
|
485
513
|
actions: {
|
|
486
514
|
"set-completion": async ({ value }) => applyCompletionSetting(value, ctx, runtime),
|
|
515
|
+
"set-consult-resources": async ({ value }) =>
|
|
516
|
+
applyConsultResourceSetting(value, ctx, runtime),
|
|
517
|
+
"set-consultation-cwd": async ({ value }) => applyConsultationCwdSetting(value, ctx, runtime),
|
|
518
|
+
"set-delegation-cwd": async ({ value }) => applyDelegationCwdSetting(value, ctx, runtime),
|
|
487
519
|
},
|
|
488
520
|
});
|
|
489
521
|
await runMenu(ctx, menu, {
|
|
@@ -493,25 +525,61 @@ async function showSubagentSettings(
|
|
|
493
525
|
});
|
|
494
526
|
}
|
|
495
527
|
|
|
496
|
-
function
|
|
497
|
-
const
|
|
528
|
+
function subagentSettingsScreen(runtime: SubagentSettingsRuntime) {
|
|
529
|
+
const completion = inspectCompletionDeliverySettings();
|
|
530
|
+
const consult = inspectConsultResourceSettings();
|
|
531
|
+
const cwdPolicy = inspectCwdPolicySettings();
|
|
532
|
+
const error = completion.error ?? consult.error ?? cwdPolicy.error;
|
|
498
533
|
return {
|
|
499
534
|
kind: "settings" as const,
|
|
500
|
-
title:
|
|
535
|
+
title: error ? "Subagent User Settings · Read only" : "Subagent User Settings",
|
|
501
536
|
lines: [
|
|
502
537
|
"Applies now and to future sessions",
|
|
503
|
-
|
|
504
|
-
|
|
538
|
+
"Target and trust settings control startup resources, not filesystem access or sandboxing.",
|
|
539
|
+
"Manage folder trust with Pi /trust; restart Pi after changing it.",
|
|
540
|
+
safeTerminalText(consult.path),
|
|
541
|
+
...(error ? [`Settings cannot be edited: ${safeTerminalText(error)}`] : []),
|
|
505
542
|
],
|
|
506
|
-
items:
|
|
543
|
+
items: error
|
|
507
544
|
? []
|
|
508
545
|
: [
|
|
546
|
+
{
|
|
547
|
+
id: "consultationCwd",
|
|
548
|
+
label: "Read-only consultation target",
|
|
549
|
+
description:
|
|
550
|
+
"Untrusted external targets inherit no target/project resources; agent and package read-only prompts remain.",
|
|
551
|
+
currentValue: consultationCwdLabel(runtime.getConsultationCwdPolicy()),
|
|
552
|
+
values: ["Anywhere · untrusted targets inherit nothing", "Current workspace only"],
|
|
553
|
+
action: "set-consultation-cwd" as const,
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
id: "delegationCwd",
|
|
557
|
+
label: "General delegation target",
|
|
558
|
+
description:
|
|
559
|
+
"Controls starting directories, not absolute paths, shell commands, or OS permissions.",
|
|
560
|
+
currentValue: delegationCwdLabel(runtime.getDelegationCwdPolicy()),
|
|
561
|
+
values: [
|
|
562
|
+
"Current or saved-trusted folders",
|
|
563
|
+
"Current workspace only",
|
|
564
|
+
"Anywhere · normal Pi permissions",
|
|
565
|
+
],
|
|
566
|
+
action: "set-delegation-cwd" as const,
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: "consultResources",
|
|
570
|
+
label: "Consultation resources for trusted targets",
|
|
571
|
+
description:
|
|
572
|
+
"Choose which trusted context, system, skill, and prompt resources a consultation inherits.",
|
|
573
|
+
currentValue: consultResourceLabel(runtime.getConsultResourcePolicy()),
|
|
574
|
+
values: ["Project context only", "No inherited resources", "All trusted resources"],
|
|
575
|
+
action: "set-consult-resources" as const,
|
|
576
|
+
},
|
|
509
577
|
{
|
|
510
578
|
id: "completionDelivery",
|
|
511
579
|
label: "When async work finishes",
|
|
512
580
|
description:
|
|
513
581
|
"Wait for your next turn, or request one synthesis turn after the root settles.",
|
|
514
|
-
currentValue: completionLabel(
|
|
582
|
+
currentValue: completionLabel(runtime.getCompletionDelivery()),
|
|
515
583
|
values: ["Wait until my next turn", "Resume automatically when finished"],
|
|
516
584
|
action: "set-completion" as const,
|
|
517
585
|
},
|
|
@@ -539,6 +607,74 @@ function applyCompletionSetting(
|
|
|
539
607
|
}
|
|
540
608
|
}
|
|
541
609
|
|
|
610
|
+
function applyConsultResourceSetting(
|
|
611
|
+
value: string | undefined,
|
|
612
|
+
ctx: ExtensionCommandContext,
|
|
613
|
+
runtime: SubagentSettingsRuntime,
|
|
614
|
+
) {
|
|
615
|
+
const previous = runtime.getConsultResourcePolicy();
|
|
616
|
+
const next: ConsultResourcePolicy =
|
|
617
|
+
value === "No inherited resources"
|
|
618
|
+
? "none"
|
|
619
|
+
: value === "All trusted resources"
|
|
620
|
+
? "all"
|
|
621
|
+
: "project-context";
|
|
622
|
+
if (next === previous) return { kind: "stay" as const };
|
|
623
|
+
try {
|
|
624
|
+
updateConsultResourceSetting(next);
|
|
625
|
+
runtime.setConsultResourcePolicy(next);
|
|
626
|
+
ctx.ui.notify(`Saved and applied: ${consultResourceLabel(next)}.`, "info");
|
|
627
|
+
return { kind: "stay" as const };
|
|
628
|
+
} catch (error) {
|
|
629
|
+
ctx.ui.notify(`Subagent settings were not saved: ${formatError(error)}`, "error");
|
|
630
|
+
return { kind: "rejected" as const };
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function applyConsultationCwdSetting(
|
|
635
|
+
value: string | undefined,
|
|
636
|
+
ctx: ExtensionCommandContext,
|
|
637
|
+
runtime: SubagentSettingsRuntime,
|
|
638
|
+
) {
|
|
639
|
+
const previous = runtime.getConsultationCwdPolicy();
|
|
640
|
+
const next: ConsultationCwdPolicy =
|
|
641
|
+
value === "Current workspace only" ? "current-workspace" : "anywhere";
|
|
642
|
+
if (next === previous) return { kind: "stay" as const };
|
|
643
|
+
try {
|
|
644
|
+
updateCwdPolicySetting("consultation", next);
|
|
645
|
+
runtime.setConsultationCwdPolicy(next);
|
|
646
|
+
ctx.ui.notify(`Saved and applied: ${consultationCwdLabel(next)}.`, "info");
|
|
647
|
+
return { kind: "stay" as const };
|
|
648
|
+
} catch (error) {
|
|
649
|
+
ctx.ui.notify(`Subagent settings were not saved: ${formatError(error)}`, "error");
|
|
650
|
+
return { kind: "rejected" as const };
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function applyDelegationCwdSetting(
|
|
655
|
+
value: string | undefined,
|
|
656
|
+
ctx: ExtensionCommandContext,
|
|
657
|
+
runtime: SubagentSettingsRuntime,
|
|
658
|
+
) {
|
|
659
|
+
const previous = runtime.getDelegationCwdPolicy();
|
|
660
|
+
const next: DelegationCwdPolicy =
|
|
661
|
+
value === "Current workspace only"
|
|
662
|
+
? "current-workspace"
|
|
663
|
+
: value === "Anywhere · normal Pi permissions"
|
|
664
|
+
? "anywhere"
|
|
665
|
+
: "trusted-targets";
|
|
666
|
+
if (next === previous) return { kind: "stay" as const };
|
|
667
|
+
try {
|
|
668
|
+
updateCwdPolicySetting("delegation", next);
|
|
669
|
+
runtime.setDelegationCwdPolicy(next);
|
|
670
|
+
ctx.ui.notify(`Saved and applied: ${delegationCwdLabel(next)}.`, "info");
|
|
671
|
+
return { kind: "stay" as const };
|
|
672
|
+
} catch (error) {
|
|
673
|
+
ctx.ui.notify(`Subagent settings were not saved: ${formatError(error)}`, "error");
|
|
674
|
+
return { kind: "rejected" as const };
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
542
678
|
function blockReloadWithRetainedAgents(
|
|
543
679
|
ctx: ExtensionCommandContext,
|
|
544
680
|
runtime: SubagentSettingsRuntime,
|
|
@@ -583,9 +719,9 @@ function showSubagentStatus(ctx: ExtensionCommandContext, runtime: SubagentSetti
|
|
|
583
719
|
);
|
|
584
720
|
}
|
|
585
721
|
|
|
586
|
-
function showSubagentHelp(ctx: ExtensionCommandContext) {
|
|
722
|
+
function showSubagentHelp(ctx: ExtensionCommandContext, runtime: SubagentSettingsRuntime) {
|
|
587
723
|
if (ctx.mode !== "tui" && !ctx.hasUI) return;
|
|
588
|
-
ctx.ui.notify(helpLines().join("\n"), "info");
|
|
724
|
+
ctx.ui.notify(helpLines(runtime).join("\n"), "info");
|
|
589
725
|
}
|
|
590
726
|
|
|
591
727
|
function statusLines(runtime: SubagentSettingsRuntime): string[] {
|
|
@@ -593,13 +729,20 @@ function statusLines(runtime: SubagentSettingsRuntime): string[] {
|
|
|
593
729
|
return formatStatus(runtime.getRuntimeStatus(), snapshot, runtime).split("\n");
|
|
594
730
|
}
|
|
595
731
|
|
|
596
|
-
function helpLines(): string[] {
|
|
732
|
+
function helpLines(runtime: SubagentSettingsRuntime): string[] {
|
|
597
733
|
const snapshot = inspectCompletionDeliverySettings();
|
|
734
|
+
const cwdPolicy = inspectCwdPolicySettings();
|
|
598
735
|
return [
|
|
599
736
|
"/subagents — choose delegation workflow, manage current agents, and configure agent tools",
|
|
600
|
-
"/subagents settings — configure async completion
|
|
737
|
+
"/subagents settings — configure target locations, trusted resources, and async completion",
|
|
601
738
|
"/subagents status — show current-session and user-setting values",
|
|
602
739
|
"/subagents help — show this help",
|
|
740
|
+
"Target policies control startup directories and resources, not filesystem access or sandboxing.",
|
|
741
|
+
"Manage saved folder trust with Pi /trust and restart Pi after changing it.",
|
|
742
|
+
`Runtime consultation target: ${consultationCwdLabel(runtime.getConsultationCwdPolicy())}`,
|
|
743
|
+
`Configured consultation target: ${consultationCwdLabel(cwdPolicy.consultation.value)} (${cwdPolicy.consultation.source})`,
|
|
744
|
+
`Runtime delegation target: ${delegationCwdLabel(runtime.getDelegationCwdPolicy())}`,
|
|
745
|
+
`Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)} (${cwdPolicy.delegation.source})`,
|
|
603
746
|
`User settings: ${safeTerminalText(snapshot.path)}`,
|
|
604
747
|
];
|
|
605
748
|
}
|
|
@@ -610,9 +753,18 @@ function formatManagerSummary(
|
|
|
610
753
|
configured: ReturnType<typeof inspectDelegationWorkflowSettings>,
|
|
611
754
|
): string {
|
|
612
755
|
const current = currentWorkflow(runtime, status);
|
|
756
|
+
const cwdPolicy = inspectCwdPolicySettings();
|
|
757
|
+
const consult = inspectConsultResourceSettings();
|
|
613
758
|
return [
|
|
614
759
|
`Delegation: ${workflowLabel(current)}`,
|
|
615
760
|
`Completion: ${completionLabel(status.completionDelivery)}`,
|
|
761
|
+
`Consult target: ${consultationCwdLabel(runtime.getConsultationCwdPolicy())}`,
|
|
762
|
+
`Delegation target: ${delegationCwdLabel(runtime.getDelegationCwdPolicy())}`,
|
|
763
|
+
`Consult resources: ${consultResourceLabel(runtime.getConsultResourcePolicy())}`,
|
|
764
|
+
`Configured consult target: ${consultationCwdLabel(cwdPolicy.consultation.value)} · ${cwdPolicy.consultation.source}`,
|
|
765
|
+
`Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)} · ${cwdPolicy.delegation.source}`,
|
|
766
|
+
`Configured consult resources: ${consultResourceLabel(consult.value)} · ${consult.source}`,
|
|
767
|
+
`Settings: ${safeTerminalText(cwdPolicy.path)}`,
|
|
616
768
|
`Agents: ${status.activeAgents} active · ${status.retainedAgents} retained`,
|
|
617
769
|
...(configured.value !== current
|
|
618
770
|
? [`Configured after reload: ${workflowLabel(configured.value)}`]
|
|
@@ -627,6 +779,8 @@ function formatStatus(
|
|
|
627
779
|
runtime?: SubagentSettingsRuntime,
|
|
628
780
|
): string {
|
|
629
781
|
const configuredWorkflow = inspectDelegationWorkflowSettings();
|
|
782
|
+
const consult = inspectConsultResourceSettings();
|
|
783
|
+
const cwdPolicy = inspectCwdPolicySettings();
|
|
630
784
|
const current = runtime ? currentWorkflow(runtime, status) : configuredWorkflow.value;
|
|
631
785
|
return [
|
|
632
786
|
"Current session",
|
|
@@ -634,15 +788,24 @@ function formatStatus(
|
|
|
634
788
|
` Async runtime: ${status.initialized ? "initialized" : status.enabled ? "not initialized" : "disabled"}`,
|
|
635
789
|
` Transport: ${status.transport}`,
|
|
636
790
|
` Completion: ${completionLabel(status.completionDelivery)}`,
|
|
791
|
+
` Consultation target: ${consultationCwdLabel(runtime?.getConsultationCwdPolicy() ?? cwdPolicy.consultation.value)}`,
|
|
792
|
+
` Delegation target: ${delegationCwdLabel(runtime?.getDelegationCwdPolicy() ?? cwdPolicy.delegation.value)}`,
|
|
793
|
+
` Consultation resources: ${consultResourceLabel(runtime?.getConsultResourcePolicy() ?? consult.value)}`,
|
|
637
794
|
` Agents: ${status.activeAgents} active, ${status.retainedAgents} retained`,
|
|
638
795
|
"User settings",
|
|
639
796
|
` Delegation source: ${configuredWorkflow.source}`,
|
|
640
797
|
` Configured delegation: ${workflowLabel(configuredWorkflow.value)}`,
|
|
641
798
|
` Completion source: ${snapshot.source}`,
|
|
642
799
|
` Configured completion: ${completionLabel(snapshot.value)}`,
|
|
800
|
+
` Configured consultation target: ${consultationCwdLabel(cwdPolicy.consultation.value)}`,
|
|
801
|
+
` Consultation target source: ${cwdPolicy.consultation.source}`,
|
|
802
|
+
` Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)}`,
|
|
803
|
+
` Delegation target source: ${cwdPolicy.delegation.source}`,
|
|
804
|
+
` Configured consultation resources: ${consultResourceLabel(consult.value)}`,
|
|
805
|
+
` Consultation resource source: ${consult.source}`,
|
|
643
806
|
` Path: ${safeTerminalText(snapshot.path)}`,
|
|
644
|
-
configuredWorkflow.error || snapshot.error
|
|
645
|
-
? ` Warning: ${safeTerminalText(configuredWorkflow.error ?? snapshot.error ?? "invalid settings")}`
|
|
807
|
+
configuredWorkflow.error || snapshot.error || cwdPolicy.error
|
|
808
|
+
? ` Warning: ${safeTerminalText(configuredWorkflow.error ?? snapshot.error ?? cwdPolicy.error ?? "invalid settings")}`
|
|
646
809
|
: " Warning: none",
|
|
647
810
|
configuredWorkflow.value !== current
|
|
648
811
|
? "Configured delegation differs from this session. Run /reload to apply it."
|
|
@@ -688,13 +851,45 @@ function completionLabel(value: CompletionDelivery): string {
|
|
|
688
851
|
return value === "auto-resume" ? "Resume automatically when finished" : "Wait until my next turn";
|
|
689
852
|
}
|
|
690
853
|
|
|
854
|
+
function consultationCwdLabel(value: ConsultationCwdPolicy): string {
|
|
855
|
+
return value === "current-workspace"
|
|
856
|
+
? "Current workspace only"
|
|
857
|
+
: "Anywhere · untrusted targets inherit nothing";
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function delegationCwdLabel(value: DelegationCwdPolicy): string {
|
|
861
|
+
switch (value) {
|
|
862
|
+
case "trusted-targets":
|
|
863
|
+
return "Current or saved-trusted folders";
|
|
864
|
+
case "current-workspace":
|
|
865
|
+
return "Current workspace only";
|
|
866
|
+
case "anywhere":
|
|
867
|
+
return "Anywhere · normal Pi permissions";
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function consultResourceLabel(value: ConsultResourcePolicy): string {
|
|
872
|
+
switch (value) {
|
|
873
|
+
case "project-context":
|
|
874
|
+
return "Project context only";
|
|
875
|
+
case "none":
|
|
876
|
+
return "No inherited resources";
|
|
877
|
+
case "all":
|
|
878
|
+
return "All trusted resources";
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
691
882
|
function workflowEffects(current: DelegationWorkflow, next: DelegationWorkflow): string[] {
|
|
692
883
|
const blockingEnabled = (value: DelegationWorkflow) =>
|
|
693
884
|
value === "all" || value === "blocking-only";
|
|
694
885
|
const asyncEnabled = (value: DelegationWorkflow) => value === "all" || value === "async-only";
|
|
695
886
|
const effects: string[] = [];
|
|
696
887
|
if (blockingEnabled(current) !== blockingEnabled(next)) {
|
|
697
|
-
effects.push(
|
|
888
|
+
effects.push(
|
|
889
|
+
blockingEnabled(next)
|
|
890
|
+
? "Add blocking `subagent` and read-only `subagent_consult`"
|
|
891
|
+
: "Remove blocking `subagent` and read-only `subagent_consult`",
|
|
892
|
+
);
|
|
698
893
|
}
|
|
699
894
|
if (asyncEnabled(current) !== asyncEnabled(next)) {
|
|
700
895
|
effects.push(
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const READ_ONLY_CONSULT_TOOLS = ["read", "grep", "find", "ls"] as const;
|
|
2
|
+
|
|
3
|
+
const READ_ONLY_CONSULT_TOOL_SET = new Set<string>(READ_ONLY_CONSULT_TOOLS);
|
|
4
|
+
|
|
5
|
+
export function resolveConsultTools(tools: readonly string[] | undefined): string[] {
|
|
6
|
+
if (tools === undefined) return [...READ_ONLY_CONSULT_TOOLS];
|
|
7
|
+
const seen = new Set<string>();
|
|
8
|
+
const effective: string[] = [];
|
|
9
|
+
for (const tool of tools) {
|
|
10
|
+
if (!READ_ONLY_CONSULT_TOOL_SET.has(tool) || seen.has(tool)) continue;
|
|
11
|
+
seen.add(tool);
|
|
12
|
+
effective.push(tool);
|
|
13
|
+
}
|
|
14
|
+
return effective;
|
|
15
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import {
|
|
3
|
+
getMarkdownTheme,
|
|
4
|
+
type Theme,
|
|
5
|
+
type ToolRenderResultOptions,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { Container, Markdown, Spacer, Text } from "@earendil-works/pi-tui";
|
|
8
|
+
import type { ConsultDetails, SubagentConsultParams } from "./consult.js";
|
|
9
|
+
import { formatUsageStats } from "./render.js";
|
|
10
|
+
import {
|
|
11
|
+
COLLAPSED_ANSWER_LINES,
|
|
12
|
+
COLLAPSED_LIST_LIMIT,
|
|
13
|
+
expansionHint,
|
|
14
|
+
previewLines,
|
|
15
|
+
projectRenderActivity,
|
|
16
|
+
type RenderStatus,
|
|
17
|
+
recordValue,
|
|
18
|
+
renderActivityLines,
|
|
19
|
+
renderFallbackResult,
|
|
20
|
+
safeBlock,
|
|
21
|
+
safeLine,
|
|
22
|
+
statusBadge,
|
|
23
|
+
stringValue,
|
|
24
|
+
type ToolRendererContext,
|
|
25
|
+
textResult,
|
|
26
|
+
toolHeader,
|
|
27
|
+
} from "./render-common.js";
|
|
28
|
+
|
|
29
|
+
export function renderConsultCall(args: Partial<SubagentConsultParams>, theme: Theme) {
|
|
30
|
+
const scope = args.agentScope ?? "user";
|
|
31
|
+
const text = [
|
|
32
|
+
toolHeader(theme, "subagent_consult", args.agent, [`[${scope}]`, "read-only"]),
|
|
33
|
+
` ${theme.fg("dim", safeLine(args.task, "...", 2 * 1024))}`,
|
|
34
|
+
].join("\n");
|
|
35
|
+
return new Text(text, 0, 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function renderConsultResult(
|
|
39
|
+
result: AgentToolResult<ConsultDetails>,
|
|
40
|
+
options: ToolRenderResultOptions,
|
|
41
|
+
theme: Theme,
|
|
42
|
+
context: ToolRendererContext<SubagentConsultParams>,
|
|
43
|
+
) {
|
|
44
|
+
const details = recordValue(result.details);
|
|
45
|
+
if (!details || !recordValue(details.policy)) {
|
|
46
|
+
return renderFallbackResult(result, options, theme, context.isError);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const progress = recordValue(details.progress);
|
|
50
|
+
const child = recordValue(details.child);
|
|
51
|
+
const status = consultStatus(details, progress, options, context.isError);
|
|
52
|
+
const agent = safeLine(details.agent, safeLine(context.args?.agent, "subagent"), 256);
|
|
53
|
+
const source = safeLine(details.agentSource, "unknown", 128);
|
|
54
|
+
const usage = recordValue(progress?.usage ?? child?.usage);
|
|
55
|
+
const usageText = formatUnknownUsage(
|
|
56
|
+
usage,
|
|
57
|
+
stringValue(details.model) || undefined,
|
|
58
|
+
stringValue(details.thinkingLevel) || undefined,
|
|
59
|
+
stringValue(progress?.actualProvider ?? child?.actualProvider) || undefined,
|
|
60
|
+
stringValue(progress?.actualModel ?? child?.actualModel) || undefined,
|
|
61
|
+
);
|
|
62
|
+
const effectiveTools = stringList(recordValue(details.policy)?.effectiveTools);
|
|
63
|
+
const activity = projectRenderActivity(progress?.recentActivity);
|
|
64
|
+
const activityTotal = Math.max(
|
|
65
|
+
activity.length,
|
|
66
|
+
typeof progress?.recentActivityTotal === "number" ? progress.recentActivityTotal : 0,
|
|
67
|
+
);
|
|
68
|
+
const answer = safeBlock(textResult(result), "", 50 * 1024).trim();
|
|
69
|
+
|
|
70
|
+
if (options.expanded) {
|
|
71
|
+
const container = new Container();
|
|
72
|
+
container.addChild(
|
|
73
|
+
new Text(
|
|
74
|
+
`${statusBadge(theme, status)} · ${theme.fg("toolTitle", theme.bold(agent))}${theme.fg("muted", ` (${source})`)}`,
|
|
75
|
+
0,
|
|
76
|
+
0,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
if (usageText) container.addChild(new Text(theme.fg("dim", usageText), 0, 0));
|
|
80
|
+
container.addChild(new Spacer(1));
|
|
81
|
+
container.addChild(new Text(theme.fg("muted", "─── Task ───"), 0, 0));
|
|
82
|
+
container.addChild(
|
|
83
|
+
new Text(theme.fg("dim", safeBlock(context.args?.task, "(unavailable)", 50 * 1024)), 0, 0),
|
|
84
|
+
);
|
|
85
|
+
container.addChild(new Spacer(1));
|
|
86
|
+
container.addChild(new Text(theme.fg("muted", "─── Policy ───"), 0, 0));
|
|
87
|
+
container.addChild(new Text(theme.fg("dim", policySummary(details.policy)), 0, 0));
|
|
88
|
+
if (activity.length > 0) {
|
|
89
|
+
container.addChild(new Spacer(1));
|
|
90
|
+
container.addChild(new Text(theme.fg("muted", "─── Activity ───"), 0, 0));
|
|
91
|
+
container.addChild(
|
|
92
|
+
new Text(renderActivityLines(activity, theme, undefined, activityTotal), 0, 0),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
container.addChild(new Spacer(1));
|
|
96
|
+
container.addChild(new Text(theme.fg("muted", "─── Answer ───"), 0, 0));
|
|
97
|
+
if (answer) container.addChild(new Markdown(answer, 0, 0, getMarkdownTheme()));
|
|
98
|
+
else
|
|
99
|
+
container.addChild(
|
|
100
|
+
new Text(
|
|
101
|
+
theme.fg("muted", options.isPartial ? "(waiting for output)" : "(no output)"),
|
|
102
|
+
0,
|
|
103
|
+
0,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
return container;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const lines = [
|
|
110
|
+
`${statusBadge(theme, status)} · ${theme.fg("toolTitle", theme.bold(agent))}${theme.fg("muted", ` (${source})`)}`,
|
|
111
|
+
];
|
|
112
|
+
if (usageText) lines.push(theme.fg("dim", usageText));
|
|
113
|
+
if (effectiveTools.length > 0 && options.isPartial) {
|
|
114
|
+
lines.push(theme.fg("dim", `tools: ${effectiveTools.join(", ")}`));
|
|
115
|
+
}
|
|
116
|
+
if (activity.length > 0) {
|
|
117
|
+
lines.push(renderActivityLines(activity, theme, COLLAPSED_LIST_LIMIT, activityTotal));
|
|
118
|
+
} else if (options.isPartial) {
|
|
119
|
+
lines.push(
|
|
120
|
+
theme.fg("muted", status === "starting" ? "(starting child)" : "(waiting for activity)"),
|
|
121
|
+
);
|
|
122
|
+
} else if (answer) {
|
|
123
|
+
lines.push(theme.fg("toolOutput", previewLines(answer, COLLAPSED_ANSWER_LINES)));
|
|
124
|
+
} else {
|
|
125
|
+
const error = safeBlock(child?.error, "", 2 * 1024).trim();
|
|
126
|
+
lines.push(theme.fg(status === "failed" ? "error" : "muted", error || "(no output)"));
|
|
127
|
+
}
|
|
128
|
+
if (!options.isPartial) lines.push(expansionHint());
|
|
129
|
+
return new Text(lines.filter(Boolean).join("\n"), 0, 0);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function consultStatus(
|
|
133
|
+
details: Record<string, unknown>,
|
|
134
|
+
progress: Record<string, unknown> | undefined,
|
|
135
|
+
options: ToolRenderResultOptions,
|
|
136
|
+
isError: boolean,
|
|
137
|
+
): RenderStatus {
|
|
138
|
+
if (details.cancelled === true) return "cancelled";
|
|
139
|
+
if (isError || details.isError === true) {
|
|
140
|
+
const child = recordValue(details.child);
|
|
141
|
+
return child?.aborted === true && child?.timedOut !== true ? "cancelled" : "failed";
|
|
142
|
+
}
|
|
143
|
+
if (options.isPartial) return progress?.phase === "starting" ? "starting" : "running";
|
|
144
|
+
return "completed";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function stringList(value: unknown): string[] {
|
|
148
|
+
return Array.isArray(value)
|
|
149
|
+
? value.flatMap((item) => (typeof item === "string" ? [safeLine(item, "", 256)] : []))
|
|
150
|
+
: [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function policySummary(value: unknown): string {
|
|
154
|
+
const policy = recordValue(value);
|
|
155
|
+
if (!policy) return "(unavailable)";
|
|
156
|
+
const resources = recordValue(policy.effectiveResources);
|
|
157
|
+
const tools = stringList(policy.effectiveTools);
|
|
158
|
+
return [
|
|
159
|
+
`tools: ${tools.length > 0 ? tools.join(", ") : "none"}`,
|
|
160
|
+
`resources: ${safeLine(resources?.policy ?? policy.requestedResources, "unknown", 256)}`,
|
|
161
|
+
`extensions: ${safeLine(policy.extensions, "unknown", 128)}`,
|
|
162
|
+
`session persistence: ${safeLine(policy.sessionPersistence, "unknown", 128)}`,
|
|
163
|
+
`retained agent: ${policy.retainedAgent === false ? "no" : "unknown"}`,
|
|
164
|
+
].join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function formatUnknownUsage(
|
|
168
|
+
value: Record<string, unknown> | undefined,
|
|
169
|
+
model?: string,
|
|
170
|
+
thinkingLevel?: string,
|
|
171
|
+
actualProvider?: string,
|
|
172
|
+
actualModel?: string,
|
|
173
|
+
): string {
|
|
174
|
+
const usage = value ?? {};
|
|
175
|
+
return formatUsageStats(
|
|
176
|
+
{
|
|
177
|
+
input: safeNumber(usage.input),
|
|
178
|
+
output: safeNumber(usage.output),
|
|
179
|
+
cacheRead: safeNumber(usage.cacheRead),
|
|
180
|
+
cacheWrite: safeNumber(usage.cacheWrite),
|
|
181
|
+
cost: safeNumber(usage.cost),
|
|
182
|
+
contextTokens: safeNumber(usage.contextTokens),
|
|
183
|
+
turns: safeNumber(usage.turns),
|
|
184
|
+
},
|
|
185
|
+
model,
|
|
186
|
+
thinkingLevel as never,
|
|
187
|
+
actualProvider,
|
|
188
|
+
actualModel,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function safeNumber(value: unknown): number {
|
|
193
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
194
|
+
}
|