@zq-silk/yui 2.7.1 → 2.8.1

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 (92) hide show
  1. package/dist/cli/commandCatalog.js +8 -7
  2. package/dist/cli/updateCommand.js +3 -0
  3. package/dist/cli/updatePorts.js +20 -0
  4. package/dist/commands/configCommands.js +1 -0
  5. package/dist/commands/globalRoleCommands.js +42 -35
  6. package/dist/commands/inboxCommands.js +60 -40
  7. package/dist/commands/taskCommands.js +7 -3
  8. package/dist/commands/taskRoleRuntimeStatus.js +2 -0
  9. package/dist/context/runContextPack.js +48 -3
  10. package/dist/context/taskContext.js +36 -12
  11. package/dist/controlPlaneCli.js +64 -17
  12. package/dist/controller/controller.js +5 -0
  13. package/dist/controller/fileSchedulerStoreAdapter.js +9 -4
  14. package/dist/controller/globalInputDelivery.js +17 -5
  15. package/dist/controller/globalRuntimeAttention.js +135 -5
  16. package/dist/controller/runtime.js +4 -0
  17. package/dist/controller/structuredProviderObservation.js +51 -15
  18. package/dist/controller/taskHostTerminalRecovery.js +33 -0
  19. package/dist/executor/fileRoleLaunchPlanner.js +4 -9
  20. package/dist/message/inboxReceipt.js +30 -0
  21. package/dist/message/taskInboxOwnership.js +25 -3
  22. package/dist/models/modelManagement.js +469 -0
  23. package/dist/models/modelManagementConfig.js +46 -0
  24. package/dist/models/modelManagementFiles.js +90 -0
  25. package/dist/models/modelManagementHttp.js +90 -0
  26. package/dist/models/modelManagementTypes.js +1 -0
  27. package/dist/output/boundedRead.js +43 -14
  28. package/dist/runtime/agentEndpointIdentity.js +8 -4
  29. package/dist/runtime/agentEndpointOwnership.js +5 -6
  30. package/dist/runtime/agentHost.js +62 -10
  31. package/dist/runtime/agentHostImplementation.js +18 -0
  32. package/dist/runtime/managedCaller.js +25 -1
  33. package/dist/runtime/managedIdentity.js +1 -1
  34. package/dist/runtime/providerRetry.js +19 -1
  35. package/dist/runtime/sessionCaller.js +53 -0
  36. package/dist/storage/contextRecords.js +28 -0
  37. package/dist/storage/maintenanceCaller.js +69 -0
  38. package/dist/storage/sqliteSchema.js +12 -2
  39. package/dist/storage/sqliteStore.js +9 -5
  40. package/dist/storage/storageVersions.js +1 -1
  41. package/dist/storage/taskStore.js +4 -0
  42. package/dist/task/completionReadiness.js +3 -3
  43. package/dist/web/assets/assetManifest.js +28 -2
  44. package/dist/web/assets/client/app/manage.js +6 -0
  45. package/dist/web/assets/client/app/models.js +20 -0
  46. package/dist/web/assets/client/app/settings.js +41 -9
  47. package/dist/web/assets/client/i18n/en.js +180 -2
  48. package/dist/web/assets/client/i18n/zh-CN.js +178 -2
  49. package/dist/web/assets/client/index.js +8 -0
  50. package/dist/web/assets/client/layout/manageShell.js +94 -0
  51. package/dist/web/assets/client/lib/api.js +13 -1
  52. package/dist/web/assets/client/lib/i18n.js +5 -3
  53. package/dist/web/assets/client/views/dock/conversation.js +231 -80
  54. package/dist/web/assets/client/views/models.js +777 -0
  55. package/dist/web/assets/client/views/skills.js +22 -15
  56. package/dist/web/assets/shared/icons.js +2 -0
  57. package/dist/web/assets/shared/manageNav.js +13 -0
  58. package/dist/web/assets/shell/manage.js +54 -0
  59. package/dist/web/assets/shell/models.js +7 -0
  60. package/dist/web/assets/shell/settings.js +1 -35
  61. package/dist/web/assets/shell/settingsContent.js +30 -0
  62. package/dist/web/assets/shell/sidebar.js +1 -0
  63. package/dist/web/assets/styles/index.js +6 -0
  64. package/dist/web/assets/styles/views/conversation.js +133 -0
  65. package/dist/web/assets/styles/views/manage.js +71 -0
  66. package/dist/web/assets/styles/views/models.js +129 -0
  67. package/dist/web/assets/styles/views/settings.js +28 -14
  68. package/dist/web/assets/styles/views/sidebar.js +1 -0
  69. package/dist/web/webServer.js +59 -9
  70. package/docs/cli-information-contract.md +42 -11
  71. package/docs/cli-information-contract.zh-CN.md +28 -7
  72. package/docs/managed-turn-and-session-runtime.md +47 -1
  73. package/docs/managed-turn-and-session-runtime.zh-CN.md +32 -0
  74. package/docs/release-workflow.md +80 -7
  75. package/docs/release-workflow.zh-CN.md +55 -4
  76. package/docs/storage-baseline.md +6 -1
  77. package/docs/storage-baseline.zh-CN.md +4 -1
  78. package/docs/testing/review-evaluation.md +148 -0
  79. package/docs/testing/review-evaluation.zh-CN.md +117 -0
  80. package/docs/testing/verification-levels.md +6 -2
  81. package/docs/testing/verification-levels.zh-CN.md +3 -0
  82. package/package.json +1 -1
  83. package/skills/yui-leader/VERSION +1 -1
  84. package/skills/yui-leader/references/execution.md +29 -0
  85. package/skills/yui-leader/references/replicated-execution.md +7 -0
  86. package/skills/yui-operator/SKILL.md +5 -1
  87. package/skills/yui-operator/VERSION +1 -1
  88. package/skills/yui-reviewer/SKILL.md +35 -0
  89. package/skills/yui-reviewer/VERSION +1 -1
  90. package/skills/yui-runtime/SKILL.md +53 -7
  91. package/skills/yui-runtime/VERSION +1 -1
  92. package/skills/yui-runtime/references/recovery.md +24 -5
