@narumitw/pi-subagents 0.49.3 → 0.52.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.
Files changed (83) hide show
  1. package/README.md +362 -53
  2. package/package.json +10 -7
  3. package/src/adaptive-scheduler.ts +224 -0
  4. package/src/admission-benchmark.ts +95 -0
  5. package/src/admission-policy.ts +78 -0
  6. package/src/agent-projection.ts +53 -0
  7. package/src/agents.ts +58 -1
  8. package/src/auto-transport.ts +114 -0
  9. package/src/blocking-status.ts +63 -0
  10. package/src/capabilities.ts +145 -0
  11. package/src/capability-grant.ts +115 -0
  12. package/src/capability-router.ts +107 -0
  13. package/src/completion-delivery.ts +257 -0
  14. package/src/config-status.ts +221 -0
  15. package/src/config-ui.ts +215 -236
  16. package/src/consult-resources.ts +4 -27
  17. package/src/consult.ts +9 -1
  18. package/src/create-stateful-transport.ts +55 -0
  19. package/src/delegation-contract.ts +417 -0
  20. package/src/execution-plan.ts +322 -0
  21. package/src/execution-profiles.ts +95 -0
  22. package/src/execution-ui.ts +320 -0
  23. package/src/execution.ts +1098 -158
  24. package/src/in-process-transport.ts +269 -25
  25. package/src/inspect-render.ts +101 -1
  26. package/src/inspect.ts +321 -3
  27. package/src/integration-controller.ts +98 -0
  28. package/src/limits.ts +3 -0
  29. package/src/orchestration-metrics.ts +109 -0
  30. package/src/outcome.ts +61 -0
  31. package/src/panel-child-group.ts +35 -0
  32. package/src/panel-contract.ts +343 -0
  33. package/src/panel-evidence.ts +59 -0
  34. package/src/panel-execution.ts +770 -0
  35. package/src/panel-failure.ts +56 -0
  36. package/src/panel-planning.ts +175 -0
  37. package/src/panel-prompts.ts +132 -0
  38. package/src/panel-reconciliation.ts +57 -0
  39. package/src/panel-render.ts +103 -0
  40. package/src/parallel-limit-ui.ts +112 -0
  41. package/src/params.ts +179 -3
  42. package/src/persistence.ts +182 -32
  43. package/src/prompt-resources.ts +38 -0
  44. package/src/registry-types.ts +175 -0
  45. package/src/registry.ts +466 -143
  46. package/src/render.ts +72 -6
  47. package/src/result-contract.ts +416 -0
  48. package/src/retained-semantic-state.ts +100 -0
  49. package/src/rpc-timeout-finalization.ts +207 -0
  50. package/src/rpc-transport-metadata.ts +65 -0
  51. package/src/rpc-transport.ts +990 -0
  52. package/src/rpc-turn-capture.ts +142 -0
  53. package/src/runner-result.ts +55 -0
  54. package/src/runner-usage.ts +48 -0
  55. package/src/runner.ts +325 -73
  56. package/src/semantic-snapshot.ts +214 -0
  57. package/src/settings.ts +254 -35
  58. package/src/spawn-idempotency.ts +61 -0
  59. package/src/stateful-config.ts +13 -0
  60. package/src/stateful-guidance.ts +1 -0
  61. package/src/stateful-lifecycle.ts +45 -2
  62. package/src/stateful-limit-ui.ts +246 -0
  63. package/src/stateful-limits.ts +96 -0
  64. package/src/stateful-prompt.ts +11 -2
  65. package/src/stateful-render.ts +48 -3
  66. package/src/stateful.ts +467 -357
  67. package/src/subagents.ts +114 -46
  68. package/src/subprocess-transport.ts +64 -5
  69. package/src/supervision.ts +103 -0
  70. package/src/timeout-checkpoint.ts +305 -0
  71. package/src/timeout-finalization.ts +75 -0
  72. package/src/transport-types.ts +68 -0
  73. package/src/transport-ui.ts +169 -0
  74. package/src/transport.ts +16 -4
  75. package/src/turn-budget.ts +109 -0
  76. package/src/verification-policy.ts +67 -0
  77. package/src/work-item-ledger.ts +931 -0
  78. package/src/work-item-persistence.ts +223 -0
  79. package/src/workflow-planning.ts +162 -0
  80. package/src/workflow-tree-identity.ts +289 -0
  81. package/src/workflow-ui.ts +61 -0
  82. package/src/workflow-verification.ts +296 -0
  83. package/src/workspace.ts +69 -12
