@zq-silk/yui 0.15.7 → 0.15.8

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 (281) hide show
  1. package/ARCHITECTURE.md +192 -399
  2. package/README.md +127 -1149
  3. package/dist/agent/adapterCatalog.js +15 -2
  4. package/dist/agent/agent.js +23 -3
  5. package/dist/agent/argumentPolicy.js +7 -1
  6. package/dist/agent/connectionPlan.js +62 -0
  7. package/dist/agent/executionComponents.js +158 -0
  8. package/dist/agent/launchEnvironment.js +31 -3
  9. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  10. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  11. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  12. package/dist/brief/taskBrief.js +12 -0
  13. package/dist/cli/agentConfigurationPicker.js +13 -0
  14. package/dist/cli/commandCatalog.js +157 -70
  15. package/dist/cli/interactionCandidates.js +5 -5
  16. package/dist/cli/interactionPolicy.js +38 -8
  17. package/dist/cli/invocationRouter.js +1 -1
  18. package/dist/cli/managedDiagnostics.js +28 -0
  19. package/dist/cli/operatorWizard.js +1 -7
  20. package/dist/cli/roleOptionOrder.js +27 -0
  21. package/dist/cli/roleWizard.js +50 -14
  22. package/dist/cli/updateOrchestrator.js +1 -1
  23. package/dist/cli/updatePorts.js +3 -4
  24. package/dist/cli.js +178 -95
  25. package/dist/commands/agentCommands.js +72 -14
  26. package/dist/commands/capabilityCommands.js +9 -6
  27. package/dist/commands/configCommands.js +20 -20
  28. package/dist/commands/deliveryGuardPreflight.js +2 -2
  29. package/dist/commands/executionAuditCommands.js +24 -24
  30. package/dist/commands/globalRoleCommands.js +1 -1
  31. package/dist/commands/grantCommands.js +4 -4
  32. package/dist/commands/operatorCommands.js +1 -7
  33. package/dist/commands/projectCommands.js +4 -4
  34. package/dist/commands/resourcesCommands.js +2 -2
  35. package/dist/commands/roleConfiguration.js +25 -5
  36. package/dist/commands/roleRuntimeGuard.js +4 -5
  37. package/dist/commands/sessionCommands.js +3 -7
  38. package/dist/commands/taskActivationCommands.js +259 -0
  39. package/dist/commands/taskActor.js +28 -49
  40. package/dist/commands/taskCommands.js +1188 -710
  41. package/dist/commands/taskContextCommand.js +39 -583
  42. package/dist/commands/taskExecutionCommands.js +32 -32
  43. package/dist/commands/taskInputCommands.js +40 -104
  44. package/dist/commands/taskIntegrationCommands.js +3 -2
  45. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  46. package/dist/commands/taskNextActionCommand.js +8 -8
  47. package/dist/commands/taskOverviewCommand.js +33 -45
  48. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  49. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  50. package/dist/commands/telemetryCommands.js +36 -38
  51. package/dist/config/configCatalog.js +4 -4
  52. package/dist/config/yuiConfig.js +8 -8
  53. package/dist/context/contextSnapshot.js +10 -10
  54. package/dist/context/dispatchContext.js +11 -11
  55. package/dist/context/roleSessionContext.js +6 -3
  56. package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
  57. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  58. package/dist/context/sessionBootstrapManifest.js +21 -2
  59. package/dist/context/sourceRunContext.js +30 -0
  60. package/dist/context/taskContext.js +458 -0
  61. package/dist/context/wakeNotification.js +27 -27
  62. package/dist/controller/agentRuntimeObserver.js +21 -24
  63. package/dist/controller/capabilityBridge.js +17 -6
  64. package/dist/controller/clientRuntime.js +65 -92
  65. package/dist/controller/controller.js +114 -188
  66. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  67. package/dist/controller/jobControl.js +54 -85
  68. package/dist/controller/resourceInventory.js +8 -27
  69. package/dist/controller/resourceInventoryLinux.js +12 -13
  70. package/dist/controller/runtime.js +529 -479
  71. package/dist/controller/runtimeEventInbox.js +55 -25
  72. package/dist/controller/runtimeEventProcessor.js +22 -31
  73. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  74. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  75. package/dist/controller/runtimeObservationHook.js +14 -18
  76. package/dist/controller/sessionNotify.js +16 -24
  77. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  78. package/dist/controller/structuredProviderObservation.js +138 -99
  79. package/dist/coordination/workMailbox.js +3 -3
  80. package/dist/coordination/workMailboxQueue.js +36 -33
  81. package/dist/core/boundedRpc.js +8 -1
  82. package/dist/core/controllerClient.js +20 -1
  83. package/dist/core/controllerServer.js +4 -4
  84. package/dist/doctor/doctor.js +13 -2
  85. package/dist/domain/agentResultTransport.js +9 -9
  86. package/dist/execution/codexThreadNaming.js +2 -8
  87. package/dist/execution/executionHealth.js +51 -63
  88. package/dist/execution/reviewMainRun.js +137 -0
  89. package/dist/execution/workItemExecution.js +28 -29
  90. package/dist/execution/workItemExecutionProjection.js +99 -107
  91. package/dist/execution/workItemMainRun.js +141 -0
  92. package/dist/executor/agentAdapter.js +227 -20
  93. package/dist/executor/agentConfigurationCatalog.js +126 -4
  94. package/dist/executor/agentConfigurationProbe.js +162 -4
  95. package/dist/executor/agentExecutor.js +79 -78
  96. package/dist/executor/effectiveLaunch.js +105 -18
  97. package/dist/executor/executorRegistry.js +29 -44
  98. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  99. package/dist/executor/workspacePreflightClassification.js +16 -16
  100. package/dist/grant/capabilityGrant.js +6 -3
  101. package/dist/input/inputRequest.js +12 -10
  102. package/dist/integration/gitIntegrationService.js +4 -11
  103. package/dist/integration/integrationQueueService.js +4 -4
  104. package/dist/interaction/operatorPresentation.js +1 -1
  105. package/dist/kernel/builtinCapabilities.js +247 -12
  106. package/dist/kernel/capabilityRegistry.js +64 -18
  107. package/dist/kernel/instanceHost.js +12 -1
  108. package/dist/kernel/kernelPorts.js +2 -2
  109. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  110. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  111. package/dist/message/message.js +62 -6
  112. package/dist/message/messageContinuation.js +204 -0
  113. package/dist/observability/executionAudit.js +70 -72
  114. package/dist/observability/faultClassification.js +2 -2
  115. package/dist/observability/orchestrationMetrics.js +8 -8
  116. package/dist/operator/operatorSessionHistory.js +1 -7
  117. package/dist/output/agentConfigurationPresentation.js +8 -3
  118. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  119. package/dist/output/rolePresentation.js +54 -3
  120. package/dist/plugins/pluginChild.js +104 -0
  121. package/dist/plugins/pluginIntent.js +26 -0
  122. package/dist/plugins/pluginInterpreter.js +43 -0
  123. package/dist/plugins/pluginPackage.js +101 -0
  124. package/dist/plugins/pluginProcess.js +112 -0
  125. package/dist/plugins/pluginService.js +380 -0
  126. package/dist/profile/agentProfile.js +1 -1
  127. package/dist/repository/gitWorkspace.js +26 -4
  128. package/dist/repository/project.js +19 -4
  129. package/dist/repository/taskBaseFreshness.js +13 -13
  130. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  131. package/dist/repository/taskWorkspacePreparer.js +344 -83
  132. package/dist/resources/autoResourceGc.js +3 -3
  133. package/dist/resources/liveReferences.js +3 -3
  134. package/dist/resources/projectResource.js +123 -0
  135. package/dist/resources/projectResourceService.js +421 -0
  136. package/dist/resources/resourceDiscovery.js +6 -6
  137. package/dist/resources/resourceGc.js +1 -1
  138. package/dist/resources/resourceRegistrar.js +1 -1
  139. package/dist/resources/resourceTypes.js +1 -1
  140. package/dist/review/deltaRecheck.js +3 -3
  141. package/dist/review/reviewAcceptance.js +16 -16
  142. package/dist/review/reviewDecision.js +7 -7
  143. package/dist/review/reviewRound.js +21 -20
  144. package/dist/review/reviewerAvailability.js +2 -2
  145. package/dist/role/role.js +51 -7
  146. package/dist/role/taskRoleUpdate.js +30 -0
  147. package/dist/runtime/acpProtocol.js +425 -0
  148. package/dist/runtime/acpSession.js +731 -0
  149. package/dist/runtime/acpSessionConfiguration.js +260 -0
  150. package/dist/runtime/agentDriver.js +30 -11
  151. package/dist/runtime/agentEndpoint.js +278 -0
  152. package/dist/runtime/agentEndpointIdentity.js +86 -0
  153. package/dist/runtime/agentEndpointOwnership.js +239 -0
  154. package/dist/runtime/agentError.js +2 -10
  155. package/dist/runtime/agentHost.js +565 -314
  156. package/dist/runtime/agentRunConfiguration.js +258 -0
  157. package/dist/runtime/builtinAgentDrivers.js +134 -18
  158. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  159. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  160. package/dist/runtime/claude-process-owner +0 -0
  161. package/dist/runtime/codexAppServerRuntime.js +38 -30
  162. package/dist/runtime/codexInteractiveHost.js +41 -6
  163. package/dist/runtime/continuationManager.js +2 -6
  164. package/dist/runtime/executionEnvironment.js +30 -0
  165. package/dist/runtime/firstProgressAdvisory.js +11 -11
  166. package/dist/runtime/index.js +4 -3
  167. package/dist/runtime/jsonLineChannel.js +109 -0
  168. package/dist/runtime/launchBroker.js +91 -16
  169. package/dist/runtime/launchDiagnostics.js +2 -2
  170. package/dist/runtime/lifecycleReservation.js +10 -18
  171. package/dist/runtime/managedCaller.js +61 -17
  172. package/dist/runtime/nativeSessionControl.js +102 -0
  173. package/dist/runtime/ports.js +6 -21
  174. package/dist/runtime/processExitObservation.js +8 -7
  175. package/dist/runtime/promptEnvelope.js +17 -6
  176. package/dist/runtime/providerContinuation.js +3 -9
  177. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  178. package/dist/runtime/providerControl.js +2 -7
  179. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  180. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  181. package/dist/runtime/runtimeBinding.js +0 -1
  182. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  183. package/dist/runtime/runtimeDeadlines.js +9 -0
  184. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  185. package/dist/runtime/runtimeObservation.js +29 -65
  186. package/dist/runtime/runtimeProjection.js +43 -51
  187. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  188. package/dist/runtime/sessionLaunchRequest.js +3 -7
  189. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  190. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  191. package/dist/runtime/sessionReconciliation.js +4 -8
  192. package/dist/runtime/sessionTerminationGuard.js +70 -259
  193. package/dist/runtime/sessionTokenMetrics.js +5 -16
  194. package/dist/runtime/structuredProviderHost.js +237 -117
  195. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  196. package/dist/runtime/tmuxAdapters.js +39 -86
  197. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  198. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  199. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  200. package/dist/scheduler/ports.js +80 -9
  201. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  202. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  203. package/dist/scheduler/taskExecutionProjection.js +120 -124
  204. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  205. package/dist/scheduler/taskWake.js +11 -4
  206. package/dist/scheduler/wakeReason.js +9 -1
  207. package/dist/setup/setupCommand.js +1 -0
  208. package/dist/storage/migrations/agentRunContract.js +159 -0
  209. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  210. package/dist/storage/sqliteSchema.js +431 -5
  211. package/dist/storage/sqliteStore.js +375 -220
  212. package/dist/storage/storageVersions.js +1 -1
  213. package/dist/storage/storeRpc.js +10 -5
  214. package/dist/storage/taskStore.js +13 -11
  215. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  216. package/dist/surface/surfaceContributions.js +102 -0
  217. package/dist/task/completionReadiness.js +32 -6
  218. package/dist/task/deliveryGuard.js +16 -16
  219. package/dist/task/draftPlan.js +72 -12
  220. package/dist/task/nextAction.js +144 -128
  221. package/dist/task/remoteDelivery.js +6 -6
  222. package/dist/task/task.js +184 -18
  223. package/dist/task/taskActivation.js +301 -0
  224. package/dist/task/taskActivationService.js +392 -0
  225. package/dist/task/taskRecordReference.js +5 -4
  226. package/dist/task/taskRecordRetirement.js +1 -1
  227. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  228. package/dist/telemetry/telemetryConfig.js +14 -14
  229. package/dist/telemetry/telemetryWiring.js +2 -2
  230. package/dist/web/assets/assetManifest.js +2 -0
  231. package/dist/web/assets/client/app.js +120 -20
  232. package/dist/web/assets/client/components.js +87 -54
  233. package/dist/web/assets/client/i18n.js +83 -41
  234. package/dist/web/assets/client/markdown.js +1 -1
  235. package/dist/web/assets/client/taskSurface.js +353 -0
  236. package/dist/web/assets/client/view.js +49 -44
  237. package/dist/web/assets/shell.js +1 -1
  238. package/dist/web/assets/styles/cards.js +22 -4
  239. package/dist/web/controllerWeb.js +60 -0
  240. package/dist/web/webMutation.js +28 -0
  241. package/dist/web/webServer.js +118 -8
  242. package/dist/web/webSnapshot.js +81 -74
  243. package/dist/web/webTaskSurface.js +64 -0
  244. package/dist/workItem/dependencyGate.js +1 -1
  245. package/dist/workItem/workItem.js +80 -48
  246. package/dist/workspace/workItemChangeSetManager.js +16 -9
  247. package/docs/agent-result-consumption.md +94 -0
  248. package/docs/agent-runtime-drivers.md +91 -0
  249. package/docs/architecture/README.md +38 -0
  250. package/docs/architecture/capabilities-and-resources.md +79 -0
  251. package/docs/managed-turn-and-session-runtime.md +222 -0
  252. package/docs/observability/README.md +81 -0
  253. package/docs/plugin-sdk.md +290 -0
  254. package/docs/provider-runtime.md +163 -0
  255. package/docs/release-workflow.md +303 -0
  256. package/docs/roles-and-configuration.md +113 -0
  257. package/docs/sqlite-control-plane-design.md +76 -0
  258. package/docs/task-dag-semantics.md +57 -0
  259. package/docs/task-delivery.md +103 -0
  260. package/docs/task-local-identity.md +6 -6
  261. package/docs/testing/verification-levels.md +86 -0
  262. package/i18n/README.zh-CN.md +98 -739
  263. package/package.json +2 -2
  264. package/skills/yui-leader/SKILL.md +130 -103
  265. package/skills/yui-leader/references/integration.md +39 -0
  266. package/skills/yui-leader/references/replicated-execution.md +42 -0
  267. package/skills/yui-leader/references/task-plugins.md +33 -0
  268. package/skills/yui-operator/SKILL.md +30 -59
  269. package/skills/yui-reviewer/SKILL.md +35 -36
  270. package/skills/yui-runtime/SKILL.md +88 -24
  271. package/skills/yui-runtime/references/publication.md +22 -0
  272. package/skills/yui-runtime/references/recovery.md +64 -0
  273. package/skills/yui-worker/SKILL.md +37 -39
  274. package/dist/cli/roleOptionCatalog.js +0 -68
  275. package/dist/context/sourceTurnContext.js +0 -30
  276. package/dist/execution/reviewMainTurn.js +0 -161
  277. package/dist/execution/workItemMainTurn.js +0 -164
  278. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  279. package/dist/runtime/preallocatedNativeSession.js +0 -13
  280. package/dist/runtime/runtimeStopReceipt.js +0 -42
  281. package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
