@wichayutdew/pi-workflows 0.3.0 → 1.0.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.
Files changed (144) hide show
  1. package/README.md +55 -39
  2. package/dist/index.js +5771 -4794
  3. package/examples/mr-comments.workflow.yaml +2 -0
  4. package/package.json +1 -1
  5. package/schemas/workflow.schema.json +10 -1
  6. package/src/command-names.ts +7 -1
  7. package/src/commands.ts +126 -52
  8. package/src/config/catalog.ts +162 -0
  9. package/src/config/ceiling.ts +120 -116
  10. package/src/config/command-conflicts.ts +4 -4
  11. package/src/config/diagnostics.ts +28 -0
  12. package/src/config/load-settings.ts +37 -0
  13. package/src/config/load-types.ts +53 -0
  14. package/src/config/load-workflows.ts +135 -0
  15. package/src/config/load.ts +43 -311
  16. package/src/config/types.ts +150 -137
  17. package/src/config/validate.ts +12 -1261
  18. package/src/config/validation/permissions.ts +312 -0
  19. package/src/config/validation/prompt.ts +21 -0
  20. package/src/config/validation/settings.ts +122 -0
  21. package/src/config/validation/shared.ts +129 -0
  22. package/src/config/validation/shortcut.ts +118 -0
  23. package/src/config/validation/step.ts +228 -0
  24. package/src/config/validation/subagent.ts +288 -0
  25. package/src/config/validation/workflow.ts +156 -0
  26. package/src/config/yaml.ts +29 -0
  27. package/src/digest.ts +38 -6
  28. package/src/engine/checkpoint.ts +13 -9
  29. package/src/engine/create-run.ts +43 -0
  30. package/src/engine/gate-transitions.ts +187 -0
  31. package/src/engine/reconciliation-history.ts +88 -0
  32. package/src/engine/resume.ts +19 -7
  33. package/src/engine/run-advance.ts +122 -0
  34. package/src/engine/run-lifecycle.ts +124 -0
  35. package/src/engine/run-reconciliation.ts +116 -0
  36. package/src/engine/run-validation.ts +129 -0
  37. package/src/engine/state-types.ts +66 -0
  38. package/src/engine/state.ts +10 -193
  39. package/src/engine/transition-helpers.ts +28 -0
  40. package/src/engine/transition-types.ts +8 -0
  41. package/src/engine/transitions.ts +17 -471
  42. package/src/harness/action-context.ts +181 -0
  43. package/src/harness/catalog.ts +45 -0
  44. package/src/harness/context-idle.ts +21 -0
  45. package/src/harness/core-actions.ts +299 -0
  46. package/src/harness/delegation-control-actions.ts +250 -0
  47. package/src/harness/delegation-failure.ts +215 -0
  48. package/src/harness/delegation-plan.ts +219 -0
  49. package/src/harness/delegation-recovery-validation.ts +159 -0
  50. package/src/harness/delegation-response-actions.ts +295 -0
  51. package/src/harness/delegation-retry-policy.ts +118 -0
  52. package/src/harness/dependencies.ts +165 -0
  53. package/src/harness/gate-submission-action.ts +154 -0
  54. package/src/harness/lifecycle-actions.ts +154 -0
  55. package/src/harness/pause-actions.ts +120 -0
  56. package/src/harness/plannotator-result-actions.ts +123 -0
  57. package/src/harness/prompt-gate-actions.ts +270 -0
  58. package/src/harness/resume-action.ts +261 -0
  59. package/src/harness/start-actions.ts +183 -0
  60. package/src/harness/status-actions.ts +191 -0
  61. package/src/harness/step-execution-actions.ts +283 -0
  62. package/src/harness/types.ts +76 -0
  63. package/src/harness.ts +236 -2303
  64. package/src/index.ts +73 -11
  65. package/src/integrations/plannotator-requests.ts +127 -0
  66. package/src/integrations/plannotator-responses.ts +141 -0
  67. package/src/integrations/plannotator-types.ts +44 -0
  68. package/src/integrations/plannotator.ts +16 -235
  69. package/src/integrations/prompt-gate.ts +17 -10
  70. package/src/integrations/subagents/child-policy-envelope.ts +119 -0
  71. package/src/integrations/subagents/child-policy-paths.ts +66 -0
  72. package/src/integrations/subagents/child-policy-sections.ts +186 -0
  73. package/src/integrations/subagents/child-policy-types.ts +32 -0
  74. package/src/integrations/subagents/child-policy-validation.ts +163 -0
  75. package/src/integrations/subagents/child-runtime-completion.ts +56 -0
  76. package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
  77. package/src/integrations/subagents/child-runtime-files.ts +156 -0
  78. package/src/integrations/subagents/child-runtime-policy.ts +83 -0
  79. package/src/integrations/subagents/child-runtime-types.ts +30 -0
  80. package/src/integrations/subagents/child-runtime.ts +140 -301
  81. package/src/integrations/subagents/client-delegation.ts +181 -0
  82. package/src/integrations/subagents/client-messages.ts +66 -0
  83. package/src/integrations/subagents/client-types.ts +36 -0
  84. package/src/integrations/subagents/client.ts +127 -219
  85. package/src/integrations/subagents/delegated-result.ts +31 -0
  86. package/src/integrations/subagents/diagnostic-format.ts +40 -0
  87. package/src/integrations/subagents/diagnostic-text.ts +114 -0
  88. package/src/integrations/subagents/diagnostic-types.ts +77 -0
  89. package/src/integrations/subagents/diagnostics.ts +24 -977
  90. package/src/integrations/subagents/failure-correlation.ts +235 -0
  91. package/src/integrations/subagents/failure-transcript.ts +192 -0
  92. package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
  93. package/src/integrations/subagents/protocol-events.ts +27 -0
  94. package/src/integrations/subagents/protocol.ts +30 -375
  95. package/src/integrations/subagents/replay-audit.ts +147 -0
  96. package/src/integrations/subagents/replay-safety.ts +82 -0
  97. package/src/integrations/subagents/session-diagnostics.ts +258 -0
  98. package/src/policy/approved-command-extraction.ts +71 -0
  99. package/src/policy/approved-commands.ts +15 -432
  100. package/src/policy/bash-authorization.ts +86 -0
  101. package/src/policy/bash-read-only.ts +161 -0
  102. package/src/policy/bash-types.ts +20 -0
  103. package/src/policy/bash.ts +4 -346
  104. package/src/policy/completion-batch.ts +21 -16
  105. package/src/policy/immutable-input.ts +5 -2
  106. package/src/policy/mcp-authorization.ts +71 -0
  107. package/src/policy/restricted-command.ts +141 -0
  108. package/src/policy/restricted-git.ts +30 -0
  109. package/src/policy/reviewed-artifact.ts +97 -0
  110. package/src/policy/reviewed-command-safety.ts +150 -0
  111. package/src/policy/reviewed-command-shape.ts +49 -0
  112. package/src/policy/reviewed-repository-contract.ts +138 -0
  113. package/src/policy/reviewed-repository-cwd.ts +102 -0
  114. package/src/policy/tool-call-authorization.ts +58 -0
  115. package/src/policy/tool-selection.ts +72 -0
  116. package/src/policy/tool-types.ts +14 -0
  117. package/src/policy/tools.ts +11 -150
  118. package/src/preflight.ts +96 -61
  119. package/src/prompt/main-workflow-notice.ts +41 -0
  120. package/src/prompt/retry-task.ts +83 -0
  121. package/src/prompt/step-contract.ts +101 -0
  122. package/src/prompt/step-sections.ts +75 -0
  123. package/src/prompt/step-task.ts +156 -0
  124. package/src/prompt/template.ts +83 -0
  125. package/src/prompt.ts +10 -254
  126. package/src/runtime/completion-tool.ts +3 -0
  127. package/src/runtime/main-step-completion.ts +78 -0
  128. package/src/runtime/main-step-lifecycle.ts +37 -0
  129. package/src/runtime/main-step-policy.ts +84 -0
  130. package/src/runtime/main-step-runtime-types.ts +64 -0
  131. package/src/runtime/main-step-runtime.ts +129 -200
  132. package/src/runtime/main-step-state.ts +85 -0
  133. package/src/runtime/serial-task-queue.ts +48 -13
  134. package/src/runtime/step-result.ts +48 -27
  135. package/src/workflow-list.ts +15 -6
  136. package/src/workflow-status/format-status.ts +52 -0
  137. package/src/workflow-status/formatting.ts +178 -0
  138. package/src/workflow-status/layout.ts +109 -0
  139. package/src/workflow-status/render-board.ts +101 -0
  140. package/src/workflow-status/render-path.ts +123 -0
  141. package/src/workflow-status/render-summary.ts +144 -0
  142. package/src/workflow-status/types.ts +40 -0
  143. package/src/workflow-status/view.ts +273 -0
  144. package/src/workflow-status.ts +20 -820
