@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.
- package/README.md +66 -38
- package/dist/index.js +5903 -4782
- package/package.json +1 -1
- package/src/command-names.ts +7 -1
- package/src/commands.ts +126 -52
- package/src/config/catalog.ts +162 -0
- package/src/config/ceiling.ts +116 -116
- package/src/config/command-conflicts.ts +4 -4
- package/src/config/diagnostics.ts +28 -0
- package/src/config/load-settings.ts +37 -0
- package/src/config/load-types.ts +53 -0
- package/src/config/load-workflows.ts +135 -0
- package/src/config/load.ts +43 -311
- package/src/config/types.ts +137 -135
- package/src/config/validate.ts +12 -1261
- package/src/config/validation/permissions.ts +291 -0
- package/src/config/validation/prompt.ts +19 -0
- package/src/config/validation/settings.ts +122 -0
- package/src/config/validation/shared.ts +129 -0
- package/src/config/validation/shortcut.ts +118 -0
- package/src/config/validation/step.ts +228 -0
- package/src/config/validation/subagent.ts +288 -0
- package/src/config/validation/workflow.ts +156 -0
- package/src/config/yaml.ts +29 -0
- package/src/digest.ts +38 -6
- package/src/engine/checkpoint.ts +13 -9
- package/src/engine/create-run.ts +42 -0
- package/src/engine/gate-transitions.ts +184 -0
- package/src/engine/reconciliation-history.ts +88 -0
- package/src/engine/resume.ts +19 -7
- package/src/engine/run-advance.ts +122 -0
- package/src/engine/run-lifecycle.ts +124 -0
- package/src/engine/run-reconciliation.ts +116 -0
- package/src/engine/run-validation.ts +128 -0
- package/src/engine/state-types.ts +64 -0
- package/src/engine/state.ts +10 -193
- package/src/engine/transition-helpers.ts +28 -0
- package/src/engine/transition-types.ts +8 -0
- package/src/engine/transitions.ts +17 -471
- package/src/harness/action-context.ts +181 -0
- package/src/harness/catalog.ts +45 -0
- package/src/harness/context-idle.ts +21 -0
- package/src/harness/core-actions.ts +299 -0
- package/src/harness/delegation-control-actions.ts +250 -0
- package/src/harness/delegation-failure.ts +215 -0
- package/src/harness/delegation-plan.ts +219 -0
- package/src/harness/delegation-recovery-validation.ts +159 -0
- package/src/harness/delegation-response-actions.ts +295 -0
- package/src/harness/delegation-retry-policy.ts +118 -0
- package/src/harness/dependencies.ts +165 -0
- package/src/harness/gate-submission-action.ts +154 -0
- package/src/harness/lifecycle-actions.ts +154 -0
- package/src/harness/pause-actions.ts +120 -0
- package/src/harness/plannotator-result-actions.ts +123 -0
- package/src/harness/prompt-gate-actions.ts +270 -0
- package/src/harness/resume-action.ts +261 -0
- package/src/harness/start-actions.ts +183 -0
- package/src/harness/status-actions.ts +191 -0
- package/src/harness/step-execution-actions.ts +283 -0
- package/src/harness/types.ts +76 -0
- package/src/harness.ts +237 -2201
- package/src/index.ts +73 -11
- package/src/integrations/plannotator-requests.ts +127 -0
- package/src/integrations/plannotator-responses.ts +141 -0
- package/src/integrations/plannotator-types.ts +44 -0
- package/src/integrations/plannotator.ts +16 -235
- package/src/integrations/prompt-gate.ts +17 -10
- package/src/integrations/subagents/child-policy-envelope.ts +119 -0
- package/src/integrations/subagents/child-policy-paths.ts +66 -0
- package/src/integrations/subagents/child-policy-sections.ts +174 -0
- package/src/integrations/subagents/child-policy-types.ts +32 -0
- package/src/integrations/subagents/child-policy-validation.ts +163 -0
- package/src/integrations/subagents/child-runtime-completion.ts +56 -0
- package/src/integrations/subagents/child-runtime-dependencies.ts +47 -0
- package/src/integrations/subagents/child-runtime-files.ts +156 -0
- package/src/integrations/subagents/child-runtime-policy.ts +83 -0
- package/src/integrations/subagents/child-runtime-types.ts +30 -0
- package/src/integrations/subagents/child-runtime.ts +140 -301
- package/src/integrations/subagents/client-delegation.ts +181 -0
- package/src/integrations/subagents/client-messages.ts +66 -0
- package/src/integrations/subagents/client-types.ts +36 -0
- package/src/integrations/subagents/client.ts +127 -219
- package/src/integrations/subagents/delegated-result.ts +31 -0
- package/src/integrations/subagents/diagnostic-format.ts +40 -0
- package/src/integrations/subagents/diagnostic-text.ts +114 -0
- package/src/integrations/subagents/diagnostic-types.ts +77 -0
- package/src/integrations/subagents/diagnostics.ts +24 -799
- package/src/integrations/subagents/failure-correlation.ts +235 -0
- package/src/integrations/subagents/failure-transcript.ts +192 -0
- package/src/integrations/subagents/hidden-bash-failure.ts +98 -0
- package/src/integrations/subagents/protocol-events.ts +27 -0
- package/src/integrations/subagents/protocol.ts +30 -375
- package/src/integrations/subagents/replay-audit.ts +147 -0
- package/src/integrations/subagents/replay-safety.ts +82 -0
- package/src/integrations/subagents/session-diagnostics.ts +258 -0
- package/src/policy/approved-command-extraction.ts +71 -0
- package/src/policy/approved-commands.ts +15 -432
- package/src/policy/bash-authorization.ts +86 -0
- package/src/policy/bash-read-only.ts +161 -0
- package/src/policy/bash-types.ts +20 -0
- package/src/policy/bash.ts +4 -346
- package/src/policy/completion-batch.ts +21 -16
- package/src/policy/immutable-input.ts +5 -2
- package/src/policy/mcp-authorization.ts +71 -0
- package/src/policy/restricted-command.ts +141 -0
- package/src/policy/restricted-git.ts +30 -0
- package/src/policy/reviewed-artifact.ts +97 -0
- package/src/policy/reviewed-command-safety.ts +150 -0
- package/src/policy/reviewed-command-shape.ts +49 -0
- package/src/policy/reviewed-repository-contract.ts +138 -0
- package/src/policy/reviewed-repository-cwd.ts +102 -0
- package/src/policy/tool-call-authorization.ts +58 -0
- package/src/policy/tool-selection.ts +72 -0
- package/src/policy/tool-types.ts +14 -0
- package/src/policy/tools.ts +11 -150
- package/src/preflight.ts +96 -61
- package/src/prompt/main-workflow-notice.ts +41 -0
- package/src/prompt/retry-task.ts +83 -0
- package/src/prompt/step-contract.ts +101 -0
- package/src/prompt/step-sections.ts +75 -0
- package/src/prompt/step-task.ts +156 -0
- package/src/prompt/template.ts +81 -0
- package/src/prompt.ts +10 -245
- package/src/runtime/completion-tool.ts +3 -0
- package/src/runtime/main-step-completion.ts +78 -0
- package/src/runtime/main-step-lifecycle.ts +37 -0
- package/src/runtime/main-step-policy.ts +84 -0
- package/src/runtime/main-step-runtime-types.ts +64 -0
- package/src/runtime/main-step-runtime.ts +129 -200
- package/src/runtime/main-step-state.ts +85 -0
- package/src/runtime/serial-task-queue.ts +48 -13
- package/src/runtime/step-result.ts +48 -27
- package/src/workflow-list.ts +15 -6
- package/src/workflow-status/format-status.ts +52 -0
- package/src/workflow-status/formatting.ts +178 -0
- package/src/workflow-status/layout.ts +109 -0
- package/src/workflow-status/render-board.ts +101 -0
- package/src/workflow-status/render-path.ts +123 -0
- package/src/workflow-status/render-summary.ts +144 -0
- package/src/workflow-status/types.ts +40 -0
- package/src/workflow-status/view.ts +273 -0
- 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;
|