@@ -1,5 +1,5 @@
1
1
  import { usageError } from "../errors/cliError.js";
2
- import { isRoleTurnStalled, latestStallProgressAt } from "../scheduler/roleTurnStall.js";
2
+ import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
3
3
  import { formatTimestamp } from "../output/timePresentation.js";
4
4
  import { pendingWakeupProjection } from "../storage/taskStore.js";
5
5
  import { defaultTableWidth, renderTable } from "../output/table.js";
@@ -65,7 +65,7 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
65
65
  const workItems = store.listWorkItems(task.id);
66
66
  const inputRequests = store.listInputRequests(task.id);
67
67
  const openInputRequests = inputRequests.filter((request) => request.status === "open");
68
- const turns = store.listTurns(task.id);
68
+ const runs = store.listRuns(task.id);
69
69
  const events = store.listEvents(task.id);
70
70
  const leaderFailure = store.getLeaderFailure(task.id);
71
71
  const leaderMailbox = store.getWorkMailbox({ kind: "role", taskId: task.id, roleName: "leader" });
@@ -77,15 +77,19 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
77
77
  const session = store.getRoleSession(task.id, role.name);
78
78
  return session === null ? [] : [{ roleName: role.name, ...session }];
79
79
  });
80
- const attention = collectAttention(task, turns, events, leaderFailure);
80
+ const providerRuns = new Map(roles.map((role) => [role.name, store.getTaskRoleSessionSet(task.id, role.name)?.providerBinding?.run]));
81
+ const runDelivery = Object.fromEntries(runs.map((run) => {
82
+ const native = providerRuns.get(run.roleName);
83
+ return [run.id, native?.runId === run.id ? native.status : "unobserved"];
84
+ }));
85
+ const attention = collectAttention(task, runs, events, leaderFailure);
81
86
  const blockers = collectBlockers(workItems, openInputRequests, attention);