@@ -193,7 +193,8 @@ const globalSessionChildren = [
193
193
  {
194
194
  name: "skills", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment", "global", "unbound"] },
195
195
  summary: "Check current Skill paths, versions and availability for this managed Session without reading bodies.",
196
- usage: "yui session skills"
196
+ usage: "yui session skills [--cursor <cursor>]",
197
+ options: ["--cursor"]
197
198
  },
198
199
  { name: "enter", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Enter a global Role's native session.", usage: "yui session enter <role>" },
199
200
  {
@@ -911,9 +912,9 @@ const taskChildren = [
911
912
  },
912
913
  {
913
914
  name: "context", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
914
- summary: "Load the exact authorized AgentRun context.",
915
- usage: "yui task run context <task>/<run> [--cursor <cursor>] [--json]",
916
- options: ["--cursor", "--json"],
915
+ summary: "Load bounded AgentRun entry facts; discover refs or read the full authorized pack explicitly.",
916
+ usage: "yui task run context <task>/<run> [--view current|refs|input|full] [--cursor <cursor>] [--json]",
917
+ options: ["--view", "--cursor", "--json"],
917
918
  executable: true,
918
919
  hidden: true,
919
920
  sections: [{ id: "load", title: "Commands", entries: ["expand", "delta"] }],
@@ -1165,7 +1166,7 @@ export const ROOT_COMMAND = buildNode({
1165
1166
  sections: [{ id: "commands", title: "Commands", entries: ["list", "show", "claim", "ack"] }],
1166
1167
  children: [
1167
1168
  { name: "list", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
1168
- summary: "Discover bounded own-address entries without consuming them.",
1169
+ summary: "Discover unhandled own-address entries, including accepted delivery, with current identity and separate handling receipts.",
1169
1170
  usage: "yui inbox list [--limit <1..100>] [--cursor <cursor>]", options: ["--limit", "--cursor"] },
1170
1171
  { name: "show", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
1171
1172
  summary: "Read an exact original or fixed notification window without acknowledgement.",
@@ -1190,8 +1191,8 @@ export const ROOT_COMMAND = buildNode({
1190
1191
  children: [
1191
1192
  {
1192
1193
  name: "list", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Discover Global Messages without consuming input.",
1193
- usage: "yui role message list <role> [--pending] [--limit <1..100>] [--cursor <cursor>]",
1194
- options: ["--pending", "--limit", "--cursor"]
1194
+ usage: "yui role message list <role> [--pending|--diagnostics|--diagnostic <key>] [--limit <1..100>] [--cursor <cursor>]",
1195
+ options: ["--pending", "--diagnostics", "--diagnostic", "--limit", "--cursor"]
1195
1196
  },
1196
1197
  {
1197
1198
  name: "show", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Read one exact Global Message; follow contentPage.nextCursor for long content.",
@@ -65,6 +65,9 @@ export function renderUpdateResult(result) {
65
65
  })();
66
66
  return [
67
67
  rendered,
68
+ ...(result.outcome === "updated" ? [
69
+ "Runtime: existing Hosts are retained, not upgraded in place. Host control capabilities and event delivery are checked separately; accepted input is never replayed."
70
+ ] : []),
68
71
  ...(result.controllerReconciliation === undefined ? [] : [
69
72
  `Controller reconciliation: ${JSON.stringify(result.controllerReconciliation)}`
70
73
  ]),
@@ -776,6 +776,26 @@ function interpretPreflight(result) {
776
776
  // error is not a safe preflight — block rather than proceed to a switch.
777
777
  const data = parseSuccessEnvelopeData(result);
778
778
  if (data === null) {
779
+ // CLI bootstrap failures use an ok:false envelope on stderr. Preserve its
780
+ // already-sanitized diagnosis (including recovery guidance), never promote
781
+ // an error envelope to a successful storage preflight.
782
+ for (const stream of [result.stderr, result.stdout]) {
783
+ let failure;
784
+ try {
785
+ failure = JSON.parse(stream.toString("utf8").trim());
786
+ }
787
+ catch {
788
+ continue;
789
+ }
790
+ if (!isRecord(failure) || failure.ok !== false || typeof failure.message !== "string")
791
+ continue;
792
+ return {
793
+ status: "blocked",
794
+ stage: "preflight",
795
+ message: `Staged binary preflight failed (exit ${result.status ?? "null"}): ${failure.message}`,
796
+ action: "Resolve the reported staged-binary failure using the same Home and matching CLI; do not force the update."
797
+ };
798
+ }
779
799
  return {
780
800
  status: "blocked",
781
801
  stage: "preflight",
@@ -56,6 +56,7 @@ export function effectiveConfigData(config, domain) {
56
56
  const health = resolveRuntimeHealth(config.runtimeHealth);
57
57
  const all = {
58
58
  schemaVersion: config.schemaVersion,
59
+ modelManagement: config.modelManagement ?? null,
59
60
  defaultAgent: config.defaultAgent ?? null,
60
61
  defaultWorkspace: config.defaultWorkspace ?? null,
61
62
  timeZone: resolveTimeZone(config.timeZone),
@@ -1,10 +1,13 @@
1
1
  import { roleNotFound, usageError } from "../errors/cliError.js";
2
2
  import { hasManagedIdentity } from "../runtime/managedIdentity.js";
3
- import { boundedDocument, recordPage, readOptions, readDigest, messageReceipt } from "../output/boundedRead.js";
4
- import { controlProviderRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
3
+ import { requireManagedGlobalCaller } from "../runtime/managedCaller.js";
4
+ import { boundedDocument, recordPage, readOptions, readDigest, messageReceipt, summaryText, summaryMetadata } from "../output/boundedRead.js";
5
+ import { controlProviderRetry, providerRetryProjection, providerRetrySummary, renderProviderRetry } from "../runtime/providerRetry.js";
6
+ import { pendingGlobalInboxMessages, inboxHandlingState, globalInboxDeliveryState } from "../message/inboxReceipt.js";
5
7
  import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
6
8
  import { globalInputBacklog } from "../message/globalInputBacklog.js";
7
9
  import { checkCurrentSkills, SKILL_REUSE_RULE } from "../context/currentSkills.js";
10
+ import { runtimeDiagnosticGroups, runtimeDiagnosticEvidence } from "../controller/globalRuntimeAttention.js";
8
11
  import { roleSkillIds } from "../context/roleSessionContext.js";
9
12
  import { createRoleSessionSet, recordRoleAgentSession } from "../executor/agentExecutor.js";
10
13
  import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
@@ -76,13 +79,7 @@ function assertGlobalInputAuthority(store, options, target) {
76
79
  throw usageError("Global input mutation is outside the exact Session authority.");
77
80
  }
78
81
  if (env.YUI_SESSION_SCOPE === "global") {
79
- const caller = store.getGlobalRoleSessionSet(env.YUI_ROLE ?? "");
80
- const session = caller?.sessions[caller.activeAgentId];
81
- if (session === undefined || session.status !== "active" || env.YUI_AGENT_ID !== session.agentId
82
- || env.YUI_ADAPTER_ID !== session.adapterId
83
- || (env.CODEX_THREAD_ID ?? env.YUI_NATIVE_SESSION_ID) !== session.nativeSessionId) {
84
- throw usageError("Global input mutation requires the current native caller Session.");
85
- }
82
+ requireManagedGlobalCaller(store, env);
86
83
  }
87
84
  else if (hasManagedIdentity(env)) {
88
85
  throw usageError("Incomplete managed identity cannot acquire user authority.");
@@ -123,9 +120,6 @@ function roleContext(args, store, options) {
123
120
  if (environment.YUI_SESSION_SCOPE !== "global" || environment.YUI_ROLE !== name) {
124
121
  throw usageError("Managed GlobalRole context is outside the exact Session authority.");
125
122
  }
126
- if (environment.YUI_SESSION_MANIFEST === undefined) {
127
- throw usageError("Managed GlobalRole context requires its Session Manifest.");
128
- }
129
123
  }
130
124
  const role = requireRole(name, store);
131
125
  const binding = activeRoleAgentBinding(role);
@@ -140,10 +134,9 @@ function roleContext(args, store, options) {
140
134
  && message.inbox?.ack === undefined
141
135
  && message.notDelivered === undefined
142
136
  && (message.interruptThen !== undefined || message.inputControl?.action === "queue"));
143
- const pending = recordPage(queued.map(globalMessageSummary), `global:${name}/messages/pending`, { limit: 8 });
144
- const recent = recordPage([...messages].reverse().map(globalMessageSummary), `global:${name}/messages/all`, { limit: 8 });
137
+ const pending = recordPage(pendingGlobalInboxMessages(messages).map(globalMessageSummary), `global:${name}/messages/pending`, { limit: 8 });
145
138
  const observedAt = new Date();
146
- const { oldestAgeMs, ...backlog } = globalInputBacklog(queued, sessions, observedAt, {
139
+ const { oldestAgeMs, messages: _backlogMessages, ...backlog } = globalInputBacklog(queued, sessions, observedAt, {
147
140
  attention: name === "operator" ? store.getWorkMailbox({ kind: "operator" }) : null,
148
141
  lifecycle: store.getWorkMailbox({ kind: "global-role-runtime", roleName: name })
149
142
  });
@@ -163,10 +156,7 @@ function roleContext(args, store, options) {
163
156
  : { nativeSessionId: session.nativeSessionId })
164
157
  },
165
158
  profile: {
166
- description: role.description,
167
- responsibilities: role.responsibilities ?? [],
168
- constraints: role.constraints ?? [],
169
- expectedOutput: role.expectedOutput,
159
+ read: "Read roleProfileRef from the current Session Manifest; it is not repeated at each entry.",
170
160
  skillIds: [
171
161
  "yui-runtime",
172
162
  ...(role.name === "operator" ? ["yui-operator"] : []),
@@ -183,23 +173,27 @@ function roleContext(args, store, options) {
183
173
  },
184
174
  // Reading pending input is neither delivery nor implementation.
185
175
  pending,
186
- recent,
187
176
  backlog,
188
177
  reads: {
189
178
  ownInbox: "yui inbox list --json",
190
179
  messages: `role message list ${name}`,
191
180
  pending: `role message list ${name} --pending`,
192
- detail: `role message show ${name} <message-id>`
181
+ detail: `role message show ${name} <message-id>`,
182
+ diagnostics: `role message list ${name} --diagnostics`,
183
+ retry: `session retry ${name} show`
193
184
  },
194
- nativeTurn: sessions?.providerBinding?.run ?? null,
195
- retry: providerRetryProjection(sessions?.providerBinding),
185
+ nativeTurn: sessions?.providerBinding?.run == null ? null : {
186
+ attemptId: sessions.providerBinding.run.attemptId,
187
+ nativeTurnId: sessions.providerBinding.run.nativeTurnId,
188
+ status: sessions.providerBinding.run.status
189
+ },
190
+ retry: providerRetrySummary(sessions?.providerBinding),
196
191
  // Historical interrupt receipts are available through Session inspection;
197
192
  // only this Turn's exact identity is required at entry.
198
193
  sessionManifestPath: environment.YUI_SESSION_MANIFEST,
199
194
  skillCheckCommand: "yui session skills --json",
200
195
  currentSkills,
201
196
  skillLoadingRule: SKILL_REUSE_RULE,
202
- skillPackages: session?.effective.skillPackages ?? null,
203
197
  cliCommand: "yui"
204
198
  });
205
199
  // Elapsed wall-clock time is live display metadata, not a changing source
@@ -214,7 +208,7 @@ function roleContext(args, store, options) {
214
208
  `Effective revision: ${role.launchRevision}`,
215
209
  `Skills: ${context.profile.skillIds.join(", ") || "none"}`,
216
210
  `Authority: ${context.authority.view}; Task implementation: no`,
217
- `Pending messages: ${pending.total}; recent messages: ${recent.total}`,
211
+ `Pending messages: ${pending.total}; history is available on demand`,
218
212
  JSON.stringify(data),
219
213
  ""
220
214
  ].join("\n") };
@@ -222,12 +216,15 @@ function roleContext(args, store, options) {
222
216
  function globalMessageSummary(message) {
223
217
  return {
224
218
  id: message.id, createdAt: message.createdAt, kind: message.kind,
225
- digest: readDigest(message), summary: message.body.slice(0, 240),
219
+ digest: readDigest(message), summary: summaryText(message.body),
220
+ read: `role message show ${message.roleName} ${message.id}`,
226
221
  delivery: message.delivery === undefined ? undefined : { deliveredAt: message.delivery.deliveredAt },
227
- notDelivered: message.notDelivered === undefined ? undefined : { summary: message.notDelivered.reason.slice(0, 240) },
222
+ notDelivered: message.notDelivered === undefined ? undefined : { summary: summaryText(message.notDelivered.reason) },
228
223
  control: message.control === undefined ? undefined : { outcome: message.control.outcome, observedAt: message.control.observedAt },
229
224
  inputAction: message.inputControl?.action,
230
- deliveryTarget: message.deliveryTarget,
225
+ handling: inboxHandlingState(message.inbox),
226
+ deliveryState: globalInboxDeliveryState(message),
227
+ deliveryTarget: message.deliveryTarget === undefined ? undefined : summaryMetadata(message.deliveryTarget),
231
228
  interruptThen: message.interruptThen === undefined ? undefined : true
232
229
  };
233
230
  }
@@ -629,8 +626,13 @@ function globalRoleMessage(args, store, options) {
629
626
  }
630
627
  requireRole(name, store);
631
628
  const pending = tail.includes("--pending");
632
- const parsed = readOptions(tail.filter(arg => arg !== "--pending"), action === "list"
633
- ? ["--cursor", "--limit"] : ["--cursor"]);
629
+ const diagnostics = tail.includes("--diagnostics");
630
+ const parsed = readOptions(tail.filter(arg => arg !== "--pending" && arg !== "--diagnostics"), action === "list"
631
+ ? ["--cursor", "--limit", "--diagnostic"] : ["--cursor"]);
632
+ const diagnostic = parsed.values.get("--diagnostic");
633
+ if (Number(pending) + Number(diagnostics) + Number(diagnostic !== undefined) > 1
634
+ || action === "show" && diagnostics)
635
+ throw usageError("Choose one Message discovery view.");
634
636
  const messages = store.listGlobalRoleMessages(name);
635
637
  let data;
636
638
  if (action === "show") {
@@ -644,11 +646,16 @@ function globalRoleMessage(args, store, options) {
644
646
  else {
645
647
  if (parsed.positionals.length !== 0)
646
648
  throw usageError("Role message list <role> [--pending] [--limit <n>] [--cursor <cursor>].");
647
- const selected = pending ? messages.filter(message => message.delivery === undefined
648
- && message.inbox?.ack === undefined
649
- && message.notDelivered === undefined
650
- && (message.interruptThen !== undefined || message.inputControl?.action === "queue")) : [...messages].reverse();
651
- data = recordPage(selected.map(globalMessageSummary), `global:${name}/messages/${pending ? "pending" : "all"}`, parsed);
649
+ if (diagnostics) {
650
+ data = recordPage(runtimeDiagnosticGroups(messages).reverse().map(group => ({
651
+ ...group, source: summaryMetadata(group.source),
652
+ read: `role message show ${name} ${group.latestEvidence}`
653
+ })), `global:${name}/diagnostics`, parsed);
654
+ return { kind: "output", output: `${JSON.stringify(data)}\n`, data };
655
+ }
656
+ const selected = diagnostic !== undefined ? runtimeDiagnosticEvidence(messages, diagnostic)
657
+ : pending ? pendingGlobalInboxMessages(messages) : [...messages].reverse();
658
+ data = recordPage(selected.map(globalMessageSummary), `global:${name}/messages/${diagnostic === undefined ? pending ? "pending" : "all" : `diagnostic/${diagnostic}`}`, parsed);
652
659
  }
653
660
  return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
654
661
  }
@@ -1,10 +1,11 @@
1
- import { requireManagedGlobalCaller, requireManagedTaskCaller } from "../runtime/managedCaller.js";
2
- import { boundedDocument, readDigest, readOptions, recordPage } from "../output/boundedRead.js";
1
+ import { requireManagedCaller } from "../runtime/sessionCaller.js";
2
+ import { boundedDocument, readDigest, readOptions, recordPage, summaryMetadata, summaryText } from "../output/boundedRead.js";
3
3
  import { claimPending, completeProcessing } from "../coordination/workMailbox.js";
4
4
  import { createTaskWake, latestTaskWake } from "../scheduler/taskWake.js";
5
5
  import { expandTaskMessageResult } from "../message/message.js";
6
6
  import { messageAssignmentBlocker } from "../message/messageContinuation.js";
7
- import { isLeaderSteer, latestMessageUpdate, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
7
+ import { globalInboxDeliveryState, inboxHandlingState, isGlobalInboxMessage, pendingGlobalInboxMessages } from "../message/inboxReceipt.js";
8
+ import { deliveredMessageWake, isLeaderSteer, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
8
9
  import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
9
10
  import { isTaskRecordRetired, retiredTaskRecordIds } from "../task/taskRecordRetirement.js";
10
11
  const target = (address) => ({ kind: "role", taskId: address.taskId, roleName: address.roleName });
@@ -13,11 +14,10 @@ const pendingId = (pending) => `pending-${readDigest(pending)}`;
13
14
  const sameSession = (a, b) => a.scope === b.scope && a.taskId === b.taskId
14
15
  && a.roleName === b.roleName && a.agentId === b.agentId && a.nativeSessionId === b.nativeSessionId;
15
16
  function addressOf(store, env) {
16
- if (env.YUI_SESSION_SCOPE === "global") {
17
- const caller = requireManagedGlobalCaller(store, env);
17
+ const caller = requireManagedCaller(store, env);
18
+ if (caller.scope === "global") {
18
19
  return { scope: "global", roleName: caller.roleName, agentId: caller.agentId, nativeSessionId: caller.nativeSessionId };
19
20
  }
20
- const caller = requireManagedTaskCaller(store, env);
21
21
  if (caller.roleName !== "leader" && caller.currentRunId === undefined) {
22
22
  throw new Error("An Assignment inbox requires its current active AgentRun.");
23
23
  }
@@ -42,7 +42,7 @@ function ownsMessage(store, address, message) {
42
42
  function entries(store, address) {
43
43
  if (address.scope === "global")
44
44
  return store.listGlobalRoleMessages(address.roleName)
45
- .filter(message => message.inputControl !== undefined || message.interruptThen !== undefined);
45
+ .filter(isGlobalInboxMessage);
46
46
  const messages = store.listMessages(address.taskId).filter(message => ownsMessage(store, address, message));
47
47
  return address.roleName === "leader" ? [...store.listTaskWakes(address.taskId), ...messages] : messages;
48
48
  }
@@ -53,6 +53,10 @@ function getEntry(store, address, id) {
53
53
  return entry;
54
54
  }
55
55
  function isWake(entry) { return "fromCursor" in entry; }
56
+ function acceptedInput(entry) {
57
+ return isWake(entry) ? entry.status === "consumed" : "roleName" in entry
58
+ ? globalInboxDeliveryState(entry) === "accepted" : entry.control?.outcome === "accepted";
59
+ }
56
60
  function assertSelectedTarget(address, message) {
57
61
  const selected = message.inputControl?.expectedSessionId;
58
62
  const globalTarget = "roleName" in message ? message.deliveryTarget : undefined;
@@ -82,23 +86,33 @@ function blocker(store, address, entry) {
82
86
  return sameSession(entry.inbox.claim.actor, address) ? undefined : "claimed-by-previous-session";
83
87
  }
84
88
  if (isWake(entry)) {
89
+ // Native acceptance releases transport ownership, not semantic handling.
90
+ // The current Leader may explicitly claim the fixed delivered window.
85
91
  if (entry.status === "consumed")
86
- return "already-delivered";
92
+ return undefined;
87
93
  return "automatic-notification-owns-window";
88
94
  }
89
95
  if (address.scope === "task" && taskMessageInboxReceipt(store, entry) !== undefined) {
90
96
  return "claimed-by-notification-window";
91
97
  }
92
- if (entry.interruptThen !== undefined)
93
- return "explicit-interrupt-handoff";
94
- if (entry.control !== undefined && entry.control.outcome !== "rejected")
98
+ if (entry.control !== undefined && ["pending", "delivery-unknown"].includes(entry.control.outcome)) {
95
99
  return `delivery-${entry.control.outcome}`;
100
+ }
101
+ const accepted = acceptedInput(entry);
102
+ if (entry.interruptThen !== undefined && !accepted
103
+ && !(address.scope === "task" && entry.continuation?.runId === address.runId
104
+ && address.runId !== undefined))
105
+ return "explicit-interrupt-handoff";
96
106
  // Both steer paths reserve pending control before submission and arbitrate
97
107
  // with inbox claims. No control is unsubmitted, not an unknown delivery.
98
108
  if (address.scope === "global") {
99
109
  const message = entry;
100
- if (message.delivery !== undefined)
110
+ if (message.delivery !== undefined && !accepted)
101
111
  return `already-delivered-${message.delivery.via}`;
112
+ // Explicitly settling delivered input is not a new submission or target
113
+ // selection. A current rightful successor retains the original target.
114
+ if (accepted)
115
+ return undefined;
102
116
  if (message.deliveryTarget !== undefined && (message.deliveryTarget.nativeSessionId !== address.nativeSessionId
103
117
  || message.deliveryTarget.agentId !== address.agentId))
104
118
  return "selected-native-session-changed";
@@ -111,9 +125,10 @@ function blocker(store, address, entry) {
111
125
  }
112
126
  else {
113
127
  const message = entry;
114
- if (message.continuation?.runId !== undefined)
128
+ if (message.continuation?.runId !== undefined && message.continuation.runId !== address.runId) {
115
129
  return "continuation-already-dispatched";
116
- if (message.inputControl?.expectedSessionId !== undefined
130
+ }
131
+ if (!accepted && message.inputControl?.expectedSessionId !== undefined
117
132
  && message.inputControl.expectedSessionId !== address.nativeSessionId)
118
133
  return "selected-native-session-changed";
119
134
  return address.roleName === "leader" && isLeaderSteer(message) ? undefined : messageAssignmentBlocker(store, message);
@@ -135,8 +150,15 @@ function checkMutation(store, address) {
135
150
  }
136
151
  function summary(store, address, entry) {
137
152
  return { id: entry.id, kind: isWake(entry) ? "notification-window" : "message",
138
- createdAt: entry.createdAt, summary: isWake(entry) ? entry.reasons.join(", ").slice(0, 240) : entry.body.slice(0, 240),
139
- blocked: blocker(store, address, entry) ?? null, inbox: entry.inbox ?? null,
153
+ createdAt: entry.createdAt, summary: summaryText(isWake(entry) ? entry.reasons.join(", ") : entry.body),
154
+ blocked: blocker(store, address, entry) ?? null, inbox: summaryMetadata(entry.inbox ?? null),
155
+ handling: inboxHandlingState(entry.inbox),
156
+ delivery: isWake(entry) ? entry.status : summaryMetadata({
157
+ control: entry.control ?? null,
158
+ ...(address.scope === "global" ? { receipt: entry.delivery ?? null,
159
+ state: globalInboxDeliveryState(entry) }
160
+ : { continuation: entry.continuation ?? null })
161
+ }),
140
162
  read: `yui inbox show ${entry.id}`, ...(isWake(entry)
141
163
  ? { originals: `yui task wake show ${entry.taskId} ${entry.id} --json` } : {}) };
142
164
  }
@@ -155,21 +177,19 @@ export function runInboxCommand(args, store, env, now = new Date()) {
155
177
  if (parsed.positionals.length)
156
178
  throw new Error("Inbox list accepts only --limit and --cursor.");
157
179
  const retired = address.scope === "task" ? retiredTaskRecordIds(store.listEvents(address.taskId), "message") : new Set();
158
- const selected = entries(store, address).filter(entry => (isWake(entry) || !retired.has(entry.id))
159
- && entry.inbox?.ack === undefined
160
- && (isWake(entry) ? entry.status !== "consumed"
161
- : address.scope === "global" ? entry.delivery === undefined
162
- : entry.continuation?.runId === undefined));
180
+ const selected = address.scope === "global" ? pendingGlobalInboxMessages(store.listGlobalRoleMessages(address.roleName))
181
+ : entries(store, address).filter(entry => (isWake(entry) || !retired.has(entry.id))
182
+ && inboxHandlingState(entry.inbox) !== "handled");
163
183
  const items = selected.map(entry => summary(store, address, entry));
164
184
  if (address.scope === "task" && address.roleName === "leader") {
165
185
  const mailbox = store.getWorkMailbox(target(address));
166
186
  if (mailbox?.pending != null)
167
187
  items.push({ id: pendingId(mailbox.pending), kind: "notification-window",
168
- summary: mailbox.pending.reasons.join(", ").slice(0, 240),
188
+ summary: summaryText(mailbox.pending.reasons.join(", ")),
169
189
  blocked: mailbox.processing === null && !unfinishedLeaderInbox(store, address.taskId) ? null : "earlier-window-owned",
170
190
  read: `yui inbox show ${pendingId(mailbox.pending)}` });
171
191
  }
172
- return recordPage(items, source(address), parsed);
192
+ return recordPage(items, source(address), parsed, { identity: address });
173
193
  }
174
194
  if (action === "show") {
175
195
  const parsed = readOptions(tail, ["--cursor"]);
@@ -230,7 +250,8 @@ export function runInboxCommand(args, store, env, now = new Date()) {
230
250
  if (ref.type !== "message")
231
251
  continue;
232
252
  const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
233
- if (message !== undefined && taskMessageInboxReceipt(tx, message) !== undefined
253
+ if (message !== undefined && (taskMessageInboxReceipt(tx, message) !== undefined
254
+ || deliveredMessageWake(tx, message) !== undefined)
234
255
  || message?.interruptThen !== undefined
235
256
  || message?.control !== undefined && message.control.outcome !== "rejected"
236
257
  || message?.inputControl?.expectedSessionId !== undefined
@@ -241,16 +262,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
241
262
  const latest = latestTaskWake(tx.listTaskWakes(current.taskId));
242
263
  // Pin each mutable Draft original's existing update event, not a clock
243
264
  // comparison. A later edit is distinct input even within the same tick.
244
- const refs = [...mailbox.pending.refs];
245
- for (const ref of mailbox.pending.refs) {
246
- if (ref.type !== "message" || ref.taskId !== current.taskId)
247
- continue;
248
- const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
249
- const revision = message === undefined ? undefined : latestMessageUpdate(tx, message);
250
- if (revision !== undefined && !refs.some(ref => ref.type === "event" && ref.id === revision.id)) {
251
- refs.push({ type: "event", taskId: current.taskId, id: revision.id });
252
- }
253
- }
265
+ const refs = pinMessageRevisions(tx, current.taskId, mailbox.pending.refs);
254
266
  const wake = createTaskWake({ id: tx.nextTaskWakeId(current.taskId), taskId: current.taskId,
255
267
  reasons: mailbox.pending.reasons, refs,
256
268
  fromCursor: latest?.toCursor ?? tx.getTask(current.taskId).createdAt,
@@ -273,6 +285,17 @@ export function runInboxCommand(args, store, env, now = new Date()) {
273
285
  const blocked = blocker(tx, current, entry);
274
286
  if (blocked !== undefined)
275
287
  throw new Error(`Inbox entry cannot be claimed: ${blocked}.`);
288
+ if (isWake(entry) && !acceptedInput(entry)) {
289
+ for (const ref of entry.refs ?? []) {
290
+ if (ref.type !== "message")
291
+ continue;
292
+ const message = tx.listMessages(current.taskId).find(message => message.id === ref.id);
293
+ if (message !== undefined)
294
+ assertSelectedTarget(current, message);
295
+ }
296
+ }
297
+ else if (!isWake(entry) && !acceptedInput(entry))
298
+ assertSelectedTarget(current, entry);
276
299
  for (const previous of entries(tx, current)) {
277
300
  if (previous.id === entry.id)
278
301
  break;
@@ -281,10 +304,7 @@ export function runInboxCommand(args, store, env, now = new Date()) {
281
304
  if (current.scope === "task"
282
305
  && isTaskRecordRetired(tx.listEvents(current.taskId), "message", previous.id))
283
306
  continue;
284
- if (current.scope === "global" && (previous.delivery !== undefined
285
- || previous.notDelivered !== undefined))
286
- continue;
287
- if (current.scope === "task" && previous.continuation?.runId !== undefined)
307
+ if (current.scope === "global" && previous.notDelivered !== undefined)
288
308
  continue;
289
309
  if (previous.inbox !== undefined && sameSession(previous.inbox.claim.actor, current))
290
310
  continue;
@@ -299,14 +319,14 @@ export function runInboxCommand(args, store, env, now = new Date()) {
299
319
  // the original. The current rightful successor may settle a claim
300
320
  // acquired by the exact selected Session, retaining both identities.
301
321
  const claimant = entry.inbox.claim.actor;
302
- if (isWake(entry)) {
322
+ if (isWake(entry) && !acceptedInput(entry)) {
303
323
  const messageIds = new Set(entry.refs?.filter(ref => ref.type === "message").map(ref => ref.id));
304
324
  for (const message of tx.listMessages(current.taskId)) {
305
325
  if (messageIds.has(message.id))
306
326
  assertSelectedTarget(claimant, message);
307
327
  }
308
328
  }
309
- else
329
+ else if (!isWake(entry) && !acceptedInput(entry))
310
330
  assertSelectedTarget(claimant, entry);
311
331
  if (!isWake(entry) && current.scope === "task"
312
332
  && !(current.roleName === "leader" && isLeaderSteer(entry))) {
@@ -7,7 +7,7 @@ import { createRun, runExecutionObservation, runPurposeAdmitsTaskState } from ".
7
7
  import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
8
8
  import { contextContentDigest, contextSnapshotRef } from "../context/contextSnapshot.js";
9
9
  import { sourceRunContextValue } from "../context/sourceRunContext.js";
10
- import { buildRunContextDelta, buildRunContextPack, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, freezeCandidateContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
10
+ import { buildRunContextDelta, buildRunContextPack, runContextEntry, runContextPointerPage, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, freezeCandidateContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
11
11
  import { createRunInput } from "../context/runInputContract.js";
12
12
  import { parseTaskCatalogOptions, readTaskCatalog, renderTaskCatalog } from "../context/taskCatalog.js";
13
13
  import { assertContextRecordReadable, readTaskContextResource } from "../context/taskContext.js";
@@ -4777,14 +4777,18 @@ function runContextCommand(args, store, options) {
4777
4777
  const data = boundedDocument(delta, `run:${taskId}/${runId}/delta/${parsed.values.get("--after")}`, parsed.cursor);
4778
4778
  return output(`${JSON.stringify(data)}\n`, { contextDelta: data });
4779
4779
  }
4780
- const parsed = readOptions(rest, ["--cursor"]);
4780
+ const parsed = readOptions(rest, ["--cursor", "--view"]);
4781
4781
  if (first === undefined || parsed.positionals.length !== 0) {
4782
4782
  throw usageError("Task run context usage: yui task run context <task>/<run>.");
4783
4783
  }
4784
4784
  const { taskId, runId } = parseRunContextReference(first);
4785
4785
  authorizeRunContext(store, taskId, runId, options.environment);
4786
4786
  const pack = store.transaction((tx) => buildRunContextPack(tx, taskId, runId));
4787
- const data = boundedDocument(pack, `run:${taskId}/${runId}/context`, parsed.cursor);
4787
+ const view = parsed.values.get("--view") ?? "current";
4788
+ if (!["current", "full", "refs", "input"].includes(view))
4789
+ throw usageError("Run Context view must be current, refs, input or full.");
4790
+ const data = view === "refs" ? runContextPointerPage(pack, parsed)
4791
+ : boundedDocument(view === "full" ? pack : view === "input" ? pack.input : runContextEntry(pack), `run:${taskId}/${runId}/context/${view}`, parsed.cursor);
4788
4792
  return output(`${JSON.stringify(data)}\n`, { context: data });
4789
4793
  }
4790
4794
  function parseRunContextReference(value) {
@@ -18,6 +18,7 @@ export function taskRoleHostDiagnostic(host) {
18
18
  const delivery = snapshot.eventDelivery;
19
19
  return [
20
20
  `Provider Host=${snapshot.state}`,
21
+ `terminal reconciliation=${snapshot.controlCapabilities?.includes("reconcile-turn") ? "supported" : "unreported/unsupported"}`,
21
22
  delivery === undefined ? "event delivery=unreported" :
22
23
  `pending facts=${delivery.pending}; native terminals awaiting acknowledgement=${delivery.pendingTerminals}`,
23
24
  delivery?.failure === undefined ? undefined :
@@ -35,6 +36,7 @@ export function withTaskRoleHostObservation(status, host) {
35
36
  return { ...status, host: { detail: "Host does not match the recorded native Session." } };
36
37
  }
37
38
  const reportingNeedsAttention = snapshot !== undefined && (snapshot.state === "failed" || snapshot.state === "exited"
39
+ || snapshot.eventDelivery === undefined
38
40
  || snapshot.eventDelivery?.failure !== undefined
39
41
  || (snapshot.eventDelivery?.pendingTerminals ?? 0) > 0);
40
42
  return {
@@ -1,4 +1,4 @@
1
- import { providerRetryProjection } from "../runtime/providerRetry.js";
1
+ import { providerRetrySummary } 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
4
  import { RUN_INPUT_MAX_DELTAS, requireRunContextSnapshotRef } from "./runInputContract.js";
@@ -9,6 +9,7 @@ import { formatGitArtifactRef, validateGitArtifactRef } from "../artifacts/gitAr
9
9
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
10
10
  import { contextContentDigest, contextSnapshotRef, createContextSnapshot, validateContextSnapshot } from "./contextSnapshot.js";
11
11
  import { MAX_SYNTHESIS_SOURCE_RUNS, sourceRunContextValue } from "./sourceRunContext.js";
12
+ import { recordPage, summaryText } from "../output/boundedRead.js";
12
13
  export const RUN_CONTEXT_PACK_SCHEMA_VERSION = 1;
13
14
  function mergeRequiredContext(automatic, required, requiredPolicy = "current") {
14
15
  const entries = new Map(automatic.map(entry => [`${entry.ref.store}/${entry.ref.refId}`, entry]));
@@ -356,6 +357,41 @@ export function buildRunContextPack(store, taskId, runId) {
356
357
  });
357
358
  return pack;
358
359
  }
360
+ /** The frozen pack remains authority. Entry is a bounded discovery projection;
361
+ * historical Skill packages and complete inventories are explicit reads only. */
362
+ export function runContextPointerPage(pack, options = {}) {
363
+ const priority = ["task", "work-item", "review-round", "role-profile"];
364
+ const pointers = [...pack.pointers].sort((a, b) => {
365
+ const rank = (store) => priority.includes(store) ? priority.indexOf(store) : priority.length;
366
+ return rank(a.store) - rank(b.store);
367
+ }).map(pointer => ({ ...pointer, summary: summaryText(pointer.summary ?? "") }));
368
+ return recordPage(pointers, `run:${pack.identity.taskId}/${pack.identity.runId}/refs/${pack.snapshot.digest}`, options);
369
+ }
370
+ export function runContextEntry(pack) {
371
+ const { skillPackages: _packages, pointers: _pointers, deltaRefs, liveTaskState, input, ...identity } = pack;
372
+ const { directive, ...inputIdentity } = input;
373
+ const { items, ...pointerPage } = runContextPointerPage(pack, { limit: 16 });
374
+ return {
375
+ ...identity, input: { ...inputIdentity, ...(directive === undefined ? {} : {
376
+ directiveSummary: summaryText(directive), directiveOmitted: true
377
+ }) }, pointers: items, pointerPage,
378
+ deltaRefs: deltaRefs.slice(0, 16), deltaRefsTotal: deltaRefs.length,
379
+ inventory: { ...pack.inventory, complete: pointerPage.complete },
380
+ liveTaskState: {
381
+ providerRetries: liveTaskState.providerRetries.slice(0, 8),
382
+ activeRuns: liveTaskState.activeRuns.slice(0, 8),
383
+ activeTaskReviews: liveTaskState.activeTaskReviews.slice(0, 8),
384
+ totals: { providerRetries: liveTaskState.providerRetries.length,
385
+ activeRuns: liveTaskState.activeRuns.length, activeTaskReviews: liveTaskState.activeTaskReviews.length }
386
+ },
387
+ reads: {
388
+ refs: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view refs`,
389
+ full: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view full`,
390
+ input: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view input`,
391
+ live: `yui task context ${pack.identity.taskId}`
392
+ }
393
+ };
394
+ }
359
395
  /** Reads the Task's current in-flight execution, outside the frozen contract. */
360
396
  function readLiveTaskState(store, taskId) {
361
397
  const activeRuns = store.listActiveRuns(taskId)
@@ -376,7 +412,7 @@ function readLiveTaskState(store, taskId) {
376
412
  }));
377
413
  return Object.freeze({
378
414
  providerRetries: Object.freeze(store.listRoleSessionSets(taskId).flatMap(set => set.providerBinding?.retry === undefined
379
- ? [] : [{ roleName: set.owner.roleName, retry: providerRetryProjection(set.providerBinding) }])),
415
+ ? [] : [{ roleName: set.owner.roleName, retry: providerRetrySummary(set.providerBinding) }])),
380
416
  activeRuns: Object.freeze(activeRuns),
381
417
  activeTaskReviews: Object.freeze(activeTaskReviews)
382
418
  });
@@ -447,9 +483,18 @@ function collectAuthorizedContext(store, run) {
447
483
  const view = contextView(run);
448
484
  const result = [materialize("L2", "task", task.id, task)];
449
485
  const brief = store.getTaskBrief(task.id);
450
- if (brief !== null && view === "leader") {
486
+ if (brief !== null && (view === "leader" || view === "reviewer")) {
451
487
  result.push(materialize("L2", "task-brief", task.id, brief));
452
488
  }
489
+ // Reviewers need the current material index and effective decisions, not
490
+ // only the original Task description. Freeze them with the candidate so
491
+ // later edits cannot silently change the review contract. Artifact bodies
492
+ // remain on-demand, commit-pinned reads; do not crawl prose or Task history.
493
+ if (view === "reviewer") {
494
+ for (const decision of store.listDecisions(task.id).filter(({ status }) => status === "active")) {
495
+ result.push(materialize("L2", "task-decision", decision.id, decision));
496
+ }
497
+ }
453
498
  result.push(...collectRunContextOverlays(store, run));
454
499
  result.push(...collectSourceRunContext(store, run));
455
500
  if (run.workItemId !== undefined) {