@zq-silk/yui 0.15.9 → 0.15.12

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 (160) hide show
  1. package/ARCHITECTURE.md +8 -4
  2. package/ARCHITECTURE.zh-CN.md +5 -2
  3. package/README.md +13 -5
  4. package/dist/agent/launchEnvironment.js +7 -0
  5. package/dist/agentRun/agentRun.js +3 -0
  6. package/dist/cli/commandCatalog.js +64 -16
  7. package/dist/cli/interactionPolicy.js +7 -3
  8. package/dist/cli/managedDiagnostics.js +1 -1
  9. package/dist/cli/updateOrchestrator.js +24 -1
  10. package/dist/cli/updatePorts.js +7 -3
  11. package/dist/cli/upgradeCommand.js +42 -2
  12. package/dist/cli.js +381 -107
  13. package/dist/commands/executionAuditCommands.js +10 -0
  14. package/dist/commands/globalRoleCommands.js +339 -4
  15. package/dist/commands/projectCommands.js +50 -22
  16. package/dist/commands/releaseCommands.js +18 -0
  17. package/dist/commands/taskActor.js +25 -0
  18. package/dist/commands/taskCommands.js +586 -96
  19. package/dist/commands/taskIntegrationCommands.js +19 -39
  20. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  21. package/dist/commands/taskOverviewCommand.js +4 -3
  22. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  23. package/dist/commands/taskPublicationCommands.js +11 -2
  24. package/dist/commands/taskPublicationVerifyCommand.js +23 -39
  25. package/dist/commands/taskRemoteDeliveryCommand.js +22 -11
  26. package/dist/commands/taskRoleRuntimeStatus.js +35 -0
  27. package/dist/context/runContextPack.js +3 -0
  28. package/dist/context/taskCatalog.js +187 -0
  29. package/dist/context/taskContext.js +55 -6
  30. package/dist/controller/agentHostObservation.js +155 -0
  31. package/dist/controller/clientRuntime.js +17 -2
  32. package/dist/controller/controller.js +14 -2
  33. package/dist/controller/fileSchedulerStoreAdapter.js +519 -25
  34. package/dist/controller/globalInputDelivery.js +132 -0
  35. package/dist/controller/jobControl.js +6 -2
  36. package/dist/controller/providerRetryAdmission.js +100 -0
  37. package/dist/controller/providerRetryDelivery.js +218 -0
  38. package/dist/controller/resourceInventory.js +14 -4
  39. package/dist/controller/resourceInventoryLinux.js +2 -6
  40. package/dist/controller/runtime.js +117 -7
  41. package/dist/controller/runtimeEventInbox.js +32 -3
  42. package/dist/controller/runtimeEventProcessor.js +26 -6
  43. package/dist/controller/runtimeHookRunFence.js +75 -19
  44. package/dist/controller/structuredProviderObservation.js +133 -70
  45. package/dist/coordination/workMailboxQueue.js +5 -0
  46. package/dist/execution/workItemExecutionProjection.js +1 -1
  47. package/dist/executor/agentExecutor.js +64 -4
  48. package/dist/executor/executorRegistry.js +3 -0
  49. package/dist/executor/fileRoleLaunchPlanner.js +78 -118
  50. package/dist/integration/deliveryObligation.js +2 -1
  51. package/dist/integration/gitIntegrationService.js +329 -386
  52. package/dist/integration/integrationAttempt.js +30 -4
  53. package/dist/integration/integrationQueueService.js +7 -7
  54. package/dist/integration/integrationSourceApplication.js +323 -0
  55. package/dist/lifecycle/exactRunTerminalization.js +4 -1
  56. package/dist/message/globalInterrupt.js +33 -0
  57. package/dist/message/globalProviderRetry.js +15 -0
  58. package/dist/message/inputControlResolution.js +106 -0
  59. package/dist/message/message.js +367 -0
  60. package/dist/message/messageContinuation.js +126 -3
  61. package/dist/message/taskInterrupt.js +34 -0
  62. package/dist/observability/executionAudit.js +19 -0
  63. package/dist/observability/orchestrationMetrics.js +1 -1
  64. package/dist/release/releaseHandover.js +22 -0
  65. package/dist/release/releaseWorkflowPorts.js +15 -7
  66. package/dist/repository/gitWorkspace.js +430 -107
  67. package/dist/repository/projectMaintenanceLock.js +75 -18
  68. package/dist/repository/taskWorkspaceCoordinator.js +182 -101
  69. package/dist/repository/taskWorkspacePreparer.js +205 -72
  70. package/dist/repository/workItemCandidateSnapshot.js +34 -0
  71. package/dist/repository/workspaceCleanupInspection.js +187 -0
  72. package/dist/resources/resourceDiscovery.js +3 -2
  73. package/dist/runtime/agentError.js +5 -3
  74. package/dist/runtime/agentHost.js +179 -82
  75. package/dist/runtime/agentHostCompatibility.js +127 -0
  76. package/dist/runtime/agentHostProtocol.js +53 -0
  77. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  78. package/dist/runtime/codexAppServerRuntime.js +34 -3
  79. package/dist/runtime/executionEnvironment.js +0 -19
  80. package/dist/runtime/launchBroker.js +6 -0
  81. package/dist/runtime/providerControl.js +5 -1
  82. package/dist/runtime/providerRetry.js +198 -0
  83. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  84. package/dist/runtime/sessionReconciliation.js +4 -4
  85. package/dist/runtime/sessionTokenMetrics.js +15 -5
  86. package/dist/runtime/structuredProviderHost.js +6 -2
  87. package/dist/runtime/taskRuntimeIsolation.js +30 -6
  88. package/dist/runtime/taskUsageMetrics.js +275 -0
  89. package/dist/runtime/tmuxAdapters.js +5 -3
  90. package/dist/scheduler/activeRoleRunDelivery.js +12 -0
  91. package/dist/scheduler/leaderWakeupProcessor.js +5 -0
  92. package/dist/scheduler/operatorEvent.js +4 -0
  93. package/dist/scheduler/taskExecutionProjection.js +38 -6
  94. package/dist/scheduler/taskObservabilityProjection.js +6 -44
  95. package/dist/scheduler/wakeReason.js +7 -1
  96. package/dist/scheduler/wakeupQueue.js +2 -0
  97. package/dist/setup/setupCommand.js +26 -8
  98. package/dist/storage/homeLayout.js +130 -0
  99. package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
  100. package/dist/storage/migrations/integrationContinuation.js +104 -0
  101. package/dist/storage/migrations/unifyHomeLayout.js +925 -0
  102. package/dist/storage/sqliteSchema.js +167 -4
  103. package/dist/storage/sqliteStore.js +57 -1
  104. package/dist/storage/storageVersions.js +1 -1
  105. package/dist/storage/storeRpc.js +2 -0
  106. package/dist/storage/taskCatalog.js +123 -0
  107. package/dist/storage/taskStore.js +2 -0
  108. package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
  109. package/dist/task/archiveDiagnostics.js +129 -0
  110. package/dist/task/archivePreflight.js +124 -0
  111. package/dist/task/nextAction.js +44 -11
  112. package/dist/task/publicationAdoption.js +56 -0
  113. package/dist/task/publicationReference.js +10 -0
  114. package/dist/task/remoteDelivery.js +31 -16
  115. package/dist/web/assets/client/app.js +147 -17
  116. package/dist/web/assets/client/components.js +56 -13
  117. package/dist/web/assets/client/i18n.js +78 -4
  118. package/dist/web/assets/client/taskSurface.js +108 -1
  119. package/dist/web/assets/client/view.js +39 -8
  120. package/dist/web/assets/shell.js +29 -0
  121. package/dist/web/assets/styles/layout.js +8 -1
  122. package/dist/web/assets/styles/widgets.js +12 -0
  123. package/dist/web/webServer.js +131 -4
  124. package/dist/web/webSnapshot.js +16 -6
  125. package/dist/web/webTaskSurface.js +222 -5
  126. package/dist/workspace/cleanupInspection.js +63 -0
  127. package/dist/workspace/workItemChangeSetManager.js +111 -35
  128. package/docs/agent-result-consumption.md +4 -0
  129. package/docs/agent-result-consumption.zh-CN.md +3 -0
  130. package/docs/agent-runtime-drivers.md +7 -0
  131. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  132. package/docs/architecture/README.md +2 -0
  133. package/docs/architecture/README.zh-CN.md +3 -1
  134. package/docs/architecture/capabilities-and-resources.md +30 -5
  135. package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
  136. package/docs/managed-turn-and-session-runtime.md +47 -0
  137. package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
  138. package/docs/observability/README.md +62 -0
  139. package/docs/observability/README.zh-CN.md +47 -0
  140. package/docs/project-refresh.md +77 -0
  141. package/docs/project-refresh.zh-CN.md +59 -0
  142. package/docs/provider-retry.md +70 -0
  143. package/docs/release-workflow.md +39 -0
  144. package/docs/release-workflow.zh-CN.md +29 -0
  145. package/docs/sqlite-control-plane-design.md +223 -1
  146. package/docs/task-delivery.md +133 -13
  147. package/docs/task-delivery.zh-CN.md +99 -10
  148. package/docs/task-discovery.md +102 -0
  149. package/docs/task-discovery.zh-CN.md +86 -0
  150. package/docs/testing/verification-levels.md +40 -0
  151. package/docs/testing/verification-levels.zh-CN.md +23 -0
  152. package/i18n/README.zh-CN.md +13 -7
  153. package/package.json +1 -1
  154. package/skills/yui-leader/references/execution.md +154 -51
  155. package/skills/yui-leader/references/integration.md +52 -2
  156. package/skills/yui-operator/SKILL.md +19 -3
  157. package/skills/yui-reviewer/SKILL.md +4 -0
  158. package/skills/yui-runtime/SKILL.md +42 -0
  159. package/skills/yui-runtime/references/publication.md +42 -0
  160. package/skills/yui-runtime/references/recovery.md +24 -0
