@zq-silk/yui 0.15.9 → 0.15.11

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 (100) hide show
  1. package/dist/agent/launchEnvironment.js +7 -0
  2. package/dist/cli/commandCatalog.js +26 -6
  3. package/dist/cli/interactionPolicy.js +3 -3
  4. package/dist/cli/updateOrchestrator.js +24 -1
  5. package/dist/cli/updatePorts.js +7 -3
  6. package/dist/cli/upgradeCommand.js +42 -2
  7. package/dist/cli.js +336 -93
  8. package/dist/commands/globalRoleCommands.js +314 -4
  9. package/dist/commands/projectCommands.js +6 -7
  10. package/dist/commands/releaseCommands.js +18 -0
  11. package/dist/commands/taskActor.js +25 -0
  12. package/dist/commands/taskCommands.js +511 -83
  13. package/dist/commands/taskIntegrationCommands.js +16 -38
  14. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  15. package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
  16. package/dist/commands/taskRoleRuntimeStatus.js +35 -0
  17. package/dist/context/taskContext.js +38 -2
  18. package/dist/controller/agentHostObservation.js +155 -0
  19. package/dist/controller/clientRuntime.js +17 -2
  20. package/dist/controller/controller.js +3 -0
  21. package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
  22. package/dist/controller/globalInputDelivery.js +119 -0
  23. package/dist/controller/jobControl.js +6 -2
  24. package/dist/controller/resourceInventory.js +14 -4
  25. package/dist/controller/resourceInventoryLinux.js +2 -6
  26. package/dist/controller/runtime.js +81 -6
  27. package/dist/controller/runtimeEventInbox.js +32 -3
  28. package/dist/controller/runtimeEventProcessor.js +26 -6
  29. package/dist/controller/runtimeHookRunFence.js +75 -19
  30. package/dist/controller/structuredProviderObservation.js +133 -70
  31. package/dist/coordination/workMailboxQueue.js +5 -0
  32. package/dist/execution/workItemExecutionProjection.js +1 -1
  33. package/dist/executor/agentExecutor.js +64 -4
  34. package/dist/executor/executorRegistry.js +3 -0
  35. package/dist/executor/fileRoleLaunchPlanner.js +78 -118
  36. package/dist/integration/deliveryObligation.js +2 -1
  37. package/dist/integration/gitIntegrationService.js +312 -382
  38. package/dist/integration/integrationAttempt.js +30 -4
  39. package/dist/integration/integrationQueueService.js +7 -7
  40. package/dist/integration/integrationSourceApplication.js +323 -0
  41. package/dist/message/globalInterrupt.js +33 -0
  42. package/dist/message/inputControlResolution.js +106 -0
  43. package/dist/message/message.js +367 -0
  44. package/dist/message/messageContinuation.js +126 -3
  45. package/dist/message/taskInterrupt.js +34 -0
  46. package/dist/observability/orchestrationMetrics.js +1 -1
  47. package/dist/release/releaseHandover.js +22 -0
  48. package/dist/release/releaseWorkflowPorts.js +15 -7
  49. package/dist/repository/gitWorkspace.js +72 -15
  50. package/dist/repository/taskWorkspaceCoordinator.js +134 -0
  51. package/dist/repository/taskWorkspacePreparer.js +120 -49
  52. package/dist/repository/workItemCandidateSnapshot.js +34 -0
  53. package/dist/resources/resourceDiscovery.js +3 -2
  54. package/dist/runtime/agentHost.js +152 -72
  55. package/dist/runtime/agentHostCompatibility.js +127 -0
  56. package/dist/runtime/agentHostProtocol.js +53 -0
  57. package/dist/runtime/executionEnvironment.js +0 -19
  58. package/dist/runtime/launchBroker.js +6 -0
  59. package/dist/runtime/sessionReconciliation.js +4 -4
  60. package/dist/runtime/taskRuntimeIsolation.js +30 -6
  61. package/dist/runtime/tmuxAdapters.js +5 -3
  62. package/dist/scheduler/operatorEvent.js +4 -0
  63. package/dist/scheduler/taskExecutionProjection.js +12 -1
  64. package/dist/scheduler/wakeReason.js +7 -1
  65. package/dist/scheduler/wakeupQueue.js +2 -0
  66. package/dist/setup/setupCommand.js +26 -8
  67. package/dist/storage/homeLayout.js +130 -0
  68. package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
  69. package/dist/storage/migrations/integrationContinuation.js +104 -0
  70. package/dist/storage/migrations/unifyHomeLayout.js +925 -0
  71. package/dist/storage/sqliteSchema.js +136 -4
  72. package/dist/storage/sqliteStore.js +40 -1
  73. package/dist/storage/storageVersions.js +1 -1
  74. package/dist/storage/storeRpc.js +1 -0
  75. package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
  76. package/dist/task/archiveDiagnostics.js +128 -0
  77. package/dist/task/nextAction.js +44 -11
  78. package/dist/web/assets/client/app.js +55 -0
  79. package/dist/web/assets/client/components.js +1 -0
  80. package/dist/web/assets/client/i18n.js +6 -0
  81. package/dist/web/assets/client/taskSurface.js +106 -0
  82. package/dist/web/assets/client/view.js +7 -4
  83. package/dist/web/assets/shell.js +23 -0
  84. package/dist/web/assets/styles/layout.js +1 -1
  85. package/dist/web/assets/styles/widgets.js +12 -0
  86. package/dist/web/webServer.js +117 -1
  87. package/dist/web/webSnapshot.js +4 -3
  88. package/dist/web/webTaskSurface.js +222 -5
  89. package/dist/workspace/workItemChangeSetManager.js +18 -2
  90. package/docs/release-workflow.md +39 -0
  91. package/docs/release-workflow.zh-CN.md +29 -0
  92. package/docs/sqlite-control-plane-design.md +223 -1
  93. package/docs/testing/verification-levels.md +24 -0
  94. package/docs/testing/verification-levels.zh-CN.md +11 -0
  95. package/package.json +1 -1
  96. package/skills/yui-leader/references/execution.md +151 -49
  97. package/skills/yui-leader/references/integration.md +52 -2
  98. package/skills/yui-operator/SKILL.md +6 -1
  99. package/skills/yui-runtime/SKILL.md +27 -0
  100. package/skills/yui-runtime/references/publication.md +20 -0
