@zq-silk/yui 0.11.3 → 0.12.1
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 +23 -4
- package/README.md +44 -22
- package/dist/cli/commandCatalog.js +13 -10
- package/dist/cli/interactionPolicy.js +5 -6
- package/dist/cli/updateCommand.js +3 -1
- package/dist/cli/updateOrchestrator.js +173 -28
- package/dist/cli/updatePorts.js +137 -8
- package/dist/cli/upgradeCommand.js +19 -9
- package/dist/cli.js +156 -57
- package/dist/commands/configCommands.js +13 -46
- package/dist/commands/executionAuditCommands.js +3 -2
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +234 -200
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +61 -13
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +55 -7
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +3 -3
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +39 -3
- package/dist/controller/controller.js +31 -27
- package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
- package/dist/controller/runtime.js +58 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeHookRunFence.js +19 -4
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/controller/structuredProviderObservation.js +20 -3
- package/dist/core/controllerClient.js +20 -2
- package/dist/core/controllerServer.js +1 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +52 -50
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +129 -45
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +69 -5
- package/dist/observability/executionAudit.js +5 -0
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/release/runtimeRelease.js +20 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/run/recoveryProjection.js +45 -6
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/agentHost.js +159 -85
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/conversationSwitch.js +277 -0
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/launchBroker.js +12 -0
- package/dist/runtime/processExitOutbox.js +88 -0
- package/dist/runtime/providerRuntimeIdentity.js +29 -1
- package/dist/runtime/runtimeHealthPolicy.js +5 -5
- package/dist/runtime/runtimeObservation.js +28 -0
- package/dist/runtime/runtimeProjection.js +22 -32
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/runtime/tmuxAdapters.js +4 -1
- package/dist/scheduler/activeRoleRunDelivery.js +34 -199
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +42 -138
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +53 -31
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +66 -23
- package/dist/storage/sqliteStore.js +46 -23
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/homeClassification.js +52 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -58
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +7 -1
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +60 -20
- package/skills/yui-operator/SKILL.md +13 -4
- package/skills/yui-reviewer/SKILL.md +35 -11
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/lifecycle/taskRoleSessionReset.js +0 -126
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { builtinDriverIdForAdapter } from "./builtinAgentDrivers.js";
|
|
2
|
+
import { isRuntimeTokenEvidence, runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
|
|
3
|
+
const UNOBSERVED = Object.freeze({ status: "unobserved" });
|
|
4
|
+
export function resolveSessionTokenIdentity(input) {
|
|
5
|
+
if (input?.taskId === undefined
|
|
6
|
+
|| input.roleName === undefined
|
|
7
|
+
|| input.agentId === undefined
|
|
8
|
+
|| input.adapterId === undefined
|
|
9
|
+
|| input.launchId === undefined
|
|
10
|
+
|| input.nativeSessionId === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
let driverId;
|
|
13
|
+
try {
|
|
14
|
+
driverId = builtinDriverIdForAdapter(input.adapterId);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
taskId: input.taskId,
|
|
21
|
+
roleName: input.roleName,
|
|
22
|
+
agentId: input.agentId,
|
|
23
|
+
driverId,
|
|
24
|
+
launchId: input.launchId,
|
|
25
|
+
nativeSessionId: input.nativeSessionId,
|
|
26
|
+
sessionGenerationId: input.launchId
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read-only projection over canonical runtime observations. Token values never
|
|
31
|
+
* decide lifecycle, health, scheduling, resource admission, or workflow state.
|
|
32
|
+
*/
|
|
33
|
+
export function projectSessionTokenMetrics(events, identity) {
|
|
34
|
+
if (identity === null)
|
|
35
|
+
return unobservedSessionTokenMetrics();
|
|
36
|
+
const observations = events
|
|
37
|
+
.map(runtimeObservationFromTaskEvent)
|
|
38
|
+
.filter((observation) => (observation !== null
|
|
39
|
+
&& isRuntimeTokenEvidence(observation)
|
|
40
|
+
&& matchesSessionGeneration(observation, identity)))
|
|
41
|
+
.sort(compareObservations);
|
|
42
|
+
const incompleteBoundaries = observations.filter(({ payload }) => (payload.usage === undefined));
|
|
43
|
+
const request = observations.filter(({ payload }) => (payload.usage?.semantics === "request-context"));
|
|
44
|
+
const cumulative = observations.filter(({ payload }) => (payload.usage?.semantics === "cumulative-session"));
|
|
45
|
+
// A generation cannot switch counter semantics without an explicit fact
|
|
46
|
+
// explaining how the two streams overlap. Remaining-context is capacity,
|
|
47
|
+
// not consumption, and is intentionally absent from both metrics.
|
|
48
|
+
if (request.length > 0 && cumulative.length > 0) {
|
|
49
|
+
return unobservedSessionTokenMetrics(identity);
|
|
50
|
+
}
|
|
51
|
+
if (request.length > 0) {
|
|
52
|
+
return projectRequestSnapshots(identity, request, incompleteBoundaries.length > 0);
|
|
53
|
+
}
|
|
54
|
+
if (cumulative.length > 0) {
|
|
55
|
+
return projectCumulativeSnapshots(identity, cumulative, incompleteBoundaries.length > 0);
|
|
56
|
+
}
|
|
57
|
+
return unobservedSessionTokenMetrics(identity);
|
|
58
|
+
}
|
|
59
|
+
export function unobservedSessionTokenMetrics(identity = null) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
identity: identity === null ? null : Object.freeze({ ...identity }),
|
|
62
|
+
cumulativeTotal: UNOBSERVED,
|
|
63
|
+
maximumRequestInput: UNOBSERVED
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function projectRequestSnapshots(identity, observations, hasIncompleteBoundary) {
|
|
67
|
+
if (hasIncompleteBoundary || observations.some(({ payload }) => (payload.observationQuality === "partial"
|
|
68
|
+
|| payload.observationQuality === "unavailable")))
|
|
69
|
+
return unobservedSessionTokenMetrics(identity);
|
|
70
|
+
const requests = new Map();
|
|
71
|
+
for (const observation of observations) {
|
|
72
|
+
const usage = observation.payload.usage;
|
|
73
|
+
const requestId = observation.payload.activityId ?? observation.semanticKey;
|
|
74
|
+
// Structured providers may publish multiple snapshots for one stable
|
|
75
|
+
// request identity. The latest snapshot supersedes the earlier delivery;
|
|
76
|
+
// replays with the same identity never become a second request.
|
|
77
|
+
requests.set(requestId, usage);
|
|
78
|
+
}
|
|
79
|
+
const values = [...requests.values()];
|
|
80
|
+
const inputTokens = safeSum(values.map((usage) => usage.inputTokens));
|
|
81
|
+
const outputTokens = safeSum(values.map((usage) => usage.outputTokens));
|
|
82
|
+
if (inputTokens === null || outputTokens === null) {
|
|
83
|
+
return unobservedSessionTokenMetrics(identity);
|
|
84
|
+
}
|
|
85
|
+
const totalTokens = safeAdd(inputTokens, outputTokens);
|
|
86
|
+
if (totalTokens === null)
|
|
87
|
+
return unobservedSessionTokenMetrics(identity);
|
|
88
|
+
const cachedInputTokens = values.every((usage) => usage.cachedInputTokens !== undefined)
|
|
89
|
+
? safeSum(values.map((usage) => usage.cachedInputTokens))
|
|
90
|
+
: null;
|
|
91
|
+
const reasoningTokens = values.every((usage) => usage.reasoningTokens !== undefined)
|
|
92
|
+
? safeSum(values.map((usage) => usage.reasoningTokens))
|
|
93
|
+
: null;
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
identity: Object.freeze({ ...identity }),
|
|
96
|
+
cumulativeTotal: Object.freeze({
|
|
97
|
+
status: "observed",
|
|
98
|
+
totalTokens,
|
|
99
|
+
inputTokens,
|
|
100
|
+
outputTokens,
|
|
101
|
+
...(cachedInputTokens === null ? {} : { cachedInputTokens }),
|
|
102
|
+
...(reasoningTokens === null ? {} : { reasoningTokens })
|
|
103
|
+
}),
|
|
104
|
+
maximumRequestInput: Object.freeze({
|
|
105
|
+
status: "observed",
|
|
106
|
+
inputTokens: Math.max(...values.map((usage) => usage.inputTokens))
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function projectCumulativeSnapshots(identity, observations, hasIncompleteBoundary) {
|
|
111
|
+
const values = observations.map(({ payload }) => payload.usage);
|
|
112
|
+
const requestBoundaryIncomplete = hasIncompleteBoundary || observations.some(({ payload }) => (payload.observationQuality === "partial"
|
|
113
|
+
|| payload.observationQuality === "unavailable"));
|
|
114
|
+
let counterRollback = false;
|
|
115
|
+
let maximumRequestInput = null;
|
|
116
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
117
|
+
const previous = values[index - 1];
|
|
118
|
+
const current = values[index];
|
|
119
|
+
if (current.inputTokens < previous.inputTokens
|
|
120
|
+
|| current.outputTokens < previous.outputTokens)
|
|
121
|
+
counterRollback = true;
|
|
122
|
+
const requestInput = Math.max(0, current.inputTokens - previous.inputTokens);
|
|
123
|
+
maximumRequestInput = maximumRequestInput === null
|
|
124
|
+
? requestInput
|
|
125
|
+
: Math.max(maximumRequestInput, requestInput);
|
|
126
|
+
}
|
|
127
|
+
const latest = values.at(-1);
|
|
128
|
+
const totalTokens = safeAdd(latest.inputTokens, latest.outputTokens);
|
|
129
|
+
const cumulativeTotal = counterRollback || totalTokens === null
|
|
130
|
+
? UNOBSERVED
|
|
131
|
+
: Object.freeze({
|
|
132
|
+
status: "observed",
|
|
133
|
+
totalTokens,
|
|
134
|
+
inputTokens: latest.inputTokens,
|
|
135
|
+
outputTokens: latest.outputTokens,
|
|
136
|
+
...(latest.cachedInputTokens === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: { cachedInputTokens: latest.cachedInputTokens }),
|
|
139
|
+
...(latest.reasoningTokens === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { reasoningTokens: latest.reasoningTokens })
|
|
142
|
+
});
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
identity: Object.freeze({ ...identity }),
|
|
145
|
+
cumulativeTotal,
|
|
146
|
+
maximumRequestInput: counterRollback
|
|
147
|
+
|| requestBoundaryIncomplete
|
|
148
|
+
|| maximumRequestInput === null
|
|
149
|
+
? UNOBSERVED
|
|
150
|
+
: Object.freeze({ status: "observed", inputTokens: maximumRequestInput })
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function matchesSessionGeneration(observation, identity) {
|
|
154
|
+
const fence = observation.fence;
|
|
155
|
+
return fence.taskId === identity.taskId
|
|
156
|
+
&& fence.roleName === identity.roleName
|
|
157
|
+
&& fence.agentId === identity.agentId
|
|
158
|
+
&& fence.driverId === identity.driverId
|
|
159
|
+
&& fence.launchId === identity.launchId
|
|
160
|
+
&& fence.nativeSessionId === identity.nativeSessionId
|
|
161
|
+
&& fence.sessionGenerationId === identity.sessionGenerationId;
|
|
162
|
+
}
|
|
163
|
+
function compareObservations(left, right) {
|
|
164
|
+
return left.receivedAt.localeCompare(right.receivedAt)
|
|
165
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
166
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
167
|
+
|| left.eventId.localeCompare(right.eventId);
|
|
168
|
+
}
|
|
169
|
+
function safeSum(values) {
|
|
170
|
+
let total = 0;
|
|
171
|
+
for (const value of values) {
|
|
172
|
+
total = safeAdd(total, value) ?? Number.NaN;
|
|
173
|
+
if (!Number.isSafeInteger(total))
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return total;
|
|
177
|
+
}
|
|
178
|
+
function safeAdd(left, right) {
|
|
179
|
+
const total = left + right;
|
|
180
|
+
return Number.isSafeInteger(total) && total >= 0 ? total : null;
|
|
181
|
+
}
|
|
@@ -391,7 +391,13 @@ class ClaudeStructuredProviderSession {
|
|
|
391
391
|
waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input for a different native Session.", waiter.attemptId));
|
|
392
392
|
return;
|
|
393
393
|
}
|
|
394
|
-
const nativeTurnId =
|
|
394
|
+
const nativeTurnId = optionalId(message.uuid);
|
|
395
|
+
if (nativeTurnId === undefined) {
|
|
396
|
+
clearTimeout(waiter.timer);
|
|
397
|
+
this.#waiter = undefined;
|
|
398
|
+
waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input without a native message identity.", waiter.attemptId));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
395
401
|
this.#activeTurnId = nativeTurnId;
|
|
396
402
|
clearTimeout(waiter.timer);
|
|
397
403
|
this.#waiter = undefined;
|
|
@@ -408,7 +408,10 @@ export class TmuxSessionHost {
|
|
|
408
408
|
throw error;
|
|
409
409
|
}
|
|
410
410
|
if (providerDispatchObserved
|
|
411
|
-
|
|
411
|
+
// A fresh Host always starts a Provider process. mode=new also starts
|
|
412
|
+
// one inside a reused Host by replacing (or creating) the native
|
|
413
|
+
// Conversation. Same-Conversation resume must retain its inherited key.
|
|
414
|
+
&& (hostCreated || request.mode === "new")
|
|
412
415
|
&& request.owner.scope === "task"
|
|
413
416
|
&& planned.launch.env.YUI_JOB_CALLER_KEY !== undefined
|
|
414
417
|
&& this.planner.commitTaskCallerKey !== undefined) {
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
|
|
2
2
|
import { isSchedulerTaskWorkspaceReady } from "./ports.js";
|
|
3
3
|
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
4
|
-
import { prefixYuiTitleInput } from "../run/runIdentity.js";
|
|
5
|
-
import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
6
4
|
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
7
|
-
import { effectiveLaunchSnapshotsCompatible,
|
|
5
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
|
|
8
6
|
import { RuntimeLaunchError } from "../runtime/ports.js";
|
|
9
7
|
import { RuntimeLaunchFailure } from "../runtime/launchDiagnostics.js";
|
|
10
|
-
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
11
8
|
import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
|
|
12
9
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
13
10
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
14
11
|
import { hasRuntimeLifecycleWork, RuntimeLifecycleBusyError, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
15
|
-
import { serializeRunBootstrapEnvelope } from "../context/runContextContract.js";
|
|
16
|
-
import { serializeProviderRetryEnvelope } from "../run/providerRetry.js";
|
|
17
|
-
import { serializeWorkflowOutcomeRequestEnvelope } from "../run/runControlRequest.js";
|
|
18
12
|
/**
|
|
19
13
|
* Delivers durable Work AgentRuns before liveness reconciliation. Task command
|
|
20
14
|
* handlers only record intent; this Controller path is the sole automated
|
|
@@ -109,9 +103,14 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
|
|
|
109
103
|
let preparedSession = existingSession;
|
|
110
104
|
let preStartFencePersisted = false;
|
|
111
105
|
let deliveryAttempted = false;
|
|
112
|
-
let providerSubmissionBegun = false;
|
|
113
106
|
try {
|
|
114
|
-
|
|
107
|
+
// Workflow-outcome control is always another Turn on the already-bound
|
|
108
|
+
// Conversation. Provider retry alone may carry an exact missing-
|
|
109
|
+
// Conversation decision and therefore retains its explicit new mode.
|
|
110
|
+
const launchMode = run.controlRequest?.state === "dispatching"
|
|
111
|
+
? "resume"
|
|
112
|
+
: run.mode;
|
|
113
|
+
const nativeSessionId = launchMode === "resume"
|
|
115
114
|
? requireResumeSession(role, run.effective, existingSession)
|
|
116
115
|
: undefined;
|
|
117
116
|
prepared = await delivery.prepareRoleSession({
|
|
@@ -124,12 +123,12 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
|
|
|
124
123
|
...(run.workspace === undefined
|
|
125
124
|
? {}
|
|
126
125
|
: { managedWorkspace: run.workspace }),
|
|
127
|
-
mode:
|
|
126
|
+
mode: launchMode,
|
|
128
127
|
runId: run.id,
|
|
129
128
|
...(nativeSessionId === undefined ? {} : { nativeSessionId }),
|
|
130
129
|
beforeHostStart: (preflight) => {
|
|
131
130
|
persistPreStartFence(store, task, role, run, existingSession, preflight, now);
|
|
132
|
-
preparedSession = preflightSession(role, run.effective, existingSession,
|
|
131
|
+
preparedSession = preflightSession(role, run.effective, existingSession, launchMode, preflight);
|
|
133
132
|
preStartFencePersisted = true;
|
|
134
133
|
}
|
|
135
134
|
});
|
|
@@ -177,20 +176,32 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
|
|
|
177
176
|
now
|
|
178
177
|
});
|
|
179
178
|
if (ready.prepared.turnRejectedDuringLaunch === true) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
? {}
|
|
186
|
-
: { launchId: ready.prepared.launchId })
|
|
179
|
+
const persisted = store.saveRoleRunDeliveryFailure({
|
|
180
|
+
...roleRunDeliveryFailure(run, processing.batchId, session, ready.prepared.launchId),
|
|
181
|
+
summary: `Provider conclusively rejected the exact initial Run input: ${run.id}.`,
|
|
182
|
+
cleanupRequired: false,
|
|
183
|
+
now
|
|
187
184
|
});
|
|
185
|
+
if (persisted === "failed") {
|
|
186
|
+
delivery.forgetPrepared?.({
|
|
187
|
+
taskId: task.id,
|
|
188
|
+
roleName: role.name,
|
|
189
|
+
runId: run.id,
|
|
190
|
+
...(ready.prepared.launchId === undefined
|
|
191
|
+
? {}
|
|
192
|
+
: { launchId: ready.prepared.launchId })
|
|
193
|
+
});
|
|
194
|
+
}
|
|
188
195
|
results.push({
|
|
189
196
|
taskId: task.id,
|
|
190
197
|
roleName: role.name,
|
|
191
198
|
runId: run.id,
|
|
192
|
-
status: "skipped",
|
|
193
|
-
reason: "
|
|
199
|
+
status: persisted === "failed" ? "failed" : "skipped",
|
|
200
|
+
reason: "provider-rejected",
|
|
201
|
+
terminalized: persisted === "failed",
|
|
202
|
+
...(persisted === "failed"
|
|
203
|
+
? {}
|
|
204
|
+
: { error: "Run state changed while recording exact Provider rejection." })
|
|
194
205
|
});
|
|
195
206
|
continue;
|
|
196
207
|
}
|
|
@@ -252,171 +263,10 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
|
|
|
252
263
|
});
|
|
253
264
|
continue;
|
|
254
265
|
}
|
|
255
|
-
|
|
256
|
-
// native event fires before the first prompt (e.g. Claude SessionStart),
|
|
257
|
-
// a freshly-started host must not be pushed until that provider-ready
|
|
258
|
-
// fact has been folded. Unsupported adapters (e.g. Codex) have no
|
|
259
|
-
// pre-input event, so their push proceeds and acceptance is confirmed
|
|
260
|
-
// only by the later exact provider-accepted fold. The gate reads the
|
|
261
|
-
// adapter capability and the durable ready projection — never a sleep,
|
|
262
|
-
// screen scrape, or pane/PID inference — and fails closed for a
|
|
263
|
-
// supported adapter whose readiness cannot be confirmed.
|
|
264
|
-
if (ready.prepared.sessionStarted
|
|
265
|
-
&& builtinAgentDriverRegistry().requireByAdapterId(run.effective.adapterId)
|
|
266
|
-
.capabilities.observation.preInputReadiness === "exact"
|
|
267
|
-
&& !providerReadyForPush(store, {
|
|
268
|
-
taskId: task.id,
|
|
269
|
-
roleName: role.name,
|
|
270
|
-
agentId: run.effective.agentId,
|
|
271
|
-
...(ready.prepared.launchId === undefined ? {} : { launchId: ready.prepared.launchId }),
|
|
272
|
-
...(session?.nativeSessionId === undefined
|
|
273
|
-
? {}
|
|
274
|
-
: { nativeSessionId: session.nativeSessionId })
|
|
275
|
-
})) {
|
|
276
|
-
results.push({
|
|
277
|
-
taskId: task.id,
|
|
278
|
-
roleName: role.name,
|
|
279
|
-
runId: run.id,
|
|
280
|
-
status: "skipped",
|
|
281
|
-
reason: "not-ready"
|
|
282
|
-
});
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
if (session === null || session.launchId === undefined
|
|
286
|
-
|| !hasText(session.nativeSessionId)
|
|
287
|
-
|| store.beginRoleRunProviderTurn?.({
|
|
288
|
-
taskId: task.id,
|
|
289
|
-
roleName: role.name,
|
|
290
|
-
runId: run.id,
|
|
291
|
-
agentId: run.effective.agentId,
|
|
292
|
-
launchId: session.launchId,
|
|
293
|
-
nativeSessionId: session.nativeSessionId,
|
|
294
|
-
attemptId: receiptId,
|
|
295
|
-
now
|
|
296
|
-
}) !== true) {
|
|
297
|
-
throw new Error("Provider Turn intent could not be durably fenced before delivery.");
|
|
298
|
-
}
|
|
299
|
-
providerSubmissionBegun = true;
|
|
300
|
-
deliveryAttempted = true;
|
|
301
|
-
const launchText = run.controlRequest?.state === "dispatching"
|
|
302
|
-
? serializeWorkflowOutcomeRequestEnvelope({
|
|
303
|
-
taskId: task.id,
|
|
304
|
-
runId: run.id,
|
|
305
|
-
roleName: role.name,
|
|
306
|
-
request: run.controlRequest
|
|
307
|
-
})
|
|
308
|
-
: run.providerRetry?.state === "dispatching"
|
|
309
|
-
? serializeProviderRetryEnvelope({
|
|
310
|
-
taskId: task.id,
|
|
311
|
-
runId: run.id,
|
|
312
|
-
roleName: role.name,
|
|
313
|
-
retry: run.providerRetry
|
|
314
|
-
})
|
|
315
|
-
: serializeRunBootstrapEnvelope(run.bootstrapEnvelope);
|
|
316
|
-
const outcome = await delivery.sendOnce({
|
|
317
|
-
delivery: ready,
|
|
318
|
-
receiptId,
|
|
319
|
-
text: prefixYuiTitleInput(launchText, resolveTaskRoleSessionTitle(session.title, task, role.name))
|
|
320
|
-
});
|
|
321
|
-
if (outcome === "busy" || outcome === "unavailable") {
|
|
322
|
-
store.resolveRoleRunProviderSubmission?.({
|
|
323
|
-
taskId: task.id,
|
|
324
|
-
roleName: role.name,
|
|
325
|
-
runId: run.id,
|
|
326
|
-
attemptId: receiptId,
|
|
327
|
-
status: "rejected",
|
|
328
|
-
reason: outcome === "busy"
|
|
329
|
-
? "Agent Host was busy before Provider mutation."
|
|
330
|
-
: "Agent Host was unavailable before Provider mutation.",
|
|
331
|
-
now
|
|
332
|
-
});
|
|
333
|
-
providerSubmissionBegun = false;
|
|
334
|
-
results.push({
|
|
335
|
-
taskId: task.id,
|
|
336
|
-
roleName: role.name,
|
|
337
|
-
runId: run.id,
|
|
338
|
-
status: "skipped",
|
|
339
|
-
reason: outcome === "busy" ? "not-ready" : "runtime-unavailable",
|
|
340
|
-
terminalFailure: roleRunDeliveryFailure(run, processing.batchId, session, ready.prepared.launchId)
|
|
341
|
-
});
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
if (outcome === "rejected") {
|
|
345
|
-
store.resolveRoleRunProviderSubmission?.({
|
|
346
|
-
taskId: task.id,
|
|
347
|
-
roleName: role.name,
|
|
348
|
-
runId: run.id,
|
|
349
|
-
attemptId: receiptId,
|
|
350
|
-
status: "rejected",
|
|
351
|
-
reason: "Provider returned an exact negative acknowledgement.",
|
|
352
|
-
now
|
|
353
|
-
});
|
|
354
|
-
providerSubmissionBegun = false;
|
|
355
|
-
results.push({
|
|
356
|
-
taskId: task.id,
|
|
357
|
-
roleName: role.name,
|
|
358
|
-
runId: run.id,
|
|
359
|
-
status: "skipped",
|
|
360
|
-
reason: "not-ready"
|
|
361
|
-
});
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if (outcome === "delivery-unknown") {
|
|
365
|
-
store.resolveRoleRunProviderSubmission?.({
|
|
366
|
-
taskId: task.id,
|
|
367
|
-
roleName: role.name,
|
|
368
|
-
runId: run.id,
|
|
369
|
-
attemptId: receiptId,
|
|
370
|
-
status: "delivery-unknown",
|
|
371
|
-
reason: "Provider Turn delivery is ambiguous; automatic retry is fenced.",
|
|
372
|
-
now
|
|
373
|
-
});
|
|
374
|
-
providerSubmissionBegun = false;
|
|
375
|
-
store.saveRoleRunDelivery({
|
|
376
|
-
task,
|
|
377
|
-
role,
|
|
378
|
-
run,
|
|
379
|
-
session,
|
|
380
|
-
...(ready.prepared.launchId === undefined
|
|
381
|
-
? {}
|
|
382
|
-
: { launchId: ready.prepared.launchId }),
|
|
383
|
-
now
|
|
384
|
-
});
|
|
385
|
-
results.push({
|
|
386
|
-
taskId: task.id,
|
|
387
|
-
roleName: role.name,
|
|
388
|
-
runId: run.id,
|
|
389
|
-
status: "skipped",
|
|
390
|
-
reason: "delivery-uncertain"
|
|
391
|
-
});
|
|
392
|
-
continue;
|
|
393
|
-
}
|
|
394
|
-
const status = outcome === "sent" ? "delivered" : "already-delivered";
|
|
395
|
-
store.saveRoleRunDelivery({
|
|
396
|
-
task,
|
|
397
|
-
role,
|
|
398
|
-
run,
|
|
399
|
-
session,
|
|
400
|
-
...(ready.prepared.launchId === undefined
|
|
401
|
-
? {}
|
|
402
|
-
: { launchId: ready.prepared.launchId }),
|
|
403
|
-
now
|
|
404
|
-
});
|
|
405
|
-
results.push({ taskId: task.id, roleName: role.name, runId: run.id, status });
|
|
266
|
+
throw new Error("Managed Provider launch completed without an initial Turn outcome.");
|
|
406
267
|
}
|
|
407
268
|
catch (error) {
|
|
408
269
|
const message = error instanceof Error ? error.message : String(error);
|
|
409
|
-
if (providerSubmissionBegun) {
|
|
410
|
-
store.resolveRoleRunProviderSubmission?.({
|
|
411
|
-
taskId: task.id,
|
|
412
|
-
roleName: role.name,
|
|
413
|
-
runId: run.id,
|
|
414
|
-
attemptId: receiptId,
|
|
415
|
-
status: "delivery-unknown",
|
|
416
|
-
reason: message,
|
|
417
|
-
now
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
270
|
// Scheduler single-flight backpressure: the Role runtime lifecycle
|
|
421
271
|
// lane was busy when the launch was reserved. The Run stays
|
|
422
272
|
// active-unpushed and its mailbox claim is retained; the next pass
|
|
@@ -881,7 +731,7 @@ function requireResumeSession(role, effective, session) {
|
|
|
881
731
|
if (session === null || !hasText(session.nativeSessionId)) {
|
|
882
732
|
throw new Error(`Role resume has no fixed native session: ${role.taskId}/${role.name}.`);
|
|
883
733
|
}
|
|
884
|
-
if (!
|
|
734
|
+
if (!effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)) {
|
|
885
735
|
throw new Error(`Role resume effective snapshot drifted: ${role.taskId}/${role.name}.`);
|
|
886
736
|
}
|
|
887
737
|
return session.nativeSessionId;
|
|
@@ -948,16 +798,11 @@ function validateRoleSession(role, effective, existing, mode, session) {
|
|
|
948
798
|
throw new Error(`Ready Role session identity changed: ${role.taskId}/${role.name}.`);
|
|
949
799
|
}
|
|
950
800
|
const compatible = mode === "resume"
|
|
951
|
-
?
|
|
801
|
+
? effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)
|
|
952
802
|
: effectiveLaunchSnapshotsCompatible(session.effective, effective);
|
|
953
803
|
if (!compatible) {
|
|
954
804
|
throw new Error(`Ready Role session effective snapshot changed: ${role.taskId}/${role.name}.`);
|
|
955
805
|
}
|
|
956
|
-
if (existing?.nativeSessionId !== undefined
|
|
957
|
-
&& effectiveLaunchSnapshotsCompatibleForTaskMain(existing.effective, effective, role.managedWorkspace)
|
|
958
|
-
&& session.nativeSessionId !== existing.nativeSessionId) {
|
|
959
|
-
throw new Error(`Ready Role session changed the fixed native session id: ${role.taskId}/${role.name}.`);
|
|
960
|
-
}
|
|
961
806
|
if (mode === "resume" && session.nativeSessionId !== existing?.nativeSessionId) {
|
|
962
807
|
throw new Error(`Role resume changed the fixed native session id: ${role.taskId}/${role.name}.`);
|
|
963
808
|
}
|
|
@@ -972,13 +817,3 @@ function validateRoleSession(role, effective, existing, mode, session) {
|
|
|
972
817
|
function hasText(value) {
|
|
973
818
|
return typeof value === "string" && value.trim().length > 0;
|
|
974
819
|
}
|
|
975
|
-
/**
|
|
976
|
-
* Fail-closed readiness check for a supported-readiness adapter's first push.
|
|
977
|
-
* When the store cannot answer (no implementation), the push is held rather than
|
|
978
|
-
* proceeding blind — a supported adapter must have a proven provider-ready fold.
|
|
979
|
-
*/
|
|
980
|
-
function providerReadyForPush(store, input) {
|
|
981
|
-
if (store.isRoleGenerationProviderReady === undefined)
|
|
982
|
-
return false;
|
|
983
|
-
return store.isRoleGenerationProviderReady(input);
|
|
984
|
-
}
|
|
@@ -35,8 +35,7 @@ export function repairOrphanedActiveTasks(store, now, selection) {
|
|
|
35
35
|
runs: activeRuns,
|
|
36
36
|
pendingWakeup: store.getPendingWakeup(task.id),
|
|
37
37
|
leaderMailbox,
|
|
38
|
-
leaderFailure: store.getLeaderFailure(task.id)
|
|
39
|
-
operatorNotification: store.getOperatorNotification(task.id)
|
|
38
|
+
leaderFailure: store.getLeaderFailure(task.id)
|
|
40
39
|
});
|
|
41
40
|
const queuedAlongsideActiveSibling = hasDispatchableQueuedResourceLane(store, task.id)
|
|
42
41
|
&& !activeRuns.some(({ roleName }) => roleName === "leader")
|
|
@@ -45,7 +44,6 @@ export function repairOrphanedActiveTasks(store, now, selection) {
|
|
|
45
44
|
if ((queuedAlongsideActiveSibling ? hasLeaderInFlightTurn : hasInFlightTurn)
|
|
46
45
|
|| store.hasOpenInputRequest(task.id)
|
|
47
46
|
|| store.getLeaderFailure(task.id) !== null
|
|
48
|
-
|| store.getOperatorNotification(task.id) !== null
|
|
49
47
|
|| (projection.status !== "needs-leader-action" && !queuedAlongsideActiveSibling)
|
|
50
48
|
|| hasUnclaimedLeaderWork(store, task.id, leaderMailbox)) {
|
|
51
49
|
continue;
|
|
@@ -121,8 +119,11 @@ function findLastLeaderRun(store, taskId) {
|
|
|
121
119
|
};
|
|
122
120
|
}
|
|
123
121
|
function hasUnclaimedLeaderWork(store, taskId, leaderMailbox) {
|
|
122
|
+
if (store.getPendingWakeup(taskId) !== null)
|
|
123
|
+
return true;
|
|
124
124
|
if (leaderMailbox === null)
|
|
125
|
-
return
|
|
126
|
-
return leaderMailbox
|
|
127
|
-
&& leaderMailbox.pending !== null
|
|
125
|
+
return false;
|
|
126
|
+
return leaderMailbox.processing === null
|
|
127
|
+
&& (leaderMailbox.pending.normal !== null
|
|
128
|
+
|| leaderMailbox.pending.userCorrection !== null);
|
|
128
129
|
}
|