@@ -0,0 +1,257 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { CompletionDelivery } from "./agents.js";
3
+ import { redactPrivateText } from "./context.js";
4
+ import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
5
+ import type { AgentTurnCompletion, ManagedAgent } from "./registry.js";
6
+ import { safeTerminalText } from "./safe-text.js";
7
+ import { PI_SUBAGENTS_RPC_PROTOCOL } from "./transport-types.js";
8
+
9
+ const MAX_TOOL_MESSAGE_BYTES = 2 * 1024;
10
+ const MAX_COMPLETION_ERROR_BYTES = 512;
11
+ const MAX_COMPLETIONS_PER_MESSAGE = 16;
12
+ const COMPLETION_BATCH_DELAY_MS = 10;
13
+
14
+ interface CompletionMetadata {
15
+ protocol: typeof PI_SUBAGENTS_RPC_PROTOCOL;
16
+ agentId: string;
17
+ agent: string;
18
+ state: string;
19
+ transport?: string;
20
+ structuredResult?: ManagedAgent["structuredResult"];
21
+ outcome?: ManagedAgent["outcome"];
22
+ capabilityGrant?: ManagedAgent["capabilityGrant"];
23
+ }
24
+
25
+ interface CompletionMessage {
26
+ customType: "pi-subagent-completion";
27
+ content: string;
28
+ display: true;
29
+ details:
30
+ | CompletionMetadata
31
+ | {
32
+ completionCount: number;
33
+ completions: CompletionMetadata[];
34
+ };
35
+ }
36
+
37
+ type CompletionContext = Pick<ExtensionContext, "hasPendingMessages" | "isIdle">;
38
+ type CompletionPi = Pick<ExtensionAPI, "sendMessage">;
39
+
40
+ export interface CompletionDeliveryBrokerOptions {
41
+ onDeliveryError?: (error: unknown) => void;
42
+ onDelivered?: (completions: readonly AgentTurnCompletion[], deliveredAt: number) => void;
43
+ now?: () => number;
44
+ }
45
+
46
+ /** Owns bounded completion batching and at most one idle-root wake for one parent session. */
47
+ export class CompletionDeliveryBroker {
48
+ private pending: AgentTurnCompletion[] = [];
49
+ private flushTimer?: NodeJS.Timeout;
50
+ private wakeInFlight = false;
51
+ private closed = false;
52
+
53
+ constructor(
54
+ private readonly pi: CompletionPi,
55
+ private readonly ctx: CompletionContext,
56
+ private delivery: CompletionDelivery,
57
+ private readonly options: CompletionDeliveryBrokerOptions = {},
58
+ ) {}
59
+
60
+ enqueue(completion: AgentTurnCompletion): void {
61
+ if (this.closed) return;
62
+ this.pending.push(completion);
63
+ this.scheduleFlush();
64
+ }
65
+
66
+ setDelivery(value: CompletionDelivery): void {
67
+ this.delivery = value;
68
+ this.scheduleFlush();
69
+ }
70
+
71
+ onParentTurnStart(): void {
72
+ this.wakeInFlight = false;
73
+ this.scheduleFlush();
74
+ }
75
+
76
+ onParentSettled(): void {
77
+ this.wakeInFlight = false;
78
+ this.scheduleFlush();
79
+ }
80
+
81
+ flush(): void {
82
+ if (this.closed || this.pending.length === 0) return;
83
+ if (this.flushTimer) clearTimeout(this.flushTimer);
84
+ this.flushTimer = undefined;
85
+ if (this.delivery === "auto-resume" && !this.isRootIdle()) return;
86
+
87
+ const completions = this.pending.splice(0);
88
+ const batches = chunkCompletions(completions);
89
+ let canWake = this.shouldWakeRoot();
90
+ for (let index = 0; index < batches.length; index++) {
91
+ const triggerTurn = canWake && index === batches.length - 1;
92
+ const message = buildCompletionMessage(batches[index]);
93
+ if (triggerTurn) this.wakeInFlight = true;
94
+ try {
95
+ this.pi.sendMessage(message, { deliverAs: "steer", triggerTurn });
96
+ this.notifyDelivered(batches[index]);
97
+ } catch (primaryError) {
98
+ if (triggerTurn) this.wakeInFlight = false;
99
+ canWake = false;
100
+ try {
101
+ this.pi.sendMessage(message, { deliverAs: "nextTurn", triggerTurn: false });
102
+ this.notifyDelivered(batches[index]);
103
+ } catch (fallbackError) {
104
+ this.pending = [...batches.slice(index).flat(), ...this.pending];
105
+ try {
106
+ this.options.onDeliveryError?.(
107
+ new AggregateError(
108
+ [primaryError, fallbackError],
109
+ "Detached subagent completion delivery failed",
110
+ ),
111
+ );
112
+ } catch {
113
+ // Delivery retention must survive a failing observer.
114
+ }
115
+ return;
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ close(): void {
122
+ this.closed = true;
123
+ if (this.flushTimer) clearTimeout(this.flushTimer);
124
+ this.flushTimer = undefined;
125
+ this.pending = [];
126
+ }
127
+
128
+ private scheduleFlush(): void {
129
+ if (this.closed || this.pending.length === 0 || this.flushTimer) return;
130
+ this.flushTimer = setTimeout(() => {
131
+ this.flushTimer = undefined;
132
+ this.flush();
133
+ }, COMPLETION_BATCH_DELAY_MS);
134
+ }
135
+
136
+ private notifyDelivered(completions: readonly AgentTurnCompletion[]): void {
137
+ try {
138
+ this.options.onDelivered?.(completions, (this.options.now ?? Date.now)());
139
+ } catch {
140
+ // Delivery already succeeded, so observer failures cannot requeue it.
141
+ }
142
+ }
143
+
144
+ private isRootIdle(): boolean {
145
+ try {
146
+ return this.ctx.isIdle();
147
+ } catch {
148
+ return false;
149
+ }
150
+ }
151
+
152
+ private shouldWakeRoot(): boolean {
153
+ if (this.delivery !== "auto-resume" || this.wakeInFlight) return false;
154
+ try {
155
+ return !this.ctx.hasPendingMessages();
156
+ } catch {
157
+ return false;
158
+ }
159
+ }
160
+ }
161
+
162
+ function chunkCompletions(completions: AgentTurnCompletion[]): AgentTurnCompletion[][] {
163
+ const batches: AgentTurnCompletion[][] = [];
164
+ for (let index = 0; index < completions.length; index += MAX_COMPLETIONS_PER_MESSAGE) {
165
+ batches.push(completions.slice(index, index + MAX_COMPLETIONS_PER_MESSAGE));
166
+ }
167
+ return batches;
168
+ }
169
+
170
+ function buildCompletionMessage(completions: AgentTurnCompletion[]): CompletionMessage {
171
+ if (completions.length === 1) {
172
+ const completion = completions[0];
173
+ return {
174
+ customType: "pi-subagent-completion",
175
+ content: buildDetachedCompletionMessage(completion),
176
+ display: true,
177
+ details: completionMetadata(completion),
178
+ };
179
+ }
180
+ const content = truncateUtf8(
181
+ [
182
+ "Message Type: SUBAGENT_COMPLETION_BATCH",
183
+ `Protocol: ${PI_SUBAGENTS_RPC_PROTOCOL}`,
184
+ `Completion Count: ${completions.length}`,
185
+ ...completions.flatMap((completion, index) => [
186
+ "",
187
+ `--- Completion ${index + 1} of ${completions.length} ---`,
188
+ buildDetachedCompletionMessage(completion),
189
+ ]),
190
+ ].join("\n"),
191
+ DEFAULT_MAX_CONTEXT_BYTES,
192
+ ).text;
193
+ return {
194
+ customType: "pi-subagent-completion",
195
+ content,
196
+ display: true,
197
+ details: {
198
+ completionCount: completions.length,
199
+ completions: completions.map(completionMetadata),
200
+ },
201
+ };
202
+ }
203
+
204
+ function completionMetadata(completion: AgentTurnCompletion): CompletionMetadata {
205
+ return {
206
+ protocol: PI_SUBAGENTS_RPC_PROTOCOL,
207
+ agentId: completion.agent.id,
208
+ agent: completion.agent.agent,
209
+ state: completion.agent.state,
210
+ ...(completion.agent.telemetry?.transport
211
+ ? { transport: completion.agent.telemetry.transport }
212
+ : {}),
213
+ ...(completion.agent.structuredResult
214
+ ? { structuredResult: completion.agent.structuredResult }
215
+ : {}),
216
+ ...(completion.agent.outcome ? { outcome: completion.agent.outcome } : {}),
217
+ ...(completion.agent.capabilityGrant
218
+ ? { capabilityGrant: completion.agent.capabilityGrant }
219
+ : {}),
220
+ };
221
+ }
222
+
223
+ export function buildDetachedCompletionMessage(completion: AgentTurnCompletion): string {
224
+ const task = sanitizeCompletionLine(completion.task, 256) || "(unknown task)";
225
+ const agentName = sanitizeCompletionLine(completion.agent.agent, 128) || "(unknown agent)";
226
+ const output = safeTerminalText(redactPrivateText(completion.output));
227
+ const error = completion.error
228
+ ? truncateUtf8(
229
+ safeTerminalText(redactPrivateText(completion.error)),
230
+ MAX_COMPLETION_ERROR_BYTES,
231
+ ).text
232
+ : "";
233
+ return truncateUtf8(
234
+ [
235
+ "Message Type: SUBAGENT_COMPLETION",
236
+ `Protocol: ${PI_SUBAGENTS_RPC_PROTOCOL}`,
237
+ `Agent ID: ${completion.agent.id}`,
238
+ `Agent: ${agentName}`,
239
+ `Task: ${task}`,
240
+ `State: ${completion.agent.state}`,
241
+ ...(error.trim() ? ["Error:", error] : []),
242
+ "Payload:",
243
+ output.trim() ? output : "(no output)",
244
+ ].join("\n"),
245
+ MAX_TOOL_MESSAGE_BYTES,
246
+ ).text;
247
+ }
248
+
249
+ function sanitizeCompletionLine(value: string, maxBytes: number): string {
250
+ return (
251
+ truncateUtf8(redactPrivateText(value), maxBytes)
252
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: Strip untrusted terminal controls.
253
+ .text.replace(/[\u0000-\u001f\u007f]+/g, " ")
254
+ .replace(/\s+/g, " ")
255
+ .trim()
256
+ );
257
+ }
@@ -0,0 +1,221 @@
1
+ import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
2
+ import type {
3
+ CompletionDelivery,
4
+ ConsultationCwdPolicy,
5
+ ConsultResourcePolicy,
6
+ DelegationCwdPolicy,
7
+ } from "./agents.js";
8
+ import type { SubagentSettingsRuntime } from "./config-ui.js";
9
+ import { safeTerminalLine as safeTerminalText } from "./safe-text.js";
10
+ import {
11
+ type DelegationWorkflow,
12
+ inspectBlockingParallelLimitSettings,
13
+ inspectCompletionDeliverySettings,
14
+ inspectConsultResourceSettings,
15
+ inspectCwdPolicySettings,
16
+ inspectDelegationWorkflowSettings,
17
+ inspectStatefulLimitSettings,
18
+ inspectStatefulTransportSettings,
19
+ } from "./settings.js";
20
+ import type { StatefulSubagentRuntimeStatus } from "./stateful.js";
21
+ import {
22
+ formatConfiguredDetachedLimitDivergence,
23
+ formatConfiguredDetachedLimits,
24
+ formatDetachedLimitSummary,
25
+ } from "./stateful-limit-ui.js";
26
+ import { STATEFUL_LIMIT_DEFINITIONS } from "./stateful-limits.js";
27
+ import { workflowLabel } from "./workflow-ui.js";
28
+
29
+ export function showSubagentStatus(
30
+ ctx: ExtensionCommandContext,
31
+ runtime: SubagentSettingsRuntime,
32
+ ): void {
33
+ if (ctx.mode !== "tui" && !ctx.hasUI) return;
34
+ const snapshot = inspectCompletionDeliverySettings();
35
+ ctx.ui.notify(
36
+ formatStatus(runtime.getRuntimeStatus(), snapshot, runtime),
37
+ snapshot.error ? "warning" : "info",
38
+ );
39
+ }
40
+
41
+ export function showSubagentHelp(
42
+ ctx: ExtensionCommandContext,
43
+ runtime: SubagentSettingsRuntime,
44
+ ): void {
45
+ if (ctx.mode !== "tui" && !ctx.hasUI) return;
46
+ ctx.ui.notify(helpLines(runtime).join("\n"), "info");
47
+ }
48
+
49
+ export function statusLines(runtime: SubagentSettingsRuntime): string[] {
50
+ const snapshot = inspectCompletionDeliverySettings();
51
+ return formatStatus(runtime.getRuntimeStatus(), snapshot, runtime).split("\n");
52
+ }
53
+
54
+ export function helpLines(runtime: SubagentSettingsRuntime): string[] {
55
+ const snapshot = inspectCompletionDeliverySettings();
56
+ const cwdPolicy = inspectCwdPolicySettings();
57
+ const parallelLimit = inspectBlockingParallelLimitSettings();
58
+ const detachedLimits = inspectStatefulLimitSettings();
59
+ const transport = inspectStatefulTransportSettings();
60
+ return [
61
+ "/subagents — choose delegation workflow, manage current agents, and configure agent tools",
62
+ "/subagents settings — configure target locations, trusted resources, and async completion",
63
+ "/subagents status — show current-session and user-setting values",
64
+ "/subagents help — show this help",
65
+ "Target policies control startup directories and resources, not filesystem access or sandboxing.",
66
+ "Manage saved folder trust with Pi /trust and restart Pi after changing it.",
67
+ `Runtime consultation target: ${consultationCwdLabel(runtime.getConsultationCwdPolicy())}`,
68
+ `Configured consultation target: ${consultationCwdLabel(cwdPolicy.consultation.value)} (${cwdPolicy.consultation.source})`,
69
+ `Runtime delegation target: ${delegationCwdLabel(runtime.getDelegationCwdPolicy())}`,
70
+ `Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)} (${cwdPolicy.delegation.source})`,
71
+ `Maximum parallel workers: ${runtime.getMaxParallelTasks()} per blocking call`,
72
+ `Configured parallel limit: ${parallelLimit.value} (${parallelLimit.source})`,
73
+ `Detached limits: ${formatDetachedLimitSummary(runtime.getRuntimeStatus())}`,
74
+ `Configured transport: ${transport.value} (${transport.source})`,
75
+ ...(detachedLimits.values
76
+ ? [`Configured detached limits: ${formatConfiguredDetachedLimits(detachedLimits.values)}`]
77
+ : ["Configured detached limits: unavailable; repair user settings"]),
78
+ "Detached limits and transport apply after /reload; clear retained agents first if their work must not be interrupted.",
79
+ `User settings: ${safeTerminalText(snapshot.path)}`,
80
+ ];
81
+ }
82
+
83
+ export function formatManagerSummary(
84
+ runtime: SubagentSettingsRuntime,
85
+ status: StatefulSubagentRuntimeStatus,
86
+ configured: ReturnType<typeof inspectDelegationWorkflowSettings>,
87
+ ): string {
88
+ const current = currentWorkflow(runtime, status);
89
+ const cwdPolicy = inspectCwdPolicySettings();
90
+ const consult = inspectConsultResourceSettings();
91
+ const detachedLimits = inspectStatefulLimitSettings();
92
+ const transport = inspectStatefulTransportSettings();
93
+ const detachedDivergence = detachedLimits.values
94
+ ? formatConfiguredDetachedLimitDivergence(status, detachedLimits.values)
95
+ : undefined;
96
+ return [
97
+ `Delegation: ${workflowLabel(current)}`,
98
+ `Completion: ${completionLabel(status.completionDelivery)}`,
99
+ `Consult target: ${consultationCwdLabel(runtime.getConsultationCwdPolicy())}`,
100
+ `Delegation target: ${delegationCwdLabel(runtime.getDelegationCwdPolicy())}`,
101
+ `Consult resources: ${consultResourceLabel(runtime.getConsultResourcePolicy())}`,
102
+ `Parallel workers: max ${runtime.getMaxParallelTasks()} per blocking call`,
103
+ `Detached limits: ${formatDetachedLimitSummary(status)}`,
104
+ `Transport: ${status.transport}`,
105
+ `Configured transport: ${transport.value} · ${transport.source}`,
106
+ `Configured consult target: ${consultationCwdLabel(cwdPolicy.consultation.value)} · ${cwdPolicy.consultation.source}`,
107
+ `Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)} · ${cwdPolicy.delegation.source}`,
108
+ `Configured consult resources: ${consultResourceLabel(consult.value)} · ${consult.source}`,
109
+ `Settings: ${safeTerminalText(cwdPolicy.path)}`,
110
+ `Agents: ${status.activeAgents} active · ${status.retainedAgents} retained`,
111
+ ...(detachedDivergence ? [detachedDivergence] : []),
112
+ ...(configured.value !== current
113
+ ? [`Configured after reload: ${workflowLabel(configured.value)}`]
114
+ : []),
115
+ ...(configured.error || detachedLimits.error
116
+ ? ["Settings need repair; open Advanced settings for details."]
117
+ : []),
118
+ ].join("\n");
119
+ }
120
+
121
+ function formatStatus(
122
+ status: StatefulSubagentRuntimeStatus,
123
+ snapshot: ReturnType<typeof inspectCompletionDeliverySettings>,
124
+ runtime?: SubagentSettingsRuntime,
125
+ ): string {
126
+ const configuredWorkflow = inspectDelegationWorkflowSettings();
127
+ const consult = inspectConsultResourceSettings();
128
+ const cwdPolicy = inspectCwdPolicySettings();
129
+ const parallelLimit = inspectBlockingParallelLimitSettings();
130
+ const detachedLimits = inspectStatefulLimitSettings();
131
+ const transport = inspectStatefulTransportSettings();
132
+ const current = runtime ? currentWorkflow(runtime, status) : configuredWorkflow.value;
133
+ return [
134
+ "Current session",
135
+ ` Delegation: ${workflowLabel(current)}`,
136
+ ` Async runtime: ${status.initialized ? "initialized" : status.enabled ? "not initialized" : "disabled"}`,
137
+ ` Transport: ${status.transport}`,
138
+ ` Configured transport: ${transport.value} (${transport.source})`,
139
+ ` Completion: ${completionLabel(status.completionDelivery)}`,
140
+ ` Consultation target: ${consultationCwdLabel(runtime?.getConsultationCwdPolicy() ?? cwdPolicy.consultation.value)}`,
141
+ ` Delegation target: ${delegationCwdLabel(runtime?.getDelegationCwdPolicy() ?? cwdPolicy.delegation.value)}`,
142
+ ` Consultation resources: ${consultResourceLabel(runtime?.getConsultResourcePolicy() ?? consult.value)}`,
143
+ ` Maximum parallel workers: ${runtime?.getMaxParallelTasks() ?? parallelLimit.value} per blocking call`,
144
+ ` Detached limits: ${formatDetachedLimitSummary(status)}`,
145
+ ` Agents: ${status.activeAgents} active, ${status.retainedAgents} retained`,
146
+ "User settings",
147
+ ` Delegation source: ${configuredWorkflow.source}`,
148
+ ` Configured delegation: ${workflowLabel(configuredWorkflow.value)}`,
149
+ ` Completion source: ${snapshot.source}`,
150
+ ` Configured completion: ${completionLabel(snapshot.value)}`,
151
+ ` Configured parallel limit: ${parallelLimit.value}`,
152
+ ` Parallel limit source: ${parallelLimit.source}`,
153
+ ...(detachedLimits.values
154
+ ? STATEFUL_LIMIT_DEFINITIONS.map((definition) => {
155
+ const configured = detachedLimits.values?.[definition.field];
156
+ return ` Configured ${definition.label.toLowerCase()}: ${configured?.value} (${configured?.source})`;
157
+ })
158
+ : [" Configured detached limits: unavailable"]),
159
+ ` Configured consultation target: ${consultationCwdLabel(cwdPolicy.consultation.value)}`,
160
+ ` Consultation target source: ${cwdPolicy.consultation.source}`,
161
+ ` Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)}`,
162
+ ` Delegation target source: ${cwdPolicy.delegation.source}`,
163
+ ` Configured consultation resources: ${consultResourceLabel(consult.value)}`,
164
+ ` Consultation resource source: ${consult.source}`,
165
+ ` Path: ${safeTerminalText(snapshot.path)}`,
166
+ configuredWorkflow.error ||
167
+ snapshot.error ||
168
+ cwdPolicy.error ||
169
+ parallelLimit.error ||
170
+ detachedLimits.error ||
171
+ transport.error
172
+ ? ` Warning: ${safeTerminalText(configuredWorkflow.error ?? snapshot.error ?? cwdPolicy.error ?? parallelLimit.error ?? detachedLimits.error ?? transport.error ?? "invalid settings")}`
173
+ : " Warning: none",
174
+ configuredWorkflow.value !== current
175
+ ? "Configured delegation differs from this session. Run /reload to apply it."
176
+ : "Manual file changes require /reload.",
177
+ ].join("\n");
178
+ }
179
+
180
+ export function currentWorkflow(
181
+ runtime: SubagentSettingsRuntime,
182
+ status: StatefulSubagentRuntimeStatus,
183
+ ): DelegationWorkflow {
184
+ const blocking = runtime.getBlockingEnabled();
185
+ if (blocking && status.enabled) return "all";
186
+ if (status.enabled) return "async-only";
187
+ if (blocking) return "blocking-only";
188
+ return "disabled";
189
+ }
190
+
191
+ export function completionLabel(value: CompletionDelivery): string {
192
+ return value === "auto-resume" ? "Resume automatically when finished" : "Wait until my next turn";
193
+ }
194
+
195
+ export function consultationCwdLabel(value: ConsultationCwdPolicy): string {
196
+ return value === "current-workspace"
197
+ ? "Current workspace only"
198
+ : "Anywhere · untrusted targets inherit nothing";
199
+ }
200
+
201
+ export function delegationCwdLabel(value: DelegationCwdPolicy): string {
202
+ switch (value) {
203
+ case "trusted-targets":
204
+ return "Current or saved-trusted folders";
205
+ case "current-workspace":
206
+ return "Current workspace only";
207
+ case "anywhere":
208
+ return "Anywhere · normal Pi permissions";
209
+ }
210
+ }
211
+
212
+ export function consultResourceLabel(value: ConsultResourcePolicy): string {
213
+ switch (value) {
214
+ case "project-context":
215
+ return "Project context only";
216
+ case "none":
217
+ return "No inherited resources";
218
+ case "all":
219
+ return "All trusted resources";
220
+ }
221
+ }