@narumitw/pi-subagents 0.43.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/subagents.ts CHANGED
@@ -14,7 +14,9 @@
14
14
 
15
15
  import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
16
16
  import {
17
+ type ConsultationCwdPolicy,
17
18
  type ConsultResourcePolicy,
19
+ type DelegationCwdPolicy,
18
20
  discoverAgentCatalog,
19
21
  formatAgentCatalog,
20
22
  type SubagentSettings,
@@ -29,6 +31,9 @@ import type { SubagentDetails } from "./runner.js";
29
31
  import {
30
32
  consumeSubagentSettingsNotice,
31
33
  DEFAULT_CONSULT_RESOURCE_POLICY,
34
+ DEFAULT_CONSULTATION_CWD_POLICY,
35
+ DEFAULT_DELEGATION_CWD_POLICY,
36
+ inspectSubagentSettings,
32
37
  readSubagentSettings,
33
38
  } from "./settings.js";
34
39
  import { registerStatefulSubagents } from "./stateful.js";
@@ -36,8 +41,11 @@ import { registerStatefulSubagents } from "./stateful.js";
36
41
  export default function (pi: ExtensionAPI) {
37
42
  const settings = readSubagentSettings();
38
43
  let currentSettings: SubagentSettings | undefined = settings;
44
+ let currentCatalog = "";
39
45
  const blockingEnabled = settings?.blocking?.enabled !== false;
40
- const refreshBlockingCatalog = blockingEnabled ? registerBlockingSubagent(pi) : () => undefined;
46
+ const refreshBlockingCatalog = blockingEnabled
47
+ ? registerBlockingSubagent(pi, () => currentSettings)
48
+ : () => undefined;
41
49
  let refreshStatefulCatalog: (catalog: string) => void = () => undefined;
42
50
  let refreshConsultCatalog: (catalog: string) => void = () => undefined;
43
51
 
@@ -46,33 +54,40 @@ export default function (pi: ExtensionAPI) {
46
54
  // validation against settings that may have changed before this session.
47
55
  const loadNotice = consumeSubagentSettingsNotice();
48
56
  const refreshedSettings = readSubagentSettings();
49
- currentSettings = refreshedSettings;
50
57
  const refreshedNotice = consumeSubagentSettingsNotice();
58
+ if (!inspectSubagentSettings().error) currentSettings = refreshedSettings;
51
59
  const notice = [
52
60
  ...new Set([loadNotice, refreshedNotice].filter((value) => value !== undefined)),
53
61
  ].join("\n");
54
62
  if (notice) ctx.ui.notify(notice, "warning");
55
63
 
56
- const catalog = formatAgentCatalog(
64
+ currentCatalog = formatAgentCatalog(
57
65
  discoverAgentCatalog(ctx.cwd, ctx.isProjectTrusted(), refreshedSettings),
58
66
  ).text;
59
- refreshBlockingCatalog(catalog);
60
- refreshStatefulCatalog(catalog);
61
- refreshConsultCatalog(catalog);
67
+ refreshBlockingCatalog(currentCatalog);
68
+ refreshStatefulCatalog(currentCatalog);
69
+ refreshConsultCatalog(currentCatalog);
62
70
  });
63
71
 
64
72
  const statefulRuntime = registerStatefulSubagents(pi, {
65
73
  blockingEnabled,
66
74
  settings: settings?.stateful,
75
+ getSettings: () => currentSettings,
67
76
  });
68
77
  refreshStatefulCatalog = statefulRuntime.setAgentCatalog;
69
78
  const getBlockingEnabled = () => blockingEnabled;
70
79
  const getConsultResourcePolicy = () =>
71
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;
72
85
  registerSubagentInspect(pi, {
73
86
  ...statefulRuntime,
74
87
  getBlockingEnabled,
75
88
  getConsultResourcePolicy,
89
+ getConsultationCwdPolicy,
90
+ getDelegationCwdPolicy,
76
91
  });
77
92
  if (blockingEnabled) {
78
93
  refreshConsultCatalog = registerSubagentConsult(pi, {
@@ -83,29 +98,52 @@ export default function (pi: ExtensionAPI) {
83
98
  ...statefulRuntime,
84
99
  getBlockingEnabled,
85
100
  getConsultResourcePolicy,
101
+ getConsultationCwdPolicy,
102
+ getDelegationCwdPolicy,
86
103
  setConsultResourcePolicy(value: ConsultResourcePolicy) {
87
104
  currentSettings = {
88
105
  ...(currentSettings ?? {}),
89
106
  consult: { ...(currentSettings?.consult ?? {}), resources: value },
90
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();
91
124
  },
92
125
  });
93
126
  }
94
127
 
95
- function registerBlockingSubagent(pi: ExtensionAPI): (catalog: string) => void {
128
+ function registerBlockingSubagent(
129
+ pi: ExtensionAPI,
130
+ getSettings: () => SubagentSettings | undefined,
131
+ ): (catalog: string) => void {
96
132
  let catalog = "";
97
- const baseDescription = [
98
- "Run specialized subagents as a blocking operation with isolated contexts.",
99
- "The call blocks the main agent until every worker and optional aggregator finishes, so queued steering waits.",
100
- "Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
101
- "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.",
102
- 'Default agent scope is "user" (from ~/.pi/agent/agents).',
103
- 'To enable project-local agents in .pi/agents, pass agentScope: "both" (or "project") as a top-level argument for that call.',
104
- ].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(" ");
105
143
  const definition: ToolDefinition<typeof SubagentParams, SubagentDetails> = {
106
144
  name: "subagent",
107
145
  label: "Blocking Subagent",
108
- description: appendAgentCatalog(baseDescription, catalog),
146
+ description: appendAgentCatalog(baseDescription(), catalog),
109
147
  promptSnippet:
110
148
  "Run blocking isolated subagents only when their outputs are required before the main agent can continue.",
111
149
  promptGuidelines: [
@@ -121,7 +159,7 @@ function registerBlockingSubagent(pi: ExtensionAPI): (catalog: string) => void {
121
159
  parameters: SubagentParams,
122
160
 
123
161
  async execute(toolCallId, params, signal, onUpdate, ctx) {
124
- return executeSubagent(toolCallId, params, signal, onUpdate, ctx);
162
+ return executeSubagent(toolCallId, params, signal, onUpdate, ctx, getSettings());
125
163
  },
126
164
 
127
165
  renderCall(args, theme) {
@@ -140,7 +178,7 @@ function registerBlockingSubagent(pi: ExtensionAPI): (catalog: string) => void {
140
178
  });
141
179
  return (nextCatalog: string) => {
142
180
  catalog = nextCatalog;
143
- definition.description = appendAgentCatalog(baseDescription, catalog);
181
+ definition.description = appendAgentCatalog(baseDescription(), catalog);
144
182
  pi.registerTool<typeof SubagentParams, SubagentDetails>(definition);
145
183
  };
146
184
  }
@@ -154,8 +192,11 @@ export { formatTokens, formatUsageStats } from "./render.js";
154
192
  export { buildPiArgs } from "./runner.js";
155
193
  export {
156
194
  DEFAULT_CONSULT_RESOURCE_POLICY,
195
+ DEFAULT_CONSULTATION_CWD_POLICY,
196
+ DEFAULT_DELEGATION_CWD_POLICY,
157
197
  inspectCompletionDeliverySettings,
158
198
  inspectConsultResourceSettings,
199
+ inspectCwdPolicySettings,
159
200
  inspectDelegationWorkflowSettings,
160
201
  inspectSubagentSettings,
161
202
  normalizeAgentSettings,
@@ -169,5 +210,6 @@ export {
169
210
  updateAgentToolsSetting,
170
211
  updateCompletionDeliverySetting,
171
212
  updateConsultResourceSetting,
213
+ updateCwdPolicySetting,
172
214
  updateDelegationWorkflowSetting,
173
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),