@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.
Files changed (61) hide show
  1. package/ARCHITECTURE.md +28 -4
  2. package/README.md +62 -24
  3. package/dist/agent/argumentPolicy.js +1 -1
  4. package/dist/agent/managedRuntimeEnvironment.js +1 -0
  5. package/dist/cli/commandCatalog.js +19 -9
  6. package/dist/cli/interactionPolicy.js +4 -2
  7. package/dist/cli.js +77 -32
  8. package/dist/commands/taskCommands.js +46 -11
  9. package/dist/commands/taskContextCommand.js +1 -1
  10. package/dist/commands/taskRoleRuntimeStatus.js +170 -10
  11. package/dist/controller/agentRuntimeObserver.js +210 -0
  12. package/dist/controller/clientRuntime.js +3 -21
  13. package/dist/controller/controller.js +47 -7
  14. package/dist/controller/fileSchedulerStoreAdapter.js +522 -388
  15. package/dist/controller/runtime.js +9 -3
  16. package/dist/controller/runtimeEventInbox.js +49 -295
  17. package/dist/controller/runtimeEventProcessor.js +184 -321
  18. package/dist/controller/runtimeHookRunFence.js +226 -0
  19. package/dist/controller/runtimeLaunchCoordinator.js +91 -26
  20. package/dist/controller/runtimeObservationHook.js +112 -0
  21. package/dist/core/controllerServer.js +5 -0
  22. package/dist/executor/agentAdapter.js +18 -3
  23. package/dist/executor/fileRoleLaunchPlanner.js +64 -15
  24. package/dist/executor/managedClaudeRunner.js +121 -0
  25. package/dist/observability/executionAudit.js +6 -3
  26. package/dist/repository/taskWorkspacePreparer.js +1 -4
  27. package/dist/run/providerRetryConfig.js +8 -3
  28. package/dist/runtime/agentDriver.js +229 -0
  29. package/dist/runtime/agentDriverObservation.js +57 -0
  30. package/dist/runtime/builtinAgentDrivers.js +235 -0
  31. package/dist/runtime/builtinTranscriptObserver.js +290 -0
  32. package/dist/runtime/builtinTranscriptUsage.js +97 -0
  33. package/dist/runtime/exactControlPlane.js +2 -2
  34. package/dist/runtime/index.js +1 -1
  35. package/dist/runtime/ports.js +12 -1
  36. package/dist/runtime/runtimeObservation.js +297 -0
  37. package/dist/runtime/runtimeProjection.js +277 -0
  38. package/dist/runtime/sessionTerminationGuard.js +78 -22
  39. package/dist/runtime/tmuxAdapters.js +35 -0
  40. package/dist/scheduler/activeRoleRunDelivery.js +28 -13
  41. package/dist/scheduler/leaderWakeupProcessor.js +21 -2
  42. package/dist/scheduler/roleRunLiveness.js +2 -2
  43. package/dist/scheduler/roleRunStall.js +62 -114
  44. package/dist/storage/migration/productionRegistry.js +41 -0
  45. package/dist/storage/sqliteStore.js +3 -3
  46. package/dist/storage/storageVersions.js +1 -1
  47. package/dist/telemetry/sqliteTelemetryStore.js +0 -28
  48. package/dist/telemetry/telemetryCompaction.js +1 -0
  49. package/dist/telemetry/telemetryConfig.js +4 -5
  50. package/dist/tmux/tmuxManager.js +136 -22
  51. package/dist/web/assets/client/view.js +1 -1
  52. package/dist/web/tmuxWebTerminal.js +17 -12
  53. package/dist/web/webSnapshot.js +1 -1
  54. package/dist/worktree/managedWorkspace.js +14 -0
  55. package/i18n/README.zh-CN.md +7 -5
  56. package/package.json +1 -1
  57. package/dist/controller/claudeLifecycleHook.js +0 -203
  58. package/dist/controller/codexLifecycleHook.js +0 -108
  59. package/dist/controller/providerHookRunFence.js +0 -156
  60. package/dist/lifecycle/providerLifecycleMapping.js +0 -190
  61. 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
- export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes) {
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 ?? "execution-stalled"}; no durable progress since ${status.stall.progressAt ?? "unknown"})`
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 === "execution-stalled"
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 { health: "running", healthReason: "the active Run has a live tmux pane" };
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 = foregroundRoleEnvironment(this.store, { scope: "global", roleName }, this.clientOptions.environment ?? process.env);
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 foregroundRoleEnvironment(store, owner, source) {
536
- const role = owner.scope === "task"
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);