@@ -1,318 +1,149 @@
1
- import { randomUUID, timingSafeEqual } from 'node:crypto';
2
- import {
3
- existsSync,
4
- lstatSync,
5
- readFileSync,
6
- realpathSync,
7
- renameSync,
8
- statSync,
9
- unlinkSync,
10
- writeFileSync,
11
- } from 'node:fs';
12
- import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
13
- import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
14
- import type { WorkflowStep } from '../../config/types.ts';
1
+ import type {
2
+ ExtensionAPI,
3
+ InputEvent,
4
+ InputEventResult,
5
+ } from '@earendil-works/pi-coding-agent';
15
6
  import { invalidCompletionCallIds } from '../../policy/completion-batch.ts';
16
7
  import { freezeToolInput } from '../../policy/immutable-input.ts';
17
8
  import { authorizeToolCall, resolveActiveTools } from '../../policy/tools.ts';
18
9
  import {
19
- extractChildPolicy,
20
- isSubagentRuntimeName,
21
- parseDelegatedStepResult,
22
- type ChildStepPolicy,
23
- } from './protocol.ts';
24
-
25
- export const CHILD_COMPLETION_TOOL = 'structured_output';
26
- const CHILD_COORDINATION_TOOLS = new Set([
27
- 'contact_supervisor',
28
- 'subagent_supervisor',
29
- 'intercom',
30
- ]);
31
- const STRUCTURED_RESULT_KEYS = new Set(['outcome', 'summary', 'artifact']);
32
- const FILE_MUTATION_TOOLS = new Set(['edit', 'write']);
33
-
34
- function policyStep(policy: ChildStepPolicy): WorkflowStep {
35
- return {
36
- title: policy.stepTitle,
37
- prompt: { inline: 'Delegated workflow step' },
38
- subagent: {
39
- agent: policy.agent,
40
- context: 'fresh',
41
- timeoutMs: 900_000,
42
- artifacts: false,
43
- retryToolFailures: false,
44
- },
45
- permissions: policy.permissions,
46
- requires: { tools: [], extensions: [], skills: [] },
47
- transitions: {},
48
- };
49
- }
50
-
51
- function childSystemPrompt(policy: ChildStepPolicy): string {
52
- const hasPauseOutcome = policy.pauseOutcomes.length > 0;
53
- return [
54
- '# Pi Workflows delegated step',
55
- '',
56
- `Workflow: ${policy.workflowId}`,
57
- `Run: ${policy.runId}`,
58
- `Step: ${policy.stepId} (${policy.stepTitle})`,
59
- '',
60
- 'The parent workflow harness owns orchestration and state transitions.',
61
- 'Perform only this delegated step. Its child-side tool policy is enforced.',
62
- 'When finished, call `structured_output` exactly once and as the only tool call in that message.',
63
- 'Pass the workflow result as its `value`: outcome, summary, and optional artifact.',
64
- `Valid outcomes: ${policy.outcomes.join(', ')}`,
65
- `Pause outcomes: ${policy.pauseOutcomes.join(', ') || '(none)'}`,
66
- `Summary limit: ${policy.summaryMaxChars} characters`,
67
- ...(policy.gateSubmitOutcome
68
- ? [
69
- `Outcome "${policy.gateSubmitOutcome}" requires the complete gate artifact.`,
70
- ]
71
- : []),
72
- ...(hasPauseOutcome
73
- ? [
74
- `If the workflow definition or environment is wrong, choose a pause outcome (${policy.pauseOutcomes.join(', ')}).`,
75
- ]
76
- : [
77
- 'If the workflow definition or environment is wrong, do not fabricate success or call the completion tool; end with a concise declarative error so the parent pauses the step.',
78
- ]),
79
- 'This is a non-interactive workflow child. Never call contact_supervisor, subagent_supervisor, or intercom.',
80
- ...(policy.gateSubmitOutcome
81
- ? [
82
- 'Put every unresolved decision in the gate artifact with evidence, options, a recommendation, and an adopted default; do not ask a terminal question.',
83
- ]
84
- : hasPauseOutcome
85
- ? [
86
- 'Treat the step instructions and incoming handoff as the final execution contract.',
87
- 'If that contract is missing, stale, or contradictory, finish with a pause outcome and describe the unresolved contract and evidence declaratively in the summary; do not ask a terminal question.',
88
- ]
89
- : [
90
- 'Treat the step instructions and incoming handoff as the final execution contract.',
91
- 'If that contract is missing, stale, or contradictory, do not fabricate success or call the completion tool; end with a concise declarative error so the parent pauses the step. Do not ask a terminal question.',
92
- ]),
93
- ...(policy.repositoryCwd
94
- ? [
95
- `Reviewed repository root: ${policy.repositoryCwd}`,
96
- ...(policy.bootstrapCwd
97
- ? [
98
- `Bootstrap directory: ${policy.bootstrapCwd}`,
99
- 'The reviewed repository root does not exist yet. Run only its exact approved setup command first, then use absolute paths under the reviewed repository root for every edit and write. Never mutate the bootstrap directory.',
100
- ]
101
- : [
102
- 'Keep every edit and write inside the reviewed repository root.',
103
- ]),
104
- ]
105
- : []),
106
- ].join('\n');
107
- }
108
-
109
- function writeResult(policy: ChildStepPolicy, result: unknown): void {
110
- if (existsSync(policy.resultPath)) {
111
- throw new Error('Delegated workflow step already produced a result');
112
- }
113
- const temporaryPath = `${policy.resultPath}.${randomUUID()}.tmp`;
114
- try {
115
- writeFileSync(temporaryPath, JSON.stringify(result), {
116
- encoding: 'utf8',
117
- flag: 'wx',
118
- mode: 0o600,
119
- });
120
- renameSync(temporaryPath, policy.resultPath);
121
- } catch (error) {
122
- try {
123
- unlinkSync(temporaryPath);
124
- } catch {
125
- // The temporary file may not have been created.
126
- }
127
- throw error;
128
- }
129
- }
10
+ CHILD_COMPLETION_TOOL,
11
+ CHILD_COORDINATION_TOOLS,
12
+ parseChildStructuredResult,
13
+ } from './child-runtime-completion.ts';
14
+ import { DEFAULT_CHILD_RUNTIME_DEPENDENCIES } from './child-runtime-dependencies.ts';
15
+ import {
16
+ repositoryMutationError,
17
+ verifyChildCapability,
18
+ writeChildResult,
19
+ } from './child-runtime-files.ts';
20
+ import { childPolicyStep, childSystemPrompt } from './child-runtime-policy.ts';
21
+ import type {
22
+ SubagentChildRuntimeDependencies,
23
+ SubagentChildRuntimeOptions,
24
+ } from './child-runtime-types.ts';
25
+ import { extractChildPolicy } from './child-policy-envelope.ts';
26
+ import type { ChildStepPolicy } from './child-policy-types.ts';
27
+ import { isSubagentRuntimeName } from './child-policy-validation.ts';
130
28
 
