@wichayutdew/pi-workflows 0.2.3 → 1.0.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 (142) hide show
  1. package/README.md +66 -38
  2. package/dist/index.js +5903 -4782
  3. package/package.json +1 -1
  4. package/src/command-names.ts +7 -1
  5. package/src/commands.ts +126 -52
  6. package/src/config/catalog.ts +162 -0
  7. package/src/config/ceiling.ts +116 -116
  8. package/src/config/command-conflicts.ts +4 -4
  9. package/src/config/diagnostics.ts +28 -0
  10. package/src/config/load-settings.ts +37 -0
  11. package/src/config/load-types.ts +53 -0
  12. package/src/config/load-workflows.ts +135 -0
  13. package/src/config/load.ts +43 -311
  14. package/src/config/types.ts +137 -135
  15. package/src/config/validate.ts +12 -1261
  16. package/src/config/validation/permissions.ts +291 -0
  17. package/src/config/validation/prompt.ts +19 -0
  18. package/src/config/validation/settings.ts +122 -0
  19. package/src/config/validation/shared.ts +129 -0
  20. package/src/config/validation/shortcut.ts +118 -0
  21. package/src/config/validation/step.ts +228 -0
  22. package/src/config/validation/subagent.ts +288 -0
  23. package/src/config/validation/workflow.ts +156 -0
  24. package/src/config/yaml.ts +29 -0
  25. package/src/digest.ts +38 -6
  26. package/src/engine/checkpoint.ts +13 -9
  27. package/src/engine/create-run.ts +42 -0
  28. package/src/engine/gate-transitions.ts +184 -0
  29. package/src/engine/reconciliation-history.ts +88 -0
  30. package/src/engine/resume.ts +19 -7
  31. package/src/engine/run-advance.ts +122 -0
  32. package/src/engine/run-lifecycle.ts +124 -0
  33. package/src/engine/run-reconciliation.ts +116 -0
  34. package/src/engine/run-validation.ts +128 -0
  35. package/src/engine/state-types.ts +64 -0
  36. package/src/engine/state.ts +10 -193
  37. package/src/engine/transition-helpers.ts +28 -0
  38. package/src/engine/transition-types.ts +8 -0
  39. package/src/engine/transitions.ts +17 -471
  40. package/src/harness/action-context.ts +181 -0
  41. package/src/harness/catalog.ts +45 -0
  42. package/src/harness/context-idle.ts +21 -0
  43. package/src/harness/core-actions.ts +299 -0
  44. package/src/harness/delegation-control-actions.ts +250 -0
  45. package/src/harness/delegation-failure.ts +215 -0
  46. package/src/harness/delegation-plan.ts +219 -0
  47. package/src/harness/delegation-recovery-validation.ts +159 -0
  48. package/src/harness/delegation-response-actions.ts +295 -0
  49. package/src/harness/delegation-retry-policy.ts +118 -0
  50. package/src/harness/dependencies.ts +165 -0
  51. package/src/harness/gate-submission-action.ts +154 -0
  52. package/src/harness/lifecycle-actions.ts +154 -0
  53. package/src/harness/pause-actions.ts +120 -0
  54. package/src/harness/plannotator-result-actions.ts +123 -0
  55. package/src/harness/prompt-gate-actions.ts +270 -0
  56. package/src/harness/resume-action.ts +261 -0
  57. package/src/harness/start-actions.ts +183 -0
  58. package/src/harness/status-actions.ts +191 -0
  59. package/src/harness/step-execution-actions.ts +283 -0
  60. package/src/harness/types.ts +76 -0
  61. package/src/harness.ts +237 -2201
  62. package/src/index.ts +73 -11
  63. package/src/integrations/plannotator-requests.ts +127 -0
  64. package/src/integrations/plannotator-responses.ts +141 -0
  65. package/src/integrations/plannotator-types.ts +44 -0
  66. package/src/integrations/plannotator.ts +16 -235
  67. package/src/integrations/prompt-gate.ts +17 -10
  68. package/src/integrations/subagents/child-policy-envelope.ts +119 -0
  69. package/src/integrations/subagents/child-policy-paths.ts +66 -0
  70. package/src/integrations/subagents/child-policy-sections.ts +174 -0
  71. package/src/integrations/subagents/child-policy-types.ts +32 -0
  72. package/src/integrations/subagents/child-policy-validation.ts +163 -0
  73. package/src/integrations/subagents/child-runtime-completion.ts +56 -0
  74. package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
  75. package/src/integrations/subagents/child-runtime-files.ts +156 -0
  76. package/src/integrations/subagents/child-runtime-policy.ts +83 -0
  77. package/src/integrations/subagents/child-runtime-types.ts +30 -0
  78. package/src/integrations/subagents/child-runtime.ts +140 -301
  79. package/src/integrations/subagents/client-delegation.ts +181 -0
  80. package/src/integrations/subagents/client-messages.ts +66 -0
  81. package/src/integrations/subagents/client-types.ts +36 -0
  82. package/src/integrations/subagents/client.ts +127 -219
  83. package/src/integrations/subagents/delegated-result.ts +31 -0
  84. package/src/integrations/subagents/diagnostic-format.ts +40 -0
  85. package/src/integrations/subagents/diagnostic-text.ts +114 -0
  86. package/src/integrations/subagents/diagnostic-types.ts +77 -0
  87. package/src/integrations/subagents/diagnostics.ts +24 -799
  88. package/src/integrations/subagents/failure-correlation.ts +235 -0
  89. package/src/integrations/subagents/failure-transcript.ts +192 -0
  90. package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
  91. package/src/integrations/subagents/protocol-events.ts +27 -0
  92. package/src/integrations/subagents/protocol.ts +30 -375
  93. package/src/integrations/subagents/replay-audit.ts +147 -0
  94. package/src/integrations/subagents/replay-safety.ts +82 -0
  95. package/src/integrations/subagents/session-diagnostics.ts +258 -0
  96. package/src/policy/approved-command-extraction.ts +71 -0
  97. package/src/policy/approved-commands.ts +15 -432
  98. package/src/policy/bash-authorization.ts +86 -0
  99. package/src/policy/bash-read-only.ts +161 -0
  100. package/src/policy/bash-types.ts +20 -0
  101. package/src/policy/bash.ts +4 -346
  102. package/src/policy/completion-batch.ts +21 -16
  103. package/src/policy/immutable-input.ts +5 -2
  104. package/src/policy/mcp-authorization.ts +71 -0
  105. package/src/policy/restricted-command.ts +141 -0
  106. package/src/policy/restricted-git.ts +30 -0
  107. package/src/policy/reviewed-artifact.ts +97 -0
  108. package/src/policy/reviewed-command-safety.ts +150 -0
  109. package/src/policy/reviewed-command-shape.ts +49 -0
  110. package/src/policy/reviewed-repository-contract.ts +138 -0
  111. package/src/policy/reviewed-repository-cwd.ts +102 -0
  112. package/src/policy/tool-call-authorization.ts +58 -0
  113. package/src/policy/tool-selection.ts +72 -0
  114. package/src/policy/tool-types.ts +14 -0
  115. package/src/policy/tools.ts +11 -150
  116. package/src/preflight.ts +96 -61
  117. package/src/prompt/main-workflow-notice.ts +41 -0
  118. package/src/prompt/retry-task.ts +83 -0
  119. package/src/prompt/step-contract.ts +101 -0
  120. package/src/prompt/step-sections.ts +75 -0
  121. package/src/prompt/step-task.ts +156 -0
  122. package/src/prompt/template.ts +81 -0
  123. package/src/prompt.ts +10 -245
  124. package/src/runtime/completion-tool.ts +3 -0
  125. package/src/runtime/main-step-completion.ts +78 -0
  126. package/src/runtime/main-step-lifecycle.ts +37 -0
  127. package/src/runtime/main-step-policy.ts +84 -0
  128. package/src/runtime/main-step-runtime-types.ts +64 -0
  129. package/src/runtime/main-step-runtime.ts +129 -200
  130. package/src/runtime/main-step-state.ts +85 -0
  131. package/src/runtime/serial-task-queue.ts +48 -13
  132. package/src/runtime/step-result.ts +48 -27
  133. package/src/workflow-list.ts +15 -6
  134. package/src/workflow-status/format-status.ts +52 -0
  135. package/src/workflow-status/formatting.ts +178 -0
  136. package/src/workflow-status/layout.ts +109 -0
  137. package/src/workflow-status/render-board.ts +101 -0
  138. package/src/workflow-status/render-path.ts +123 -0
  139. package/src/workflow-status/render-summary.ts +144 -0
  140. package/src/workflow-status/types.ts +40 -0
  141. package/src/workflow-status/view.ts +273 -0
  142. package/src/workflow-status.ts +20 -820
