@wichayutdew/pi-workflows 2.5.1 → 2.7.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 (103) hide show
  1. package/README.md +34 -1
  2. package/dist/index.js +1315 -2467
  3. package/examples/starter-kit/agents/planner.md +11 -0
  4. package/examples/starter-kit/agents/reviewer.md +10 -0
  5. package/examples/starter-kit/agents/scout.md +10 -0
  6. package/examples/starter-kit/agents/worker.md +11 -0
  7. package/examples/starter-kit/agents/workspace-preparer.md +10 -0
  8. package/examples/starter-kit/investigate.workflow.yaml +84 -0
  9. package/examples/starter-kit/jira.workflow.yaml +75 -0
  10. package/examples/starter-kit/mr-comment.workflow.yaml +86 -93
  11. package/examples/starter-kit/mr-review.workflow.yaml +52 -88
  12. package/examples/starter-kit/settings.yaml +4 -0
  13. package/examples/starter-kit/steps/investigate/investigate.md +40 -0
  14. package/examples/starter-kit/steps/investigate/retrieve.md +28 -0
  15. package/examples/starter-kit/steps/investigate/validate.md +20 -0
  16. package/examples/starter-kit/steps/jira/create.md +25 -0
  17. package/examples/starter-kit/steps/jira/draft.md +18 -0
  18. package/examples/starter-kit/steps/jira/plan.md +30 -0
  19. package/examples/starter-kit/steps/mr-comment/checkout-source.md +14 -0
  20. package/examples/starter-kit/steps/mr-comment/fetch.md +11 -28
  21. package/examples/starter-kit/steps/mr-comment/implement.md +9 -26
  22. package/examples/starter-kit/steps/mr-comment/plan.md +46 -55
  23. package/examples/starter-kit/steps/mr-comment/publish.md +11 -31
  24. package/examples/starter-kit/steps/mr-comment/verify.md +7 -34
  25. package/examples/starter-kit/steps/mr-review/fetch.md +13 -21
  26. package/examples/starter-kit/steps/mr-review/publish-approved.md +18 -0
  27. package/examples/starter-kit/steps/mr-review/review-for-approval.md +53 -0
  28. package/examples/starter-kit/steps/mr-review/verify-published.md +16 -0
  29. package/examples/starter-kit/steps/shared/prepare-workspace.md +11 -89
  30. package/examples/starter-kit/steps/shared/publish-remote.md +16 -0
  31. package/examples/starter-kit/steps/ticket/implement.md +10 -23
  32. package/examples/starter-kit/steps/ticket/plan.md +49 -98
  33. package/examples/starter-kit/steps/ticket/verify.md +14 -65
  34. package/examples/starter-kit/steps/work/implement.md +11 -22
  35. package/examples/starter-kit/steps/work/plan.md +43 -59
  36. package/examples/starter-kit/steps/work/verify.md +14 -25
  37. package/examples/starter-kit/ticket.workflow.yaml +57 -61
  38. package/examples/starter-kit/work.workflow.yaml +49 -57
  39. package/package.json +9 -19
  40. package/schemas/workflow.schema.json +63 -15
  41. package/scripts/patch-herdr-agent-state.mjs +36 -0
  42. package/src/agents/profile.ts +98 -0
  43. package/src/config/ceiling.ts +0 -82
  44. package/src/config/types.ts +15 -53
  45. package/src/config/validation/settings.ts +2 -14
  46. package/src/config/validation/step.ts +129 -8
  47. package/src/config/validation/workflow.ts +1 -10
  48. package/src/engine/run-workflow-validation.ts +0 -3
  49. package/src/engine/state-types.ts +1 -1
  50. package/src/harness/action-context.ts +1 -13
  51. package/src/harness/artifact-contract.ts +46 -0
  52. package/src/harness/delegation-control-actions.ts +4 -63
  53. package/src/harness/delegation-plan.ts +26 -80
  54. package/src/harness/delegation-response-actions.ts +22 -135
  55. package/src/harness/dependencies.ts +1 -12
  56. package/src/harness/gate-submission-action.ts +28 -0
  57. package/src/harness/status-actions.ts +20 -0
  58. package/src/harness/step-execution-actions.ts +2 -9
  59. package/src/harness/types.ts +2 -40
  60. package/src/harness.ts +2 -17
  61. package/src/herdr-workflow-state.ts +65 -0
  62. package/src/index.ts +4 -9
  63. package/src/integrations/subagents/child-policy-validation.ts +6 -7
  64. package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
  65. package/src/integrations/subagents/child-runtime-policy.ts +1 -7
  66. package/src/integrations/subagents/child-runtime.ts +18 -9
  67. package/src/integrations/subagents/client.ts +240 -98
  68. package/src/integrations/subagents/protocol-events.ts +32 -15
  69. package/src/integrations/subagents/protocol.ts +1 -1
  70. package/src/preflight.ts +0 -8
  71. package/src/prompt/main-workflow-notice.ts +8 -15
  72. package/src/prompt/step-task.ts +7 -6
  73. package/src/workflow-status/format-status.ts +5 -1
  74. package/src/workflow-status/render-step-detail.ts +94 -0
  75. package/src/workflow-status/types.ts +1 -0
  76. package/src/workflow-status/view.ts +49 -14
  77. package/agents/step.md +0 -32
  78. package/examples/mr-comments.workflow.yaml +0 -125
  79. package/examples/prompts/mr-comments/implement.md +0 -17
  80. package/examples/prompts/mr-comments/inspect.md +0 -5
  81. package/examples/prompts/mr-comments/plan.md +0 -54
  82. package/examples/prompts/mr-comments/verify.md +0 -9
  83. package/examples/settings.yaml +0 -27
  84. package/examples/starter-kit/steps/mr-review/publish.md +0 -48
  85. package/examples/starter-kit/steps/mr-review/review.md +0 -76
  86. package/examples/starter-kit/steps/mr-review/verify.md +0 -35
  87. package/src/config/validation/subagent.ts +0 -288
  88. package/src/harness/delegation-failure.ts +0 -248
  89. package/src/harness/delegation-recovery-validation.ts +0 -161
  90. package/src/harness/delegation-retry-policy.ts +0 -120
  91. package/src/integrations/subagents/client-delegation.ts +0 -181
  92. package/src/integrations/subagents/client-messages.ts +0 -66
  93. package/src/integrations/subagents/client-types.ts +0 -36
  94. package/src/integrations/subagents/diagnostic-format.ts +0 -45
  95. package/src/integrations/subagents/diagnostic-text.ts +0 -114
  96. package/src/integrations/subagents/diagnostic-types.ts +0 -83
  97. package/src/integrations/subagents/diagnostics.ts +0 -26
  98. package/src/integrations/subagents/failure-correlation.ts +0 -285
  99. package/src/integrations/subagents/failure-transcript.ts +0 -252
  100. package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
  101. package/src/integrations/subagents/replay-audit.ts +0 -146
  102. package/src/integrations/subagents/replay-safety.ts +0 -67
  103. package/src/integrations/subagents/session-diagnostics.ts +0 -357