131
- function structuredResult(
132
- input: unknown,
133
- policy: ChildStepPolicy,
134
- ): ReturnType<typeof parseDelegatedStepResult> {
135
- if (input === null || typeof input !== 'object' || Array.isArray(input)) {
136
- throw new Error('structured_output input must be an object');
137
- }
138
- const wrapper = input as Record<string, unknown>;
139
- if (Object.keys(wrapper).length !== 1 || !Object.hasOwn(wrapper, 'value')) {
140
- throw new Error('structured_output input must contain only value');
141
- }
142
- if (
143
- wrapper.value === null ||
144
- typeof wrapper.value !== 'object' ||
145
- Array.isArray(wrapper.value)
146
- ) {
147
- throw new Error('structured_output value must be an object');
148
- }
149
- const value = wrapper.value as Record<string, unknown>;
150
- const unknownKey = Object.keys(value).find(
151
- (key) => !STRUCTURED_RESULT_KEYS.has(key),
152
- );
153
- if (unknownKey) {
154
- throw new Error(
155
- `structured_output value has unknown property "${unknownKey}"`,
156
- );
157
- }
158
- return parseDelegatedStepResult(
159
- {
160
- version: 1,
161
- policyDigest: policy.policyDigest,
162
- ...value,
163
- },
164
- policy,
165
- );
166
- }
29
+ export { CHILD_COMPLETION_TOOL } from './child-runtime-completion.ts';
30
+ export type {
31
+ ChildRuntimeFileSystem,
32
+ ChildRuntimePathInspection,
33
+ SubagentChildRuntimeDependencies,
34
+ SubagentChildRuntimeOptions,
35
+ } from './child-runtime-types.ts';
167
36
 