82
87
  const leader = {
83
88
  role: "leader",
84
89
  roleStatus: leaderRole === null
85
90
  ? "missing"
86
- : turns.some((run) => run.roleName === "leader" && run.status === "active")
87
- ? "running"
88
- : roleSessions.find((session) => session.roleName === "leader")?.status ?? "idle",
91
+ : providerRuns.get("leader")?.status === "accepted" ? "running"
92
+ : roleSessions.some((session) => session.roleName === "leader") ? "unknown" : "idle",
89
93
  summary: brief?.leaderSummary ?? null,
90
94
  currentFocus: brief?.currentFocus ?? null,
91
95
  updatedAt: brief?.updatedAt ?? null,
@@ -93,29 +97,28 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
93
97
  summaryStatus: brief === null ? "missing" : "available"
94
98
  };
95
99
  const counts = countWorkItems(workItems);
96
- const runtimeTurns = turns
100
+ const runtimeRuns = runs
97
101
  .filter((run) => run.status === "active")
98
102
  .map((run) => ({
99
103
  id: run.id,
100
104
  roleName: run.roleName,
101
105
  status: run.status,
102
- runtime: roleSessions.some((session) => (session.roleName === run.roleName
103
- && session.agentId === run.effective.agentId
104
- && session.status !== "ended")) ? "session-active" : "starting",
106
+ runtime: runDelivery[run.id],
105
107
  createdAt: run.createdAt,
106
108
  updatedAt: run.updatedAt
107
109
  }));
