@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
package/src/config-ui.ts CHANGED
@@ -6,10 +6,42 @@ import {
6
6
  type DelegationCwdPolicy,
7
7
  discoverAgents,
8
8
  } from "./agents.js";
9
+ import {
10
+ completionLabel,
11
+ consultationCwdLabel,
12
+ consultResourceLabel,
13
+ currentWorkflow,
14
+ delegationCwdLabel,
15
+ formatManagerSummary,
16
+ helpLines,
17
+ showSubagentHelp,
18
+ showSubagentStatus,
19
+ statusLines,
20
+ } from "./config-status.js";
21
+ import {
22
+ applyAgentModel,
23
+ applyAgentThinking,
24
+ applyAgentTimeout,
25
+ applyExecutionProfileFromUi,
26
+ executionAgentPickerScreen,
27
+ executionAgentScreen,
28
+ executionModelInputScreen,
29
+ executionModelScreen,
30
+ executionProfileScreen,
31
+ executionThinkingScreen,
32
+ executionTimeoutInputScreen,
33
+ resetAgentExecution,
34
+ } from "./execution-ui.js";
35
+ import {
36
+ applyBlockingParallelLimitSetting,
37
+ blockingParallelLimitScreen,
38
+ } from "./parallel-limit-ui.js";
9
39
  import type { ManagedAgent } from "./registry.js";