@@ -5,27 +5,66 @@ import { formatRunReceiptId } from "../task/taskRecordReference.js";
5
5
  import { currentProviderConversation, managedProviderTurnId } from "../runtime/providerRuntimeIdentity.js";
6
6
  import { taskRoleRuntimeIdentity } from "../runtime/managedCaller.js";
7
7
  import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
8
+ export class RuntimeHookRunFenceError extends Error {
9
+ }
8
10
  /**
9
11
  * Resolve observations by Session and exact input identity. A startup Hook
10
12
  * may introduce a Session for an explicit new-Session AgentRun.
11
13
  */
12
- export function resolveRuntimeHookRunFence(environment, adapterId, payloadNativeSessionId, options = {}) {
14
+ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNativeSessionId, options = {}, currentStore) {
15
+ if (currentStore === undefined) {
16
+ const store = openCurrentTaskStore(requireIdentity(environment.YUI_HOME, "YUI_HOME"));
17
+ try {
18
+ return resolveRuntimeHookRunFence(environment, adapterId, payloadNativeSessionId, options, store);
19
+ }
20
+ finally {
21
+ store.close();
22
+ }
23
+ }
24
+ if (environment.YUI_SESSION_SCOPE === "global") {
25
+ if (environment.YUI_ADAPTER_ID !== adapterId)
26
+ throw new RuntimeHookRunFenceError("Global observation adapter mismatch.");
27
+ const roleName = requireIdentity(environment.YUI_ROLE, "Role name");
28
+ const agentId = requireIdentity(environment.YUI_AGENT_ID, "Agent id");
29
+ const workspace = requireIdentity(environment.YUI_WORKSPACE, "YUI workspace");
30
+ const nativeSessionId = requireIdentity(payloadNativeSessionId, "Provider session id");
31
+ const sessions = currentStore.getGlobalRoleSessionSet(roleName);
32
+ const role = currentStore.getGlobalRole(roleName);
33
+ const session = sessions?.sessions[agentId];
34
+ const turn = sessions?.providerBinding?.run;
35
+ if (role?.activeAgentId !== agentId || sessions?.activeAgentId !== agentId || session?.adapterId !== adapterId
36
+ || session.status === "ended"
37
+ || session.nativeSessionId !== nativeSessionId || session.effective.workspace.root !== workspace) {
38
+ throw new RuntimeHookRunFenceError("Global observation Session does not match durable state.");
39
+ }
40
+ if (options.attemptId !== undefined && turn?.attemptId !== options.attemptId
41
+ || options.nativeTurnId !== undefined && turn?.nativeTurnId !== undefined
42
+ && turn.nativeTurnId !== options.nativeTurnId) {
43
+ throw new RuntimeHookRunFenceError("Global observation does not match the exact current input.");
44
+ }
45
+ if (options.exactInput && (turn == null
46
+ || options.attemptId === undefined && (options.nativeTurnId === undefined || turn.nativeTurnId !== options.nativeTurnId)
47
+ || ["rejected", "deferred"].includes(turn.status))) {
48
+ throw new RuntimeHookRunFenceError("Global observation input has no exact execution binding.");
49
+ }
50
+ return { roleName, agentId, workspace, nativeSessionId,
51
+ ...(options.sessionOnly || turn == null ? {} : { receiptId: turn.attemptId }) };
52
+ }
13
53
  if (environment.YUI_SESSION_SCOPE !== "task") {
14
- throw new Error("Runtime observation Hook requires a Task session scope.");
54
+ throw new RuntimeHookRunFenceError("Runtime observation Hook requires a Task session scope.");
15
55
  }
16
56
  if (environment.YUI_ADAPTER_ID !== adapterId) {
17
- throw new Error(`Runtime observation Hook requires the ${adapterId} adapter.`);
57
+ throw new RuntimeHookRunFenceError(`Runtime observation Hook requires the ${adapterId} adapter.`);
18
58
  }
19
- const home = requireIdentity(environment.YUI_HOME, "YUI_HOME");
20
59
  const taskId = requireIdentity(environment.YUI_TASK_ID, "Task id");
21
60
  const roleName = requireIdentity(environment.YUI_ROLE, "Role name");
22
61
  const agentId = requireIdentity(environment.YUI_AGENT_ID, "Agent id");
23
62
  const workspace = requireIdentity(environment.YUI_WORKSPACE, "YUI workspace");
24
63
  const nativeSessionId = requireIdentity(payloadNativeSessionId, "Provider session id");
25
- const store = openCurrentTaskStore(home);
64
+ const store = currentStore;
26
65
  const task = store.getTask(taskId);
27
66
  if (task === null) {
28
- throw new Error("Runtime observation Hook Task does not accept this lifecycle boundary.");
67
+ throw new RuntimeHookRunFenceError("Runtime observation Hook Task does not accept this lifecycle boundary.");
29
68
  }
30
69
  const role = store.getRole(taskId, roleName);
31
70
  const sessions = store.getTaskRoleSessionSet(taskId, roleName);
@@ -56,6 +95,14 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
56
95
  taskId, roleName, agentId, nativeSessionId,
57
96
  continuationId: options.continuationId,
58
97
  }));
