@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/src/subagents.ts CHANGED
@@ -13,20 +13,41 @@
13
13
  */
14
14
 
15
15
  import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
16
- import { discoverAgentCatalog, formatAgentCatalog } from "./agents.js";
16
+ import {
17
+ type ConsultationCwdPolicy,
18
+ type ConsultResourcePolicy,
19
+ type DelegationCwdPolicy,
20
+ discoverAgentCatalog,
21
+ formatAgentCatalog,
22
+ type SubagentSettings,
23
+ } from "./agents.js";
17
24
  import { registerSubagentConfigCommand } from "./config-ui.js";
25
+ import { registerSubagentConsult } from "./consult.js";
18
26
  import { executeSubagent } from "./execution.js";
27
+ import { registerSubagentInspect } from "./inspect.js";
19
28
  import { SubagentParams } from "./params.js";
20
29
  import { renderSubagentCall, renderSubagentResult } from "./render.js";
21
30
  import type { SubagentDetails } from "./runner.js";
22
- import { consumeSubagentSettingsNotice, readSubagentSettings } from "./settings.js";
31
+ import {
32
+ consumeSubagentSettingsNotice,
33
+ DEFAULT_CONSULT_RESOURCE_POLICY,
34
+ DEFAULT_CONSULTATION_CWD_POLICY,
35
+ DEFAULT_DELEGATION_CWD_POLICY,
36
+ inspectSubagentSettings,
37
+ readSubagentSettings,
38
+ } from "./settings.js";
23
39
  import { registerStatefulSubagents } from "./stateful.js";
24
40
 
