@zq-silk/yui 0.16.0 → 0.99.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.
Potentially problematic release.
This version of @zq-silk/yui might be problematic. Click here for more details.
- package/ARCHITECTURE.md +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +3 -0
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +7 -17
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -33
- package/dist/cli/updatePorts.js +51 -20
- package/dist/cli.js +35 -26
- package/dist/commands/controllerCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +84 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +1 -1
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/controller.js +8 -6
- package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
- package/dist/controller/globalInputDelivery.js +28 -7
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +10 -3
- package/dist/controller/runtime.js +1 -27
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +27 -0
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/doctor/doctor.js +2 -1
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +5 -2
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/message/messageContinuation.js +8 -9
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +3 -5
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +22 -62
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +1 -1
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/providerContinuationReconciliationService.js +42 -3
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +2 -1
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/docs/managed-turn-and-session-runtime.md +31 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +128 -20
- package/docs/release-workflow.zh-CN.md +107 -14
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/task-delivery.md +109 -0
- package/docs/task-delivery.zh-CN.md +80 -0
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +33 -12
- package/docs/testing/verification-levels.zh-CN.md +28 -8
- package/i18n/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
|
@@ -70,8 +70,12 @@ export class CodexAppServerRuntime {
|
|
|
70
70
|
async readGoal(conversationId) {
|
|
71
71
|
const id = text(conversationId, "Codex thread id");
|
|
72
72
|
const result = await this.transport.request("thread/goal/get", { threadId: id });
|
|
73
|
+
if (result.goal === null)
|
|
74
|
+
return null;
|
|
73
75
|
const goal = objectMember(result, "goal");
|
|
74
|
-
|
|
76
|
+
if (goal === null)
|
|
77
|
+
throw new Error("Codex thread/goal/get returned no Goal state.");
|
|
78
|
+
return codexThreadGoal(goal, id);
|
|
75
79
|
}
|
|
76
80
|
async inspectConversation(conversationId) {
|
|
77
81
|
const id = text(conversationId, "Codex thread id");
|
|
@@ -162,8 +166,7 @@ export class CodexAppServerRuntime {
|
|
|
162
166
|
text_elements: []
|
|
163
167
|
}]
|
|
164
168
|
});
|
|
165
|
-
const turnId = optionalId(result
|
|
166
|
-
?? optionalId(objectMember(result, "turn")?.id);
|
|
169
|
+
const turnId = optionalId(objectMember(result, "turn")?.id);
|
|
167
170
|
return turnId === undefined
|
|
168
171
|
? { status: "unknown", reason: "turn/start returned no durable turn id" }
|
|
169
172
|
: { status: "accepted", turnId };
|
|
@@ -244,7 +247,7 @@ export class CodexAppServerRuntime {
|
|
|
244
247
|
const result = await this.transport.request("thread/list", {
|
|
245
248
|
ancestorThreadId: threadId
|
|
246
249
|
});
|
|
247
|
-
const candidates = arrayMember(result, "
|
|
250
|
+
const candidates = arrayMember(result, "data")
|
|
248
251
|
.flatMap((entry) => optionalId(object(entry)?.id) === undefined
|
|
249
252
|
? []
|
|
250
253
|
: [optionalId(object(entry)?.id)]);
|
|
@@ -276,7 +279,7 @@ export class CodexAppServerRuntime {
|
|
|
276
279
|
return {
|
|
277
280
|
quality: "unavailable",
|
|
278
281
|
continuations: [],
|
|
279
|
-
detail:
|
|
282
|
+
detail: serializeAgentErrorRaw(error)
|
|
280
283
|
};
|
|
281
284
|
}
|
|
282
285
|
return { quality: "exact", continuations: Object.freeze(observed) };
|
|
@@ -284,10 +287,12 @@ export class CodexAppServerRuntime {
|
|
|
284
287
|
}
|
|
285
288
|
/** thread/closed unloads the client attachment; the durable thread remains resumable. */
|
|
286
289
|
export function codexNotificationBoundary(input) {
|
|
287
|
-
const conversationId =
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
290
|
+
const conversationId = input.method === "thread/started"
|
|
291
|
+
? optionalId(objectMember(input.params, "thread")?.id)
|
|
292
|
+
: optionalId(input.params.threadId);
|
|
293
|
+
const turnId = input.method === "turn/started" || input.method === "turn/completed"
|
|
294
|
+
? optionalId(objectMember(input.params, "turn")?.id)
|
|
295
|
+
: optionalId(input.params.turnId);
|
|
291
296
|
if (input.method === "thread/closed")
|
|
292
297
|
return { kind: "attachment-closed", conversationId };
|
|
293
298
|
if (input.method === "thread/goal/updated")
|
|
@@ -310,45 +315,53 @@ export function codexGoalNotification(input) {
|
|
|
310
315
|
return undefined;
|
|
311
316
|
const goal = objectMember(input.params, "goal");
|
|
312
317
|
if (goal === null)
|
|
313
|
-
|
|
318
|
+
throw new Error("Codex Goal notification has no Goal state.");
|
|
314
319
|
return codexThreadGoal(goal, conversationId);
|
|
315
320
|
}
|
|
316
321
|
function parseThreadSnapshot(result, expectedThreadId, loaded) {
|
|
317
|
-
const thread = objectMember(result, "thread")
|
|
318
|
-
|
|
322
|
+
const thread = objectMember(result, "thread");
|
|
323
|
+
if (thread === null)
|
|
324
|
+
throw new Error("Codex App Server returned no thread.");
|
|
325
|
+
const id = text(optionalId(thread.id), "Codex response thread id");
|
|
319
326
|
if (id !== expectedThreadId)
|
|
320
327
|
throw new Error("Codex App Server returned a different thread.");
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
+
const status = threadStatus(thread.status);
|
|
329
|
+
if (status === "unknown")
|
|
330
|
+
throw new Error("Codex thread status is missing or unsupported.");
|
|
331
|
+
if (!Array.isArray(thread.turns))
|
|
332
|
+
throw new Error("Codex thread turns are missing or invalid.");
|
|
333
|
+
const turnSnapshots = thread.turns.map((value) => {
|
|
334
|
+
const turn = object(value);
|
|
335
|
+
if (turn === null)
|
|
336
|
+
throw new Error("Codex thread contains an invalid Turn.");
|
|
337
|
+
const turnId = text(optionalId(turn.id), "Codex response Turn id");
|
|
328
338
|
const status = optionalTurnStatus(turn.status);
|
|
339
|
+
if (status === undefined)
|
|
340
|
+
throw new Error("Codex Turn status is missing or unsupported.");
|
|
329
341
|
const output = codexTurnOutput(turn);
|
|
330
342
|
const error = providerError(turn.error);
|
|
331
|
-
return
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
343
|
+
return {
|
|
344
|
+
turnId,
|
|
345
|
+
status,
|
|
346
|
+
...(output === undefined ? {} : { output }),
|
|
347
|
+
...(error === undefined ? {} : { error }),
|
|
348
|
+
...(turn.error === undefined || turn.error === null ? {} : {
|
|
349
|
+
rawError: serializeAgentErrorRaw(turn.error)
|
|
350
|
+
})
|
|
351
|
+
};
|
|
340
352
|
});
|
|
353
|
+
const active = [...turnSnapshots].reverse().find((turn) => turn.status === "inProgress");
|
|
354
|
+
const latestStatus = turnSnapshots.at(-1)?.status;
|
|
341
355
|
return {
|
|
342
356
|
threadId: id,
|
|
343
357
|
loaded,
|
|
344
|
-
status
|
|
345
|
-
...(
|
|
358
|
+
status,
|
|
359
|
+
...(active === undefined ? {} : { activeTurnId: active.turnId }),
|
|
346
360
|
...(latestStatus === undefined ? {} : { latestTurnStatus: latestStatus }),
|
|
347
361
|
turns: Object.freeze(turnSnapshots),
|
|
348
362
|
...(optionalId(thread.parentThreadId) === undefined
|
|
349
363
|
? {}
|
|
350
364
|
: { parentThreadId: optionalId(thread.parentThreadId) }),
|
|
351
|
-
ancestorThreadIds: Object.freeze(arrayMember(thread, "ancestorThreadIds").flatMap((entry) => (optionalId(entry) === undefined ? [] : [optionalId(entry)]))),
|
|
352
365
|
raw: result
|
|
353
366
|
};
|
|
354
367
|
}
|
|
@@ -356,36 +369,9 @@ function parseThreadSnapshot(result, expectedThreadId, loaded) {
|
|
|
356
369
|
export function codexTurnOutput(turn) {
|
|
357
370
|
for (const value of [...arrayMember(turn, "items")].reverse()) {
|
|
358
371
|
const item = object(value);
|
|
359
|
-
if (item ===
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const message = object(item.message);
|
|
363
|
-
const agentMessage = type === "agentMessage"
|
|
364
|
-
|| type === "agent_message"
|
|
365
|
-
|| (type === "message" && (item.role === "assistant" || message?.role === "assistant"));
|
|
366
|
-
if (!agentMessage)
|
|
367
|
-
continue;
|
|
368
|
-
const text = messageOutput(item) ?? (message === null ? undefined : messageOutput(message));
|
|
369
|
-
if (text !== undefined)
|
|
370
|
-
return text;
|
|
371
|
-
}
|
|
372
|
-
return undefined;
|
|
373
|
-
}
|
|
374
|
-
function messageOutput(message) {
|
|
375
|
-
for (const value of [message.text, message.content]) {
|
|
376
|
-
if (typeof value === "string" && value.trim().length > 0)
|
|
377
|
-
return value;
|
|
378
|
-
if (!Array.isArray(value))
|
|
379
|
-
continue;
|
|
380
|
-
const parts = value.flatMap((part) => {
|
|
381
|
-
if (typeof part === "string" && part.trim().length > 0)
|
|
382
|
-
return [part];
|
|
383
|
-
const record = object(part);
|
|
384
|
-
const text = record?.text ?? record?.content;
|
|
385
|
-
return typeof text === "string" && text.trim().length > 0 ? [text] : [];
|
|
386
|
-
});
|
|
387
|
-
if (parts.length > 0)
|
|
388
|
-
return parts.join("\n");
|
|
372
|
+
if (item?.type === "agentMessage"
|
|
373
|
+
&& typeof item.text === "string" && item.text.trim().length > 0)
|
|
374
|
+
return item.text;
|
|
389
375
|
}
|
|
390
376
|
return undefined;
|
|
391
377
|
}
|
|
@@ -393,33 +379,12 @@ function messageOutput(message) {
|
|
|
393
379
|
export function codexTurnInput(turn) {
|
|
394
380
|
for (const value of arrayMember(turn, "items")) {
|
|
395
381
|
const item = object(value);
|
|
396
|
-
if (item
|
|
397
|
-
continue;
|
|
398
|
-
const type = item.type;
|
|
399
|
-
const message = object(item.message);
|
|
400
|
-
const userMessage = type === "userMessage"
|
|
401
|
-
|| type === "user_message"
|
|
402
|
-
|| (type === "message" && (item.role === "user" || message?.role === "user"));
|
|
403
|
-
if (!userMessage)
|
|
404
|
-
continue;
|
|
405
|
-
const text = messageText(item) ?? (message === null ? undefined : messageText(message));
|
|
406
|
-
if (text !== undefined)
|
|
407
|
-
return text;
|
|
408
|
-
}
|
|
409
|
-
return undefined;
|
|
410
|
-
}
|
|
411
|
-
function messageText(message) {
|
|
412
|
-
for (const value of [message.text, message.content]) {
|
|
413
|
-
if (typeof value === "string" && value.trim().length > 0)
|
|
414
|
-
return value.trim();
|
|
415
|
-
if (!Array.isArray(value))
|
|
382
|
+
if (item?.type !== "userMessage")
|
|
416
383
|
continue;
|
|
417
|
-
const parts =
|
|
418
|
-
if (typeof part === "string" && part.trim().length > 0)
|
|
419
|
-
return [part];
|
|
384
|
+
const parts = arrayMember(item, "content").flatMap((part) => {
|
|
420
385
|
const record = object(part);
|
|
421
|
-
|
|
422
|
-
|
|
386
|
+
return record?.type === "text" && typeof record.text === "string"
|
|
387
|
+
&& record.text.trim().length > 0 ? [record.text] : [];
|
|
423
388
|
});
|
|
424
389
|
if (parts.length > 0)
|
|
425
390
|
return parts.join("\n").trim();
|
|
@@ -438,7 +403,7 @@ function providerError(value) {
|
|
|
438
403
|
return JSON.stringify(record);
|
|
439
404
|
}
|
|
440
405
|
function codexThreadGoal(goal, expectedThreadId) {
|
|
441
|
-
const conversationId = text(goal.threadId, "Codex Goal thread id");
|
|
406
|
+
const conversationId = text(optionalId(goal.threadId), "Codex Goal thread id");
|
|
442
407
|
if (conversationId !== expectedThreadId) {
|
|
443
408
|
throw new Error("Codex Goal belongs to a different thread.");
|
|
444
409
|
}
|
|
@@ -554,7 +519,7 @@ export function codexAppServerErrorIsMissing(error) {
|
|
|
554
519
|
|| /\b(not found|missing|does not exist)\b.*\b(thread|conversation)\b/u.test(message);
|
|
555
520
|
}
|
|
556
521
|
function threadId(result) {
|
|
557
|
-
return text(optionalId(
|
|
522
|
+
return text(optionalId(objectMember(result, "thread")?.id), "Codex response thread id");
|
|
558
523
|
}
|
|
559
524
|
function object(value) {
|
|
560
525
|
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
@@ -568,7 +533,8 @@ function arrayMember(value, key) {
|
|
|
568
533
|
return Array.isArray(value[key]) ? value[key] : [];
|
|
569
534
|
}
|
|
570
535
|
function optionalId(value) {
|
|
571
|
-
return typeof value === "string" && value.
|
|
536
|
+
return typeof value === "string" && !value.includes("\0") && value.length > 0
|
|
537
|
+
&& value.trim() === value ? value : undefined;
|
|
572
538
|
}
|
|
573
539
|
function text(value, label) {
|
|
574
540
|
if (typeof value !== "string" || value.includes("\0") || value.trim().length === 0) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serializeAgentErrorRaw } from "./agentError.js";
|
|
1
2
|
export const PROVIDER_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
|
|
2
3
|
/**
|
|
3
4
|
* Newline-delimited JSON over a child process's stdio. This is a plain shared
|
|
@@ -7,13 +8,15 @@ export const PROVIDER_MESSAGE_MAX_BYTES = 16 * 1024 * 1024;
|
|
|
7
8
|
export class JsonLineChannel {
|
|
8
9
|
child;
|
|
9
10
|
mirror;
|
|
11
|
+
onFailure;
|
|
10
12
|
#listeners = new Set();
|
|
11
13
|
#closeListeners = new Set();
|
|
12
14
|
#buffer = "";
|
|
13
15
|
#closedError;
|
|
14
|
-
constructor(child, mirror) {
|
|
16
|
+
constructor(child, mirror, onFailure) {
|
|
15
17
|
this.child = child;
|
|
16
18
|
this.mirror = mirror;
|
|
19
|
+
this.onFailure = onFailure;
|
|
17
20
|
child.stdout.setEncoding("utf8");
|
|
18
21
|
child.stdout.on("data", (chunk) => this.#receive(chunk));
|
|
19
22
|
child.once("error", (error) => this.#close(error));
|
|
@@ -57,10 +60,12 @@ export class JsonLineChannel {
|
|
|
57
60
|
});
|
|
58
61
|
}
|
|
59
62
|
#receive(chunk) {
|
|
63
|
+
if (this.#closedError !== undefined)
|
|
64
|
+
return;
|
|
60
65
|
this.mirror("stdout", chunk);
|
|
61
66
|
this.#buffer += chunk;
|
|
62
67
|
if (Buffer.byteLength(this.#buffer, "utf8") > PROVIDER_MESSAGE_MAX_BYTES) {
|
|
63
|
-
this.#
|
|
68
|
+
this.#fail(new Error("Provider response line exceeds its message bound."));
|
|
64
69
|
terminateProcessGroup(this.child, "SIGTERM");
|
|
65
70
|
return;
|
|
66
71
|
}
|
|
@@ -72,18 +77,41 @@ export class JsonLineChannel {
|
|
|
72
77
|
this.#buffer = this.#buffer.slice(newline + 1);
|
|
73
78
|
if (line.length === 0)
|
|
74
79
|
continue;
|
|
80
|
+
let parsed;
|
|
81
|
+
try {
|
|
82
|
+
parsed = JSON.parse(line);
|
|
83
|
+
}
|
|
84
|
+
catch (cause) {
|
|
85
|
+
this.#fail(new Error("Provider response is not valid JSON.", { cause }));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
89
|
+
this.#fail(new Error("Provider response must be a JSON object."));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Unknown protocol objects belong to the protocol listener. A listener
|
|
93
|
+
// throwing is not an unrelated notification or a parse error.
|
|
75
94
|
try {
|
|
76
|
-
const parsed = JSON.parse(line);
|
|
77
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
78
|
-
continue;
|
|
79
95
|
for (const listener of this.#listeners)
|
|
80
96
|
listener(parsed);
|
|
81
97
|
}
|
|
82
|
-
catch {
|
|
83
|
-
|
|
98
|
+
catch (cause) {
|
|
99
|
+
this.#fail(new Error("Provider message listener failed.", { cause }));
|
|
100
|
+
return;
|
|
84
101
|
}
|
|
85
102
|
}
|
|
86
103
|
}
|
|
104
|
+
#fail(error) {
|
|
105
|
+
// Failing this pipe neither proves a native terminal nor permits replay.
|
|
106
|
+
// Keep the original cause for pending callers and the Host's durable sink.
|
|
107
|
+
try {
|
|
108
|
+
this.mirror("stderr", `${serializeAgentErrorRaw(error)}\n`);
|
|
109
|
+
this.onFailure?.(error);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
this.#close(error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
87
115
|
#close(error) {
|
|
88
116
|
if (this.#closedError !== undefined)
|
|
89
117
|
return;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { serializeAgentErrorRaw, standardAgentError } from "./agentError.js";
|
|
2
3
|
import { providerContinuationKey } from "./providerContinuation.js";
|
|
3
4
|
import { projectProviderContinuations } from "./runtimeContinuationProjection.js";
|
|
4
5
|
import { reconcileKnownDetachedContinuations } from "./providerRuntimeReconciler.js";
|
|
@@ -39,17 +40,55 @@ export class ProviderContinuationReconciliationService {
|
|
|
39
40
|
now
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
|
-
catch {
|
|
43
|
+
catch (error) {
|
|
43
44
|
// One malformed identity group must not restart or block the
|
|
44
45
|
// Controller. Keep it writer-owned and retry through the same
|
|
45
46
|
// bounded, metadata-only schedule.
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
result = {
|
|
48
|
+
continuations: group, quality: "unavailable", changed: false,
|
|
49
|
+
schedule: failureSchedule(groupKey, previous, now),
|
|
50
|
+
failure: standardAgentError({
|
|
51
|
+
source: "yui", phase: "turn-reconcile",
|
|
52
|
+
message: error instanceof Error ? error.message : String(error),
|
|
53
|
+
raw: serializeAgentErrorRaw(error)
|
|
54
|
+
})
|
|
55
|
+
};
|
|
48
56
|
}
|
|
49
57
|
if (result.schedule === null)
|
|
50
58
|
this.#schedules.delete(groupKey);
|
|
51
59
|
else
|
|
52
60
|
this.#schedules.set(groupKey, result.schedule);
|
|
61
|
+
if (result.failure !== undefined) {
|
|
62
|
+
const failure = result.failure;
|
|
63
|
+
// One original failure and one circuit-limit notice, not one wake per
|
|
64
|
+
// poll. Existing observations deduplicate across Controller restarts.
|
|
65
|
+
const digest = createHash("sha256").update(JSON.stringify([
|
|
66
|
+
task.id, candidates.map(entry => entry.fence), failure.raw,
|
|
67
|
+
result.schedule?.circuitOpenUntil === undefined ? "unavailable" : "circuit-open"
|
|
68
|
+
])).digest("hex");
|
|
69
|
+
const eventId = `continuation-reconcile-error:${digest}`;
|
|
70
|
+
if (!events.some(event => event.payload.eventId === eventId
|
|
71
|
+
|| event.type === "runtime.agent-error" && event.payload.sourceEventId === eventId)) {
|
|
72
|
+
const observation = createRuntimeObservation({
|
|
73
|
+
schemaVersion: 4, eventId, semanticKey: eventId,
|
|
74
|
+
kind: "observer.health", authority: "controller",
|
|
75
|
+
receivedAt: now.toISOString(), observedAt: now.toISOString(),
|
|
76
|
+
fence: candidates[0].fence,
|
|
77
|
+
payload: {
|
|
78
|
+
sourceId: "continuation-metadata", observerStatus: "unavailable",
|
|
79
|
+
observerDetail: failure.message,
|
|
80
|
+
failure: { error: { ...failure, raw: serializeAgentErrorRaw({
|
|
81
|
+
failure, targets: group.map(entry => entry.identity),
|
|
82
|
+
schedule: result.schedule,
|
|
83
|
+
confirmed: "Metadata query did not prove quiescence; existing writer ownership is preserved.",
|
|
84
|
+
unknown: "Continuation execution, result and external effects."
|
|
85
|
+
}) } }
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (this.sink.observeRuntimeObservation(observation, now) === "applied")
|
|
89
|
+
changedTaskIds.add(task.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
53
92
|
for (let index = 0; index < group.length; index += 1) {
|
|
54
93
|
const before = group[index];
|
|
55
94
|
const after = result.continuations[index];
|
|
@@ -152,6 +152,21 @@ export function providerRetryProjection(binding) {
|
|
|
152
152
|
category: retry.error.category, preservesWork: true, nativeSessionId: retry.nativeSessionId,
|
|
153
153
|
...(retry.reason === undefined ? {} : { reason: retry.reason }),
|
|
154
154
|
failureRef: retry.failureRef,
|
|
155
|
+
error: retry.error,
|
|
156
|
+
inputRef: retry.input.kind === "text" ? { kind: "text" } : retry.input,
|
|
157
|
+
failedAttemptId: retry.failedAttemptId,
|
|
158
|
+
failureAttemptId: retry.failureAttemptId,
|
|
159
|
+
...(retry.failedNativeTurnId === undefined ? {} : { failedNativeTurnId: retry.failedNativeTurnId }),
|
|
160
|
+
...(retry.currentAttemptId === undefined ? {} : { currentAttemptId: retry.currentAttemptId }),
|
|
161
|
+
currentTurn: binding?.run == null ? null : {
|
|
162
|
+
attemptId: binding.run.attemptId, status: binding.run.status,
|
|
163
|
+
...(binding.run.nativeTurnId === undefined ? {} : { nativeTurnId: binding.run.nativeTurnId }),
|
|
164
|
+
...(binding.run.failure === undefined ? {} : { failure: binding.run.failure })
|
|
165
|
+
},
|
|
166
|
+
history: (binding?.retryHistory ?? []).map(entry => ({
|
|
167
|
+
chainId: entry.chainId, status: entry.status, attempts: entry.attempts,
|
|
168
|
+
failureRef: entry.failureRef, failedAttemptId: entry.failedAttemptId
|
|
169
|
+
})),
|
|
155
170
|
...(retry.previousRunId === undefined ? {} : { previousRunId: retry.previousRunId }),
|
|
156
171
|
...(retry.successorRunId === undefined ? {} : { successorRunId: retry.successorRunId })
|
|
157
172
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { continuationOwnsWriterUmbrella, observeProviderContinuation, providerContinuationKey } from "./providerContinuation.js";
|
|
2
|
+
import { serializeAgentErrorRaw, standardAgentError } from "./agentError.js";
|
|
2
3
|
const BASE_RECONCILE_MS = 2_000;
|
|
3
4
|
const MAX_RECONCILE_MS = 5 * 60_000;
|
|
4
5
|
const CIRCUIT_ERROR_LIMIT = 5;
|
|
@@ -47,8 +48,11 @@ export async function reconcileKnownDetachedContinuations(input) {
|
|
|
47
48
|
})))
|
|
48
49
|
}), QUERY_TIMEOUT_MS);
|
|
49
50
|
result = validateQueryResult(raw, new Set(candidates.map((entry) => (providerContinuationKey(entry.identity)))));
|
|
51
|
+
if (result.quality === "unavailable") {
|
|
52
|
+
throw new Error(result.detail ?? "Provider continuation metadata is unavailable.");
|
|
53
|
+
}
|
|
50
54
|
}
|
|
51
|
-
catch {
|
|
55
|
+
catch (error) {
|
|
52
56
|
const errors = (input.previous?.consecutiveErrors ?? 0) + 1;
|
|
53
57
|
const delay = backoff(input.previous?.attempts ?? 0);
|
|
54
58
|
const circuitOpenUntil = errors >= CIRCUIT_ERROR_LIMIT
|
|
@@ -64,7 +68,13 @@ export async function reconcileKnownDetachedContinuations(input) {
|
|
|
64
68
|
...(circuitOpenUntil === undefined ? {} : { circuitOpenUntil })
|
|
65
69
|
},
|
|
66
70
|
quality: "unavailable",
|
|
67
|
-
changed: false
|
|
71
|
+
changed: false,
|
|
72
|
+
failure: standardAgentError({
|
|
73
|
+
source: "driver", phase: "turn-reconcile",
|
|
74
|
+
message: error instanceof Error ? error.message : String(error),
|
|
75
|
+
raw: serializeAgentErrorRaw(error),
|
|
76
|
+
inputDisposition: "unknown", sessionDisposition: "unknown"
|
|
77
|
+
})
|
|
68
78
|
};
|
|
69
79
|
}
|
|
70
80
|
const observations = new Map(result.continuations.map((entry) => [entry.key, entry]));
|