@wichayutdew/pi-workflows 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/index.js +1133 -2409
- package/examples/starter-kit/agents/planner.md +7 -0
- package/examples/starter-kit/agents/reviewer.md +6 -0
- package/examples/starter-kit/agents/scout.md +6 -0
- package/examples/starter-kit/agents/worker.md +7 -0
- package/examples/starter-kit/agents/workspace-preparer.md +6 -0
- package/examples/starter-kit/investigate.workflow.yaml +114 -0
- package/examples/starter-kit/mr-comment.workflow.yaml +114 -54
- package/examples/starter-kit/mr-review.workflow.yaml +77 -56
- package/examples/starter-kit/settings.yaml +3 -0
- package/examples/starter-kit/steps/investigate/investigate.md +77 -0
- package/examples/starter-kit/steps/investigate/retrieve.md +63 -0
- package/examples/starter-kit/steps/investigate/validate.md +46 -0
- package/examples/starter-kit/steps/mr-comment/checkout-source.md +67 -0
- package/examples/starter-kit/steps/mr-comment/fetch.md +34 -26
- package/examples/starter-kit/steps/mr-comment/implement.md +34 -27
- package/examples/starter-kit/steps/mr-comment/plan.md +54 -40
- package/examples/starter-kit/steps/mr-comment/publish.md +28 -27
- package/examples/starter-kit/steps/mr-comment/verify.md +42 -35
- package/examples/starter-kit/steps/mr-review/fetch.md +52 -22
- package/examples/starter-kit/steps/mr-review/publish-approved.md +49 -0
- package/examples/starter-kit/steps/mr-review/review-for-approval.md +142 -0
- package/examples/starter-kit/steps/mr-review/verify-published.md +37 -0
- package/examples/starter-kit/steps/shared/prepare-workspace.md +91 -73
- package/examples/starter-kit/steps/shared/publish-remote.md +45 -0
- package/examples/starter-kit/steps/ticket/implement.md +59 -25
- package/examples/starter-kit/steps/ticket/plan.md +145 -72
- package/examples/starter-kit/steps/ticket/verify.md +80 -45
- package/examples/starter-kit/steps/work/implement.md +60 -24
- package/examples/starter-kit/steps/work/plan.md +129 -57
- package/examples/starter-kit/steps/work/verify.md +58 -22
- package/examples/starter-kit/ticket.workflow.yaml +75 -36
- package/examples/starter-kit/work.workflow.yaml +63 -33
- package/package.json +3 -16
- package/schemas/workflow.schema.json +2 -15
- package/src/agents/profile.ts +98 -0
- package/src/config/ceiling.ts +0 -82
- package/src/config/types.ts +6 -53
- package/src/config/validation/settings.ts +2 -14
- package/src/config/validation/step.ts +12 -8
- package/src/config/validation/workflow.ts +1 -10
- package/src/engine/run-workflow-validation.ts +0 -3
- package/src/engine/state-types.ts +1 -1
- package/src/harness/action-context.ts +1 -13
- package/src/harness/delegation-control-actions.ts +4 -63
- package/src/harness/delegation-plan.ts +26 -80
- package/src/harness/delegation-response-actions.ts +22 -135
- package/src/harness/dependencies.ts +1 -12
- package/src/harness/status-actions.ts +1 -0
- package/src/harness/step-execution-actions.ts +2 -9
- package/src/harness/types.ts +2 -40
- package/src/harness.ts +2 -17
- package/src/index.ts +4 -9
- package/src/integrations/subagents/child-policy-validation.ts +6 -7
- package/src/integrations/subagents/child-runtime-dependencies.ts +1 -1
- package/src/integrations/subagents/child-runtime-policy.ts +1 -7
- package/src/integrations/subagents/child-runtime.ts +18 -9
- package/src/integrations/subagents/client.ts +240 -98
- package/src/integrations/subagents/protocol-events.ts +32 -15
- package/src/integrations/subagents/protocol.ts +1 -1
- package/src/preflight.ts +0 -8
- package/src/prompt/main-workflow-notice.ts +8 -15
- package/src/prompt/step-task.ts +7 -6
- package/src/workflow-status/format-status.ts +5 -1
- package/src/workflow-status/render-step-detail.ts +94 -0
- package/src/workflow-status/types.ts +1 -0
- package/src/workflow-status/view.ts +49 -14
- package/agents/step.md +0 -32
- package/examples/mr-comments.workflow.yaml +0 -125
- package/examples/prompts/mr-comments/implement.md +0 -17
- package/examples/prompts/mr-comments/inspect.md +0 -5
- package/examples/prompts/mr-comments/plan.md +0 -54
- package/examples/prompts/mr-comments/verify.md +0 -9
- package/examples/settings.yaml +0 -27
- package/examples/starter-kit/steps/mr-review/publish.md +0 -48
- package/examples/starter-kit/steps/mr-review/review.md +0 -76
- package/examples/starter-kit/steps/mr-review/verify.md +0 -35
- package/src/config/validation/subagent.ts +0 -288
- package/src/harness/delegation-failure.ts +0 -248
- package/src/harness/delegation-recovery-validation.ts +0 -161
- package/src/harness/delegation-retry-policy.ts +0 -120
- package/src/integrations/subagents/client-delegation.ts +0 -181
- package/src/integrations/subagents/client-messages.ts +0 -66
- package/src/integrations/subagents/client-types.ts +0 -36
- package/src/integrations/subagents/diagnostic-format.ts +0 -45
- package/src/integrations/subagents/diagnostic-text.ts +0 -114
- package/src/integrations/subagents/diagnostic-types.ts +0 -83
- package/src/integrations/subagents/diagnostics.ts +0 -26
- package/src/integrations/subagents/failure-correlation.ts +0 -285
- package/src/integrations/subagents/failure-transcript.ts +0 -251
- package/src/integrations/subagents/hidden-bash-failure.ts +0 -98
- package/src/integrations/subagents/replay-audit.ts +0 -146
- package/src/integrations/subagents/replay-safety.ts +0 -67
- package/src/integrations/subagents/session-diagnostics.ts +0 -357
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { digest } from '../digest.ts';
|
|
2
|
-
import type { ChildStepPolicy } from '../integrations/subagents/protocol.ts';
|
|
3
|
-
import type { DelegationFailureDetails } from './types.ts';
|
|
4
|
-
|
|
5
|
-
const MAX_FAILURE_FIELD_CHARS = 1_600;
|
|
6
|
-
export const MAX_DELEGATION_RECOVERY_ATTEMPTS = 2;
|
|
7
|
-
|
|
8
|
-
const normalizedFingerprintField = (
|
|
9
|
-
value: string | undefined,
|
|
10
|
-
): string | undefined => value?.trim().replaceAll(/\s+/g, ' ') || undefined;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Creates a stable identity for the semantic evidence behind one failure.
|
|
14
|
-
*
|
|
15
|
-
* Runtime request identifiers and session paths are deliberately absent so a
|
|
16
|
-
* fresh child repeating the same failed approach is recognized.
|
|
17
|
-
*
|
|
18
|
-
* @param failure - Normalized terminal failure evidence.
|
|
19
|
-
* @returns Stable digest used to stop duplicate automatic recovery attempts.
|
|
20
|
-
*/
|
|
21
|
-
export function delegationFailureFingerprint(
|
|
22
|
-
failure: DelegationFailureDetails,
|
|
23
|
-
): string {
|
|
24
|
-
return digest({
|
|
25
|
-
status: failure.status,
|
|
26
|
-
exitCode: failure.exitCode,
|
|
27
|
-
error: normalizedFingerprintField(failure.error),
|
|
28
|
-
recoveryBlocker: failure.recoveryBlocker,
|
|
29
|
-
replayToolCount: failure.replayAudit?.toolCount,
|
|
30
|
-
diagnostic: failure.diagnostic
|
|
31
|
-
? {
|
|
32
|
-
tool: normalizedFingerprintField(failure.diagnostic.tool),
|
|
33
|
-
call: normalizedFingerprintField(failure.diagnostic.call),
|
|
34
|
-
output: normalizedFingerprintField(failure.diagnostic.output),
|
|
35
|
-
postCompletionWarning: normalizedFingerprintField(
|
|
36
|
-
failure.diagnostic.postCompletionWarning,
|
|
37
|
-
),
|
|
38
|
-
}
|
|
39
|
-
: undefined,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Bounds a failure field while retaining evidence from both ends.
|
|
45
|
-
*
|
|
46
|
-
* @param value - Untrusted diagnostic text.
|
|
47
|
-
* @returns Text safe for inclusion in a workflow notification.
|
|
48
|
-
*/
|
|
49
|
-
export function boundedFailureField(value: string): string {
|
|
50
|
-
if (value.length <= MAX_FAILURE_FIELD_CHARS) return value;
|
|
51
|
-
const marker = '… [truncated] …';
|
|
52
|
-
const available = MAX_FAILURE_FIELD_CHARS - marker.length - 2;
|
|
53
|
-
const startLength = Math.ceil(available / 2);
|
|
54
|
-
const endLength = Math.floor(available / 2);
|
|
55
|
-
return `${value.slice(0, startLength)}\n${marker}\n${value.slice(-endLength)}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Returns whether a terminal failure contains evidence suitable for recovery.
|
|
60
|
-
*
|
|
61
|
-
* @param failure - Normalized terminal failure.
|
|
62
|
-
* @returns Whether diagnostic recovery may be attempted.
|
|
63
|
-
*/
|
|
64
|
-
export function isRetryableTerminalFailure(
|
|
65
|
-
failure: DelegationFailureDetails,
|
|
66
|
-
): boolean {
|
|
67
|
-
if (failure.recoveryBlocker !== undefined) return false;
|
|
68
|
-
if (failure.diagnostic?.postCompletionWarning !== undefined) return false;
|
|
69
|
-
if (
|
|
70
|
-
failure.status === 'timed_out' ||
|
|
71
|
-
failure.status === 'turn_budget_exhausted' ||
|
|
72
|
-
failure.status === 'tool_budget_exhausted'
|
|
73
|
-
) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
if (
|
|
77
|
-
failure.status !== 'failed' &&
|
|
78
|
-
failure.status !== 'structured_output_failed'
|
|
79
|
-
) {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
return (
|
|
83
|
-
failure.error !== undefined ||
|
|
84
|
-
(Number.isSafeInteger(failure.exitCode) && failure.exitCode !== 0)
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Applies replay-safety policy to a proposed delegation retry.
|
|
90
|
-
*
|
|
91
|
-
* @param policy - Child policy from the failed delegation.
|
|
92
|
-
* @param isReplayExplicitlyAuthorized - Workflow-level retry authorization.
|
|
93
|
-
* @param replayAudit - Transcript-derived replay evidence.
|
|
94
|
-
* @returns Whether retrying the delegation is safe.
|
|
95
|
-
*/
|
|
96
|
-
export function isSafeToRetryDelegation(
|
|
97
|
-
policy: ChildStepPolicy,
|
|
98
|
-
isReplayExplicitlyAuthorized: boolean,
|
|
99
|
-
replayAudit: DelegationFailureDetails['replayAudit'],
|
|
100
|
-
): boolean {
|
|
101
|
-
return (
|
|
102
|
-
replayAudit?.replaySafe === true &&
|
|
103
|
-
(isReplayExplicitlyAuthorized || policy.permissions.bash.mode === 'deny')
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Extends a failure reason with bounded recovery-rejection evidence.
|
|
109
|
-
*
|
|
110
|
-
* @param failure - Original normalized failure.
|
|
111
|
-
* @param error - Error raised while attempting recovery.
|
|
112
|
-
* @returns User-facing rejection reason.
|
|
113
|
-
*/
|
|
114
|
-
export function rejectedRecoveryReason(
|
|
115
|
-
failure: DelegationFailureDetails,
|
|
116
|
-
error: unknown,
|
|
117
|
-
): string {
|
|
118
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
119
|
-
return `${failure.reason}\nRecovery rejected: ${boundedFailureField(detail)}`;
|
|
120
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
parseDelegationResponse,
|
|
3
|
-
parseDelegationUpdate,
|
|
4
|
-
requestIdOf,
|
|
5
|
-
} from './client-messages.ts';
|
|
6
|
-
import type {
|
|
7
|
-
ActiveDelegation,
|
|
8
|
-
DelegateOptions,
|
|
9
|
-
DelegationTimer,
|
|
10
|
-
SubagentDelegationClientDependencies,
|
|
11
|
-
SubagentEventBus,
|
|
12
|
-
} from './client-types.ts';
|
|
13
|
-
import {
|
|
14
|
-
SUBAGENT_DELEGATION_CANCEL_EVENT,
|
|
15
|
-
SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
16
|
-
SUBAGENT_DELEGATION_REQUEST_EVENT,
|
|
17
|
-
SUBAGENT_DELEGATION_RESPONSE_EVENT,
|
|
18
|
-
SUBAGENT_DELEGATION_STARTED_EVENT,
|
|
19
|
-
SUBAGENT_DELEGATION_UPDATE_EVENT,
|
|
20
|
-
} from './protocol-events.ts';
|
|
21
|
-
import type {
|
|
22
|
-
SubagentDelegationRequest,
|
|
23
|
-
SubagentDelegationResponse,
|
|
24
|
-
} from './protocol-events.ts';
|
|
25
|
-
|
|
26
|
-
export const DEFAULT_CLIENT_DEPENDENCIES = {
|
|
27
|
-
scheduleTimeout: (callback: () => void, timeoutMs: number) =>
|
|
28
|
-
setTimeout(callback, timeoutMs),
|
|
29
|
-
cancelTimeout: (timer: DelegationTimer) => {
|
|
30
|
-
clearTimeout(timer);
|
|
31
|
-
},
|
|
32
|
-
} as const satisfies SubagentDelegationClientDependencies;
|
|
33
|
-
|
|
34
|
-
type CreatedDelegation = {
|
|
35
|
-
readonly active: ActiveDelegation;
|
|
36
|
-
readonly promise: Promise<SubagentDelegationResponse>;
|
|
37
|
-
readonly start: () => void;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
type CreateDelegationOptions = {
|
|
41
|
-
readonly events: SubagentEventBus;
|
|
42
|
-
readonly request: SubagentDelegationRequest;
|
|
43
|
-
readonly options: DelegateOptions;
|
|
44
|
-
readonly dependencies: SubagentDelegationClientDependencies;
|
|
45
|
-
readonly releaseActive: (requestId: string) => void;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
function isUnsubscribe(value: unknown): value is () => void {
|
|
49
|
-
return typeof value === 'function';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Creates one delegation lifecycle without retaining client-level state.
|
|
54
|
-
*/
|
|
55
|
-
export const createDelegation = ({
|
|
56
|
-
events,
|
|
57
|
-
request,
|
|
58
|
-
options,
|
|
59
|
-
dependencies,
|
|
60
|
-
releaseActive,
|
|
61
|
-
}: CreateDelegationOptions): CreatedDelegation => {
|
|
62
|
-
let requestCancellation = (): void => undefined;
|
|
63
|
-
let resolveTerminal = (): void => undefined;
|
|
64
|
-
const terminal = new Promise<void>((resolve) => {
|
|
65
|
-
resolveTerminal = resolve;
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const promise = new Promise<SubagentDelegationResponse>((resolve, reject) => {
|
|
69
|
-
let isSettled = false;
|
|
70
|
-
let isCancellationRequested = false;
|
|
71
|
-
const subscriptions: Array<() => void> = [];
|
|
72
|
-
|
|
73
|
-
const subscribe = (
|
|
74
|
-
event: string,
|
|
75
|
-
handler: (data: unknown) => void,
|
|
76
|
-
): void => {
|
|
77
|
-
const unsubscribe = events.on(event, handler);
|
|
78
|
-
if (isUnsubscribe(unsubscribe)) {
|
|
79
|
-
subscriptions.push(() => {
|
|
80
|
-
unsubscribe();
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
const stopLocalWatchers = (): void => {
|
|
85
|
-
dependencies.cancelTimeout(startTimer);
|
|
86
|
-
dependencies.cancelTimeout(overallTimer);
|
|
87
|
-
options.signal?.removeEventListener('abort', abort);
|
|
88
|
-
};
|
|
89
|
-
const cleanup = (): void => {
|
|
90
|
-
stopLocalWatchers();
|
|
91
|
-
for (const unsubscribe of subscriptions) unsubscribe();
|
|
92
|
-
releaseActive(request.requestId);
|
|
93
|
-
};
|
|
94
|
-
const finish = (
|
|
95
|
-
result:
|
|
96
|
-
| { readonly response: SubagentDelegationResponse }
|
|
97
|
-
| { readonly error: Error },
|
|
98
|
-
): void => {
|
|
99
|
-
if (isSettled) return;
|
|
100
|
-
isSettled = true;
|
|
101
|
-
cleanup();
|
|
102
|
-
if ('response' in result) resolve(result.response);
|
|
103
|
-
else reject(result.error);
|
|
104
|
-
};
|
|
105
|
-
const emitCancel = (): void => {
|
|
106
|
-
if (isCancellationRequested || isSettled) return;
|
|
107
|
-
isCancellationRequested = true;
|
|
108
|
-
events.emit(SUBAGENT_DELEGATION_CANCEL_EVENT, {
|
|
109
|
-
version: SUBAGENT_DELEGATION_PROTOCOL_VERSION,
|
|
110
|
-
requestId: request.requestId,
|
|
111
|
-
});
|
|
112
|
-
};
|
|
113
|
-
const failAndCancel = (reason: string): void => {
|
|
114
|
-
if (isSettled) return;
|
|
115
|
-
emitCancel();
|
|
116
|
-
// A local timeout is not proof that the child process terminated.
|
|
117
|
-
// Retain correlation listeners and ownership until the terminal event.
|
|
118
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- emitting cancellation can synchronously settle through the event bus
|
|
119
|
-
if (isSettled) return;
|
|
120
|
-
isSettled = true;
|
|
121
|
-
stopLocalWatchers();
|
|
122
|
-
reject(new Error(reason));
|
|
123
|
-
};
|
|
124
|
-
const abort = (): void => {
|
|
125
|
-
failAndCancel('subagent delegation was cancelled');
|
|
126
|
-
};
|
|
127
|
-
requestCancellation = emitCancel;
|
|
128
|
-
|
|
129
|
-
subscribe(SUBAGENT_DELEGATION_STARTED_EVENT, (data) => {
|
|
130
|
-
if (requestIdOf(data) !== request.requestId) return;
|
|
131
|
-
dependencies.cancelTimeout(startTimer);
|
|
132
|
-
});
|
|
133
|
-
subscribe(SUBAGENT_DELEGATION_UPDATE_EVENT, (data) => {
|
|
134
|
-
const update = parseDelegationUpdate(data);
|
|
135
|
-
if (!update || update.requestId !== request.requestId) return;
|
|
136
|
-
options.onUpdate?.(update);
|
|
137
|
-
});
|
|
138
|
-
subscribe(SUBAGENT_DELEGATION_RESPONSE_EVENT, (data) => {
|
|
139
|
-
const response = parseDelegationResponse(data);
|
|
140
|
-
if (!response || response.requestId !== request.requestId) return;
|
|
141
|
-
resolveTerminal();
|
|
142
|
-
if (isSettled) {
|
|
143
|
-
cleanup();
|
|
144
|
-
options.onLateTerminal?.(response);
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
finish({ response });
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const startTimer = dependencies.scheduleTimeout(() => {
|
|
151
|
-
failAndCancel(
|
|
152
|
-
'pi-subagents did not accept the delegation request; verify it is installed and loaded',
|
|
153
|
-
);
|
|
154
|
-
}, options.startTimeoutMs ?? 3_000);
|
|
155
|
-
const overallTimer = dependencies.scheduleTimeout(
|
|
156
|
-
() => {
|
|
157
|
-
failAndCancel(
|
|
158
|
-
'pi-subagents did not settle the delegation request before its deadline',
|
|
159
|
-
);
|
|
160
|
-
},
|
|
161
|
-
(request.timeoutMs ?? 900_000) + 5_000,
|
|
162
|
-
);
|
|
163
|
-
startTimer.unref();
|
|
164
|
-
overallTimer.unref();
|
|
165
|
-
options.signal?.addEventListener('abort', abort, { once: true });
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
active: {
|
|
170
|
-
requestId: request.requestId,
|
|
171
|
-
requestCancellation: () => {
|
|
172
|
-
requestCancellation();
|
|
173
|
-
},
|
|
174
|
-
terminal,
|
|
175
|
-
},
|
|
176
|
-
promise,
|
|
177
|
-
start: () => {
|
|
178
|
-
events.emit(SUBAGENT_DELEGATION_REQUEST_EVENT, request);
|
|
179
|
-
},
|
|
180
|
-
};
|
|
181
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { SUBAGENT_DELEGATION_PROTOCOL_VERSION } from './protocol-events.ts';
|
|
2
|
-
import type {
|
|
3
|
-
SubagentDelegationResponse,
|
|
4
|
-
SubagentDelegationStatus,
|
|
5
|
-
SubagentDelegationUpdate,
|
|
6
|
-
} from './protocol-events.ts';
|
|
7
|
-
|
|
8
|
-
const DELEGATION_STATUSES: ReadonlySet<string> = new Set([
|
|
9
|
-
'completed',
|
|
10
|
-
'failed',
|
|
11
|
-
'timed_out',
|
|
12
|
-
'cancelled',
|
|
13
|
-
'interrupted',
|
|
14
|
-
'turn_budget_exhausted',
|
|
15
|
-
'tool_budget_exhausted',
|
|
16
|
-
'structured_output_failed',
|
|
17
|
-
'acceptance_failed',
|
|
18
|
-
'invalid_request',
|
|
19
|
-
'unavailable_context',
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
23
|
-
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
24
|
-
|
|
25
|
-
const isDelegationStatus = (value: string): value is SubagentDelegationStatus =>
|
|
26
|
-
DELEGATION_STATUSES.has(value);
|
|
27
|
-
|
|
28
|
-
const isDelegationResponse = (
|
|
29
|
-
value: unknown,
|
|
30
|
-
): value is SubagentDelegationResponse =>
|
|
31
|
-
isRecord(value) &&
|
|
32
|
-
value.version === SUBAGENT_DELEGATION_PROTOCOL_VERSION &&
|
|
33
|
-
typeof value.requestId === 'string' &&
|
|
34
|
-
typeof value.status === 'string' &&
|
|
35
|
-
isDelegationStatus(value.status);
|
|
36
|
-
|
|
37
|
-
const isDelegationUpdate = (
|
|
38
|
-
value: unknown,
|
|
39
|
-
): value is SubagentDelegationUpdate =>
|
|
40
|
-
isRecord(value) &&
|
|
41
|
-
value.version === SUBAGENT_DELEGATION_PROTOCOL_VERSION &&
|
|
42
|
-
typeof value.requestId === 'string';
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Reads a request identifier from an untrusted event payload.
|
|
46
|
-
*/
|
|
47
|
-
export const requestIdOf = (value: unknown): string | undefined =>
|
|
48
|
-
isRecord(value) && typeof value.requestId === 'string'
|
|
49
|
-
? value.requestId
|
|
50
|
-
: undefined;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Validates an untrusted terminal delegation response.
|
|
54
|
-
*/
|
|
55
|
-
export const parseDelegationResponse = (
|
|
56
|
-
value: unknown,
|
|
57
|
-
): SubagentDelegationResponse | undefined =>
|
|
58
|
-
isDelegationResponse(value) ? value : undefined;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Validates an untrusted delegation progress update.
|
|
62
|
-
*/
|
|
63
|
-
export const parseDelegationUpdate = (
|
|
64
|
-
value: unknown,
|
|
65
|
-
): SubagentDelegationUpdate | undefined =>
|
|
66
|
-
isDelegationUpdate(value) ? value : undefined;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SubagentDelegationResponse,
|
|
3
|
-
SubagentDelegationUpdate,
|
|
4
|
-
} from './protocol-events.ts';
|
|
5
|
-
|
|
6
|
-
export type SubagentEventBus = {
|
|
7
|
-
readonly on: (event: string, handler: (data: unknown) => void) => unknown;
|
|
8
|
-
readonly emit: (event: string, data: unknown) => void;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type DelegateOptions = {
|
|
12
|
-
readonly signal?: AbortSignal;
|
|
13
|
-
readonly startTimeoutMs?: number;
|
|
14
|
-
readonly onUpdate?: (update: SubagentDelegationUpdate) => void;
|
|
15
|
-
/**
|
|
16
|
-
* Called when a terminal response arrives after the delegate promise already
|
|
17
|
-
* rejected locally. The child was still potentially alive until this event.
|
|
18
|
-
*/
|
|
19
|
-
readonly onLateTerminal?: (response: SubagentDelegationResponse) => void;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type DelegationTimer = ReturnType<typeof setTimeout>;
|
|
23
|
-
|
|
24
|
-
export type SubagentDelegationClientDependencies = {
|
|
25
|
-
readonly scheduleTimeout: (
|
|
26
|
-
callback: () => void,
|
|
27
|
-
timeoutMs: number,
|
|
28
|
-
) => DelegationTimer;
|
|
29
|
-
readonly cancelTimeout: (timer: DelegationTimer) => void;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type ActiveDelegation = {
|
|
33
|
-
readonly requestId: string;
|
|
34
|
-
readonly requestCancellation: () => void;
|
|
35
|
-
readonly terminal: Promise<void>;
|
|
36
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { ToolFailureDiagnostic } from './diagnostic-types.ts';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Extracts a failed tool name from a terminal error string.
|
|
5
|
-
*/
|
|
6
|
-
export const failedToolName = (error: string | undefined): string | undefined =>
|
|
7
|
-
error?.match(/\b([a-z][\w-]*) failed(?:\s*\(|:)/i)?.[1];
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Formats a correlated tool failure as concise human-readable lines.
|
|
11
|
-
*/
|
|
12
|
-
export const formatToolFailureDiagnostic = (
|
|
13
|
-
diagnostic: ToolFailureDiagnostic,
|
|
14
|
-
): Array<string> => {
|
|
15
|
-
const hasSuccessfulOutputCorrelation =
|
|
16
|
-
diagnostic.correlation === 'successful-output-before-completion';
|
|
17
|
-
return [
|
|
18
|
-
`${hasSuccessfulOutputCorrelation ? 'Terminal-reported tool' : 'Failed tool'}: ${diagnostic.tool}`,
|
|
19
|
-
...(diagnostic.call
|
|
20
|
-
? [
|
|
21
|
-
`${diagnostic.tool === 'bash' ? 'Command' : 'Arguments'}: ${diagnostic.call}`,
|
|
22
|
-
]
|
|
23
|
-
: []),
|
|
24
|
-
...(diagnostic.output
|
|
25
|
-
? [
|
|
26
|
-
`${hasSuccessfulOutputCorrelation ? 'Successful tool output' : 'Tool error'}: ${diagnostic.output}`,
|
|
27
|
-
]
|
|
28
|
-
: []),
|
|
29
|
-
...(diagnostic.postCompletionWarning
|
|
30
|
-
? [
|
|
31
|
-
`Post-completion watchdog warning: ${diagnostic.postCompletionWarning}`,
|
|
32
|
-
]
|
|
33
|
-
: []),
|
|
34
|
-
...(diagnostic.correlation === 'latest-before-completion'
|
|
35
|
-
? [
|
|
36
|
-
'Correlation: latest failed tool call before successful structured_output; terminal text did not identify the call',
|
|
37
|
-
]
|
|
38
|
-
: []),
|
|
39
|
-
...(hasSuccessfulOutputCorrelation
|
|
40
|
-
? [
|
|
41
|
-
'Correlation: terminal error text came from a successful tool result before the final structured_output',
|
|
42
|
-
]
|
|
43
|
-
: []),
|
|
44
|
-
];
|
|
45
|
-
};
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const MAX_DIAGNOSTIC_FIELD_CHARS = 1_600;
|
|
2
|
-
const TRUNCATION_MARKER = '… [truncated] …';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Narrows an unknown transcript value to a plain record.
|
|
6
|
-
*/
|
|
7
|
-
export const isDiagnosticRecord = (
|
|
8
|
-
value: unknown,
|
|
9
|
-
): value is Record<string, unknown> =>
|
|
10
|
-
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Bounds diagnostic text while preserving evidence from both ends.
|
|
14
|
-
*/
|
|
15
|
-
export const boundedDiagnosticText = (value: string): string => {
|
|
16
|
-
if (value.length <= MAX_DIAGNOSTIC_FIELD_CHARS) return value;
|
|
17
|
-
const available = MAX_DIAGNOSTIC_FIELD_CHARS - TRUNCATION_MARKER.length - 2;
|
|
18
|
-
const startLength = Math.ceil(available / 2);
|
|
19
|
-
const endLength = Math.floor(available / 2);
|
|
20
|
-
return `${value.slice(0, startLength)}\n${TRUNCATION_MARKER}\n${value.slice(-endLength)}`;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Joins and bounds every text item in a transcript content array.
|
|
25
|
-
*/
|
|
26
|
-
export const diagnosticTextContent = (value: unknown): string | undefined => {
|
|
27
|
-
if (!Array.isArray(value)) return undefined;
|
|
28
|
-
const text = value
|
|
29
|
-
.flatMap((item) =>
|
|
30
|
-
isDiagnosticRecord(item) &&
|
|
31
|
-
item.type === 'text' &&
|
|
32
|
-
typeof item.text === 'string'
|
|
33
|
-
? [item.text]
|
|
34
|
-
: [],
|
|
35
|
-
)
|
|
36
|
-
.join('\n')
|
|
37
|
-
.trim();
|
|
38
|
-
return text ? boundedDiagnosticText(text) : undefined;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Returns the first unmodified text item in transcript content.
|
|
43
|
-
*/
|
|
44
|
-
export const firstDiagnosticTextContent = (
|
|
45
|
-
value: unknown,
|
|
46
|
-
): string | undefined => {
|
|
47
|
-
if (!Array.isArray(value)) return undefined;
|
|
48
|
-
const content: ReadonlyArray<unknown> = value;
|
|
49
|
-
const text = content.find(
|
|
50
|
-
(item) =>
|
|
51
|
-
isDiagnosticRecord(item) &&
|
|
52
|
-
item.type === 'text' &&
|
|
53
|
-
typeof item.text === 'string',
|
|
54
|
-
);
|
|
55
|
-
return isDiagnosticRecord(text) && typeof text.text === 'string'
|
|
56
|
-
? text.text
|
|
57
|
-
: undefined;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Formats a tool call for bounded diagnostic output.
|
|
62
|
-
*/
|
|
63
|
-
export const diagnosticToolCallText = ({
|
|
64
|
-
tool,
|
|
65
|
-
argumentsValue,
|
|
66
|
-
}: {
|
|
67
|
-
readonly tool: string;
|
|
68
|
-
readonly argumentsValue: unknown;
|
|
69
|
-
}): string | undefined => {
|
|
70
|
-
if (!isDiagnosticRecord(argumentsValue)) return undefined;
|
|
71
|
-
if (tool === 'bash') {
|
|
72
|
-
const command = argumentsValue.command ?? argumentsValue.cmd;
|
|
73
|
-
if (typeof command === 'string' && command.trim()) {
|
|
74
|
-
return boundedDiagnosticText(command.trim());
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return boundedDiagnosticText(JSON.stringify(argumentsValue));
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Extracts the value from an exclusive structured-output argument shape.
|
|
82
|
-
*/
|
|
83
|
-
export const structuredCompletionValue = (
|
|
84
|
-
argumentsValue: unknown,
|
|
85
|
-
): Readonly<Record<string, unknown>> | undefined => {
|
|
86
|
-
if (!isDiagnosticRecord(argumentsValue)) return undefined;
|
|
87
|
-
if (
|
|
88
|
-
Object.keys(argumentsValue).length !== 1 ||
|
|
89
|
-
!Object.hasOwn(argumentsValue, 'value') ||
|
|
90
|
-
!isDiagnosticRecord(argumentsValue.value)
|
|
91
|
-
) {
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
|
-
return argumentsValue.value;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const normalizeDiagnosticText = (value: string): string =>
|
|
98
|
-
value.replace(/\s+/g, ' ').trim().toLowerCase();
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Produces normalized, non-trivial fragments for error correlation.
|
|
102
|
-
*/
|
|
103
|
-
export const comparableDiagnosticFragments = (
|
|
104
|
-
value: string,
|
|
105
|
-
): ReadonlyArray<string> => {
|
|
106
|
-
const normalizedValue = normalizeDiagnosticText(value);
|
|
107
|
-
const lines = value
|
|
108
|
-
.split(/\r?\n/)
|
|
109
|
-
.map(normalizeDiagnosticText)
|
|
110
|
-
.filter((line) => line.length >= 8);
|
|
111
|
-
return [...new Set([normalizedValue, ...lines])].filter(
|
|
112
|
-
(fragment) => fragment.length >= 8,
|
|
113
|
-
);
|
|
114
|
-
};
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { BashPermission } from '../../config/types.ts';
|
|
2
|
-
|
|
3
|
-
export type ToolFailureDiagnostic = {
|
|
4
|
-
readonly tool: string;
|
|
5
|
-
readonly call?: string;
|
|
6
|
-
readonly output?: string;
|
|
7
|
-
readonly postCompletionWarning?: string;
|
|
8
|
-
readonly replaySafe?: true;
|
|
9
|
-
readonly completionAfterFailure?: true;
|
|
10
|
-
readonly completionValue?: Readonly<Record<string, unknown>>;
|
|
11
|
-
readonly transcriptToolCount?: number;
|
|
12
|
-
readonly transcriptTurnCount?: number;
|
|
13
|
-
readonly correlation?:
|
|
14
|
-
'latest-before-completion' | 'successful-output-before-completion';
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type DelegationReplayAudit = {
|
|
18
|
-
readonly replaySafe: boolean;
|
|
19
|
-
readonly toolCount: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type DelegationReplayExpectation = {
|
|
23
|
-
readonly task: string;
|
|
24
|
-
readonly bashPermission: BashPermission;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export type SubagentSessionIdentity = {
|
|
28
|
-
readonly runId: string;
|
|
29
|
-
readonly childIndex: number;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type RecordedToolCall = {
|
|
33
|
-
readonly id: string;
|
|
34
|
-
readonly order: number;
|
|
35
|
-
readonly tool: string;
|
|
36
|
-
readonly call?: string;
|
|
37
|
-
readonly completionValue?: Readonly<Record<string, unknown>>;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type RecordedToolFailure = ToolFailureDiagnostic & {
|
|
41
|
-
readonly callId?: string;
|
|
42
|
-
readonly order: number;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export type RecordedToolSuccess = {
|
|
46
|
-
readonly order: number;
|
|
47
|
-
readonly tool: string;
|
|
48
|
-
readonly call?: string;
|
|
49
|
-
readonly output?: string;
|
|
50
|
-
readonly detectorOutput?: string;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
export type RecordedCompletion = {
|
|
54
|
-
readonly order: number;
|
|
55
|
-
readonly value: Readonly<Record<string, unknown>>;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export type RecordedTranscriptWarning = {
|
|
59
|
-
readonly order: number;
|
|
60
|
-
readonly content: string;
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export type RecordedMessage = {
|
|
64
|
-
readonly order: number;
|
|
65
|
-
readonly value: Readonly<Record<string, unknown>>;
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
export type ParsedFailureTranscript = {
|
|
69
|
-
readonly recordedCalls: ReadonlyArray<RecordedToolCall>;
|
|
70
|
-
readonly diagnostics: ReadonlyArray<RecordedToolFailure>;
|
|
71
|
-
readonly successfulResults: ReadonlyArray<RecordedToolSuccess>;
|
|
72
|
-
readonly successfulCompletions: ReadonlyArray<RecordedCompletion>;
|
|
73
|
-
readonly transcriptWarnings: ReadonlyArray<RecordedTranscriptWarning>;
|
|
74
|
-
readonly recordedMessages: ReadonlyArray<RecordedMessage>;
|
|
75
|
-
readonly resultCallIds: ReadonlySet<string>;
|
|
76
|
-
readonly hasValidFalsePositiveProof: boolean;
|
|
77
|
-
readonly lastInteractionOrder: number;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export type SessionTail = {
|
|
81
|
-
readonly content: string;
|
|
82
|
-
readonly truncated: boolean;
|
|
83
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
failedToolName,
|
|
3
|
-
formatToolFailureDiagnostic,
|
|
4
|
-
} from './diagnostic-format.ts';
|
|
5
|
-
export type {
|
|
6
|
-
DelegationReplayAudit,
|
|
7
|
-
DelegationReplayExpectation,
|
|
8
|
-
SubagentSessionIdentity,
|
|
9
|
-
ToolFailureDiagnostic,
|
|
10
|
-
} from './diagnostic-types.ts';
|
|
11
|
-
export { parseToolFailureDiagnostic } from './failure-correlation.ts';
|
|
12
|
-
export { parseDelegationReplayAudit } from './replay-audit.ts';
|
|
13
|
-
export {
|
|
14
|
-
auditCompletedDelegationTranscript,
|
|
15
|
-
deriveSubagentSessionRoot,
|
|
16
|
-
readDelegationReplayAudit,
|
|
17
|
-
readToolFailureDiagnostic,
|
|
18
|
-
} from './session-diagnostics.ts';
|
|
19
|
-
export type {
|
|
20
|
-
CompletedDelegationTranscriptAudit,
|
|
21
|
-
SubagentDiagnosticDependencies,
|
|
22
|
-
SubagentDiagnosticFileHandle,
|
|
23
|
-
SubagentDiagnosticFileSnapshot,
|
|
24
|
-
SubagentDiagnosticFileSystem,
|
|
25
|
-
SubagentDiagnosticPathInspection,
|
|
26
|
-
} from './session-diagnostics.ts';
|