168
- function verifyCapability(
169
- policy: ChildStepPolicy,
170
- childAgent: string | undefined,
171
- ): void {
172
- if (!isSubagentRuntimeName(childAgent) || childAgent !== policy.agent) {
173
- throw new Error('child agent does not match the delegated workflow policy');
174
- }
175
- let actual: Buffer;
176
- try {
177
- actual = Buffer.from(readFileSync(policy.capabilityPath, 'utf8'), 'utf8');
178
- } catch {
179
- throw new Error('delegated workflow capability is missing');
180
- }
181
- const expected = Buffer.from(policy.capabilityToken, 'utf8');
182
- if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) {
183
- throw new Error('delegated workflow capability is invalid');
184
- }
185
- unlinkSync(policy.capabilityPath);
186
- }
37
+ type ChildRuntimeState = {
38
+ readonly activePolicy: ChildStepPolicy | undefined;
39
+ readonly policyError: string | undefined;
40
+ readonly invalidCompletionCalls: ReadonlySet<string>;
41
+ readonly effectiveTools: ReadonlySet<string>;
42
+ };
187
43
 
188
- function authorizeRepositoryMutation(
189
- toolName: string,
190
- input: Record<string, unknown>,
191
- policy: ChildStepPolicy,
192
- ): string | undefined {
193
- if (!policy.repositoryCwd || !FILE_MUTATION_TOOLS.has(toolName)) return;
194
- if (typeof input.path !== 'string' || !input.path.trim()) {
195
- return `${toolName} must name a path inside the reviewed repository root`;
196
- }
197
- const candidate = resolve(process.cwd(), input.path);
198
- const root = resolve(policy.repositoryCwd);
199
- if (!pathIsInside(root, candidate)) {
200
- return `${toolName} path is outside the reviewed repository root "${policy.repositoryCwd}"`;
201
- }
202
- let canonicalRoot: string;
203
- try {
204
- if (!statSync(root).isDirectory()) throw new Error('not a directory');
205
- canonicalRoot = realpathSync(root);
206
- } catch {
207
- return `reviewed repository root is not an existing directory: ${policy.repositoryCwd}`;
208
- }
209
- const canonicalAncestor = nearestCanonicalAncestor(candidate);
210
- if (
211
- canonicalAncestor === undefined ||
212
- !pathIsInside(canonicalRoot, canonicalAncestor)
213
- ) {
214
- return `${toolName} path is outside the reviewed repository root "${policy.repositoryCwd}"`;
215
- }
216
- return;
217
- }
44
+ const INITIAL_STATE: ChildRuntimeState = {
45
+ activePolicy: undefined,
46
+ policyError: undefined,
47
+ invalidCompletionCalls: new Set(),
48
+ effectiveTools: new Set(),
49
+ };
218
50
 
