@wichayutdew/pi-workflows 2.5.0 → 2.6.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 (95) hide show
  1. package/README.md +4 -1
  2. package/dist/index.js +1133 -2409
  3. package/examples/starter-kit/agents/planner.md +7 -0
  4. package/examples/starter-kit/agents/reviewer.md +6 -0
  5. package/examples/starter-kit/agents/scout.md +6 -0
  6. package/examples/starter-kit/agents/worker.md +7 -0
  7. package/examples/starter-kit/agents/workspace-preparer.md +6 -0
  8. package/examples/starter-kit/investigate.workflow.yaml +114 -0
  9. package/examples/starter-kit/mr-comment.workflow.yaml +114 -54
  10. package/examples/starter-kit/mr-review.workflow.yaml +77 -56
  11. package/examples/starter-kit/settings.yaml +3 -0
  12. package/examples/starter-kit/steps/investigate/investigate.md +77 -0
  13. package/examples/starter-kit/steps/investigate/retrieve.md +63 -0
  14. package/examples/starter-kit/steps/investigate/validate.md +46 -0
  15. package/examples/starter-kit/steps/mr-comment/checkout-source.md +67 -0
  16. package/examples/starter-kit/steps/mr-comment/fetch.md +34 -26
  17. package/examples/starter-kit/steps/mr-comment/implement.md +34 -27
  18. package/examples/starter-kit/steps/mr-comment/plan.md +54 -40
  19. package/examples/starter-kit/steps/mr-comment/publish.md +28 -27
  20. package/examples/starter-kit/steps/mr-comment/verify.md +42 -35
  21. package/examples/starter-kit/steps/mr-review/fetch.md +52 -22
  22. package/examples/starter-kit/steps/mr-review/publish-approved.md +49 -0
  23. package/examples/starter-kit/steps/mr-review/review-for-approval.md +142 -0
  24. package/examples/starter-kit/steps/mr-review/verify-published.md +37 -0
  25. package/examples/starter-kit/steps/shared/prepare-workspace.md +91 -73
  26. package/examples/starter-kit/steps/shared/publish-remote.md +45 -0
  27. package/examples/starter-kit/steps/ticket/implement.md +59 -25
  28. package/examples/starter-kit/steps/ticket/plan.md +145 -72
  29. package/examples/starter-kit/steps/ticket/verify.md +80 -45
  30. package/examples/starter-kit/steps/work/implement.md +60 -24
  31. package/examples/starter-kit/steps/work/plan.md +129 -57
  32. package/examples/starter-kit/steps/work/verify.md +58 -22
  33. package/examples/starter-kit/ticket.workflow.yaml +75 -36
  34. package/examples/starter-kit/work.workflow.yaml +63 -33
  35. package/package.json +3 -16
  36. package/schemas/workflow.schema.json +2 -15
  37. package/src/agents/profile.ts +98 -0
  38. package/src/config/ceiling.ts +0 -82
  39. package/src/config/types.ts +6 -53
  40. package/src/config/validation/settings.ts +2 -14
  41. package/src/config/validation/step.ts +12 -8
  42. package/src/config/validation/workflow.ts +1 -10
  43. package/src/engine/run-workflow-validation.ts +0 -3
  44. package/src/engine/state-types.ts +1 -1
  45. package/src/harness/action-context.ts +1 -13
  46. package/src/harness/delegation-control-actions.ts +4 -63
  47. package/src/harness/delegation-plan.ts +26 -80
  48. package/src/harness/delegation-response-actions.ts +22 -135
  49. package/src/harness/dependencies.ts +1 -12
  50. package/src/harness/status-actions.ts +1 -0
  51. package/src/harness/step-execution-actions.ts +2 -9
  52. package/src/harness/types.ts +2 -40
  53. package/src/harness.ts +2 -17
  54. package/src/index.ts +4 -9
  55. package/src/integrations/subagents/child-policy-validation.ts +6 -7
  56. package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
  57. package/src/integrations/subagents/child-runtime-policy.ts +1 -7
  58. package/src/integrations/subagents/child-runtime.ts +18 -9
  59. package/src/integrations/subagents/client.ts +240 -98
  60. package/src/integrations/subagents/protocol-events.ts +32 -15
  61. package/src/integrations/subagents/protocol.ts +1 -1
  62. package/src/preflight.ts +0 -8
  63. package/src/prompt/main-workflow-notice.ts +8 -15
  64. package/src/prompt/step-task.ts +7 -6
  65. package/src/workflow-status/format-status.ts +5 -1
  66. package/src/workflow-status/render-step-detail.ts +94 -0
  67. package/src/workflow-status/types.ts +1 -0
  68. package/src/workflow-status/view.ts +49 -14
  69. package/agents/step.md +0 -32
  70. package/examples/mr-comments.workflow.yaml +0 -125
  71. package/examples/prompts/mr-comments/implement.md +0 -17
  72. package/examples/prompts/mr-comments/inspect.md +0 -5
  73. package/examples/prompts/mr-comments/plan.md +0 -54
  74. package/examples/prompts/mr-comments/verify.md +0 -9
  75. package/examples/settings.yaml +0 -27
  76. package/examples/starter-kit/steps/mr-review/publish.md +0 -48
  77. package/examples/starter-kit/steps/mr-review/review.md +0 -76
  78. package/examples/starter-kit/steps/mr-review/verify.md +0 -35
  79. package/src/config/validation/subagent.ts +0 -288
  80. package/src/harness/delegation-failure.ts +0 -248
  81. package/src/harness/delegation-recovery-validation.ts +0 -161
  82. package/src/harness/delegation-retry-policy.ts +0 -120
  83. package/src/integrations/subagents/client-delegation.ts +0 -181
  84. package/src/integrations/subagents/client-messages.ts +0 -66
  85. package/src/integrations/subagents/client-types.ts +0 -36
  86. package/src/integrations/subagents/diagnostic-format.ts +0 -45
  87. package/src/integrations/subagents/diagnostic-text.ts +0 -114
  88. package/src/integrations/subagents/diagnostic-types.ts +0 -83
  89. package/src/integrations/subagents/diagnostics.ts +0 -26
  90. package/src/integrations/subagents/failure-correlation.ts +0 -285
  91. package/src/integrations/subagents/failure-transcript.ts +0 -251
  92. package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
  93. package/src/integrations/subagents/replay-audit.ts +0 -146
  94. package/src/integrations/subagents/replay-safety.ts +0 -67
  95. package/src/integrations/subagents/session-diagnostics.ts +0 -357
