@zq-silk/yui 0.6.2 → 0.6.3
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 +62 -24
- 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 +41 -0
- package/dist/storage/sqliteStore.js +3 -3
- package/dist/storage/storageVersions.js +1 -1
- 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 +7 -5
- 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
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { requireDriverId } from "./agentDriver.js";
|
|
2
|
+
export const RUNTIME_OBSERVATION_TASK_EVENT = "runtime.observation";
|
|
3
|
+
const KINDS = [
|
|
4
|
+
"host.observed",
|
|
5
|
+
"session.started",
|
|
6
|
+
"session.ready",
|
|
7
|
+
"session.ended",
|
|
8
|
+
"session.failed",
|
|
9
|
+
"turn.accepted",
|
|
10
|
+
"turn.waiting",
|
|
11
|
+
"turn.completed",
|
|
12
|
+
"turn.failed",
|
|
13
|
+
"turn.cancelled",
|
|
14
|
+
"operation.started",
|
|
15
|
+
"operation.completed",
|
|
16
|
+
"operation.failed",
|
|
17
|
+
"activity.observed",
|
|
18
|
+
"observer.health"
|
|
19
|
+
];
|
|
20
|
+
const AUTHORITIES = [
|
|
21
|
+
"controller",
|
|
22
|
+
"transport",
|
|
23
|
+
"provider-structured",
|
|
24
|
+
"driver-inferred",
|
|
25
|
+
"host",
|
|
26
|
+
"diagnostic"
|
|
27
|
+
];
|
|
28
|
+
const RUN_SCOPED = new Set([
|
|
29
|
+
"turn.accepted",
|
|
30
|
+
"turn.waiting",
|
|
31
|
+
"turn.completed",
|
|
32
|
+
"turn.failed",
|
|
33
|
+
"turn.cancelled",
|
|
34
|
+
"operation.started",
|
|
35
|
+
"operation.completed",
|
|
36
|
+
"operation.failed",
|
|
37
|
+
"activity.observed",
|
|
38
|
+
"observer.health"
|
|
39
|
+
]);
|
|
40
|
+
const PROVIDER_STATE = new Set([
|
|
41
|
+
"session.started",
|
|
42
|
+
"session.ready",
|
|
43
|
+
"session.ended",
|
|
44
|
+
"session.failed",
|
|
45
|
+
"turn.accepted",
|
|
46
|
+
"turn.waiting",
|
|
47
|
+
"turn.completed",
|
|
48
|
+
"turn.failed",
|
|
49
|
+
"turn.cancelled",
|
|
50
|
+
"operation.started",
|
|
51
|
+
"operation.completed",
|
|
52
|
+
"operation.failed"
|
|
53
|
+
]);
|
|
54
|
+
export function createRuntimeObservation(input) {
|
|
55
|
+
if (input.schemaVersion !== 1)
|
|
56
|
+
throw new Error("Runtime observation schemaVersion must be 1.");
|
|
57
|
+
if (!KINDS.includes(input.kind))
|
|
58
|
+
throw new Error("Runtime observation kind is invalid.");
|
|
59
|
+
if (!AUTHORITIES.includes(input.authority))
|
|
60
|
+
throw new Error("Runtime observation authority is invalid.");
|
|
61
|
+
if (input.kind === "host.observed" && input.authority !== "host" && input.authority !== "controller") {
|
|
62
|
+
throw new Error("host.observed requires host or controller authority.");
|
|
63
|
+
}
|
|
64
|
+
if (PROVIDER_STATE.has(input.kind)
|
|
65
|
+
&& input.authority !== "provider-structured"
|
|
66
|
+
&& input.authority !== "controller") {
|
|
67
|
+
throw new Error(`${input.kind} requires provider-structured or controller authority.`);
|
|
68
|
+
}
|
|
69
|
+
const fence = normalizeFence(input.fence);
|
|
70
|
+
if (RUN_SCOPED.has(input.kind) && fence.runId === undefined) {
|
|
71
|
+
throw new Error(`${input.kind} requires runId.`);
|
|
72
|
+
}
|
|
73
|
+
if (RUN_SCOPED.has(input.kind) && fence.nativeSessionId === undefined) {
|
|
74
|
+
throw new Error(`${input.kind} requires nativeSessionId.`);
|
|
75
|
+
}
|
|
76
|
+
if (RUN_SCOPED.has(input.kind) && fence.nativeTurnId === undefined) {
|
|
77
|
+
throw new Error(`${input.kind} requires nativeTurnId.`);
|
|
78
|
+
}
|
|
79
|
+
const payload = normalizePayload(input.kind, input.payload);
|
|
80
|
+
const sequence = input.sequence;
|
|
81
|
+
if (sequence !== undefined && (!Number.isSafeInteger(sequence) || sequence < 0)) {
|
|
82
|
+
throw new Error("Runtime observation sequence must be a non-negative safe integer.");
|
|
83
|
+
}
|
|
84
|
+
const ordinal = input.ordinal;
|
|
85
|
+
if (ordinal !== undefined && (!Number.isSafeInteger(ordinal) || ordinal < 0)) {
|
|
86
|
+
throw new Error("Runtime observation ordinal must be a non-negative safe integer.");
|
|
87
|
+
}
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
eventId: requireIdentity(input.eventId, "Runtime observation event id"),
|
|
91
|
+
kind: input.kind,
|
|
92
|
+
authority: input.authority,
|
|
93
|
+
receivedAt: requireTimestamp(input.receivedAt, "Runtime observation receivedAt"),
|
|
94
|
+
...(input.observedAt === undefined
|
|
95
|
+
? {}
|
|
96
|
+
: { observedAt: requireTimestamp(input.observedAt, "Runtime observation observedAt") }),
|
|
97
|
+
...(sequence === undefined ? {} : { sequence }),
|
|
98
|
+
...(ordinal === undefined ? {} : { ordinal }),
|
|
99
|
+
fence,
|
|
100
|
+
payload
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function runtimeObservationFenceMatches(expected, actual) {
|
|
104
|
+
for (const field of [
|
|
105
|
+
"taskId",
|
|
106
|
+
"roleName",
|
|
107
|
+
"runId",
|
|
108
|
+
"agentId",
|
|
109
|
+
"driverId",
|
|
110
|
+
"launchId",
|
|
111
|
+
"sessionGenerationId",
|
|
112
|
+
"nativeSessionId",
|
|
113
|
+
"nativeTurnId",
|
|
114
|
+
"receiptId"
|
|
115
|
+
]) {
|
|
116
|
+
if (expected[field] !== actual[field])
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/** Compact TaskEvent payload used for durable state-boundary observations. */
|
|
122
|
+
export function runtimeObservationTaskEventPayload(input) {
|
|
123
|
+
const observation = createRuntimeObservation(input);
|
|
124
|
+
return Object.freeze({
|
|
125
|
+
eventId: observation.eventId,
|
|
126
|
+
roleName: observation.fence.roleName,
|
|
127
|
+
agentId: observation.fence.agentId,
|
|
128
|
+
driverId: observation.fence.driverId,
|
|
129
|
+
launchId: observation.fence.launchId,
|
|
130
|
+
...(observation.fence.taskId === undefined ? {} : { taskId: observation.fence.taskId }),
|
|
131
|
+
...(observation.fence.runId === undefined ? {} : { runId: observation.fence.runId }),
|
|
132
|
+
...(observation.fence.nativeSessionId === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { nativeSessionId: observation.fence.nativeSessionId }),
|
|
135
|
+
kind: observation.kind,
|
|
136
|
+
receivedAt: observation.receivedAt,
|
|
137
|
+
observation: JSON.stringify(observation)
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
export function runtimeObservationFromTaskEvent(event) {
|
|
141
|
+
if (event.type !== RUNTIME_OBSERVATION_TASK_EVENT)
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(event.payload.observation ?? "");
|
|
145
|
+
return createRuntimeObservation(parsed);
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function normalizeFence(input) {
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
...(input.taskId === undefined ? {} : { taskId: requireIdentity(input.taskId, "Task id") }),
|
|
154
|
+
roleName: requireIdentity(input.roleName, "Role name"),
|
|
155
|
+
...(input.runId === undefined ? {} : { runId: requireIdentity(input.runId, "Run id") }),
|
|
156
|
+
agentId: requireIdentity(input.agentId, "Agent id"),
|
|
157
|
+
driverId: requireDriverId(input.driverId),
|
|
158
|
+
launchId: requireIdentity(input.launchId, "Launch id"),
|
|
159
|
+
sessionGenerationId: requireIdentity(input.sessionGenerationId, "Session generation id"),
|
|
160
|
+
...(input.nativeSessionId === undefined
|
|
161
|
+
? {}
|
|
162
|
+
: { nativeSessionId: requireIdentity(input.nativeSessionId, "Native Session id") }),
|
|
163
|
+
...(input.nativeTurnId === undefined
|
|
164
|
+
? {}
|
|
165
|
+
: { nativeTurnId: requireIdentity(input.nativeTurnId, "Native Turn id") }),
|
|
166
|
+
...(input.receiptId === undefined
|
|
167
|
+
? {}
|
|
168
|
+
: { receiptId: requireIdentity(input.receiptId, "Receipt id") })
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
function normalizePayload(kind, input) {
|
|
172
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
173
|
+
throw new Error("Runtime observation payload must be an object.");
|
|
174
|
+
}
|
|
175
|
+
if (kind === "host.observed" && typeof input.alive !== "boolean") {
|
|
176
|
+
throw new Error("host.observed requires payload.alive.");
|
|
177
|
+
}
|
|
178
|
+
if (kind === "turn.waiting"
|
|
179
|
+
&& input.reason !== "user"
|
|
180
|
+
&& input.reason !== "permission"
|
|
181
|
+
&& input.reason !== "external") {
|
|
182
|
+
throw new Error("turn.waiting requires a supported reason.");
|
|
183
|
+
}
|
|
184
|
+
if (kind === "turn.waiting")
|
|
185
|
+
requireIdentity(input.waitId, "Runtime wait id");
|
|
186
|
+
if (kind.startsWith("operation.")) {
|
|
187
|
+
requireIdentity(input.operationId, "Runtime operation id");
|
|
188
|
+
if (input.operation !== "model" && input.operation !== "tool" && input.operation !== "subagent") {
|
|
189
|
+
throw new Error("Runtime operation kind is invalid.");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (kind === "activity.observed") {
|
|
193
|
+
if (!["model", "tool", "subagent", "provider", "resource"].includes(input.activity ?? "")) {
|
|
194
|
+
throw new Error("activity.observed requires an activity kind.");
|
|
195
|
+
}
|
|
196
|
+
if (input.usage !== undefined)
|
|
197
|
+
validateUsage(input.usage);
|
|
198
|
+
}
|
|
199
|
+
if (kind === "observer.health") {
|
|
200
|
+
requireIdentity(input.sourceId, "Runtime observer source id");
|
|
201
|
+
if (!['healthy', 'degraded', 'unavailable'].includes(input.observerStatus ?? "")) {
|
|
202
|
+
throw new Error("observer.health requires a supported status.");
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const observerSource = input.observerSource === undefined
|
|
206
|
+
? undefined
|
|
207
|
+
: normalizeObserverSource(input.observerSource);
|
|
208
|
+
if (kind === "turn.failed" && input.failure === undefined) {
|
|
209
|
+
throw new Error("turn.failed requires normalized failure evidence.");
|
|
210
|
+
}
|
|
211
|
+
const failure = input.failure === undefined
|
|
212
|
+
? undefined
|
|
213
|
+
: normalizeFailure(input.failure);
|
|
214
|
+
return Object.freeze({
|
|
215
|
+
...(input.alive === undefined ? {} : { alive: input.alive }),
|
|
216
|
+
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
217
|
+
...(input.waitId === undefined ? {} : { waitId: requireIdentity(input.waitId, "Runtime wait id") }),
|
|
218
|
+
...(input.operationId === undefined
|
|
219
|
+
? {}
|
|
220
|
+
: { operationId: requireIdentity(input.operationId, "Runtime operation id") }),
|
|
221
|
+
...(input.operation === undefined ? {} : { operation: input.operation }),
|
|
222
|
+
...(input.activity === undefined ? {} : { activity: input.activity }),
|
|
223
|
+
...(input.activityId === undefined
|
|
224
|
+
? {}
|
|
225
|
+
: { activityId: requireIdentity(input.activityId, "Runtime activity id") }),
|
|
226
|
+
...(input.usage === undefined ? {} : { usage: Object.freeze({ ...input.usage }) }),
|
|
227
|
+
...(observerSource === undefined ? {} : { observerSource }),
|
|
228
|
+
...(input.sourceId === undefined
|
|
229
|
+
? {}
|
|
230
|
+
: { sourceId: requireIdentity(input.sourceId, "Runtime observer source id") }),
|
|
231
|
+
...(input.observerStatus === undefined ? {} : { observerStatus: input.observerStatus }),
|
|
232
|
+
...(input.observerDetail === undefined
|
|
233
|
+
? {}
|
|
234
|
+
: { observerDetail: requireText(input.observerDetail, "Runtime observer detail") }),
|
|
235
|
+
...(failure === undefined ? {} : { failure }),
|
|
236
|
+
...(input.summary === undefined ? {} : { summary: requireText(input.summary, "Runtime summary") })
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function normalizeObserverSource(input) {
|
|
240
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
241
|
+
throw new Error("Runtime observer source must be an object.");
|
|
242
|
+
}
|
|
243
|
+
if (input.schemaVersion !== 1 || input.transport !== "append-only-jsonl") {
|
|
244
|
+
throw new Error("Runtime observer source is invalid.");
|
|
245
|
+
}
|
|
246
|
+
return Object.freeze({
|
|
247
|
+
schemaVersion: 1,
|
|
248
|
+
sourceId: requireIdentity(input.sourceId, "Runtime observer source id"),
|
|
249
|
+
transport: "append-only-jsonl",
|
|
250
|
+
locator: requireText(input.locator, "Runtime observer locator")
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function normalizeFailure(input) {
|
|
254
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
255
|
+
throw new Error("Runtime failure evidence must be an object.");
|
|
256
|
+
}
|
|
257
|
+
return Object.freeze({
|
|
258
|
+
code: requireText(input.code, "Runtime failure code"),
|
|
259
|
+
...(input.details === undefined
|
|
260
|
+
? {}
|
|
261
|
+
: { details: requireText(input.details, "Runtime failure details") }),
|
|
262
|
+
...(input.lastOutput === undefined
|
|
263
|
+
? {}
|
|
264
|
+
: { lastOutput: requireText(input.lastOutput, "Runtime failure last output") })
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function validateUsage(input) {
|
|
268
|
+
for (const [name, value] of Object.entries(input)) {
|
|
269
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
270
|
+
throw new Error(`Runtime usage ${name} must be a non-negative safe integer.`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (input.inputTokens === undefined || input.outputTokens === undefined) {
|
|
274
|
+
throw new Error("Runtime usage requires inputTokens and outputTokens.");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function requireTimestamp(value, label) {
|
|
278
|
+
const timestamp = requireText(value, label);
|
|
279
|
+
if (!Number.isFinite(Date.parse(timestamp)))
|
|
280
|
+
throw new Error(`${label} is invalid.`);
|
|
281
|
+
return timestamp;
|
|
282
|
+
}
|
|
283
|
+
function requireIdentity(value, label) {
|
|
284
|
+
const identity = requireText(value, label);
|
|
285
|
+
if (identity.includes("/../") || identity === "__proto__")
|
|
286
|
+
throw new Error(`${label} is invalid.`);
|
|
287
|
+
return identity;
|
|
288
|
+
}
|
|
289
|
+
function requireText(value, label) {
|
|
290
|
+
if (typeof value !== "string" || value.includes("\0"))
|
|
291
|
+
throw new Error(`${label} is invalid.`);
|
|
292
|
+
const normalized = value.trim();
|
|
293
|
+
if (normalized.length === 0 || normalized.length > 32 * 1024) {
|
|
294
|
+
throw new Error(`${label} is invalid.`);
|
|
295
|
+
}
|
|
296
|
+
return normalized;
|
|
297
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
|
|
2
|
+
export function createRuntimeProjection(fence, createdAt) {
|
|
3
|
+
const timestamp = requireTimestamp(createdAt);
|
|
4
|
+
return Object.freeze({
|
|
5
|
+
fence: Object.freeze({ ...fence }),
|
|
6
|
+
host: "unknown",
|
|
7
|
+
session: "unknown",
|
|
8
|
+
turn: "none",
|
|
9
|
+
operations: Object.freeze({}),
|
|
10
|
+
activity: Object.freeze({ kind: "none" }),
|
|
11
|
+
observer: Object.freeze({ status: "unknown" }),
|
|
12
|
+
stateSince: timestamp,
|
|
13
|
+
workflow: Object.freeze({
|
|
14
|
+
lastSemanticProgressAt: timestamp,
|
|
15
|
+
completed: false,
|
|
16
|
+
blocked: false
|
|
17
|
+
})
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export function projectRuntimeTaskEvents(fence, createdAt, events) {
|
|
21
|
+
const observations = events
|
|
22
|
+
.map(runtimeObservationFromTaskEvent)
|
|
23
|
+
.filter((event) => (event !== null && runtimeObservationFenceMatches(fence, event.fence)))
|
|
24
|
+
.sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
|
|
25
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
26
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
27
|
+
|| left.eventId.localeCompare(right.eventId)));
|
|
28
|
+
return observations.reduce(projectRuntimeObservation, createRuntimeProjection(fence, createdAt));
|
|
29
|
+
}
|
|
30
|
+
export function projectRuntimeObservation(current, raw) {
|
|
31
|
+
const event = createRuntimeObservation(raw);
|
|
32
|
+
if (!runtimeObservationFenceMatches(current.fence, event.fence)) {
|
|
33
|
+
throw new Error("Runtime observation fence does not match the projection.");
|
|
34
|
+
}
|
|
35
|
+
const at = event.receivedAt;
|
|
36
|
+
switch (event.kind) {
|
|
37
|
+
case "host.observed":
|
|
38
|
+
return next(current, {
|
|
39
|
+
host: event.payload.alive ? "alive" : "exited",
|
|
40
|
+
stateSince: at
|
|
41
|
+
});
|
|
42
|
+
case "session.started":
|
|
43
|
+
return next(current, { session: "started", stateSince: at });
|
|
44
|
+
case "session.ready":
|
|
45
|
+
return next(current, { session: "ready", stateSince: at });
|
|
46
|
+
case "session.ended":
|
|
47
|
+
return next(current, {
|
|
48
|
+
session: "ended",
|
|
49
|
+
turn: terminalTurn(current.turn),
|
|
50
|
+
operations: Object.freeze({}),
|
|
51
|
+
stateSince: at
|
|
52
|
+
});
|
|
53
|
+
case "session.failed":
|
|
54
|
+
return next(current, {
|
|
55
|
+
session: "failed",
|
|
56
|
+
turn: current.turn === "none" ? "failed" : terminalTurn(current.turn, "failed"),
|
|
57
|
+
operations: Object.freeze({}),
|
|
58
|
+
stateSince: at
|
|
59
|
+
});
|
|
60
|
+
case "turn.accepted":
|
|
61
|
+
return withActivity(next(current, {
|
|
62
|
+
session: "active",
|
|
63
|
+
turn: "accepted",
|
|
64
|
+
waitingReason: undefined,
|
|
65
|
+
waitId: undefined,
|
|
66
|
+
stateSince: at
|
|
67
|
+
}), "provider", at);
|
|
68
|
+
case "turn.waiting":
|
|
69
|
+
return next(current, {
|
|
70
|
+
session: "waiting",
|
|
71
|
+
turn: "waiting",
|
|
72
|
+
waitingReason: event.payload.reason,
|
|
73
|
+
waitId: event.payload.waitId,
|
|
74
|
+
stateSince: at
|
|
75
|
+
});
|
|
76
|
+
case "turn.completed":
|
|
77
|
+
return withActivity(next(current, {
|
|
78
|
+
session: "ready",
|
|
79
|
+
turn: "completed",
|
|
80
|
+
waitingReason: undefined,
|
|
81
|
+
waitId: undefined,
|
|
82
|
+
operations: Object.freeze({}),
|
|
83
|
+
stateSince: at
|
|
84
|
+
}), "provider", at);
|
|
85
|
+
case "turn.failed":
|
|
86
|
+
return withActivity(next(current, {
|
|
87
|
+
session: "ready",
|
|
88
|
+
turn: "failed",
|
|
89
|
+
waitingReason: undefined,
|
|
90
|
+
waitId: undefined,
|
|
91
|
+
operations: Object.freeze({}),
|
|
92
|
+
stateSince: at
|
|
93
|
+
}), "provider", at);
|
|
94
|
+
case "turn.cancelled":
|
|
95
|
+
return withActivity(next(current, {
|
|
96
|
+
session: "ready",
|
|
97
|
+
turn: "cancelled",
|
|
98
|
+
waitingReason: undefined,
|
|
99
|
+
waitId: undefined,
|
|
100
|
+
operations: Object.freeze({}),
|
|
101
|
+
stateSince: at
|
|
102
|
+
}), "provider", at);
|
|
103
|
+
case "operation.started": {
|
|
104
|
+
const operationId = event.payload.operationId;
|
|
105
|
+
const operation = event.payload.operation;
|
|
106
|
+
return withActivity(next(current, {
|
|
107
|
+
session: "active",
|
|
108
|
+
turn: current.turn === "waiting" ? "accepted" : current.turn,
|
|
109
|
+
waitingReason: undefined,
|
|
110
|
+
waitId: undefined,
|
|
111
|
+
operations: Object.freeze({
|
|
112
|
+
...current.operations,
|
|
113
|
+
[operationId]: Object.freeze({ kind: operation, startedAt: at })
|
|
114
|
+
}),
|
|
115
|
+
stateSince: at
|
|
116
|
+
}), operation, at);
|
|
117
|
+
}
|
|
118
|
+
case "operation.completed":
|
|
119
|
+
case "operation.failed": {
|
|
120
|
+
const operations = { ...current.operations };
|
|
121
|
+
delete operations[event.payload.operationId];
|
|
122
|
+
return withActivity(next(current, {
|
|
123
|
+
session: "active",
|
|
124
|
+
turn: current.turn === "waiting" ? "accepted" : current.turn,
|
|
125
|
+
waitingReason: undefined,
|
|
126
|
+
waitId: undefined,
|
|
127
|
+
operations: Object.freeze(operations),
|
|
128
|
+
stateSince: at
|
|
129
|
+
}), event.payload.operation, at);
|
|
130
|
+
}
|
|
131
|
+
case "activity.observed": {
|
|
132
|
+
const usage = event.payload.usage;
|
|
133
|
+
if (usage !== undefined && !usageAdvanced(current.usage, usage)) {
|
|
134
|
+
return next(current, { usage: Object.freeze({ ...usage }) });
|
|
135
|
+
}
|
|
136
|
+
return withActivity(next(current, {
|
|
137
|
+
...(usage === undefined ? {} : { usage: Object.freeze({ ...usage }) }),
|
|
138
|
+
session: current.turn === "waiting" ? "active" : current.session,
|
|
139
|
+
turn: current.turn === "waiting" ? "accepted" : current.turn,
|
|
140
|
+
waitingReason: undefined,
|
|
141
|
+
waitId: undefined
|
|
142
|
+
}), event.payload.activity, at);
|
|
143
|
+
}
|
|
144
|
+
case "observer.health":
|
|
145
|
+
return next(current, {
|
|
146
|
+
observer: Object.freeze({
|
|
147
|
+
status: event.payload.observerStatus,
|
|
148
|
+
sourceId: event.payload.sourceId,
|
|
149
|
+
...(event.payload.observerDetail === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { detail: event.payload.observerDetail })
|
|
152
|
+
})
|
|
153
|
+
});
|
|
154
|
+
default:
|
|
155
|
+
return current;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
export function recordRuntimeSemanticProgress(current, progressAt) {
|
|
159
|
+
const at = requireTimestamp(progressAt);
|
|
160
|
+
if (Date.parse(at) <= Date.parse(current.workflow.lastSemanticProgressAt))
|
|
161
|
+
return current;
|
|
162
|
+
return next(current, {
|
|
163
|
+
workflow: Object.freeze({ ...current.workflow, lastSemanticProgressAt: at })
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
export function completeRuntimeWorkflow(current, completedAt) {
|
|
167
|
+
const at = requireTimestamp(completedAt);
|
|
168
|
+
return next(current, {
|
|
169
|
+
workflow: Object.freeze({
|
|
170
|
+
lastSemanticProgressAt: maxTimestamp(current.workflow.lastSemanticProgressAt, at),
|
|
171
|
+
completed: true,
|
|
172
|
+
blocked: false
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
export function runtimeDisplayStatus(current) {
|
|
177
|
+
if (current.session === "failed")
|
|
178
|
+
return "broken";
|
|
179
|
+
if (current.host === "exited" || current.session === "ended")
|
|
180
|
+
return "stopped";
|
|
181
|
+
const operation = dominantOperation(current.operations);
|
|
182
|
+
if (operation !== null)
|
|
183
|
+
return `${operation}-active`;
|
|
184
|
+
if (current.turn === "waiting")
|
|
185
|
+
return `waiting-${current.waitingReason ?? "external"}`;
|
|
186
|
+
if (current.session === "ready" || current.turn === "completed"
|
|
187
|
+
|| current.turn === "failed" || current.turn === "cancelled")
|
|
188
|
+
return "ready";
|
|
189
|
+
if (current.turn === "accepted" || current.session === "active") {
|
|
190
|
+
return current.activity.kind === "model" ? "model-active" : "active-quiet";
|
|
191
|
+
}
|
|
192
|
+
if (current.session === "started")
|
|
193
|
+
return "awaiting-provider-acceptance";
|
|
194
|
+
if (current.host === "alive")
|
|
195
|
+
return "runtime-unobservable";
|
|
196
|
+
return "starting";
|
|
197
|
+
}
|
|
198
|
+
export function evaluateRuntimeAttention(current, now, policy) {
|
|
199
|
+
const display = runtimeDisplayStatus(current);
|
|
200
|
+
const runtimeIdleMs = current.lastRuntimeActivityAt === undefined
|
|
201
|
+
? Number.POSITIVE_INFINITY
|
|
202
|
+
: now.getTime() - Date.parse(current.lastRuntimeActivityAt);
|
|
203
|
+
const semanticIdleMs = now.getTime() - Date.parse(current.workflow.lastSemanticProgressAt);
|
|
204
|
+
const runtime = display === "broken"
|
|
205
|
+
? "broken"
|
|
206
|
+
: display === "stopped"
|
|
207
|
+
? "stopped"
|
|
208
|
+
: display === "runtime-unobservable"
|
|
209
|
+
? "unobservable"
|
|
210
|
+
: display.startsWith("waiting-")
|
|
211
|
+
? "waiting"
|
|
212
|
+
: runtimeIdleMs <= policy.runtimeSilenceMs
|
|
213
|
+
? "healthy"
|
|
214
|
+
: Object.keys(current.operations).length > 0
|
|
215
|
+
? "active-operation-quiet"
|
|
216
|
+
: "quiet";
|
|
217
|
+
return Object.freeze({
|
|
218
|
+
runtime,
|
|
219
|
+
workflow: current.workflow.completed
|
|
220
|
+
? "completed"
|
|
221
|
+
: semanticIdleMs > policy.semanticSilenceMs
|
|
222
|
+
? "not-progressing"
|
|
223
|
+
: "progressing"
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
function withActivity(current, kind, at) {
|
|
227
|
+
return next(current, {
|
|
228
|
+
activity: Object.freeze({ kind, observedAt: at }),
|
|
229
|
+
lastRuntimeActivityAt: maxTimestamp(current.lastRuntimeActivityAt, at)
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function next(current, patch) {
|
|
233
|
+
const copy = { ...current, ...patch };
|
|
234
|
+
if (patch.waitingReason === undefined && Object.hasOwn(patch, "waitingReason")) {
|
|
235
|
+
delete copy.waitingReason;
|
|
236
|
+
}
|
|
237
|
+
if (patch.waitId === undefined && Object.hasOwn(patch, "waitId"))
|
|
238
|
+
delete copy.waitId;
|
|
239
|
+
return Object.freeze(copy);
|
|
240
|
+
}
|
|
241
|
+
function usageAdvanced(previous, current) {
|
|
242
|
+
// A first cumulative snapshot may contain history from a resumed native
|
|
243
|
+
// Session. It establishes the generation baseline but cannot prove that
|
|
244
|
+
// tokens were consumed during the current observation window.
|
|
245
|
+
if (previous === undefined)
|
|
246
|
+
return false;
|
|
247
|
+
return usageTotal(current) > usageTotal(previous);
|
|
248
|
+
}
|
|
249
|
+
function usageTotal(usage) {
|
|
250
|
+
// cachedInputTokens and reasoningTokens are breakdowns of the normalized
|
|
251
|
+
// input/output totals, not additional usage.
|
|
252
|
+
return usage.inputTokens + usage.outputTokens;
|
|
253
|
+
}
|
|
254
|
+
function dominantOperation(operations) {
|
|
255
|
+
const kinds = Object.values(operations).map(({ kind }) => kind);
|
|
256
|
+
if (kinds.includes("subagent"))
|
|
257
|
+
return "subagent";
|
|
258
|
+
if (kinds.includes("tool"))
|
|
259
|
+
return "tool";
|
|
260
|
+
if (kinds.includes("model"))
|
|
261
|
+
return "model";
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
function terminalTurn(current, fallback = "cancelled") {
|
|
265
|
+
return current === "completed" || current === "failed" || current === "cancelled"
|
|
266
|
+
? current
|
|
267
|
+
: fallback;
|
|
268
|
+
}
|
|
269
|
+
function maxTimestamp(left, right) {
|
|
270
|
+
return left === undefined || Date.parse(right) > Date.parse(left) ? right : left;
|
|
271
|
+
}
|
|
272
|
+
function requireTimestamp(value) {
|
|
273
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value))) {
|
|
274
|
+
throw new Error("Runtime projection timestamp is invalid.");
|
|
275
|
+
}
|
|
276
|
+
return value;
|
|
277
|
+
}
|