@wichayutdew/pi-workflows 2.7.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1505,17 +1505,19 @@ var directWorkerCommand = (request) => [
1505
1505
  "--print",
1506
1506
  request.task
1507
1507
  ];
1508
- function directWorkerResponse(request, code, signal, stderr) {
1508
+ function directWorkerResponse(request, code, signal, stderr, diagnostic) {
1509
1509
  const status = code === 0 ? "completed" : signal ? "cancelled" : "failed";
1510
1510
  return {
1511
1511
  requestId: request.requestId,
1512
1512
  agent: request.agent,
1513
1513
  status,
1514
1514
  ...code === null ? {} : { exitCode: code },
1515
- ...status !== "completed" && stderr.trim() ? { error: stderr.trim().slice(-4000) } : {}
1515
+ ...status !== "completed" && stderr.trim() ? { error: stderr.trim().slice(-4000) } : {},
1516
+ ...diagnostic ? { diagnostic } : {}
1516
1517
  };
1517
1518
  }
1518
1519
  var MAX_PROGRESS_DETAIL_CHARS = 480;
1520
+ var MAX_DIAGNOSTIC_CALLS = 64;
1519
1521
  var SECRET_KEY = /authorization|cookie|password|secret|token|api[-_]?key/i;
1520
1522
  function redactProgressValue(value, key = "") {
1521
1523
  if (SECRET_KEY.test(key))
@@ -1537,6 +1539,52 @@ function formatToolCall(toolName, args) {
1537
1539
  const rendered = JSON.stringify(redactProgressValue(args));
1538
1540
  return `call ${toolName} ${rendered}`.slice(0, MAX_PROGRESS_DETAIL_CHARS);
1539
1541
  }
1542
+ var createDiagnostic2 = () => ({
1543
+ settled: false,
1544
+ truncated: false,
1545
+ calls: new Map
1546
+ });
1547
+ var diagnosticSnapshot = (diagnostic) => ({
1548
+ settled: diagnostic.settled,
1549
+ truncated: diagnostic.truncated,
1550
+ calls: [...diagnostic.calls.values()]
1551
+ });
1552
+ var recordWorkerDiagnostic = (line, diagnostic) => {
1553
+ let event;
1554
+ try {
1555
+ const parsed = JSON.parse(line);
1556
+ if (typeof parsed !== "object" || parsed === null)
1557
+ return;
1558
+ event = parsed;
1559
+ } catch {
1560
+ return;
1561
+ }
1562
+ if (event.type === "agent_settled") {
1563
+ diagnostic.settled = true;
1564
+ return;
1565
+ }
1566
+ if (event.type !== "tool_execution_start" && event.type !== "tool_execution_end" || typeof event.toolName !== "string" || typeof event.toolCallId !== "string") {
1567
+ return;
1568
+ }
1569
+ if (!diagnostic.calls.has(event.toolCallId)) {
1570
+ if (diagnostic.calls.size >= MAX_DIAGNOSTIC_CALLS) {
1571
+ diagnostic.truncated = true;
1572
+ return;
1573
+ }
1574
+ diagnostic.calls.set(event.toolCallId, {
1575
+ id: event.toolCallId,
1576
+ name: event.toolName,
1577
+ state: "started"
1578
+ });
1579
+ }
1580
+ if (event.type === "tool_execution_end") {
1581
+ diagnostic.calls.set(event.toolCallId, {
1582
+ id: event.toolCallId,
1583
+ name: event.toolName,
1584
+ state: event.isError === false ? "completed" : "failed"
1585
+ });
1586
+ }
1587
+ };
1540
1588
  function workerProgressFromJsonLine(line, requestId, toolCount, responseText = "") {
1541
1589
  let event;
1542
1590
  try {
@@ -1609,6 +1657,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1609
1657
  let stdoutBuffer = "";
1610
1658
  let toolCount = 0;
1611
1659
  let responseText = "";
1660
+ const diagnostic = createDiagnostic2();
1612
1661
  const stdoutDecoder = new StringDecoder("utf8");
1613
1662
  const consumeWorkerLines = () => {
1614
1663
  while (true) {
@@ -1618,6 +1667,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1618
1667
  return;
1619
1668
  const line = stdoutBuffer.slice(0, newline);
1620
1669
  stdoutBuffer = stdoutBuffer.slice(newline + 1);
1670
+ recordWorkerDiagnostic(line, diagnostic);
1621
1671
  const progress = workerProgressFromJsonLine(line, request.requestId, toolCount, responseText);
1622
1672
  toolCount = progress.toolCount;
1623
1673
  responseText = progress.responseText;
@@ -1648,7 +1698,7 @@ function createSubagentDelegationClient(spawnWorker = spawn) {
1648
1698
  if (active?.process === child)
1649
1699
  active = undefined;
1650
1700
  options.signal?.removeEventListener("abort", abort);
1651
- resolve3(directWorkerResponse(request, code, signal, stderr));
1701
+ resolve3(directWorkerResponse(request, code, signal, stderr, diagnosticSnapshot(diagnostic)));
1652
1702
  });
1653
1703
  });
1654
1704
  };
@@ -6563,6 +6613,26 @@ function createStepExecutionActions() {
6563
6613
  };
6564
6614
  }
6565
6615
 
6616
+ // src/integrations/subagents/diagnostics.ts
6617
+ var READ_ONLY_TOOLS = new Set([
6618
+ "read",
6619
+ "ls",
6620
+ "grep",
6621
+ "structured_output"
6622
+ ]);
6623
+ var classifyRecoverySafety = (diagnostic) => {
6624
+ if (!diagnostic || !diagnostic.settled || diagnostic.truncated) {
6625
+ return "incomplete";
6626
+ }
6627
+ if (diagnostic.calls.some((call) => call.state !== "completed" || !READ_ONLY_TOOLS.has(call.name))) {
6628
+ return "unsafe";
6629
+ }
6630
+ return "read-only";
6631
+ };
6632
+
6633
+ // src/harness/delegation-recovery.ts
6634
+ var shouldRetryMissingCompletion = (diagnostic, subagentAttemptCount) => subagentAttemptCount === 1 && classifyRecoverySafety(diagnostic) === "read-only";
6635
+
6566
6636
  // src/harness/delegation-response-actions.ts
6567
6637
  function hasErrorCode(error, code) {
6568
6638
  return error instanceof Error && "code" in error && error.code === code;
@@ -6638,7 +6708,15 @@ async function finishDelegation(active, response) {
6638
6708
  serializedResult = await this.dependencies.readDelegatedResult(active);
6639
6709
  } catch (error) {
6640
6710
  if (hasErrorCode(error, "ENOENT")) {
6641
- throw new Error(`Subagent "${active.agent}" completed without producing the required correlated structured_output result`, { cause: error });
6711
+ const subagentAttemptCount = this.run.currentStepAttempts?.filter((attempt) => attempt.kind === "subagent").length ?? 0;
6712
+ if (shouldRetryMissingCompletion(response.diagnostic, subagentAttemptCount)) {
6713
+ cleanupAttempted = true;
6714
+ await this.cleanupDelegation(active);
6715
+ this.launchCurrentStep(workflow);
6716
+ return;
6717
+ }
6718
+ const diagnosticState = response.diagnostic ? `settled=${response.diagnostic.settled}, truncated=${response.diagnostic.truncated}, calls=${response.diagnostic.calls.length}` : "unavailable";
6719
+ throw new Error(`Subagent "${active.agent}" completed without producing the required correlated structured_output result (request ${active.requestId}; diagnostic ${diagnosticState})`, { cause: error });
6642
6720
  }
6643
6721
  throw error;
6644
6722
  }
@@ -7521,6 +7599,24 @@ var DEFAULT_CHILD_RUNTIME_DEPENDENCIES = {
7521
7599
  tokensAreEqual
7522
7600
  };
7523
7601
 
7602
+ // src/integrations/subagents/child-runtime-repair.ts
7603
+ var COMPLETION_REPAIR_PROMPT = [
7604
+ "The delegated step settled without its required correlated result.",
7605
+ "Do not repeat completed work and do not execute work tools.",
7606
+ "Call `structured_output` exactly once, alone, with one configured outcome and the required summary, artifact, and workspace fields."
7607
+ ].join(`
7608
+ `);
7609
+ var needsCompletionRepair = ({
7610
+ policy,
7611
+ dependencies
7612
+ }) => {
7613
+ try {
7614
+ return !dependencies.fileSystem.exists(policy.resultPath);
7615
+ } catch {
7616
+ return false;
7617
+ }
7618
+ };
7619
+
7524
7620
  // src/integrations/subagents/child-runtime-files.ts
7525
7621
  var verifyChildWorkingDirectory = (policy, dependencies) => {
7526
7622
  let expected;
@@ -7618,7 +7714,8 @@ var INITIAL_STATE = {
7618
7714
  activePolicy: undefined,
7619
7715
  policyError: undefined,
7620
7716
  invalidCompletionCalls: new Set,
7621
- effectiveTools: new Set
7717
+ effectiveTools: new Set,
7718
+ repairRequested: false
7622
7719
  };
7623
7720
  var errorMessage2 = (error) => error instanceof Error ? error.message : String(error);
7624
7721
  var invalidPolicyInput = (pi, policyError, images) => {
@@ -7684,7 +7781,8 @@ var registerSubagentChildRuntime = (pi, options = {}) => {
7684
7781
  ...state,
7685
7782
  activePolicy: extracted.policy,
7686
7783
  policyError: undefined,
7687
- effectiveTools
7784
+ effectiveTools,
7785
+ repairRequested: false
7688
7786
  };
7689
7787
  } catch (error) {
7690
7788
  const policyError = errorMessage2(error);
@@ -7717,6 +7815,19 @@ ${childSystemPrompt(state.activePolicy)}`
7717
7815
  pi.on("turn_start", () => {
7718
7816
  state = { ...state, invalidCompletionCalls: new Set };
7719
7817
  });
7818
+ pi.on("agent_settled", () => {
7819
+ const policy = state.activePolicy;
7820
+ if (!policy || state.repairRequested || !needsCompletionRepair({ policy, dependencies })) {
7821
+ return;
7822
+ }
7823
+ state = {
7824
+ ...state,
7825
+ repairRequested: true,
7826
+ effectiveTools: new Set([CHILD_COMPLETION_TOOL])
7827
+ };
7828
+ pi.setActiveTools([CHILD_COMPLETION_TOOL]);
7829
+ pi.sendUserMessage(COMPLETION_REPAIR_PROMPT, { deliverAs: "followUp" });
7830
+ });
7720
7831
  pi.on("message_end", (event) => {
7721
7832
  if (!state.activePolicy)
7722
7833
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wichayutdew/pi-workflows",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "A declarative, pauseable workflow harness for Pi",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -0,0 +1,15 @@
1
+ import {
2
+ classifyRecoverySafety,
3
+ type DelegationDiagnostic,
4
+ } from '../integrations/subagents/diagnostics.ts';
5
+
6
+ /**
7
+ * Allows one fresh retry only after the same-child repair settled with complete
8
+ * read-only evidence. The caller owns preserving run identity and cleanup.
9
+ */
10
+ export const shouldRetryMissingCompletion = (
11
+ diagnostic: DelegationDiagnostic | undefined,
12
+ subagentAttemptCount: number,
13
+ ): boolean =>
14
+ subagentAttemptCount === 1 &&
15
+ classifyRecoverySafety(diagnostic) === 'read-only';
@@ -9,6 +9,7 @@ import type { WorkflowStepResult } from '../runtime/step-result.ts';
9
9
  import type { HarnessActionContext as FullHarnessActionContext } from './action-context.ts';
10
10
  import type { ActiveDelegation } from './types.ts';
11
11
  import { resolveStepEffects } from './step-effects.ts';
12
+ import { shouldRetryMissingCompletion } from './delegation-recovery.ts';
12
13
 
13
14
  type HarnessActionContext = Pick<
14
15
  FullHarnessActionContext,
@@ -19,6 +20,7 @@ type HarnessActionContext = Pick<
19
20
  | 'finishDelegation'
20
21
  | 'isSessionActive'
21
22
  | 'latestContext'
23
+ | 'launchCurrentStep'
22
24
  | 'mutationQueue'
23
25
  | 'pauseForDelegationFailure'
24
26
  | 'releaseMainAfterCancellation'
@@ -174,8 +176,26 @@ async function finishDelegation(
174
176
  serializedResult = await this.dependencies.readDelegatedResult(active);
175
177
  } catch (error) {
176
178
  if (hasErrorCode(error, 'ENOENT')) {
179
+ const subagentAttemptCount =
180
+ this.run.currentStepAttempts?.filter(
181
+ (attempt) => attempt.kind === 'subagent',
182
+ ).length ?? 0;
183
+ if (
184
+ shouldRetryMissingCompletion(
185
+ response.diagnostic,
186
+ subagentAttemptCount,
187
+ )
188
+ ) {
189
+ cleanupAttempted = true;
190
+ await this.cleanupDelegation(active);
191
+ this.launchCurrentStep(workflow);
192
+ return;
193
+ }
194
+ const diagnosticState = response.diagnostic
195
+ ? `settled=${response.diagnostic.settled}, truncated=${response.diagnostic.truncated}, calls=${response.diagnostic.calls.length}`
196
+ : 'unavailable';
177
197
  throw new Error(
178
- `Subagent "${active.agent}" completed without producing the required correlated structured_output result`,
198
+ `Subagent "${active.agent}" completed without producing the required correlated structured_output result (request ${active.requestId}; diagnostic ${diagnosticState})`,
179
199
  { cause: error },
180
200
  );
181
201
  }
@@ -0,0 +1,23 @@
1
+ import type { ChildStepPolicy } from './child-policy-types.ts';
2
+ import type { SubagentChildRuntimeDependencies } from './child-runtime-types.ts';
3
+
4
+ export const COMPLETION_REPAIR_PROMPT = [
5
+ 'The delegated step settled without its required correlated result.',
6
+ 'Do not repeat completed work and do not execute work tools.',
7
+ 'Call `structured_output` exactly once, alone, with one configured outcome and the required summary, artifact, and workspace fields.',
8
+ ].join('\n');
9
+
10
+ /** Returns whether a same-child completion repair may be requested safely. */
11
+ export const needsCompletionRepair = ({
12
+ policy,
13
+ dependencies,
14
+ }: {
15
+ readonly policy: ChildStepPolicy;
16
+ readonly dependencies: SubagentChildRuntimeDependencies;
17
+ }): boolean => {
18
+ try {
19
+ return !dependencies.fileSystem.exists(policy.resultPath);
20
+ } catch {
21
+ return false;
22
+ }
23
+ };
@@ -13,6 +13,10 @@ import {
13
13
  parseChildStructuredResult,
14
14
  } from './child-runtime-completion.ts';
15
15
  import { DEFAULT_CHILD_RUNTIME_DEPENDENCIES } from './child-runtime-dependencies.ts';
16
+ import {
17
+ COMPLETION_REPAIR_PROMPT,
18
+ needsCompletionRepair,
19
+ } from './child-runtime-repair.ts';
16
20
  import {
17
21
  verifyChildCapability,
18
22
  verifyChildWorkingDirectory,
@@ -39,6 +43,7 @@ type ChildRuntimeState = {
39
43
  readonly policyError: string | undefined;
40
44
  readonly invalidCompletionCalls: ReadonlySet<string>;
41
45
  readonly effectiveTools: ReadonlySet<string>;
46
+ readonly repairRequested: boolean;
42
47
  };
43
48
 
44
49
  const INITIAL_STATE: ChildRuntimeState = {
@@ -46,6 +51,7 @@ const INITIAL_STATE: ChildRuntimeState = {
46
51
  policyError: undefined,
47
52
  invalidCompletionCalls: new Set(),
48
53
  effectiveTools: new Set(),
54
+ repairRequested: false,
49
55
  };
50
56
 
51
57
  const errorMessage = (error: unknown): string =>
@@ -142,6 +148,7 @@ export const registerSubagentChildRuntime = (
142
148
  activePolicy: extracted.policy,
143
149
  policyError: undefined,
144
150
  effectiveTools,
151
+ repairRequested: false,
145
152
  };
146
153
  } catch (error) {
147
154
  const policyError = errorMessage(error);
@@ -175,6 +182,24 @@ export const registerSubagentChildRuntime = (
175
182
  state = { ...state, invalidCompletionCalls: new Set() };
176
183
  });
177
184
 
185
+ pi.on('agent_settled', () => {
186
+ const policy = state.activePolicy;
187
+ if (
188
+ !policy ||
189
+ state.repairRequested ||
190
+ !needsCompletionRepair({ policy, dependencies })
191
+ ) {
192
+ return;
193
+ }
194
+ state = {
195
+ ...state,
196
+ repairRequested: true,
197
+ effectiveTools: new Set([CHILD_COMPLETION_TOOL]),
198
+ };
199
+ pi.setActiveTools([CHILD_COMPLETION_TOOL]);
200
+ pi.sendUserMessage(COMPLETION_REPAIR_PROMPT, { deliverAs: 'followUp' });
201
+ });
202
+
178
203
  pi.on('message_end', (event) => {
179
204
  if (!state.activePolicy) return;
180
205
  const invalid = invalidCompletionCallIds(
@@ -5,6 +5,10 @@ import type {
5
5
  SubagentDelegationResponse,
6
6
  SubagentDelegationUpdate,
7
7
  } from './protocol-events.ts';
8
+ import type {
9
+ DelegationDiagnostic,
10
+ DelegationDiagnosticCall,
11
+ } from './diagnostics.ts';
8
12
 
9
13
  export type DelegateOptions = {
10
14
  readonly signal?: AbortSignal;
@@ -52,6 +56,7 @@ export function directWorkerResponse(
52
56
  code: number | null,
53
57
  signal: NodeJS.Signals | null,
54
58
  stderr: string,
59
+ diagnostic?: DelegationDiagnostic,
55
60
  ): SubagentDelegationResponse {
56
61
  const status = code === 0 ? 'completed' : signal ? 'cancelled' : 'failed';
57
62
  return {
@@ -62,12 +67,15 @@ export function directWorkerResponse(
62
67
  ...(status !== 'completed' && stderr.trim()
63
68
  ? { error: stderr.trim().slice(-4_000) }
64
69
  : {}),
70
+ ...(diagnostic ? { diagnostic } : {}),
65
71
  };
66
72
  }
67
73
 
68
74
  type WorkerJsonEvent = {
69
75
  readonly type?: unknown;
76
+ readonly toolCallId?: unknown;
70
77
  readonly toolName?: unknown;
78
+ readonly isError?: unknown;
71
79
  readonly args?: unknown;
72
80
  readonly message?: { readonly role?: unknown };
73
81
  readonly assistantMessageEvent?: {
@@ -83,6 +91,7 @@ type WorkerProgress = {
83
91
  };
84
92
 
85
93
  const MAX_PROGRESS_DETAIL_CHARS = 480;
94
+ const MAX_DIAGNOSTIC_CALLS = 64;
86
95
  const SECRET_KEY = /authorization|cookie|password|secret|token|api[-_]?key/i;
87
96
 
88
97
  function redactProgressValue(value: unknown, key = ''): unknown {
@@ -110,6 +119,70 @@ function formatToolCall(toolName: string, args: unknown): string {
110
119
  return `call ${toolName} ${rendered}`.slice(0, MAX_PROGRESS_DETAIL_CHARS);
111
120
  }
112
121
 
122
+ type MutableDiagnostic = {
123
+ settled: boolean;
124
+ truncated: boolean;
125
+ calls: Map<string, DelegationDiagnosticCall>;
126
+ };
127
+
128
+ const createDiagnostic = (): MutableDiagnostic => ({
129
+ settled: false,
130
+ truncated: false,
131
+ calls: new Map(),
132
+ });
133
+
134
+ const diagnosticSnapshot = (
135
+ diagnostic: MutableDiagnostic,
136
+ ): DelegationDiagnostic => ({
137
+ settled: diagnostic.settled,
138
+ truncated: diagnostic.truncated,
139
+ calls: [...diagnostic.calls.values()],
140
+ });
141
+
142
+ const recordWorkerDiagnostic = (
143
+ line: string,
144
+ diagnostic: MutableDiagnostic,
145
+ ): void => {
146
+ let event: WorkerJsonEvent;
147
+ try {
148
+ const parsed: unknown = JSON.parse(line);
149
+ if (typeof parsed !== 'object' || parsed === null) return;
150
+ event = parsed;
151
+ } catch {
152
+ return;
153
+ }
154
+ if (event.type === 'agent_settled') {
155
+ diagnostic.settled = true;
156
+ return;
157
+ }
158
+ if (
159
+ (event.type !== 'tool_execution_start' &&
160
+ event.type !== 'tool_execution_end') ||
161
+ typeof event.toolName !== 'string' ||
162
+ typeof event.toolCallId !== 'string'
163
+ ) {
164
+ return;
165
+ }
166
+ if (!diagnostic.calls.has(event.toolCallId)) {
167
+ if (diagnostic.calls.size >= MAX_DIAGNOSTIC_CALLS) {
168
+ diagnostic.truncated = true;
169
+ return;
170
+ }
171
+ diagnostic.calls.set(event.toolCallId, {
172
+ id: event.toolCallId,
173
+ name: event.toolName,
174
+ state: 'started',
175
+ });
176
+ }
177
+ if (event.type === 'tool_execution_end') {
178
+ diagnostic.calls.set(event.toolCallId, {
179
+ id: event.toolCallId,
180
+ name: event.toolName,
181
+ state: event.isError === false ? 'completed' : 'failed',
182
+ });
183
+ }
184
+ };
185
+
113
186
  /** Converts one Pi JSONL event into safe, operator-visible worker progress. */
114
187
  export function workerProgressFromJsonLine(
115
188
  line: string,
@@ -211,6 +284,7 @@ export function createSubagentDelegationClient(
211
284
  let stdoutBuffer = '';
212
285
  let toolCount = 0;
213
286
  let responseText = '';
287
+ const diagnostic = createDiagnostic();
214
288
  const stdoutDecoder = new StringDecoder('utf8');
215
289
  const consumeWorkerLines = (): void => {
216
290
  while (true) {
@@ -218,6 +292,7 @@ export function createSubagentDelegationClient(
218
292
  if (newline === -1) return;
219
293
  const line = stdoutBuffer.slice(0, newline);
220
294
  stdoutBuffer = stdoutBuffer.slice(newline + 1);
295
+ recordWorkerDiagnostic(line, diagnostic);
221
296
  const progress = workerProgressFromJsonLine(
222
297
  line,
223
298
  request.requestId,
@@ -250,7 +325,15 @@ export function createSubagentDelegationClient(
250
325
  consumeWorkerLines();
251
326
  if (active?.process === child) active = undefined;
252
327
  options.signal?.removeEventListener('abort', abort);
253
- resolve(directWorkerResponse(request, code, signal, stderr));
328
+ resolve(
329
+ directWorkerResponse(
330
+ request,
331
+ code,
332
+ signal,
333
+ stderr,
334
+ diagnosticSnapshot(diagnostic),
335
+ ),
336
+ );
254
337
  });
255
338
  });
256
339
  };
@@ -0,0 +1,43 @@
1
+ export type DiagnosticCallState = 'completed' | 'failed' | 'started';
2
+
3
+ export type DelegationDiagnosticCall = {
4
+ readonly id: string;
5
+ readonly name: string;
6
+ readonly state: DiagnosticCallState;
7
+ };
8
+
9
+ export type DelegationDiagnostic = {
10
+ readonly settled: boolean;
11
+ readonly truncated: boolean;
12
+ readonly calls: ReadonlyArray<DelegationDiagnosticCall>;
13
+ };
14
+
15
+ export type RecoverySafety = 'read-only' | 'unsafe' | 'incomplete';
16
+
17
+ const READ_ONLY_TOOLS: ReadonlySet<string> = new Set([
18
+ 'read',
19
+ 'ls',
20
+ 'grep',
21
+ 'structured_output',
22
+ ]);
23
+
24
+ /**
25
+ * Decides whether a fresh child may safely repeat a step after same-child
26
+ * completion repair failed. Unknown, partial, and mutation-capable evidence
27
+ * always fails closed.
28
+ */
29
+ export const classifyRecoverySafety = (
30
+ diagnostic: DelegationDiagnostic | undefined,
31
+ ): RecoverySafety => {
32
+ if (!diagnostic || !diagnostic.settled || diagnostic.truncated) {
33
+ return 'incomplete';
34
+ }
35
+ if (
36
+ diagnostic.calls.some(
37
+ (call) => call.state !== 'completed' || !READ_ONLY_TOOLS.has(call.name),
38
+ )
39
+ ) {
40
+ return 'unsafe';
41
+ }
42
+ return 'read-only';
43
+ };
@@ -1,3 +1,5 @@
1
+ import type { DelegationDiagnostic } from './diagnostics.ts';
2
+
1
3
  export const SUBAGENT_DELEGATION_PROTOCOL_VERSION = 1 as const;
2
4
  export const SUBAGENT_DELEGATION_REQUEST_EVENT =
3
5
  'prompt-template:subagent:request';
@@ -41,4 +43,5 @@ export type SubagentDelegationResponse = {
41
43
  readonly error?: string;
42
44
  readonly exitCode?: number;
43
45
  readonly warnings?: ReadonlyArray<string>;
46
+ readonly diagnostic?: DelegationDiagnostic;
44
47
  };