98
+ if (options.exactInput && acceptedBinding === null && !matchesProviderTurn) {
99
+ throw new RuntimeHookRunFenceError("Runtime observation Hook input has no exact accepted execution binding.");
100
+ }
101
+ if (options.startupRunId !== undefined && options.startupRunId !== activeRun?.id) {
102
+ // A historical Session can report terminal evidence, but its old startup
103
+ // must not bind a new Session under a successor's active Run.
104
+ throw new RuntimeHookRunFenceError("Runtime observation Hook startup does not match its exact launch Run.");
105
+ }
59
106
  // Collection of an exactly accepted terminal fact is not a new action by
60
107
  // the Role. A successor's active Agent or revoked execution permission
61
108
  // cannot erase the original AgentRun's evidence.
@@ -70,17 +117,26 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
70
117
  && activeRun.purpose === "planning"
71
118
  && activeRun.roleName === roleName
72
119
  && runPurposeAdmitsTaskState(activeRun.purpose, task);
120
+ // Restoring the Draft Leader's existing planning Session needs no new Run.
121
+ // Admit its Session facts; the shared identity/workspace fence below still
122
+ // requires the exact current Session and never grants delivery authority.
123
+ const planningSessionObservation = options.sessionOnly === true
124
+ && task.status === "draft" && roleName === "leader"
125
+ && runPurposeAdmitsTaskState("planning", task)
126
+ && session?.status === "active"
127
+ && session.effective.executionAuthority === "planning";
73
128
  if (!(task.status === "active" && task.executionGate.state === "enabled")
74
129
  && !(task.status === "completed" && options.sessionOnly === true)
75
130
  && !planningObservation
131
+ && !planningSessionObservation
76
132
  && !existingExecutionObservation) {
77
- throw new Error("Runtime observation Hook Task does not accept this lifecycle boundary.");
133
+ throw new RuntimeHookRunFenceError("Runtime observation Hook Task does not accept this lifecycle boundary.");
78
134
  }
79
135
  const runtimeIdentity = role === null ? null : taskRoleRuntimeIdentity(role, activeRun);
80
136
  if (!existingExecutionObservation && (runtimeIdentity === null || runtimeIdentity.agentId !== agentId
81
137
  || runtimeIdentity.adapterId !== adapterId
82
138
  || (sessions !== null && sessions.activeAgentId !== agentId))) {
83
- throw new Error("Runtime observation Hook Role or Agent is not current.");
139
+ throw new RuntimeHookRunFenceError("Runtime observation Hook Role or Agent is not current.");
84
140
  }
