@zq-silk/yui 0.16.0 → 0.16.2

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 (114) hide show
  1. package/ARCHITECTURE.md +12 -0
  2. package/ARCHITECTURE.zh-CN.md +8 -0
  3. package/README.md +3 -0
  4. package/dist/agent/managedRuntimeEnvironment.js +0 -4
  5. package/dist/agentRun/agentRun.js +7 -17
  6. package/dist/cli/agentConfigurationPicker.js +24 -21
  7. package/dist/cli/commandCatalog.js +18 -10
  8. package/dist/cli/roleWizard.js +41 -30
  9. package/dist/cli/updateCommand.js +35 -13
  10. package/dist/cli/updateOrchestrator.js +73 -33
  11. package/dist/cli/updatePorts.js +51 -20
  12. package/dist/cli.js +35 -26
  13. package/dist/commands/controllerCommands.js +1 -1
  14. package/dist/commands/executionAuditCommands.js +2 -2
  15. package/dist/commands/projectCommands.js +1 -1
  16. package/dist/commands/releaseCommands.js +9 -38
  17. package/dist/commands/taskCommands.js +84 -81
  18. package/dist/commands/taskCompletionGate.js +0 -117
  19. package/dist/commands/taskRoleRuntimeStatus.js +1 -1
  20. package/dist/commands/taskUpstreamCommands.js +93 -54
  21. package/dist/context/runContextPack.js +30 -54
  22. package/dist/context/runInputContract.js +9 -0
  23. package/dist/context/sessionBootstrapManifest.js +11 -19
  24. package/dist/controller/agentHostObservation.js +4 -2
  25. package/dist/controller/controller.js +8 -6
  26. package/dist/controller/fileSchedulerStoreAdapter.js +85 -18
  27. package/dist/controller/globalInputDelivery.js +28 -7
  28. package/dist/controller/globalRuntimeAttention.js +34 -0
  29. package/dist/controller/operatorNotification.js +31 -0
  30. package/dist/controller/providerRetryAdmission.js +3 -1
  31. package/dist/controller/providerRetryDelivery.js +10 -3
  32. package/dist/controller/runtime.js +1 -27
  33. package/dist/controller/sessionOwnerReconciliation.js +42 -24
  34. package/dist/controller/structuredProviderObservation.js +27 -0
  35. package/dist/controller/updateReconciliation.js +72 -20
  36. package/dist/doctor/doctor.js +2 -1
  37. package/dist/executor/agentAdapter.js +28 -96
  38. package/dist/executor/agentConfigurationCatalog.js +13 -68
  39. package/dist/executor/agentConfigurationFields.js +120 -0
  40. package/dist/executor/agentConfigurationProbe.js +55 -80
  41. package/dist/executor/agentExecutor.js +5 -2
  42. package/dist/executor/executorRegistry.js +0 -9
  43. package/dist/executor/fileRoleLaunchPlanner.js +12 -11
  44. package/dist/interaction/operatorPresentation.js +2 -1
  45. package/dist/message/messageContinuation.js +8 -9
  46. package/dist/observability/executionAudit.js +1 -10
  47. package/dist/observability/runtimeIdentity.js +0 -23
  48. package/dist/output/agentConfigurationPresentation.js +4 -2
  49. package/dist/release/releaseHandover.js +2 -2
  50. package/dist/release/releaseIdempotencyStore.js +0 -23
  51. package/dist/release/releaseWorkflowPorts.js +8 -4
  52. package/dist/repository/gitWorkspace.js +66 -19
  53. package/dist/repository/taskWorkspacePreparer.js +75 -68
  54. package/dist/resources/resourceRegistry.js +90 -44
  55. package/dist/resources/sqliteResourceRegistry.js +3 -5
  56. package/dist/runtime/acpSession.js +21 -5
  57. package/dist/runtime/agentEndpoint.js +4 -1
  58. package/dist/runtime/agentHost.js +22 -62
  59. package/dist/runtime/agentHostCleanup.js +85 -0
  60. package/dist/runtime/agentHostProtocol.js +1 -1
  61. package/dist/runtime/builtinAgentDrivers.js +46 -119
  62. package/dist/runtime/builtinTranscriptObserver.js +10 -6
  63. package/dist/runtime/codexAppServerRuntime.js +55 -89
  64. package/dist/runtime/jsonLineChannel.js +35 -7
  65. package/dist/runtime/providerContinuationReconciliationService.js +42 -3
  66. package/dist/runtime/providerRetry.js +15 -0
  67. package/dist/runtime/providerRuntimeReconciler.js +12 -2
  68. package/dist/runtime/structuredProviderHost.js +106 -81
  69. package/dist/scheduler/activeRoleRunDelivery.js +5 -1
  70. package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
  71. package/dist/storage/upgrade/upgradeOrchestrator.js +0 -10
  72. package/dist/task/nextAction.js +33 -23
  73. package/dist/task/task.js +2 -1
  74. package/dist/web/assets/assetManifest.js +2 -0
  75. package/dist/web/assets/client/app.js +72 -3
  76. package/dist/web/assets/client/components.js +2 -1
  77. package/dist/web/assets/client/i18n.js +2 -0
  78. package/dist/web/assets/client/taskSummary.js +345 -0
  79. package/dist/web/assets/client/taskSurface.js +44 -26
  80. package/dist/web/assets/client/view.js +33 -0
  81. package/dist/web/assets/shell.js +1 -0
  82. package/dist/web/assets/styles/cards.js +21 -0
  83. package/dist/web/webServer.js +39 -5
  84. package/dist/web/webSessions.js +165 -0
  85. package/dist/web/webSnapshot.js +23 -0
  86. package/dist/web/webTaskSurface.js +23 -0
  87. package/docs/managed-turn-and-session-runtime.md +31 -0
  88. package/docs/managed-turn-and-session-runtime.zh-CN.md +23 -0
  89. package/docs/observability/README.md +47 -0
  90. package/docs/observability/README.zh-CN.md +37 -0
  91. package/docs/project-refresh.md +9 -0
  92. package/docs/project-refresh.zh-CN.md +8 -0
  93. package/docs/provider-retry.md +35 -0
  94. package/docs/release-workflow.md +139 -20
  95. package/docs/release-workflow.zh-CN.md +115 -14
  96. package/docs/roles-and-configuration.md +30 -0
  97. package/docs/roles-and-configuration.zh-CN.md +20 -0
  98. package/docs/task-delivery.md +109 -0
  99. package/docs/task-delivery.zh-CN.md +80 -0
  100. package/docs/task-discovery.md +9 -0
  101. package/docs/task-discovery.zh-CN.md +6 -0
  102. package/docs/testing/verification-levels.md +33 -12
  103. package/docs/testing/verification-levels.zh-CN.md +28 -8
  104. package/i18n/README.zh-CN.md +2 -0
  105. package/package.json +1 -1
  106. package/skills/yui-leader/SKILL.md +11 -0
  107. package/skills/yui-leader/references/execution.md +22 -0
  108. package/skills/yui-leader/references/planning.md +6 -2
  109. package/skills/yui-operator/SKILL.md +45 -13
  110. package/skills/yui-operator/references/task-delivery.md +99 -0
  111. package/skills/yui-runtime/SKILL.md +5 -3
  112. package/skills/yui-runtime/references/publication.md +56 -5
  113. package/skills/yui-runtime/references/recovery.md +12 -2
  114. package/dist/runtime/agentHostCompatibility.js +0 -127