@@ -1,5 +1,5 @@
1
1
  import { dirname, isAbsolute, resolve } from 'node:path';
2
- import { SUBAGENT_RUNTIME_NAME_PATTERN } from '../../config/types.ts';
2
+ import { AGENT_PROFILE_NAME_PATTERN } from '../../config/types.ts';
3
3
  import {
4
4
  DEFAULT_CHILD_POLICY_ENVIRONMENT,
5
5
  isSafeStepCapabilityPath,
@@ -61,11 +61,10 @@ const requiredString = (
61
61
 
62
62
  /**
63
63
  * Returns whether a name follows the runtime naming contract used by
64
- * pi-subagents.
64
+ * workflow agent profiles.
65
65
  */
66
- export const isSubagentRuntimeName = (
67
- name: string | undefined,
68
- ): name is string => Boolean(name && SUBAGENT_RUNTIME_NAME_PATTERN.test(name));
66
+ export const isAgentProfileName = (name: string | undefined): name is string =>
67
+ Boolean(name && AGENT_PROFILE_NAME_PATTERN.test(name));
69
68
 
70
69
  const rejectUnknownProperties = (
71
70
  value: Readonly<Record<string, unknown>>,
@@ -115,8 +114,8 @@ const parseIdentityAndPaths = (
115
114
  if (!POLICY_DIGEST_PATTERN.test(policyDigest)) {
116
115
  throw new Error('child policy digest is invalid');
117
116
  }
118
- if (!isSubagentRuntimeName(agent)) {
119
- throw new Error('child policy agent is not a valid subagent runtime name');
117
+ if (!isAgentProfileName(agent)) {
118
+ throw new Error('child policy agent is not a valid agent profile name');
120
119
  }
121
120
  if (!CAPABILITY_TOKEN_PATTERN.test(capabilityToken)) {
122
121
  throw new Error('child policy capability token is invalid');
@@ -41,7 +41,7 @@ export const DEFAULT_CHILD_RUNTIME_DEPENDENCIES = {
41
41
  createUniqueId: randomUUID,
42
42
  currentWorkingDirectory: () => process.cwd(),
43
43
  environmentChildAgent: () =>
44
- process.env.PI_SUBAGENT_CHILD_AGENT?.trim() || undefined,
44
+ process.env.PI_WORKFLOWS_CHILD_AGENT?.trim() || undefined,
45
45
  temporaryDirectory: tmpdir,
46
46
  tokensAreEqual,
47
47
  } as const satisfies SubagentChildRuntimeDependencies;
@@ -8,13 +8,7 @@ import type { ChildStepPolicy } from './child-policy-types.ts';
8
8
  export const childPolicyStep = (policy: ChildStepPolicy): WorkflowStep => ({
9
9
  title: policy.stepTitle,
10
10
  prompt: { inline: 'Delegated workflow step' },
11
- subagent: {
12
- agent: policy.agent,
13
- context: 'fresh',
14
- timeoutMs: 900_000,
15
- artifacts: false,
16
- retryToolFailures: false,
17
- },
11
+ agent: { name: policy.agent },
18
12
  permissions: policy.permissions,
19
13
  requires: { tools: [], extensions: [], skills: [] },
20
14
  transitions: {},
@@ -3,6 +3,7 @@ import type {
3
3
  InputEvent,
4
4
  InputEventResult,
5
5
  } from '@earendil-works/pi-coding-agent';
6
+ import { Type } from 'typebox';
6
7
  import { invalidCompletionCallIds } from '../../policy/completion-batch.ts';
7
8
  import { freezeToolInput } from '../../policy/immutable-input.ts';
8
9
  import { authorizeToolCall, resolveActiveTools } from '../../policy/tools.ts';
@@ -24,7 +25,6 @@ import type {
24
25
  } from './child-runtime-types.ts';
25
26
  import { extractChildPolicy } from './child-policy-envelope.ts';
26
27
  import type { ChildStepPolicy } from './child-policy-types.ts';
27
- import { isSubagentRuntimeName } from './child-policy-validation.ts';
28
28
 
29
29
  export { CHILD_COMPLETION_TOOL } from './child-runtime-completion.ts';
30
30
  export type {
@@ -85,6 +85,21 @@ export const registerSubagentChildRuntime = (
85
85
  const childAgent = resolveChildAgent(options, dependencies);
86
86
  let state = INITIAL_STATE;
87
87
 
88
+ pi.registerTool({
89
+ name: CHILD_COMPLETION_TOOL,
90
+ label: 'Complete Workflow Step',
91
+ description: 'Return the one structured result for this workflow step',
92
+ parameters: Type.Object({ value: Type.Any() }),
93
+ executionMode: 'sequential',
94
+ execute: async () => ({
95
+ content: [
96
+ { type: 'text' as const, text: 'Captured workflow step result.' },
97
+ ],
98
+ details: {},
99
+ terminate: true,
100
+ }),
101
+ });
102
+
88
103
  pi.on('input', (event) => {
89
104
  let extracted;
90
105
  try {
@@ -104,11 +119,7 @@ export const registerSubagentChildRuntime = (
104
119
  }
105
120
 
106
121
  try {
107
- if (!isSubagentRuntimeName(childAgent)) {
108
- throw new Error(
109
- 'child agent does not match the delegated workflow policy',
110
- );
111
- }
122
+ if (!childAgent) throw new Error('workflow worker agent is unavailable');
112
123
  verifyChildWorkingDirectory(extracted.policy, dependencies);
113
124
  verifyChildCapability({
114
125
  policy: extracted.policy,
@@ -117,9 +128,7 @@ export const registerSubagentChildRuntime = (
117
128
  });
118
129
  const profileTools = new Set(pi.getActiveTools());
119
130
  if (!profileTools.has(CHILD_COMPLETION_TOOL)) {
120
- throw new Error(
121
- 'pi-subagents structured_output completion is unavailable',
122
- );
131
+ throw new Error('workflow worker completion tool is unavailable');
123
132
  }
124
133
  const effectiveTools = new Set(
125
134
  resolveActiveTools(
@@ -1,51 +1,188 @@
1
- import {
2
- createDelegation,
3
- DEFAULT_CLIENT_DEPENDENCIES,
4
- } from './client-delegation.ts';
5
- import type {
6
- ActiveDelegation,
7
- DelegateOptions,
8
- SubagentDelegationClientDependencies,
9
- SubagentEventBus,
10
- } from './client-types.ts';
1
+ import { spawn, type ChildProcess } from 'node:child_process';
2
+ import { StringDecoder } from 'node:string_decoder';
11
3
  import type {
12
4
  SubagentDelegationRequest,
13
5
  SubagentDelegationResponse,
6
+ SubagentDelegationUpdate,
14
7
  } from './protocol-events.ts';
15
8
 
16
- export type {
17
- DelegateOptions,
18
- SubagentDelegationClientDependencies,
19
- SubagentEventBus,
20
- } from './client-types.ts';
9
+ export type DelegateOptions = {
10
+ readonly signal?: AbortSignal;
11
+ readonly onUpdate?: (update: { readonly requestId: string }) => void;
12
+ readonly onLateTerminal?: (response: SubagentDelegationResponse) => void;
13
+ };
21
14
 
22
- /**
23
- * Functional surface used by workflow orchestration.
24
- */
15
+ /** A pi-workflows-owned foreground Pi process. */
25
16
  export type SubagentDelegationClientController = {
26
- /** Request currently owned by this client, if any. */
27
17
  readonly activeRequestId: string | undefined;
28
- /** Starts one correlated delegation. */
29
18
  readonly delegate: (
30
19
  request: SubagentDelegationRequest,
31
20
  options?: DelegateOptions,
32
21
  ) => Promise<SubagentDelegationResponse>;
33
- /** Cancels the active delegation and waits for terminal confirmation. */
34
22
  readonly cancelActiveAndWait: (waitMs?: number) => Promise<boolean>;
35
23
  };
36
24
 
37
- /**
38
- * Creates an isolated subagent delegation controller.
39
- *
40
- * @param events - Event transport shared with the subagent runtime.
41
- * @param dependencies - Timer effects used for cancellation and timeouts.
42
- * @returns A controller that owns at most one active delegation.
43
- */
25
+ export type DirectWorkerSpawn = (
26
+ command: string,
27
+ args: Array<string>,
28
+ options: {
29
+ readonly cwd: string;
30
+ readonly env: NodeJS.ProcessEnv;
31
+ readonly stdio: ['ignore', 'pipe', 'pipe'];
32
+ },
33
+ ) => ChildProcess & {
34
+ readonly stdout: NonNullable<ChildProcess['stdout']>;
35
+ readonly stderr: NonNullable<ChildProcess['stderr']>;
36
+ };
37
+
38
+ export const directWorkerCommand = (
39
+ request: SubagentDelegationRequest,
40
+ ): ReadonlyArray<string> => [
41
+ '--no-session',
42
+ '--mode',
43
+ 'json',
44
+ ...(request.model ? ['--model', request.model] : []),
45
+ ...(request.thinking ? ['--thinking', request.thinking] : []),
46
+ '--print',
47
+ request.task,
48
+ ];
49
+
50
+ export function directWorkerResponse(
51
+ request: SubagentDelegationRequest,
52
+ code: number | null,
53
+ signal: NodeJS.Signals | null,
54
+ stderr: string,
55
+ ): SubagentDelegationResponse {
56
+ const status = code === 0 ? 'completed' : signal ? 'cancelled' : 'failed';
57
+ return {
58
+ requestId: request.requestId,
59
+ agent: request.agent,
60
+ status,
61
+ ...(code === null ? {} : { exitCode: code }),
62
+ ...(status !== 'completed' && stderr.trim()
63
+ ? { error: stderr.trim().slice(-4_000) }
64
+ : {}),
65
+ };
66
+ }
67
+
68
+ type WorkerJsonEvent = {
69
+ readonly type?: unknown;
70
+ readonly toolName?: unknown;
71
+ readonly args?: unknown;
72
+ readonly message?: { readonly role?: unknown };
73
+ readonly assistantMessageEvent?: {
74
+ readonly type?: unknown;
75
+ readonly delta?: unknown;
76
+ };
77
+ };
78
+
79
+ type WorkerProgress = {
80
+ readonly toolCount: number;
81
+ readonly responseText: string;
82
+ readonly update?: SubagentDelegationUpdate;
83
+ };
84
+
85
+ const MAX_PROGRESS_DETAIL_CHARS = 480;
86
+ const SECRET_KEY = /authorization|cookie|password|secret|token|api[-_]?key/i;
87
+
88
+ function redactProgressValue(value: unknown, key = ''): unknown {
89
+ if (SECRET_KEY.test(key)) return '[redacted]';
90
+ if (typeof value === 'string') {
91
+ return value.length > MAX_PROGRESS_DETAIL_CHARS
92
+ ? `${value.slice(0, MAX_PROGRESS_DETAIL_CHARS - 1)}…`
93
+ : value;
94
+ }
95
+ if (Array.isArray(value))
96
+ return value.map((item) => redactProgressValue(item));
97
+ if (value && typeof value === 'object') {
98
+ return Object.fromEntries(
99
+ Object.entries(value).map(([entryKey, entryValue]) => [
100
+ entryKey,
101
+ redactProgressValue(entryValue, entryKey),
102
+ ]),
103
+ );
104
+ }
105
+ return value;
106
+ }
107
+
108
+ function formatToolCall(toolName: string, args: unknown): string {
109
+ const rendered = JSON.stringify(redactProgressValue(args));
110
+ return `call ${toolName} ${rendered}`.slice(0, MAX_PROGRESS_DETAIL_CHARS);
111
+ }
112
+
113
+ /** Converts one Pi JSONL event into safe, operator-visible worker progress. */
114
+ export function workerProgressFromJsonLine(
115
+ line: string,
116
+ requestId: string,
117
+ toolCount: number,
118
+ responseText = '',
119
+ ): WorkerProgress {
120
+ let event: WorkerJsonEvent;
121
+ try {
122
+ const parsed: unknown = JSON.parse(line);
123
+ if (typeof parsed !== 'object' || parsed === null)
124
+ return { toolCount, responseText };
125
+ event = parsed;
126
+ } catch {
127
+ return { toolCount, responseText };
128
+ }
129
+ if (event.type === 'agent_start') {
130
+ return {
131
+ toolCount,
132
+ responseText,
133
+ update: { requestId, activity: 'thinking', toolCount },
134
+ };
135
+ }
136
+ if (event.type === 'message_start' && event.message?.role === 'assistant') {
137
+ return { toolCount, responseText: '' };
138
+ }
139
+ if (
140
+ event.type === 'message_update' &&
141
+ event.assistantMessageEvent?.type === 'text_delta' &&
142
+ typeof event.assistantMessageEvent.delta === 'string'
143
+ ) {
144
+ const nextResponseText =
145
+ `${responseText}${event.assistantMessageEvent.delta}`.slice(
146
+ -MAX_PROGRESS_DETAIL_CHARS,
147
+ );
148
+ return {
149
+ toolCount,
150
+ responseText: nextResponseText,
151
+ update: {
152
+ requestId,
153
+ activity: 'responding',
154
+ detail: `response: ${nextResponseText}`,
155
+ toolCount,
156
+ },
157
+ };
158
+ }
159
+ if (
160
+ (event.type === 'tool_execution_start' ||
161
+ event.type === 'tool_execution_update') &&
162
+ typeof event.toolName === 'string'
163
+ ) {
164
+ const nextToolCount =
165
+ event.type === 'tool_execution_start' ? toolCount + 1 : toolCount;
166
+ return {
167
+ toolCount: nextToolCount,
168
+ responseText,
169
+ update: {
170
+ requestId,
171
+ currentTool: event.toolName,
172
+ ...(event.type === 'tool_execution_start'
173
+ ? { detail: formatToolCall(event.toolName, event.args) }
174
+ : {}),
175
+ toolCount: nextToolCount,
176
+ },
177
+ };
178
+ }
179
+ return { toolCount, responseText };
180
+ }
181
+
44
182
  export function createSubagentDelegationClient(
45
- events: SubagentEventBus,
46
- dependencies: SubagentDelegationClientDependencies = DEFAULT_CLIENT_DEPENDENCIES,
183
+ spawnWorker: DirectWorkerSpawn = spawn,
47
184
  ): SubagentDelegationClientController {
48
- let active: ActiveDelegation | undefined;
185
+ let active: { requestId: string; process: ChildProcess } | undefined;
49
186
 
50
187
  const delegate = (
51
188
  request: SubagentDelegationRequest,
@@ -53,45 +190,67 @@ export function createSubagentDelegationClient(
53
190
  ): Promise<SubagentDelegationResponse> => {
54
191
  if (active) {
55
192
  return Promise.reject(
56
- new Error(`subagent request "${active.requestId}" is still active`),
193
+ new Error(`workflow worker "${active.requestId}" is still active`),
57
194
  );
58
195
  }
59
196
  if (options.signal?.aborted) {
60
- return Promise.reject(new Error('subagent delegation was cancelled'));
197
+ return Promise.reject(new Error('workflow worker was cancelled'));
61
198
  }
62
-
63
- const delegation = createDelegation({
64
- events,
65
- request,
66
- options,
67
- dependencies,
68
- releaseActive: (requestId) => {
69
- if (active?.requestId === requestId) active = undefined;
70
- },
71
- });
72
- active = delegation.active;
73
- delegation.start();
74
- return delegation.promise;
75
- };
76
-
77
- const cancelActiveAndWait = (waitMs = 5_000): Promise<boolean> => {
78
- const current = active;
79
- if (!current) return Promise.resolve(true);
80
-
81
- current.requestCancellation();
82
- return new Promise<boolean>((resolve) => {
83
- let isFinished = false;
84
- const finish = (isConfirmed: boolean): void => {
85
- if (isFinished) return;
86
- isFinished = true;
87
- dependencies.cancelTimeout(timer);
88
- resolve(isConfirmed);
199
+ return new Promise((resolve, reject) => {
200
+ const child = spawnWorker('pi', [...directWorkerCommand(request)], {
201
+ cwd: request.cwd,
202
+ env: {
203
+ ...process.env,
204
+ PI_WORKFLOWS_CHILD: '1',
205
+ PI_WORKFLOWS_CHILD_AGENT: request.agent,
206
+ },
207
+ stdio: ['ignore', 'pipe', 'pipe'],
208
+ });
209
+ active = { requestId: request.requestId, process: child };
210
+ let stderr = '';
211
+ let stdoutBuffer = '';
212
+ let toolCount = 0;
213
+ let responseText = '';
214
+ const stdoutDecoder = new StringDecoder('utf8');
215
+ const consumeWorkerLines = (): void => {
216
+ while (true) {
217
+ const newline = stdoutBuffer.indexOf('\n');
218
+ if (newline === -1) return;
219
+ const line = stdoutBuffer.slice(0, newline);
220
+ stdoutBuffer = stdoutBuffer.slice(newline + 1);
221
+ const progress = workerProgressFromJsonLine(
222
+ line,
223
+ request.requestId,
224
+ toolCount,
225
+ responseText,
226
+ );
227
+ toolCount = progress.toolCount;
228
+ responseText = progress.responseText;
229
+ if (progress.update) options.onUpdate?.(progress.update);
230
+ }
231
+ };
232
+ const consumeWorkerOutput = (chunk: Buffer): void => {
233
+ stdoutBuffer += stdoutDecoder.write(chunk);
234
+ consumeWorkerLines();
89
235
  };
90
- const timer = dependencies.scheduleTimeout(() => {
91
- finish(false);
92
- }, waitMs);
93
- void current.terminal.then(() => {
94
- finish(true);
236
+ child.stdout.on('data', consumeWorkerOutput);
237
+ child.stderr.on('data', (chunk: Buffer) => {
238
+ stderr += chunk.toString('utf8');
239
+ });
240
+ const abort = (): void => {
241
+ child.kill('SIGTERM');
242
+ };
243
+ options.signal?.addEventListener('abort', abort, { once: true });
244
+ child.once('error', (error) => {
245
+ if (active?.process === child) active = undefined;
246
+ reject(error);
247
+ });
248
+ child.once('close', (code, signal) => {
249
+ stdoutBuffer += stdoutDecoder.end();
250
+ consumeWorkerLines();
251
+ if (active?.process === child) active = undefined;
252
+ options.signal?.removeEventListener('abort', abort);
253
+ resolve(directWorkerResponse(request, code, signal, stderr));
95
254
  });
96
255
  });
97
256
  };
@@ -101,48 +260,31 @@ export function createSubagentDelegationClient(
101
260
  return active?.requestId;
102
261
  },
103
262
  delegate,
104
- cancelActiveAndWait,
263
+ async cancelActiveAndWait(): Promise<boolean> {
264
+ const current = active;
265
+ if (!current) return true;
266
+ current.process.kill('SIGTERM');
267
+ return new Promise((resolve) => {
268
+ current.process.once('close', () => {
269
+ resolve(true);
270
+ });
271
+ });
272
+ },
105
273
  };
106
274
  }
107
275
 
108
- /**
109
- * Coordinates one foreground pi-subagents request at a time.
110
- *
111
- * Event and timer boundaries are constructor-injected; production defaults
112
- * preserve the original runtime behavior.
113
- */
114
276
  export class SubagentDelegationClient implements SubagentDelegationClientController {
115
- readonly #controller: SubagentDelegationClientController;
116
-
117
- /**
118
- * Creates a client over the supplied event and timer boundaries.
119
- */
120
- constructor(
121
- events: SubagentEventBus,
122
- dependencies: SubagentDelegationClientDependencies = DEFAULT_CLIENT_DEPENDENCIES,
123
- ) {
124
- this.#controller = createSubagentDelegationClient(events, dependencies);
125
- }
126
-
127
- /** Returns the request currently owned by this client, if any. */
277
+ readonly #client = createSubagentDelegationClient();
128
278
  get activeRequestId(): string | undefined {
129
- return this.#controller.activeRequestId;
279
+ return this.#client.activeRequestId;
130
280
  }
131
-
132
- /**
133
- * Starts a correlated delegation and resolves on its terminal event.
134
- */
135
281
  delegate(
136
282
  request: SubagentDelegationRequest,
137
- options: DelegateOptions = {},
283
+ options?: DelegateOptions,
138
284
  ): Promise<SubagentDelegationResponse> {
139
- return this.#controller.delegate(request, options);
285
+ return this.#client.delegate(request, options);
140
286
  }
141
-
142
- /**
143
- * Requests cancellation and waits briefly for terminal confirmation.
144
- */
145
- cancelActiveAndWait(waitMs = 5_000): Promise<boolean> {
146
- return this.#controller.cancelActiveAndWait(waitMs);
287
+ cancelActiveAndWait(waitMs?: number): Promise<boolean> {
288
+ return this.#client.cancelActiveAndWait(waitMs);
147
289
  }
148
290
  }
@@ -1,14 +1,3 @@
1
- import type {
2
- SubagentDelegationRequest as UpstreamDelegationRequest,
3
- SubagentDelegationResponse as UpstreamDelegationResponse,
4
- SubagentDelegationStatus as UpstreamDelegationStatus,
5
- SubagentDelegationUpdate as UpstreamDelegationUpdate,
6
- } from 'pi-subagents/delegation';
7
-
8
- // These released v1 transport values are duplicated as literals because
9
- // pi-subagents 0.36.0 exports TypeScript source. Node's native type stripping
10
- // cannot execute TypeScript below node_modules; the public types above remain
11
- // the compile-time compatibility check.
12
1
  export const SUBAGENT_DELEGATION_PROTOCOL_VERSION = 1 as const;
13
2
  export const SUBAGENT_DELEGATION_REQUEST_EVENT =
14
3
  'prompt-template:subagent:request';
@@ -21,7 +10,35 @@ export const SUBAGENT_DELEGATION_RESPONSE_EVENT =
21
10
  export const SUBAGENT_DELEGATION_CANCEL_EVENT =
22
11
  'prompt-template:subagent:cancel';
23
12
 
24
- export type SubagentDelegationRequest = UpstreamDelegationRequest;
25
- export type SubagentDelegationUpdate = UpstreamDelegationUpdate;
26
- export type SubagentDelegationStatus = UpstreamDelegationStatus;
27
- export type SubagentDelegationResponse = UpstreamDelegationResponse;
13
+ export type SubagentDelegationRequest = {
14
+ readonly version: 1;
15
+ readonly requestId: string;
16
+ readonly agent: string;
17
+ readonly task: string;
18
+ readonly cwd: string;
19
+ readonly timeoutMs?: number;
20
+ readonly model?: string;
21
+ readonly thinking?:
22
+ 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
23
+ readonly [key: string]: unknown;
24
+ };
25
+ export type SubagentDelegationUpdate = {
26
+ readonly requestId: string;
27
+ readonly activity?: string;
28
+ /** Operator-visible detail; never used as workflow instruction or input. */
29
+ readonly detail?: string;
30
+ readonly currentTool?: string;
31
+ readonly toolCount?: number;
32
+ readonly tokens?: number;
33
+ };
34
+ export type SubagentDelegationStatus = 'completed' | 'failed' | 'cancelled';
35
+
36
+ export type SubagentDelegationResponse = {
37
+ readonly version?: number;
38
+ readonly requestId: string;
39
+ readonly agent?: string;
40
+ readonly status: SubagentDelegationStatus;
41
+ readonly error?: string;
42
+ readonly exitCode?: number;
43
+ readonly warnings?: ReadonlyArray<string>;
44
+ };
@@ -11,7 +11,7 @@ export type {
11
11
  ChildStepPolicy,
12
12
  ExtractedChildPolicy,
13
13
  } from './child-policy-types.ts';
14
- export { isSubagentRuntimeName } from './child-policy-validation.ts';
14
+ export { isAgentProfileName } from './child-policy-validation.ts';
15
15
  export { parseDelegatedStepResult } from './delegated-result.ts';
16
16
  export type { DelegatedStepResult } from './delegated-result.ts';
17
17
  export {
package/src/preflight.ts CHANGED
@@ -69,19 +69,11 @@ export function preflightStep(
69
69
  const extensionResources = [...inventory.tools, ...inventory.commands];
70
70
  const hasExtension = (extension: string): boolean =>
71
71
  extensionResources.some((resource) => sourceMatches(resource, extension));
72
- const hasSubagentTool = inventory.tools.some(
73
- (tool) => tool.name === 'subagent' && sourceMatches(tool, 'pi-subagents'),
74
- );
75
72
  const isPlannotatorRequired =
76
73
  step.gate?.provider === 'plannotator' &&
77
74
  !step.requires.extensions.includes('plannotator');
78
75
 
79
76
  return [
80
- ...(step.subagent && !hasSubagentTool
81
- ? [
82
- 'pi-subagents is required, but its "subagent" tool is not installed or detectable',
83
- ]
84
- : []),
85
77
  ...missingRequiredResources({
86
78
  requiredNames: step.requires.tools,
87
79
  hasResource: (toolName) => toolNames.has(toolName),
@@ -20,22 +20,15 @@ export function buildMainWorkflowNotice(
20
20
  throw new Error(`unknown workflow step "${run.currentStepId}"`);
21
21
  }
22
22
 
23
- if (!step.subagent) {
24
- return [
25
- '# Active main-agent workflow',
26
- '',
27
- `Workflow "${workflow.definition.id}" is running step "${run.currentStepId}" (${step.title}) in this session.`,
28
- 'Perform only the active workflow step with its allowed resources.',
29
- 'Call `workflow_complete_step` exactly once when finished.',
30
- 'Use `/workflow-pause` to halt and repair the workflow before resuming.',
31
- ].join('\n');
32
- }
33
-
34
23
  return [
35
- '# Active subagent workflow',
24
+ '# Active workflow',
36
25
  '',
37
- `Workflow "${workflow.definition.id}" is running step "${run.currentStepId}" (${step.title}) in a separate pi-subagents child process.`,
38
- 'Do not perform the workflow step in this main session.',
39
- `Use \`${statusShortcutLabel}\` or \`/workflow-status\` to open the workflow status overlay, or \`/workflow-pause\` to cancel the child and repair the workflow before resuming.`,
26
+ `Workflow "${workflow.definition.id}" is running step "${run.currentStepId}" (${step.title}) in this session.`,
27
+ ...(step.agent
28
+ ? [`Apply the "${step.agent.name}" workflow role prompt.`]
29
+ : []),
30
+ 'Perform only the active workflow step with its allowed resources.',
31
+ 'Call `workflow_complete_step` exactly once when finished.',
32
+ `Use \`${statusShortcutLabel}\` or \`/workflow-status\` to inspect status, or \`/workflow-pause\` to halt and repair the workflow before resuming.`,
40
33
  ].join('\n');
41
34
  }
@@ -1,3 +1,4 @@
1
+ import { loadAgentProfile } from '../agents/profile.ts';
1
2
  import type { LoadedWorkflow, WorkflowStep } from '../config/types.ts';
2
3
  import type { WorkflowRun } from '../engine/state.ts';
3
4
  import { createStepContract } from './step-contract.ts';
@@ -16,6 +17,8 @@ import {
16
17
 
17
18
  type StepExecution = 'delegated' | 'main';
18
19
 
20
+ const rolePrompt = (name: string): string => loadAgentProfile(name).prompt;
21
+
19
22
  type BuildStepTaskOptions =
20
23
  | {
21
24
  readonly execution: 'delegated';
@@ -123,15 +126,13 @@ export function buildStepTask(options: BuildStepTaskOptions): string {
123
126
  `Run: ${run.runId}`,
124
127
  `Iteration: ${run.iteration ?? 1}`,
125
128
  `Step: ${run.currentStepId} (${step.title})`,
126
- ...(isDelegated
127
- ? [
128
- `Agent profile: ${step.subagent?.agent ?? 'generalist'}`,
129
- 'Context: fresh workflow-step context; no parent or sibling transcript is inherited.',
130
- ]
131
- : []),
129
+ ...(step.agent ? [`Agent profile: ${step.agent.name}`] : []),
132
130
  '',
133
131
  '## Step instructions',
134
132
  '',
133
+ ...(step.agent
134
+ ? ['## Role prompt', '', rolePrompt(step.agent.name), '']
135
+ : []),
135
136
  prompt,
136
137
  '',
137
138
  ...(isDelegated ? buildDelegatedHandoffSection(handoff) : []),
@@ -26,7 +26,11 @@ export function formatWorkflowProgressStatus(
26
26
  );
27
27
  const activity =
28
28
  run.status === 'awaiting-gate' ? 'awaiting review' : 'working';
29
- return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity} · ${statusShortcutLabel}`;
29
+ const workerProgress =
30
+ snapshot.execution?.kind === 'subagent'
31
+ ? ` · ${snapshot.execution.progress}`
32
+ : '';
33
+ return `${workflowStatusIcon(run, snapshot.now)} ${run.workflowId} · step ${currentStep} · ${activity}${workerProgress} · ${statusShortcutLabel}`;
30
34
  }
31
35
 
32
36
  /** Format a plain-text workflow status suitable for fallback notifications. */