@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,13 +1,16 @@
|
|
|
1
|
+
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
1
2
|
const DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN = 64;
|
|
2
3
|
/** Folds immutable Hook facts in one bounded transaction before acknowledging them. */
|
|
3
4
|
export class FileRuntimeEventProcessor {
|
|
4
5
|
inbox;
|
|
5
6
|
observer;
|
|
6
7
|
options;
|
|
8
|
+
drivers;
|
|
7
9
|
constructor(inbox, observer, options = {}) {
|
|
8
10
|
this.inbox = inbox;
|
|
9
11
|
this.observer = observer;
|
|
10
12
|
this.options = options;
|
|
13
|
+
this.drivers = options.drivers ?? builtinAgentDriverRegistry();
|
|
11
14
|
}
|
|
12
15
|
drain(now) {
|
|
13
16
|
const acknowledgedEventIds = [];
|
|
@@ -58,11 +61,11 @@ export class FileRuntimeEventProcessor {
|
|
|
58
61
|
}
|
|
59
62
|
}
|
|
60
63
|
}
|
|
61
|
-
const progressEventsSelected = selected.filter(({ event }) => (event
|
|
64
|
+
const progressEventsSelected = selected.filter(({ event }) => (isRuntimeActivityEvent(event))).length;
|
|
62
65
|
const representedEventCount = selected.reduce((count, candidate) => (count + candidate.representedEventIds.length), 0);
|
|
63
66
|
const acknowledged = new Set(acknowledgedEventIds);
|
|
64
67
|
const remaining = events.filter(({ id }) => !acknowledged.has(id));
|
|
65
|
-
const remainingProgressEventCount = remaining.filter(
|
|
68
|
+
const remainingProgressEventCount = remaining.filter(isRuntimeActivityEvent).length;
|
|
66
69
|
return {
|
|
67
70
|
acknowledgedEventIds,
|
|
68
71
|
deferred,
|
|
@@ -86,33 +89,34 @@ export class FileRuntimeEventProcessor {
|
|
|
86
89
|
if (event.type === "native-turn-completed") {
|
|
87
90
|
outcome = this.applyCodex(event, now);
|
|
88
91
|
}
|
|
89
|
-
else if (event.type === "
|
|
90
|
-
this.
|
|
92
|
+
else if (event.type === "runtime-observation") {
|
|
93
|
+
outcome = this.applyRuntimeObservation(event, now);
|
|
91
94
|
}
|
|
92
|
-
else
|
|
93
|
-
outcome = this.applySessionLifecycle(event, now);
|
|
94
|
-
}
|
|
95
|
-
else if (event.type === "native-turn-progress") {
|
|
96
|
-
outcome = this.applyProviderTurnProgress(event, now);
|
|
97
|
-
}
|
|
98
|
-
else if (event.type === "durable-job-terminal") {
|
|
95
|
+
else {
|
|
99
96
|
// f7/rr5: The supervisor already transitioned the job and
|
|
100
97
|
// enqueued the Leader wakeup. This event is the durable terminal
|
|
101
98
|
// channel: acknowledge it so the Controller's event pipeline
|
|
102
99
|
// converges without re-processing. No state change to apply.
|
|
103
100
|
this.applyDurableJobTerminal(event);
|
|
104
101
|
}
|
|
105
|
-
else {
|
|
106
|
-
outcome = this.applyPromptAccepted(event, now);
|
|
107
|
-
}
|
|
108
102
|
return {
|
|
109
103
|
candidate,
|
|
110
104
|
outcome,
|
|
111
|
-
notifyTaskRuntime: outcome === "applied" && (event.type === "
|
|
112
|
-
|
|
105
|
+
notifyTaskRuntime: outcome === "applied" && ((event.type === "runtime-observation" && event.scope === "task"
|
|
106
|
+
&& ["session.started", "session.ready", "turn.accepted", "turn.completed"]
|
|
107
|
+
.includes(event.observation.kind))
|
|
113
108
|
|| (event.type === "native-turn-completed" && event.scope === "task"))
|
|
114
109
|
};
|
|
115
110
|
}
|
|
111
|
+
applyRuntimeObservation(event, now) {
|
|
112
|
+
const taskId = event.observation.fence.taskId;
|
|
113
|
+
if (taskId !== undefined) {
|
|
114
|
+
const task = this.observer.getTask(taskId);
|
|
115
|
+
if (task === null || task.status !== "active")
|
|
116
|
+
return "obsolete";
|
|
117
|
+
}
|
|
118
|
+
return this.observer.observeRuntimeObservation?.(event.observation, now) ?? "obsolete";
|
|
119
|
+
}
|
|
116
120
|
finalizeOne(folded, acknowledged, deferred, failed) {
|
|
117
121
|
const { candidate, outcome } = folded;
|
|
118
122
|
try {
|
|
@@ -123,16 +127,34 @@ export class FileRuntimeEventProcessor {
|
|
|
123
127
|
}
|
|
124
128
|
if (folded.notifyTaskRuntime) {
|
|
125
129
|
const event = candidate.event;
|
|
126
|
-
if (event.scope === "task"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
130
|
+
if (event.scope === "task"
|
|
131
|
+
&& event.taskId !== undefined
|
|
132
|
+
&& event.type !== "durable-job-terminal") {
|
|
133
|
+
if (event.type === "runtime-observation") {
|
|
134
|
+
const fence = event.observation.fence;
|
|
135
|
+
if (fence.taskId !== undefined && fence.nativeSessionId !== undefined) {
|
|
136
|
+
this.options.onTaskRuntimeApplied?.({
|
|
137
|
+
taskId: fence.taskId,
|
|
138
|
+
roleName: fence.roleName,
|
|
139
|
+
agentId: fence.agentId,
|
|
140
|
+
adapterId: this.drivers.require(fence.driverId).adapterId,
|
|
141
|
+
launchId: fence.launchId,
|
|
142
|
+
nativeSessionId: fence.nativeSessionId,
|
|
143
|
+
...(fence.runId === undefined ? {} : { runId: fence.runId })
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else if (event.type === "native-turn-completed") {
|
|
148
|
+
this.options.onTaskRuntimeApplied?.({
|
|
149
|
+
taskId: event.taskId,
|
|
150
|
+
roleName: event.roleName,
|
|
151
|
+
agentId: event.agentId,
|
|
152
|
+
adapterId: event.adapterId,
|
|
153
|
+
...(event.launchId === undefined ? {} : { launchId: event.launchId }),
|
|
154
|
+
nativeSessionId: event.nativeSessionId,
|
|
155
|
+
...(event.runId === undefined ? {} : { runId: event.runId })
|
|
156
|
+
});
|
|
157
|
+
}
|
|
136
158
|
}
|
|
137
159
|
}
|
|
138
160
|
this.acknowledge(candidate.representedEventIds, acknowledged);
|
|
@@ -141,44 +163,6 @@ export class FileRuntimeEventProcessor {
|
|
|
141
163
|
failed.push({ eventId: candidate.event.id, error });
|
|
142
164
|
}
|
|
143
165
|
}
|
|
144
|
-
applySessionLifecycle(event, now) {
|
|
145
|
-
const task = this.observer.getTask(event.taskId);
|
|
146
|
-
if (task === null || task.status !== "active")
|
|
147
|
-
return "obsolete";
|
|
148
|
-
if (this.observer.observeProviderSessionLifecycle === undefined)
|
|
149
|
-
return "obsolete";
|
|
150
|
-
const outcome = this.observer.observeProviderSessionLifecycle({
|
|
151
|
-
eventId: event.id,
|
|
152
|
-
taskId: event.taskId,
|
|
153
|
-
roleName: event.roleName,
|
|
154
|
-
agentId: event.agentId,
|
|
155
|
-
adapterId: event.adapterId,
|
|
156
|
-
launchId: event.launchId,
|
|
157
|
-
nativeSessionId: event.nativeSessionId,
|
|
158
|
-
...(event.runId === undefined ? {} : { runId: event.runId }),
|
|
159
|
-
...(event.sessionSource === undefined ? {} : { sessionSource: event.sessionSource })
|
|
160
|
-
}, now);
|
|
161
|
-
return outcome === "deferred" ? "deferred" : outcome;
|
|
162
|
-
}
|
|
163
|
-
applyPromptAccepted(event, now) {
|
|
164
|
-
const task = this.observer.getTask(event.taskId);
|
|
165
|
-
if (task === null || task.status !== "active")
|
|
166
|
-
return "obsolete";
|
|
167
|
-
if (this.observer.observeProviderPromptAccepted === undefined)
|
|
168
|
-
return "obsolete";
|
|
169
|
-
const outcome = this.observer.observeProviderPromptAccepted({
|
|
170
|
-
eventId: event.id,
|
|
171
|
-
taskId: event.taskId,
|
|
172
|
-
roleName: event.roleName,
|
|
173
|
-
agentId: event.agentId,
|
|
174
|
-
adapterId: event.adapterId,
|
|
175
|
-
launchId: event.launchId,
|
|
176
|
-
nativeSessionId: event.nativeSessionId,
|
|
177
|
-
runId: event.runId,
|
|
178
|
-
receiptId: event.receiptId
|
|
179
|
-
}, now);
|
|
180
|
-
return outcome === "deferred" ? "deferred" : outcome;
|
|
181
|
-
}
|
|
182
166
|
/**
|
|
183
167
|
* f7/rr5: Acknowledge a durable-job-terminal event. The supervisor
|
|
184
168
|
* already committed the terminal transition and the Leader wakeup;
|
|
@@ -190,27 +174,6 @@ export class FileRuntimeEventProcessor {
|
|
|
190
174
|
// transaction as the terminal transition.
|
|
191
175
|
void event;
|
|
192
176
|
}
|
|
193
|
-
applyProviderTurnProgress(event, now) {
|
|
194
|
-
const task = this.observer.getTask(event.taskId);
|
|
195
|
-
if (task === null || task.status !== "active")
|
|
196
|
-
return "obsolete";
|
|
197
|
-
if (this.observer.observeProviderTurnProgress === undefined)
|
|
198
|
-
return "obsolete";
|
|
199
|
-
const outcome = this.observer.observeProviderTurnProgress({
|
|
200
|
-
eventId: event.id,
|
|
201
|
-
receivedAt: event.receivedAt,
|
|
202
|
-
taskId: event.taskId,
|
|
203
|
-
roleName: event.roleName,
|
|
204
|
-
agentId: event.agentId,
|
|
205
|
-
adapterId: event.adapterId,
|
|
206
|
-
launchId: event.launchId,
|
|
207
|
-
nativeSessionId: event.nativeSessionId,
|
|
208
|
-
runId: event.runId,
|
|
209
|
-
progressId: event.progressId,
|
|
210
|
-
...(event.sequence === undefined ? {} : { sequence: event.sequence })
|
|
211
|
-
}, now);
|
|
212
|
-
return outcome === "deferred" ? "deferred" : "applied";
|
|
213
|
-
}
|
|
214
177
|
applyCodex(event, now) {
|
|
215
178
|
if (event.scope === "task") {
|
|
216
179
|
const task = this.observer.getTask(event.taskId);
|
|
@@ -263,43 +226,26 @@ export class FileRuntimeEventProcessor {
|
|
|
263
226
|
}
|
|
264
227
|
return "obsolete";
|
|
265
228
|
}
|
|
266
|
-
applyClaudeStopFailure(event, now) {
|
|
267
|
-
const task = this.observer.getTask(event.taskId);
|
|
268
|
-
if (task === null)
|
|
269
|
-
return;
|
|
270
|
-
const input = {
|
|
271
|
-
eventId: event.id,
|
|
272
|
-
type: event.type,
|
|
273
|
-
taskId: event.taskId,
|
|
274
|
-
roleName: event.roleName,
|
|
275
|
-
agentId: event.agentId,
|
|
276
|
-
adapterId: event.adapterId,
|
|
277
|
-
launchId: event.launchId,
|
|
278
|
-
nativeSessionId: event.nativeSessionId,
|
|
279
|
-
runId: event.runId,
|
|
280
|
-
error: event.error,
|
|
281
|
-
...(event.errorDetails === undefined ? {} : { errorDetails: event.errorDetails }),
|
|
282
|
-
...(event.lastAssistantMessage === undefined
|
|
283
|
-
? {}
|
|
284
|
-
: { lastAssistantMessage: event.lastAssistantMessage })
|
|
285
|
-
};
|
|
286
|
-
if (task.status !== "active"
|
|
287
|
-
|| this.observer.classifyClaudeStopFailureEvent?.(input) === "obsolete") {
|
|
288
|
-
this.recordObsolete(event, task.status === "archived"
|
|
289
|
-
? "task-archived"
|
|
290
|
-
: task.status !== "active"
|
|
291
|
-
? "task-retired"
|
|
292
|
-
: "identity-mismatch-or-terminal", now);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (this.observer.observeClaudeStopFailureEvent === undefined) {
|
|
296
|
-
throw new Error("Claude StopFailure observer is unavailable.");
|
|
297
|
-
}
|
|
298
|
-
this.observer.observeClaudeStopFailureEvent(input, now);
|
|
299
|
-
}
|
|
300
229
|
recordObsolete(event, reason, now) {
|
|
301
230
|
if (event.scope !== "task" || event.taskId === undefined)
|
|
302
231
|
return;
|
|
232
|
+
if (event.type === "runtime-observation") {
|
|
233
|
+
const fence = event.observation.fence;
|
|
234
|
+
if (fence.nativeSessionId === undefined)
|
|
235
|
+
return;
|
|
236
|
+
this.observer.observeObsoleteRuntimeEvent?.({
|
|
237
|
+
eventId: event.id,
|
|
238
|
+
eventType: event.type,
|
|
239
|
+
taskId: event.taskId,
|
|
240
|
+
roleName: fence.roleName,
|
|
241
|
+
agentId: fence.agentId,
|
|
242
|
+
...(fence.runId === undefined ? {} : { runId: fence.runId }),
|
|
243
|
+
launchId: fence.launchId,
|
|
244
|
+
nativeSessionId: fence.nativeSessionId,
|
|
245
|
+
reason
|
|
246
|
+
}, now);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
303
249
|
// durable-job-terminal events have no provider identity; they are never
|
|
304
250
|
// recorded as obsolete.
|
|
305
251
|
if (!("roleName" in event) || !("nativeSessionId" in event))
|
|
@@ -333,51 +279,38 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
|
|
|
333
279
|
const flush = () => {
|
|
334
280
|
if (segment.length === 0)
|
|
335
281
|
return;
|
|
336
|
-
const
|
|
282
|
+
const indicesByStream = new Map();
|
|
337
283
|
for (let index = 0; index < segment.length; index += 1) {
|
|
338
284
|
instrumentation.onProgressVisit?.();
|
|
339
285
|
const event = segment[index];
|
|
340
286
|
const key = progressStreamKey(event);
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
latestReceivedAt: event.receivedAt,
|
|
345
|
-
...(event.sequence === undefined
|
|
346
|
-
? {}
|
|
347
|
-
: {
|
|
348
|
-
greatestSequence: event.sequence,
|
|
349
|
-
firstGreatestSequenceIndex: index
|
|
350
|
-
}),
|
|
351
|
-
firstLatestIndex: index
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
else if (event.receivedAt === frontier.latestReceivedAt
|
|
355
|
-
&& event.sequence !== undefined
|
|
356
|
-
&& (frontier.greatestSequence === undefined
|
|
357
|
-
|| event.sequence > frontier.greatestSequence)) {
|
|
358
|
-
frontiers.set(key, {
|
|
359
|
-
...frontier,
|
|
360
|
-
greatestSequence: event.sequence,
|
|
361
|
-
firstGreatestSequenceIndex: index
|
|
362
|
-
});
|
|
363
|
-
}
|
|
287
|
+
const indices = indicesByStream.get(key) ?? [];
|
|
288
|
+
indices.push(index);
|
|
289
|
+
indicesByStream.set(key, indices);
|
|
364
290
|
}
|
|
365
291
|
const representedByIndex = new Map();
|
|
366
|
-
for (
|
|
367
|
-
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const
|
|
379
|
-
|
|
380
|
-
|
|
292
|
+
for (const indices of indicesByStream.values()) {
|
|
293
|
+
const hasUsage = segment[indices[0]].observation.payload.usage !== undefined;
|
|
294
|
+
const retainedPositions = hasUsage ? [0] : [];
|
|
295
|
+
if (hasUsage) {
|
|
296
|
+
for (let position = 1; position < indices.length; position += 1) {
|
|
297
|
+
const previous = segment[indices[position - 1]].observation.payload.usage;
|
|
298
|
+
const current = segment[indices[position]].observation.payload.usage;
|
|
299
|
+
if (runtimeUsageTotal(current) < runtimeUsageTotal(previous)) {
|
|
300
|
+
retainedPositions.push(position);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const lastPosition = indices.length - 1;
|
|
305
|
+
if (retainedPositions.at(-1) !== lastPosition)
|
|
306
|
+
retainedPositions.push(lastPosition);
|
|
307
|
+
let previousRetainedPosition = -1;
|
|
308
|
+
for (const retainedPosition of retainedPositions) {
|
|
309
|
+
const retainedIndex = indices[retainedPosition];
|
|
310
|
+
representedByIndex.set(retainedIndex, indices.slice(previousRetainedPosition + 1, retainedPosition + 1)
|
|
311
|
+
.map((index) => segment[index].id));
|
|
312
|
+
previousRetainedPosition = retainedPosition;
|
|
313
|
+
}
|
|
381
314
|
}
|
|
382
315
|
for (let index = 0; index < segment.length; index += 1) {
|
|
383
316
|
const representedEventIds = representedByIndex.get(index);
|
|
@@ -388,7 +321,7 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
|
|
|
388
321
|
segment = [];
|
|
389
322
|
};
|
|
390
323
|
for (const event of events) {
|
|
391
|
-
if (event
|
|
324
|
+
if (!isRuntimeActivityEvent(event)) {
|
|
392
325
|
flush();
|
|
393
326
|
result.push({ event, representedEventIds: [event.id] });
|
|
394
327
|
continue;
|
|
@@ -399,22 +332,32 @@ export function coalesceRuntimeProgress(events, instrumentation = {}) {
|
|
|
399
332
|
return result;
|
|
400
333
|
}
|
|
401
334
|
function selectDrainBatch(events, maximum) {
|
|
402
|
-
// A batch is always an arrival-order prefix.
|
|
403
|
-
//
|
|
335
|
+
// A batch is always an arrival-order prefix. Drain-time coalescing bounds
|
|
336
|
+
// worker folds without allowing a later semantic fact to
|
|
404
337
|
// overtake an earlier progress fence from another Run.
|
|
405
338
|
return events.slice(0, maximum);
|
|
406
339
|
}
|
|
407
340
|
function progressStreamKey(event) {
|
|
341
|
+
const { fence, payload } = event.observation;
|
|
408
342
|
return JSON.stringify([
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
343
|
+
fence.taskId ?? null,
|
|
344
|
+
fence.roleName,
|
|
345
|
+
fence.agentId,
|
|
346
|
+
fence.driverId,
|
|
347
|
+
fence.launchId,
|
|
348
|
+
fence.nativeSessionId ?? null,
|
|
349
|
+
fence.runId ?? null,
|
|
350
|
+
payload.activity,
|
|
351
|
+
payload.usage === undefined ? "signal" : "usage"
|
|
416
352
|
]);
|
|
417
353
|
}
|
|
354
|
+
function runtimeUsageTotal(usage) {
|
|
355
|
+
return usage.inputTokens + usage.outputTokens;
|
|
356
|
+
}
|
|
357
|
+
function isRuntimeActivityEvent(event) {
|
|
358
|
+
return event.type === "runtime-observation"
|
|
359
|
+
&& event.observation.kind === "activity.observed";
|
|
360
|
+
}
|
|
418
361
|
function emptyDrainFailure(error) {
|
|
419
362
|
return {
|
|
420
363
|
acknowledgedEventIds: [],
|
|
@@ -456,13 +399,10 @@ export function createAsyncRuntimeObserver(invoke) {
|
|
|
456
399
|
const withNow = (method, input, now) => now === undefined ? call(method, [input]) : call(method, [input, now]);
|
|
457
400
|
return {
|
|
458
401
|
getTask: (taskId) => call("getTask", [taskId]),
|
|
402
|
+
observeRuntimeObservation: (input, now) => withNow("observeRuntimeObservation", input, now),
|
|
459
403
|
observeRuntimeTurnCompleted: (input, now) => withNow("observeRuntimeTurnCompleted", input, now),
|
|
460
404
|
observeGlobalRuntimeTurnCompleted: (input, now) => withNow("observeGlobalRuntimeTurnCompleted", input, now),
|
|
461
|
-
|
|
462
|
-
observeObsoleteRuntimeEvent: (input, now) => withNow("observeObsoleteRuntimeEvent", input, now),
|
|
463
|
-
observeProviderSessionLifecycle: (input, now) => withNow("observeProviderSessionLifecycle", input, now),
|
|
464
|
-
observeProviderPromptAccepted: (input, now) => withNow("observeProviderPromptAccepted", input, now),
|
|
465
|
-
observeProviderTurnProgress: (input, now) => withNow("observeProviderTurnProgress", input, now)
|
|
405
|
+
observeObsoleteRuntimeEvent: (input, now) => withNow("observeObsoleteRuntimeEvent", input, now)
|
|
466
406
|
};
|
|
467
407
|
}
|
|
468
408
|
/**
|
|
@@ -475,10 +415,12 @@ export class AsyncRuntimeEventProcessor {
|
|
|
475
415
|
inbox;
|
|
476
416
|
observer;
|
|
477
417
|
options;
|
|
418
|
+
drivers;
|
|
478
419
|
constructor(inbox, observer, options = {}) {
|
|
479
420
|
this.inbox = inbox;
|
|
480
421
|
this.observer = observer;
|
|
481
422
|
this.options = options;
|
|
423
|
+
this.drivers = options.drivers ?? builtinAgentDriverRegistry();
|
|
482
424
|
}
|
|
483
425
|
async drainAsync(now) {
|
|
484
426
|
const acknowledgedEventIds = [];
|
|
@@ -491,46 +433,31 @@ export class AsyncRuntimeEventProcessor {
|
|
|
491
433
|
catch (error) {
|
|
492
434
|
return emptyDrainFailure(error);
|
|
493
435
|
}
|
|
494
|
-
|
|
436
|
+
const coalesced = coalesceRuntimeProgress(events);
|
|
437
|
+
const maximum = positiveInteger(this.options.maxEventsPerDrain, DEFAULT_MAX_RUNTIME_EVENTS_PER_DRAIN);
|
|
438
|
+
const selected = selectDrainBatch(coalesced, maximum);
|
|
439
|
+
for (const candidate of selected) {
|
|
440
|
+
const event = candidate.event;
|
|
495
441
|
try {
|
|
442
|
+
let outcome = "applied";
|
|
496
443
|
if (event.type === "native-turn-completed") {
|
|
497
|
-
|
|
498
|
-
if (outcome === "deferred") {
|
|
499
|
-
deferred.push(event);
|
|
500
|
-
continue;
|
|
501
|
-
}
|
|
444
|
+
outcome = await this.applyCodex(event, now);
|
|
502
445
|
}
|
|
503
|
-
else if (event.type === "
|
|
504
|
-
await this.
|
|
505
|
-
}
|
|
506
|
-
else if (event.type === "native-session-lifecycle") {
|
|
507
|
-
const outcome = await this.applySessionLifecycle(event, now);
|
|
508
|
-
if (outcome === "deferred") {
|
|
509
|
-
deferred.push(event);
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
else if (event.type === "native-turn-progress") {
|
|
514
|
-
const outcome = await this.applyProviderTurnProgress(event, now);
|
|
515
|
-
if (outcome === "deferred") {
|
|
516
|
-
deferred.push(event);
|
|
517
|
-
continue;
|
|
518
|
-
}
|
|
446
|
+
else if (event.type === "runtime-observation") {
|
|
447
|
+
outcome = await this.applyRuntimeObservation(event, now);
|
|
519
448
|
}
|
|
520
|
-
else
|
|
449
|
+
else {
|
|
521
450
|
// f7/rr5: The supervisor already transitioned the job and
|
|
522
451
|
// enqueued the Leader wakeup. This event is the durable terminal
|
|
523
452
|
// channel: acknowledge it so the pipeline converges without
|
|
524
453
|
// re-processing. No state change to apply.
|
|
525
454
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
455
|
+
if (outcome === "deferred") {
|
|
456
|
+
deferred.push(event);
|
|
457
|
+
this.acknowledge(candidate.representedEventIds.filter((id) => id !== event.id), acknowledgedEventIds);
|
|
458
|
+
continue;
|
|
532
459
|
}
|
|
533
|
-
this.acknowledge(
|
|
460
|
+
this.acknowledge(candidate.representedEventIds, acknowledgedEventIds);
|
|
534
461
|
}
|
|
535
462
|
catch (error) {
|
|
536
463
|
failed.push({ eventId: event.id, error });
|
|
@@ -538,8 +465,9 @@ export class AsyncRuntimeEventProcessor {
|
|
|
538
465
|
}
|
|
539
466
|
const acknowledged = new Set(acknowledgedEventIds);
|
|
540
467
|
const remaining = events.filter(({ id }) => !acknowledged.has(id));
|
|
541
|
-
const progressEventsSelected =
|
|
542
|
-
const
|
|
468
|
+
const progressEventsSelected = selected.filter(({ event }) => (isRuntimeActivityEvent(event))).length;
|
|
469
|
+
const representedEventCount = selected.reduce((count, candidate) => (count + candidate.representedEventIds.length), 0);
|
|
470
|
+
const remainingProgressEventCount = remaining.filter(isRuntimeActivityEvent).length;
|
|
543
471
|
return {
|
|
544
472
|
acknowledgedEventIds,
|
|
545
473
|
deferred,
|
|
@@ -547,96 +475,42 @@ export class AsyncRuntimeEventProcessor {
|
|
|
547
475
|
remainingEventCount: remaining.length,
|
|
548
476
|
metrics: {
|
|
549
477
|
listedEventCount: events.length,
|
|
550
|
-
selectedEventCount:
|
|
551
|
-
semanticEventsSelected:
|
|
478
|
+
selectedEventCount: selected.length,
|
|
479
|
+
semanticEventsSelected: selected.length - progressEventsSelected,
|
|
552
480
|
progressEventsSelected,
|
|
553
|
-
progressEventsCoalesced:
|
|
481
|
+
progressEventsCoalesced: representedEventCount - selected.length,
|
|
554
482
|
stateTransactions: 0,
|
|
555
483
|
remainingSemanticEventCount: remaining.length - remainingProgressEventCount,
|
|
556
484
|
remainingProgressEventCount
|
|
557
485
|
}
|
|
558
486
|
};
|
|
559
487
|
}
|
|
560
|
-
async
|
|
561
|
-
const
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
const outcome = await this.observer.observeProviderSessionLifecycle({
|
|
567
|
-
eventId: event.id,
|
|
568
|
-
taskId: event.taskId,
|
|
569
|
-
roleName: event.roleName,
|
|
570
|
-
agentId: event.agentId,
|
|
571
|
-
adapterId: event.adapterId,
|
|
572
|
-
launchId: event.launchId,
|
|
573
|
-
nativeSessionId: event.nativeSessionId,
|
|
574
|
-
...(event.runId === undefined ? {} : { runId: event.runId }),
|
|
575
|
-
...(event.sessionSource === undefined ? {} : { sessionSource: event.sessionSource })
|
|
576
|
-
}, now);
|
|
577
|
-
if (outcome === "applied") {
|
|
578
|
-
this.options.onTaskRuntimeApplied?.({
|
|
579
|
-
taskId: event.taskId,
|
|
580
|
-
roleName: event.roleName,
|
|
581
|
-
agentId: event.agentId,
|
|
582
|
-
adapterId: event.adapterId,
|
|
583
|
-
launchId: event.launchId,
|
|
584
|
-
nativeSessionId: event.nativeSessionId,
|
|
585
|
-
...(event.runId === undefined ? {} : { runId: event.runId })
|
|
586
|
-
});
|
|
488
|
+
async applyRuntimeObservation(event, now) {
|
|
489
|
+
const taskId = event.observation.fence.taskId;
|
|
490
|
+
if (taskId !== undefined) {
|
|
491
|
+
const task = await this.observer.getTask(taskId);
|
|
492
|
+
if (task === null || task.status !== "active")
|
|
493
|
+
return "obsolete";
|
|
587
494
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
const outcome = await this.observer.observeProviderPromptAccepted({
|
|
597
|
-
eventId: event.id,
|
|
598
|
-
taskId: event.taskId,
|
|
599
|
-
roleName: event.roleName,
|
|
600
|
-
agentId: event.agentId,
|
|
601
|
-
adapterId: event.adapterId,
|
|
602
|
-
launchId: event.launchId,
|
|
603
|
-
nativeSessionId: event.nativeSessionId,
|
|
604
|
-
runId: event.runId,
|
|
605
|
-
receiptId: event.receiptId
|
|
606
|
-
}, now);
|
|
607
|
-
if (outcome === "applied") {
|
|
495
|
+
const outcome = (await this.observer.observeRuntimeObservation?.(event.observation, now))
|
|
496
|
+
?? "obsolete";
|
|
497
|
+
const fence = event.observation.fence;
|
|
498
|
+
if (outcome === "applied"
|
|
499
|
+
&& fence.taskId !== undefined
|
|
500
|
+
&& fence.nativeSessionId !== undefined
|
|
501
|
+
&& ["session.started", "session.ready", "turn.accepted", "turn.completed"]
|
|
502
|
+
.includes(event.observation.kind)) {
|
|
608
503
|
this.options.onTaskRuntimeApplied?.({
|
|
609
|
-
taskId:
|
|
610
|
-
roleName:
|
|
611
|
-
agentId:
|
|
612
|
-
adapterId:
|
|
613
|
-
launchId:
|
|
614
|
-
nativeSessionId:
|
|
615
|
-
runId:
|
|
504
|
+
taskId: fence.taskId,
|
|
505
|
+
roleName: fence.roleName,
|
|
506
|
+
agentId: fence.agentId,
|
|
507
|
+
adapterId: this.drivers.require(fence.driverId).adapterId,
|
|
508
|
+
launchId: fence.launchId,
|
|
509
|
+
nativeSessionId: fence.nativeSessionId,
|
|
510
|
+
...(fence.runId === undefined ? {} : { runId: fence.runId })
|
|
616
511
|
});
|
|
617
512
|
}
|
|
618
|
-
return outcome
|
|
619
|
-
}
|
|
620
|
-
async applyProviderTurnProgress(event, now) {
|
|
621
|
-
const task = await this.observer.getTask(event.taskId);
|
|
622
|
-
if (task === null || task.status !== "active")
|
|
623
|
-
return "obsolete";
|
|
624
|
-
if (this.observer.observeProviderTurnProgress === undefined)
|
|
625
|
-
return "obsolete";
|
|
626
|
-
const outcome = await this.observer.observeProviderTurnProgress({
|
|
627
|
-
eventId: event.id,
|
|
628
|
-
receivedAt: event.receivedAt,
|
|
629
|
-
taskId: event.taskId,
|
|
630
|
-
roleName: event.roleName,
|
|
631
|
-
agentId: event.agentId,
|
|
632
|
-
adapterId: event.adapterId,
|
|
633
|
-
launchId: event.launchId,
|
|
634
|
-
nativeSessionId: event.nativeSessionId,
|
|
635
|
-
runId: event.runId,
|
|
636
|
-
progressId: event.progressId,
|
|
637
|
-
...(event.sequence === undefined ? {} : { sequence: event.sequence })
|
|
638
|
-
}, now);
|
|
639
|
-
return outcome === "deferred" ? "deferred" : "applied";
|
|
513
|
+
return outcome;
|
|
640
514
|
}
|
|
641
515
|
async applyCodex(event, now) {
|
|
642
516
|
if (event.scope === "task") {
|
|
@@ -699,43 +573,26 @@ export class AsyncRuntimeEventProcessor {
|
|
|
699
573
|
}
|
|
700
574
|
return "obsolete";
|
|
701
575
|
}
|
|
702
|
-
async applyClaudeStopFailure(event, now) {
|
|
703
|
-
const task = await this.observer.getTask(event.taskId);
|
|
704
|
-
if (task === null)
|
|
705
|
-
return;
|
|
706
|
-
const input = {
|
|
707
|
-
eventId: event.id,
|
|
708
|
-
type: event.type,
|
|
709
|
-
taskId: event.taskId,
|
|
710
|
-
roleName: event.roleName,
|
|
711
|
-
agentId: event.agentId,
|
|
712
|
-
adapterId: event.adapterId,
|
|
713
|
-
launchId: event.launchId,
|
|
714
|
-
nativeSessionId: event.nativeSessionId,
|
|
715
|
-
runId: event.runId,
|
|
716
|
-
error: event.error,
|
|
717
|
-
...(event.errorDetails === undefined ? {} : { errorDetails: event.errorDetails }),
|
|
718
|
-
...(event.lastAssistantMessage === undefined
|
|
719
|
-
? {}
|
|
720
|
-
: { lastAssistantMessage: event.lastAssistantMessage })
|
|
721
|
-
};
|
|
722
|
-
if (task.status !== "active"
|
|
723
|
-
|| (await this.observer.classifyClaudeStopFailureEvent?.(input)) === "obsolete") {
|
|
724
|
-
await this.recordObsolete(event, task.status === "archived"
|
|
725
|
-
? "task-archived"
|
|
726
|
-
: task.status !== "active"
|
|
727
|
-
? "task-retired"
|
|
728
|
-
: "identity-mismatch-or-terminal", now);
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
if (this.observer.observeClaudeStopFailureEvent === undefined) {
|
|
732
|
-
throw new Error("Claude StopFailure observer is unavailable.");
|
|
733
|
-
}
|
|
734
|
-
await this.observer.observeClaudeStopFailureEvent(input, now);
|
|
735
|
-
}
|
|
736
576
|
async recordObsolete(event, reason, now) {
|
|
737
577
|
if (event.scope !== "task" || event.taskId === undefined || event.type === "durable-job-terminal")
|
|
738
578
|
return;
|
|
579
|
+
if (event.type === "runtime-observation") {
|
|
580
|
+
const fence = event.observation.fence;
|
|
581
|
+
if (fence.nativeSessionId === undefined)
|
|
582
|
+
return;
|
|
583
|
+
await this.observer.observeObsoleteRuntimeEvent?.({
|
|
584
|
+
eventId: event.id,
|
|
585
|
+
eventType: event.type,
|
|
586
|
+
taskId: event.taskId,
|
|
587
|
+
roleName: fence.roleName,
|
|
588
|
+
agentId: fence.agentId,
|
|
589
|
+
...(fence.runId === undefined ? {} : { runId: fence.runId }),
|
|
590
|
+
launchId: fence.launchId,
|
|
591
|
+
nativeSessionId: fence.nativeSessionId,
|
|
592
|
+
reason
|
|
593
|
+
}, now);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
739
596
|
await this.observer.observeObsoleteRuntimeEvent?.({
|
|
740
597
|
eventId: event.id,
|
|
741
598
|
eventType: event.type,
|
|
@@ -748,8 +605,14 @@ export class AsyncRuntimeEventProcessor {
|
|
|
748
605
|
reason
|
|
749
606
|
}, now);
|
|
750
607
|
}
|
|
751
|
-
acknowledge(
|
|
752
|
-
this.inbox.
|
|
753
|
-
|
|
608
|
+
acknowledge(ids, acknowledged) {
|
|
609
|
+
if (this.inbox.acknowledgeMany !== undefined) {
|
|
610
|
+
acknowledged.push(...this.inbox.acknowledgeMany(ids));
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
for (const id of ids) {
|
|
614
|
+
if (this.inbox.acknowledge(id))
|
|
615
|
+
acknowledged.push(id);
|
|
616
|
+
}
|
|
754
617
|
}
|
|
755
618
|
}
|