@@ -61,71 +61,110 @@ async function integrateUpstream(args, store, home, options) {
61
61
  }
62
62
  const service = new GitIntegrationService(store.rootDirectory(), store, git, now, options.environment, undefined, options.jobPort);
63
63
  const results = [];
64
- for (const binding of task.projectBindings) {
65
- if (projectRef !== undefined && binding.projectId !== projectRef)
66
- continue;
67
- const project = store.getProject(binding.projectId);
68
- if (project === null)
69
- throw usageError(`Project not found: ${binding.projectId}.`);
70
- if (project.remoteUrl === undefined) {
71
- throw usageError(`Project has no remote URL: ${project.id}.`);
72
- }
73
- if (binding.baseCommit === undefined || binding.currentCommit === undefined) {
74
- throw usageError(`Task Project has no activated commit boundary: ${task.id}/${project.id}.`);
75
- }
76
- const entry = workspaceProjectEntry(workspace, project.id);
77
- if (entry === undefined) {
78
- throw usageError(`Task workspace has no entry for Project: ${project.id}.`);
79
- }
80
- const head = (await git.inspect(entry.path, entry.branch)).baseCommit;
81
- if (head !== binding.currentCommit) {
82
- throw usageError(`Task Project current commit diverged from its main clone: ${task.id}/${project.id}.`);
64
+ let failure;
65
+ const selected = task.projectBindings.filter(binding => projectRef === undefined || binding.projectId === projectRef);
66
+ if (projectRef !== undefined && selected.length === 0) {
67
+ throw usageError(`Task Project not found: ${task.id}/${projectRef}.`);
68
+ }
69
+ for (const binding of selected) {
70
+ let phase = "prepare";
71
+ let integrationId;
72
+ try {
73
+ const project = store.getProject(binding.projectId);
74
+ if (project === null)
75
+ throw usageError(`Project not found: ${binding.projectId}.`);
76
+ if (project.remoteUrl === undefined) {
77
+ throw usageError(`Project has no remote URL: ${project.id}.`);
78
+ }
79
+ if (binding.baseCommit === undefined || binding.currentCommit === undefined) {
80
+ throw usageError(`Task Project has no activated commit boundary: ${task.id}/${project.id}.`);
81
+ }
82
+ const entry = workspaceProjectEntry(workspace, project.id);
83
+ if (entry === undefined) {
84
+ throw usageError(`Task workspace has no entry for Project: ${project.id}.`);
85
+ }
86
+ const head = (await git.inspect(entry.path, entry.branch)).baseCommit;
87
+ if (head !== binding.currentCommit) {
88
+ throw usageError(`Task Project current commit diverged from its main clone: ${task.id}/${project.id}.`);
89
+ }
90
+ phase = "resolve-remote";
91
+ const remote = await git.resolveRemoteHead({
92
+ remoteUrl: project.remoteUrl,
93
+ branch: binding.baseRef
94
+ });
95
+ const attempt = store.transaction((tx) => {
96
+ const created = createIntegrationAttempt({
97
+ id: tx.nextIntegrationAttemptId(task.id),
98
+ taskId: task.id,
99
+ projectId: project.id,
100
+ targetRef: entry.branch,
101
+ beforeCommit: binding.currentCommit,
102
+ source: {
103
+ kind: "upstream",
104
+ branch: remote.branch,
105
+ remoteCommit: remote.commit,
106
+ taskBaseCommit: binding.baseCommit,
107
+ strategy: "rebase"
108
+ },
109
+ checkCommands: parsed.many.get("--check") ?? [],
110
+ rerunChecks: parsed.one.has("--rerun-checks")
111
+ }, now());
112
+ tx.saveIntegrationAttempt(task.id, created);
113
+ return created;
114
+ });
115
+ integrationId = attempt.id;
116
+ phase = "integrate";
117
+ const result = await service.integrate(task.id, attempt.id);
118
+ results.push(result);
119
+ // Each Project has an independent candidate and Job. --latest admits all
120
+ // requested Projects, then the Agent continues the returned exact attempts.
121
+ if (result.status !== "committed" && result.status !== "checks-running")
122
+ break;
83
123
  }
84
- const remote = await git.resolveRemoteHead({
85
- remoteUrl: project.remoteUrl,
86
- branch: binding.baseRef
87
- });
88
- const attempt = store.transaction((tx) => {
89
- const created = createIntegrationAttempt({
90
- id: tx.nextIntegrationAttemptId(task.id),
91
- taskId: task.id,
92
- projectId: project.id,
93
- targetRef: entry.branch,
94
- beforeCommit: binding.currentCommit,
95
- source: {
96
- kind: "upstream",
97
- branch: remote.branch,
98
- remoteCommit: remote.commit,
99
- taskBaseCommit: binding.baseCommit,
100
- strategy: "rebase"
101
- },
102
- checkCommands: parsed.many.get("--check") ?? [],
103
- rerunChecks: parsed.one.has("--rerun-checks")
104
- }, now());
105
- tx.saveIntegrationAttempt(task.id, created);
106
- return created;
107
- });
108
- const result = await service.integrate(task.id, attempt.id);
109
- results.push(result);
110
- // Each Project has an independent candidate and Job. --latest admits all
111
- // requested Projects, then the Agent continues the returned exact attempts.
112
- if (result.status !== "committed" && result.status !== "checks-running")
124
+ catch (error) {
125
+ // Earlier Projects may already have advanced HEAD or admitted a Job.
126
+ // Preserve those receipts and the exact uncertain attempt; never turn a
127
+ // partially executed batch into a usage error or replay it automatically.
128
+ failure = {
129
+ projectId: binding.projectId,
130
+ phase,
131
+ message: error instanceof Error ? error.message : String(error),
132
+ ...(integrationId === undefined ? {} : { integrationId }),
133
+ effect: integrationId === undefined ? "not-started" : "inspect-required"
134
+ };
113
135
  break;
136
+ }
114
137
  }
115
- if (projectRef !== undefined && results.length === 0) {
116
- throw usageError(`Task Project not found: ${task.id}/${projectRef}.`);
117
- }
138
+ const attempted = new Set([
139
+ ...results.map(result => result.attempt.projectId),
140
+ ...(failure === undefined ? [] : [failure.projectId])
141
+ ]);
142
+ const remainingProjectIds = selected.map(binding => binding.projectId)
143
+ .filter(id => !attempted.has(id));
144
+ const stage = failure !== undefined
145
+ || results.some(result => result.status !== "committed" && result.status !== "checks-running")
146
+ ? "integration-blocked"
147
+ : results.some(result => result.status === "checks-running")
148
+ ? "checks-running" : "integrated";
118
149
  const lines = results.flatMap(result => {
119
150
  const { attempt, status } = result;
120
151
  return [
121
- ` ${attempt.id} ${attempt.projectId}: ${attempt.beforeCommit.slice(0, 12)} -> ${(attempt.afterCommit ?? attempt.candidateCommit ?? attempt.beforeCommit).slice(0, 12)} (${status})`,
152
+ ` ${attempt.id} ${attempt.projectId}: ${status} (rebase)`,
153
+ ` Expected HEAD: ${attempt.beforeCommit}; committed HEAD: ${attempt.afterCommit ?? "not committed"}`,
154
+ ` Candidate: ${attempt.candidateCommit ?? "not prepared"}`,
122
155
  ...(status === "checks-running" ? [
123
156
  ` Job ${result.job.id}; continue with yui task integration continue ${task.id}/${attempt.id}`
124
157
  ] : [])
125
158
  ];
126
159
  });
127
160
  return {
128
- output: `Upstream Integration results for Task ${task.id}:\n${lines.join("\n")}\n`,
129
- data: { taskId: task.id, integrations: results }
161
+ output: `Upstream Integration results for Task ${task.id} (${stage}):\n${lines.join("\n")}\n`
162
+ + (failure === undefined ? "" : (` ${failure.projectId} stopped during ${failure.phase}: ${failure.message}\n`
163
+ + (failure.integrationId === undefined
164
+ ? " No Integration was started for this Project.\n"
165
+ : ` Inspect yui task integration show ${task.id}/${failure.integrationId}; effects require inspection, not batch replay.\n`)))
166
+ + (remainingProjectIds.length === 0 ? "" : ` Not attempted: ${remainingProjectIds.join(", ")}\n`),
167
+ data: { taskId: task.id, stage, strategy: "rebase", integrations: results,
168
+ remainingProjectIds, ...(failure === undefined ? {} : { failure }) }
130
169
  };
131
170
  }
@@ -1,7 +1,8 @@
1
1
  import { providerRetryProjection } from "../runtime/providerRetry.js";
2
2
  import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
3
3
  import { TASK_COMPLETION_PUBLISHED_TREE_AUTHORIZED_EVENT } from "../task/publicationReference.js";
4
- import { RUN_INPUT_MAX_DELTAS } from "./runInputContract.js";
4
+ import { RUN_INPUT_MAX_DELTAS, requireRunContextSnapshotRef } from "./runInputContract.js";
5
+ import { requireIdentity } from "../domain/validation.js";
5
6
  import { assertWorkItemDependenciesCompleted } from "../workItem/dependencyGate.js";
6
7
  import { governingWorkItemCandidate, workItemExecutionGroupById } from "../workItem/workItem.js";
7
8
  import { formatGitArtifactRef, validateGitArtifactRef } from "../artifacts/gitArtifactRef.js";
@@ -209,26 +210,14 @@ export function contextSnapshotDeltaRefIds(store, snapshot) {
209
210
  }
210
211
  export function buildRunContextPack(store, taskId, runId) {
211
212
  const run = requireExactRun(store, taskId, runId);
212
- const current = collectAuthorizedContext(store, run);
213
- let pointers = current.map(({ ref }) => ref);
214
- let snapshotRef;
215
- if (run.inputs[0].input.contextSnapshotRef !== undefined) {
216
- const expected = run.inputs[0].input.contextSnapshotRef;
217
- const snapshot = store.getContextSnapshot(taskId, expected.id);
218
- if (snapshot === null)
219
- throw new Error(`AgentRun Context Snapshot is missing: ${expected.id}.`);
220
- validateContextSnapshot(snapshot);
221
- if (snapshot.digest !== expected.digest || snapshot.taskId !== taskId) {
222
- throw new Error(`AgentRun Context Snapshot identity drifted: ${expected.id}.`);
223
- }
224
- pointers = snapshot.refs;
225
- snapshotRef = contextSnapshotRef(snapshot);
226
- }
213
+ const snapshot = readRunContextSnapshot(store, run);
214
+ const pointers = snapshot.refs;
227
215
  if (pointers.length > RUN_CONTEXT_PACK_MAX_REFS) {
228
216
  throw new Error(`AgentRun Context exceeds ${RUN_CONTEXT_PACK_MAX_REFS} authorized refs.`);
229
217
  }
230
218
  const view = contextView(run);
231
- const writableProjectIds = writableProjects(store, run, view);
219
+ const writableProjectIds = run.effective.executionAuthority === "planning"
220
+ ? [] : run.effective.writeProjectIds;
232
221
  const summaries = pointers.map((ref) => Object.freeze({
233
222
  refId: ref.refId,
234
223
  store: ref.store,
@@ -246,7 +235,7 @@ export function buildRunContextPack(store, taskId, runId) {
246
235
  adapterId: run.effective.adapterId,
247
236
  workspace: run.effective.workspace.root
248
237
  }),
249
- ...(snapshotRef === undefined ? {} : { snapshot: snapshotRef }),
238
+ snapshot: contextSnapshotRef(snapshot),
250
239
  input: run.inputs[0].input,
251
240
  authority: Object.freeze({ view, readableRefs: pointers, writableProjectIds }),
252
241
  pointers,
@@ -302,17 +291,15 @@ function readLiveTaskState(store, taskId) {
302
291
  });
303
292
  }
304
293
  export function expandRunContextRef(store, taskId, runId, refId, refStore) {
305
- const pack = buildRunContextPack(store, taskId, runId);
306
- const authorized = pack.pointers.filter((ref) => (ref.refId === refId && (refStore === undefined || ref.store === refStore)));
307
- const selector = refStore === undefined ? refId : `${refStore}/${refId}`;
294
+ requireIdentity(refStore, "Context ref store");
295
+ const run = requireExactRun(store, taskId, runId);
296
+ const snapshot = readRunContextSnapshot(store, run);
297
+ const authorized = snapshot.refs.filter((ref) => (ref.refId === refId && ref.store === refStore));
298
+ const selector = `${refStore}/${refId}`;
308
299
  if (authorized.length !== 1) {
309
300
  throw new Error(`AgentRun Context ref is not uniquely authorized: ${selector}.`);
310
301
  }
311
- const run = requireExactRun(store, taskId, runId);
312
- const snapshotRef = run.inputs[0].input.contextSnapshotRef;
313
- const materialized = snapshotRef === undefined
314
- ? collectAuthorizedContext(store, run).find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])))
315
- : store.getContextSnapshot(taskId, snapshotRef.id)?.resources.find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])));
302
+ const materialized = snapshot.resources.find(({ ref }) => (contextRefIdentity(ref) === contextRefIdentity(authorized[0])));
316
303
  if (materialized === undefined || materialized.ref.digest !== authorized[0].digest) {
317
304
  throw new Error(`AgentRun Context ref is unavailable or drifted: ${selector}.`);
318
305
  }