@@ -7,39 +7,70 @@ import { createRuntimeObservation, runtimeObservationSemanticKey } from "../runt
7
7
  import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
8
8
  import { isForeignHandoverLockHeld } from "../release/runtimeRelease.js";
9
9
  import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
10
- import { resolveRuntimeHookRunFence } from "./runtimeHookRunFence.js";
10
+ import { AGENT_HOST_EVENT_PROTOCOL } from "../runtime/agentHostProtocol.js";
11
+ import { redactAgentErrorText } from "../runtime/agentError.js";
11
12
  let structuredSequence = 0;
13
+ let deliveryFailure;
14
+ // Only live diagnostics for this process's immutable files, not another queue.
15
+ // No payload, retry, or authoritative delivery state is kept in this map.
16
+ const emittedEvents = new Map();
17
+ /** A live transport observation, independent of Provider state and Run truth. */
18
+ export function structuredProviderEventDelivery(home, environment, nativeSessionId) {
19
+ try {
20
+ const inbox = new FileRuntimeEventInbox(home);
21
+ const pending = [];
22
+ for (const [id, entry] of emittedEvents) {
23
+ if (entry.home !== home)
24
+ continue;
25
+ if (!inbox.has(id)) {
26
+ emittedEvents.delete(id);
27
+ continue;
28
+ }
29
+ if (entry.taskId === environment.YUI_TASK_ID && entry.roleName === environment.YUI_ROLE
30
+ && (nativeSessionId === undefined || entry.nativeSessionId === nativeSessionId))
31
+ pending.push(entry);
32
+ }
33
+ return {
34
+ pending: pending.length,
35
+ pendingTerminals: pending.filter(event => event.terminal).length,
36
+ ...(deliveryFailure === undefined ? {} : { failure: deliveryFailure })
37
+ };
38
+ }
39
+ catch (error) {
40
+ return { pending: 0, pendingTerminals: 0, failure: deliveryFault("persistence", error) };
41
+ }
42
+ }
43
+ function deliveryFault(stage, error) {
44
+ return {
45
+ stage, detail: redactAgentErrorText(error instanceof Error ? error.message : String(error)),
46
+ observedAt: new Date().toISOString()
47
+ };
48
+ }
12
49
  export async function publishStructuredProviderAttachmentExit(input) {
13
50
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
14
51
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
15
- const owner = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId, {
16
- terminal: true, attemptId: input.attemptId, nativeTurnId: input.nativeTurnId
17
- });
52
+ const owner = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
18
53
  await persistAndApply(input.home, [observation({
19
54
  kind: input.failed ? "session.failed" : "session.ended", observedAt: input.observedAt,
20
55
  sequence: nextStructuredSequence(), ordinal: 0,
21
56
  fence: {
22
57
  taskId: owner.taskId, roleName: owner.roleName, agentId: owner.agentId, driverId: driver.id,
23
58
  nativeSessionId: input.nativeSessionId, conversationId: input.nativeSessionId,
24
- ...(owner.runId === undefined ? {} : { runId: owner.runId }),
25
59
  ...(input.attemptId === undefined ? {} : { receiptId: input.attemptId }),
26
60
  ...(input.nativeTurnId === undefined ? {} : { nativeTurnId: input.nativeTurnId })
27
61
  }
28
- })], owner.taskId, owner.roleName);
62
+ })], input.environment);
29
63
  }