40
+ import { safeTerminalLine as safeTerminalText } from "./safe-text.js";
10
41
  import {
11
42
  type DelegationWorkflow,
12
43
  hasOwn,
44
+ inspectBlockingParallelLimitSettings,
13
45
  inspectCompletionDeliverySettings,
14
46
  inspectConsultResourceSettings,
15
47
  inspectCwdPolicySettings,
@@ -24,6 +56,20 @@ import {
24
56
  updateDelegationWorkflowSetting,
25
57
  } from "./settings.js";
26
58
  import { formatStatefulAgentLine, type StatefulSubagentRuntimeStatus } from "./stateful.js";
59
+ import {
60
+ applyStatefulLimitSetting,
61
+ formatDetachedLimitSummary,
62
+ formatEmptyStatefulRuntime,
63
+ statefulLimitInputScreen,
64
+ statefulLimitListScreen,
65
+ } from "./stateful-limit-ui.js";
66
+ import { isStatefulLimitField, type StatefulLimitField } from "./stateful-limits.js";
67
+ import {
68
+ applyTransportSetting,
69
+ responsivenessSetupScreen,
70
+ transportSettingsScreen,
71
+ } from "./transport-ui.js";
72
+ import { showWorkflowPreview, workflowLabel } from "./workflow-ui.js";
27
73
 
28
74
  const SUBCOMMANDS = [
29
75
  { value: "settings", label: "settings", description: "Configure subagent user settings" },
@@ -34,10 +80,12 @@ const TOOL_VIEWPORT_SIZE = 10;
34
80
 
35
81
  export interface SubagentSettingsRuntime {
36
82
  getBlockingEnabled(): boolean;
83
+ getMaxParallelTasks(): number;
37
84
  getCompletionDelivery(): CompletionDelivery;
38
85
  getConsultResourcePolicy(): ConsultResourcePolicy;
39
86
  getConsultationCwdPolicy(): ConsultationCwdPolicy;
40
87
  getDelegationCwdPolicy(): DelegationCwdPolicy;
88
+ setMaxParallelTasks(value: number): void;
41
89
  setCompletionDelivery(value: CompletionDelivery): void;
42
90
  setConsultResourcePolicy(value: ConsultResourcePolicy): void;
43
91
  setConsultationCwdPolicy(value: ConsultationCwdPolicy): void;
@@ -47,7 +95,7 @@ export interface SubagentSettingsRuntime {
47
95
  clearAgents(): Promise<number>;
48
96
  }
49
97
 
50
- interface MenuOwner {
98
+ export interface SubagentMenuOwner {
51
99
  generation: number;
52
100
  controller: AbortController;
53
101
  }
@@ -61,8 +109,8 @@ interface ToolDraft {
61
109
  selected: Set<string>;
62
110
  }
63
111
 
64
- export function registerSubagentConfigCommand(pi: ExtensionAPI, runtime: SubagentSettingsRuntime) {
65
- const owner: MenuOwner = { generation: 0, controller: new AbortController() };
112
+ export function registerSubagentConfigLifecycle(pi: ExtensionAPI): SubagentMenuOwner {
113
+ const owner: SubagentMenuOwner = { generation: 0, controller: new AbortController() };
66
114
  pi.on("session_start", () => {
67
115
  owner.generation += 1;
68
116
  owner.controller.abort(new DOMException("Subagent session replaced", "AbortError"));
@@ -72,13 +120,21 @@ export function registerSubagentConfigCommand(pi: ExtensionAPI, runtime: Subagen
72
120
  owner.generation += 1;
73
121
  owner.controller.abort(new DOMException("Subagent session shut down", "AbortError"));
74
122
  });
123
+ return owner;
124
+ }
125
+
126
+ export function registerSubagentConfigCommand(
127
+ pi: ExtensionAPI,
128
+ runtime: SubagentSettingsRuntime,
129
+ owner = registerSubagentConfigLifecycle(pi),
130
+ ) {
75
131
  registerSubagentPrimaryCommand(pi, runtime, owner);
76
132
  }
77
133
 
78
134
  function registerSubagentPrimaryCommand(
79
135
  pi: ExtensionAPI,
80
136
  runtime: SubagentSettingsRuntime,
81
- owner: MenuOwner,
137
+ owner: SubagentMenuOwner,
82
138
  ) {
83
139
  pi.registerCommand("subagents", {
84
140
  description: "Manage current-session subagents and user settings",
@@ -116,7 +172,7 @@ async function showSubagentManager(
116
172
  pi: ExtensionAPI,
117
173
  ctx: ExtensionCommandContext,
118
174
  runtime: SubagentSettingsRuntime,
119
- owner: MenuOwner,
175
+ owner: SubagentMenuOwner,
120
176
  ) {
121
177
  if (ctx.mode !== "tui") {
122
178
  showSubagentStatus(ctx, runtime);
@@ -128,12 +184,27 @@ async function showSubagentManager(
128
184
  if (!isCurrent()) return;
129
185
  let availableAgents = discoverAgents(ctx.cwd, "user", readSubagentSettings() ?? {}).agents;
130
186
  let toolDraft: ToolDraft | undefined;
187
+ let selectedExecutionAgent: (typeof availableAgents)[number] | undefined;
188
+ let selectedStatefulLimit: StatefulLimitField = "maxAgents";
131
189
  type Screen =
132
190
  | "main"
133
191
  | "workflow"
134
192
  | "agents"
135
193
  | "settings"
136
194
  | "advanced"
195
+ | "performance"
196
+ | "responsiveness"
197
+ | "transport"
198
+ | "execution-profiles"
199
+ | "execution-agent-picker"
200
+ | "execution-agent"
201
+ | "execution-thinking"
202
+ | "execution-model"
203
+ | "execution-model-input"
204
+ | "execution-timeout"
205
+ | "parallel-limit"
206
+ | "stateful-limits"
207
+ | "stateful-limit-input"
137
208
  | "status"
138
209
  | "help"
139
210
  | "agent-picker"
@@ -141,6 +212,16 @@ async function showSubagentManager(
141
212
  type Action =
142
213
  | "set-workflow"
143
214
  | "clear-agents"
215
+ | "set-transport"
216
+ | "apply-execution-profile"
217
+ | "pick-execution-agent"
218
+ | "set-agent-thinking"
219
+ | "set-agent-model"
220
+ | "set-agent-timeout"
221
+ | "reset-agent-execution"
222
+ | "set-parallel-limit"
223
+ | "pick-stateful-limit"
224
+ | "set-stateful-limit"
144
225
  | "set-completion"
145
226
  | "set-consult-resources"
146
227
  | "set-consultation-cwd"
@@ -183,7 +264,7 @@ async function showSubagentManager(
183
264
  {
184
265
  id: "advanced",
185
266
  label: "Advanced settings",
186
- description: "Agent permissions, runtime details, and settings path",
267
+ description: "Agent permissions, parallel limit, runtime details, and settings path",
187
268
  to: "advanced",
188
269
  },
189
270
  { id: "help", label: "Help", to: "help" },
@@ -240,7 +321,9 @@ async function showSubagentManager(
240
321
  return {
241
322
  kind: "actions",
242
323
  title: "Current-session Subagents",
243
- lines: agents.length ? agents.map(formatStatefulAgentLine) : [formatEmptyRuntime(status)],
324
+ lines: agents.length
325
+ ? agents.map(formatStatefulAgentLine)
326
+ : [formatEmptyStatefulRuntime(status)],
244
327
  items: [
245
328
  ...(agents.length > 0
246
329
  ? [
@@ -258,26 +341,85 @@ async function showSubagentManager(
258
341
  };
259
342
  },
260
343
  settings: () => subagentSettingsScreen(runtime),
261
- advanced: () => ({
344
+ advanced: () => {
345
+ const limit = inspectBlockingParallelLimitSettings();
346
+ return {
347
+ kind: "actions",
348
+ title: "Advanced Subagent Settings",
349
+ items: [
350
+ {
351
+ id: "agent-tools",
352
+ label: "Agent tool permissions",
353
+ description: "Customize persistent per-agent tool allow-lists",
354
+ action: "load-agent-picker",
355
+ },
356
+ {
357
+ id: "status",
358
+ label: "Runtime details",
359
+ description: "Show transport, configured source, and settings path",
360
+ to: "status",
361
+ },
362
+ {
363
+ id: "parallel-limit",
364
+ label: "Maximum parallel workers",
365
+ description: `Current: ${runtime.getMaxParallelTasks()} per blocking call`,
366
+ to: "parallel-limit",
367
+ disabled: limit.error !== undefined,
368
+ disabledReason: limit.error
369
+ ? `Repair ${safeTerminalText(limit.path)} before editing this setting`
370
+ : undefined,
371
+ },
372
+ {
373
+ id: "stateful-limits",
374
+ label: "Detached agent limits",
375
+ description: formatDetachedLimitSummary(runtime.getRuntimeStatus()),
376
+ to: "stateful-limits",
377
+ },
378
+ {
379
+ id: "performance",
380
+ label: "Performance and execution",
381
+ description: "Transport, responsiveness, profiles, and agent defaults",
382
+ to: "performance",
383
+ },
384
+ { id: "back", label: "Back", action: "back" },
385
+ ],
386
+ hint: "back",
387
+ };
388
+ },
389
+ performance: () => ({
262
390
  kind: "actions",
263
- title: "Advanced Subagent Settings",
391
+ title: "Performance and Execution",
264
392
  items: [
265
393
  {
266
- id: "agent-tools",
267
- label: "Agent tool permissions",
268
- description: "Customize persistent per-agent tool allow-lists",
269
- action: "load-agent-picker",
394
+ id: "responsiveness",
395
+ label: "Responsiveness setup",
396
+ description: "Preview automatic retained transport and completion guidance",
397
+ to: "responsiveness",
270
398
  },
399
+ { id: "transport", label: "Detached transport", to: "transport" },
400
+ { id: "profiles", label: "Execution profiles", to: "execution-profiles" },
271
401
  {
272
- id: "status",
273
- label: "Runtime details",
274
- description: "Show transport, configured source, and settings path",
275
- to: "status",
402
+ id: "agents",
403
+ label: "Agent execution defaults",
404
+ description: "Model, thinking level, and timeout",
405
+ to: "execution-agent-picker",
276
406
  },
277
407
  { id: "back", label: "Back", action: "back" },
278
408
  ],
279
409
  hint: "back",
280
410
  }),
411
+ responsiveness: () => responsivenessSetupScreen(runtime),
412
+ transport: () => transportSettingsScreen(runtime),
413
+ "execution-profiles": () => executionProfileScreen(),
414
+ "execution-agent-picker": () => executionAgentPickerScreen(availableAgents),
415
+ "execution-agent": () => executionAgentScreen(selectedExecutionAgent),
416
+ "execution-thinking": () => executionThinkingScreen(selectedExecutionAgent),
417
+ "execution-model": () => executionModelScreen(selectedExecutionAgent, ctx),
418
+ "execution-model-input": () => executionModelInputScreen(selectedExecutionAgent),
419
+ "execution-timeout": () => executionTimeoutInputScreen(selectedExecutionAgent),
420
+ "parallel-limit": () => blockingParallelLimitScreen(runtime),
421
+ "stateful-limits": () => statefulLimitListScreen(runtime),
422
+ "stateful-limit-input": () => statefulLimitInputScreen(selectedStatefulLimit, runtime),
281
423
  status: () => ({
282
424
  kind: "detail",
283
425
  title: "Subagent runtime details",
@@ -351,7 +493,7 @@ async function showSubagentManager(
351
493
  }),
352
494
  },
353
495
  actions: {
354
- "set-workflow": async ({ itemId }) => {
496
+ "set-workflow": async ({ itemId, signal }) => {
355
497
  if (!isWorkflow(itemId)) return { kind: "rejected" };
356
498
  const snapshot = inspectDelegationWorkflowSettings();
357
499
  if (snapshot.error) return { kind: "rejected" };
@@ -364,9 +506,10 @@ async function showSubagentManager(
364
506
  if (requiresReload && blockReloadWithRetainedAgents(ctx, runtime)) {
365
507
  return { kind: "rejected" };
366
508
  }
367
- if (!(await showWorkflowPreview(ctx, active, itemId, requiresReload))) {
368
- return { kind: "rejected" };
509
+ if (!(await showWorkflowPreview(ctx, active, itemId, requiresReload, signal))) {
510
+ return signal.aborted || !isCurrent() ? { kind: "close" } : { kind: "rejected" };
369
511
  }
512
+ if (signal.aborted || !isCurrent()) return { kind: "close" };
370
513
  if (requiresReload && blockReloadWithRetainedAgents(ctx, runtime)) {
371
514
  return { kind: "rejected" };
372
515
  }
@@ -393,21 +536,71 @@ async function showSubagentManager(
393
536
  await ctx.reload();
394
537
  return { kind: "close" };
395
538
  },
396
- "clear-agents": async () => {
539
+ "clear-agents": async ({ signal }) => {
397
540
  const agents = runtime.listAgents();
398
541
  if (agents.length === 0) return { kind: "stay" };
399
542
  const confirmed = await ctx.ui.confirm(
400
543
  "Clear current-session subagents?",
401
544
  `Close and delete ${agents.length} retained agent${agents.length === 1 ? "" : "s"}?`,
545
+ { signal },
402
546
  );
547
+ if (signal.aborted || !isCurrent()) return { kind: "close" };
403
548
  if (!confirmed) return { kind: "rejected" };
549
+ if (
550
+ runtime
551
+ .listAgents()
552
+ .map((agent) => agent.id)
553
+ .join("\0") !== agents.map((agent) => agent.id).join("\0")
554
+ ) {
555
+ ctx.ui.notify(
556
+ "Detached agents changed while confirming; review the list again.",
557
+ "warning",
558
+ );
559
+ return { kind: "rejected" };
560
+ }
404
561
  const cleared = await runtime.clearAgents();
562
+ if (signal.aborted || !isCurrent()) return { kind: "close" };
405
563
  ctx.ui.notify(
406
564
  `Cleared ${cleared} current-session subagent${cleared === 1 ? "" : "s"}.`,
407
565
  "info",
408
566
  );
409
567
  return { kind: "stay" };
410
568
  },
569
+ "set-transport": async ({ itemId, signal }) =>
570
+ applyTransportSetting(itemId, ctx, runtime, signal, isCurrent),
571
+ "apply-execution-profile": async ({ itemId, signal }) =>
572
+ applyExecutionProfileFromUi(itemId, ctx, signal, isCurrent),
573
+ "pick-execution-agent": async ({ itemId }) => {
574
+ selectedExecutionAgent = availableAgents.find((agent) => agent.name === itemId);
575
+ return selectedExecutionAgent
576
+ ? { kind: "to", screen: "execution-agent" as const }
577
+ : { kind: "rejected" as const };
578
+ },
579
+ "set-agent-thinking": async ({ value }) =>
580
+ applyAgentThinking(selectedExecutionAgent, value, ctx),
581
+ "set-agent-model": async ({ itemId, value }) => {
582
+ const selected = value ?? (itemId.startsWith("model:") ? itemId.slice(6) : undefined);
583
+ return applyAgentModel(
584
+ selectedExecutionAgent,
585
+ selected === "__inherited__" ? undefined : selected,
586
+ ctx,
587
+ );
588
+ },
589
+ "set-agent-timeout": async ({ value }) =>
590
+ applyAgentTimeout(selectedExecutionAgent, value, ctx),
591
+ "reset-agent-execution": async () => resetAgentExecution(selectedExecutionAgent, ctx),
592
+ "set-parallel-limit": async ({ value }) =>
593
+ applyBlockingParallelLimitSetting(value, ctx, runtime),
594
+ "pick-stateful-limit": async ({ itemId }) => {
595
+ if (!isStatefulLimitField(itemId)) return { kind: "rejected" };
596
+ selectedStatefulLimit = itemId;
597
+ return { kind: "to", screen: "stateful-limit-input" };
598
+ },
599
+ "set-stateful-limit": async ({ value, signal }) =>
600
+ applyStatefulLimitSetting(selectedStatefulLimit, value, ctx, runtime, {
601
+ signal,
602
+ isCurrent,
603
+ }),
411
604
  "set-completion": async ({ value }) => applyCompletionSetting(value, ctx, runtime),
412
605
  "set-consult-resources": async ({ value }) =>
413
606
  applyConsultResourceSetting(value, ctx, runtime),
@@ -491,7 +684,7 @@ async function showSubagentManager(
491
684
  async function showSubagentSettings(
492
685
  ctx: ExtensionCommandContext,
493
686
  runtime: SubagentSettingsRuntime,
494
- owner: MenuOwner,
687
+ owner: SubagentMenuOwner,
495
688
  ) {
496
689
  const snapshot = inspectConsultResourceSettings();
497
690
  if (ctx.mode !== "tui") {
@@ -693,224 +886,10 @@ function blockReloadWithRetainedAgents(
693
886
  return true;
694
887
  }
695
888
 
696
- async function showWorkflowPreview(
697
- ctx: ExtensionCommandContext,
698
- current: DelegationWorkflow,
699
- next: DelegationWorkflow,
700
- requiresReload: boolean,
701
- ): Promise<boolean> {
702
- const changes = workflowEffects(current, next);
703
- return ctx.ui.confirm(
704
- requiresReload ? "Save delegation change and reload?" : "Save delegation change?",
705
- [
706
- `Current: ${workflowLabel(current)}`,
707
- `New: ${workflowLabel(next)}`,
708
- "",
709
- "Effect:",
710
- ...(changes.length > 0 ? changes : ["Keep the current registered tools"]).map(
711
- (effect) => `- ${effect}`,
712
- ),
713
- `- ${requiresReload ? "Reload the extension to apply this tool surface" : "No reload is needed because the active tools already match"}`,
714
- ].join("\n"),
715
- );
716
- }
717
-
718
- function showSubagentStatus(ctx: ExtensionCommandContext, runtime: SubagentSettingsRuntime) {
719
- if (ctx.mode !== "tui" && !ctx.hasUI) return;
720
- const snapshot = inspectCompletionDeliverySettings();
721
- ctx.ui.notify(
722
- formatStatus(runtime.getRuntimeStatus(), snapshot, runtime),
723
- snapshot.error ? "warning" : "info",
724
- );
725
- }
726
-
727
- function showSubagentHelp(ctx: ExtensionCommandContext, runtime: SubagentSettingsRuntime) {
728
- if (ctx.mode !== "tui" && !ctx.hasUI) return;
729
- ctx.ui.notify(helpLines(runtime).join("\n"), "info");
730
- }
731
-
732
- function statusLines(runtime: SubagentSettingsRuntime): string[] {
733
- const snapshot = inspectCompletionDeliverySettings();
734
- return formatStatus(runtime.getRuntimeStatus(), snapshot, runtime).split("\n");
735
- }
736
-
737
- function helpLines(runtime: SubagentSettingsRuntime): string[] {
738
- const snapshot = inspectCompletionDeliverySettings();
739
- const cwdPolicy = inspectCwdPolicySettings();
740
- return [
741
- "/subagents — choose delegation workflow, manage current agents, and configure agent tools",
742
- "/subagents settings — configure target locations, trusted resources, and async completion",
743
- "/subagents status — show current-session and user-setting values",
744
- "/subagents help — show this help",
745
- "Target policies control startup directories and resources, not filesystem access or sandboxing.",
746
- "Manage saved folder trust with Pi /trust and restart Pi after changing it.",
747
- `Runtime consultation target: ${consultationCwdLabel(runtime.getConsultationCwdPolicy())}`,
748
- `Configured consultation target: ${consultationCwdLabel(cwdPolicy.consultation.value)} (${cwdPolicy.consultation.source})`,
749
- `Runtime delegation target: ${delegationCwdLabel(runtime.getDelegationCwdPolicy())}`,
750
- `Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)} (${cwdPolicy.delegation.source})`,
751
- `User settings: ${safeTerminalText(snapshot.path)}`,
752
- ];
753
- }
754
-
755
- function formatManagerSummary(
756
- runtime: SubagentSettingsRuntime,
757
- status: StatefulSubagentRuntimeStatus,
758
- configured: ReturnType<typeof inspectDelegationWorkflowSettings>,
759
- ): string {
760
- const current = currentWorkflow(runtime, status);
761
- const cwdPolicy = inspectCwdPolicySettings();
762
- const consult = inspectConsultResourceSettings();
763
- return [
764
- `Delegation: ${workflowLabel(current)}`,
765
- `Completion: ${completionLabel(status.completionDelivery)}`,
766
- `Consult target: ${consultationCwdLabel(runtime.getConsultationCwdPolicy())}`,
767
- `Delegation target: ${delegationCwdLabel(runtime.getDelegationCwdPolicy())}`,
768
- `Consult resources: ${consultResourceLabel(runtime.getConsultResourcePolicy())}`,
769
- `Configured consult target: ${consultationCwdLabel(cwdPolicy.consultation.value)} · ${cwdPolicy.consultation.source}`,
770
- `Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)} · ${cwdPolicy.delegation.source}`,
771
- `Configured consult resources: ${consultResourceLabel(consult.value)} · ${consult.source}`,
772
- `Settings: ${safeTerminalText(cwdPolicy.path)}`,
773
- `Agents: ${status.activeAgents} active · ${status.retainedAgents} retained`,
774
- ...(configured.value !== current
775
- ? [`Configured after reload: ${workflowLabel(configured.value)}`]
776
- : []),
777
- ...(configured.error ? ["Settings need repair; open Advanced settings for details."] : []),
778
- ].join("\n");
779
- }
780
-
781
- function formatStatus(
782
- status: StatefulSubagentRuntimeStatus,
783
- snapshot: ReturnType<typeof inspectCompletionDeliverySettings>,
784
- runtime?: SubagentSettingsRuntime,
785
- ): string {
786
- const configuredWorkflow = inspectDelegationWorkflowSettings();
787
- const consult = inspectConsultResourceSettings();
788
- const cwdPolicy = inspectCwdPolicySettings();
789
- const current = runtime ? currentWorkflow(runtime, status) : configuredWorkflow.value;
790
- return [
791
- "Current session",
792
- ` Delegation: ${workflowLabel(current)}`,
793
- ` Async runtime: ${status.initialized ? "initialized" : status.enabled ? "not initialized" : "disabled"}`,
794
- ` Transport: ${status.transport}`,
795
- ` Completion: ${completionLabel(status.completionDelivery)}`,
796
- ` Consultation target: ${consultationCwdLabel(runtime?.getConsultationCwdPolicy() ?? cwdPolicy.consultation.value)}`,
797
- ` Delegation target: ${delegationCwdLabel(runtime?.getDelegationCwdPolicy() ?? cwdPolicy.delegation.value)}`,
798
- ` Consultation resources: ${consultResourceLabel(runtime?.getConsultResourcePolicy() ?? consult.value)}`,
799
- ` Agents: ${status.activeAgents} active, ${status.retainedAgents} retained`,
800
- "User settings",
801
- ` Delegation source: ${configuredWorkflow.source}`,
802
- ` Configured delegation: ${workflowLabel(configuredWorkflow.value)}`,
803
- ` Completion source: ${snapshot.source}`,
804
- ` Configured completion: ${completionLabel(snapshot.value)}`,
805
- ` Configured consultation target: ${consultationCwdLabel(cwdPolicy.consultation.value)}`,
806
- ` Consultation target source: ${cwdPolicy.consultation.source}`,
807
- ` Configured delegation target: ${delegationCwdLabel(cwdPolicy.delegation.value)}`,
808
- ` Delegation target source: ${cwdPolicy.delegation.source}`,
809
- ` Configured consultation resources: ${consultResourceLabel(consult.value)}`,
810
- ` Consultation resource source: ${consult.source}`,
811
- ` Path: ${safeTerminalText(snapshot.path)}`,
812
- configuredWorkflow.error || snapshot.error || cwdPolicy.error
813
- ? ` Warning: ${safeTerminalText(configuredWorkflow.error ?? snapshot.error ?? cwdPolicy.error ?? "invalid settings")}`
814
- : " Warning: none",
815
- configuredWorkflow.value !== current
816
- ? "Configured delegation differs from this session. Run /reload to apply it."
817
- : "Manual file changes require /reload.",
818
- ].join("\n");
819
- }
820
-
821
- function formatEmptyRuntime(status: StatefulSubagentRuntimeStatus): string {
822
- if (!status.enabled) return "Stateful subagents are disabled in user settings.";
823
- if (!status.initialized) return "Stateful subagents are not initialized for this session.";
824
- return "No current-session subagents.";
825
- }
826
-
827
- function currentWorkflow(
828
- runtime: SubagentSettingsRuntime,
829
- status: StatefulSubagentRuntimeStatus,
830
- ): DelegationWorkflow {
831
- const blocking = runtime.getBlockingEnabled();
832
- if (blocking && status.enabled) return "all";
833
- if (status.enabled) return "async-only";
834
- if (blocking) return "blocking-only";
835
- return "disabled";
836
- }
837
-
838
889
  function isWorkflow(value: string): value is Exclude<DelegationWorkflow, "disabled"> {
839
890
  return value === "all" || value === "async-only" || value === "blocking-only";
840
891
  }
841
892
 
842
- function workflowLabel(value: DelegationWorkflow): string {
843
- switch (value) {
844
- case "all":
845
- return "All delegation methods";
846
- case "async-only":
847
- return "Async only";
848
- case "blocking-only":
849
- return "Blocking only";
850
- case "disabled":
851
- return "Delegation disabled";
852
- }
853
- }
854
-
855
- function completionLabel(value: CompletionDelivery): string {
856
- return value === "auto-resume" ? "Resume automatically when finished" : "Wait until my next turn";
857
- }
858
-
859
- function consultationCwdLabel(value: ConsultationCwdPolicy): string {
860
- return value === "current-workspace"
861
- ? "Current workspace only"
862
- : "Anywhere · untrusted targets inherit nothing";
863
- }
864
-
865
- function delegationCwdLabel(value: DelegationCwdPolicy): string {
866
- switch (value) {
867
- case "trusted-targets":
868
- return "Current or saved-trusted folders";
869
- case "current-workspace":
870
- return "Current workspace only";
871
- case "anywhere":
872
- return "Anywhere · normal Pi permissions";
873
- }
874
- }
875
-
876
- function consultResourceLabel(value: ConsultResourcePolicy): string {
877
- switch (value) {
878
- case "project-context":
879
- return "Project context only";
880
- case "none":
881
- return "No inherited resources";
882
- case "all":
883
- return "All trusted resources";
884
- }
885
- }
886
-
887
- function workflowEffects(current: DelegationWorkflow, next: DelegationWorkflow): string[] {
888
- const blockingEnabled = (value: DelegationWorkflow) =>
889
- value === "all" || value === "blocking-only";
890
- const asyncEnabled = (value: DelegationWorkflow) => value === "all" || value === "async-only";
891
- const effects: string[] = [];
892
- if (blockingEnabled(current) !== blockingEnabled(next)) {
893
- effects.push(
894
- blockingEnabled(next)
895
- ? "Add blocking `subagent` and read-only `subagent_consult`"
896
- : "Remove blocking `subagent` and read-only `subagent_consult`",
897
- );
898
- }
899
- if (asyncEnabled(current) !== asyncEnabled(next)) {
900
- effects.push(
901
- asyncEnabled(next)
902
- ? "Add reusable async lifecycle tools"
903
- : "Remove reusable async lifecycle tools",
904
- );
905
- }
906
- return effects;
907
- }
908
-
909
- function safeTerminalText(value: string): string {
910
- // biome-ignore lint/suspicious/noControlCharactersInRegex: Escape untrusted terminal controls.
911
- return value.replace(/[\u0000-\u001f\u007f-\u009f]/gu, "?");
912
- }
913
-
914
893
  function formatError(error: unknown): string {
915
894
  return safeTerminalText(error instanceof Error ? error.message : String(error));
916
895
  }
@@ -1,11 +1,6 @@
1
- import {
2
- DefaultResourceLoader,
3
- getAgentDir,
4
- SettingsManager,
5
- } from "@earendil-works/pi-coding-agent";
6
1
  import type { ConsultResourcePolicy } from "./agents.js";
7
2
  import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
8
- import { assertPiPromptSourcesAreReadableFiles } from "./prompt-source-safety.js";
3
+ import { resolvePiPromptResources } from "./prompt-resources.js";
9
4
  import type { ChildLaunchPolicy } from "./runner.js";
10
5
 
11
6
  const MINIMAL_CONSULT_SYSTEM_PROMPT =
@@ -31,30 +26,12 @@ export async function resolveConsultResourceLaunchPolicy(
31
26
  };
32
27
  }
33
28
 
34
- const agentDir = getAgentDir();
35
- assertPiPromptSourcesAreReadableFiles(cwd, agentDir, projectTrusted, [
36
- "SYSTEM.md",
37
- "APPEND_SYSTEM.md",
38
- ]);
39
- const loader = new DefaultResourceLoader({
40
- cwd,
41
- agentDir,
42
- settingsManager: SettingsManager.inMemory({}, { projectTrusted }),
43
- noExtensions: true,
44
- noSkills: true,
45
- noPromptTemplates: true,
46
- noThemes: true,
47
- noContextFiles: true,
48
- });
49
- await loader.reload();
50
-
51
- const discoveredSystemPrompt = loader.getSystemPrompt();
29
+ const resources = await resolvePiPromptResources(cwd, projectTrusted);
30
+ const discoveredSystemPrompt = resources.systemPrompt;
52
31
  const baseSystemPrompt = discoveredSystemPrompt
53
32
  ? truncateUtf8(discoveredSystemPrompt, DEFAULT_MAX_CONTEXT_BYTES).text
54
33
  : undefined;
55
- const appendSystemPromptPaths = loader
56
- .getAppendSystemPromptSources()
57
- .map((source) => source.path);
34
+ const appendSystemPromptPaths = resources.appendSystemPromptPaths;
58
35
  const shared = {
59
36
  disableExtensions: true,
60
37
  disableContextFiles: !projectTrusted,
package/src/consult.ts CHANGED
@@ -61,7 +61,14 @@ export const SubagentConsultParams = Type.Object(
61
61
  agentScope: Type.Optional(ConsultScopeSchema),
62
62
  confirmProjectAgents: Type.Optional(Type.Boolean({ default: true })),
63
63
  cwd: Type.Optional(Type.String({ minLength: 1 })),
64
- timeoutMs: Type.Optional(Type.Number({ minimum: 1, maximum: MAX_SUBAGENT_TIMEOUT_MS })),
64
+ timeoutMs: Type.Optional(
65
+ Type.Number({
66
+ minimum: 1,
67
+ maximum: MAX_SUBAGENT_TIMEOUT_MS,
68
+ description:
69
+ "Work deadline selected for the consultation difficulty. On expiry, Pi aborts the work and makes one separately bounded summary attempt.",
70
+ }),
71
+ ),
65
72
  thinkingLevel: Type.Optional(ConsultThinkingSchema),
66
73
  },
67
74
  { additionalProperties: false },
@@ -191,6 +198,7 @@ export function registerSubagentConsult(
191
198
  promptSnippet: "Consult one constrained read-only subagent and wait for its answer",
192
199
  promptGuidelines: [
193
200
  "Use subagent_consult for bounded reconnaissance, planning, or review whose result is required in the current turn.",
201
+ "Set subagent_consult timeoutMs to the shortest realistic work deadline for the task difficulty; split oversized consultations instead of extending the deadline merely to compensate for broad scope.",
194
202
  "Implementation-shaped tasks remain read-only and can return only analysis or instructions.",
195
203
  ],
196
204
  parameters: SubagentConsultParams,