@zq-silk/yui 0.15.9 → 0.15.12
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/ARCHITECTURE.md +8 -4
- package/ARCHITECTURE.zh-CN.md +5 -2
- package/README.md +13 -5
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/cli/commandCatalog.js +64 -16
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +381 -107
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +339 -4
- package/dist/commands/projectCommands.js +50 -22
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +586 -96
- package/dist/commands/taskIntegrationCommands.js +19 -39
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskOverviewCommand.js +4 -3
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +11 -2
- package/dist/commands/taskPublicationVerifyCommand.js +23 -39
- package/dist/commands/taskRemoteDeliveryCommand.js +22 -11
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +3 -0
- package/dist/context/taskCatalog.js +187 -0
- package/dist/context/taskContext.js +55 -6
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +14 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +519 -25
- package/dist/controller/globalInputDelivery.js +132 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +117 -7
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +329 -386
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/lifecycle/exactRunTerminalization.js +4 -1
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +367 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +430 -107
- package/dist/repository/projectMaintenanceLock.js +75 -18
- package/dist/repository/taskWorkspaceCoordinator.js +182 -101
- package/dist/repository/taskWorkspacePreparer.js +205 -72
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentHost.js +179 -82
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +6 -2
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/activeRoleRunDelivery.js +12 -0
- package/dist/scheduler/leaderWakeupProcessor.js +5 -0
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +38 -6
- package/dist/scheduler/taskObservabilityProjection.js +6 -44
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +26 -8
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +167 -4
- package/dist/storage/sqliteStore.js +57 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +2 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +129 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/web/assets/client/app.js +147 -17
- package/dist/web/assets/client/components.js +56 -13
- package/dist/web/assets/client/i18n.js +78 -4
- package/dist/web/assets/client/taskSurface.js +108 -1
- package/dist/web/assets/client/view.js +39 -8
- package/dist/web/assets/shell.js +29 -0
- package/dist/web/assets/styles/layout.js +8 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +131 -4
- package/dist/web/webSnapshot.js +16 -6
- package/dist/web/webTaskSurface.js +222 -5
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +111 -35
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +30 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
- package/docs/managed-turn-and-session-runtime.md +47 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/release-workflow.md +39 -0
- package/docs/release-workflow.zh-CN.md +29 -0
- package/docs/sqlite-control-plane-design.md +223 -1
- package/docs/task-delivery.md +133 -13
- package/docs/task-delivery.zh-CN.md +99 -10
- package/docs/task-discovery.md +102 -0
- package/docs/task-discovery.zh-CN.md +86 -0
- package/docs/testing/verification-levels.md +40 -0
- package/docs/testing/verification-levels.zh-CN.md +23 -0
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +154 -51
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-operator/SKILL.md +19 -3
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +42 -0
- package/skills/yui-runtime/references/publication.md +42 -0
- package/skills/yui-runtime/references/recovery.md +24 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createSessionOwnerIdentity } from "./sessionOwnerIdentity.js";
|
|
2
|
+
/** Independent of CLI/RPC and Home schema. Keep v1 readable while v1 Hosts live. */
|
|
3
|
+
export const AGENT_HOST_EVENT_PROTOCOL = "yui-agent-host-events/v1";
|
|
4
|
+
export const AGENT_HOST_CONTROL_PROTOCOL = "yui-agent-host/v5";
|
|
5
|
+
export function readAgentHostObservationSource(value) {
|
|
6
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
7
|
+
throw new Error("Agent Host observation source is invalid.");
|
|
8
|
+
}
|
|
9
|
+
const source = value;
|
|
10
|
+
if (source.protocol !== AGENT_HOST_EVENT_PROTOCOL) {
|
|
11
|
+
throw new Error("Agent Host event protocol is unsupported.");
|
|
12
|
+
}
|
|
13
|
+
if (source.connection !== undefined && (source.connection === null
|
|
14
|
+
|| typeof source.connection !== "object" || Array.isArray(source.connection))) {
|
|
15
|
+
throw new Error("Agent Host connection evidence is invalid.");
|
|
16
|
+
}
|
|
17
|
+
if (source.connection?.processOwner !== undefined
|
|
18
|
+
&& (source.connection.processOwner.schemaVersion !== 2
|
|
19
|
+
|| source.connection.processOwner.kind !== "yui-session-owner")) {
|
|
20
|
+
throw new Error("Agent Host process custody wire contract is invalid.");
|
|
21
|
+
}
|
|
22
|
+
const text = (value) => {
|
|
23
|
+
if (typeof value !== "string" || !value.trim() || value.includes("\0") || value.length > 4096) {
|
|
24
|
+
throw new Error("Agent Host observation identity is invalid.");
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
protocol: AGENT_HOST_EVENT_PROTOCOL,
|
|
30
|
+
adapterId: text(source.adapterId),
|
|
31
|
+
workspace: text(source.workspace),
|
|
32
|
+
...(source.startupRunId === undefined ? {} : { startupRunId: text(source.startupRunId) }),
|
|
33
|
+
...(source.connection === undefined ? {} : {
|
|
34
|
+
connection: {
|
|
35
|
+
...(source.connection.processOwner === undefined ? {} : {
|
|
36
|
+
processOwner: createSessionOwnerIdentity({
|
|
37
|
+
...source.connection.processOwner,
|
|
38
|
+
recordedAt: new Date(source.connection.processOwner.recordedAt)
|
|
39
|
+
})
|
|
40
|
+
}),
|
|
41
|
+
...(source.connection.account === undefined ? {} : {
|
|
42
|
+
account: {
|
|
43
|
+
home: text(source.connection.account.home),
|
|
44
|
+
codexHome: text(source.connection.account.codexHome),
|
|
45
|
+
...(source.connection.account.nativeAccountHome === undefined ? {} : {
|
|
46
|
+
nativeAccountHome: text(source.connection.account.nativeAccountHome)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/** Provider-specific Claude Code failure recognition. */
|
|
2
2
|
export function mapClaudeAgentError(input) {
|
|
3
|
+
return withTransientEvidence(mapClaudeDisplayError(input), input);
|
|
4
|
+
}
|
|
5
|
+
function mapClaudeDisplayError(input) {
|
|
3
6
|
const text = `${input.message}\n${input.raw}`;
|
|
4
7
|
if (/^No conversation found with session ID: [A-Za-z0-9-]+\.?$/imu.test(input.message)) {
|
|
5
8
|
return sessionUnavailable("provider.session-not-found");
|
|
@@ -23,6 +26,9 @@ export function mapClaudeAgentError(input) {
|
|
|
23
26
|
}
|
|
24
27
|
/** Provider-specific Codex failure recognition. */
|
|
25
28
|
export function mapCodexAgentError(input) {
|
|
29
|
+
return withTransientEvidence(mapCodexDisplayError(input), input);
|
|
30
|
+
}
|
|
31
|
+
function mapCodexDisplayError(input) {
|
|
26
32
|
const text = `${input.message}\n${input.raw}`;
|
|
27
33
|
if (/^server_error$/iu.test(input.message)) {
|
|
28
34
|
return recoverable("availability", "provider.server-error");
|
|
@@ -200,3 +206,88 @@ function sessionUnavailable(code) {
|
|
|
200
206
|
sessionDisposition: "unrecoverable"
|
|
201
207
|
});
|
|
202
208
|
}
|
|
209
|
+
/** Replay qualification is stricter than the readable, best-effort category.
|
|
210
|
+
* Never promote a number occurring in provider prose to submission safety. */
|
|
211
|
+
function structuredTransientError(input) {
|
|
212
|
+
if (/insufficient[_ -]quota|quota.{0,25}(exhaust|exceed)|billing[_ -]hard[_ -]limit|usageLimitExceeded|credits?.{0,20}(exhaust|insufficient)/iu.test(`${input.message}\n${input.raw}`))
|
|
213
|
+
return { category: "access", code: "provider.quota-exhausted", retryable: false };
|
|
214
|
+
const nodes = [];
|
|
215
|
+
const visit = (value, depth) => {
|
|
216
|
+
if (depth > 8 || value === null || typeof value !== "object" || Array.isArray(value))
|
|
217
|
+
return;
|
|
218
|
+
const node = value;
|
|
219
|
+
nodes.push(node);
|
|
220
|
+
for (const key of ["cause", "error", "data", "codexErrorInfo", "responseTooManyFailedAttempts",
|
|
221
|
+
"httpConnectionFailed", "responseStreamConnectionFailed", "responseStreamDisconnected", "error_details"]) {
|
|
222
|
+
visit(node[key], depth + 1);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
try {
|
|
226
|
+
visit(JSON.parse(input.raw), 0);
|
|
227
|
+
}
|
|
228
|
+
catch { /* Display classification may still use prose. */ }
|
|
229
|
+
const codes = nodes.flatMap(n => [n.code, n.type, n.error, n.codexErrorInfo]).filter(v => typeof v === "string");
|
|
230
|
+
if (codes.some(c => ["authentication_error", "permission_error", "invalid_api_key", "invalid_model",
|
|
231
|
+
"model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))) {
|
|
232
|
+
return { category: "access", code: "provider.access-denied", retryable: false };
|
|
233
|
+
}
|
|
234
|
+
const statuses = nodes.flatMap(n => [n.httpStatusCode, n.statusCode, n.status]).filter(v => typeof v === "number");
|
|
235
|
+
if (statuses.some(s => s === 401 || s === 403 || s === 400 || s === 404)) {
|
|
236
|
+
return { category: "invalid-request", code: "provider.http-4xx", retryable: false };
|
|
237
|
+
}
|
|
238
|
+
const nativeCode = input.message.trim();
|
|
239
|
+
let result;
|
|
240
|
+
if (statuses.includes(429) || codes.includes("rate_limit_error") || nativeCode === "rate_limit_error") {
|
|
241
|
+
result = recoverable("rate-limit", "provider.rate-limit");
|
|
242
|
+
}
|
|
243
|
+
else if (statuses.some(s => [500, 502, 503, 504].includes(s))
|
|
244
|
+
|| codes.some(c => ["server_error", "overloaded_error"].includes(c))
|
|
245
|
+
|| ["server_error", "overloaded_error"].includes(nativeCode)) {
|
|
246
|
+
result = recoverable("availability", "provider.server-error");
|
|
247
|
+
}
|
|
248
|
+
else if (codes.some(c => ["ECONNRESET", "ECONNREFUSED", "EAI_AGAIN", "ETIMEDOUT", "EPIPE"].includes(c))) {
|
|
249
|
+
result = recoverable("transport", "transport.temporary-failure");
|
|
250
|
+
}
|
|
251
|
+
if (result === undefined)
|
|
252
|
+
return undefined;
|
|
253
|
+
const waits = [];
|
|
254
|
+
for (const node of nodes) {
|
|
255
|
+
for (const value of [node.retry_after_ms, node.retryAfterMs]) {
|
|
256
|
+
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0)
|
|
257
|
+
waits.push(value);
|
|
258
|
+
}
|
|
259
|
+
const headers = node.headers;
|
|
260
|
+
if (headers === null || typeof headers !== "object" || Array.isArray(headers))
|
|
261
|
+
continue;
|
|
262
|
+
const entries = Object.entries(headers);
|
|
263
|
+
const value = entries.find(([k]) => k.toLowerCase() === "retry-after")?.[1];
|
|
264
|
+
if (typeof value !== "string")
|
|
265
|
+
continue;
|
|
266
|
+
if (/^\d+(?:\.\d+)?$/u.test(value.trim()))
|
|
267
|
+
waits.push(Math.ceil(Number(value) * 1_000));
|
|
268
|
+
else {
|
|
269
|
+
const date = Date.parse(value);
|
|
270
|
+
if (Number.isFinite(date))
|
|
271
|
+
waits.push(Math.max(0, date - Date.now()));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const retryAfterMs = Math.max(...waits.filter(Number.isSafeInteger));
|
|
275
|
+
return { ...result, retryable: true,
|
|
276
|
+
...(Number.isFinite(retryAfterMs) ? { retryAfterMs } : {}) };
|
|
277
|
+
}
|
|
278
|
+
/** Add positive evidence without renaming established diagnostics or letting
|
|
279
|
+
* a transient nested cause override a known fatal Session/request failure. */
|
|
280
|
+
function withTransientEvidence(base, input) {
|
|
281
|
+
const evidence = structuredTransientError(input);
|
|
282
|
+
if (evidence === undefined)
|
|
283
|
+
return base;
|
|
284
|
+
const possiblyTransient = ["rate-limit", "availability", "transport", "unknown"].includes(base.category);
|
|
285
|
+
if (evidence.retryable === false)
|
|
286
|
+
return possiblyTransient ? evidence : { ...base, retryable: false };
|
|
287
|
+
if (!possiblyTransient || base.sessionDisposition === "unrecoverable")
|
|
288
|
+
return base;
|
|
289
|
+
return {
|
|
290
|
+
...(base.category === "unknown" ? evidence : base), retryable: true,
|
|
291
|
+
...(evidence.retryAfterMs === undefined ? {} : { retryAfterMs: evidence.retryAfterMs })
|
|
292
|
+
};
|
|
293
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { YUI_VERSION } from "../version.js";
|
|
2
|
+
import { serializeAgentErrorRaw } from "./agentError.js";
|
|
2
3
|
/**
|
|
3
4
|
* Preserve the native non-interactive Codex identity used by our historical
|
|
4
5
|
* execution adapter. A proxy's environment cannot set the daemon's originator;
|
|
@@ -94,6 +95,10 @@ export class CodexAppServerRuntime {
|
|
|
94
95
|
conversationId: input.conversationId,
|
|
95
96
|
text: input.text,
|
|
96
97
|
expectedNoActiveTurn: input.expectedNoActiveTurn,
|
|
98
|
+
...(input.requireQuiescent ? { requireQuiescent: true } : {}),
|
|
99
|
+
...(input.expectedFailedNativeTurnId === undefined ? {} : {
|
|
100
|
+
expectedFailedNativeTurnId: input.expectedFailedNativeTurnId
|
|
101
|
+
}),
|
|
97
102
|
clientUserMessageId: input.attemptId
|
|
98
103
|
});
|
|
99
104
|
}
|
|
@@ -101,11 +106,13 @@ export class CodexAppServerRuntime {
|
|
|
101
106
|
const requestedThreadId = text(input.conversationId, "Codex thread id");
|
|
102
107
|
let threadId = requestedThreadId;
|
|
103
108
|
try {
|
|
109
|
+
const expectedFailedTurn = input.expectedFailedNativeTurnId === undefined ? undefined
|
|
110
|
+
: text(input.expectedFailedNativeTurnId, "Expected failed Codex Turn id");
|
|
104
111
|
// Submission needs current availability, not historical Turns. Some
|
|
105
112
|
// App Server transports expose status without supporting history reads.
|
|
106
113
|
const snapshot = await this.readConversation(requestedThreadId, { includeTurns: false });
|
|
107
114
|
threadId = snapshot.threadId;
|
|
108
|
-
if (input.expectedNoActiveTurn
|
|
115
|
+
if ((input.expectedNoActiveTurn || expectedFailedTurn !== undefined || input.requireQuiescent)
|
|
109
116
|
&& (snapshot.status === "active" || snapshot.activeTurnId !== undefined)) {
|
|
110
117
|
return {
|
|
111
118
|
status: "busy",
|
|
@@ -115,9 +122,30 @@ export class CodexAppServerRuntime {
|
|
|
115
122
|
: `active-turn:${snapshot.activeTurnId}`
|
|
116
123
|
};
|
|
117
124
|
}
|
|
118
|
-
if (snapshot.status !== "idle" && snapshot.status !== "active"
|
|
125
|
+
if (snapshot.status !== "idle" && snapshot.status !== "active"
|
|
126
|
+
&& !(snapshot.status === "systemError" && expectedFailedTurn !== undefined)) {
|
|
119
127
|
throw new Error(`Codex Session availability is ${snapshot.status}; no input was submitted.`);
|
|
120
128
|
}
|
|
129
|
+
if (expectedFailedTurn !== undefined) {
|
|
130
|
+
// systemError alone proves neither terminal execution nor permission
|
|
131
|
+
// to replay. Only the exact latest failed Turn permits this recovery.
|
|
132
|
+
const page = await this.transport.request("thread/turns/list", {
|
|
133
|
+
threadId, limit: 1, sortDirection: "desc", itemsView: "notLoaded"
|
|
134
|
+
});
|
|
135
|
+
const latest = object(arrayMember(page, "data")[0]);
|
|
136
|
+
if (latest?.id !== expectedFailedTurn || latest.status !== "failed") {
|
|
137
|
+
throw new Error("Codex retry has no exact failed latest Turn proof; no input was submitted.");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (expectedFailedTurn !== undefined || input.requireQuiescent) {
|
|
141
|
+
const backgrounds = await this.transport.request("thread/backgroundTerminals/list", {
|
|
142
|
+
threadId, limit: 1
|
|
143
|
+
});
|
|
144
|
+
if (!Array.isArray(backgrounds.data) || backgrounds.data.length !== 0
|
|
145
|
+
|| backgrounds.nextCursor !== null) {
|
|
146
|
+
throw new Error("Codex retry background execution is not proven empty; no input was submitted.");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
121
149
|
}
|
|
122
150
|
catch (error) {
|
|
123
151
|
throw new CodexPreSubmissionError("Codex Session inspection failed before input submission.", { cause: error });
|
|
@@ -304,7 +332,10 @@ function parseThreadSnapshot(result, expectedThreadId, loaded) {
|
|
|
304
332
|
turnId,
|
|
305
333
|
...(status === undefined ? {} : { status }),
|
|
306
334
|
...(output === undefined ? {} : { output }),
|
|
307
|
-
...(error === undefined ? {} : { error })
|
|
335
|
+
...(error === undefined ? {} : { error }),
|
|
336
|
+
...(turn.error === undefined || turn.error === null ? {} : {
|
|
337
|
+
rawError: serializeAgentErrorRaw(turn.error)
|
|
338
|
+
})
|
|
308
339
|
}];
|
|
309
340
|
});
|
|
310
341
|
return {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import { openCurrentTaskStore } from "../storage/currentTaskStore.js";
|
|
3
2
|
import { createProjectResources } from "../resources/projectResourceService.js";
|
|
4
3
|
/** Revalidate the adopted owner; a saved path alone never authorizes execution. */
|
|
5
4
|
export function assertExecutionEnvironmentCurrent(store, taskId, snapshot) {
|
|
@@ -10,21 +9,3 @@ export function assertExecutionEnvironmentCurrent(store, taskId, snapshot) {
|
|
|
10
9
|
throw new Error("Execution environment changed; select an adopted environment and start a new Session.");
|
|
11
10
|
}
|
|
12
11
|
}
|
|
13
|
-
/** Last local check before native process creation or another Provider input. */
|
|
14
|
-
export function assertAgentExecutionEnvironment(home, payload) {
|
|
15
|
-
const snapshot = payload.executionEnvironment;
|
|
16
|
-
if (snapshot === undefined)
|
|
17
|
-
return;
|
|
18
|
-
if (payload.environment.YUI_SESSION_SCOPE !== "task"
|
|
19
|
-
|| payload.environment.YUI_TASK_ID !== snapshot.taskId
|
|
20
|
-
|| payload.cwd !== snapshot.directory.path) {
|
|
21
|
-
throw new Error("Agent launch does not match its adopted execution environment.");
|
|
22
|
-
}
|
|
23
|
-
const store = openCurrentTaskStore(home);
|
|
24
|
-
try {
|
|
25
|
-
assertExecutionEnvironmentCurrent(store, snapshot.taskId, snapshot);
|
|
26
|
-
}
|
|
27
|
-
finally {
|
|
28
|
-
store.close();
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -60,6 +60,12 @@ function validatePayload(payload) {
|
|
|
60
60
|
throw new Error("Agent Host launch payload version is invalid.");
|
|
61
61
|
text(payload.command, "command");
|
|
62
62
|
text(payload.cwd, "cwd");
|
|
63
|
+
if (payload.startupRunId !== undefined) {
|
|
64
|
+
text(payload.startupRunId, "startup Run id");
|
|
65
|
+
if (payload.environment?.YUI_SESSION_SCOPE !== "task") {
|
|
66
|
+
throw new Error("Only a Task launch may carry a startup Run identity.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
63
69
|
if (!Array.isArray(payload.args))
|
|
64
70
|
throw new Error("Agent Host launch args must be an array.");
|
|
65
71
|
payload.args.forEach((value) => text(value, "argument"));
|
|
@@ -14,7 +14,11 @@ export class FencedProviderControl {
|
|
|
14
14
|
conversationId: input.fence.conversationId,
|
|
15
15
|
attemptId: identity(input.attemptId, "Provider input attempt id"),
|
|
16
16
|
text: text(input.text, "Provider Turn input"),
|
|
17
|
-
expectedNoActiveTurn: input.expectedNoActiveTurn ?? true
|
|
17
|
+
expectedNoActiveTurn: input.expectedNoActiveTurn ?? true,
|
|
18
|
+
...(input.requireQuiescent ? { requireQuiescent: true } : {}),
|
|
19
|
+
...(input.expectedFailedNativeTurnId === undefined ? {} : {
|
|
20
|
+
expectedFailedNativeTurnId: identity(input.expectedFailedNativeTurnId, "Expected failed Provider Turn id")
|
|
21
|
+
})
|
|
18
22
|
});
|
|
19
23
|
}
|
|
20
24
|
async interruptTurn(input) {
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
export const PROVIDER_RETRY_LIMIT = 5;
|
|
2
|
+
export const PROVIDER_RETRY_BUDGET_MS = 600_000;
|
|
3
|
+
export function providerRetryPending(binding) {
|
|
4
|
+
return binding?.retry?.status === "waiting" || binding?.retry?.status === "in-flight";
|
|
5
|
+
}
|
|
6
|
+
/** Called only in the transaction recording the exact failed/rejected input. */
|
|
7
|
+
export function recordProviderFailure(binding, input) {
|
|
8
|
+
let turn = binding.run;
|
|
9
|
+
const previous = binding.retry;
|
|
10
|
+
if (turn === null || !["failed", "rejected", "delivery-unknown"].includes(turn.status))
|
|
11
|
+
return binding;
|
|
12
|
+
turn = { ...turn, failure: { error: input.error, ref: input.failureRef } };
|
|
13
|
+
binding = { ...binding, run: turn };
|
|
14
|
+
if (turn.status === "delivery-unknown")
|
|
15
|
+
return binding;
|
|
16
|
+
if (previous?.currentAttemptId === turn.attemptId
|
|
17
|
+
&& ["cancelled", "exhausted", "needs-attention"].includes(previous.status))
|
|
18
|
+
return binding;
|
|
19
|
+
if (previous?.failedAttemptId === turn.attemptId && previous.status !== "in-flight")
|
|
20
|
+
return binding;
|
|
21
|
+
if (input.error.retryable !== true || input.error.inputDisposition === "unknown"
|
|
22
|
+
|| input.error.sessionDisposition !== "recoverable"
|
|
23
|
+
|| binding.authority.owner !== "controller" || binding.retryDisabled === true
|
|
24
|
+
|| turn.status === "failed" && (turn.nativeTurnId === undefined || input.error.inputDisposition !== "accepted")) {
|
|
25
|
+
return providerRetryPending(binding)
|
|
26
|
+
? cancelProviderRetry(binding, "failure-not-safely-retryable", input.at) : binding;
|
|
27
|
+
}
|
|
28
|
+
const continuing = previous?.status === "in-flight" && previous.currentAttemptId === turn.attemptId;
|
|
29
|
+
const originalInput = continuing ? previous.input : turn.input;
|
|
30
|
+
if (originalInput === undefined)
|
|
31
|
+
return binding; // no owned input: never replay arbitrary native chat
|
|
32
|
+
const current = {
|
|
33
|
+
policyVersion: 1,
|
|
34
|
+
chainId: continuing ? previous.chainId : turn.attemptId,
|
|
35
|
+
status: "waiting",
|
|
36
|
+
input: originalInput,
|
|
37
|
+
nativeSessionId: binding.conversations.find(c => c.epoch === binding.currentConversationEpoch).conversationId,
|
|
38
|
+
authorityEpoch: binding.authority.epoch,
|
|
39
|
+
attempts: continuing ? previous.attempts - (input.noProviderWrite && previous.successorAutomatic !== false ? 1 : 0) : 0,
|
|
40
|
+
transportSequence: continuing ? previous.transportSequence : 0,
|
|
41
|
+
startedAt: continuing ? previous.startedAt : iso(input.at),
|
|
42
|
+
intentAt: continuing ? previous.intentAt : turn.submittedAt,
|
|
43
|
+
deadline: continuing ? previous.deadline : iso(input.at + PROVIDER_RETRY_BUDGET_MS),
|
|
44
|
+
nextEligibleAt: iso(input.at),
|
|
45
|
+
updatedAt: iso(input.at),
|
|
46
|
+
failedAttemptId: turn.attemptId,
|
|
47
|
+
failureAttemptId: turn.nativeTurnId !== undefined || !continuing ? turn.attemptId : previous.failureAttemptId,
|
|
48
|
+
...((turn.nativeTurnId ?? (continuing ? previous.failedNativeTurnId : undefined)) === undefined ? {}
|
|
49
|
+
: { failedNativeTurnId: turn.nativeTurnId ?? previous.failedNativeTurnId }),
|
|
50
|
+
...(turn.runId === undefined ? {} : { previousRunId: turn.runId }),
|
|
51
|
+
failureRef: input.failureRef,
|
|
52
|
+
error: input.error
|
|
53
|
+
};
|
|
54
|
+
const random = unitRandom(input.random ?? Math.random);
|
|
55
|
+
const window = Math.min(60_000, 5_000 * 2 ** current.attempts);
|
|
56
|
+
const backoff = Math.ceil(window * (0.5 + random / 2));
|
|
57
|
+
const serverWait = input.error.retryAfterMs;
|
|
58
|
+
const delay = serverWait === undefined ? backoff
|
|
59
|
+
: Math.max(backoff, serverWait + 1 + Math.ceil(random * 1_000));
|
|
60
|
+
const eligible = input.at + delay;
|
|
61
|
+
const exhausted = current.attempts >= PROVIDER_RETRY_LIMIT || eligible >= Date.parse(current.deadline);
|
|
62
|
+
return {
|
|
63
|
+
...binding,
|
|
64
|
+
...(!continuing && previous !== undefined
|
|
65
|
+
? { retryHistory: [...(binding.retryHistory ?? []), previous] } : {}),
|
|
66
|
+
retry: {
|
|
67
|
+
// A terminal chain has no next submission. Keep its bounded deadline
|
|
68
|
+
// representable even for huge hints; the unshortened hint stays in error.
|
|
69
|
+
...current, nextEligibleAt: iso(exhausted ? Math.min(eligible, Date.parse(current.deadline)) : eligible),
|
|
70
|
+
...(turn.retrySupported !== true ? { status: "needs-attention", reason: "host-safe-retry-unsupported" }
|
|
71
|
+
: exhausted ? { status: "exhausted", reason: current.attempts >= PROVIDER_RETRY_LIMIT
|
|
72
|
+
? "automatic-attempt-limit" : "retry-after-exceeds-deadline" } : {})
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Atomically called at the shared native write admission, never by a timer alone. */
|
|
77
|
+
export function admitProviderRetry(binding, input) {
|
|
78
|
+
const retry = binding.retry;
|
|
79
|
+
if (retry?.status === "in-flight" && retry.currentAttemptId === input.attemptId)
|
|
80
|
+
return binding;
|
|
81
|
+
if (retry?.status !== "waiting"
|
|
82
|
+
|| input.at < Date.parse(retry.nextEligibleAt)
|
|
83
|
+
|| retry.successorAutomatic !== false && (binding.retryDisabled === true
|
|
84
|
+
|| input.at >= Date.parse(retry.deadline) || retry.attempts >= PROVIDER_RETRY_LIMIT)
|
|
85
|
+
|| binding.authority.owner !== "controller" || binding.authority.epoch !== retry.authorityEpoch
|
|
86
|
+
|| binding.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId !== retry.nativeSessionId
|
|
87
|
+
|| binding.run?.attemptId !== retry.failedAttemptId
|
|
88
|
+
|| !["failed", "rejected", "deferred"].includes(binding.run.status)) {
|
|
89
|
+
throw new Error("Provider retry is not eligible under the current input, Session, authority or budget.");
|
|
90
|
+
}
|
|
91
|
+
return { ...binding, retry: {
|
|
92
|
+
...retry, status: "in-flight", attempts: retry.attempts + (retry.successorAutomatic === false ? 0 : 1),
|
|
93
|
+
transportSequence: retry.transportSequence + 1,
|
|
94
|
+
currentAttemptId: input.attemptId, updatedAt: iso(input.at)
|
|
95
|
+
} };
|
|
96
|
+
}
|
|
97
|
+
/** Busy/preflight refusal is not a model attempt. Keep its exact receipt visible. */
|
|
98
|
+
export function deferProviderRetry(binding, at, reason) {
|
|
99
|
+
const retry = binding.retry;
|
|
100
|
+
if (retry?.status !== "in-flight" || retry.currentAttemptId !== binding.run?.attemptId)
|
|
101
|
+
return binding;
|
|
102
|
+
const { currentAttemptId: _attempt, successorRunId, ...remaining } = retry;
|
|
103
|
+
return { ...binding, retry: {
|
|
104
|
+
...remaining, attempts: retry.attempts - (retry.successorAutomatic === false ? 0 : 1),
|
|
105
|
+
failedAttemptId: binding.run.attemptId,
|
|
106
|
+
...(reason === undefined && successorRunId !== undefined ? { successorRunId } : {}),
|
|
107
|
+
...(reason !== undefined && binding.run?.runId !== undefined ? { previousRunId: binding.run.runId } : {}),
|
|
108
|
+
status: reason === undefined ? "waiting" : "needs-attention",
|
|
109
|
+
nextEligibleAt: iso(Math.min(at + 1_000, Date.parse(retry.deadline))),
|
|
110
|
+
updatedAt: iso(at), ...(reason === undefined ? {} : { reason })
|
|
111
|
+
} };
|
|
112
|
+
}
|
|
113
|
+
export function cancelProviderRetry(binding, reason, at) {
|
|
114
|
+
return !providerRetryPending(binding) ? binding : {
|
|
115
|
+
...binding, retry: { ...binding.retry, status: "cancelled", reason, updatedAt: iso(at) }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
export function completeProviderRetry(binding, turn) {
|
|
119
|
+
const retry = binding.retry;
|
|
120
|
+
return retry === undefined || !["in-flight", "cancelled", "needs-attention"].includes(retry.status)
|
|
121
|
+
|| retry.currentAttemptId !== turn.attemptId
|
|
122
|
+
|| turn.status !== "completed" ? binding : {
|
|
123
|
+
...binding, retry: { ...retry, status: "recovered", updatedAt: turn.updatedAt }
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function providerRetryAttemptId(retry) {
|
|
127
|
+
return `provider-retry:${retry.chainId}/${retry.transportSequence + 1}`;
|
|
128
|
+
}
|
|
129
|
+
export function providerRetryPrompt(retry, original) {
|
|
130
|
+
if (retry.failedNativeTurnId === undefined)
|
|
131
|
+
return original;
|
|
132
|
+
return [
|
|
133
|
+
"Yui infrastructure recovery, not new user authorization.",
|
|
134
|
+
`Continue the existing input after failed attempt ${retry.failureAttemptId}`,
|
|
135
|
+
`(native Turn ${retry.failedNativeTurnId}; failure ${retry.failureRef}).`,
|
|
136
|
+
"Read current authorized context and the original input. Inspect existing work and receipts first;",
|
|
137
|
+
"continue only unfinished work. Preserve local changes and do not repeat confirmed side effects.",
|
|
138
|
+
original
|
|
139
|
+
].join("\n");
|
|
140
|
+
}
|
|
141
|
+
/** The same presentation is consumed by CLI, Context and Web. */
|
|
142
|
+
export function providerRetryProjection(binding) {
|
|
143
|
+
const retry = binding?.retry;
|
|
144
|
+
if (retry === undefined)
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
chainId: retry.chainId,
|
|
148
|
+
status: retry.status === "in-flight" && binding?.run?.status === "delivery-unknown"
|
|
149
|
+
? "delivery-unknown" : retry.status,
|
|
150
|
+
attempts: retry.status === "recovered" ? 0 : retry.attempts,
|
|
151
|
+
limit: PROVIDER_RETRY_LIMIT, nextEligibleAt: retry.nextEligibleAt, deadline: retry.deadline,
|
|
152
|
+
category: retry.error.category, preservesWork: true, nativeSessionId: retry.nativeSessionId,
|
|
153
|
+
...(retry.reason === undefined ? {} : { reason: retry.reason }),
|
|
154
|
+
failureRef: retry.failureRef,
|
|
155
|
+
...(retry.previousRunId === undefined ? {} : { previousRunId: retry.previousRunId }),
|
|
156
|
+
...(retry.successorRunId === undefined ? {} : { successorRunId: retry.successorRunId })
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
export function controlProviderRetry(binding, action, at) {
|
|
160
|
+
if (action === "enable")
|
|
161
|
+
return { ...binding, retryDisabled: false };
|
|
162
|
+
return { ...cancelProviderRetry(binding, `user-${action}`, at),
|
|
163
|
+
...(action === "disable" ? { retryDisabled: true } : {}) };
|
|
164
|
+
}
|
|
165
|
+
export function renderProviderRetry(binding) {
|
|
166
|
+
const view = providerRetryProjection(binding);
|
|
167
|
+
return view === null ? `Provider retry: ${binding?.retryDisabled ? "disabled" : "none"}`
|
|
168
|
+
: `Provider retry: ${view.category}/${view.status}; ${view.attempts}/${view.limit}; next=${view.nextEligibleAt}; `
|
|
169
|
+
+ `deadline=${view.deadline}; existing work preserved${view.reason === undefined ? "" : `; ${view.reason}`}`;
|
|
170
|
+
}
|
|
171
|
+
export function validateProviderRetry(retry) {
|
|
172
|
+
if (retry.policyVersion !== 1 || !["waiting", "in-flight", "recovered", "cancelled", "exhausted", "needs-attention"].includes(retry.status)
|
|
173
|
+
|| !Number.isSafeInteger(retry.attempts) || retry.attempts < 0 || retry.attempts > PROVIDER_RETRY_LIMIT
|
|
174
|
+
|| !Number.isSafeInteger(retry.transportSequence) || retry.transportSequence < retry.attempts
|
|
175
|
+
|| !Number.isSafeInteger(retry.authorityEpoch) || retry.authorityEpoch < 1
|
|
176
|
+
|| [retry.chainId, retry.nativeSessionId, retry.failedAttemptId, retry.failureRef].some(v => typeof v !== "string" || v.length === 0)
|
|
177
|
+
|| [retry.startedAt, retry.intentAt, retry.deadline, retry.nextEligibleAt, retry.updatedAt].some(v => !Number.isFinite(Date.parse(v)))
|
|
178
|
+
|| Date.parse(retry.deadline) - Date.parse(retry.startedAt) !== PROVIDER_RETRY_BUDGET_MS
|
|
179
|
+
|| retry.status === "in-flight" && !retry.currentAttemptId)
|
|
180
|
+
throw new Error("Invalid Provider retry record.");
|
|
181
|
+
validateProviderInput(retry.input);
|
|
182
|
+
}
|
|
183
|
+
export function validateProviderInput(input) {
|
|
184
|
+
const fields = input.kind === "run" ? [input.taskId, input.runId]
|
|
185
|
+
: input.kind === "wake" ? [input.taskId, input.wakeId]
|
|
186
|
+
: input.kind === "message" ? [input.roleName, input.messageId]
|
|
187
|
+
: input.kind === "text" ? [input.text] : [];
|
|
188
|
+
if (fields.length === 0 || fields.some(v => typeof v !== "string" || !v.trim())) {
|
|
189
|
+
throw new Error("Invalid owned Provider input.");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function iso(at) { return new Date(at).toISOString(); }
|
|
193
|
+
function unitRandom(random) {
|
|
194
|
+
const value = random();
|
|
195
|
+
if (!Number.isFinite(value) || value < 0 || value > 1)
|
|
196
|
+
throw new Error("Retry RNG must return a unit fraction.");
|
|
197
|
+
return value;
|
|
198
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { cancelProviderRetry, completeProviderRetry, validateProviderInput, validateProviderRetry } from "./providerRetry.js";
|
|
2
|
+
import { isStandardAgentError } from "./agentError.js";
|
|
1
3
|
export function createProviderRuntimeBinding(input) {
|
|
2
4
|
const startedAt = timestamp(input.startedAt, "Provider Conversation startedAt");
|
|
3
5
|
return validateProviderRuntimeBinding({
|
|
@@ -84,7 +86,7 @@ export function transferProviderAuthority(raw, input) {
|
|
|
84
86
|
});
|
|
85
87
|
}
|
|
86
88
|
export function beginProviderTurn(raw, input) {
|
|
87
|
-
|
|
89
|
+
let binding = validateProviderRuntimeBinding(raw);
|
|
88
90
|
const attemptId = identity(input.attemptId, "Provider input attempt id");
|
|
89
91
|
const runId = input.runId === undefined ? undefined : identity(input.runId, "AgentRun id");
|
|
90
92
|
const currentRun = binding.run;
|
|
@@ -104,6 +106,9 @@ export function beginProviderTurn(raw, input) {
|
|
|
104
106
|
throw new Error("Provider Conversation already has an unsettled AgentRun.");
|
|
105
107
|
}
|
|
106
108
|
const submittedAt = timestamp(input.submittedAt, "Provider Turn submittedAt");
|
|
109
|
+
if (binding.retry?.currentAttemptId !== attemptId) {
|
|
110
|
+
binding = cancelProviderRetry(binding, "superseded-by-explicit-input", Date.parse(submittedAt));
|
|
111
|
+
}
|
|
107
112
|
return validateProviderRuntimeBinding({
|
|
108
113
|
...binding,
|
|
109
114
|
run: {
|
|
@@ -111,6 +116,8 @@ export function beginProviderTurn(raw, input) {
|
|
|
111
116
|
attemptId,
|
|
112
117
|
authorityEpoch: input.authorityEpoch,
|
|
113
118
|
status: "submitting",
|
|
119
|
+
...(input.input === undefined ? {} : { input: input.input }),
|
|
120
|
+
...(input.retrySupported === undefined ? {} : { retrySupported: input.retrySupported }),
|
|
114
121
|
submittedAt,
|
|
115
122
|
updatedAt: submittedAt
|
|
116
123
|
}
|
|
@@ -221,7 +228,7 @@ export function settleProviderTurn(raw, input) {
|
|
|
221
228
|
throw new Error("Provider Turn settlement does not match the current AgentRun.");
|
|
222
229
|
}
|
|
223
230
|
const settledAt = orderedRunTimestamp(run, input.settledAt, "Provider Turn settledAt");
|
|
224
|
-
|
|
231
|
+
const settled = validateProviderRuntimeBinding({
|
|
225
232
|
...binding,
|
|
226
233
|
run: {
|
|
227
234
|
...run,
|
|
@@ -233,6 +240,7 @@ export function settleProviderTurn(raw, input) {
|
|
|
233
240
|
: { terminalReason: identity(input.reason, "Provider Turn terminal reason") })
|
|
234
241
|
}
|
|
235
242
|
});
|
|
243
|
+
return completeProviderRetry(settled, settled.run);
|
|
236
244
|
}
|
|
237
245
|
/** Explicitly abandon an engineering input only after its execution resources
|
|
238
246
|
* are quiescent. This does not invent native acceptance, a Turn id or success. */
|
|
@@ -367,6 +375,24 @@ export function validateProviderRuntimeBinding(value) {
|
|
|
367
375
|
throw new Error("Provider Runtime Binding requires Goal state.");
|
|
368
376
|
if (value.goal !== null)
|
|
369
377
|
validateProviderGoal(value.goal);
|
|
378
|
+
if (value.retry !== undefined)
|
|
379
|
+
validateProviderRetry(value.retry);
|
|
380
|
+
if (value.retryHistory !== undefined) {
|
|
381
|
+
if (!Array.isArray(value.retryHistory))
|
|
382
|
+
throw new Error("Provider retry audit must be an array.");
|
|
383
|
+
for (const retry of value.retryHistory) {
|
|
384
|
+
validateProviderRetry(retry);
|
|
385
|
+
if (retry.status === "waiting" || retry.status === "in-flight")
|
|
386
|
+
throw new Error("Provider retry audit cannot schedule work.");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (value.retryDisabled !== undefined && typeof value.retryDisabled !== "boolean")
|
|
390
|
+
throw new Error("Provider retry switch must be boolean.");
|
|
391
|
+
if (value.run?.input !== undefined)
|
|
392
|
+
validateProviderInput(value.run.input);
|
|
393
|
+
if (value.run?.failure !== undefined
|
|
394
|
+
&& (!isStandardAgentError(value.run.failure.error) || !value.run.failure.ref))
|
|
395
|
+
throw new Error("Invalid Provider input failure.");
|
|
370
396
|
return value;
|
|
371
397
|
}
|
|
372
398
|
function validateProviderGoal(goal) {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* live Session can always be re-attributed and reported.
|
|
9
9
|
*
|
|
10
10
|
* Pure: all I/O is injected. Unknown owners are reported, never cleaned.
|
|
11
|
-
* An entry
|
|
12
|
-
*
|
|
11
|
+
* An entry blocks ordinary archive / workspace cleanup when a terminal Task
|
|
12
|
+
* still owns a live or unverified physical root, regardless of Session status.
|
|
13
13
|
*/
|
|
14
14
|
export function reconcileSessionOwners(input) {
|
|
15
15
|
const entries = input.records.map((record) => reconcileOne(record, input));
|
|
@@ -54,8 +54,8 @@ function reconcileOne(record, input) {
|
|
|
54
54
|
const terminalTask = taskStatus === "completed"
|
|
55
55
|
|| taskStatus === "cancelled"
|
|
56
56
|
|| taskStatus === "archived";
|
|
57
|
-
const archiveBlocked =
|
|
58
|
-
|
|
57
|
+
const archiveBlocked = terminalTask && (physical === undefined
|
|
58
|
+
|| physical.alive || physical.identityConflict);
|
|
59
59
|
return {
|
|
60
60
|
owner: {
|
|
61
61
|
scope: owner.scope,
|
|
@@ -26,11 +26,21 @@ export function resolveSessionTokenIdentity(input) {
|
|
|
26
26
|
export function projectSessionTokenMetrics(events, identity) {
|
|
27
27
|
if (identity === null)
|
|
28
28
|
return unobservedSessionTokenMetrics();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
return projectSessionTokenObservations(events.flatMap((event) => {
|
|
30
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
31
|
+
return observation === null ? [] : [observation];
|
|
32
|
+
}), identity);
|
|
33
|
+
}
|
|
34
|
+
/** Shared reducer for authorized, already decoded canonical observations. */
|
|
35
|
+
export function projectSessionTokenObservations(input, identity) {
|
|
36
|
+
const observations = input
|
|
37
|
+
.filter((observation) => (isRuntimeTokenEvidence(observation)
|
|
38
|
+
&& matchesSessionIdentity(observation, identity)
|
|
39
|
+
&& (observation.authority === "provider-structured" || observation.authority === "driver-inferred")
|
|
40
|
+
// Child counters have no declared inclusion relationship with the
|
|
41
|
+
// Session's parent counter. They must never be added as extra requests.
|
|
42
|
+
&& observation.fence.continuationId === undefined
|
|
43
|
+
&& observation.fence.parentContinuationId === undefined))
|
|
34
44
|
.sort(compareObservations);
|
|
35
45
|
const incompleteBoundaries = observations.filter(({ payload }) => (payload.usage === undefined));
|
|
36
46
|
const request = observations.filter(({ payload }) => (payload.usage?.semantics === "request-context"));
|
|
@@ -462,7 +462,7 @@ class CodexStructuredProviderSession {
|
|
|
462
462
|
? {}
|
|
463
463
|
: {
|
|
464
464
|
error: recovered.error ?? "Provider Turn failed.",
|
|
465
|
-
rawError:
|
|
465
|
+
...(recovered.rawError === undefined ? {} : { rawError: recovered.rawError })
|
|
466
466
|
})
|
|
467
467
|
};
|
|
468
468
|
session.#clientOwnedTurnId = undefined;
|
|
@@ -594,7 +594,11 @@ class CodexStructuredProviderSession {
|
|
|
594
594
|
conversationId: this.conversationId,
|
|
595
595
|
attemptId: turn.attemptId,
|
|
596
596
|
text: turn.boundedText,
|
|
597
|
-
expectedNoActiveTurn: true
|
|
597
|
+
expectedNoActiveTurn: true,
|
|
598
|
+
...(turn.requireQuiescent ? { requireQuiescent: true } : {}),
|
|
599
|
+
...(turn.expectedFailedNativeTurnId === undefined ? {} : {
|
|
600
|
+
expectedFailedNativeTurnId: turn.expectedFailedNativeTurnId
|
|
601
|
+
})
|
|
598
602
|
});
|
|
599
603
|
if (acceptance.status === "unknown") {
|
|
600
604
|
throw new ProviderDeliveryUnknownError(acceptance.reason, turn.attemptId);
|