85
141
  const lifecycleMailbox = store.getWorkMailbox(runtimeLifecycleTarget({
86
142
  scope: "task",
@@ -97,7 +153,7 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
97
153
  || observedSession.adapterId !== adapterId
98
154
  || observedSession.nativeSessionId !== nativeSessionId
99
155
  || observedSession.effective.workspace.root !== workspace) {
100
- throw new Error("Runtime observation Hook Session does not match durable state.");
156
+ throw new RuntimeHookRunFenceError("Runtime observation Hook Session does not match durable state.");
101
157
  }
102
158
  return {
103
159
  taskId,
@@ -146,7 +202,7 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
146
202
  ? managedProviderTurnId(providerTurn) ?? undefined
147
203
  : undefined;
148
204
  if (options.terminal === true && acceptedBinding === null && registeredRunId === undefined) {
149
- throw new Error("Runtime observation Hook terminal has no exact accepted execution binding.");
205
+ throw new RuntimeHookRunFenceError("Runtime observation Hook terminal has no exact accepted execution binding.");
150
206
  }
151
207
  const terminalRun = acceptedBinding !== null
152
208
  ? store.getRun(taskId, acceptedBinding.fence.runId)
@@ -166,7 +222,7 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
166
222
  && !exactTerminal
167
223
  && registeredRunId === undefined
168
224
  && acceptedBinding === null) {
169
- throw new Error("Runtime observation Hook has no matching durable in-flight AgentRun.");
225
+ throw new RuntimeHookRunFenceError("Runtime observation Hook has no matching durable in-flight AgentRun.");
170
226
  }
171
227
  const runId = acceptedBinding?.fence.runId
172
228
  ?? registeredRunId
@@ -181,21 +237,21 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
181
237
  || run.roleName !== roleName
182
238
  || run.effective.agentId !== agentId
183
239
  || run.effective.adapterId !== adapterId) {
184
- throw new Error("Runtime observation Hook AgentRun does not match durable active state.");
240
+ throw new RuntimeHookRunFenceError("Runtime observation Hook AgentRun does not match durable active state.");
185
241
  }
186
242
  if (run.effective.workspace.root !== workspace) {
187
- throw new Error("Runtime observation Hook workspace does not match the durable AgentRun snapshot.");
243
+ throw new RuntimeHookRunFenceError("Runtime observation Hook workspace does not match the durable AgentRun snapshot.");
188
244
  }
189
245
  if (session !== undefined && acceptedBinding === null && !matchesProviderTurn && !replacementStartup && !resumedStartup) {
190
246
  if (session.adapterId !== adapterId
191
247
  || session.nativeSessionId !== nativeSessionId
192
248
  || session.effective.workspace.root !== workspace) {
193
- throw new Error("Runtime observation Hook Session does not match durable state.");
249
+ throw new RuntimeHookRunFenceError("Runtime observation Hook Session does not match durable state.");
194
250
  }
195
251
  }
196
252
  else if (acceptedBinding === null && !matchesProviderTurn && (session === undefined || replacementStartup)) {
197
253
  if (!discoveredStartup && !preallocatedStartup) {
198
- throw new Error("Runtime observation Hook has no matching new-Session intent.");
254
+ throw new RuntimeHookRunFenceError("Runtime observation Hook has no matching new-Session intent.");
199
255
  }
200
256
  }
201
257
  return {
@@ -230,7 +286,7 @@ function knownContinuationBinding(events, expected) {
230
286
  return null;
231
287
  if (matches.some((candidate) => candidate.fence.runId !== binding.fence.runId
232
288
  || candidate.fence.receiptId !== binding.fence.receiptId)) {
233
- throw new Error("Runtime observation Hook continuation has conflicting durable AgentRun bindings.");
289
+ throw new RuntimeHookRunFenceError("Runtime observation Hook continuation has conflicting durable AgentRun bindings.");
234
290
  }
235
291
  return binding;
236
292
  }
@@ -260,17 +316,17 @@ function acceptedRunBinding(events, expected) {
260
316
  && candidate.fence.nativeTurnId !== expected.nativeTurnId)
261
317
  || candidate.fence.runId !== binding.fence.runId
262
318
  || candidate.fence.receiptId !== binding.fence.receiptId))) {
263
- throw new Error("Runtime observation Hook native Turn has conflicting durable AgentRun bindings.");
319
+ throw new RuntimeHookRunFenceError("Runtime observation Hook native Turn has conflicting durable AgentRun bindings.");
264
320
  }
265
321
  return binding;
266
322
  }
267
323
  function requireIdentity(value, label) {
268
324
  if (typeof value !== "string" || value.includes("\0")) {
269
- throw new Error(`${label} is required.`);
325
+ throw new RuntimeHookRunFenceError(`${label} is required.`);
270
326
  }
271
327
  const normalized = value.trim();
272
328
  if (normalized.length === 0 || normalized.length > 1_024) {
273
- throw new Error(`${label} is invalid.`);
329
+ throw new RuntimeHookRunFenceError(`${label} is invalid.`);
274
330
  }
275
331
  return normalized;
276
332
  }
@@ -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
  }