@@ -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. */
@@ -262,6 +262,100 @@ function attemptDisplayOrdinal(
262
262
  return omittedAttempts + index + 1;
263
263
  }
264
264
 
265
+ function renderLiveWorkerSession(
266
+ theme: WorkflowStatusTheme,
267
+ snapshot: WorkflowStatusSnapshot,
268
+ detail: SelectedStepDetail,
269
+ width: number,
270
+ ): Array<string> {
271
+ if (snapshot.execution?.kind !== 'subagent') return [];
272
+ const attempt = detail.attempts.at(-1);
273
+ const events = snapshot.execution.activityLog ?? [];
274
+ const eventLines = events.flatMap((event) => {
275
+ const isToolCall = event.startsWith('call ');
276
+ const isResponse = event.startsWith('response: ');
277
+ const label = isToolCall
278
+ ? '● Tool call'
279
+ : isResponse
280
+ ? '● Assistant'
281
+ : '● Worker';
282
+ const value = isResponse ? event.slice('response: '.length) : event;
283
+ return [
284
+ theme.bold(theme.fg(isToolCall ? 'warning' : 'accent', label)),
285
+ ...wrapPlain(value, width, theme),
286
+ '',
287
+ ];
288
+ });
289
+ return [
290
+ '',
291
+ theme.bold(theme.fg('accent', 'Live Worker Session')),
292
+ ...keyValueLines(
293
+ theme,
294
+ 'worker',
295
+ snapshot.execution.agent,
296
+ width,
297
+ 'accent',
298
+ ),
299
+ ...keyValueLines(
300
+ theme,
301
+ 'state',
302
+ snapshot.execution.progress,
303
+ width,
304
+ 'accent',
305
+ ),
306
+ '',
307
+ theme.bold(theme.fg('accent', '● Input prompt')),
308
+ ...wrapPlain(
309
+ attempt
310
+ ? `${attempt.task}${
311
+ attempt.taskTruncated
312
+ ? `\n… [${attempt.omittedTaskChars ?? 0} prompt characters omitted from the bounded checkpoint trace]`
313
+ : ''
314
+ }`
315
+ : 'The worker prompt is being prepared.',
316
+ width,
317
+ theme,
318
+ ),
319
+ '',
320
+ theme.bold(theme.fg('accent', '● Live message chain')),
321
+ ...(eventLines.length > 0
322
+ ? eventLines
323
+ : [theme.fg('muted', 'Waiting for the worker to emit activity…'), '']),
324
+ ];
325
+ }
326
+
327
+ /** Renders the active worker's prompt and live event chain as a step subpage. */
328
+ export function renderLiveWorkerActivity(
329
+ theme: WorkflowStatusTheme,
330
+ snapshot: WorkflowStatusSnapshot,
331
+ selectedIndex: number,
332
+ width: number,
333
+ ): Array<string> {
334
+ const entry = buildPathEntries(snapshot)[selectedIndex];
335
+ const detail = selectedStepDetail(snapshot, selectedIndex);
336
+ if (!entry?.isCurrent || !detail || snapshot.execution?.kind !== 'subagent') {
337
+ return boxed(
338
+ theme,
339
+ 'Live Worker Session',
340
+ width,
341
+ [
342
+ theme.fg(
343
+ 'muted',
344
+ 'Live activity is available only for the active worker step.',
345
+ ),
346
+ ],
347
+ 'borderAccent',
348
+ );
349
+ }
350
+ return boxed(
351
+ theme,
352
+ `Live Worker Session · ${entry.title} · visit ${entry.visit}`,
353
+ width,
354
+ renderLiveWorkerSession(theme, snapshot, detail, width - 4),
355
+ 'borderAccent',
356
+ );
357
+ }
358
+
265
359
  /** Renders one selected execution-path row and its persisted evidence. */
266
360
  export function renderStepDetail(
267
361
  theme: WorkflowStatusTheme,
@@ -14,6 +14,7 @@ export type WorkflowStatusExecution =
14
14
  readonly agent: string;
15
15
  readonly requestId: string;
16
16
  readonly progress: string;
17
+ readonly activityLog?: ReadonlyArray<string>;
17
18
  };
18
19
 
19
20
  export type WorkflowStatusSnapshot = {