@abelxiaoxing/cadence 1.0.8 → 1.0.9
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/package.json +3 -3
- package/src/child-session.ts +96 -25
- package/src/contracts.ts +35 -2
- package/src/parent-payload-bridge.ts +66 -9
- package/src/parent-provider.ts +86 -30
- package/src/runtime.ts +22 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abelxiaoxing/cadence",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Prompts, skills, and a private orchestration extension exposing the Abel four-stage workflow (/abel-init, /abel-design, /abel-implement, /abel-diagnose) with package-owned professional Agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@biomejs/biome": "^2.4.16",
|
|
56
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
57
|
-
"@earendil-works/pi-tui": "0.84.
|
|
56
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
57
|
+
"@earendil-works/pi-tui": "0.84.3",
|
|
58
58
|
"@types/node": "^22.15.3",
|
|
59
59
|
"rumdl": "^0.2.10",
|
|
60
60
|
"typescript": "^6.0.3",
|
package/src/child-session.ts
CHANGED
|
@@ -157,6 +157,39 @@ function isSingleStructuralSubmit(message: AssistantMessage | undefined) {
|
|
|
157
157
|
);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
+
function safeChildError(failure: ChildFailure): string {
|
|
161
|
+
switch (failure.kind) {
|
|
162
|
+
case "environment":
|
|
163
|
+
return failure.code === "child-session-create-failed"
|
|
164
|
+
? "child session creation failed"
|
|
165
|
+
: "child environment unavailable";
|
|
166
|
+
case "transport":
|
|
167
|
+
switch (failure.code) {
|
|
168
|
+
case "child-timeout":
|
|
169
|
+
case "timeout":
|
|
170
|
+
return "child phase timeout";
|
|
171
|
+
case "child-no-final-assistant":
|
|
172
|
+
return "child produced no final assistant";
|
|
173
|
+
case "child-provider-stream-aborted":
|
|
174
|
+
return "child provider stream aborted";
|
|
175
|
+
case "child-provider-stream-error":
|
|
176
|
+
case "transport-failure":
|
|
177
|
+
return "child provider stream failed";
|
|
178
|
+
}
|
|
179
|
+
return "child provider stream failed";
|
|
180
|
+
case "artifact":
|
|
181
|
+
return failure.code === "child-no-structural-submit"
|
|
182
|
+
? "child produced no structural submission"
|
|
183
|
+
: "child structural submission is invalid";
|
|
184
|
+
case "cancelled":
|
|
185
|
+
return "child phase cancelled";
|
|
186
|
+
case "stale":
|
|
187
|
+
case "approval-boundary":
|
|
188
|
+
case "result-limit":
|
|
189
|
+
return "child result rejected";
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
160
193
|
export type ChildSessionResult =
|
|
161
194
|
| {
|
|
162
195
|
ok: true;
|
|
@@ -192,6 +225,7 @@ export async function runChildSession(input: {
|
|
|
192
225
|
allowedPaths?: string[];
|
|
193
226
|
timeoutMs: number;
|
|
194
227
|
signal?: AbortSignal;
|
|
228
|
+
failureOverride?: () => ChildFailure | undefined;
|
|
195
229
|
}): Promise<ChildSessionResult> {
|
|
196
230
|
const submit = createSubmitTool({
|
|
197
231
|
requestId: input.requestId,
|
|
@@ -255,23 +289,46 @@ export async function runChildSession(input: {
|
|
|
255
289
|
identity: submit.getIdentity(),
|
|
256
290
|
};
|
|
257
291
|
};
|
|
258
|
-
const
|
|
292
|
+
const transportFailure = ():
|
|
293
|
+
| Extract<ChildFailure, { kind: "transport" }>
|
|
294
|
+
| undefined => {
|
|
259
295
|
const final = session?.messages
|
|
260
296
|
.filter((message) => message.role === "assistant")
|
|
261
297
|
.at(-1);
|
|
262
|
-
if (final === undefined)
|
|
298
|
+
if (final === undefined) {
|
|
299
|
+
return {
|
|
300
|
+
kind: "transport",
|
|
301
|
+
code: "child-no-final-assistant",
|
|
302
|
+
stage: "child-finalization",
|
|
303
|
+
};
|
|
304
|
+
}
|
|
263
305
|
if (final.stopReason === "error") {
|
|
264
|
-
return (
|
|
265
|
-
submit.getAttempts() === 0 &&
|
|
306
|
+
return submit.getAttempts() === 0 &&
|
|
266
307
|
!final.content.some(
|
|
267
308
|
(content) =>
|
|
268
309
|
content.type === "toolCall" &&
|
|
269
310
|
content.name === "abel_submit_result",
|
|
270
311
|
)
|
|
271
|
-
|
|
312
|
+
? {
|
|
313
|
+
kind: "transport",
|
|
314
|
+
code: "child-provider-stream-error",
|
|
315
|
+
stage: "child-provider-stream",
|
|
316
|
+
}
|
|
317
|
+
: undefined;
|
|
272
318
|
}
|
|
273
|
-
|
|
319
|
+
if (final.stopReason === "aborted") {
|
|
320
|
+
return {
|
|
321
|
+
kind: "transport",
|
|
322
|
+
code: "child-provider-stream-aborted",
|
|
323
|
+
stage: "child-provider-stream",
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
274
327
|
};
|
|
328
|
+
const noStructuralSubmit = (): ChildFailure =>
|
|
329
|
+
submit.getAttempts() === 0
|
|
330
|
+
? { kind: "artifact", code: "child-no-structural-submit" }
|
|
331
|
+
: { kind: "artifact", code: "invalid-structural-result" };
|
|
275
332
|
try {
|
|
276
333
|
abort.signal.throwIfAborted();
|
|
277
334
|
const creation = createAgentSession({
|
|
@@ -331,22 +388,19 @@ export async function runChildSession(input: {
|
|
|
331
388
|
const attempts = submit.getAttempts();
|
|
332
389
|
const classification = classifySession();
|
|
333
390
|
if (!result || attempts !== 1) {
|
|
334
|
-
const transportFailure = transportFailed();
|
|
335
391
|
const failure =
|
|
336
392
|
submit.getFailure() ??
|
|
337
|
-
(
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
code: "invalid-structural-result",
|
|
342
|
-
} as const));
|
|
393
|
+
input.failureOverride?.() ??
|
|
394
|
+
transportFailure() ??
|
|
395
|
+
noStructuralSubmit();
|
|
396
|
+
const isTransport = failure.kind === "transport";
|
|
343
397
|
disposeOnce();
|
|
344
398
|
return {
|
|
345
399
|
ok: false,
|
|
346
|
-
error:
|
|
400
|
+
error: safeChildError(failure),
|
|
347
401
|
failure,
|
|
348
402
|
failureKind: "failed",
|
|
349
|
-
transportFailure,
|
|
403
|
+
transportFailure: isTransport,
|
|
350
404
|
disposeCount,
|
|
351
405
|
usage: usage.total(),
|
|
352
406
|
classification,
|
|
@@ -355,11 +409,15 @@ export async function runChildSession(input: {
|
|
|
355
409
|
const assistants = session.messages.filter((m) => m.role === "assistant");
|
|
356
410
|
const final = assistants.at(-1);
|
|
357
411
|
if (!isSingleStructuralSubmit(final)) {
|
|
412
|
+
const failure = {
|
|
413
|
+
kind: "artifact",
|
|
414
|
+
code: "invalid-structural-result",
|
|
415
|
+
} as const;
|
|
358
416
|
disposeOnce();
|
|
359
417
|
return {
|
|
360
418
|
ok: false,
|
|
361
|
-
error:
|
|
362
|
-
failure
|
|
419
|
+
error: safeChildError(failure),
|
|
420
|
+
failure,
|
|
363
421
|
failureKind: "failed",
|
|
364
422
|
transportFailure: false,
|
|
365
423
|
disposeCount,
|
|
@@ -390,22 +448,35 @@ export async function runChildSession(input: {
|
|
|
390
448
|
? "cancelled"
|
|
391
449
|
: "failed";
|
|
392
450
|
const classification = classifySession();
|
|
393
|
-
const transportFailure = transportFailed();
|
|
394
451
|
const failure: ChildFailure = timedOut
|
|
395
|
-
? {
|
|
452
|
+
? {
|
|
453
|
+
kind: "transport",
|
|
454
|
+
code: "child-timeout",
|
|
455
|
+
stage: "child-timeout",
|
|
456
|
+
}
|
|
396
457
|
: failureKind === "cancelled"
|
|
397
458
|
? { kind: "cancelled", code: "cancelled" }
|
|
398
|
-
:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
459
|
+
: session === undefined
|
|
460
|
+
? {
|
|
461
|
+
kind: "environment",
|
|
462
|
+
code: "child-session-create-failed",
|
|
463
|
+
stage: "child-session-create",
|
|
464
|
+
}
|
|
465
|
+
: (submit.getFailure() ??
|
|
466
|
+
input.failureOverride?.() ??
|
|
467
|
+
transportFailure() ??
|
|
468
|
+
noStructuralSubmit());
|
|
469
|
+
const isTransport = failure.kind === "transport";
|
|
402
470
|
disposeOnce();
|
|
403
471
|
return {
|
|
404
472
|
ok: false,
|
|
405
|
-
error:
|
|
473
|
+
error:
|
|
474
|
+
failure.kind === "cancelled" && error instanceof Error
|
|
475
|
+
? error.message
|
|
476
|
+
: safeChildError(failure),
|
|
406
477
|
failure,
|
|
407
478
|
failureKind,
|
|
408
|
-
transportFailure,
|
|
479
|
+
transportFailure: isTransport,
|
|
409
480
|
disposeCount,
|
|
410
481
|
usage: usage.total(),
|
|
411
482
|
classification,
|
package/src/contracts.ts
CHANGED
|
@@ -74,6 +74,7 @@ export const ARTIFACT_FAILURE_CODES = [
|
|
|
74
74
|
"invalid-diff-bytes",
|
|
75
75
|
"invalid-snapshot",
|
|
76
76
|
"invalid-structural-result",
|
|
77
|
+
"child-no-structural-submit",
|
|
77
78
|
"invalid-verification-contract",
|
|
78
79
|
"lockfile-bound-mismatch",
|
|
79
80
|
"noncanonical-path",
|
|
@@ -108,6 +109,7 @@ export const ENVIRONMENT_FAILURE_CODES = [
|
|
|
108
109
|
"checkout-failed",
|
|
109
110
|
"clone-alternates",
|
|
110
111
|
"clone-failed",
|
|
112
|
+
"child-session-create-failed",
|
|
111
113
|
"dependency-path-unsafe",
|
|
112
114
|
"git-apply-check-unavailable",
|
|
113
115
|
"git-apply-failed",
|
|
@@ -115,6 +117,11 @@ export const ENVIRONMENT_FAILURE_CODES = [
|
|
|
115
117
|
"git-ignore-check-failed",
|
|
116
118
|
"git-index-unavailable",
|
|
117
119
|
"invalid-subagent-endpoint",
|
|
120
|
+
"parent-bridge-capture-not-ready",
|
|
121
|
+
"parent-bridge-generation-invalidated",
|
|
122
|
+
"parent-bridge-model-key-mismatch",
|
|
123
|
+
"parent-bridge-provider-not-installed",
|
|
124
|
+
"parent-bridge-session-unavailable",
|
|
118
125
|
"root-unavailable",
|
|
119
126
|
"sandbox-runtime-unavailable",
|
|
120
127
|
] as const;
|
|
@@ -126,18 +133,39 @@ export const APPROVAL_BOUNDARY_CODES = [
|
|
|
126
133
|
"verification-contract-insufficient",
|
|
127
134
|
"agents-contract-insufficient",
|
|
128
135
|
] as const;
|
|
129
|
-
export const CHILD_TRANSPORT_CODES = [
|
|
136
|
+
export const CHILD_TRANSPORT_CODES = [
|
|
137
|
+
"child-no-final-assistant",
|
|
138
|
+
"child-provider-stream-aborted",
|
|
139
|
+
"child-provider-stream-error",
|
|
140
|
+
"child-timeout",
|
|
141
|
+
"timeout",
|
|
142
|
+
"transport-failure",
|
|
143
|
+
] as const;
|
|
144
|
+
export const FAILURE_STAGES = [
|
|
145
|
+
"phase-runtime",
|
|
146
|
+
"child-session-create",
|
|
147
|
+
"child-provider-stream",
|
|
148
|
+
"child-finalization",
|
|
149
|
+
"child-timeout",
|
|
150
|
+
] as const;
|
|
130
151
|
|
|
131
152
|
export type ArtifactFailureCode = (typeof ARTIFACT_FAILURE_CODES)[number];
|
|
132
153
|
export type StaleFailureCode = (typeof STALE_FAILURE_CODES)[number];
|
|
133
154
|
export type EnvironmentFailureCode = (typeof ENVIRONMENT_FAILURE_CODES)[number];
|
|
134
155
|
export type ApprovalBoundaryCode = (typeof APPROVAL_BOUNDARY_CODES)[number];
|
|
135
156
|
export type ChildTransportCode = (typeof CHILD_TRANSPORT_CODES)[number];
|
|
157
|
+
export type FailureStage = (typeof FAILURE_STAGES)[number];
|
|
158
|
+
|
|
159
|
+
export interface SafeFailureDiagnostic {
|
|
160
|
+
code: ChildTransportCode;
|
|
161
|
+
stage: FailureStage;
|
|
162
|
+
}
|
|
136
163
|
|
|
137
164
|
export type EnvironmentFailure = {
|
|
138
165
|
kind: "environment";
|
|
139
166
|
code: EnvironmentFailureCode;
|
|
140
167
|
message?: string;
|
|
168
|
+
stage?: FailureStage;
|
|
141
169
|
};
|
|
142
170
|
|
|
143
171
|
export type CandidateFailure =
|
|
@@ -150,7 +178,11 @@ export type CandidateFailure =
|
|
|
150
178
|
|
|
151
179
|
export type ChildFailure =
|
|
152
180
|
| CandidateFailure
|
|
153
|
-
| {
|
|
181
|
+
| {
|
|
182
|
+
kind: "transport";
|
|
183
|
+
code: ChildTransportCode;
|
|
184
|
+
stage?: FailureStage;
|
|
185
|
+
};
|
|
154
186
|
|
|
155
187
|
export type CandidateRejection =
|
|
156
188
|
| {
|
|
@@ -165,6 +197,7 @@ export type TaskFailure =
|
|
|
165
197
|
| {
|
|
166
198
|
kind: "attempts-exhausted";
|
|
167
199
|
cause: "artifact" | "stale" | "transport";
|
|
200
|
+
lastFailure?: SafeFailureDiagnostic;
|
|
168
201
|
}
|
|
169
202
|
| {
|
|
170
203
|
kind: "checkpoint-attempts-exhausted";
|
|
@@ -27,6 +27,23 @@ export interface ParentPayloadCapture {
|
|
|
27
27
|
readonly onPayload: ParentPayloadCallback;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
export type ParentBridgeFailureCode =
|
|
31
|
+
| "parent-bridge-session-unavailable"
|
|
32
|
+
| "parent-bridge-provider-not-installed"
|
|
33
|
+
| "parent-bridge-capture-not-ready"
|
|
34
|
+
| "parent-bridge-model-key-mismatch"
|
|
35
|
+
| "parent-bridge-generation-invalidated";
|
|
36
|
+
|
|
37
|
+
export class ParentPayloadBridgeError extends Error {
|
|
38
|
+
readonly code: ParentBridgeFailureCode;
|
|
39
|
+
|
|
40
|
+
constructor(code: ParentBridgeFailureCode) {
|
|
41
|
+
super(code);
|
|
42
|
+
this.name = "ParentPayloadBridgeError";
|
|
43
|
+
this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
30
47
|
export interface ParentProviderRegistry {
|
|
31
48
|
getProvider(provider: string): Provider | undefined;
|
|
32
49
|
registerProvider(provider: Provider): void;
|
|
@@ -217,6 +234,32 @@ export class ParentPayloadBridge {
|
|
|
217
234
|
return capture;
|
|
218
235
|
}
|
|
219
236
|
|
|
237
|
+
diagnoseCapture(
|
|
238
|
+
modelKey: ParentModelKey,
|
|
239
|
+
registry: Pick<ParentProviderRegistry, "getProvider">,
|
|
240
|
+
): ParentBridgeFailureCode | undefined {
|
|
241
|
+
const state = this.state;
|
|
242
|
+
if (!state?.active) return "parent-bridge-session-unavailable";
|
|
243
|
+
const current = registry.getProvider(modelKey.provider);
|
|
244
|
+
if (!current || !this.isWrappedProvider(current)) {
|
|
245
|
+
return "parent-bridge-provider-not-installed";
|
|
246
|
+
}
|
|
247
|
+
const capture = state.ready.get(modelKeyId(modelKey));
|
|
248
|
+
if (capture && sameModelKey(capture.modelKey, modelKey)) {
|
|
249
|
+
return this.unwrapProvider(current) === capture.delegate
|
|
250
|
+
? undefined
|
|
251
|
+
: "parent-bridge-provider-not-installed";
|
|
252
|
+
}
|
|
253
|
+
if (
|
|
254
|
+
[...state.ready.values()].some(
|
|
255
|
+
(ready) => ready.modelKey.provider === modelKey.provider,
|
|
256
|
+
)
|
|
257
|
+
) {
|
|
258
|
+
return "parent-bridge-model-key-mismatch";
|
|
259
|
+
}
|
|
260
|
+
return "parent-bridge-capture-not-ready";
|
|
261
|
+
}
|
|
262
|
+
|
|
220
263
|
wrapProvider(delegate: Provider): Provider {
|
|
221
264
|
const original = this.unwrapProvider(delegate);
|
|
222
265
|
const stream = (
|
|
@@ -265,7 +308,9 @@ export class ParentPayloadBridge {
|
|
|
265
308
|
childOnPayload?: ParentPayloadCallback,
|
|
266
309
|
signal?: AbortSignal,
|
|
267
310
|
): Promise<Record<string, unknown>> {
|
|
268
|
-
if (!capture)
|
|
311
|
+
if (!capture) {
|
|
312
|
+
throw new ParentPayloadBridgeError("parent-bridge-capture-not-ready");
|
|
313
|
+
}
|
|
269
314
|
const requestedKey = modelKeyFor(model);
|
|
270
315
|
const state = this.requireCurrentCapture(capture, requestedKey);
|
|
271
316
|
signal?.throwIfAborted();
|
|
@@ -360,13 +405,13 @@ export class ParentPayloadBridge {
|
|
|
360
405
|
model: Model<string>,
|
|
361
406
|
provisional: ProvisionalCapture | undefined,
|
|
362
407
|
): Promise<void> {
|
|
363
|
-
let successful = false;
|
|
364
408
|
try {
|
|
365
409
|
for await (const event of source) {
|
|
366
|
-
if (event.type === "done")
|
|
410
|
+
if (event.type === "done") {
|
|
411
|
+
if (provisional) this.commitCapture(provisional);
|
|
412
|
+
}
|
|
367
413
|
output.push(event);
|
|
368
414
|
}
|
|
369
|
-
if (successful && provisional) this.commitCapture(provisional);
|
|
370
415
|
} catch {
|
|
371
416
|
const error = streamFailure(model);
|
|
372
417
|
output.push({ type: "error", reason: "error", error });
|
|
@@ -401,20 +446,32 @@ export class ParentPayloadBridge {
|
|
|
401
446
|
modelKey: ParentModelKey,
|
|
402
447
|
): GenerationState {
|
|
403
448
|
const state = this.state;
|
|
449
|
+
if (!capture) {
|
|
450
|
+
throw new ParentPayloadBridgeError("parent-bridge-capture-not-ready");
|
|
451
|
+
}
|
|
404
452
|
if (
|
|
405
|
-
!capture ||
|
|
406
453
|
!state?.active ||
|
|
407
454
|
this.captureStates.get(capture) !== state ||
|
|
408
455
|
capture.generation !== state.generation ||
|
|
409
|
-
capture.sessionId !== state.sessionId
|
|
410
|
-
!sameModelKey(capture.modelKey, modelKey) ||
|
|
411
|
-
state.ready.get(modelKeyId(modelKey)) !== capture
|
|
456
|
+
capture.sessionId !== state.sessionId
|
|
412
457
|
) {
|
|
413
|
-
throw new
|
|
458
|
+
throw new ParentPayloadBridgeError(
|
|
459
|
+
"parent-bridge-generation-invalidated",
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (!sameModelKey(capture.modelKey, modelKey)) {
|
|
463
|
+
throw new ParentPayloadBridgeError("parent-bridge-model-key-mismatch");
|
|
464
|
+
}
|
|
465
|
+
if (state.ready.get(modelKeyId(modelKey)) !== capture) {
|
|
466
|
+
throw new ParentPayloadBridgeError("parent-bridge-capture-not-ready");
|
|
414
467
|
}
|
|
415
468
|
return state;
|
|
416
469
|
}
|
|
417
470
|
|
|
471
|
+
private isWrappedProvider(provider: Provider): boolean {
|
|
472
|
+
return Reflect.get(provider, PROVIDER_WRAPPER) !== undefined;
|
|
473
|
+
}
|
|
474
|
+
|
|
418
475
|
private unwrapProvider(provider: Provider): Provider {
|
|
419
476
|
let current = provider;
|
|
420
477
|
const seen = new Set<Provider>();
|
package/src/parent-provider.ts
CHANGED
|
@@ -14,10 +14,11 @@ import {
|
|
|
14
14
|
ModelRuntime,
|
|
15
15
|
} from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import type { ChildFailure } from "./contracts.ts";
|
|
17
|
-
import
|
|
18
|
-
ParentPayloadBridge,
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
import {
|
|
18
|
+
type ParentPayloadBridge,
|
|
19
|
+
ParentPayloadBridgeError,
|
|
20
|
+
type ParentPayloadCallback,
|
|
21
|
+
type ParentPayloadCapture,
|
|
21
22
|
} from "./parent-payload-bridge.ts";
|
|
22
23
|
import type { SubagentEndpoint } from "./subagent-endpoint.ts";
|
|
23
24
|
|
|
@@ -145,7 +146,12 @@ type PhaseRuntimeFailure = Extract<
|
|
|
145
146
|
>;
|
|
146
147
|
|
|
147
148
|
export type PhaseRuntimeResult =
|
|
148
|
-
| {
|
|
149
|
+
| {
|
|
150
|
+
ok: true;
|
|
151
|
+
modelRuntime: ModelRuntime;
|
|
152
|
+
model: Model<string>;
|
|
153
|
+
failureOverride?: () => PhaseRuntimeFailure | undefined;
|
|
154
|
+
}
|
|
149
155
|
| { ok: false; error: string; failure: PhaseRuntimeFailure };
|
|
150
156
|
|
|
151
157
|
function phaseRuntimeFailure(
|
|
@@ -170,15 +176,28 @@ function phasePayloadCallback(
|
|
|
170
176
|
payloadBridge: PhasePayloadBridge,
|
|
171
177
|
childOnPayload: ParentPayloadCallback | undefined,
|
|
172
178
|
signal: AbortSignal | undefined,
|
|
179
|
+
diagnostic?: { failure?: PhaseRuntimeFailure },
|
|
173
180
|
): ParentPayloadCallback {
|
|
174
|
-
return (payload, model) =>
|
|
175
|
-
|
|
176
|
-
payloadBridge.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
return async (payload, model) => {
|
|
182
|
+
try {
|
|
183
|
+
return await payloadBridge.bridge.composePayload(
|
|
184
|
+
payloadBridge.capture,
|
|
185
|
+
payload,
|
|
186
|
+
model,
|
|
187
|
+
childOnPayload,
|
|
188
|
+
signal,
|
|
189
|
+
);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (error instanceof ParentPayloadBridgeError && diagnostic) {
|
|
192
|
+
diagnostic.failure = {
|
|
193
|
+
kind: "environment",
|
|
194
|
+
code: error.code,
|
|
195
|
+
stage: "child-provider-stream",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
182
201
|
}
|
|
183
202
|
|
|
184
203
|
export async function runtimeForProvider(
|
|
@@ -209,6 +228,7 @@ export function phaseProvider(
|
|
|
209
228
|
env?: Record<string, string>;
|
|
210
229
|
},
|
|
211
230
|
payloadBridge: PhasePayloadBridge,
|
|
231
|
+
diagnostic?: { failure?: PhaseRuntimeFailure },
|
|
212
232
|
): Provider {
|
|
213
233
|
return {
|
|
214
234
|
...parent,
|
|
@@ -232,6 +252,7 @@ export function phaseProvider(
|
|
|
232
252
|
payloadBridge,
|
|
233
253
|
options?.onPayload as ParentPayloadCallback | undefined,
|
|
234
254
|
options?.signal,
|
|
255
|
+
diagnostic,
|
|
235
256
|
);
|
|
236
257
|
return parent.stream(model, context, {
|
|
237
258
|
...options,
|
|
@@ -247,6 +268,7 @@ export function phaseProvider(
|
|
|
247
268
|
payloadBridge,
|
|
248
269
|
options?.onPayload as ParentPayloadCallback | undefined,
|
|
249
270
|
options?.signal,
|
|
271
|
+
diagnostic,
|
|
250
272
|
);
|
|
251
273
|
return parent.streamSimple(model, context, {
|
|
252
274
|
...options,
|
|
@@ -321,31 +343,55 @@ export async function runtimeFromContext(
|
|
|
321
343
|
const effectiveModelKey = modelKeyFor(model);
|
|
322
344
|
const capture = payloadBridge.capture(effectiveModelKey, ctx.modelRegistry);
|
|
323
345
|
if (!capture) {
|
|
346
|
+
const code =
|
|
347
|
+
payloadBridge.diagnoseCapture(effectiveModelKey, ctx.modelRegistry) ??
|
|
348
|
+
"parent-bridge-capture-not-ready";
|
|
324
349
|
return phaseRuntimeFailure(
|
|
325
|
-
{ kind: "
|
|
326
|
-
|
|
350
|
+
{ kind: "environment", code, stage: "phase-runtime" },
|
|
351
|
+
code,
|
|
327
352
|
);
|
|
328
353
|
}
|
|
329
|
-
const
|
|
354
|
+
const captureFailure = () => {
|
|
330
355
|
const currentModel = ctx.model;
|
|
331
|
-
|
|
332
|
-
currentModel
|
|
333
|
-
sameSelectedModel(currentModel, selectedModelKey)
|
|
356
|
+
if (
|
|
357
|
+
currentModel === undefined ||
|
|
358
|
+
!sameSelectedModel(currentModel, selectedModelKey)
|
|
359
|
+
) {
|
|
360
|
+
return "parent-bridge-model-key-mismatch" as const;
|
|
361
|
+
}
|
|
362
|
+
if (
|
|
334
363
|
payloadBridge.capture(effectiveModelKey, ctx.modelRegistry) === capture
|
|
364
|
+
) {
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
367
|
+
return (
|
|
368
|
+
payloadBridge.diagnoseCapture(effectiveModelKey, ctx.modelRegistry) ??
|
|
369
|
+
"parent-bridge-capture-not-ready"
|
|
335
370
|
);
|
|
336
371
|
};
|
|
337
|
-
|
|
372
|
+
const initialCaptureFailure = captureFailure();
|
|
373
|
+
if (initialCaptureFailure) {
|
|
338
374
|
return phaseRuntimeFailure(
|
|
339
|
-
{
|
|
340
|
-
|
|
375
|
+
{
|
|
376
|
+
kind: "environment",
|
|
377
|
+
code: initialCaptureFailure,
|
|
378
|
+
stage: "phase-runtime",
|
|
379
|
+
},
|
|
380
|
+
initialCaptureFailure,
|
|
341
381
|
);
|
|
342
382
|
}
|
|
343
383
|
const delegate = capture.delegate;
|
|
344
384
|
if (!delegate) throw new Error("parent Provider is unavailable");
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
385
|
+
const diagnostic: { failure?: PhaseRuntimeFailure } = {};
|
|
386
|
+
const provider = phaseProvider(
|
|
387
|
+
delegate,
|
|
388
|
+
resolved,
|
|
389
|
+
{
|
|
390
|
+
bridge: payloadBridge,
|
|
391
|
+
capture,
|
|
392
|
+
},
|
|
393
|
+
diagnostic,
|
|
394
|
+
);
|
|
349
395
|
let modelRuntime: ModelRuntime;
|
|
350
396
|
try {
|
|
351
397
|
modelRuntime = await runtimeForProvider(provider, signal);
|
|
@@ -354,13 +400,23 @@ export async function runtimeFromContext(
|
|
|
354
400
|
throw error;
|
|
355
401
|
}
|
|
356
402
|
if (signal?.aborted) return cancelledPhaseRuntime();
|
|
357
|
-
|
|
403
|
+
const finalCaptureFailure = captureFailure();
|
|
404
|
+
if (finalCaptureFailure) {
|
|
358
405
|
return phaseRuntimeFailure(
|
|
359
|
-
{
|
|
360
|
-
|
|
406
|
+
{
|
|
407
|
+
kind: "environment",
|
|
408
|
+
code: finalCaptureFailure,
|
|
409
|
+
stage: "phase-runtime",
|
|
410
|
+
},
|
|
411
|
+
finalCaptureFailure,
|
|
361
412
|
);
|
|
362
413
|
}
|
|
363
|
-
return {
|
|
414
|
+
return {
|
|
415
|
+
ok: true,
|
|
416
|
+
modelRuntime,
|
|
417
|
+
model,
|
|
418
|
+
failureOverride: () => diagnostic.failure,
|
|
419
|
+
};
|
|
364
420
|
}
|
|
365
421
|
|
|
366
422
|
function abortable<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
|
package/src/runtime.ts
CHANGED
|
@@ -521,6 +521,25 @@ function attemptFailureCause(
|
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
function attemptsExhaustedFailure(failure: ChildFailure): TaskFailure {
|
|
525
|
+
const cause = attemptFailureCause(failure);
|
|
526
|
+
if (failure.kind !== "transport") {
|
|
527
|
+
return { kind: "attempts-exhausted", cause };
|
|
528
|
+
}
|
|
529
|
+
return {
|
|
530
|
+
kind: "attempts-exhausted",
|
|
531
|
+
cause,
|
|
532
|
+
lastFailure: {
|
|
533
|
+
code: failure.code,
|
|
534
|
+
stage:
|
|
535
|
+
failure.stage ??
|
|
536
|
+
(failure.code === "child-timeout" || failure.code === "timeout"
|
|
537
|
+
? "child-timeout"
|
|
538
|
+
: "child-provider-stream"),
|
|
539
|
+
},
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
524
543
|
function assertNever(value: never): never {
|
|
525
544
|
throw new Error(`unhandled candidate failure: ${JSON.stringify(value)}`);
|
|
526
545
|
}
|
|
@@ -1397,6 +1416,7 @@ export class Runtime {
|
|
|
1397
1416
|
],
|
|
1398
1417
|
timeoutMs: LIMITS.phaseTimeoutMs,
|
|
1399
1418
|
signal,
|
|
1419
|
+
failureOverride: phase.failureOverride,
|
|
1400
1420
|
});
|
|
1401
1421
|
if (signal.aborted)
|
|
1402
1422
|
return {
|
|
@@ -1901,12 +1921,7 @@ export class Runtime {
|
|
|
1901
1921
|
const firstTerminal = terminalTaskFailure(firstFailure);
|
|
1902
1922
|
if (firstTerminal) return finish(block(firstTerminal));
|
|
1903
1923
|
if (launchIndex === 1) {
|
|
1904
|
-
return finish(
|
|
1905
|
-
block({
|
|
1906
|
-
kind: "attempts-exhausted",
|
|
1907
|
-
cause: attemptFailureCause(firstFailure),
|
|
1908
|
-
}),
|
|
1909
|
-
);
|
|
1924
|
+
return finish(block(attemptsExhaustedFailure(firstFailure)));
|
|
1910
1925
|
}
|
|
1911
1926
|
if (firstFailure.kind === "artifact" || firstFailure.kind === "stale") {
|
|
1912
1927
|
task.state = {
|
|
@@ -1940,12 +1955,7 @@ export class Runtime {
|
|
|
1940
1955
|
}
|
|
1941
1956
|
const secondTerminal = terminalTaskFailure(secondFailure);
|
|
1942
1957
|
if (secondTerminal) return finish(block(secondTerminal));
|
|
1943
|
-
return finish(
|
|
1944
|
-
block({
|
|
1945
|
-
kind: "attempts-exhausted",
|
|
1946
|
-
cause: attemptFailureCause(secondFailure),
|
|
1947
|
-
}),
|
|
1948
|
-
);
|
|
1958
|
+
return finish(block(attemptsExhaustedFailure(secondFailure)));
|
|
1949
1959
|
}
|
|
1950
1960
|
|
|
1951
1961
|
private captureUsage(
|