108
110
  const runtime = {
109
- activeTurns: runtimeTurns,
110
- activeTurnCount: runtimeTurns.length,
111
- pendingDeliveryCount: runtimeTurns.filter((run) => run.runtime === "starting").length
111
+ activeRuns: runtimeRuns,
112
+ activeRunCount: runtimeRuns.length,
113
+ pendingDeliveryCount: runtimeRuns.filter((run) => run.runtime !== "accepted").length
112
114
  };
113
115
  // Fold the execution projection from the facts already read above instead of
114
116
  // reading the store a second time for the same unchanged revision.
115
117
  const execution = projectTaskExecutionFromFacts({
116
118
  task,
117
119
  roles,
118
- turns,
120
+ runs,
121
+ runDelivery,
119
122
  workItems,
120
123
  inputRequests,
121
124
  reviewRounds,
@@ -140,7 +143,7 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
140
143
  currentTaskReviewCandidate: null,
141
144
  executionGroups: execution.executionGroups
142
145
  }));
143
- const persistedCandidate = task.status === "active" || task.status === "retired"
146
+ const persistedCandidate = task.status === "active" || task.status === "cancelled"
144
147
  ? taskRemoteDeliveryCandidate(task)
145
148
  : null;
146
149
  const remoteDelivery = projectTaskRemoteDeliveryFromStore(store, task, persistedCandidate);
@@ -166,7 +169,7 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
166
169
  remoteDelivery
167
170
  };
168
171
  }