25
41
  export default function (pi: ExtensionAPI) {
26
42
  const settings = readSubagentSettings();
43
+ let currentSettings: SubagentSettings | undefined = settings;
44
+ let currentCatalog = "";
27
45
  const blockingEnabled = settings?.blocking?.enabled !== false;
28
- const refreshBlockingCatalog = blockingEnabled ? registerBlockingSubagent(pi) : () => undefined;
46
+ const refreshBlockingCatalog = blockingEnabled
47
+ ? registerBlockingSubagent(pi, () => currentSettings)
48
+ : () => undefined;
29
49
  let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
50
+ let refreshConsultCatalog: (catalog: string) => void = () => undefined;
30
51
 
31
52
  pi.on("session_start", (_event, ctx) => {
32
53
  // Preserve a one-shot migration notice from extension load while refreshing
@@ -34,43 +55,95 @@ export default function (pi: ExtensionAPI) {
34
55
  const loadNotice = consumeSubagentSettingsNotice();
35
56
  const refreshedSettings = readSubagentSettings();
36
57
  const refreshedNotice = consumeSubagentSettingsNotice();
58
+ if (!inspectSubagentSettings().error) currentSettings = refreshedSettings;
37
59
  const notice = [
38
60
  ...new Set([loadNotice, refreshedNotice].filter((value) => value !== undefined)),
39
61
  ].join("\n");
40
62
  if (notice) ctx.ui.notify(notice, "warning");
41
63
 
42
- const catalog = formatAgentCatalog(
64
+ currentCatalog = formatAgentCatalog(
43
65
  discoverAgentCatalog(ctx.cwd, ctx.isProjectTrusted(), refreshedSettings),
44
66
  ).text;
45
- refreshBlockingCatalog(catalog);
46
- refreshStatefulCatalog(catalog);
67
+ refreshBlockingCatalog(currentCatalog);
68
+ refreshStatefulCatalog(currentCatalog);
69
+ refreshConsultCatalog(currentCatalog);
47
70
  });
48
71
 
49
72
  const statefulRuntime = registerStatefulSubagents(pi, {
50
73
  blockingEnabled,
51
74
  settings: settings?.stateful,
75
+ getSettings: () => currentSettings,
52
76
  });
53
77
  refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
78
+ const getBlockingEnabled = () => blockingEnabled;
79
+ const getConsultResourcePolicy = () =>
80
+ currentSettings?.consult?.resources ?? DEFAULT_CONSULT_RESOURCE_POLICY;
81
+ const getConsultationCwdPolicy = () =>
82
+ currentSettings?.cwdPolicy?.consultation ?? DEFAULT_CONSULTATION_CWD_POLICY;
83
+ const getDelegationCwdPolicy = () =>
84
+ currentSettings?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY;
85
+ registerSubagentInspect(pi, {
86
+ ...statefulRuntime,
87
+ getBlockingEnabled,
88
+ getConsultResourcePolicy,
89
+ getConsultationCwdPolicy,
90
+ getDelegationCwdPolicy,
91
+ });
92
+ if (blockingEnabled) {
93
+ refreshConsultCatalog = registerSubagentConsult(pi, {
94
+ getSettings: () => currentSettings,
95
+ });
96
+ }
54
97
  registerSubagentConfigCommand(pi, {
55
98
  ...statefulRuntime,
56
- getBlockingEnabled: () => blockingEnabled,
99
+ getBlockingEnabled,
100
+ getConsultResourcePolicy,
101
+ getConsultationCwdPolicy,
102
+ getDelegationCwdPolicy,
103
+ setConsultResourcePolicy(value: ConsultResourcePolicy) {
104
+ currentSettings = {
105
+ ...(currentSettings ?? {}),
106
+ consult: { ...(currentSettings?.consult ?? {}), resources: value },
107
+ };
108
+ refreshConsultCatalog(currentCatalog);
109
+ },
110
+ setConsultationCwdPolicy(value: ConsultationCwdPolicy) {
111
+ currentSettings = {
112
+ ...(currentSettings ?? {}),
113
+ cwdPolicy: { ...(currentSettings?.cwdPolicy ?? {}), consultation: value },
114
+ };
115
+ refreshConsultCatalog(currentCatalog);
116
+ },
117
+ setDelegationCwdPolicy(value: DelegationCwdPolicy) {
118
+ currentSettings = {
119
+ ...(currentSettings ?? {}),
120
+ cwdPolicy: { ...(currentSettings?.cwdPolicy ?? {}), delegation: value },
121
+ };
122
+ refreshBlockingCatalog(currentCatalog);
123
+ statefulRuntime.refreshSettingsGuidance();
124
+ },
57
125
  });
58
126
  }
59
127
 
60
- function registerBlockingSubagent(pi: ExtensionAPI): (catalog: string) => void {
128
+ function registerBlockingSubagent(
129
+ pi: ExtensionAPI,
130
+ getSettings: () => SubagentSettings | undefined,
131
+ ): (catalog: string) => void {
61
132
  let catalog = "";
62
- const baseDescription = [
63
- "Run specialized subagents as a blocking operation with isolated contexts.",
64
- "The call blocks the main agent until every worker and optional aggregator finishes, so queued steering waits.",
65
- "Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
66
- "Parallel mode may include an aggregator fan-in step that receives all task outputs.",
67
- 'Default agent scope is "user" (from ~/.pi/agent/agents).',
68
- 'To enable project-local agents in .pi/agents, pass agentScope: "both" (or "project") as a top-level argument for that call.',
69
- ].join(" ");
133
+ const baseDescription = () =>
134
+ [
135
+ "Run specialized subagents as a blocking operation with isolated contexts.",
136
+ "The call blocks the main agent until every worker and optional aggregator finishes, so queued steering waits.",
137
+ "Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
138
+ "Parallel mode may include an aggregator fan-in step that receives all task outputs. Use subagent_consult instead for one synchronous child that must be executor-constrained to read-only tools.",
139
+ 'Default agent scope is "user" (from ~/.pi/agent/agents).',
140
+ 'To enable project-local agents in .pi/agents, pass agentScope: "both" (or "project") as a top-level argument for that call.',
141
+ `Working-directory target policy: ${getSettings()?.cwdPolicy?.delegation ?? DEFAULT_DELEGATION_CWD_POLICY}. This controls launch targets and protected project resources, not filesystem access or sandboxing.`,
142
+ ].join(" ");
70
143
  const definition: ToolDefinition<typeof SubagentParams, SubagentDetails> = {
71
144
  name: "subagent",
72
145
  label: "Blocking Subagent",
73
- description: appendAgentCatalog(baseDescription, catalog),
146
+ description: appendAgentCatalog(baseDescription(), catalog),
74
147
  promptSnippet:
75
148
  "Run blocking isolated subagents only when their outputs are required before the main agent can continue.",
76
149
  promptGuidelines: [
@@ -86,7 +159,7 @@ function registerBlockingSubagent(pi: ExtensionAPI): (catalog: string) => void {
86
159
  parameters: SubagentParams,
87
160
 
88
161
  async execute(toolCallId, params, signal, onUpdate, ctx) {
89
- return executeSubagent(toolCallId, params, signal, onUpdate, ctx);
162
+ return executeSubagent(toolCallId, params, signal, onUpdate, ctx, getSettings());
90
163
  },
91
164
 
92
165
  renderCall(args, theme) {
@@ -105,7 +178,7 @@ function registerBlockingSubagent(pi: ExtensionAPI): (catalog: string) => void {
105
178
  });
106
179
  return (nextCatalog: string) => {
107
180
  catalog = nextCatalog;
108
- definition.description = appendAgentCatalog(baseDescription, catalog);
181
+ definition.description = appendAgentCatalog(baseDescription(), catalog);
109
182
  pi.registerTool<typeof SubagentParams, SubagentDetails>(definition);
110
183
  };
111
184
  }
@@ -118,8 +191,14 @@ export { parsePositiveInteger } from "./execution.js";
118
191
  export { formatTokens, formatUsageStats } from "./render.js";
119
192
  export { buildPiArgs } from "./runner.js";
120
193
  export {
194
+ DEFAULT_CONSULT_RESOURCE_POLICY,
195
+ DEFAULT_CONSULTATION_CWD_POLICY,
196
+ DEFAULT_DELEGATION_CWD_POLICY,
121
197
  inspectCompletionDeliverySettings,
198
+ inspectConsultResourceSettings,
199
+ inspectCwdPolicySettings,
122
200
  inspectDelegationWorkflowSettings,
201
+ inspectSubagentSettings,
123
202
  normalizeAgentSettings,
124
203
  normalizeSubagentSettings,
125
204
  readSubagentSettings,
@@ -130,5 +209,7 @@ export {
130
209
  uniqueToolNames,
131
210
  updateAgentToolsSetting,
132
211
  updateCompletionDeliverySetting,
212
+ updateConsultResourceSetting,
213
+ updateCwdPolicySetting,
133
214
  updateDelegationWorkflowSetting,
134
215
  } from "./settings.js";
@@ -1,4 +1,9 @@
1
- import { type AgentConfig, discoverAgents, type SubagentThinkingLevel } from "./agents.js";
1
+ import {
2
+ type AgentConfig,
3
+ discoverAgents,
4
+ type SubagentSettings,
5
+ type SubagentThinkingLevel,
6
+ } from "./agents.js";
2
7
  import type { ManagedAgent, TurnOutcome } from "./registry.js";
3
8
  import { getResultFinalOutput, runSingleAgent, type SubagentDetails } from "./runner.js";
4
9
  import { readSubagentSettings, resolveSubagentThinkingLevel } from "./settings.js";
@@ -12,11 +17,17 @@ export function resolveStatefulSubprocessThinkingLevel(
12
17
  return resolveSubagentThinkingLevel(agents, record.agent, record.thinkingLevel);
13
18
  }
14
19
 
20
+ export interface SubprocessTransportOptions {
21
+ getSettings?: () => SubagentSettings | undefined;
22
+ }
23
+
15
24
  export class SubprocessTransport implements SubagentTransport {
16
25
  readonly kind = "subprocess" as const;
17
26
 
27
+ constructor(private readonly options: SubprocessTransportOptions = {}) {}
28
+
18
29
  async runTurn(record: ManagedAgent, task: string, signal: AbortSignal): Promise<TurnOutcome> {
19
- const settings = readSubagentSettings();
30
+ const settings = this.options.getSettings ? this.options.getSettings() : readSubagentSettings();
20
31
  const discovery = discoverAgents(record.cwd, record.agentScope ?? "user", settings);
21
32
  const agent = discovery.agents.find((candidate) => candidate.name === record.agent);
22
33
  const boundedTask = buildStatefulTurnPrompt(record, task);
@@ -38,6 +49,12 @@ export class SubprocessTransport implements SubagentTransport {
38
49
  resolveStatefulTurnTimeout(agent),
39
50
  undefined,
40
51
  makeDetails,
52
+ undefined,
53
+ {
54
+ projectTrust:
55
+ record.target?.trust.projectTrusted ??
56
+ (record.agentScope === "project" || record.agentScope === "both"),
57
+ },
41
58
  );
42
59
  return {
43
60
  output: getResultFinalOutput(single),