@zq-silk/yui 0.6.2 → 0.6.4
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 +28 -4
- package/README.md +60 -97
- package/dist/agent/argumentPolicy.js +1 -1
- package/dist/agent/managedRuntimeEnvironment.js +1 -0
- package/dist/cli/commandCatalog.js +19 -9
- package/dist/cli/interactionPolicy.js +4 -2
- package/dist/cli.js +77 -32
- package/dist/commands/taskCommands.js +46 -11
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskRoleRuntimeStatus.js +170 -10
- package/dist/controller/agentRuntimeObserver.js +210 -0
- package/dist/controller/clientRuntime.js +3 -21
- package/dist/controller/controller.js +47 -7
- package/dist/controller/fileSchedulerStoreAdapter.js +522 -388
- package/dist/controller/runtime.js +9 -3
- package/dist/controller/runtimeEventInbox.js +49 -295
- package/dist/controller/runtimeEventProcessor.js +184 -321
- package/dist/controller/runtimeHookRunFence.js +226 -0
- package/dist/controller/runtimeLaunchCoordinator.js +91 -26
- package/dist/controller/runtimeObservationHook.js +112 -0
- package/dist/core/controllerServer.js +5 -0
- package/dist/executor/agentAdapter.js +18 -3
- package/dist/executor/fileRoleLaunchPlanner.js +64 -15
- package/dist/executor/managedClaudeRunner.js +121 -0
- package/dist/observability/executionAudit.js +6 -3
- package/dist/repository/taskWorkspacePreparer.js +1 -4
- package/dist/run/providerRetryConfig.js +8 -3
- package/dist/runtime/agentDriver.js +229 -0
- package/dist/runtime/agentDriverObservation.js +57 -0
- package/dist/runtime/builtinAgentDrivers.js +235 -0
- package/dist/runtime/builtinTranscriptObserver.js +290 -0
- package/dist/runtime/builtinTranscriptUsage.js +97 -0
- package/dist/runtime/exactControlPlane.js +2 -2
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/ports.js +12 -1
- package/dist/runtime/runtimeObservation.js +297 -0
- package/dist/runtime/runtimeProjection.js +277 -0
- package/dist/runtime/sessionTerminationGuard.js +78 -22
- package/dist/runtime/tmuxAdapters.js +35 -0
- package/dist/scheduler/activeRoleRunDelivery.js +28 -13
- package/dist/scheduler/leaderWakeupProcessor.js +21 -2
- package/dist/scheduler/roleRunLiveness.js +2 -2
- package/dist/scheduler/roleRunStall.js +62 -114
- package/dist/storage/migration/productionRegistry.js +85 -0
- package/dist/storage/sqliteStore.js +3 -3
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +2 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +123 -0
- package/dist/telemetry/sqliteTelemetryStore.js +0 -28
- package/dist/telemetry/telemetryCompaction.js +1 -0
- package/dist/telemetry/telemetryConfig.js +4 -5
- package/dist/tmux/tmuxManager.js +136 -22
- package/dist/web/assets/client/view.js +1 -1
- package/dist/web/tmuxWebTerminal.js +17 -12
- package/dist/web/webSnapshot.js +1 -1
- package/dist/worktree/managedWorkspace.js +14 -0
- package/i18n/README.zh-CN.md +12 -7
- package/package.json +1 -1
- package/dist/controller/claudeLifecycleHook.js +0 -203
- package/dist/controller/codexLifecycleHook.js +0 -108
- package/dist/controller/providerHookRunFence.js +0 -156
- package/dist/lifecycle/providerLifecycleMapping.js +0 -190
- package/dist/telemetry/telemetryRouter.js +0 -32
|
@@ -1,6 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { isDeepStrictEqual } from "node:util";
|
|
2
|
-
import { routeProviderProgress } from "../telemetry/telemetryRouter.js";
|
|
3
|
-
import { NullTelemetrySink } from "../telemetry/telemetryStore.js";
|
|
4
3
|
import { activeLiveRoleAgentSession, bindTaskRoleRun, clearTaskRoleRun, createRoleSessionSet, markTaskRoleRunDelivered, markTaskRoleRunPushed, recordObservedTaskRoleCompletion, recordRoleAgentSession, rememberRoleAgentCompletedTurn, settleTaskRoleCompletion, updateRoleAgentSessionStatus } from "../executor/agentExecutor.js";
|
|
5
4
|
import { createPendingTurnCompletion, hasRecentTurnId } from "../executor/turnCompletion.js";
|
|
6
5
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
@@ -13,13 +12,12 @@ import { classifyProviderError, isRetryableProviderErrorClass } from "../lifecyc
|
|
|
13
12
|
import { providerRetryIsDue, scheduleProviderRetry } from "../run/providerRetry.js";
|
|
14
13
|
import { providerRetryConfig } from "../run/providerRetryConfig.js";
|
|
15
14
|
import { terminalizeExactTaskRun } from "../lifecycle/exactRunTerminalization.js";
|
|
16
|
-
import { foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
17
|
-
import { mapNativeLifecycleSignal } from "../lifecycle/providerLifecycleMapping.js";
|
|
15
|
+
import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
18
16
|
import { recordLeaderFailure } from "../scheduler/leaderFailure.js";
|
|
19
17
|
import { createLeaderRecoveryNotification, createLeaderStallNotification } from "../scheduler/operatorNotification.js";
|
|
20
18
|
import { pendingWakeupsMatch } from "../scheduler/pendingWakeup.js";
|
|
21
19
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
22
|
-
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey,
|
|
20
|
+
import { foldRunProgressFacts, latestRunDurableProgressAt, latestRunEventTime, latestStallEvidenceKey, clearMatchingLeaderStallAttention, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT, RUN_STALLED_EVENT } from "../scheduler/roleRunStall.js";
|
|
23
21
|
import { currentWorkItemExecutionGroup, updateWorkItemExecutionGroup, updateWorkItemStatus } from "../workItem/workItem.js";
|
|
24
22
|
import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
25
23
|
import { formatAgentRunReceiptId, formatTaskRecordReference } from "../task/taskRecordReference.js";
|
|
@@ -27,21 +25,221 @@ import { bindExecution, claimPending, completeProcessing, releaseProcessing } fr
|
|
|
27
25
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
28
26
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_LAUNCH_RESERVED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
29
27
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
30
|
-
|
|
28
|
+
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
29
|
+
import { RUNTIME_OBSERVATION_TASK_EVENT, createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent, runtimeObservationTaskEventPayload } from "../runtime/runtimeObservation.js";
|
|
31
30
|
/** Maps the authoritative FileTaskStore records to the scheduler's narrow port. */
|
|
32
31
|
export class FileSchedulerStoreAdapter {
|
|
33
32
|
store;
|
|
34
33
|
telemetry;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* upserts progress to the sidecar; `bounded` routes progress to the
|
|
39
|
-
* sidecar only and feeds the scheduler projection synthesized latest
|
|
40
|
-
* progress from the reader.
|
|
41
|
-
*/
|
|
42
|
-
constructor(store, telemetry = null) {
|
|
34
|
+
drivers;
|
|
35
|
+
/** Diagnostic telemetry is optional and never participates in runtime truth. */
|
|
36
|
+
constructor(store, telemetry = null, drivers = builtinAgentDriverRegistry()) {
|
|
43
37
|
this.store = store;
|
|
44
38
|
this.telemetry = telemetry;
|
|
39
|
+
this.drivers = drivers;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Sole provider-independent ingress for structured runtime state. Driver
|
|
43
|
+
* mapping has already happened before this boundary; this method validates
|
|
44
|
+
* the exact durable Run/session fence, applies the small workflow-relevant
|
|
45
|
+
* transitions, and retains the canonical observation for status projection.
|
|
46
|
+
*/
|
|
47
|
+
observeRuntimeObservation(raw, now = new Date()) {
|
|
48
|
+
const input = createRuntimeObservation(raw);
|
|
49
|
+
const taskId = input.fence.taskId;
|
|
50
|
+
if (taskId === undefined)
|
|
51
|
+
return "obsolete";
|
|
52
|
+
if (Date.parse(input.receivedAt) > now.getTime()) {
|
|
53
|
+
return this.recordObsoleteCanonicalObservation(input, "received-at-in-future", now);
|
|
54
|
+
}
|
|
55
|
+
const adapterId = this.adapterForRuntimeObservation(input);
|
|
56
|
+
if (adapterId === null) {
|
|
57
|
+
return this.recordObsoleteCanonicalObservation(input, "driver-or-run-mismatch", now);
|
|
58
|
+
}
|
|
59
|
+
let outcome;
|
|
60
|
+
switch (input.kind) {
|
|
61
|
+
case "session.started":
|
|
62
|
+
case "session.ready":
|
|
63
|
+
outcome = this.observeRuntimeSession(input, adapterId, now);
|
|
64
|
+
break;
|
|
65
|
+
case "turn.accepted":
|
|
66
|
+
outcome = this.observeRuntimePromptAccepted(input, adapterId, now);
|
|
67
|
+
break;
|
|
68
|
+
case "turn.completed": {
|
|
69
|
+
const completed = {
|
|
70
|
+
taskId,
|
|
71
|
+
roleName: input.fence.roleName,
|
|
72
|
+
agentId: input.fence.agentId,
|
|
73
|
+
adapterId,
|
|
74
|
+
launchId: input.fence.launchId,
|
|
75
|
+
nativeSessionId: input.fence.nativeSessionId,
|
|
76
|
+
turnId: input.fence.nativeTurnId,
|
|
77
|
+
runId: input.fence.runId,
|
|
78
|
+
summary: input.payload.summary ?? "Agent turn completed without a workflow outcome."
|
|
79
|
+
};
|
|
80
|
+
const classification = this.classifyRuntimeTurnCompleted(completed);
|
|
81
|
+
if (classification !== "apply")
|
|
82
|
+
return classification;
|
|
83
|
+
const result = this.observeRuntimeTurnCompleted(completed, now);
|
|
84
|
+
outcome = result.disposition === "obsolete" ? "obsolete" : "applied";
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "turn.failed": {
|
|
88
|
+
const failureEvidence = input.payload.failure;
|
|
89
|
+
const failure = {
|
|
90
|
+
eventId: input.eventId,
|
|
91
|
+
eventType: input.kind,
|
|
92
|
+
taskId,
|
|
93
|
+
roleName: input.fence.roleName,
|
|
94
|
+
agentId: input.fence.agentId,
|
|
95
|
+
adapterId,
|
|
96
|
+
launchId: input.fence.launchId,
|
|
97
|
+
nativeSessionId: input.fence.nativeSessionId,
|
|
98
|
+
runId: input.fence.runId,
|
|
99
|
+
error: failureEvidence.code,
|
|
100
|
+
...(failureEvidence.details === undefined
|
|
101
|
+
? {}
|
|
102
|
+
: { errorDetails: failureEvidence.details }),
|
|
103
|
+
...(failureEvidence.lastOutput === undefined
|
|
104
|
+
? {}
|
|
105
|
+
: { lastAssistantMessage: failureEvidence.lastOutput })
|
|
106
|
+
};
|
|
107
|
+
if (this.classifyRuntimeTurnFailed(failure) !== "apply")
|
|
108
|
+
return "obsolete";
|
|
109
|
+
outcome = this.observeRuntimeTurnFailed(failure, now).disposition === "applied"
|
|
110
|
+
? "applied"
|
|
111
|
+
: "obsolete";
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
case "operation.started":
|
|
115
|
+
case "operation.completed":
|
|
116
|
+
case "operation.failed":
|
|
117
|
+
case "turn.waiting":
|
|
118
|
+
case "turn.cancelled":
|
|
119
|
+
case "activity.observed":
|
|
120
|
+
case "observer.health":
|
|
121
|
+
outcome = this.validateCanonicalRunObservation(input, now);
|
|
122
|
+
break;
|
|
123
|
+
case "session.ended":
|
|
124
|
+
case "session.failed":
|
|
125
|
+
outcome = this.validateCanonicalSessionObservation(input, now);
|
|
126
|
+
break;
|
|
127
|
+
case "host.observed":
|
|
128
|
+
outcome = "obsolete";
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
outcome = "obsolete";
|
|
132
|
+
}
|
|
133
|
+
if (outcome === "applied")
|
|
134
|
+
this.persistRuntimeObservation(input, now);
|
|
135
|
+
return outcome;
|
|
136
|
+
}
|
|
137
|
+
adapterForRuntimeObservation(input) {
|
|
138
|
+
const taskId = input.fence.taskId;
|
|
139
|
+
const runId = input.fence.runId;
|
|
140
|
+
if (taskId === undefined || runId === undefined)
|
|
141
|
+
return null;
|
|
142
|
+
const run = this.store.getAgentRun(taskId, runId);
|
|
143
|
+
if (run === null
|
|
144
|
+
|| run.roleName !== input.fence.roleName
|
|
145
|
+
|| run.effective.agentId !== input.fence.agentId)
|
|
146
|
+
return null;
|
|
147
|
+
try {
|
|
148
|
+
return this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
|
|
149
|
+
? run.effective.adapterId
|
|
150
|
+
: null;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
validateCanonicalRunObservation(input, now) {
|
|
157
|
+
return this.store.transaction((store) => {
|
|
158
|
+
if (store.listEvents(input.fence.taskId).some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
159
|
+
&& event.payload.eventId === input.eventId)))
|
|
160
|
+
return "applied";
|
|
161
|
+
const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
|
|
162
|
+
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
163
|
+
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
164
|
+
const session = sessions?.sessions[input.fence.agentId];
|
|
165
|
+
const requiresActive = input.kind !== "turn.cancelled";
|
|
166
|
+
const valid = run !== null
|
|
167
|
+
&& (!requiresActive || (run.status === "active" && active?.id === run.id))
|
|
168
|
+
&& run.roleName === input.fence.roleName
|
|
169
|
+
&& run.effective.agentId === input.fence.agentId
|
|
170
|
+
&& this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId
|
|
171
|
+
&& session?.launchId === input.fence.launchId
|
|
172
|
+
&& session.nativeSessionId === input.fence.nativeSessionId
|
|
173
|
+
&& input.fence.sessionGenerationId === input.fence.launchId
|
|
174
|
+
&& input.fence.receiptId === formatAgentRunReceiptId(input.fence.taskId, run.id)
|
|
175
|
+
&& (run.deliveredAt !== undefined || input.kind === "turn.cancelled");
|
|
176
|
+
if (!valid) {
|
|
177
|
+
recordCanonicalObservationObsolete(store, input, "runtime-fence-not-current", now);
|
|
178
|
+
return "obsolete";
|
|
179
|
+
}
|
|
180
|
+
return "applied";
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
validateCanonicalSessionObservation(input, now) {
|
|
184
|
+
return this.store.transaction((store) => {
|
|
185
|
+
const run = store.getAgentRun(input.fence.taskId, input.fence.runId);
|
|
186
|
+
const sessions = store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName);
|
|
187
|
+
const session = sessions?.sessions[input.fence.agentId];
|
|
188
|
+
if (run === null
|
|
189
|
+
|| run.roleName !== input.fence.roleName
|
|
190
|
+
|| run.effective.agentId !== input.fence.agentId
|
|
191
|
+
|| this.drivers.requireByAdapterId(run.effective.adapterId).id !== input.fence.driverId
|
|
192
|
+
|| input.fence.receiptId !== formatAgentRunReceiptId(input.fence.taskId, run.id)
|
|
193
|
+
|| session?.launchId !== input.fence.launchId
|
|
194
|
+
|| session.nativeSessionId !== input.fence.nativeSessionId
|
|
195
|
+
|| input.fence.sessionGenerationId !== input.fence.launchId) {
|
|
196
|
+
recordCanonicalObservationObsolete(store, input, "runtime-session-not-current", now);
|
|
197
|
+
return "obsolete";
|
|
198
|
+
}
|
|
199
|
+
const status = input.kind === "session.failed" ? "broken" : "stopped";
|
|
200
|
+
store.saveTaskRoleSessionSet(updateRoleAgentSessionStatus(sessions, input.fence.agentId, status, now));
|
|
201
|
+
return "applied";
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
persistRuntimeObservation(input, now) {
|
|
205
|
+
this.store.transaction((store) => {
|
|
206
|
+
const taskId = input.fence.taskId;
|
|
207
|
+
const events = store.listEvents(taskId);
|
|
208
|
+
if (events.some((event) => (event.type === RUNTIME_OBSERVATION_TASK_EVENT
|
|
209
|
+
&& event.payload.eventId === input.eventId)))
|
|
210
|
+
return;
|
|
211
|
+
if (usageSnapshotIsSuperseded(events, input))
|
|
212
|
+
return;
|
|
213
|
+
const confirmedActivity = confirmedUsageActivityObservation(events, input);
|
|
214
|
+
const removable = [
|
|
215
|
+
...compactedRuntimeObservationIds(events, input),
|
|
216
|
+
...(confirmedActivity === null
|
|
217
|
+
? []
|
|
218
|
+
: compactedRuntimeObservationIds(events, confirmedActivity))
|
|
219
|
+
];
|
|
220
|
+
if (removable.length > 0)
|
|
221
|
+
store.removeEvents(taskId, removable);
|
|
222
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(input), now));
|
|
223
|
+
if (confirmedActivity !== null) {
|
|
224
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, RUNTIME_OBSERVATION_TASK_EVENT, runtimeObservationTaskEventPayload(confirmedActivity), now));
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
if (this.telemetry !== null && input.fence.runId !== undefined) {
|
|
228
|
+
try {
|
|
229
|
+
this.telemetry.sink.observe(runtimeObservationTelemetryEntry(input));
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Runtime telemetry is diagnostic; the compact durable state snapshot
|
|
233
|
+
// above remains authoritative when a sidecar is unavailable.
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
recordObsoleteCanonicalObservation(input, reason, now) {
|
|
238
|
+
const taskId = input.fence.taskId;
|
|
239
|
+
if (taskId === undefined || this.store.getTask(taskId) === null)
|
|
240
|
+
return "obsolete";
|
|
241
|
+
this.store.transaction((store) => recordCanonicalObservationObsolete(store, input, reason, now));
|
|
242
|
+
return "obsolete";
|
|
45
243
|
}
|
|
46
244
|
/**
|
|
47
245
|
* Revision-scoped read projection. Keyed by the store's durable revision;
|
|
@@ -53,24 +251,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
53
251
|
const revision = typeof this.store.getStateRevision === "function"
|
|
54
252
|
? this.store.getStateRevision()
|
|
55
253
|
: Number.NaN;
|
|
56
|
-
const telemetryRevision = this.telemetry?.mode === "bounded"
|
|
57
|
-
? this.telemetry.reader.revision()
|
|
58
|
-
: 0;
|
|
59
254
|
if (this.#readProjection === null
|
|
60
|
-
|| this.#readProjection.revision !== revision
|
|
61
|
-
|
|
62
|
-
this.#readProjection = { revision, telemetryRevision, tasks: new Map() };
|
|
255
|
+
|| this.#readProjection.revision !== revision) {
|
|
256
|
+
this.#readProjection = { revision, tasks: new Map() };
|
|
63
257
|
}
|
|
64
258
|
const tasks = this.#readProjection.tasks;
|
|
65
259
|
let task = tasks.get(taskId);
|
|
66
260
|
if (task === undefined) {
|
|
67
|
-
const
|
|
68
|
-
// In bounded mode progress lives only in the sidecar; merge the latest
|
|
69
|
-
// observation per Run so liveness/stall folds keep working. In legacy
|
|
70
|
-
// and dual modes semantic history already carries progress.
|
|
71
|
-
const events = this.telemetry?.mode === "bounded"
|
|
72
|
-
? [...semanticEvents, ...this.telemetry.reader.latestProgressEvents(taskId)]
|
|
73
|
-
: semanticEvents;
|
|
261
|
+
const events = this.store.listEvents(taskId);
|
|
74
262
|
task = {
|
|
75
263
|
events,
|
|
76
264
|
runFacts: foldRunProgressFacts(events),
|
|
@@ -301,58 +489,24 @@ export class FileSchedulerStoreAdapter {
|
|
|
301
489
|
return existing && !recovered ? "already-recorded" : "recorded";
|
|
302
490
|
});
|
|
303
491
|
}
|
|
304
|
-
recordRoleRunResourceSuppression(input) {
|
|
305
|
-
return this.store.transaction((store) => {
|
|
306
|
-
const task = store.getTask(input.taskId);
|
|
307
|
-
const run = store.getActiveAgentRun(input.taskId, input.roleName);
|
|
308
|
-
if (task === null
|
|
309
|
-
|| task.status !== "active"
|
|
310
|
-
|| run === null
|
|
311
|
-
|| run.id !== input.runId
|
|
312
|
-
|| run.status !== "active")
|
|
313
|
-
return "state-changed";
|
|
314
|
-
const session = store.getRoleSession(input.taskId, input.roleName);
|
|
315
|
-
if (session === null
|
|
316
|
-
|| session.agentId !== input.agentId
|
|
317
|
-
|| session.adapterId !== input.adapterId
|
|
318
|
-
|| session.status === "stopped"
|
|
319
|
-
|| session.status === "broken"
|
|
320
|
-
|| session.nativeSessionId !== input.nativeSessionId
|
|
321
|
-
|| session.launchId !== input.launchId)
|
|
322
|
-
return "state-changed";
|
|
323
|
-
const existing = store.listEvents(task.id).some((event) => (event.type === RUN_RESOURCE_SUPPRESSED_EVENT
|
|
324
|
-
&& event.payload.runId === run.id
|
|
325
|
-
&& event.payload.roleName === input.roleName
|
|
326
|
-
&& event.payload.progressAt === input.progressAt));
|
|
327
|
-
if (existing)
|
|
328
|
-
return "already-recorded";
|
|
329
|
-
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_RESOURCE_SUPPRESSED_EVENT, {
|
|
330
|
-
runId: run.id,
|
|
331
|
-
roleName: input.roleName,
|
|
332
|
-
progressAt: input.progressAt,
|
|
333
|
-
observedAt: input.observedAt,
|
|
334
|
-
kind: "advisory-resource"
|
|
335
|
-
}, input.now));
|
|
336
|
-
return "recorded";
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
492
|
hasInFlightTurn(taskId, roleName) {
|
|
340
493
|
const sessions = this.store.getTaskRoleSessionSet(taskId, roleName);
|
|
341
494
|
return sessions !== null && sessions.inFlight !== null;
|
|
342
495
|
}
|
|
343
496
|
isRoleGenerationProviderReady(input) {
|
|
344
|
-
// A
|
|
345
|
-
//
|
|
346
|
-
// matching agent/launch/nativeSession fence. This reads durable folded truth
|
|
497
|
+
// A session.ready observation for this exact generation is the durable
|
|
498
|
+
// pre-input fence. This reads folded Driver truth
|
|
347
499
|
// only — no liveness, screen, or pane/PID inference. Served from the
|
|
348
500
|
// revision projection: a stale miss only repeats an idempotent first push.
|
|
349
|
-
return this.listEvents(input.taskId).some((event) =>
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
501
|
+
return this.listEvents(input.taskId).some((event) => {
|
|
502
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
503
|
+
return observation?.kind === "session.ready"
|
|
504
|
+
&& observation.fence.roleName === input.roleName
|
|
505
|
+
&& observation.fence.agentId === input.agentId
|
|
506
|
+
&& (input.launchId === undefined || observation.fence.launchId === input.launchId)
|
|
507
|
+
&& (input.nativeSessionId === undefined
|
|
508
|
+
|| observation.fence.nativeSessionId === input.nativeSessionId);
|
|
509
|
+
});
|
|
356
510
|
}
|
|
357
511
|
peekNextAgentRunId(taskId) {
|
|
358
512
|
return this.store.peekNextAgentRunId(taskId);
|
|
@@ -995,7 +1149,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
995
1149
|
if (hasRuntimeCleanupObligation(mailbox)) {
|
|
996
1150
|
throw new Error("Runtime cleanup is still pending.");
|
|
997
1151
|
}
|
|
998
|
-
// A synchronous
|
|
1152
|
+
// A synchronous Driver Hook may bind a fresh native Session
|
|
999
1153
|
// and complete the reservation while the host start call is still
|
|
1000
1154
|
// unwinding. Preserve that exact hook-won generation instead of treating
|
|
1001
1155
|
// the already-settled reservation as a launch failure.
|
|
@@ -1151,7 +1305,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1151
1305
|
}
|
|
1152
1306
|
// Provider completion is not a transport acknowledgement. Retain the
|
|
1153
1307
|
// immutable Hook until the exact pane receipt independently reconciles
|
|
1154
|
-
// run.pushed; never infer transport from
|
|
1308
|
+
// run.pushed; never infer transport from Driver activity/terminal facts.
|
|
1155
1309
|
return sessions.inFlight.pushedAt === undefined ? "deferred" : "apply";
|
|
1156
1310
|
}
|
|
1157
1311
|
observeRuntimeTurnCompleted(input, now = new Date()) {
|
|
@@ -1251,23 +1405,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
1251
1405
|
observedAt: now,
|
|
1252
1406
|
dueAt: new Date(now.getTime() + 2_000)
|
|
1253
1407
|
});
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
roleName: role.name,
|
|
1258
|
-
kind: "native-turn",
|
|
1259
|
-
turnId: input.turnId,
|
|
1260
|
-
note: boundedRuntimeNote(input.summary)
|
|
1261
|
-
}, now));
|
|
1262
|
-
if (wasStalled) {
|
|
1263
|
-
store.saveEvent(task.id, createTaskEvent(store.nextEventId(task.id), task.id, RUN_RECOVERED_EVENT, {
|
|
1264
|
-
runId: fence.runId,
|
|
1265
|
-
roleName: role.name,
|
|
1266
|
-
progressAt: now.toISOString(),
|
|
1267
|
-
kind: "native-turn"
|
|
1268
|
-
}, now));
|
|
1269
|
-
}
|
|
1270
|
-
clearMatchingLeaderStallAttention(store, task.id, fence.runId);
|
|
1408
|
+
// Runtime completion is not workflow progress. The exact Run remains
|
|
1409
|
+
// open for the short outcome grace period so a concurrently committed
|
|
1410
|
+
// yui yield/block/complete action can win; otherwise the contract fails.
|
|
1271
1411
|
sessions = recordObservedTaskRoleCompletion(sessions, completion);
|
|
1272
1412
|
const active = store.getActiveAgentRun(task.id, role.name);
|
|
1273
1413
|
if (active === null || active.id !== fence.runId || active.status !== "active") {
|
|
@@ -1391,53 +1531,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1391
1531
|
|| (input.expectedRunId !== undefined && active.id !== input.expectedRunId)) {
|
|
1392
1532
|
return { session };
|
|
1393
1533
|
}
|
|
1394
|
-
|
|
1395
|
-
const summary = `Role turn completed without yui task run yield. Last assistant message: ${input.summary}`;
|
|
1396
|
-
const result = terminalizeExactTaskRun(store, {
|
|
1397
|
-
taskId: task.id,
|
|
1398
|
-
roleName: role.name,
|
|
1399
|
-
agentId: active.effective.agentId,
|
|
1400
|
-
runId: active.id,
|
|
1401
|
-
receiptId: formatAgentRunReceiptId(task.id, active.id),
|
|
1402
|
-
nativeSessionId: input.nativeSessionId,
|
|
1403
|
-
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1404
|
-
outcome: { status: "failed", summary }
|
|
1405
|
-
}, now);
|
|
1406
|
-
if (result.disposition !== "applied" || result.run === null) {
|
|
1407
|
-
throw new Error(`Role Run changed during native Turn completion: ${active.id}/${result.reason}.`);
|
|
1408
|
-
}
|
|
1409
|
-
const terminal = result.run;
|
|
1410
|
-
if (active.purpose === "execution" && active.workItemId !== undefined) {
|
|
1411
|
-
const item = store.getWorkItem(task.id, active.workItemId);
|
|
1412
|
-
if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
|
|
1413
|
-
store.saveWorkItem(task.id, updateWorkItemStatus(item, "failed", now, summary));
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
store.saveRole(task.id, updateRoleStatus(role, "idle", now));
|
|
1417
|
-
clearMatchingLeaderStallAttention(store, task.id, terminal.id);
|
|
1418
|
-
enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
|
|
1419
|
-
{ type: "run", taskId: task.id, id: terminal.id },
|
|
1420
|
-
...(terminal.workItemId === undefined
|
|
1421
|
-
? []
|
|
1422
|
-
: [{
|
|
1423
|
-
type: "work-item",
|
|
1424
|
-
taskId: task.id,
|
|
1425
|
-
id: terminal.workItemId
|
|
1426
|
-
}])
|
|
1427
|
-
]);
|
|
1428
|
-
return { session, finalizedRunId: terminal.id };
|
|
1429
|
-
}
|
|
1430
|
-
const leaderTarget = { kind: "role", taskId: task.id, roleName: role.name };
|
|
1431
|
-
const leaderMailbox = store.getWorkMailbox(leaderTarget);
|
|
1432
|
-
const recoveryTurn = leaderMailbox?.processing?.executionRef?.type === "run"
|
|
1433
|
-
&& leaderMailbox.processing.executionRef.taskId === task.id
|
|
1434
|
-
&& leaderMailbox.processing.executionRef.id === active.id
|
|
1435
|
-
&& leaderMailbox.processing.batch.reasons.includes("leader-turn-unclosed");
|
|
1436
|
-
const quiescent = leaderMailbox?.pending === null
|
|
1437
|
-
&& !store.listRoles(task.id).some((candidate) => (candidate.name !== "leader"
|
|
1438
|
-
&& store.getActiveAgentRun(task.id, candidate.name) !== null))
|
|
1439
|
-
&& !store.listWorkItems(task.id).some((item) => item.status === "running")
|
|
1440
|
-
&& !store.listInputRequests(task.id).some((request) => request.status === "open");
|
|
1534
|
+
const summary = `Agent turn completed without a Yui workflow outcome. Last assistant message: ${input.summary}`;
|
|
1441
1535
|
const result = terminalizeExactTaskRun(store, {
|
|
1442
1536
|
taskId: task.id,
|
|
1443
1537
|
roleName: role.name,
|
|
@@ -1446,38 +1540,45 @@ export class FileSchedulerStoreAdapter {
|
|
|
1446
1540
|
receiptId: formatAgentRunReceiptId(task.id, active.id),
|
|
1447
1541
|
nativeSessionId: input.nativeSessionId,
|
|
1448
1542
|
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
1449
|
-
outcome: { status: "
|
|
1543
|
+
outcome: { status: "failed", summary }
|
|
1450
1544
|
}, now);
|
|
1451
1545
|
if (result.disposition !== "applied" || result.run === null) {
|
|
1452
|
-
throw new Error(`
|
|
1546
|
+
throw new Error(`Role Run changed during native Turn completion: ${active.id}/${result.reason}.`);
|
|
1453
1547
|
}
|
|
1454
1548
|
const terminal = result.run;
|
|
1455
|
-
|
|
1456
|
-
|
|
1549
|
+
const leaderWorkflowRun = role.name === "leader" && active.workItemId === undefined;
|
|
1550
|
+
if (active.purpose === "execution" && active.workItemId !== undefined) {
|
|
1551
|
+
const item = store.getWorkItem(task.id, active.workItemId);
|
|
1552
|
+
if (item !== null && !["completed", "failed", "retired"].includes(item.status)) {
|
|
1553
|
+
store.saveWorkItem(task.id, updateWorkItemStatus(item, "failed", now, summary));
|
|
1554
|
+
}
|
|
1457
1555
|
}
|
|
1556
|
+
store.saveRole(task.id, updateRoleStatus(role, leaderWorkflowRun ? "failed" : "idle", now));
|
|
1458
1557
|
clearMatchingLeaderStallAttention(store, task.id, terminal.id);
|
|
1459
|
-
if (
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1558
|
+
if (!leaderWorkflowRun) {
|
|
1559
|
+
enqueueWork(store, { kind: "role", taskId: task.id, roleName: "leader" }, "role-run-failed", now, [
|
|
1560
|
+
{ type: "run", taskId: task.id, id: terminal.id },
|
|
1561
|
+
...(terminal.workItemId === undefined
|
|
1562
|
+
? []
|
|
1563
|
+
: [{
|
|
1564
|
+
type: "work-item",
|
|
1565
|
+
taskId: task.id,
|
|
1566
|
+
id: terminal.workItemId
|
|
1567
|
+
}])
|
|
1568
|
+
]);
|
|
1569
|
+
}
|
|
1570
|
+
else {
|
|
1571
|
+
store.saveLeaderFailure(recordLeaderFailure(task.id, input.nativeSessionId, summary, now, store.getLeaderFailure(task.id)));
|
|
1572
|
+
store.saveOperatorNotification(createLeaderRecoveryNotification(task.id, summary, now, store.getOperatorNotification(task.id)));
|
|
1573
|
+
enqueueWork(store, { kind: "operator" }, "leader-run-failed", now, [
|
|
1574
|
+
{ type: "task", id: task.id },
|
|
1575
|
+
{ type: "run", taskId: task.id, id: terminal.id }
|
|
1576
|
+
]);
|
|
1476
1577
|
}
|
|
1477
1578
|
return { session, finalizedRunId: terminal.id };
|
|
1478
1579
|
});
|
|
1479
1580
|
}
|
|
1480
|
-
|
|
1581
|
+
classifyRuntimeTurnFailed(input) {
|
|
1481
1582
|
const task = this.store.getTask(input.taskId);
|
|
1482
1583
|
if (task?.status !== "active")
|
|
1483
1584
|
return "obsolete";
|
|
@@ -1491,8 +1592,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1491
1592
|
|| active?.id !== run.id
|
|
1492
1593
|
|| run.roleName !== input.roleName
|
|
1493
1594
|
|| run.effective.agentId !== input.agentId
|
|
1494
|
-
|| run.effective.adapterId !==
|
|
1495
|
-
|| input.adapterId !== "claude")
|
|
1595
|
+
|| run.effective.adapterId !== input.adapterId)
|
|
1496
1596
|
return "obsolete";
|
|
1497
1597
|
const sessions = this.store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
1498
1598
|
const session = sessions?.sessions[input.agentId];
|
|
@@ -1505,14 +1605,14 @@ export class FileSchedulerStoreAdapter {
|
|
|
1505
1605
|
return "obsolete";
|
|
1506
1606
|
return "apply";
|
|
1507
1607
|
}
|
|
1508
|
-
|
|
1608
|
+
observeRuntimeTurnFailed(input, now = new Date()) {
|
|
1509
1609
|
return this.store.transaction((store) => {
|
|
1510
1610
|
const before = store.getAgentRun(input.taskId, input.runId);
|
|
1511
1611
|
if (before === null) {
|
|
1512
1612
|
recordObsoleteRuntimeEvent(store, input, "run-missing", now);
|
|
1513
1613
|
return { disposition: "obsolete", runId: input.runId };
|
|
1514
1614
|
}
|
|
1515
|
-
const summary =
|
|
1615
|
+
const summary = runtimeTurnFailureSummary(input);
|
|
1516
1616
|
// Issue 04: a classified transient Provider failure keeps the exact Run
|
|
1517
1617
|
// and Native Session and is retried in place instead of terminalizing.
|
|
1518
1618
|
const retryDecision = this.providerRetryDecision(store, input, summary, now);
|
|
@@ -1553,7 +1653,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1553
1653
|
if (role !== null) {
|
|
1554
1654
|
store.saveRole(input.taskId, updateRoleStatus(role, input.roleName === "leader" ? "failed" : "idle", now));
|
|
1555
1655
|
}
|
|
1556
|
-
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.
|
|
1656
|
+
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.turn-failed", {
|
|
1557
1657
|
eventId: input.eventId,
|
|
1558
1658
|
runId: input.runId,
|
|
1559
1659
|
roleName: input.roleName,
|
|
@@ -1790,206 +1890,119 @@ export class FileSchedulerStoreAdapter {
|
|
|
1790
1890
|
return reopened;
|
|
1791
1891
|
}
|
|
1792
1892
|
/**
|
|
1793
|
-
*
|
|
1794
|
-
*
|
|
1795
|
-
* acceptance; this only records generation facts and binds a discovered native
|
|
1796
|
-
* id under the exact launch fence. Idempotent replays are no-ops.
|
|
1893
|
+
* Applies already-normalized Session observations. Driver-specific startup
|
|
1894
|
+
* names and source variants have terminated before this boundary.
|
|
1797
1895
|
*/
|
|
1798
|
-
|
|
1896
|
+
observeRuntimeSession(input, adapterId, now) {
|
|
1799
1897
|
return this.store.transaction((store) => {
|
|
1800
|
-
const
|
|
1801
|
-
kind: "
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1898
|
+
const event = createCanonicalLifecycleEvent({
|
|
1899
|
+
phase: input.kind === "session.ready" ? "provider-ready" : "provider-session-started",
|
|
1900
|
+
source: "provider-native",
|
|
1901
|
+
evidence: "provider-native-durable",
|
|
1902
|
+
...(input.kind === "session.ready"
|
|
1903
|
+
? {
|
|
1904
|
+
preInputReady: true,
|
|
1905
|
+
readinessVariant: `${input.fence.driverId}:session.ready`
|
|
1906
|
+
}
|
|
1907
|
+
: {}),
|
|
1908
|
+
fence: runtimeObservationLifecycleFence(input, adapterId)
|
|
1909
|
+
});
|
|
1910
|
+
const decision = this.foldRuntimeLifecycleEvent(store, event, input);
|
|
1805
1911
|
switch (decision.kind) {
|
|
1806
1912
|
case "obsolete":
|
|
1807
|
-
|
|
1913
|
+
recordCanonicalObservationObsolete(store, input, decision.reason, now);
|
|
1808
1914
|
return "obsolete";
|
|
1809
1915
|
case "deferred":
|
|
1810
1916
|
return "deferred";
|
|
1811
1917
|
case "idempotent":
|
|
1812
1918
|
return "applied";
|
|
1813
|
-
case "apply":
|
|
1919
|
+
case "apply": {
|
|
1814
1920
|
if (decision.outcome.outcome === "mark-ready"
|
|
1815
|
-
&& input.
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
// before the Controller projects its preallocated Session. Keep
|
|
1821
|
-
// the immutable fact queued until that exact Session is durable.
|
|
1822
|
-
return "deferred";
|
|
1921
|
+
&& store.getTaskRoleSessionSet(input.fence.taskId, input.fence.roleName)
|
|
1922
|
+
?.sessions[input.fence.agentId] === undefined) {
|
|
1923
|
+
return preallocatedRuntimeReadyAwaitingProjection(store, input, this.drivers)
|
|
1924
|
+
? "applied"
|
|
1925
|
+
: "deferred";
|
|
1823
1926
|
}
|
|
1824
1927
|
if (decision.outcome.outcome === "bind-native-session") {
|
|
1825
|
-
const
|
|
1826
|
-
const
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
: store.getAgentRun(input.taskId, input.runId);
|
|
1928
|
+
const taskId = input.fence.taskId;
|
|
1929
|
+
const role = store.getRole(taskId, input.fence.roleName);
|
|
1930
|
+
const sessions = store.getTaskRoleSessionSet(taskId, input.fence.roleName);
|
|
1931
|
+
const run = store.getAgentRun(taskId, input.fence.runId);
|
|
1830
1932
|
if (role === null || sessions === null || run === null) {
|
|
1831
|
-
|
|
1933
|
+
recordCanonicalObservationObsolete(store, input, "bind-state-missing", now);
|
|
1832
1934
|
return "obsolete";
|
|
1833
1935
|
}
|
|
1834
1936
|
const bound = recordRoleAgentSession(sessions, {
|
|
1835
|
-
agentId: input.agentId,
|
|
1836
|
-
adapterId
|
|
1937
|
+
agentId: input.fence.agentId,
|
|
1938
|
+
adapterId,
|
|
1837
1939
|
nativeSessionId: decision.outcome.nativeSessionId,
|
|
1838
|
-
launchId: input.launchId,
|
|
1940
|
+
launchId: input.fence.launchId,
|
|
1839
1941
|
policy: "fixed",
|
|
1840
1942
|
status: "running",
|
|
1841
1943
|
effective: run.effective
|
|
1842
1944
|
}, now);
|
|
1843
1945
|
store.saveTaskRoleSessionSet(bound);
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
// Preserve that truth before clearing the launch reservation: a
|
|
1848
|
-
// restarted Controller must not allocate a second generation or
|
|
1849
|
-
// push the same first input again. UserPromptSubmit remains the
|
|
1850
|
-
// sole acceptance/delivered transition.
|
|
1851
|
-
if (input.adapterId === "codex" && run.pushedAt === undefined) {
|
|
1946
|
+
const driver = this.drivers.require(input.fence.driverId);
|
|
1947
|
+
if (driver.capabilities.observation.sessionBootstrap === "discovered"
|
|
1948
|
+
&& run.pushedAt === undefined) {
|
|
1852
1949
|
const pushed = markAgentRunPushed(run, now);
|
|
1853
1950
|
store.saveAgentRun(pushed);
|
|
1854
1951
|
markTaskRoleRunPushedInFlight(store, role, pushed, now);
|
|
1855
|
-
store.saveEvent(
|
|
1952
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "run.pushed", runLaunchEventPayload(pushed), now));
|
|
1856
1953
|
}
|
|
1857
|
-
completeRuntimeHookReservation(store, { scope: "task", taskId
|
|
1954
|
+
completeRuntimeHookReservation(store, { scope: "task", taskId, roleName: input.fence.roleName }, input.fence.launchId);
|
|
1858
1955
|
}
|
|
1859
|
-
// Session-started/ready/bind facts are recorded provider-neutrally as
|
|
1860
|
-
// an auditable event. The
|
|
1861
|
-
// payload carries the generation fence + preInputReady so the fresh
|
|
1862
|
-
// push gate can read the exact provider-ready fact for this generation.
|
|
1863
|
-
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-session-lifecycle", {
|
|
1864
|
-
eventId: input.eventId,
|
|
1865
|
-
roleName: input.roleName,
|
|
1866
|
-
agentId: input.agentId,
|
|
1867
|
-
adapterId: input.adapterId,
|
|
1868
|
-
launchId: input.launchId,
|
|
1869
|
-
nativeSessionId: input.nativeSessionId,
|
|
1870
|
-
outcome: decision.outcome.outcome,
|
|
1871
|
-
preInputReady: (decision.outcome.outcome === "mark-ready"
|
|
1872
|
-
&& decision.outcome.preInputReady) ? "true" : "false",
|
|
1873
|
-
...(input.runId === undefined ? {} : { runId: input.runId })
|
|
1874
|
-
}, now));
|
|
1875
1956
|
return "applied";
|
|
1957
|
+
}
|
|
1876
1958
|
}
|
|
1877
1959
|
});
|
|
1878
1960
|
}
|
|
1879
|
-
/**
|
|
1880
|
-
|
|
1881
|
-
* that may advance a Run to accepted/delivered, and only after an exact
|
|
1882
|
-
* transport receipt has independently committed run.pushed.
|
|
1883
|
-
*/
|
|
1884
|
-
observeProviderPromptAccepted(input, now = new Date()) {
|
|
1961
|
+
/** Provider acceptance is canonical before storage and remains receipt-fenced. */
|
|
1962
|
+
observeRuntimePromptAccepted(input, adapterId, now) {
|
|
1885
1963
|
return this.store.transaction((store) => {
|
|
1886
|
-
const
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1964
|
+
const event = createCanonicalLifecycleEvent({
|
|
1965
|
+
phase: "provider-accepted",
|
|
1966
|
+
source: "provider-native",
|
|
1967
|
+
evidence: "provider-native-durable",
|
|
1968
|
+
fence: runtimeObservationLifecycleFence(input, adapterId)
|
|
1969
|
+
});
|
|
1970
|
+
const decision = this.foldRuntimeLifecycleEvent(store, event, input);
|
|
1890
1971
|
if (decision.kind === "obsolete") {
|
|
1891
|
-
|
|
1972
|
+
recordCanonicalObservationObsolete(store, input, decision.reason, now);
|
|
1892
1973
|
return "obsolete";
|
|
1893
1974
|
}
|
|
1894
1975
|
if (decision.kind === "deferred")
|
|
1895
1976
|
return "deferred";
|
|
1896
1977
|
if (decision.kind === "idempotent")
|
|
1897
1978
|
return "applied";
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1979
|
+
const active = store.getActiveAgentRun(input.fence.taskId, input.fence.roleName);
|
|
1980
|
+
if (active === null
|
|
1981
|
+
|| active.id !== input.fence.runId
|
|
1982
|
+
|| active.status !== "active") {
|
|
1983
|
+
recordCanonicalObservationObsolete(store, input, "run-not-active", now);
|
|
1902
1984
|
return "obsolete";
|
|
1903
1985
|
}
|
|
1904
|
-
const role = store.getRole(input.taskId, input.roleName);
|
|
1986
|
+
const role = store.getRole(input.fence.taskId, input.fence.roleName);
|
|
1905
1987
|
if (role === null) {
|
|
1906
|
-
|
|
1988
|
+
recordCanonicalObservationObsolete(store, input, "role-missing", now);
|
|
1907
1989
|
return "obsolete";
|
|
1908
1990
|
}
|
|
1909
1991
|
if (active.deliveredAt === undefined) {
|
|
1910
1992
|
const delivered = markAgentRunDelivered(active, now);
|
|
1911
1993
|
store.saveAgentRun(delivered);
|
|
1912
1994
|
markTaskRoleRunDeliveredInFlight(store, role, delivered, now);
|
|
1913
|
-
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "run.delivered", runLaunchEventPayload(delivered), now));
|
|
1995
|
+
store.saveEvent(input.fence.taskId, createTaskEvent(store.nextEventId(input.fence.taskId), input.fence.taskId, "run.delivered", runLaunchEventPayload(delivered), now));
|
|
1914
1996
|
}
|
|
1915
1997
|
return "applied";
|
|
1916
1998
|
});
|
|
1917
1999
|
}
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
* contract. Progress is an advisory semantic fence only: it never advances
|
|
1921
|
-
* Run delivery/acceptance and never reads pane text or process counters.
|
|
1922
|
-
*/
|
|
1923
|
-
observeProviderTurnProgress(input, now = new Date()) {
|
|
1924
|
-
return this.store.transaction((store) => {
|
|
1925
|
-
const receivedAtMs = Date.parse(input.receivedAt);
|
|
1926
|
-
if (!Number.isFinite(receivedAtMs) || receivedAtMs > now.getTime()) {
|
|
1927
|
-
recordCanonicalObsolete(store, input, "native-turn-progress", "received-at-invalid", now);
|
|
1928
|
-
return "obsolete";
|
|
1929
|
-
}
|
|
1930
|
-
const decision = this.foldProviderSignal(store, {
|
|
1931
|
-
kind: "native-turn-progress",
|
|
1932
|
-
...(input.sequence === undefined ? {} : { sequence: input.sequence }),
|
|
1933
|
-
fence: providerFence(input)
|
|
1934
|
-
}, input.runId);
|
|
1935
|
-
switch (decision.kind) {
|
|
1936
|
-
case "obsolete":
|
|
1937
|
-
recordCanonicalObsolete(store, input, "native-turn-progress", decision.reason, now);
|
|
1938
|
-
return "obsolete";
|
|
1939
|
-
case "deferred":
|
|
1940
|
-
return "deferred";
|
|
1941
|
-
case "idempotent":
|
|
1942
|
-
return "applied";
|
|
1943
|
-
case "apply":
|
|
1944
|
-
if (decision.outcome.outcome !== "advance-progress")
|
|
1945
|
-
return "obsolete";
|
|
1946
|
-
return routeProviderProgress({
|
|
1947
|
-
mode: this.telemetry?.mode ?? "legacy",
|
|
1948
|
-
entry: toTelemetryEntry(input),
|
|
1949
|
-
semanticExists: this.telemetry?.mode === "bounded"
|
|
1950
|
-
? false
|
|
1951
|
-
: store.listEvents(input.taskId).some((event) => (event.type === "runtime.provider-turn-progress"
|
|
1952
|
-
&& event.payload.eventId === input.eventId)),
|
|
1953
|
-
writeSemantic: () => {
|
|
1954
|
-
store.saveEvent(input.taskId, createTaskEvent(store.nextEventId(input.taskId), input.taskId, "runtime.provider-turn-progress", {
|
|
1955
|
-
eventId: input.eventId,
|
|
1956
|
-
roleName: input.roleName,
|
|
1957
|
-
agentId: input.agentId,
|
|
1958
|
-
adapterId: input.adapterId,
|
|
1959
|
-
launchId: input.launchId,
|
|
1960
|
-
nativeSessionId: input.nativeSessionId,
|
|
1961
|
-
runId: input.runId,
|
|
1962
|
-
progressId: input.progressId,
|
|
1963
|
-
progressAt: input.receivedAt,
|
|
1964
|
-
...(input.sequence === undefined ? {} : { sequence: String(input.sequence) })
|
|
1965
|
-
}, now));
|
|
1966
|
-
},
|
|
1967
|
-
sink: this.telemetry?.sink ?? NULL_TELEMETRY_SINK
|
|
1968
|
-
});
|
|
1969
|
-
}
|
|
1970
|
-
});
|
|
1971
|
-
}
|
|
1972
|
-
/**
|
|
1973
|
-
* Shared projection + canonical fold for provider-native lifecycle signals.
|
|
1974
|
-
* Reads current durable Run/session state into a CanonicalRunExpectation,
|
|
1975
|
-
* maps the neutral signal through the per-adapter registry, and folds. The
|
|
1976
|
-
* fold owns every ordering/identity/evidence decision; this method only reads
|
|
1977
|
-
* durable truth and never branches on the provider name.
|
|
1978
|
-
*/
|
|
1979
|
-
foldProviderSignal(store, signal, runId) {
|
|
1980
|
-
let event;
|
|
1981
|
-
try {
|
|
1982
|
-
event = mapNativeLifecycleSignal(signal);
|
|
1983
|
-
}
|
|
1984
|
-
catch (error) {
|
|
1985
|
-
return { kind: "obsolete", reason: error instanceof Error ? error.message : "map-failed" };
|
|
1986
|
-
}
|
|
1987
|
-
const expectation = this.projectRunExpectation(store, signal.fence, runId);
|
|
2000
|
+
foldRuntimeLifecycleEvent(store, event, observation) {
|
|
2001
|
+
const expectation = this.projectRunExpectation(store, event.fence, observation.fence.runId);
|
|
1988
2002
|
if (expectation === null) {
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
return { kind: "obsolete", reason: "run-or-role-missing" };
|
|
2003
|
+
return preallocatedRuntimeReadyAwaitingProjection(store, observation, this.drivers)
|
|
2004
|
+
? { kind: "apply", outcome: { outcome: "mark-ready", preInputReady: true } }
|
|
2005
|
+
: { kind: "obsolete", reason: "run-or-role-missing" };
|
|
1993
2006
|
}
|
|
1994
2007
|
const outcome = foldCanonicalLifecycleEvent(event, expectation);
|
|
1995
2008
|
switch (outcome.outcome) {
|
|
@@ -2047,16 +2060,23 @@ export class FileSchedulerStoreAdapter {
|
|
|
2047
2060
|
receiptId: inFlight.receiptId,
|
|
2048
2061
|
...(boundNativeSessionId === undefined ? {} : { nativeSessionId: boundNativeSessionId })
|
|
2049
2062
|
};
|
|
2050
|
-
const
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2063
|
+
const driverId = this.drivers.requireByAdapterId(run.effective.adapterId).id;
|
|
2064
|
+
const lifecycleEvents = store.listEvents(fence.taskId).flatMap((event) => {
|
|
2065
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
2066
|
+
return observation !== null
|
|
2067
|
+
&& (observation.kind === "session.started" || observation.kind === "session.ready")
|
|
2068
|
+
&& observation.fence.roleName === fence.roleName
|
|
2069
|
+
&& observation.fence.agentId === run.effective.agentId
|
|
2070
|
+
&& observation.fence.driverId === driverId
|
|
2071
|
+
&& observation.fence.launchId === launchId
|
|
2072
|
+
&& observation.fence.nativeSessionId === (boundNativeSessionId ?? fence.nativeSessionId)
|
|
2073
|
+
? [observation]
|
|
2074
|
+
: [];
|
|
2075
|
+
});
|
|
2056
2076
|
return {
|
|
2057
2077
|
fence: expectedFence,
|
|
2058
2078
|
sessionStarted: lifecycleEvents.length > 0,
|
|
2059
|
-
ready: lifecycleEvents.some((event) => event.
|
|
2079
|
+
ready: lifecycleEvents.some((event) => event.kind === "session.ready"),
|
|
2060
2080
|
pushed: run.pushedAt !== undefined,
|
|
2061
2081
|
accepted: run.deliveredAt !== undefined,
|
|
2062
2082
|
terminal: run.status !== "active",
|
|
@@ -2142,9 +2162,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
2142
2162
|
return "apply";
|
|
2143
2163
|
}
|
|
2144
2164
|
}
|
|
2145
|
-
function
|
|
2165
|
+
function runtimeTurnFailureSummary(input) {
|
|
2146
2166
|
return [
|
|
2147
|
-
"
|
|
2167
|
+
"Agent runtime turn failed.",
|
|
2148
2168
|
`error: ${input.error}`,
|
|
2149
2169
|
...(input.errorDetails === undefined
|
|
2150
2170
|
? []
|
|
@@ -2154,20 +2174,34 @@ function claudeStopFailureSummary(input) {
|
|
|
2154
2174
|
: [`last_assistant_message: ${input.lastAssistantMessage}`])
|
|
2155
2175
|
].join("\n");
|
|
2156
2176
|
}
|
|
2157
|
-
function
|
|
2177
|
+
function runtimeObservationLifecycleFence(input, adapterId) {
|
|
2158
2178
|
return {
|
|
2159
|
-
taskId: input.taskId,
|
|
2160
|
-
roleName: input.roleName,
|
|
2161
|
-
agentId: input.agentId,
|
|
2162
|
-
adapterId
|
|
2163
|
-
launchId: input.launchId,
|
|
2164
|
-
|
|
2165
|
-
...(input.
|
|
2166
|
-
|
|
2179
|
+
taskId: input.fence.taskId,
|
|
2180
|
+
roleName: input.fence.roleName,
|
|
2181
|
+
agentId: input.fence.agentId,
|
|
2182
|
+
adapterId,
|
|
2183
|
+
launchId: input.fence.launchId,
|
|
2184
|
+
...(input.fence.runId === undefined ? {} : { runId: input.fence.runId }),
|
|
2185
|
+
...(input.fence.nativeSessionId === undefined
|
|
2186
|
+
? {}
|
|
2187
|
+
: { nativeSessionId: input.fence.nativeSessionId }),
|
|
2188
|
+
...(input.fence.receiptId === undefined ? {} : { receiptId: input.fence.receiptId })
|
|
2167
2189
|
};
|
|
2168
2190
|
}
|
|
2169
|
-
function
|
|
2170
|
-
|
|
2191
|
+
function recordCanonicalObservationObsolete(store, input, reason, now) {
|
|
2192
|
+
const taskId = input.fence.taskId;
|
|
2193
|
+
if (taskId === undefined)
|
|
2194
|
+
return;
|
|
2195
|
+
recordObsoleteRuntimeEvent(store, {
|
|
2196
|
+
eventId: input.eventId,
|
|
2197
|
+
eventType: input.kind,
|
|
2198
|
+
taskId,
|
|
2199
|
+
roleName: input.fence.roleName,
|
|
2200
|
+
agentId: input.fence.agentId,
|
|
2201
|
+
...(input.fence.runId === undefined ? {} : { runId: input.fence.runId }),
|
|
2202
|
+
launchId: input.fence.launchId,
|
|
2203
|
+
nativeSessionId: input.fence.nativeSessionId ?? input.fence.sessionGenerationId
|
|
2204
|
+
}, reason, now);
|
|
2171
2205
|
}
|
|
2172
2206
|
function recordObsoleteRuntimeEvent(store, input, reason, now) {
|
|
2173
2207
|
if (store.listEvents(input.taskId).some((event) => (event.type === "runtime.event-obsolete"
|
|
@@ -2219,10 +2253,6 @@ function sessionPreview(value) {
|
|
|
2219
2253
|
? truncated.slice(0, -1)
|
|
2220
2254
|
: truncated;
|
|
2221
2255
|
}
|
|
2222
|
-
function boundedRuntimeNote(value) {
|
|
2223
|
-
const normalized = value.trim().replaceAll(/\s+/gu, " ");
|
|
2224
|
-
return normalized.length <= 280 ? normalized : `${normalized.slice(0, 279)}…`;
|
|
2225
|
-
}
|
|
2226
2256
|
function requireRuntimeLaunchReservation(store, owner, launchId) {
|
|
2227
2257
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget(owner));
|
|
2228
2258
|
if (!isRuntimeLaunchReservation(mailbox?.processing, launchId)) {
|
|
@@ -2238,50 +2268,43 @@ function runtimeHookMatchesReservation(store, owner, launchId) {
|
|
|
2238
2268
|
return launchId !== undefined
|
|
2239
2269
|
&& isRuntimeLaunchReservation(processing, launchId);
|
|
2240
2270
|
}
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
if (signal.kind !== "native-session-start"
|
|
2250
|
-
|| signal.sessionSource !== "startup"
|
|
2251
|
-
|| signal.fence.adapterId !== "claude"
|
|
2271
|
+
function preallocatedRuntimeReadyAwaitingProjection(store, observation, drivers) {
|
|
2272
|
+
const taskId = observation.fence.taskId;
|
|
2273
|
+
const runId = observation.fence.runId;
|
|
2274
|
+
const nativeSessionId = observation.fence.nativeSessionId;
|
|
2275
|
+
const driver = drivers.find(observation.fence.driverId);
|
|
2276
|
+
if (observation.kind !== "session.ready"
|
|
2277
|
+
|| driver?.capabilities.observation.sessionBootstrap !== "preallocated"
|
|
2278
|
+
|| taskId === undefined
|
|
2252
2279
|
|| runId === undefined
|
|
2253
|
-
||
|
|
2254
|
-
|| signal.fence.launchId === undefined
|
|
2255
|
-
|| signal.fence.nativeSessionId === undefined)
|
|
2280
|
+
|| nativeSessionId === undefined)
|
|
2256
2281
|
return false;
|
|
2257
|
-
const task = store.getTask(
|
|
2258
|
-
const role = store.getRole(
|
|
2259
|
-
const run = store.getActiveAgentRun(
|
|
2260
|
-
const sessions = store.getTaskRoleSessionSet(
|
|
2261
|
-
if (task
|
|
2262
|
-
||
|
|
2263
|
-
||
|
|
2264
|
-
|| role.activeAgentId !== signal.fence.agentId
|
|
2265
|
-
|| run === null
|
|
2266
|
-
|| run.id !== runId
|
|
2282
|
+
const task = store.getTask(taskId);
|
|
2283
|
+
const role = store.getRole(taskId, observation.fence.roleName);
|
|
2284
|
+
const run = store.getActiveAgentRun(taskId, observation.fence.roleName);
|
|
2285
|
+
const sessions = store.getTaskRoleSessionSet(taskId, observation.fence.roleName);
|
|
2286
|
+
if (task?.status !== "active"
|
|
2287
|
+
|| role?.activeAgentId !== observation.fence.agentId
|
|
2288
|
+
|| run?.id !== runId
|
|
2267
2289
|
|| run.status !== "active"
|
|
2268
|
-
|| run.effective.agentId !==
|
|
2269
|
-
|| run.effective.adapterId !==
|
|
2290
|
+
|| run.effective.agentId !== observation.fence.agentId
|
|
2291
|
+
|| run.effective.adapterId !== driver.adapterId
|
|
2292
|
+
|| observation.fence.receiptId !== formatAgentRunReceiptId(taskId, runId)
|
|
2270
2293
|
|| (sessions?.inFlight !== null && sessions?.inFlight !== undefined)
|
|
2271
|
-
|| sessions?.sessions[
|
|
2294
|
+
|| sessions?.sessions[observation.fence.agentId] !== undefined)
|
|
2272
2295
|
return false;
|
|
2273
2296
|
const mailbox = store.getWorkMailbox(runtimeLifecycleTarget({
|
|
2274
2297
|
scope: "task",
|
|
2275
|
-
taskId
|
|
2276
|
-
roleName:
|
|
2298
|
+
taskId,
|
|
2299
|
+
roleName: observation.fence.roleName
|
|
2277
2300
|
}));
|
|
2278
2301
|
const executionRef = mailbox?.processing?.executionRef;
|
|
2279
|
-
return isRuntimeLaunchReservation(mailbox?.processing,
|
|
2302
|
+
return isRuntimeLaunchReservation(mailbox?.processing, observation.fence.launchId)
|
|
2280
2303
|
&& !hasRuntimeCleanupObligation(mailbox)
|
|
2281
2304
|
&& executionRef?.type === "run"
|
|
2282
|
-
&& executionRef.taskId ===
|
|
2305
|
+
&& executionRef.taskId === taskId
|
|
2283
2306
|
&& executionRef.id === runId
|
|
2284
|
-
&&
|
|
2307
|
+
&& nativeSessionId === nativeSessionIdForLaunch(store.rootDirectory(), observation.fence.launchId, observation.fence.agentId, driver.adapterId);
|
|
2285
2308
|
}
|
|
2286
2309
|
function completeRuntimeHookReservation(store, owner, launchId) {
|
|
2287
2310
|
if (launchId === undefined)
|
|
@@ -2734,31 +2757,142 @@ function runLaunchEventPayload(run) {
|
|
|
2734
2757
|
writeProjectIds: run.effective.writeProjectIds.join(",") || "none"
|
|
2735
2758
|
};
|
|
2736
2759
|
}
|
|
2737
|
-
|
|
2738
|
-
* Maps a provider progress observation to the telemetry sidecar entry. The
|
|
2739
|
-
* payload mirrors the semantic event payload so sidecar rows and legacy
|
|
2740
|
-
* events carry identical evidence.
|
|
2741
|
-
*/
|
|
2742
|
-
function toTelemetryEntry(input) {
|
|
2760
|
+
function runtimeObservationTelemetryEntry(input) {
|
|
2743
2761
|
return {
|
|
2744
|
-
taskId: input.taskId,
|
|
2745
|
-
roleName: input.roleName,
|
|
2746
|
-
runId: input.runId,
|
|
2747
|
-
generation: input.
|
|
2748
|
-
progressId:
|
|
2762
|
+
taskId: input.fence.taskId,
|
|
2763
|
+
roleName: input.fence.roleName,
|
|
2764
|
+
runId: input.fence.runId,
|
|
2765
|
+
generation: input.fence.sessionGenerationId,
|
|
2766
|
+
progressId: [
|
|
2767
|
+
input.kind,
|
|
2768
|
+
input.payload.operationId ?? input.payload.activity ?? "state"
|
|
2769
|
+
].join(":"),
|
|
2749
2770
|
...(input.sequence === undefined ? {} : { sequence: input.sequence }),
|
|
2750
2771
|
payload: {
|
|
2751
2772
|
eventId: input.eventId,
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
launchId: input.launchId,
|
|
2756
|
-
nativeSessionId: input.nativeSessionId,
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2773
|
+
kind: input.kind,
|
|
2774
|
+
authority: input.authority,
|
|
2775
|
+
driverId: input.fence.driverId,
|
|
2776
|
+
launchId: input.fence.launchId,
|
|
2777
|
+
nativeSessionId: input.fence.nativeSessionId ?? "",
|
|
2778
|
+
nativeTurnId: input.fence.nativeTurnId ?? "",
|
|
2779
|
+
...(input.payload.operation === undefined
|
|
2780
|
+
? {}
|
|
2781
|
+
: { operation: input.payload.operation }),
|
|
2782
|
+
...(input.payload.activity === undefined
|
|
2783
|
+
? {}
|
|
2784
|
+
: { activity: input.payload.activity }),
|
|
2785
|
+
...(input.payload.usage === undefined
|
|
2786
|
+
? {}
|
|
2787
|
+
: { usage: JSON.stringify(input.payload.usage) })
|
|
2761
2788
|
},
|
|
2762
2789
|
receivedAt: input.receivedAt
|
|
2763
2790
|
};
|
|
2764
2791
|
}
|
|
2792
|
+
/**
|
|
2793
|
+
* Runtime observations are a current-state snapshot, not an append-only
|
|
2794
|
+
* semantic history. Retain active operations, one latest usage baseline, and
|
|
2795
|
+
* one latest confirmed activity boundary so a token stream remains
|
|
2796
|
+
* O(active operations) per Run without losing the delta that was already
|
|
2797
|
+
* proven before an unchanged later snapshot arrived.
|
|
2798
|
+
*/
|
|
2799
|
+
function compactedRuntimeObservationIds(events, incoming) {
|
|
2800
|
+
const existing = events.flatMap((event) => {
|
|
2801
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
2802
|
+
return observation !== null
|
|
2803
|
+
&& runtimeObservationFenceMatches(observation.fence, incoming.fence)
|
|
2804
|
+
? [{ event, observation }]
|
|
2805
|
+
: [];
|
|
2806
|
+
});
|
|
2807
|
+
const remove = ({ observation }) => {
|
|
2808
|
+
if (incoming.kind === "activity.observed") {
|
|
2809
|
+
return observation.kind === "activity.observed"
|
|
2810
|
+
&& (observation.payload.usage === undefined)
|
|
2811
|
+
=== (incoming.payload.usage === undefined);
|
|
2812
|
+
}
|
|
2813
|
+
if (incoming.kind === "operation.started") {
|
|
2814
|
+
return (observation.kind === "operation.started"
|
|
2815
|
+
&& observation.payload.operationId === incoming.payload.operationId)
|
|
2816
|
+
|| observation.kind === "operation.completed"
|
|
2817
|
+
|| observation.kind === "operation.failed";
|
|
2818
|
+
}
|
|
2819
|
+
if (incoming.kind === "operation.completed" || incoming.kind === "operation.failed") {
|
|
2820
|
+
return (observation.kind.startsWith("operation.")
|
|
2821
|
+
&& observation.payload.operationId === incoming.payload.operationId)
|
|
2822
|
+
|| observation.kind === "operation.completed"
|
|
2823
|
+
|| observation.kind === "operation.failed";
|
|
2824
|
+
}
|
|
2825
|
+
if (incoming.kind === "turn.waiting")
|
|
2826
|
+
return observation.kind === "turn.waiting";
|
|
2827
|
+
if (incoming.kind === "observer.health") {
|
|
2828
|
+
return observation.kind === "observer.health"
|
|
2829
|
+
&& observation.payload.sourceId === incoming.payload.sourceId;
|
|
2830
|
+
}
|
|
2831
|
+
if (["turn.completed", "turn.failed", "turn.cancelled"].includes(incoming.kind)) {
|
|
2832
|
+
return observation.kind.startsWith("operation.")
|
|
2833
|
+
|| observation.kind === "turn.waiting"
|
|
2834
|
+
|| observation.kind === "turn.completed"
|
|
2835
|
+
|| observation.kind === "turn.failed"
|
|
2836
|
+
|| observation.kind === "turn.cancelled";
|
|
2837
|
+
}
|
|
2838
|
+
if (incoming.kind === "turn.accepted")
|
|
2839
|
+
return observation.kind === "turn.accepted";
|
|
2840
|
+
if (incoming.kind.startsWith("session."))
|
|
2841
|
+
return observation.kind.startsWith("session.");
|
|
2842
|
+
return false;
|
|
2843
|
+
};
|
|
2844
|
+
return existing.filter(remove).map(({ event }) => event.id);
|
|
2845
|
+
}
|
|
2846
|
+
function confirmedUsageActivityObservation(events, incoming) {
|
|
2847
|
+
const usage = incoming.payload.usage;
|
|
2848
|
+
if (incoming.kind !== "activity.observed" || usage === undefined)
|
|
2849
|
+
return null;
|
|
2850
|
+
const previous = events
|
|
2851
|
+
.map(runtimeObservationFromTaskEvent)
|
|
2852
|
+
.filter((observation) => (observation !== null
|
|
2853
|
+
&& observation.kind === "activity.observed"
|
|
2854
|
+
&& observation.payload.usage !== undefined
|
|
2855
|
+
&& runtimeObservationFenceMatches(observation.fence, incoming.fence)))
|
|
2856
|
+
.sort(compareCanonicalObservationOrder)
|
|
2857
|
+
.at(-1)?.payload.usage;
|
|
2858
|
+
if (previous === undefined
|
|
2859
|
+
|| usage.inputTokens + usage.outputTokens
|
|
2860
|
+
<= previous.inputTokens + previous.outputTokens)
|
|
2861
|
+
return null;
|
|
2862
|
+
const digest = createHash("sha256")
|
|
2863
|
+
.update(`confirmed-runtime-activity\0${incoming.eventId}`)
|
|
2864
|
+
.digest("hex");
|
|
2865
|
+
return createRuntimeObservation({
|
|
2866
|
+
schemaVersion: 1,
|
|
2867
|
+
eventId: `runtime-activity-${digest}`,
|
|
2868
|
+
kind: "activity.observed",
|
|
2869
|
+
authority: "controller",
|
|
2870
|
+
receivedAt: incoming.receivedAt,
|
|
2871
|
+
...(incoming.observedAt === undefined ? {} : { observedAt: incoming.observedAt }),
|
|
2872
|
+
...(incoming.sequence === undefined ? {} : { sequence: incoming.sequence }),
|
|
2873
|
+
...(incoming.ordinal === undefined ? {} : { ordinal: incoming.ordinal }),
|
|
2874
|
+
fence: incoming.fence,
|
|
2875
|
+
payload: { activity: incoming.payload.activity ?? "model" }
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
function usageSnapshotIsSuperseded(events, incoming) {
|
|
2879
|
+
if (incoming.kind !== "activity.observed" || incoming.payload.usage === undefined) {
|
|
2880
|
+
return false;
|
|
2881
|
+
}
|
|
2882
|
+
return events
|
|
2883
|
+
.map(runtimeObservationFromTaskEvent)
|
|
2884
|
+
.some((observation) => observation !== null
|
|
2885
|
+
&& observation.kind === "activity.observed"
|
|
2886
|
+
&& observation.payload.usage !== undefined
|
|
2887
|
+
&& runtimeObservationFenceMatches(observation.fence, incoming.fence)
|
|
2888
|
+
&& observationIsStrictlyNewer(observation, incoming));
|
|
2889
|
+
}
|
|
2890
|
+
function observationIsStrictlyNewer(left, right) {
|
|
2891
|
+
return compareCanonicalObservationOrder(left, right) > 0;
|
|
2892
|
+
}
|
|
2893
|
+
function compareCanonicalObservationOrder(left, right) {
|
|
2894
|
+
return left.receivedAt.localeCompare(right.receivedAt)
|
|
2895
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
2896
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
2897
|
+
|| left.eventId.localeCompare(right.eventId);
|
|
2898
|
+
}
|