@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
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
3
3
|
import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
|
|
4
|
-
|
|
4
|
+
import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
5
|
+
import { evaluateRuntimeAttention, projectRuntimeObservation, projectRuntimeTaskEvents, runtimeDisplayStatus } from "../runtime/runtimeProjection.js";
|
|
6
|
+
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
7
|
+
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
8
|
+
export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
|
|
5
9
|
const taskOpenInputRequestCount = store.listInputRequests(taskId)
|
|
6
10
|
.filter((request) => request.status === "open").length;
|
|
7
11
|
const panesByRole = new Map();
|
|
@@ -10,7 +14,7 @@ export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes) {
|
|
|
10
14
|
if (current === undefined || current.dead && !pane.dead)
|
|
11
15
|
panesByRole.set(pane.roleName, pane);
|
|
12
16
|
}
|
|
13
|
-
return roles.map((role) => inspectTaskRoleRuntimeStatus(taskId, role, store, panesByRole.get(role.name), role.name === "leader" ? taskOpenInputRequestCount : 0));
|
|
17
|
+
return roles.map((role) => inspectTaskRoleRuntimeStatus(taskId, role, store, panesByRole.get(role.name), role.name === "leader" ? taskOpenInputRequestCount : 0, now));
|
|
14
18
|
}
|
|
15
19
|
export function renderTaskRoleRuntimeStatus(status) {
|
|
16
20
|
const activeRun = status.activeRun === null
|
|
@@ -36,6 +40,23 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
36
40
|
const workspaceDetails = status.workspace.managed
|
|
37
41
|
? status.workspace.entries.map((entry) => (` Project ${entry.directory} (${entry.access}) ${entry.branch} @ ${entry.baseCommit}`))
|
|
38
42
|
: [];
|
|
43
|
+
const runtime = status.runtime === null
|
|
44
|
+
? "not observable"
|
|
45
|
+
: [
|
|
46
|
+
`${status.runtime.driverId}: ${status.runtime.status}`,
|
|
47
|
+
`attention=${status.runtime.attention}`,
|
|
48
|
+
status.runtime.lastActivityAt === undefined
|
|
49
|
+
? undefined
|
|
50
|
+
: `last activity=${status.runtime.lastActivityAt}`,
|
|
51
|
+
status.runtime.activeOperations.length === 0
|
|
52
|
+
? undefined
|
|
53
|
+
: `operations=${status.runtime.activeOperations.join(",")}`,
|
|
54
|
+
status.runtime.observerStatus === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: `observer=${status.runtime.observerStatus}${status.runtime.observerDetail === undefined
|
|
57
|
+
? ""
|
|
58
|
+
: ` (${status.runtime.observerDetail})`}`
|
|
59
|
+
].filter((value) => value !== undefined).join("; ");
|
|
39
60
|
return [
|
|
40
61
|
`Task Role status: ${status.taskId}/${status.roleName}`,
|
|
41
62
|
"",
|
|
@@ -53,9 +74,10 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
53
74
|
` Active work ${activeWork}`,
|
|
54
75
|
` Active run ${activeRun}`,
|
|
55
76
|
` Run attention ${status.stall.active
|
|
56
|
-
? `needs-attention (${status.stall.kind ?? "
|
|
77
|
+
? `needs-attention (${status.stall.kind ?? "workflow-not-progressing"}; no workflow progress since ${status.stall.progressAt ?? "unknown"})`
|
|
57
78
|
: "none"}`,
|
|
58
79
|
` Native session ${nativeSession}`,
|
|
80
|
+
` Agent runtime ${runtime}`,
|
|
59
81
|
` Runtime cleanup ${status.runtimeCleanupPending ? "pending" : "none"}`,
|
|
60
82
|
` Fresh launch ${status.freshLaunchAllowed ? "allowed" : "blocked"}`,
|
|
61
83
|
` tmux pane ${tmux}`,
|
|
@@ -97,7 +119,7 @@ export function taskRoleTmuxLabel(status) {
|
|
|
97
119
|
? "running"
|
|
98
120
|
: `running (${status.tmux.currentCommand})`;
|
|
99
121
|
}
|
|
100
|
-
function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputRequestCount) {
|
|
122
|
+
function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputRequestCount, now) {
|
|
101
123
|
const activeRun = store.getActiveAgentRun(taskId, role.name);
|
|
102
124
|
const activeWork = activeRun?.workItemId === undefined
|
|
103
125
|
? null
|
|
@@ -132,6 +154,7 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
132
154
|
? { managed: false, path: role.workspace }
|
|
133
155
|
: { ...managedWorkspace, managed: true };
|
|
134
156
|
const events = store.listEvents(taskId);
|
|
157
|
+
const runtime = projectTaskRoleRuntime(activeRun, nativeSession, tmux, events, now);
|
|
135
158
|
const stalled = activeRun !== null && isRoleRunStalled(events, activeRun.id);
|
|
136
159
|
const stallProgressAt = activeRun === null
|
|
137
160
|
? undefined
|
|
@@ -139,7 +162,7 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
139
162
|
const stallKind = activeRun === null
|
|
140
163
|
? undefined
|
|
141
164
|
: latestStallKind(events, activeRun.id);
|
|
142
|
-
const health = calculateHealth(role, activeRun, nativeSession, recovery.runtimeCleanupPending, tmux, openInputRequestCount, stalled);
|
|
165
|
+
const health = calculateHealth(role, activeRun, nativeSession, recovery.runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime);
|
|
143
166
|
const stall = activeRun === null
|
|
144
167
|
? { active: false }
|
|
145
168
|
: {
|
|
@@ -165,6 +188,7 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
|
|
|
165
188
|
nativeSession,
|
|
166
189
|
tmux,
|
|
167
190
|
workspace,
|
|
191
|
+
runtime,
|
|
168
192
|
stall
|
|
169
193
|
};
|
|
170
194
|
}
|
|
@@ -172,11 +196,11 @@ function latestStallKind(events, runId) {
|
|
|
172
196
|
const event = [...events]
|
|
173
197
|
.filter((candidate) => candidate.type === "run.stalled" && candidate.payload.runId === runId)
|
|
174
198
|
.sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
|
|
175
|
-
return event?.payload.kind === "delivery-stalled" || event?.payload.kind === "
|
|
199
|
+
return event?.payload.kind === "delivery-stalled" || event?.payload.kind === "workflow-not-progressing"
|
|
176
200
|
? event.payload.kind
|
|
177
201
|
: undefined;
|
|
178
202
|
}
|
|
179
|
-
function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending, tmux, openInputRequestCount, stalled) {
|
|
203
|
+
function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime) {
|
|
180
204
|
if (runtimeCleanupPending && nativeSession === null) {
|
|
181
205
|
return {
|
|
182
206
|
health: "needs-attention",
|
|
@@ -217,6 +241,49 @@ function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending,
|
|
|
217
241
|
healthReason: "the live active Run has no durable progress in the configured stall window"
|
|
218
242
|
};
|
|
219
243
|
}
|
|
244
|
+
if (activeRun.deliveredAt !== undefined) {
|
|
245
|
+
if (runtime === null
|
|
246
|
+
|| runtime.status === "runtime-unobservable"
|
|
247
|
+
|| runtime.attention === "unobservable") {
|
|
248
|
+
return {
|
|
249
|
+
health: "needs-attention",
|
|
250
|
+
healthReason: "the host is present but the Agent Driver exposes no current runtime state"
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (runtime.attention === "quiet" || runtime.attention === "active-operation-quiet") {
|
|
254
|
+
return {
|
|
255
|
+
health: "needs-attention",
|
|
256
|
+
healthReason: runtime.attention === "active-operation-quiet"
|
|
257
|
+
? "the Agent Driver reports an open operation but no recent structured runtime activity"
|
|
258
|
+
: "the Agent Driver has not reported recent structured runtime activity"
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (runtime.status === "broken" || runtime.status === "stopped") {
|
|
262
|
+
return {
|
|
263
|
+
health: "failed",
|
|
264
|
+
healthReason: `the Agent Driver runtime is ${runtime.status}`
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (runtime.status.startsWith("waiting-")) {
|
|
268
|
+
return {
|
|
269
|
+
health: runtime.status === "waiting-user" ? "blocked-input" : "waiting",
|
|
270
|
+
healthReason: `the Agent Driver is ${runtime.status.replaceAll("-", " ")}`
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (runtime.status === "ready") {
|
|
274
|
+
return {
|
|
275
|
+
health: "needs-attention",
|
|
276
|
+
healthReason: "the Agent turn ended while the workflow Run is still active"
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (["model-active", "tool-active", "subagent-active", "active-quiet"]
|
|
280
|
+
.includes(runtime.status)) {
|
|
281
|
+
return {
|
|
282
|
+
health: "running",
|
|
283
|
+
healthReason: `the Agent Driver reports ${runtime.status.replaceAll("-", " ")}`
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
}
|
|
220
287
|
}
|
|
221
288
|
if (activeRun === null && role.status === "running") {
|
|
222
289
|
return { health: "needs-attention", healthReason: "the Role is running without an active Run" };
|
|
@@ -234,9 +301,11 @@ function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending,
|
|
|
234
301
|
};
|
|
235
302
|
}
|
|
236
303
|
if (activeRun !== null) {
|
|
237
|
-
if (activeRun.deliveredAt !== undefined)
|
|
238
|
-
return {
|
|
239
|
-
|
|
304
|
+
if (activeRun.deliveredAt !== undefined)
|
|
305
|
+
return {
|
|
306
|
+
health: "needs-attention",
|
|
307
|
+
healthReason: "the delivered Run has no authoritative Agent Driver state"
|
|
308
|
+
};
|
|
240
309
|
if (activeRun.pushedAt !== undefined) {
|
|
241
310
|
return {
|
|
242
311
|
health: "awaiting-provider-acceptance",
|
|
@@ -249,6 +318,97 @@ function calculateHealth(role, activeRun, nativeSession, runtimeCleanupPending,
|
|
|
249
318
|
? { health: "ready", healthReason: "the native Agent pane is ready without active work" }
|
|
250
319
|
: { health: "idle", healthReason: "there is no active work or live tmux pane" };
|
|
251
320
|
}
|
|
321
|
+
function projectTaskRoleRuntime(run, session, tmux, events, now) {
|
|
322
|
+
if (run === null || session?.launchId === undefined)
|
|
323
|
+
return null;
|
|
324
|
+
let driverId;
|
|
325
|
+
try {
|
|
326
|
+
driverId = builtinDriverIdForAdapter(run.effective.adapterId);
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const fence = {
|
|
332
|
+
taskId: run.taskId,
|
|
333
|
+
roleName: run.roleName,
|
|
334
|
+
runId: run.id,
|
|
335
|
+
agentId: run.effective.agentId,
|
|
336
|
+
driverId,
|
|
337
|
+
launchId: session.launchId,
|
|
338
|
+
sessionGenerationId: session.launchId,
|
|
339
|
+
nativeSessionId: session.nativeSessionId,
|
|
340
|
+
nativeTurnId: runtimeNativeTurnId(events, {
|
|
341
|
+
taskId: run.taskId,
|
|
342
|
+
roleName: run.roleName,
|
|
343
|
+
runId: run.id,
|
|
344
|
+
agentId: run.effective.agentId,
|
|
345
|
+
driverId,
|
|
346
|
+
launchId: session.launchId,
|
|
347
|
+
nativeSessionId: session.nativeSessionId,
|
|
348
|
+
receiptId: formatAgentRunReceiptId(run.taskId, run.id)
|
|
349
|
+
}) ?? run.id,
|
|
350
|
+
receiptId: formatAgentRunReceiptId(run.taskId, run.id)
|
|
351
|
+
};
|
|
352
|
+
let projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
|
|
353
|
+
projection = projectRuntimeObservation(projection, createRuntimeObservation({
|
|
354
|
+
schemaVersion: 1,
|
|
355
|
+
eventId: `runtime-host-${run.id}`,
|
|
356
|
+
kind: "host.observed",
|
|
357
|
+
authority: "host",
|
|
358
|
+
receivedAt: run.updatedAt,
|
|
359
|
+
fence,
|
|
360
|
+
payload: { alive: tmux.state === "running" }
|
|
361
|
+
}));
|
|
362
|
+
const attention = evaluateRuntimeAttention(projection, now, {
|
|
363
|
+
runtimeSilenceMs: 5 * 60_000,
|
|
364
|
+
// Workflow attention has its own durable scheduler policy. This value is
|
|
365
|
+
// deliberately not consumed here; keeping it separate prevents token/tool
|
|
366
|
+
// activity from extending the workflow deadline.
|
|
367
|
+
semanticSilenceMs: 30 * 60_000
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
driverId,
|
|
371
|
+
status: runtimeDisplayStatus(projection),
|
|
372
|
+
attention: attention.runtime,
|
|
373
|
+
...(projection.lastRuntimeActivityAt === undefined
|
|
374
|
+
? {}
|
|
375
|
+
: { lastActivityAt: projection.lastRuntimeActivityAt }),
|
|
376
|
+
activeOperations: Object.entries(projection.operations).map(([id, operation]) => (`${operation.kind}:${id}`)),
|
|
377
|
+
...(projection.waitingReason === undefined
|
|
378
|
+
? {}
|
|
379
|
+
: { waitingReason: projection.waitingReason }),
|
|
380
|
+
...(projection.usage === undefined ? {} : { usage: projection.usage }),
|
|
381
|
+
...(projection.observer.status === "unknown"
|
|
382
|
+
? {}
|
|
383
|
+
: {
|
|
384
|
+
observerStatus: projection.observer.status,
|
|
385
|
+
...(projection.observer.detail === undefined
|
|
386
|
+
? {}
|
|
387
|
+
: { observerDetail: projection.observer.detail })
|
|
388
|
+
})
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
function runtimeNativeTurnId(events, expected) {
|
|
392
|
+
const observations = events
|
|
393
|
+
.map(runtimeObservationFromTaskEvent)
|
|
394
|
+
.filter((observation) => observation !== null
|
|
395
|
+
&& observation.fence.taskId === expected.taskId
|
|
396
|
+
&& observation.fence.roleName === expected.roleName
|
|
397
|
+
&& observation.fence.runId === expected.runId
|
|
398
|
+
&& observation.fence.agentId === expected.agentId
|
|
399
|
+
&& observation.fence.driverId === expected.driverId
|
|
400
|
+
&& observation.fence.launchId === expected.launchId
|
|
401
|
+
&& observation.fence.nativeSessionId === expected.nativeSessionId
|
|
402
|
+
&& observation.fence.receiptId === expected.receiptId
|
|
403
|
+
&& observation.fence.nativeTurnId !== undefined)
|
|
404
|
+
.sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
|
|
405
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
406
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
407
|
+
|| left.eventId.localeCompare(right.eventId)));
|
|
408
|
+
return observations.filter(({ kind }) => kind === "turn.accepted").at(-1)
|
|
409
|
+
?.fence.nativeTurnId
|
|
410
|
+
?? observations.at(-1)?.fence.nativeTurnId;
|
|
411
|
+
}
|
|
252
412
|
function activeRunDeliveryLabel(run) {
|
|
253
413
|
if (run.deliveredAt !== undefined)
|
|
254
414
|
return "delivered";
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
3
|
+
import { createRuntimeObservation, runtimeObservationFenceMatches, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
4
|
+
/**
|
|
5
|
+
* Controller-owned, provider-independent sampler. Drivers own source parsing;
|
|
6
|
+
* this component owns active-Run discovery, cursor lifetime, canonical event
|
|
7
|
+
* creation, and low-latency mailbox wakes.
|
|
8
|
+
*/
|
|
9
|
+
export class AgentRuntimeObserver {
|
|
10
|
+
store;
|
|
11
|
+
inbox;
|
|
12
|
+
drivers;
|
|
13
|
+
#states = new Map();
|
|
14
|
+
#sequence = 0;
|
|
15
|
+
constructor(store, inbox, drivers = builtinAgentDriverRegistry()) {
|
|
16
|
+
this.store = store;
|
|
17
|
+
this.inbox = inbox;
|
|
18
|
+
this.drivers = drivers;
|
|
19
|
+
}
|
|
20
|
+
async sample(now = new Date()) {
|
|
21
|
+
const active = this.activeSources();
|
|
22
|
+
const activeKeys = new Set(active.map(({ key }) => key));
|
|
23
|
+
for (const key of this.#states.keys()) {
|
|
24
|
+
if (!activeKeys.has(key))
|
|
25
|
+
this.#states.delete(key);
|
|
26
|
+
}
|
|
27
|
+
const dirty = new Set();
|
|
28
|
+
await Promise.all(active.map(async ({ key, fence, source, freshSession, persistedState }) => {
|
|
29
|
+
const existingState = this.#states.get(key);
|
|
30
|
+
// Cursor state is intentionally process-local, but the latest canonical
|
|
31
|
+
// usage/activity baseline is durable. Rehydrate it after Controller
|
|
32
|
+
// restart so rereading the bounded transcript tail cannot manufacture a
|
|
33
|
+
// fresh activity edge from tokens that were already observed.
|
|
34
|
+
const state = existingState ?? { ...persistedState };
|
|
35
|
+
const driver = this.drivers.require(fence.driverId);
|
|
36
|
+
const observer = driver.runtime.observer;
|
|
37
|
+
if (observer === undefined)
|
|
38
|
+
return;
|
|
39
|
+
let sample;
|
|
40
|
+
try {
|
|
41
|
+
sample = await observer.sample(source, state.cursor);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
sample = Object.freeze({
|
|
45
|
+
cursor: state.cursor ?? Object.freeze({}),
|
|
46
|
+
status: "unavailable",
|
|
47
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
state.cursor = sample.cursor;
|
|
51
|
+
const at = now.toISOString();
|
|
52
|
+
const sequence = this.#sequence++;
|
|
53
|
+
if (existingState === undefined && freshSession && state.usage === undefined) {
|
|
54
|
+
const zero = Object.freeze({ inputTokens: 0, outputTokens: 0 });
|
|
55
|
+
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
eventId: observationId("baseline", fence, source.sourceId, "zero"),
|
|
58
|
+
kind: "activity.observed",
|
|
59
|
+
authority: "controller",
|
|
60
|
+
receivedAt: at,
|
|
61
|
+
sequence,
|
|
62
|
+
ordinal: 1,
|
|
63
|
+
fence,
|
|
64
|
+
payload: { activity: "model", usage: zero }
|
|
65
|
+
}));
|
|
66
|
+
state.usage = zero;
|
|
67
|
+
dirty.add(`role:${fence.taskId}/${fence.roleName}`);
|
|
68
|
+
}
|
|
69
|
+
const health = JSON.stringify([sample.status, sample.detail ?? null]);
|
|
70
|
+
if (state.health !== health) {
|
|
71
|
+
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
72
|
+
schemaVersion: 1,
|
|
73
|
+
eventId: observationId("health", fence, source.sourceId, health),
|
|
74
|
+
kind: "observer.health",
|
|
75
|
+
authority: "diagnostic",
|
|
76
|
+
receivedAt: at,
|
|
77
|
+
sequence,
|
|
78
|
+
ordinal: 0,
|
|
79
|
+
fence,
|
|
80
|
+
payload: {
|
|
81
|
+
sourceId: source.sourceId,
|
|
82
|
+
observerStatus: sample.status,
|
|
83
|
+
...(sample.detail === undefined ? {} : { observerDetail: sample.detail })
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
state.health = health;
|
|
87
|
+
dirty.add(`role:${fence.taskId}/${fence.roleName}`);
|
|
88
|
+
}
|
|
89
|
+
const usageChanged = sample.usage !== undefined
|
|
90
|
+
&& !sameUsage(state.usage, sample.usage);
|
|
91
|
+
const activityChanged = sample.activityId !== undefined
|
|
92
|
+
&& sample.activityId !== state.activityId;
|
|
93
|
+
if (usageChanged || (activityChanged && state.cursor !== undefined)) {
|
|
94
|
+
const usage = sample.usage;
|
|
95
|
+
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
96
|
+
schemaVersion: 1,
|
|
97
|
+
eventId: observationId("activity", fence, source.sourceId, JSON.stringify([usage ?? null, sample.activityId ?? null])),
|
|
98
|
+
kind: "activity.observed",
|
|
99
|
+
authority: "driver-inferred",
|
|
100
|
+
receivedAt: at,
|
|
101
|
+
sequence,
|
|
102
|
+
ordinal: 2,
|
|
103
|
+
fence,
|
|
104
|
+
payload: {
|
|
105
|
+
activity: sample.activity ?? "model",
|
|
106
|
+
...(sample.activityId === undefined ? {} : { activityId: sample.activityId }),
|
|
107
|
+
...(usage === undefined ? {} : { usage })
|
|
108
|
+
}
|
|
109
|
+
}));
|
|
110
|
+
dirty.add(`role:${fence.taskId}/${fence.roleName}`);
|
|
111
|
+
}
|
|
112
|
+
if (sample.usage !== undefined)
|
|
113
|
+
state.usage = sample.usage;
|
|
114
|
+
if (sample.activityId !== undefined)
|
|
115
|
+
state.activityId = sample.activityId;
|
|
116
|
+
this.#states.set(key, state);
|
|
117
|
+
}));
|
|
118
|
+
return Object.freeze([...dirty].sort());
|
|
119
|
+
}
|
|
120
|
+
activeSources() {
|
|
121
|
+
const result = [];
|
|
122
|
+
for (const task of this.store.listTasks()) {
|
|
123
|
+
if (task.status !== "active")
|
|
124
|
+
continue;
|
|
125
|
+
const observations = this.store.listEvents(task.id)
|
|
126
|
+
.map(runtimeObservationFromTaskEvent)
|
|
127
|
+
.filter((value) => value !== null);
|
|
128
|
+
for (const run of this.store.listAgentRuns(task.id)) {
|
|
129
|
+
if (run.status !== "active"
|
|
130
|
+
|| this.store.getActiveAgentRun(task.id, run.roleName)?.id !== run.id)
|
|
131
|
+
continue;
|
|
132
|
+
const accepted = observations
|
|
133
|
+
.filter((observation) => observation.kind === "turn.accepted"
|
|
134
|
+
&& observation.fence.runId === run.id
|
|
135
|
+
&& observation.fence.roleName === run.roleName
|
|
136
|
+
&& observation.fence.agentId === run.effective.agentId
|
|
137
|
+
&& observation.payload.observerSource !== undefined)
|
|
138
|
+
.sort(compareObservations)
|
|
139
|
+
.at(-1);
|
|
140
|
+
const source = accepted?.payload.observerSource;
|
|
141
|
+
if (accepted === undefined || source === undefined
|
|
142
|
+
|| accepted.fence.taskId === undefined || accepted.fence.runId === undefined)
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
if (this.drivers.require(accepted.fence.driverId).runtime.observer === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const fence = accepted.fence;
|
|
152
|
+
const exact = observations
|
|
153
|
+
.filter((observation) => runtimeObservationFenceMatches(fence, observation.fence))
|
|
154
|
+
.sort(compareObservations);
|
|
155
|
+
const persistedUsage = exact.filter((observation) => (observation.kind === "activity.observed"
|
|
156
|
+
&& observation.payload.usage !== undefined)).at(-1);
|
|
157
|
+
const persistedHealth = exact.filter((observation) => (observation.kind === "observer.health"
|
|
158
|
+
&& observation.payload.sourceId === source.sourceId)).at(-1);
|
|
159
|
+
result.push(Object.freeze({
|
|
160
|
+
key: JSON.stringify([
|
|
161
|
+
fence.driverId,
|
|
162
|
+
fence.sessionGenerationId,
|
|
163
|
+
fence.nativeSessionId,
|
|
164
|
+
fence.nativeTurnId,
|
|
165
|
+
fence.runId,
|
|
166
|
+
source.sourceId
|
|
167
|
+
]),
|
|
168
|
+
fence,
|
|
169
|
+
source,
|
|
170
|
+
freshSession: run.mode === "new",
|
|
171
|
+
persistedState: Object.freeze({
|
|
172
|
+
...(persistedUsage?.payload.usage === undefined
|
|
173
|
+
? {}
|
|
174
|
+
: { usage: persistedUsage.payload.usage }),
|
|
175
|
+
...(persistedUsage?.payload.activityId === undefined
|
|
176
|
+
? {}
|
|
177
|
+
: { activityId: persistedUsage.payload.activityId }),
|
|
178
|
+
...(persistedHealth === undefined
|
|
179
|
+
? {}
|
|
180
|
+
: {
|
|
181
|
+
health: JSON.stringify([
|
|
182
|
+
persistedHealth.payload.observerStatus,
|
|
183
|
+
persistedHealth.payload.observerDetail ?? null
|
|
184
|
+
])
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function compareObservations(left, right) {
|
|
194
|
+
return left.receivedAt.localeCompare(right.receivedAt)
|
|
195
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
196
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
197
|
+
|| left.eventId.localeCompare(right.eventId);
|
|
198
|
+
}
|
|
199
|
+
function observationId(kind, fence, sourceId, value) {
|
|
200
|
+
return `runtime-observer-${createHash("sha256")
|
|
201
|
+
.update(JSON.stringify([kind, fence, sourceId, value]))
|
|
202
|
+
.digest("hex")}`;
|
|
203
|
+
}
|
|
204
|
+
function sameUsage(left, right) {
|
|
205
|
+
return left !== undefined
|
|
206
|
+
&& left.inputTokens === right.inputTokens
|
|
207
|
+
&& left.outputTokens === right.outputTokens
|
|
208
|
+
&& left.cachedInputTokens === right.cachedInputTokens
|
|
209
|
+
&& left.reasoningTokens === right.reasoningTokens;
|
|
210
|
+
}
|
|
@@ -405,22 +405,6 @@ export class FileTaskWorkflowRuntime {
|
|
|
405
405
|
reconcileTask(taskId) {
|
|
406
406
|
void this.#prepareAndScan(taskId).catch(this.clientOptions.onError ?? (() => { }));
|
|
407
407
|
}
|
|
408
|
-
async prepareTaskRoleEnter(input) {
|
|
409
|
-
const task = this.store.getTask(input.taskId);
|
|
410
|
-
if (task?.status === "active" && this.workspacePreparer !== undefined) {
|
|
411
|
-
await this.workspacePreparer.prepareTaskWorkspace(task.id);
|
|
412
|
-
}
|
|
413
|
-
const environment = foregroundRoleEnvironment(this.store, { scope: "task", taskId: input.taskId, roleName: input.roleName }, this.clientOptions.environment ?? process.env);
|
|
414
|
-
await callFileTaskController(this.home, "runtime.ensure-role-session", {
|
|
415
|
-
scope: "task",
|
|
416
|
-
taskId: input.taskId,
|
|
417
|
-
roleName: input.roleName,
|
|
418
|
-
...(environment === undefined ? {} : { environment })
|
|
419
|
-
}, {
|
|
420
|
-
...this.clientOptions,
|
|
421
|
-
requestTimeoutMs: LIFECYCLE_REQUEST_TIMEOUT_MS
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
408
|
async stopTaskRoleSessions(taskId, roleNames) {
|
|
425
409
|
const targets = [];
|
|
426
410
|
for (const roleName of [...new Set(roleNames)]) {
|
|
@@ -508,7 +492,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
508
492
|
return this.tmux.inspectTaskRolePanes(taskId);
|
|
509
493
|
}
|
|
510
494
|
async prepareGlobalRoleEnter(roleName) {
|
|
511
|
-
const environment =
|
|
495
|
+
const environment = foregroundGlobalRoleEnvironment(this.store, roleName, this.clientOptions.environment ?? process.env);
|
|
512
496
|
await callFileTaskController(this.home, "runtime.ensure-role-session", {
|
|
513
497
|
scope: "global",
|
|
514
498
|
roleName,
|
|
@@ -532,10 +516,8 @@ export class FileTaskWorkflowRuntime {
|
|
|
532
516
|
}
|
|
533
517
|
}
|
|
534
518
|
const MANAGED_RUNTIME_ENVIRONMENT = new Set(YUI_MANAGED_RUNTIME_ENVIRONMENT_NAMES);
|
|
535
|
-
function
|
|
536
|
-
const role =
|
|
537
|
-
? store.getRole?.(owner.taskId, owner.roleName)
|
|
538
|
-
: store.getGlobalRole?.(owner.roleName);
|
|
519
|
+
function foregroundGlobalRoleEnvironment(store, roleName, source) {
|
|
520
|
+
const role = store.getGlobalRole?.(roleName);
|
|
539
521
|
if (role === null || role === undefined)
|
|
540
522
|
return undefined;
|
|
541
523
|
const agent = store.getConfiguredAgent?.(role.activeAgentId);
|