@@ -329,15 +316,12 @@ export function expandRunContextRef(store, taskId, runId, refId, refStore) {
329
316
  /** Fail-closed delta cursor resolution for one immutable AgentRun lineage. */
330
317
  export function buildRunContextDelta(store, taskId, runId, after) {
331
318
  const pack = buildRunContextPack(store, taskId, runId);
332
- if (after === pack.digest || after === pack.snapshot?.digest) {
319
+ if (after === pack.digest || after === pack.snapshot.digest) {
333
320
  return Object.freeze({ schemaVersion: 1, after, cursor: pack.digest, refs: [] });
334
321
  }
335
322
  const run = requireExactRun(store, taskId, runId);
336
- const snapshotRef = run.inputs[0].input.contextSnapshotRef;
337
- const snapshot = snapshotRef === undefined
338
- ? null
339
- : store.getContextSnapshot(taskId, snapshotRef.id);
340
- const parentDigest = snapshot?.parentRef?.digest;
323
+ const snapshot = readRunContextSnapshot(store, run);
324
+ const parentDigest = snapshot.parentRef?.digest;
341
325
  if (!((parentDigest !== undefined && after === parentDigest)
342
326
  || (parentDigest === undefined && after === "none"))) {
343
327
  throw new Error("AgentRun Context delta cursor is outside the frozen Snapshot lineage.");
@@ -356,6 +340,19 @@ function requireExactRun(store, taskId, runId) {
356
340
  }
357
341
  return run;
358
342
  }
343
+ export function readRunContextSnapshot(store, run) {
344
+ const expected = requireRunContextSnapshotRef(run.inputs[0].input);
345
+ const snapshot = store.getContextSnapshot(run.taskId, expected.id);
346
+ if (snapshot === null)
347
+ throw new Error(`AgentRun Context Snapshot is missing: ${expected.id}.`);
348
+ if (snapshot.id !== expected.id || snapshot.taskId !== run.taskId
349
+ || expected.taskId !== run.taskId || snapshot.digest !== expected.digest
350
+ || snapshot.sequence !== expected.sequence || snapshot.scope !== expected.scope
351
+ || snapshot.scopeRef !== expected.scopeRef) {
352
+ throw new Error(`AgentRun Context Snapshot identity drifted: ${expected.id}.`);
353
+ }
354
+ return validateContextSnapshot(snapshot);
355
+ }
359
356
  function collectAuthorizedContext(store, run) {
360
357
  const task = store.getTask(run.taskId);
361
358
  if (task === null)
@@ -575,16 +572,7 @@ function collectSourceRunContext(store, run, sourceRunIds) {
575
572
  }
576
573
  /** The frozen refs, not the Group's changing lane state, define provenance. */
577
574
  export function synthesisSourceRunIds(store, run) {
578
- const ref = run.inputs[0].input.contextSnapshotRef;
579
- if (ref === undefined)
580
- throw new Error(`Synthesis AgentRun has no Context Snapshot: ${run.id}.`);
581
- const snapshot = store.getContextSnapshot(run.taskId, ref.id);
582
- if (snapshot === null || snapshot.digest !== ref.digest
583
- || snapshot.sequence !== ref.sequence || snapshot.scope !== ref.scope
584
- || snapshot.scopeRef !== ref.scopeRef || snapshot.taskId !== run.taskId) {
585
- throw new Error(`Synthesis Context Snapshot is missing or drifted: ${ref.id}.`);
586
- }
587
- validateContextSnapshot(snapshot);
575
+ const snapshot = readRunContextSnapshot(store, run);
588
576
  return snapshot.resources.filter(({ ref: entry, value }) => (entry.store === "source-run"
589
577
  && value.executionGroupId === run.sourceExecutionGroupId)).map(({ ref: entry }) => entry.refId);
590
578
  }
@@ -621,18 +609,6 @@ function contextView(run) {
621
609
  return "operator";
622
610
  return "worker";
623
611
  }
624
- function writableProjects(store, run, view) {
625
- if (run.effective.executionAuthority === "planning")
626
- return Object.freeze([]);
627
- if (view === "leader")
628
- return Object.freeze(store.getTask(run.taskId)?.projectBindings.map(({ projectId }) => projectId) ?? []);
629
- if (view === "reviewer") {
630
- return Object.freeze(run.workspace?.entries.filter(({ access }) => access === "write").map(({ projectId }) => projectId) ?? []);
631
- }
632
- if (run.workItemId === undefined)
633
- return Object.freeze([]);
634
- return Object.freeze(store.getWorkItem(run.taskId, run.workItemId)?.writeProjectIds ?? []);
635
- }
636
612
  function completionActions(view, authority) {
637
613
  if (authority === "planning")
638
614
  return Object.freeze(["finish-turn", "request-input", "request-activation"]);
@@ -33,6 +33,13 @@ export function validateRunInput(value) {
33
33
  }
34
34
  return value;
35
35
  }
36
+ /** Execution must never substitute current Task facts for missing frozen evidence. */
37
+ export function requireRunContextSnapshotRef(input) {
38
+ if (input.contextSnapshotRef === undefined) {
39
+ throw new Error("AgentRun Context Snapshot is required for execution; the record remains audit-only.");
40
+ }
41
+ return validateContextSnapshotRef(input.contextSnapshotRef);
42
+ }
36
43
  export function createRunInputEnvelope(context, input) {
37
44
  validateRunInput(input);
38
45
  const normalized = normalizeEnvelopeContext(context, input);
@@ -65,6 +72,8 @@ export function serializeRunInputEnvelope(value) {
65
72
  throw new Error("AgentRun input protocol is unsupported.");
66
73
  }
67
74
  const normalized = normalizeEnvelope(value);
75
+ if (normalized.purpose !== "global")
76
+ requireRunContextSnapshotRef(normalized);
68
77
  const subject = Object.entries(normalized.subject)
69
78
  .map(([key, id]) => `${key}:${id}`)
70
79
  .join(",");
@@ -4,7 +4,6 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { writeTextFileAtomically } from "../storage/durableFile.js";
5
5
  import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
6
6
  export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
7
- const ordinarySessionCli = ["#!/bin/sh", "exec yui \"$@\"", ""].join("\n");
8
7
  /** Portable read pointers accompany every managed input, including the first
9
8
  * notification on a fresh Session. No Task content or credentials are copied. */
10
9
  export function withSessionContextPointer(text, environment) {
@@ -24,9 +23,8 @@ export function withSessionContextPointer(text, environment) {
24
23
  /**
25
24
  * A managed Session wrapper answers exactly one question: which installation
26
25
  * runs this command. It therefore carries only the resolved entry point, never
27
- * a package or build identity. Version identity changes on every release while
28
- * a Session legitimately outlives it, so embedding it here would turn an
29
- * ordinary update into a broken Session.
26
+ * a package or build identity. Current Sessions may survive a compatible
27
+ * installation relocation; this is not a cross-version runtime adapter.
30
28
  */
31
29
  function renderSessionCli(entryPoint) {
32
30
  return [
@@ -108,8 +106,8 @@ export function materializeSessionBootstrap(input) {
108
106
  // Provider command runners may rebuild PATH independently of the managed
109
107
  // process environment, so a bare `yui` could resolve to another install or
110
108
  // Home. The wrapper pins the resolved entry point instead. Package identity
111
- // stays out of it: the compatible continuity preflight and the Session's
112
- // caller key already authorize the command.
109
+ // stays out of it: current Session authority and the CLI/Controller protocol
110
+ // boundary authorize the command.
113
111
  const sessionCliContent = renderSessionCli(input.entryPoint);
114
112
  const sessionCliDigest = digest(sessionCliContent);
115
113
  const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
@@ -185,11 +183,9 @@ function quoteShellWord(value) {
185
183
  return `'${value.replaceAll("'", "'\"'\"'")}'`;
186
184
  }
187
185
  /**
188
- * Retargets known managed wrappers to the current resolved control plane after
189
- * a compatible update. Only a valid Session Manifest may nominate a wrapper,
190
- * and only Yui's own two-line wrapper shapes are changed. A wrapper written by
191
- * an earlier release may still pin a control-plane digest; retargeting it here
192
- * is what removes that stale pin from a live Home.
186
+ * Retargets current managed wrappers to the resolved installation entry point.
187
+ * Only a valid Session Manifest may nominate a wrapper, and only the exact
188
+ * two-argument shell form generated here is changed.
193
189
  */
194
190
  export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
195
191
  const home = resolve(homeInput);
@@ -217,8 +213,7 @@ export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
217
213
  wrapperPaths.add(wrapperPath);
218
214
  }
219
215
  catch {
220
- // Historical or incomplete manifests are audit material. They must not
221
- // block current Sessions or an otherwise compatible package update.
216
+ // An invalid Manifest cannot nominate a wrapper for mutation.
222
217
  skipped += 1;
223
218
  }
224
219
  }
@@ -234,7 +229,7 @@ export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
234
229
  current += 1;
235
230
  continue;
236
231
  }
237
- if (content !== ordinarySessionCli && !isManagedSessionCli(content)) {
232
+ if (!isManagedSessionCli(content)) {
238
233
  skipped += 1;
239
234
  continue;
240
235
  }
@@ -244,12 +239,9 @@ export function refreshManagedSessionCliWrappers(homeInput, entryPoint) {
244
239
  }
245
240
  return Object.freeze({ refreshed, current, skipped });
246
241
  }
247
- /**
248
- * Both wrapper shapes Yui has written: the current entry-point-only form and
249
- * the earlier form that also pinned a control-plane digest.
250
- */
242
+ /** Match exactly the two shell-quoted words emitted by renderSessionCli. */
251
243
  function isManagedSessionCli(content) {
252
- return /^#!\/bin\/sh\nexec [^\n]+(?: '--yui-control' '[a-f0-9]{64}')? "\$@"\n$/u.test(content);
244
+ return /^#!\/bin\/sh\nexec '(?:[^'\n]|'"'"')*' '(?:[^'\n]|'"'"')*' "\$@"\n$/u.test(content);
253
245
  }
254
246
  function writeImmutableText(path, content) {
255
247
  writeTextFileAtomically(path, content);
@@ -16,7 +16,7 @@ export function resolveAgentHostObservation(store, event) {
16
16
  "conversation.observed", "goal.updated", "goal.cleared",
17
17
  "turn.accepted", "turn.completed", "turn.failed", "turn.cancelled",
18
18
  "input.accepted", "input.rejected", "input.delivery-unknown",
19
- "operation.started", "operation.completed", "operation.failed", "activity.observed"
19
+ "operation.started", "operation.completed", "operation.failed", "activity.observed", "observer.health"
20
20
  ];
21
21
  if (!hostKinds.includes(kind)
22
22
  || (kind === "host.observed" ? input.authority !== "host"
@@ -44,7 +44,9 @@ export function resolveAgentHostObservation(store, event) {
44
44
  const directStart = kind === "turn.accepted" && fence.receiptId === `direct:${fence.nativeTurnId}`;
45
45
  const additionalInput = kind.startsWith("input.") && (fence.receiptId?.startsWith("native-input:") || fence.receiptId?.startsWith("turn-input:")
46
46
  || fence.receiptId?.startsWith("steer:"));
47
- const terminal = ["turn.completed", "turn.failed", "turn.cancelled", "session.ended", "session.failed"].includes(kind);
47
+ // Post-execution diagnostics may describe an already-terminal exact input.
48
+ // This permits evidence collection, never a terminal transition.
49
+ const terminal = ["turn.completed", "turn.failed", "turn.cancelled", "session.ended", "session.failed", "observer.health"].includes(kind);
48
50
  if (fence.taskId === undefined) {
49
51
  if (host.startupRunId !== undefined)
50
52
  throw new RuntimeHookRunFenceError("Global Host cannot carry a startup Run.");
@@ -143,7 +143,7 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
143
143
  }));
144
144
  const laterWakeups = await processLeaderWakeups(store, delivery, now, exactTaskSelection(laterWakeupTaskIds));
145
145
  const inputNotifications = includeOperator
146
- ? await processOperatorInputNotifications(store, delivery, selection, now)
146
+ ? await processOperatorInputNotifications(store, selection, now)
147
147
  : [];
148
148
  return {
149
149
  activeRunDeliveries,
@@ -1407,18 +1407,20 @@ export class FileTaskController {
1407
1407
  else if (wakeup.status === "dispatched")
1408
1408
  settled.add(key);
1409
1409
  }
1410
- const operatorRetries = result.inputNotifications.filter((notification) => notification.reason === "operator-not-ready"
1411
- || (notification.reason === "operator-unavailable"
1412
- && this.#operatorStartupRetryArmed));
1410
+ const operatorRetries = result.inputNotifications.filter((notification) => notification.reason === "operator-unavailable"
1411
+ && this.#operatorStartupRetryArmed);
1413
1412
  if (operatorRetries.length > 0) {
1414
1413
  retry.set("operator", {
1415
1414
  identity: operatorRetries.map((notification) => (`${notification.sourceKind}:${notification.taskId}:${notification.sourceId}`)).join("|")
1416
1415
  });
1417
1416
  }
1418
- else if (result.inputNotifications.some((notification) => notification.status === "sent"
1419
- || notification.status === "already-sent")) {
1417
+ else if (result.inputNotifications.some((notification) => notification.status === "queued"
1418
+ || notification.status === "already-queued")) {
1420
1419
  this.#operatorStartupRetryArmed = false;
1421
1420
  settled.add("operator");
1421
+ // The Global queue owns delivery now. Request its next pass without
1422
+ // retaining a second attention retry loop while the Provider is busy.
1423
+ resignal.add("global-role:operator");
1422
1424
  }
1423
1425
  for (const key of settled)
1424
1426
  this.#clearDeliveryRetry(key);