30
64
  export async function publishStructuredProviderActivity(input) {
31
65
  const activity = input.activity;
32
66
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
33
67
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
34
- const owner = resolveRuntimeHookRunFence(input.environment, adapterId, activity.nativeSessionId, {
35
- nativeTurnId: activity.nativeTurnId, attemptId: activity.attemptId
36
- });
68
+ const owner = describeHostIdentity(input.environment, adapterId, activity.nativeSessionId);
37
69
  const event = observation({
38
70
  kind: activity.phase === "model" ? "activity.observed" : `operation.${activity.phase}`,
39
71
  observedAt: activity.observedAt, sequence: nextStructuredSequence(), ordinal: 0,
40
72
  fence: {
41
73
  taskId: owner.taskId, roleName: owner.roleName,
42
- ...(owner.runId === undefined ? {} : { runId: owner.runId }),
43
74
  agentId: owner.agentId, driverId: driver.id,
44
75
  nativeSessionId: activity.nativeSessionId, conversationId: activity.conversationId,
45
76
  receiptId: activity.attemptId,
@@ -49,7 +80,7 @@ export async function publishStructuredProviderActivity(input) {
49
80
  ? { activity: "model", activityId: activity.id }
50
81
  : { operation: "tool", operationId: activity.id }
51
82
  });
52
- await persistAndApply(input.home, [event], owner.taskId, owner.roleName);
83
+ await persistAndApply(input.home, [event], input.environment);
53
84
  }
54
85
  /** An exact provider item id is evidence of visible input, not an inferred
55
86
  * human author. The original managed request is never replaced.
@@ -57,28 +88,21 @@ export async function publishStructuredProviderActivity(input) {
57
88
  export async function publishStructuredProviderInputObserved(input) {
58
89
  const observed = input.observed;
59
90
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
60
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, observed.nativeSessionId, {
61
- nativeTurnId: observed.nativeTurnId, terminal: true, sessionOnly: true
62
- });
63
- if (fence.runId === undefined)
64
- return; // Ordinary chat need not be mirrored.
91
+ const fence = describeHostIdentity(input.environment, adapterId, observed.nativeSessionId);
65
92
  await persistAndApply(input.home, [observation({
66
93
  kind: "input.accepted", observedAt: observed.observedAt, sequence: nextStructuredSequence(), ordinal: 0,
67
94
  fence: { ...fence, driverId: builtinAgentDriverRegistry().requireByAdapterId(adapterId).id,
68
95
  conversationId: observed.conversationId, nativeTurnId: observed.nativeTurnId,
69
96
  receiptId: `native-input:${observed.nativeTurnId}/${observed.inputId}` },
70
97
  payload: { input: observed.input }
71
- })], fence.taskId, fence.roleName);
98
+ })], input.environment);
72
99
  }
73
100
  export async function publishStructuredProviderStarted(input) {
74
101
  if (input.started.clientOwned)
75
102
  return;
76
103
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
77
104
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
78
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.started.nativeSessionId, {
79
- nativeTurnId: input.started.nativeTurnId,
80
- sessionOnly: true
81
- });
105
+ const fence = describeHostIdentity(input.environment, adapterId, input.started.nativeSessionId);
82
106
  const receiptId = `direct:${input.started.nativeTurnId}`;
83
107
  const startedObservation = observation({
84
108
  kind: "turn.accepted",
@@ -97,22 +121,16 @@ export async function publishStructuredProviderStarted(input) {
97
121
  },
98
122
  payload: input.started.input === undefined ? {} : { input: input.started.input }
99
123
  });
100
- await persistAndApply(input.home, [startedObservation], fence.taskId, fence.roleName);
124
+ await persistAndApply(input.home, [startedObservation], input.environment);
101
125
  }
102
126
  export async function publishStructuredProviderAccepted(input) {
103
127
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
104
128
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
105
- const startupSession = driver.capabilities.observation.sessionBootstrap;
106
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.receipt.nativeSessionId, {
107
- startupSession,
108
- nativeTurnId: input.receipt.nativeTurnId,
109
- attemptId: input.receipt.attemptId
110
- });
129
+ const fence = describeHostIdentity(input.environment, adapterId, input.receipt.nativeSessionId);
111
130
  const observedAt = input.receipt.acceptedAt;
112
131
  const commonFence = {
113
132
  taskId: fence.taskId,
114
133
  roleName: fence.roleName,
115
- ...(fence.runId === undefined ? {} : { runId: fence.runId }),
116
134
  agentId: fence.agentId,
117
135
  driverId: driver.id,
118
136
  conversationId: input.receipt.conversationId,
@@ -136,18 +154,15 @@ export async function publishStructuredProviderAccepted(input) {
136
154
  ordinal: 0,
137
155
  fence: commonFence
138
156
  })];
139
- await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
157
+ await persistAndApply(input.home, observations, input.environment);
140
158
  }
141
159
  /** Steer settles an additional input, never the parent AgentRun's initial delivery. */
142
160
  export async function publishStructuredProviderInputSettlement(input) {
143
161
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
144
162
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
145
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId, {
146
- nativeTurnId: input.nativeTurnId,
147
- terminal: true
148
- });
149
- if (fence.runId === undefined)
150
- throw new Error("Steer settlement has no exact original AgentRun.");
163
+ const fence = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
164
+ // The Host reports the exact Session/turn/attempt, including input with no
165
+ // AgentRun. Only the Controller resolves a current Run from durable state.
151
166
  const entry = observation({
152
167
  kind: input.status === "accepted" ? "input.accepted"
153
168
  : input.status === "rejected" ? "input.rejected" : "input.delivery-unknown",
@@ -157,7 +172,6 @@ export async function publishStructuredProviderInputSettlement(input) {
157
172
  fence: {
158
173
  taskId: fence.taskId,
159
174
  roleName: fence.roleName,
160
- runId: fence.runId,
161
175
  agentId: fence.agentId,
162
176
  driverId: driver.id,
163
177
  conversationId: input.nativeSessionId,
@@ -180,22 +194,20 @@ export async function publishStructuredProviderInputSettlement(input) {
180
194
  })
181
195
  }
182
196
  });
183
- await persistAndApply(input.home, [entry], fence.taskId, fence.roleName);
197
+ await persistAndApply(input.home, [entry], input.environment);
184
198
  }
185
199
  export async function publishStructuredProviderOpened(input) {
186
200
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
187
201
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
188
202
  const startupSession = driver.capabilities.observation.sessionBootstrap;
189
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.nativeSessionId, { startupSession });
203
+ const fence = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
190
204
  const commonFence = {
191
205
  taskId: fence.taskId,
192
206
  roleName: fence.roleName,
193
- ...(fence.runId === undefined ? {} : { runId: fence.runId }),
194
207
  agentId: fence.agentId,
195
208
  driverId: driver.id,
196
209
  conversationId: input.conversationId,
197
- nativeSessionId: input.nativeSessionId,
198
- ...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
210
+ nativeSessionId: input.nativeSessionId
199
211
  };
200
212
  const sequence = nextStructuredSequence();
201
213
  const observations = [observation({
@@ -212,12 +224,12 @@ export async function publishStructuredProviderOpened(input) {
212
224
  fence: commonFence,
213
225
  payload: { recoverability: input.recoverability }
214
226
  })];
215
- await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
227
+ await persistAndApply(input.home, observations, input.environment, undefined, input.startupRunId);
216
228
  }
217
229
  export async function publishStructuredProviderGoal(input) {
218
230
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
219
231
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
220
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.conversationId, { sessionOnly: true });
232
+ const fence = describeHostIdentity(input.environment, adapterId, input.conversationId);
221
233
  const goal = input.goal;
222
234
  const observedAt = input.observedAt ?? goal?.updatedAt ?? new Date().toISOString();
223
235
  const goalObservation = observation({
@@ -241,22 +253,20 @@ export async function publishStructuredProviderGoal(input) {
241
253
  ...(goal.tokenBudget === undefined ? {} : { goalTokenBudget: goal.tokenBudget })
242
254
  }
243
255
  });
244
- await persistAndApply(input.home, [goalObservation], fence.taskId, fence.roleName);
256
+ await persistAndApply(input.home, [goalObservation], input.environment);
245
257
  }