219
- function pathIsInside(root: string, candidate: string): boolean {
220
- const fromRoot = relative(root, candidate);
221
- return (
222
- fromRoot !== '..' &&
223
- !fromRoot.startsWith(`..${sep}`) &&
224
- !isAbsolute(fromRoot)
225
- );
226
- }
51
+ const errorMessage = (error: unknown): string =>
52
+ error instanceof Error ? error.message : String(error);
227
53
 
228
- function nearestCanonicalAncestor(path: string): string | undefined {
229
- let candidate = path;
230
- while (true) {
231
- try {
232
- lstatSync(candidate);
233
- } catch (error) {
234
- const code = (error as { code?: unknown }).code;
235
- if (code !== 'ENOENT') return undefined;
236
- const parent = dirname(candidate);
237
- if (parent === candidate) return undefined;
238
- candidate = parent;
239
- continue;
240
- }
241
- try {
242
- return realpathSync(candidate);
243
- } catch {
244
- return undefined;
245
- }
246
- }
247
- }
54
+ const invalidPolicyInput = (
55
+ pi: ExtensionAPI,
56
+ policyError: string,
57
+ images: InputEvent['images'],
58
+ ): InputEventResult => {
59
+ pi.setActiveTools([]);
60
+ return {
61
+ action: 'transform' as const,
62
+ text: `Delegated workflow policy is invalid: ${policyError}`,
63
+ ...(images ? { images } : {}),
64
+ };
65
+ };
248
66
 