169
- function collectAttention(task, turns, events, failure) {
172
+ function collectAttention(task, runs, events, failure) {
170
173
  const items = [];
171
174
  if (failure !== null) {
172
175
  items.push({
@@ -178,21 +181,21 @@ function collectAttention(task, turns, events, failure) {
178
181
  updatedAt: failure.lastFailedAt
179
182
  });
180
183
  }
181
- for (const turn of turns) {
182
- if (turn.roleName !== "leader"
183
- || turn.status !== "active"
184
- || !isRoleTurnStalled(events, turn.id))
184
+ for (const run of runs) {
185
+ if (run.roleName !== "leader"
186
+ || run.status !== "active"
187
+ || !isRoleRunStalled(events, run.id))
185
188
  continue;
186
- const progressAt = latestStallProgressAt(events, turn.id) ?? turn.updatedAt;
189
+ const progressAt = latestStallProgressAt(events, run.id) ?? run.updatedAt;
187
190
  items.push({
188
191
  kind: "leader-stalled",
189
- id: `leader-stall:${turn.id}:${progressAt}`,
192
+ id: `leader-stall:${run.id}:${progressAt}`,
190
193
  status: "needs-attention",
191
194
  owner: "operator",
192
- summary: `Turn ${turn.id} for ${turn.roleName} has no durable progress after ${progressAt}.`,
195
+ summary: `AgentRun ${run.id} for ${run.roleName} has no durable progress after ${progressAt}.`,
193
196
  updatedAt: progressAt,
194
- turnId: turn.id,
195
- roleName: turn.roleName
197
+ runId: run.id,
198
+ roleName: run.roleName
196
199
  });
197
200
  }
198
201
  return items.sort((left, right) => (Date.parse(left.updatedAt) - Date.parse(right.updatedAt)
@@ -202,19 +205,7 @@ function collectBlockers(workItems, openInputRequests, attention) {
202
205
  const blockers = [];
203
206
  const workById = new Map(workItems.map((item) => [item.id, item]));
204
207
  for (const item of workItems) {
205
- if (item.status === "failed") {
206
- blockers.push({
207
- kind: "work",
208
- type: "work",
209
- id: item.id,
210
- status: item.status,
211
- owner: item.assignee ?? "leader",
212
- action: "review-failed-work",
213
- summary: item.outcome ?? `Work Item ${item.id} failed.`
214
- });
215
- continue;
216
- }
217
- if (item.status === "awaiting_acceptance") {
208
+ if ((item.status === "open" && item.currentCandidateId !== undefined)) {
218
209
  blockers.push({
219
210
  kind: "work",
220
211
  type: "work",
@@ -226,9 +217,9 @@ function collectBlockers(workItems, openInputRequests, attention) {
226
217
  });
227
218
  continue;
228
219
  }
229
- if (item.status !== "pending")
220
+ if (item.status !== "open")
230
221
  continue;
231
- const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "completed"));
222
+ const dependencies = item.dependsOn.filter((dependency) => (workById.get(dependency)?.status !== "accepted"));
232
223
  if (dependencies.length === 0)
233
224
  continue;
234
225
  blockers.push({
@@ -296,11 +287,8 @@ function overviewNextAction(task, workItems, execution, action) {
296
287
  function countWorkItems(items) {
297
288
  const counts = {
298
289
  total: items.length,
299
- pending: 0,
300
- running: 0,
301
- awaiting_acceptance: 0,
302
- completed: 0,
303
- failed: 0,
290
+ open: 0,
291
+ accepted: 0,
304
292
  retired: 0
305
293
  };
306
294
  for (const item of items)
@@ -29,7 +29,7 @@ export function projectTaskRemoteDeliveryFromStore(store, task, currentCandidate
29
29
  events: store.listEvents(task.id),
30
30
  publications: store.listPublicationReferences(task.id),
31
31
  managedWorkspaces: store.listManagedWorkspaces(task.id),
32
- turns: store.listTurns(task.id),
32
+ runs: store.listRuns(task.id),
33
33
  currentCandidate
34
34
  });
35
35
  }
@@ -40,7 +40,7 @@ export function createTaskRemoteDeliveryProof(store, task, currentCandidate = nu
40
40
  events: store.listEvents(task.id),
41
41
  publications,
42
42
  managedWorkspaces: store.listManagedWorkspaces(task.id),
43
- turns: store.listTurns(task.id),
43
+ runs: store.listRuns(task.id),
44
44
  currentCandidate
45
45
  });
46
46
  return {
@@ -1,12 +1,14 @@
1
1
  import { isDeepStrictEqual } from "node:util";
2
+ import { runExecutionObservation } from "../agentRun/agentRun.js";
2
3
  import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
3
- import { isRoleTurnStalled, latestStallProgressAt } from "../scheduler/roleTurnStall.js";
4
+ import { currentProviderConversation, managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
5
+ import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
4
6
  import { createRuntimeObservation, runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
5
7
  import { classifyRuntimeHealth, projectRuntimeObservation, projectRuntimeTaskEvents, runtimeDisplayStatus } from "../runtime/runtimeProjection.js";
6
- import { latestTurnDurableProgressAt } from "../scheduler/roleTurnStall.js";
8
+ import { latestRunDurableProgressAt } from "../scheduler/roleRunStall.js";
7
9
  import { resolveRuntimeHealth } from "../config/yuiConfig.js";
8
10
  import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
9
- import { formatTurnReceiptId } from "../task/taskRecordReference.js";
11
+ import { formatRunReceiptId } from "../task/taskRecordReference.js";
10
12
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
11
13
  import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
12
14
  export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
@@ -21,21 +23,25 @@ export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now
21
23
  return roles.map((role) => inspectTaskRoleRuntimeStatus(taskId, role, store, panesByRole.get(role.name), role.name === "leader" ? taskOpenInputRequestCount : 0, now));
22
24
  }
23
25
  export function renderTaskRoleRuntimeStatus(status) {
24
- const activeTurn = status.activeTurn === null
26
+ const activeRun = status.activeRun === null
25
27
  ? "-"
26
- : `${status.activeTurn.id} (${activeTurnDeliveryLabel(status.activeTurn)})`;
27
- const lastTurn = status.activeTurn !== null || status.lastTurn === null
28
+ : `${status.activeRun.id} (${status.execution?.delivery ?? "unobserved"})`;
29
+ const lastRun = status.activeRun !== null || status.lastRun === null
28
30
  ? undefined
29
- : `${status.lastTurn.id} (${status.lastTurn.status}${status.lastTurn.result === undefined ? "" : ` at ${status.lastTurn.result.completedAt}`})`;
31
+ : `${status.lastRun.id} (${status.lastRun.status}${status.lastRun.result === undefined ? "" : ` at ${status.lastRun.result.completedAt}`})`;
30
32
  const activeWork = status.activeWork === null
31
33
  ? "-"
32
34
  : `${status.activeWork.id} (${status.activeWork.status}) ${status.activeWork.title}`;
35
+ // Both lines name the execution component, as the Role view does. The
36
+ // connection plan cannot stand in for it here either: two different ACP
37
+ // products would print identically, and this display exists to say what is
38
+ // actually running.
33
39
  const nativeSession = status.nativeSession === null
34
40
  ? "not recorded"
35
- : `${status.nativeSession.nativeSessionId} (${status.nativeSession.status}, ${status.nativeSession.adapterId}, effective r${status.nativeSession.effective.sourceDesiredRevision})`;
41
+ : `${status.nativeSession.nativeSessionId} (${status.nativeSession.status}, ${status.nativeSession.effective.component}, effective r${status.nativeSession.effective.sourceDesiredRevision})`;
36
42
  const effectiveLaunch = status.effectiveLaunch === null
37
43
  ? "not started"
38
- : `${status.effectiveLaunch.agentId}/${status.effectiveLaunch.adapterId}; r${status.effectiveLaunch.sourceDesiredRevision}; Profile intent=${status.effectiveLaunch.profileAccess}; permission=${status.effectiveLaunch.permission.strategy}`;
44
+ : `${status.effectiveLaunch.agentId}/${status.effectiveLaunch.component}; r${status.effectiveLaunch.sourceDesiredRevision}; Profile intent=${status.effectiveLaunch.profileAccess}; permission=${status.effectiveLaunch.permission.strategy}`;
39
45
  const tmux = status.tmux.state === "missing"
40
46
  ? "missing"
41
47
  : [
@@ -84,11 +90,11 @@ export function renderTaskRoleRuntimeStatus(status) {
84
90
  ` Desired drift ${status.effectiveLaunch === null
85
91
  ? "-"
86
92
  : status.launchDrift ? "pending next launch" : "none"}`,
87
- ` Turn/session ${status.turnSessionDrift ? "snapshot mismatch" : "snapshot consistent"}`,
93
+ ` AgentRun/session ${status.runSessionDrift ? "snapshot mismatch" : "snapshot consistent"}`,
88
94
  ` Active work ${activeWork}`,
89
- ` Active run ${activeTurn}`,
90
- ...(lastTurn === undefined ? [] : [` Last turn ${lastTurn}`]),
91
- ` Turn attention ${status.stall.active
95
+ ` Active run ${activeRun}`,
96
+ ...(lastRun === undefined ? [] : [` Last turn ${lastRun}`]),
97
+ ` AgentRun attention ${status.stall.active
92
98
  ? `needs-attention (${status.stall.kind ?? "workflow-not-progressing"}; no workflow progress since ${status.stall.progressAt ?? "unknown"})`
93
99
  : "none"}`,
94
100
  ` Native session ${nativeSession}`,
@@ -103,15 +109,15 @@ export function renderTaskRoleRuntimeStatus(status) {
103
109
  export function taskRoleActiveWorkLabel(status) {
104
110
  if (status.activeWork !== null)
105
111
  return `${status.activeWork.id}: ${status.activeWork.title}`;
106
- return status.activeTurn === null ? "-" : status.activeTurn.id;
112
+ return status.activeRun === null ? "-" : status.activeRun.id;
107
113
  }
108
- /** Issue 09: compact last-Turn outcome label for the Role list table. */
109
- export function taskRoleLastTurnLabel(status) {
110
- if (status.activeTurn !== null)
111
- return `${status.activeTurn.id} ${status.activeTurn.status}`;
112
- if (status.lastTurn === null)
114
+ /** Issue 09: compact last-AgentRun outcome label for the Role list table. */
115
+ export function taskRoleLastRunLabel(status) {
116
+ if (status.activeRun !== null)
117
+ return `${status.activeRun.id} ${status.activeRun.status}`;
118
+ if (status.lastRun === null)
113
119
  return "-";
114
- return `${status.lastTurn.id} ${status.lastTurn.status}`;
120
+ return `${status.lastRun.id} ${status.lastRun.status}`;
115
121
  }
116
122
  export function taskRoleNativeSessionLabel(status) {
117
123
  if (status.runtimeCleanupPending && status.nativeSession === null)
@@ -140,26 +146,26 @@ export function taskRoleTmuxLabel(status) {
140
146
  : `running (${status.tmux.currentCommand})`;
141
147
  }
142
148
  function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputRequestCount, now) {
143
- const activeTurn = store.getActiveTurn(taskId, role.name);
144
- // The last Turn outcome is a separate axis from the Session lifecycle. A
145
- // Session that stops after its Turn completed must not retroactively turn
146
- // that Turn into a failure; the status display keeps both visible.
147
- const lastTurn = operationalTaskRecords(store.listTurns(taskId), store.listEvents(taskId), "turn")
149
+ const activeRun = store.getActiveRun(taskId, role.name);
150
+ // The last AgentRun outcome is a separate axis from the Session lifecycle. A
151
+ // Session that stops after its AgentRun completed must not retroactively turn
152
+ // that AgentRun into a failure; the status display keeps both visible.
153
+ const lastRun = operationalTaskRecords(store.listRuns(taskId), store.listEvents(taskId), "run")
148
154
  .filter((candidate) => candidate.roleName === role.name)
149
155
  .sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))[0]
150
156
  ?? null;
151
- const activeWork = activeTurn?.workItemId === undefined
157
+ const activeWork = activeRun?.workItemId === undefined
152
158
  ? null
153
- : store.getWorkItem(taskId, activeTurn.workItemId);
159
+ : store.getWorkItem(taskId, activeRun.workItemId);
154
160
  const sessions = store.getTaskRoleSessionSet(taskId, role.name);
155
- const effectiveAgentId = activeTurn?.effective.agentId ?? sessions?.activeAgentId;
161
+ const effectiveAgentId = activeRun?.effective.agentId ?? sessions?.activeAgentId;
156
162
  const nativeSession = effectiveAgentId === undefined
157
163
  ? null
158
164
  : sessions?.sessions[effectiveAgentId] ?? null;
159
- const effectiveLaunch = activeTurn?.effective ?? nativeSession?.effective ?? null;
160
- const turnSessionDrift = activeTurn !== null
165
+ const effectiveLaunch = activeRun?.effective ?? nativeSession?.effective ?? null;
166
+ const runSessionDrift = activeRun !== null
161
167
  && nativeSession !== null
162
- && !isDeepStrictEqual(activeTurn.effective, nativeSession.effective);
168
+ && !isDeepStrictEqual(activeRun.effective, nativeSession.effective);
163
169
  const recovery = inspectTaskRoleSessionRecovery(taskId, role.name, store);
164
170
  const tmux = pane === undefined
165
171
  ? { state: "missing" }
@@ -170,30 +176,46 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
170
176
  ...(pane.pid === undefined ? {} : { pid: pane.pid }),
171
177
  currentCommand: pane.currentCommand
172
178
  };
173
- // The active Turn snapshot is authoritative for the live Role session. It
179
+ // The active AgentRun snapshot is authoritative for the live Role session. It
174
180
  // may point at a ReviewRound-owned workspace, which is intentionally
175
181
  // distinct from the WorkItem Develop workspace.
176
- const managedWorkspace = activeTurn?.workspace
177
- ?? (activeTurn?.workItemId === undefined
178
- ? store.getTaskWorkspace(taskId)
179
- : store.getWorkItemWorkspace(taskId, activeTurn.workItemId));
182
+ const actualWorkspaceRoot = effectiveLaunch?.workspace.root ?? role.workspace;
183
+ const managedWorkspace = activeRun?.workspace
184
+ ?? store.listManagedWorkspaces(taskId).find(({ root }) => root === actualWorkspaceRoot)
185
+ ?? null;
180
186
  const workspace = managedWorkspace === null
181
- ? { managed: false, path: role.workspace }
187
+ ? { managed: false, path: actualWorkspaceRoot }
182
188
  : { ...managedWorkspace, managed: true };
183
189
  const events = store.listEvents(taskId);
184
190
  const sessionTokens = projectSessionTokenMetrics(events, resolveSessionTokenIdentity(nativeSession === null
185
191
  ? null
186
192
  : { taskId, roleName: role.name, ...nativeSession }));
187
- const runtime = projectTaskRoleRuntime(activeTurn, nativeSession, tmux, events, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }), store, taskId, role.name, now);
188
- const stalled = activeTurn !== null && isRoleTurnStalled(events, activeTurn.id);
189
- const stallProgressAt = activeTurn === null
193
+ const runtime = projectTaskRoleRuntime(activeRun, nativeSession, tmux, events, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }), store, taskId, role.name, now);
194
+ const stalled = activeRun !== null && isRoleRunStalled(events, activeRun.id);
195
+ const stallProgressAt = activeRun === null
190
196
  ? undefined
191
- : latestStallProgressAt(events, activeTurn.id);
192
- const stallKind = activeTurn === null
197
+ : latestStallProgressAt(events, activeRun.id);
198
+ const stallKind = activeRun === null
193
199
  ? undefined
194
- : latestStallKind(events, activeTurn.id);
195
- const health = calculateHealth(role, activeTurn, lastTurn, nativeSession, recovery.runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime);
196
- const stall = activeTurn === null
200
+ : latestStallKind(events, activeRun.id);
201
+ const execution = activeRun === null ? undefined
202
+ : runExecutionObservation(activeRun, sessions?.providerBinding, events);
203
+ const conversation = sessions?.providerBinding == null
204
+ ? undefined : currentProviderConversation(sessions.providerBinding);
205
+ const currentProvider = nativeSession !== null
206
+ && conversation?.conversationId === nativeSession.nativeSessionId
207
+ ? sessions?.providerBinding : undefined;
208
+ const computedHealth = calculateHealth(role, activeRun, lastRun, nativeSession, recovery.runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime, currentProvider?.run?.status, currentProvider != null && conversation?.recoverability === "unrecoverable");
209
+ const retainedInput = sessions?.providerBinding?.run;
210
+ const health = nativeSession === null && retainedInput != null
211
+ && ["submitting", "accepted", "delivery-unknown"].includes(retainedInput.status)
212
+ ? { health: "needs-attention",
213
+ healthReason: `Session cache is unbound; retained native input is ${retainedInput.status}. Inspect or replace this Role's Session.` }
214
+ : execution !== undefined && computedHealth.health === "running" && execution.delivery !== "accepted"
215
+ ? { health: execution?.delivery === "delivery-unknown" ? "needs-attention" : "awaiting-provider-acceptance",
216
+ healthReason: `Execution record is open; native admission is ${execution?.delivery ?? "unobserved"}.` }
217
+ : computedHealth;
218
+ const stall = activeRun === null
197
219
  ? { active: false }
198
220
  : {
199
221
  active: stalled,
@@ -209,13 +231,14 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
209
231
  effectiveLaunch,
210
232
  launchDrift: effectiveLaunch !== null
211
233
  && effectiveLaunch.sourceDesiredRevision !== role.launchRevision,
212
- turnSessionDrift,
234
+ runSessionDrift,
213
235
  ...health,
214
236
  openInputRequestCount,
215
237
  role,
216
- activeTurn,
217
- lastTurn,
238
+ activeRun,
239
+ lastRun,
218
240
  activeWork,
241
+ ...(execution === undefined ? {} : { execution }),
219
242
  nativeSession,
220
243
  tmux,
221
244
  workspace,
@@ -236,17 +259,17 @@ function sessionCumulativeTokenLabel(metrics) {
236
259
  ];
237
260
  return `${total.totalTokens} (${breakdown.join(", ")})`;
238
261
  }
239
- function latestStallKind(events, turnId) {
262
+ function latestStallKind(events, runId) {
240
263
  const event = [...events]
241
- .filter((candidate) => candidate.type === "turn.stalled"
242
- && candidate.payload.turnId === turnId
264
+ .filter((candidate) => candidate.type === "run.stalled"
265
+ && candidate.payload.runId === runId
243
266
  && candidate.payload.status !== "diagnostic-only")
244
267
  .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0];
245
268
  return event?.payload.kind === "delivery-stalled" || event?.payload.kind === "workflow-not-progressing"
246
269
  ? event.payload.kind
247
270
  : undefined;
248
271
  }
249
- function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime) {
272
+ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanupPending, tmux, openInputRequestCount, stalled, runtime, nativeInputStatus, nativeConversationUnrecoverable = false) {
250
273
  if (runtimeCleanupPending && nativeSession === null) {
251
274
  return {
252
275
  health: "needs-attention",
@@ -254,33 +277,40 @@ function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeClean
254
277
  };
255
278
  }
256
279
  if (nativeSession?.status === "ended" && nativeSession.endReason === "failed") {
257
- // A broken Session only fails a live Turn. When the last Turn already
258
- // completed, the Session death is a lifecycle event, not a Turn failure —
259
- // surface it as attention with the persisted Turn outcome.
260
- if (activeTurn !== null) {
280
+ // A broken Session only fails a live AgentRun. When the last AgentRun already
281
+ // completed, the Session death is a lifecycle event, not a AgentRun failure —
282
+ // surface it as attention with the persisted AgentRun outcome.
283
+ if (activeRun !== null) {
261
284
  return { health: "failed", healthReason: "the active native session is broken" };
262
285
  }
263
286
  return {
264
287
  health: "needs-attention",
265
- healthReason: lastTurn === null
288
+ healthReason: lastRun === null
266
289
  ? "the native session is broken"
267
- : `the native session is broken; last Turn ${lastTurn.id} ${lastTurn.status}`
290
+ : `the native session is broken; last AgentRun ${lastRun.id} ${lastRun.status}`
268
291
  };
269
292
  }
270
293
  if (tmux.state === "exited") {
294
+ if (nativeSession?.status === "ended" && nativeSession.endReason === "stopped"
295
+ && activeRun === null && !runtimeCleanupPending
296
+ && !["submitting", "accepted", "delivery-unknown"].includes(nativeInputStatus ?? "")) {
297
+ return role.name === "leader" && openInputRequestCount > 0
298
+ ? { health: "blocked-input", healthReason: `${openInputRequestCount} durable InputRequest(s) still require user input` }
299
+ : { health: "idle", healthReason: "the Session was deliberately stopped; no native input remains unsettled" };
300
+ }
271
301
  return {
272
302
  health: "needs-attention",
273
303
  healthReason: "the tmux pane exited; Provider Conversation/continuation state is unobservable"
274
304
  };
275
305
  }
276
- if (activeTurn !== null) {
306
+ if (activeRun !== null) {
277
307
  if (tmux.state !== "running") {
278
- return { health: "needs-attention", healthReason: "the active Turn has no live tmux pane" };
308
+ return { health: "needs-attention", healthReason: "the active AgentRun has no live tmux pane" };
279
309
  }
280
310
  if (stalled) {
281
311
  return {
282
312
  health: "needs-attention",
283
- healthReason: "the live active Turn has no durable progress in the configured stall window"
313
+ healthReason: "the live active AgentRun has no durable progress in the configured stall window"
284
314
  };
285
315
  }
286
316
  if (runtime !== null) {
@@ -315,7 +345,7 @@ function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeClean
315
345
  }
316
346
  return {
317
347
  health: "starting",
318
- healthReason: "the active Turn is awaiting Provider runtime observations"
348
+ healthReason: "the active AgentRun is awaiting Provider runtime observations"
319
349
  };
320
350
  }
321
351
  if (nativeSession?.status === "active" && tmux.state !== "running") {
@@ -324,6 +354,20 @@ function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeClean
324
354
  if (nativeSession?.status === "ended" && tmux.state === "running") {
325
355
  return { health: "needs-attention", healthReason: "a stopped native session has a live tmux pane" };
326
356
  }
357
+ if (nativeSession?.status === "active" && tmux.state === "running") {
358
+ if (nativeConversationUnrecoverable) {
359
+ return { health: "needs-attention", healthReason: "the native conversation cannot resume; stop its idle Session and explicitly select a new one" };
360
+ }
361
+ if (nativeInputStatus === "submitting") {
362
+ return { health: "awaiting-provider-acceptance", healthReason: "native input is submitting; no AgentRun is required for a notification" };
363
+ }
364
+ if (nativeInputStatus === "accepted") {
365
+ return { health: "running", healthReason: "accepted native input is awaiting its terminal, independently of AgentRun records" };
366
+ }
367
+ if (nativeInputStatus === "delivery-unknown") {
368
+ return { health: "needs-attention", healthReason: "native input disposition is unknown; absence of an AgentRun does not prove idle" };
369
+ }
370
+ }
327
371
  if (role.name === "leader" && openInputRequestCount > 0) {
328
372
  return {
329
373
  health: "blocked-input",
@@ -335,52 +379,51 @@ function calculateHealth(role, activeTurn, lastTurn, nativeSession, runtimeClean
335
379
  : { health: "idle", healthReason: "there is no active work or live tmux pane" };
336
380
  }
337
381
  function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, taskId, roleName, now) {
338
- if (run === null || session?.runtimeGenerationId === undefined)
382
+ if (session === null)
383
+ return null;
384
+ const observed = store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding?.run;
385
+ // A newly admitted Run must not inherit the preceding input's receipt. A
386
+ // terminal Run no longer occupies the active index, but its exact binding
387
+ // still identifies the observations to render.
388
+ const native = run !== null && managedProviderTurnId(observed) !== run.id ? undefined : observed;
389
+ if (run === null && native == null)
339
390
  return null;
340
391
  let driverId;
341
392
  try {
342
- driverId = builtinDriverIdForAdapter(run.effective.adapterId);
393
+ driverId = builtinDriverIdForAdapter(run?.effective.adapterId ?? session.adapterId);
343
394
  }
344
395
  catch {
345
396
  return null;
346
397
  }
347
- const fence = {
348
- taskId: run.taskId,
349
- roleName: run.roleName,
350
- turnId: run.id,
351
- agentId: run.effective.agentId,
398
+ const createdAt = run?.createdAt ?? native.submittedAt;
399
+ const updatedAt = run?.updatedAt ?? native.updatedAt;
400
+ const receiptId = native?.attemptId ?? formatRunReceiptId(taskId, run.id);
401
+ const basicFence = {
402
+ taskId,
403
+ roleName,
404
+ ...(run?.id === undefined && native?.runId === undefined ? {} : { runId: run?.id ?? native?.runId }),
405
+ agentId: run?.effective.agentId ?? session.agentId,
352
406
  driverId,
353
- runtimeGenerationId: session.runtimeGenerationId,
354
407
  nativeSessionId: session.nativeSessionId,
355
- nativeTurnId: runtimeNativeTurnId(events, {
356
- taskId: run.taskId,
357
- roleName: run.roleName,
358
- turnId: run.id,
359
- agentId: run.effective.agentId,
360
- driverId,
361
- runtimeGenerationId: session.runtimeGenerationId,
362
- nativeSessionId: session.nativeSessionId,
363
- receiptId: store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding?.turn?.attemptId
364
- ?? formatTurnReceiptId(run.taskId, run.id)
365
- }) ?? run.id,
366
- receiptId: store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding?.turn?.attemptId
367
- ?? formatTurnReceiptId(run.taskId, run.id)
408
+ receiptId
368
409
  };
369
- let projection = projectRuntimeTaskEvents(fence, run.createdAt, events);
410
+ const nativeTurnId = native?.nativeTurnId ?? runtimeNativeTurnId(events, basicFence);
411
+ const fence = { ...basicFence, ...(nativeTurnId === undefined ? {} : { nativeTurnId }) };
412
+ let projection = projectRuntimeTaskEvents(fence, createdAt, events);
370
413
  projection = projectRuntimeObservation(projection, createRuntimeObservation({
371
414
  schemaVersion: 4,
372
- eventId: `runtime-host-${run.id}`,
373
- semanticKey: `runtime-host-${run.id}`,
415
+ eventId: `runtime-host-${receiptId}`,
416
+ semanticKey: `runtime-host-${receiptId}`,
374
417
  kind: "host.observed",
375
418
  authority: "host",
376
- receivedAt: run.updatedAt,
419
+ receivedAt: updatedAt,
377
420
  fence,
378
421
  payload: { alive: tmux.state === "running" }
379
422
  }));
380
423
  // The semantic progress fence is the same durable fold the scheduler stall
381
424
  // pass consumes, so CLI/Web/scheduler share one progress clock.
382
- const semanticProgress = latestTurnDurableProgressAt(store, taskId, roleName, run.id)
383
- ?? { progressAt: run.createdAt };
425
+ const semanticProgress = (run === null ? undefined : latestRunDurableProgressAt(store, taskId, roleName, run.id))
426
+ ?? { progressAt: createdAt };
384
427
  const classification = classifyRuntimeHealth({
385
428
  projection,
386
429
  semanticProgressAt: semanticProgress.progressAt,
@@ -414,16 +457,7 @@ function projectTaskRoleRuntime(run, session, tmux, events, _mailbox, store, tas
414
457
  function runtimeNativeTurnId(events, expected) {
415
458
  const observations = events
416
459
  .map(runtimeObservationFromTaskEvent)
417
- .filter((observation) => observation !== null
418
- && observation.fence.taskId === expected.taskId
419
- && observation.fence.roleName === expected.roleName
420
- && observation.fence.turnId === expected.turnId
421
- && observation.fence.agentId === expected.agentId
422
- && observation.fence.driverId === expected.driverId
423
- && observation.fence.runtimeGenerationId === expected.runtimeGenerationId
424
- && observation.fence.nativeSessionId === expected.nativeSessionId
425
- && observation.fence.receiptId === expected.receiptId
426
- && observation.fence.nativeTurnId !== undefined)
460
+ .filter((observation) => observation !== null && observation.fence.taskId === expected.taskId && observation.fence.roleName === expected.roleName && observation.fence.runId === expected.runId && observation.fence.agentId === expected.agentId && observation.fence.driverId === expected.driverId && observation.fence.nativeSessionId === expected.nativeSessionId && observation.fence.receiptId === expected.receiptId && observation.fence.nativeTurnId !== undefined)
427
461
  .sort((left, right) => (left.receivedAt.localeCompare(right.receivedAt)
428
462
  || (left.sequence ?? -1) - (right.sequence ?? -1)
429
463
  || (left.ordinal ?? -1) - (right.ordinal ?? -1)
@@ -432,6 +466,3 @@ function runtimeNativeTurnId(events, expected) {
432
466
  ?.fence.nativeTurnId
433
467
  ?? observations.at(-1)?.fence.nativeTurnId;
434
468
  }
435
- function activeTurnDeliveryLabel(run) {
436
- return run.status === "active" ? "active" : run.status;
437
- }