@@ -0,0 +1,235 @@
1
+ import { comparableDiagnosticFragments } from './diagnostic-text.ts';
2
+ import type {
3
+ RecordedCompletion,
4
+ RecordedToolCall,
5
+ RecordedToolFailure,
6
+ ToolFailureDiagnostic,
7
+ } from './diagnostic-types.ts';
8
+ import { parseFailureTranscript } from './failure-transcript.ts';
9
+ import { reproduceHiddenBashFalsePositive } from './hidden-bash-failure.ts';
10
+ import { isFailureTranscriptReplaySafe } from './replay-safety.ts';
11
+
12
+ const diagnosticMatchesTerminalError = (
13
+ diagnostic: ToolFailureDiagnostic,
14
+ terminalError: string,
15
+ ): boolean => {
16
+ if (!diagnostic.output) return false;
17
+ const detail =
18
+ terminalError.match(
19
+ /\b[a-z][\w-]* failed(?:\s*\([^)]*\))?\s*:\s*([\s\S]+)/i,
20
+ )?.[1] ?? terminalError;
21
+ const outputFragments = comparableDiagnosticFragments(diagnostic.output);
22
+ const errorFragments = comparableDiagnosticFragments(
23
+ `${terminalError}\n${detail}`,
24
+ );
25
+ return outputFragments.some((output) =>
26
+ errorFragments.some(
27
+ (error) => output.includes(error) || error.includes(output),
28
+ ),
29
+ );
30
+ };
31
+
32
+ const latestMatching = (
33
+ diagnostics: ReadonlyArray<RecordedToolFailure>,
34
+ predicate: (diagnostic: RecordedToolFailure) => boolean,
35
+ ): RecordedToolFailure | undefined => {
36
+ for (let index = diagnostics.length - 1; index >= 0; index -= 1) {
37
+ const diagnostic = diagnostics[index];
38
+ if (diagnostic && predicate(diagnostic)) return diagnostic;
39
+ }
40
+ return undefined;
41
+ };
42
+
43
+ const finalCompletion = (
44
+ completions: ReadonlyArray<RecordedCompletion>,
45
+ latestFailureOrder: number,
46
+ lastInteractionOrder: number,
47
+ allowCompletionProof: boolean,
48
+ ): RecordedCompletion | undefined => {
49
+ if (!allowCompletionProof || completions.length !== 1) return undefined;
50
+ const completion = completions[0];
51
+ return completion &&
52
+ completion.order > latestFailureOrder &&
53
+ completion.order === lastInteractionOrder
54
+ ? completion
55
+ : undefined;
56
+ };
57
+
58
+ const publicDiagnostic = (
59
+ diagnostic: RecordedToolFailure,
60
+ diagnostics: ReadonlyArray<RecordedToolFailure>,
61
+ recordedCalls: ReadonlyArray<RecordedToolCall>,
62
+ successfulCompletions: ReadonlyArray<RecordedCompletion>,
63
+ lastInteractionOrder: number,
64
+ allowCompletionProof: boolean,
65
+ correlation?: ToolFailureDiagnostic['correlation'],
66
+ ): ToolFailureDiagnostic => {
67
+ const result: ToolFailureDiagnostic = {
68
+ tool: diagnostic.tool,
69
+ ...(diagnostic.call ? { call: diagnostic.call } : {}),
70
+ ...(diagnostic.output ? { output: diagnostic.output } : {}),
71
+ };
72
+ const latestFailureOrder = diagnostics.at(-1)?.order ?? diagnostic.order;
73
+ const completion = finalCompletion(
74
+ successfulCompletions,
75
+ latestFailureOrder,
76
+ lastInteractionOrder,
77
+ allowCompletionProof,
78
+ );
79
+ return {
80
+ ...result,
81
+ ...(isFailureTranscriptReplaySafe({
82
+ calls: recordedCalls,
83
+ diagnostics,
84
+ isCompleteTranscript: allowCompletionProof,
85
+ })
86
+ ? { replaySafe: true as const }
87
+ : {}),
88
+ ...(completion
89
+ ? {
90
+ completionAfterFailure: true as const,
91
+ completionValue: completion.value,
92
+ }
93
+ : {}),
94
+ ...(correlation ? { correlation } : {}),
95
+ };
96
+ };
97
+
98
+ const hiddenFalsePositiveDiagnostic = (
99
+ expectedTool: string | undefined,
100
+ terminalError: string,
101
+ allowCompletionProof: boolean,
102
+ transcript: ReturnType<typeof parseFailureTranscript>,
103
+ ): ToolFailureDiagnostic | undefined => {
104
+ const {
105
+ diagnostics,
106
+ hasValidFalsePositiveProof,
107
+ lastInteractionOrder,
108
+ recordedCalls,
109
+ recordedMessages,
110
+ resultCallIds,
111
+ successfulCompletions,
112
+ successfulResults,
113
+ } = transcript;
114
+ if (diagnostics.length > 0) return undefined;
115
+
116
+ const falsePositive = reproduceHiddenBashFalsePositive(
117
+ recordedMessages,
118
+ successfulResults,
119
+ );
120
+ const completion = falsePositive
121
+ ? finalCompletion(
122
+ successfulCompletions,
123
+ falsePositive.result.order,
124
+ lastInteractionOrder,
125
+ allowCompletionProof,
126
+ )
127
+ : undefined;
128
+ if (
129
+ !hasValidFalsePositiveProof ||
130
+ recordedCalls.length !== resultCallIds.size ||
131
+ !recordedCalls.every((call) => resultCallIds.has(call.id)) ||
132
+ recordedCalls.filter((call) => call.tool === 'structured_output').length !==
133
+ 1 ||
134
+ expectedTool?.toLowerCase() !== 'bash' ||
135
+ !falsePositive ||
136
+ terminalError !== falsePositive.terminalError ||
137
+ !completion
138
+ ) {
139
+ return undefined;
140
+ }
141
+
142
+ const successfulOutput = falsePositive.result;
143
+ return {
144
+ tool: successfulOutput.tool,
145
+ ...(successfulOutput.call ? { call: successfulOutput.call } : {}),
146
+ ...(successfulOutput.output ? { output: successfulOutput.output } : {}),
147
+ completionAfterFailure: true,
148
+ completionValue: completion.value,
149
+ transcriptToolCount: recordedCalls.length,
150
+ transcriptTurnCount: recordedMessages.filter(
151
+ ({ value }) => value.role === 'assistant',
152
+ ).length,
153
+ correlation: 'successful-output-before-completion',
154
+ };
155
+ };
156
+
157
+ /**
158
+ * Correlates a terminal tool failure with trusted evidence from a child
159
+ * transcript.
160
+ */
161
+ export const parseToolFailureDiagnostic = (
162
+ transcript: string,
163
+ expectedTool?: string,
164
+ terminalError?: string,
165
+ allowCompletionProof = true,
166
+ ): ToolFailureDiagnostic | undefined => {
167
+ const parsed = parseFailureTranscript(transcript);
168
+ const {
169
+ diagnostics,
170
+ lastInteractionOrder,
171
+ recordedCalls,
172
+ successfulCompletions,
173
+ } = parsed;
174
+ const matchesExpectedTool = (
175
+ diagnostic: Pick<RecordedToolFailure, 'tool'>,
176
+ ): boolean =>
177
+ expectedTool === undefined ||
178
+ diagnostic.tool.toLowerCase() === expectedTool.toLowerCase();
179
+
180
+ let selected: RecordedToolFailure | undefined;
181
+ if (terminalError) {
182
+ selected = latestMatching(
183
+ diagnostics,
184
+ (diagnostic) =>
185
+ matchesExpectedTool(diagnostic) &&
186
+ diagnosticMatchesTerminalError(diagnostic, terminalError),
187
+ );
188
+ if (!selected) {
189
+ const fallback = latestMatching(diagnostics, matchesExpectedTool);
190
+ const latestFailureOrder = diagnostics.at(-1)?.order;
191
+ if (
192
+ fallback &&
193
+ latestFailureOrder !== undefined &&
194
+ finalCompletion(
195
+ successfulCompletions,
196
+ latestFailureOrder,
197
+ lastInteractionOrder,
198
+ allowCompletionProof,
199
+ )
200
+ ) {
201
+ return publicDiagnostic(
202
+ fallback,
203
+ diagnostics,
204
+ recordedCalls,
205
+ successfulCompletions,
206
+ lastInteractionOrder,
207
+ allowCompletionProof,
208
+ 'latest-before-completion',
209
+ );
210
+ }
211
+ }
212
+ if (!selected) {
213
+ const falsePositive = hiddenFalsePositiveDiagnostic(
214
+ expectedTool,
215
+ terminalError,
216
+ allowCompletionProof,
217
+ parsed,
218
+ );
219
+ if (falsePositive) return falsePositive;
220
+ }
221
+ } else {
222
+ selected = latestMatching(diagnostics, matchesExpectedTool);
223
+ }
224
+
225
+ return selected
226
+ ? publicDiagnostic(
227
+ selected,
228
+ diagnostics,
229
+ recordedCalls,
230
+ successfulCompletions,
231
+ lastInteractionOrder,
232
+ allowCompletionProof,
233
+ )
234
+ : undefined;
235
+ };
@@ -0,0 +1,192 @@
1
+ import {
2
+ diagnosticTextContent,
3
+ diagnosticToolCallText,
4
+ firstDiagnosticTextContent,
5
+ isDiagnosticRecord,
6
+ structuredCompletionValue,
7
+ } from './diagnostic-text.ts';
8
+ import type {
9
+ ParsedFailureTranscript,
10
+ RecordedCompletion,
11
+ RecordedMessage,
12
+ RecordedToolCall,
13
+ RecordedToolFailure,
14
+ RecordedToolSuccess,
15
+ } from './diagnostic-types.ts';
16
+
17
+ /**
18
+ * Parses the tool calls, results, completions, and structural evidence needed
19
+ * to correlate a terminal failure.
20
+ */
21
+ export const parseFailureTranscript = (
22
+ transcript: string,
23
+ ): ParsedFailureTranscript => {
24
+ const calls = new Map<string, RecordedToolCall>();
25
+ const recordedCalls: Array<RecordedToolCall> = [];
26
+ const diagnostics: Array<RecordedToolFailure> = [];
27
+ const successfulResults: Array<RecordedToolSuccess> = [];
28
+ const successfulCompletions: Array<RecordedCompletion> = [];
29
+ const recordedMessages: Array<RecordedMessage> = [];
30
+ const resultCallIds = new Set<string>();
31
+ let hasValidFalsePositiveProof = true;
32
+ let lastInteractionOrder = 0;
33
+ let order = 0;
34
+
35
+ for (const line of transcript.split('\n')) {
36
+ order += 1;
37
+ if (!line.trim()) continue;
38
+
39
+ let entry: unknown;
40
+ try {
41
+ entry = JSON.parse(line);
42
+ } catch {
43
+ hasValidFalsePositiveProof = false;
44
+ continue;
45
+ }
46
+ if (!isDiagnosticRecord(entry) || entry.type !== 'message') continue;
47
+ const message = entry.message;
48
+ if (!isDiagnosticRecord(message)) {
49
+ hasValidFalsePositiveProof = false;
50
+ continue;
51
+ }
52
+ recordedMessages.push({ order, value: message });
53
+ if (
54
+ message.role === 'assistant' &&
55
+ ((typeof message.errorMessage === 'string' &&
56
+ message.errorMessage.trim().length > 0) ||
57
+ message.stopReason === 'error' ||
58
+ message.stopReason === 'aborted')
59
+ ) {
60
+ hasValidFalsePositiveProof = false;
61
+ }
62
+
63
+ if (message.role === 'assistant') {
64
+ if (!Array.isArray(message.content)) {
65
+ hasValidFalsePositiveProof = false;
66
+ continue;
67
+ }
68
+ lastInteractionOrder = order;
69
+ const toolCalls = message.content.filter(
70
+ (item): item is Record<string, unknown> =>
71
+ isDiagnosticRecord(item) &&
72
+ item.type === 'toolCall' &&
73
+ typeof item.id === 'string' &&
74
+ typeof item.name === 'string',
75
+ );
76
+ for (const item of message.content) {
77
+ if (
78
+ !isDiagnosticRecord(item) ||
79
+ item.type !== 'toolCall' ||
80
+ typeof item.id !== 'string' ||
81
+ typeof item.name !== 'string'
82
+ ) {
83
+ if (isDiagnosticRecord(item) && item.type === 'toolCall') {
84
+ hasValidFalsePositiveProof = false;
85
+ }
86
+ continue;
87
+ }
88
+ if (calls.has(item.id)) hasValidFalsePositiveProof = false;
89
+
90
+ const call = diagnosticToolCallText({
91
+ tool: item.name,
92
+ argumentsValue: item.arguments,
93
+ });
94
+ const isExclusiveCompletion =
95
+ toolCalls.length === 1 &&
96
+ message.content.every(
97
+ (contentItem) =>
98
+ isDiagnosticRecord(contentItem) &&
99
+ (contentItem.type === 'thinking' ||
100
+ contentItem.type === 'toolCall'),
101
+ );
102
+ const completionValue =
103
+ item.name === 'structured_output' && isExclusiveCompletion
104
+ ? structuredCompletionValue(item.arguments)
105
+ : undefined;
106
+ const recordedCall: RecordedToolCall = {
107
+ id: item.id,
108
+ order,
109
+ tool: item.name,
110
+ ...(call ? { call } : {}),
111
+ ...(completionValue ? { completionValue } : {}),
112
+ };
113
+ calls.set(item.id, recordedCall);
114
+ recordedCalls.push(recordedCall);
115
+ }
116
+ continue;
117
+ }
118
+
119
+ if (message.role !== 'toolResult' || typeof message.toolName !== 'string') {
120
+ if (message.role === 'toolResult') hasValidFalsePositiveProof = false;
121
+ continue;
122
+ }
123
+ lastInteractionOrder = order;
124
+ if (
125
+ typeof message.toolCallId !== 'string' ||
126
+ typeof message.isError !== 'boolean' ||
127
+ !Array.isArray(message.content) ||
128
+ resultCallIds.has(message.toolCallId)
129
+ ) {
130
+ hasValidFalsePositiveProof = false;
131
+ }
132
+ if (typeof message.toolCallId === 'string') {
133
+ resultCallIds.add(message.toolCallId);
134
+ }
135
+ const recorded =
136
+ typeof message.toolCallId === 'string'
137
+ ? calls.get(message.toolCallId)
138
+ : undefined;
139
+ const doesCallMatchResult = recorded?.tool === message.toolName;
140
+ if (!doesCallMatchResult) hasValidFalsePositiveProof = false;
141
+
142
+ if (
143
+ message.toolName === 'structured_output' &&
144
+ message.isError === false &&
145
+ doesCallMatchResult &&
146
+ recorded.completionValue
147
+ ) {
148
+ successfulCompletions.push({
149
+ order,
150
+ value: recorded.completionValue,
151
+ });
152
+ }
153
+ if (
154
+ message.isError === false &&
155
+ message.toolName !== 'structured_output' &&
156
+ doesCallMatchResult
157
+ ) {
158
+ const output = diagnosticTextContent(message.content);
159
+ const detectorOutput = firstDiagnosticTextContent(message.content);
160
+ successfulResults.push({
161
+ order,
162
+ tool: message.toolName,
163
+ ...(recorded.call ? { call: recorded.call } : {}),
164
+ ...(output ? { output } : {}),
165
+ ...(detectorOutput !== undefined ? { detectorOutput } : {}),
166
+ });
167
+ }
168
+ if (message.isError !== true) continue;
169
+
170
+ const output = diagnosticTextContent(message.content);
171
+ diagnostics.push({
172
+ tool: message.toolName,
173
+ ...(doesCallMatchResult && recorded.call ? { call: recorded.call } : {}),
174
+ ...(output ? { output } : {}),
175
+ ...(doesCallMatchResult && typeof message.toolCallId === 'string'
176
+ ? { callId: message.toolCallId }
177
+ : {}),
178
+ order,
179
+ });
180
+ }
181
+
182
+ return {
183
+ recordedCalls,
184
+ diagnostics,
185
+ successfulResults,
186
+ successfulCompletions,
187
+ recordedMessages,
188
+ resultCallIds,
189
+ hasValidFalsePositiveProof,
190
+ lastInteractionOrder,
191
+ };
192
+ };
@@ -0,0 +1,98 @@
1
+ import {
2
+ firstDiagnosticTextContent,
3
+ isDiagnosticRecord,
4
+ } from './diagnostic-text.ts';
5
+ import type {
6
+ RecordedMessage,
7
+ RecordedToolSuccess,
8
+ } from './diagnostic-types.ts';
9
+
10
+ const HIDDEN_BASH_FATAL_PATTERNS = [
11
+ /command not found/i,
12
+ /permission denied/i,
13
+ /no such file or directory/i,
14
+ /segmentation fault/i,
15
+ /killed|terminated/i,
16
+ /out of memory/i,
17
+ /connection refused/i,
18
+ /timeout/i,
19
+ ] as const;
20
+ const HIDDEN_BASH_EXIT_PATTERN =
21
+ /exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i;
22
+
23
+ export type HiddenBashFalsePositive = {
24
+ readonly result: RecordedToolSuccess;
25
+ readonly terminalError: string;
26
+ };
27
+
28
+ const lastAssistantTextIndex = (
29
+ messages: ReadonlyArray<RecordedMessage>,
30
+ ): number => {
31
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
32
+ const message = messages[index]?.value;
33
+ const hasAssistantText =
34
+ message?.role === 'assistant' &&
35
+ Array.isArray(message.content) &&
36
+ message.content.some(
37
+ (item) =>
38
+ isDiagnosticRecord(item) &&
39
+ item.type === 'text' &&
40
+ typeof item.text === 'string' &&
41
+ item.text.trim().length > 0,
42
+ );
43
+ if (hasAssistantText) return index;
44
+ }
45
+ return -1;
46
+ };
47
+
48
+ const hiddenExitCode = (output: string): number | undefined => {
49
+ const exitCodeText = output.match(HIDDEN_BASH_EXIT_PATTERN)?.[1];
50
+ const exitCode =
51
+ exitCodeText === undefined ? undefined : Number.parseInt(exitCodeText, 10);
52
+ if (exitCode !== undefined && exitCode !== 0) return exitCode;
53
+ return HIDDEN_BASH_FATAL_PATTERNS.some((pattern) => pattern.test(output))
54
+ ? 1
55
+ : undefined;
56
+ };
57
+
58
+ /**
59
+ * Reproduces pi-subagents' hidden Bash failure detection from successful tool
60
+ * output.
61
+ */
62
+ export const reproduceHiddenBashFalsePositive = (
63
+ messages: ReadonlyArray<RecordedMessage>,
64
+ successfulResults: ReadonlyArray<RecordedToolSuccess>,
65
+ ): HiddenBashFalsePositive | undefined => {
66
+ const assistantTextIndex = lastAssistantTextIndex(messages);
67
+ const scanStart = assistantTextIndex >= 0 ? assistantTextIndex + 1 : 0;
68
+
69
+ for (let index = messages.length - 1; index >= scanStart; index -= 1) {
70
+ const recordedMessage = messages[index];
71
+ const message = recordedMessage?.value;
72
+ if (
73
+ !recordedMessage ||
74
+ message?.role !== 'toolResult' ||
75
+ message.toolName !== 'bash' ||
76
+ message.isError !== false
77
+ ) {
78
+ continue;
79
+ }
80
+ const output = firstDiagnosticTextContent(message.content);
81
+ if (output === undefined) continue;
82
+
83
+ const detectedExitCode = hiddenExitCode(output);
84
+ if (detectedExitCode === undefined) continue;
85
+ const result = successfulResults.find(
86
+ (candidate) =>
87
+ candidate.order === recordedMessage.order &&
88
+ candidate.tool === 'bash' &&
89
+ candidate.detectorOutput === output,
90
+ );
91
+ if (!result) return undefined;
92
+ return {
93
+ result,
94
+ terminalError: `bash failed (exit ${detectedExitCode}): ${output.slice(0, 200)}`,
95
+ };
96
+ }
97
+ return undefined;
98
+ };
@@ -0,0 +1,27 @@
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
+ export const SUBAGENT_DELEGATION_PROTOCOL_VERSION = 1 as const;
13
+ export const SUBAGENT_DELEGATION_REQUEST_EVENT =
14
+ 'prompt-template:subagent:request';
15
+ export const SUBAGENT_DELEGATION_STARTED_EVENT =
16
+ 'prompt-template:subagent:started';
17
+ export const SUBAGENT_DELEGATION_UPDATE_EVENT =
18
+ 'prompt-template:subagent:update';
19
+ export const SUBAGENT_DELEGATION_RESPONSE_EVENT =
20
+ 'prompt-template:subagent:response';
21
+ export const SUBAGENT_DELEGATION_CANCEL_EVENT =
22
+ 'prompt-template:subagent:cancel';
23
+
24
+ export type SubagentDelegationRequest = UpstreamDelegationRequest;
25
+ export type SubagentDelegationUpdate = UpstreamDelegationUpdate;
26
+ export type SubagentDelegationStatus = UpstreamDelegationStatus;
27
+ export type SubagentDelegationResponse = UpstreamDelegationResponse;