249
- export interface SubagentChildRuntimeOptions {
250
- childAgent?: string;
251
- }
67
+ const resolveChildAgent = (
68
+ options: SubagentChildRuntimeOptions,
69
+ dependencies: SubagentChildRuntimeDependencies,
70
+ ): string | undefined =>
71
+ options.childAgent ?? dependencies.environmentChildAgent();
252
72
 
253
- export function registerSubagentChildRuntime(
73
+ /**
74
+ * Registers the policy-enforcing runtime used inside a delegated subagent.
75
+ *
76
+ * All file-system, identity, process, and capability-comparison boundaries can
77
+ * be supplied through `options.dependencies`.
78
+ */
79
+ export const registerSubagentChildRuntime = (
254
80
  pi: ExtensionAPI,
255
81
  options: SubagentChildRuntimeOptions = {},
256
- ): void {
257
- let activePolicy: ChildStepPolicy | undefined;
258
- let policyError: string | undefined;
259
- let invalidCompletionCalls = new Set<string>();
260
- let effectiveTools = new Set<string>();
261
- const childAgent =
262
- options.childAgent ?? process.env.PI_SUBAGENT_CHILD_AGENT?.trim();
82
+ ): void => {
83
+ const dependencies =
84
+ options.dependencies ?? DEFAULT_CHILD_RUNTIME_DEPENDENCIES;
85
+ const childAgent = resolveChildAgent(options, dependencies);
86
+ let state = INITIAL_STATE;
263
87
 
264
88
  pi.on('input', (event) => {
265
89
  let extracted;
266
90
  try {
267
- extracted = extractChildPolicy(event.text);
91
+ extracted = extractChildPolicy(event.text, {
92
+ temporaryDirectory: dependencies.temporaryDirectory,
93
+ });
268
94
  } catch (error) {
269
- policyError = error instanceof Error ? error.message : String(error);
270
- pi.setActiveTools([]);
271
- return {
272
- action: 'transform' as const,
273
- text: `Delegated workflow policy is invalid: ${policyError}`,
274
- ...(event.images ? { images: event.images } : {}),
275
- };
95
+ const policyError = errorMessage(error);
96
+ state = { ...state, policyError };
97
+ return invalidPolicyInput(pi, policyError, event.images);
276
98
  }
277
99
  if (!extracted) return;
278
- if (activePolicy) {
279
- policyError = 'child received more than one workflow policy';
280
- pi.setActiveTools([]);
281
- return {
282
- action: 'transform' as const,
283
- text: `Delegated workflow policy is invalid: ${policyError}`,
284
- ...(event.images ? { images: event.images } : {}),
285
- };
100
+ if (state.activePolicy) {
101
+ const policyError = 'child received more than one workflow policy';
102
+ state = { ...state, policyError };
103
+ return invalidPolicyInput(pi, policyError, event.images);
286
104
  }
287
105
 
288
106
  try {
289
- verifyCapability(extracted.policy, childAgent);
107
+ if (!isSubagentRuntimeName(childAgent)) {
108
+ throw new Error(
109
+ 'child agent does not match the delegated workflow policy',
110
+ );
111
+ }
112
+ verifyChildCapability({
113
+ policy: extracted.policy,
114
+ childAgent,
115
+ dependencies,
116
+ });
290
117
  const profileTools = new Set(pi.getActiveTools());
291
118
  if (!profileTools.has(CHILD_COMPLETION_TOOL)) {
292
119
  throw new Error(
293
120
  'pi-subagents structured_output completion is unavailable',
294
121
  );
295
122
  }
296
- activePolicy = extracted.policy;
297
- policyError = undefined;
298
- effectiveTools = new Set(
123
+ const effectiveTools = new Set(
299
124
  resolveActiveTools(
300
125
  pi.getAllTools(),
301
- policyStep(activePolicy),
126
+ childPolicyStep(extracted.policy),
302
127
  CHILD_COMPLETION_TOOL,
303
128
  ).filter((toolName) => !CHILD_COORDINATION_TOOLS.has(toolName)),
304
129
  );
130
+ state = {
131
+ ...state,
132
+ activePolicy: extracted.policy,
133
+ policyError: undefined,
134
+ effectiveTools,
135
+ };
305
136
  } catch (error) {
306
- policyError = error instanceof Error ? error.message : String(error);
307
- effectiveTools.clear();
308
- pi.setActiveTools([]);
309
- return {
310
- action: 'transform' as const,
311
- text: `Delegated workflow policy is invalid: ${policyError}`,
312
- ...(event.images ? { images: event.images } : {}),
137
+ const policyError = errorMessage(error);
138
+ state = {
139
+ ...state,
140
+ policyError,
141
+ effectiveTools: new Set(),
313
142
  };
143
+ return invalidPolicyInput(pi, policyError, event.images);
314
144
  }
315
- pi.setActiveTools([...effectiveTools]);
145
+
146
+ pi.setActiveTools([...state.effectiveTools]);
316
147
  return {
317
148
  action: 'transform' as const,
318
149
  text: extracted.task,
@@ -321,34 +152,32 @@ export function registerSubagentChildRuntime(
321
152
  });
322
153
 
323
154
  pi.on('before_agent_start', (event) => {
324
- if (!activePolicy) {
325
- if (policyError) pi.setActiveTools([]);
155
+ if (!state.activePolicy) {
156
+ if (state.policyError) pi.setActiveTools([]);
326
157
  return;
327
158
  }
328
159
  return {
329
- systemPrompt: `${event.systemPrompt}\n\n${childSystemPrompt(activePolicy)}`,
160
+ systemPrompt: `${event.systemPrompt}\n\n${childSystemPrompt(state.activePolicy)}`,
330
161
  };
331
162
  });
332
163
 
333
164
  pi.on('turn_start', () => {
334
- invalidCompletionCalls.clear();
165
+ state = { ...state, invalidCompletionCalls: new Set() };
335
166
  });
336
167
 
337
168
  pi.on('message_end', (event) => {
338
- if (!activePolicy) return;
169
+ if (!state.activePolicy) return;
339
170
  const invalid = invalidCompletionCallIds(
340
171
  event.message,
341
172
  CHILD_COMPLETION_TOOL,
342
173
  );
343
- if (
344
- invalid.size > 0 ||
345
- (event.message as { role?: unknown }).role === 'assistant'
346
- ) {
347
- invalidCompletionCalls = invalid;
174
+ if (invalid.size > 0 || event.message.role === 'assistant') {
175
+ state = { ...state, invalidCompletionCalls: invalid };
348
176
  }
349
177
  });
350
178
 
351
179
  pi.on('tool_call', (event) => {
180
+ const { activePolicy, policyError } = state;
352
181
  if (!activePolicy) {
353
182
  if (!policyError) return;
354
183
  return {
@@ -356,7 +185,7 @@ export function registerSubagentChildRuntime(
356
185
  reason: policyError,
357
186
  };
358
187
  }
359
- if (invalidCompletionCalls.has(event.toolCallId)) {
188
+ if (state.invalidCompletionCalls.has(event.toolCallId)) {
360
189
  return {
361
190
  block: true,
362
191
  reason: `${CHILD_COMPLETION_TOOL} must be the only tool call in its message`,
@@ -370,14 +199,21 @@ export function registerSubagentChildRuntime(
370
199
  };
371
200
  }
372
201
  try {
373
- const result = structuredResult(event.input, activePolicy);
374
- writeResult(activePolicy, result);
202
+ const result = parseChildStructuredResult({
203
+ input: event.input,
204
+ policy: activePolicy,
205
+ });
206
+ writeChildResult({
207
+ policy: activePolicy,
208
+ result,
209
+ dependencies,
210
+ });
375
211
  freezeToolInput(event.input);
376
212
  return;
377
213
  } catch (error) {
378
214
  return {
379
215
  block: true,
380
- reason: error instanceof Error ? error.message : String(error),
216
+ reason: errorMessage(error),
381
217
  };
382
218
  }
383
219
  }
@@ -388,10 +224,12 @@ export function registerSubagentChildRuntime(
388
224
  'workflow children are non-interactive; use structured_output with a pause outcome and describe the unresolved contract in summary',
389
225
  };
390
226
  }
227
+
228
+ const input: Record<string, unknown> = { ...event.input };
391
229
  const authorization = authorizeToolCall(
392
230
  event.toolName,
393
- event.input as unknown as Record<string, unknown>,
394
- policyStep(activePolicy),
231
+ input,
232
+ childPolicyStep(activePolicy),
395
233
  pi.getAllTools(),
396
234
  activePolicy.approvedBashCommands ?? [],
397
235
  );
@@ -401,18 +239,19 @@ export function registerSubagentChildRuntime(
401
239
  reason: authorization.reason ?? 'Tool blocked by workflow child policy',
402
240
  };
403
241
  }
404
- if (!effectiveTools.has(event.toolName)) {
242
+ if (!state.effectiveTools.has(event.toolName)) {
405
243
  return {
406
244
  block: true,
407
245
  reason: `tool "${event.toolName}" is allowed by the workflow but unavailable in this child runtime`,
408
246
  };
409
247
  }
410
248
 
411
- const mutationError = authorizeRepositoryMutation(
412
- event.toolName,
413
- event.input as unknown as Record<string, unknown>,
414
- activePolicy,
415
- );
249
+ const mutationError = repositoryMutationError({
250
+ toolName: event.toolName,
251
+ input,
252
+ policy: activePolicy,
253
+ dependencies,
254
+ });
416
255
  if (mutationError) {
417
256
  return {
418
257
  block: true,
@@ -421,4 +260,4 @@ export function registerSubagentChildRuntime(
421
260
  }
422
261
  freezeToolInput(event.input);
423
262
  });
424
- }
263
+ };