246
258
  export async function publishStructuredConversationRecoverability(input) {
247
259
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
248
260
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
249
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.conversationId, { sessionOnly: true });
261
+ const fence = describeHostIdentity(input.environment, adapterId, input.conversationId);
250
262
  const sequence = nextStructuredSequence();
251
263
  const observationFence = {
252
264
  taskId: fence.taskId,
253
265
  roleName: fence.roleName,
254
- ...(fence.runId === undefined ? {} : { runId: fence.runId }),
255
266
  agentId: fence.agentId,
256
267
  driverId: driver.id,
257
268
  conversationId: input.conversationId,
258
- nativeSessionId: input.conversationId,
259
- ...(fence.receiptId === undefined ? {} : { receiptId: fence.receiptId })
269
+ nativeSessionId: input.conversationId
260
270
  };
261
271
  const observations = [observation({
262
272
  kind: "conversation.observed",
@@ -266,17 +276,12 @@ export async function publishStructuredConversationRecoverability(input) {
266
276
  fence: observationFence,
267
277
  payload: { recoverability: input.recoverability }
268
278
  })];
269
- await persistAndApply(input.home, observations, fence.taskId, fence.roleName);
279
+ await persistAndApply(input.home, observations, input.environment);
270
280
  }
271
281
  export async function publishStructuredProviderTerminal(input) {
272
282
  const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
273
283
  const driver = builtinAgentDriverRegistry().requireByAdapterId(adapterId);
274
- const fence = resolveRuntimeHookRunFence(input.environment, adapterId, input.terminal.nativeSessionId, {
275
- terminal: true,
276
- nativeTurnId: input.terminal.nativeTurnId,
277
- attemptId: input.terminal.attemptId,
278
- ...(input.terminal.clientOwned ? {} : { sessionOnly: true })
279
- });
284
+ const fence = describeHostIdentity(input.environment, adapterId, input.terminal.nativeSessionId);
280
285
  const kind = input.terminal.status === "completed"
281
286
  ? "turn.completed"
282
287
  : input.terminal.status === "cancelled" ? "turn.cancelled" : "turn.failed";
@@ -321,7 +326,6 @@ export async function publishStructuredProviderTerminal(input) {
321
326
  fence: {
322
327
  taskId: fence.taskId,
323
328
  roleName: fence.roleName,
324
- ...(fence.runId === undefined ? {} : { runId: fence.runId }),
325
329
  agentId: fence.agentId,
326
330
  driverId: driver.id,
327
331
  conversationId: input.terminal.conversationId,
@@ -329,9 +333,7 @@ export async function publishStructuredProviderTerminal(input) {
329
333
  ...(input.terminal.nativeTurnId === undefined ? {} : {
330
334
  nativeTurnId: input.terminal.nativeTurnId
331
335
  }),
332
- ...(input.terminal.attemptId === undefined
333
- ? fence.receiptId === undefined ? {} : { receiptId: fence.receiptId }
334
- : { receiptId: input.terminal.attemptId })
336
+ ...(input.terminal.attemptId === undefined ? {} : { receiptId: input.terminal.attemptId })
335
337
  },
336
338
  payload: {
337
339
  ...payload,
@@ -340,7 +342,7 @@ export async function publishStructuredProviderTerminal(input) {
340
342
  ...(input.terminal.input === undefined ? {} : { input: input.terminal.input })
341
343
  }
342
344
  });
343
- await persistAndApply(input.home, [terminalObservation], fence.taskId, fence.roleName);
345
+ await persistAndApply(input.home, [terminalObservation], input.environment);
344
346
  }
345
347
  function observation(input) {
346
348
  const eventId = `agent-host-${randomUUID()}`;
@@ -367,7 +369,8 @@ function observation(input) {
367
369
  }
368
370
  async function signalController(home, taskId, roleName) {
369
371
  await callController(home, "scheduler.signal", {
370
- key: runtimeLifecycleSignalKey({ scope: "task", taskId, roleName })
372
+ key: runtimeLifecycleSignalKey(taskId === undefined
373
+ ? { scope: "global", roleName } : { scope: "task", taskId, roleName })
371
374
  }, { timeoutMs: 100 }).catch(() => { });
372
375
  }
373
376
  function nextStructuredSequence() {
@@ -380,20 +383,42 @@ function requireIdentity(value, label) {
380
383
  }
381
384
  return value.trim();
382
385
  }
383
- async function persistAndApply(home, observations, taskId, roleName) {
386
+ async function persistAndApply(home, observations, environment, connection, startupRunId) {
384
387
  const inbox = new FileRuntimeEventInbox(home);
385
- for (const entry of observations)
386
- inbox.enqueueObservation(entry);
388
+ const host = {
389
+ protocol: AGENT_HOST_EVENT_PROTOCOL,
390
+ adapterId: requireIdentity(environment.YUI_ADAPTER_ID, "Agent adapter id"),
391
+ workspace: requireIdentity(environment.YUI_WORKSPACE, "Yui workspace"),
392
+ ...(connection === undefined ? {} : { connection })
393
+ };
394
+ let events;
395
+ try {
396
+ structuredProviderEventDelivery(home, environment); // prune ACKed local diagnostics
397
+ events = observations.map(entry => inbox.enqueueObservation(entry, {
398
+ ...host,
399
+ ...(startupRunId === undefined ? {} : { startupRunId })
400
+ }).event);
401
+ for (const event of events)
402
+ emittedEvents.set(event.id, {
403
+ home, taskId: event.taskId, roleName: event.observation.fence.roleName,
404
+ nativeSessionId: event.observation.fence.nativeSessionId,
405
+ terminal: ["turn.completed", "turn.failed", "turn.cancelled"].includes(event.observation.kind)
406
+ });
407
+ }
408
+ catch (error) {
409
+ deliveryFailure = deliveryFault("persistence", error);
410
+ throw error;
411
+ }
387
412
  // The immutable inbox is authoritative. During a release/update handover the
388
413
  // old Controller is draining and the replacement is not ready yet; leave the
389
414
  // entries for normal inbox replay instead of turning a healthy Provider Turn
390
415
  // into a transport failure.
391
416
  if (isForeignHandoverLockHeld(home))
392
417
  return;
393
- for (const entry of observations) {
418
+ for (const entry of events) {
394
419
  let result;
395
420
  try {
396
- result = await callController(home, "runtime.observation-apply", entry, {
421
+ result = await callController(home, "runtime.host-observation-apply", { eventId: entry.id }, {
397
422
  // This is only an eager application hint after durable enqueue, not
398
423
  // native admission. Bound it like the wake signal so a frozen
399
424
  // Controller cannot serialize ten seconds of delay per tool event.
@@ -409,7 +434,10 @@ async function persistAndApply(home, observations, taskId, roleName) {
409
434
  && ["CONTROLLER_NOT_RUNNING", "CONTROLLER_UNAVAILABLE", "CONTROLLER_TIMEOUT",
410
435
  "CONTROLLER_DELIVERY_UNKNOWN"].includes(error.code)))
411
436
  return;
412
- throw error;
437
+ // The native fact is already durable. An incompatible/refusing
438
+ // Controller is a reporting fault, not Provider execution failure.
439
+ deliveryFailure = deliveryFault("controller", error);
440
+ return;
413
441
  }
414
442
  // A fast Provider can accept the initial AgentRun before the scheduler call
415
443
  // that launched this Host has returned and committed `turn.pushed`. The
@@ -417,8 +445,43 @@ async function persistAndApply(home, observations, taskId, roleName) {
417
445
  // `deferred` therefore means "retained for replay", not delivery failure.
418
446
  // The signal below schedules the replay after the transport transaction.
419
447
  if (result.outcome !== "applied" && result.outcome !== "deferred") {
420
- throw new Error(`Structured Provider observation was not applied: ${result.outcome ?? "unknown"}.`);
448
+ deliveryFailure = deliveryFault("controller", `Structured Provider observation was rejected: ${result.outcome ?? "unknown"}. Event: ${entry.id}`);
449
+ return;
421
450
  }
422
451
  }
423
- await signalController(home, taskId, roleName);
452
+ if (deliveryFailure?.stage === "controller")
453
+ deliveryFailure = undefined;
454
+ await signalController(home, hostTaskId(environment), requireIdentity(environment.YUI_ROLE, "Role"));
455
+ }
456
+ /** Capture only identities the Host actually received; the current Controller
457
+ * owns all TaskStore reads and resolves the exact attempt's Run at consumption. */
458
+ function describeHostIdentity(environment, adapterId, nativeSessionId) {
459
+ if (environment.YUI_ADAPTER_ID !== adapterId)
460
+ throw new Error("Agent adapter id does not match.");
461
+ return {
462
+ taskId: hostTaskId(environment),
463
+ roleName: requireIdentity(environment.YUI_ROLE, "Role name"),
464
+ agentId: requireIdentity(environment.YUI_AGENT_ID, "Agent id"),
465
+ nativeSessionId: requireIdentity(nativeSessionId, "Native Session id")
466
+ };
467
+ }
468
+ function hostTaskId(environment) {
469
+ if (environment.YUI_SESSION_SCOPE === "global") {
470
+ if (environment.YUI_TASK_ID !== undefined)
471
+ throw new Error("Global Host must not carry a Task id.");
472
+ return undefined;
473
+ }
474
+ if (environment.YUI_SESSION_SCOPE !== "task")
475
+ throw new Error("Agent Host observation requires Task scope.");
476
+ return requireIdentity(environment.YUI_TASK_ID, "Task id");
477
+ }
478
+ export async function publishStructuredProviderConnection(input) {
479
+ const adapterId = requireIdentity(input.environment.YUI_ADAPTER_ID, "Agent adapter id");
480
+ const fence = describeHostIdentity(input.environment, adapterId, input.nativeSessionId);
481
+ await persistAndApply(input.home, [observation({
482
+ kind: "host.observed", observedAt: new Date().toISOString(),
483
+ authority: "host", payload: { alive: true },
484
+ sequence: nextStructuredSequence(), ordinal: 0,
485
+ fence: { ...fence, driverId: builtinAgentDriverRegistry().requireByAdapterId(adapterId).id }
486
+ })], input.environment, input.connection, input.startupRunId);
424
487
  }
@@ -9,6 +9,11 @@ const LEGACY_ROLE_RUN_DISPATCH_REASONS = new Set([
9
9
  /** Atomically useful when called inside the caller's TaskStore transaction. */
10
10
  export function enqueueWork(store, target, reason, occurredAt, refs = [], metadata = {}) {
11
11
  const mailbox = store.getWorkMailbox(target) ?? createWorkMailbox(target);
12
+ // Archive is an admission boundary, not an acknowledgement of old input.
13
+ // Runtime cleanup mailboxes remain usable for exact-owner recovery.
14
+ if ((target.kind === "task" || target.kind === "role")
15
+ && store.getTask?.(target.taskId)?.status === "archived")
16
+ return mailbox;
12
17
  const queued = enqueueSignal(mailbox, {
13
18
  reason,
14
19
  refs,
@@ -278,7 +278,7 @@ function projectNextAction(item, lanes, synthesis, mainRun, candidate) {
278
278
  return action("inspect-unknown", ["leader"], mainRun.runId === undefined ? [item.id] : [mainRun.runId]);
279
279
  }
280
280
  if (item.status === "open") {
281
- return action("dispatch-work", ["leader"], [item.id]);
281
+ return action(item.assignee === undefined ? "execute-directly" : "dispatch-work", ["leader"], [item.id]);
282
282
  }
283
283
  return action("inspect-unknown", ["leader"], [item.id]);
284
284
  }
@@ -342,6 +342,42 @@ export function updateTaskRoleProviderRuntime(set, binding, updatedAt) {
342
342
  updatedAt: requireDate(updatedAt, "Provider Runtime Binding timestamp")
343
343
  });
344
344
  }
345
+ /**
346
+ * Bind a Global Role's Provider Runtime evidence, mirroring the Task Role
347
+ * writer (decision-3 §6/§9). Create-only: an existing distinct binding is a
348
+ * conflict, an identical one is idempotent. The Global set stays schemaVersion
349
+ * 5; only the optional providerBinding is added.
350
+ */
351
+ export function bindGlobalRoleProviderRuntime(set, binding, updatedAt) {
352
+ validateRoleSessionSet(set);
353
+ const normalized = validateProviderRuntimeBinding(binding);
354
+ if (set.providerBinding != null) {
355
+ if (JSON.stringify(set.providerBinding) === JSON.stringify(normalized))
356
+ return set;
357
+ throw new Error("Global Role already has a Provider Runtime Binding.");
358
+ }
359
+ return validateRoleSessionSet({
360
+ ...set,
361
+ providerBinding: normalized,
362
+ updatedAt: requireDate(updatedAt, "Provider Runtime Binding timestamp")
363
+ });
364
+ }
365
+ /** In-place update of a Global Role's Provider Runtime binding; namespace and
366
+ * account scope are immutable, exactly as for a Task Role. */
367
+ export function updateGlobalRoleProviderRuntime(set, binding, updatedAt) {
368
+ validateRoleSessionSet(set);
369
+ const normalized = validateProviderRuntimeBinding(binding);
370
+ if (set.providerBinding == null
371
+ || normalized.providerNamespace !== set.providerBinding.providerNamespace
372
+ || normalized.accountScope !== set.providerBinding.accountScope) {
373
+ throw new Error("Provider Runtime Binding identity cannot change in place.");
374
+ }
375
+ return validateRoleSessionSet({
376
+ ...set,
377
+ providerBinding: normalized,
378
+ updatedAt: requireDate(updatedAt, "Provider Runtime Binding timestamp")
379
+ });
380
+ }
345
381
  /**
346
382
  * Detaches a confirmed-dead local Host without ending its resumable native
347
383
  * Session. Host connections are disposable execution
@@ -409,7 +445,7 @@ export function recordTaskRoleNativeTurnBoundary(set, input, completedAt) {
409
445
  export function validateRoleSessionSet(set) {
410
446
  const ownerScope = set.owner?.scope;
411
447
  rejectUnknownFields(set, ownerScope === "global"
412
- ? ["schemaVersion", "owner", "activeAgentId", "sessions", "updatedAt", "history"]
448
+ ? ["schemaVersion", "owner", "activeAgentId", "sessions", "updatedAt", "history", "providerBinding", "interrupts"]
413
449
  : [
414
450
  "schemaVersion",
415
451
  "owner",
@@ -428,10 +464,19 @@ export function validateRoleSessionSet(set) {
428
464
  if (set.schemaVersion !== 5) {
429
465
  throw new Error("Global Role session set schema version is invalid.");
430
466
  }
431
- if (Object.hasOwn(set, "providerBinding")) {
432
- throw new Error("Global Role session set must not contain Task Role lifecycle fields.");
467
+ const globalSet = set;
468
+ for (const [requestId, control] of Object.entries(globalSet.interrupts ?? {})) {
469
+ requireSafeIdentity(requestId, "Global interrupt request");
470
+ requireText(control.fingerprint, "Global interrupt fingerprint");
471
+ requireText(control.attemptId, "Global interrupt attempt");
472
+ requireText(control.nativeSessionId, "Global interrupt Session");
473
+ requireText(control.receiptId, "Global interrupt receipt");
474
+ if (control.receipt !== undefined
475
+ && !["interrupt-requested", "interrupt-not-active", "interrupt-unknown", "interrupt-unavailable"]
476
+ .includes(control.receipt.state))
477
+ throw new Error("Global interrupt receipt is invalid.");
433
478
  }
434
- const history = set.history;
479
+ const history = globalSet.history;
435
480
  if (history !== undefined) {
436
481
  for (const [ref, session] of Object.entries(history)) {
437
482
  requireSafeIdentity(ref, "Operator session ref");
@@ -441,6 +486,21 @@ export function validateRoleSessionSet(set) {
441
486
  }
442
487
  }
443
488
  }
489
+ // A Global Role now carries the same optional Provider Runtime Binding shape
490
+ // as a Task Role (decision-3 §6/§9). It stays absent on every legacy set;
491
+ // when present, it is the exact native control evidence a Global steer or
492
+ // interrupt targets, validated identically to the Task branch. A fabricated
493
+ // Task binding is never accepted here, and its presence never upgrades the
494
+ // schemaVersion these raw-JSON sets are read at.
495
+ if (Object.hasOwn(globalSet, "providerBinding") && globalSet.providerBinding != null) {
496
+ const providerBinding = validateProviderRuntimeBinding(globalSet.providerBinding);
497
+ const conversationId = currentProviderConversation(providerBinding).conversationId;
498
+ const session = [...Object.values(globalSet.sessions), ...Object.values(globalSet.history ?? {})]
499
+ .find(entry => entry.agentId === providerBinding.accountScope && entry.nativeSessionId === conversationId);
500
+ if (session !== undefined && providerBinding.providerNamespace !== builtinDriverIdForAdapter(session.adapterId)) {
501
+ throw new Error("Provider Runtime Binding namespace does not match the Agent adapter.");
502
+ }
503
+ }
444
504
  }
445
505
  else {
446
506
  if (set.schemaVersion !== 12) {
@@ -177,6 +177,9 @@ export class ExecutorRegistry {
177
177
  return deliveryReport(outcome);
178
178
  }
179
179
  async notifyOperatorInputOnce(input) {
180
+ if (this.runtimePorts?.notifyOperatorInputOnce !== undefined) {
181
+ return this.runtimePorts.notifyOperatorInputOnce(input);
182
+ }
180
183
  const probe = this.readiness(input.adapterId, "operator");
181
184
  return this.tmux.sendRoleInputOnceIfReadyAsync === undefined
182
185
  ? this.tmux.sendRoleInputOnceIfReady("operator", input.roleName, input.receiptId, input.text, probe)