@zq-silk/yui 2.6.0 → 2.8.0

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 (126) hide show
  1. package/README.md +23 -0
  2. package/dist/cli/commandCatalog.js +34 -7
  3. package/dist/cli/updateCommand.js +3 -0
  4. package/dist/commands/configCommands.js +1 -0
  5. package/dist/commands/globalRoleCommands.js +62 -35
  6. package/dist/commands/inboxCommands.js +372 -0
  7. package/dist/commands/roleSkillValidation.js +2 -0
  8. package/dist/commands/taskCommands.js +39 -7
  9. package/dist/commands/taskRoleRuntimeStatus.js +2 -0
  10. package/dist/context/currentSkills.js +48 -0
  11. package/dist/context/roleSessionContext.js +13 -16
  12. package/dist/context/runContextPack.js +50 -3
  13. package/dist/context/runInputContract.js +1 -1
  14. package/dist/context/sessionBootstrapManifest.js +27 -4
  15. package/dist/context/skillCatalog.js +2 -3
  16. package/dist/context/taskContext.js +38 -12
  17. package/dist/controlPlaneCli.js +58 -7
  18. package/dist/controller/controller.js +8 -2
  19. package/dist/controller/fileSchedulerStoreAdapter.js +26 -6
  20. package/dist/controller/globalInputDelivery.js +86 -16
  21. package/dist/controller/globalRuntimeAttention.js +136 -6
  22. package/dist/controller/operatorNotification.js +6 -1
  23. package/dist/controller/providerRetryDelivery.js +3 -5
  24. package/dist/controller/runtime.js +6 -2
  25. package/dist/controller/structuredProviderObservation.js +51 -15
  26. package/dist/controller/taskHostTerminalRecovery.js +33 -0
  27. package/dist/doctor/doctor.js +25 -2
  28. package/dist/executor/effectiveLaunch.js +2 -2
  29. package/dist/executor/fileRoleLaunchPlanner.js +8 -15
  30. package/dist/interaction/notificationPresentation.js +2 -2
  31. package/dist/interaction/operatorPresentation.js +1 -1
  32. package/dist/message/globalInputBacklog.js +69 -0
  33. package/dist/message/globalMessageQuery.js +1 -0
  34. package/dist/message/inboxReceipt.js +62 -0
  35. package/dist/message/message.js +6 -1
  36. package/dist/message/messageContinuation.js +34 -13
  37. package/dist/message/taskInboxOwnership.js +43 -0
  38. package/dist/models/modelManagement.js +469 -0
  39. package/dist/models/modelManagementConfig.js +46 -0
  40. package/dist/models/modelManagementFiles.js +90 -0
  41. package/dist/models/modelManagementHttp.js +90 -0
  42. package/dist/models/modelManagementTypes.js +1 -0
  43. package/dist/nativeAgent/session/backends.js +1 -1
  44. package/dist/observability/runtimeIdentity.js +1 -1
  45. package/dist/output/boundedRead.js +43 -14
  46. package/dist/resources/sqliteResourceRegistry.js +1 -1
  47. package/dist/runtime/agentEndpointIdentity.js +8 -4
  48. package/dist/runtime/agentEndpointOwnership.js +5 -6
  49. package/dist/runtime/agentHost.js +165 -24
  50. package/dist/runtime/agentHostImplementation.js +18 -0
  51. package/dist/runtime/managedCaller.js +25 -1
  52. package/dist/runtime/managedIdentity.js +1 -1
  53. package/dist/runtime/providerRetry.js +19 -1
  54. package/dist/runtime/sessionCaller.js +53 -0
  55. package/dist/scheduler/operatorEvent.js +49 -0
  56. package/dist/scheduler/operatorInputNotificationProcessor.js +5 -4
  57. package/dist/scheduler/taskWake.js +5 -0
  58. package/dist/sqlite/driver.js +108 -0
  59. package/dist/storage/contextRecords.js +28 -0
  60. package/dist/storage/sqliteSchema.js +21 -1
  61. package/dist/storage/sqliteStore.js +64 -1
  62. package/dist/storage/storageSchema.js +1 -1
  63. package/dist/storage/storageVersions.js +1 -1
  64. package/dist/storage/storeRpc.js +1 -0
  65. package/dist/storage/taskStore.js +4 -0
  66. package/dist/storage/upgrade/upgradeOrchestrator.js +1 -1
  67. package/dist/task/completionReadiness.js +13 -6
  68. package/dist/telemetry/sqliteTelemetryStore.js +1 -1
  69. package/dist/web/assets/assetManifest.js +28 -2
  70. package/dist/web/assets/client/app/manage.js +6 -0
  71. package/dist/web/assets/client/app/models.js +20 -0
  72. package/dist/web/assets/client/app/settings.js +41 -9
  73. package/dist/web/assets/client/i18n/en.js +180 -2
  74. package/dist/web/assets/client/i18n/zh-CN.js +178 -2
  75. package/dist/web/assets/client/index.js +8 -0
  76. package/dist/web/assets/client/layout/manageShell.js +94 -0
  77. package/dist/web/assets/client/lib/api.js +13 -1
  78. package/dist/web/assets/client/lib/i18n.js +5 -3
  79. package/dist/web/assets/client/views/dock/conversation.js +231 -80
  80. package/dist/web/assets/client/views/models.js +777 -0
  81. package/dist/web/assets/client/views/skills.js +22 -15
  82. package/dist/web/assets/shared/icons.js +2 -0
  83. package/dist/web/assets/shared/manageNav.js +13 -0
  84. package/dist/web/assets/shell/manage.js +54 -0
  85. package/dist/web/assets/shell/models.js +7 -0
  86. package/dist/web/assets/shell/settings.js +1 -35
  87. package/dist/web/assets/shell/settingsContent.js +30 -0
  88. package/dist/web/assets/shell/sidebar.js +1 -0
  89. package/dist/web/assets/styles/index.js +6 -0
  90. package/dist/web/assets/styles/views/conversation.js +133 -0
  91. package/dist/web/assets/styles/views/manage.js +71 -0
  92. package/dist/web/assets/styles/views/models.js +129 -0
  93. package/dist/web/assets/styles/views/settings.js +28 -14
  94. package/dist/web/assets/styles/views/sidebar.js +1 -0
  95. package/dist/web/webServer.js +59 -9
  96. package/docs/cli-information-contract.md +42 -11
  97. package/docs/cli-information-contract.zh-CN.md +28 -7
  98. package/docs/managed-turn-and-session-runtime.md +127 -5
  99. package/docs/managed-turn-and-session-runtime.zh-CN.md +83 -3
  100. package/docs/release-workflow.md +105 -9
  101. package/docs/release-workflow.zh-CN.md +73 -5
  102. package/docs/storage-baseline.md +22 -7
  103. package/docs/storage-baseline.zh-CN.md +14 -5
  104. package/docs/task-delivery.md +17 -8
  105. package/docs/task-delivery.zh-CN.md +11 -5
  106. package/docs/testing/review-evaluation.md +148 -0
  107. package/docs/testing/review-evaluation.zh-CN.md +117 -0
  108. package/docs/testing/verification-levels.md +26 -4
  109. package/docs/testing/verification-levels.zh-CN.md +11 -1
  110. package/i18n/README.zh-CN.md +19 -0
  111. package/package.json +5 -2
  112. package/skills/yui-leader/SKILL.md +28 -11
  113. package/skills/yui-leader/VERSION +1 -0
  114. package/skills/yui-leader/references/execution.md +29 -0
  115. package/skills/yui-leader/references/replicated-execution.md +7 -0
  116. package/skills/yui-operator/SKILL.md +15 -3
  117. package/skills/yui-operator/VERSION +1 -0
  118. package/skills/yui-operator/references/task-delivery.md +2 -0
  119. package/skills/yui-reviewer/SKILL.md +43 -1
  120. package/skills/yui-reviewer/VERSION +1 -0
  121. package/skills/yui-runtime/SKILL.md +144 -15
  122. package/skills/yui-runtime/VERSION +1 -0
  123. package/skills/yui-runtime/references/publication.md +16 -5
  124. package/skills/yui-runtime/references/recovery.md +24 -5
  125. package/skills/yui-worker/SKILL.md +8 -1
  126. package/skills/yui-worker/VERSION +1 -0
@@ -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]));
@@ -344,6 +345,8 @@ export function buildRunContextPack(store, taskId, runId) {
344
345
  const preliminaryBytes = Buffer.byteLength(JSON.stringify({ ...body, digest }), "utf8");
345
346
  const pack = Object.freeze({
346
347
  ...body,
348
+ // Current loading guidance is not part of the frozen Assignment digest.
349
+ skillCheckCommand: "yui session skills --json",
347
350
  inventory: Object.freeze({
348
351
  refs: pointers.length,
349
352
  frozenBytes: preliminaryBytes,
@@ -354,6 +357,41 @@ export function buildRunContextPack(store, taskId, runId) {
354
357
  });
355
358
  return pack;
356
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
+ }
357
395
  /** Reads the Task's current in-flight execution, outside the frozen contract. */
358
396
  function readLiveTaskState(store, taskId) {
359
397
  const activeRuns = store.listActiveRuns(taskId)
@@ -374,7 +412,7 @@ function readLiveTaskState(store, taskId) {
374
412
  }));
375
413
  return Object.freeze({
376
414
  providerRetries: Object.freeze(store.listRoleSessionSets(taskId).flatMap(set => set.providerBinding?.retry === undefined
377
- ? [] : [{ roleName: set.owner.roleName, retry: providerRetryProjection(set.providerBinding) }])),
415
+ ? [] : [{ roleName: set.owner.roleName, retry: providerRetrySummary(set.providerBinding) }])),
378
416
  activeRuns: Object.freeze(activeRuns),
379
417
  activeTaskReviews: Object.freeze(activeTaskReviews)
380
418
  });
@@ -445,9 +483,18 @@ function collectAuthorizedContext(store, run) {
445
483
  const view = contextView(run);
446
484
  const result = [materialize("L2", "task", task.id, task)];
447
485
  const brief = store.getTaskBrief(task.id);
448
- if (brief !== null && view === "leader") {
486
+ if (brief !== null && (view === "leader" || view === "reviewer")) {
449
487
  result.push(materialize("L2", "task-brief", task.id, brief));
450
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
+ }
451
498
  result.push(...collectRunContextOverlays(store, run));
452
499
  result.push(...collectSourceRunContext(store, run));
453
500
  if (run.workItemId !== undefined) {
@@ -79,7 +79,7 @@ export function serializeRunInputEnvelope(value) {
79
79
  .join(",");
80
80
  const snapshot = normalized.contextSnapshotRef;
81
81
  const lines = [
82
- "Yui AgentRun input. Follow the Session Manifest and injected Skills.",
82
+ "Yui AgentRun input. Check current Skills: yui session skills --json. Follow the Session Manifest profile and current Skills.",
83
83
  `${normalized.subject.taskId === undefined ? "" : `task=${normalized.subject.taskId} `}run=${normalized.runId} role=${normalized.roleName}`,
84
84
  `source=${normalized.source.type}/${normalized.source.channel} purpose=${normalized.purpose} subject=${subject || "global"} snapshot=${snapshot === undefined ? "none" : `${snapshot.id}@${snapshot.digest}`}`,
85
85
  normalized.deltaRefIds.length === 0
@@ -3,6 +3,7 @@ import { chmodSync, readFileSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { writeTextFileAtomically } from "../storage/durableFile.js";
5
5
  import { validateSkillPackageRef } from "./skillPackage.js";
6
+ import { checkCurrentSkills, SKILL_CHECK_COMMAND, SKILL_REUSE_RULE } from "./currentSkills.js";
6
7
  import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
7
8
  export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
8
9
  /** Portable read pointers accompany every managed input, including the first
@@ -20,9 +21,22 @@ export function withSessionContextPointer(text, environment) {
20
21
  /** No remembered/sent flag can prove instructions survived context compaction. */
21
22
  export function sessionContextInstructions(manifestPath, digest) {
22
23
  return `Yui Session Manifest: ${manifestPath}${digest === undefined ? "" : ` (digest ${digest})`}. `
23
- + "Apply yui-runtime and its Role Skills/profile by exact manifest paths. "
24
- + "Load on first entry, version change or missing context; reuse only same-version instructions "
25
- + "fully available now, never infer availability from prior reads.";
24
+ + `Before work on every turn run the Manifest's contextProtocol.skillCheckCommand `
25
+ + `(older Manifests: ${SKILL_CHECK_COMMAND}; Global loadCommand also checks current Skills). `
26
+ + "Use its current stable Skill paths instead of historical frozen-package paths; preserve the Manifest's Role profile. "
27
+ + SKILL_REUSE_RULE;
28
+ }
29
+ /** The caller must already pass the ordinary managed Session/CLI/Home checks.
30
+ * Old manifests remain immutable audit records, while this read resolves their
31
+ * Skill IDs against the currently executing installation and Home. */
32
+ export function checkSessionSkills(home, manifestPath) {
33
+ const manifest = readSessionBootstrapManifest(manifestPath);
34
+ return {
35
+ skills: checkCurrentSkills(home, manifest.skills.map(skill => skill.id)),
36
+ roleProfileRef: manifest.roleProfileRef,
37
+ rule: SKILL_REUSE_RULE,
38
+ historicalPackagesAreAuditOnly: true
39
+ };
26
40
  }
27
41
  /** Read back one immutable Session Manifest and verify its content digest. */
28
42
  export function readSessionBootstrapManifest(path) {
@@ -76,6 +90,10 @@ export function readSessionBootstrapManifest(path) {
76
90
  requireText(entry.id, "Session Manifest Skill id");
77
91
  requireText(entry.path, "Session Manifest Skill path");
78
92
  requireDigest(entry.digest, "Session Manifest Skill digest");
93
+ if (entry.version !== undefined) {
94
+ requireText(entry.version, "Session Manifest Skill version");
95
+ requireText(entry.versionPath, "Session Manifest Skill version path");
96
+ }
79
97
  if (entry.package !== undefined) {
80
98
  const ref = entry.package;
81
99
  validateSkillPackageRef(ref);
@@ -121,6 +139,7 @@ export function materializeSessionBootstrap(input) {
121
139
  id: skill.id,
122
140
  path: skill.path,
123
141
  digest: skill.package?.digest ?? digest(skill.content),
142
+ ...(skill.version === undefined ? {} : { version: skill.version, versionPath: skill.versionPath }),
124
143
  ...(skill.package === undefined ? {} : { package: skill.package })
125
144
  })),
126
145
  roleProfileRef: { digest: profileDigest, path: roleProfilePath },
@@ -129,11 +148,15 @@ export function materializeSessionBootstrap(input) {
129
148
  // A Global Session may move between Yui's remote TUI and Desktop.
130
149
  // Carry its read entry in the Thread-visible Manifest instead of
131
150
  // depending on the client process that happened to create it.
132
- loadCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name)
151
+ loadCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name),
152
+ // Same self-contained read remains usable from Desktop without the
153
+ // launching shell's managed environment or PATH.
154
+ skillCheckCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name)
133
155
  }
134
156
  : {
135
157
  loadCommand: `yui task run context ${quoteShellWord(`${input.owner.taskId}/<run-id>`)} --json`,
136
158
  expandCommand: `yui task run context expand ${quoteShellWord(`${input.owner.taskId}/<run-id>`)} <ref-id> --store <store> --mode full --json`,
159
+ skillCheckCommand: SKILL_CHECK_COMMAND,
137
160
  ...(input.role.name === "leader" ? {
138
161
  currentCommand: `yui task context ${quoteShellWord(input.owner.taskId)} --json`
139
162
  } : {})
@@ -1,9 +1,8 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
3
  import { dirname, join, relative, resolve, sep } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
4
  import { verifySkillPackage } from "./skillPackage.js";
6
- import { BUILTIN_YUI_SKILLS } from "./roleSessionContext.js";
5
+ import { BUILTIN_YUI_SKILLS, currentSkillPath } from "./currentSkills.js";
7
6
  const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
8
7
  const MAX_TEXT = 256 * 1024;
9
8
  const digest = (data) => createHash("sha256").update(data).digest("hex");
@@ -13,7 +12,7 @@ export function skillSource(home, id) {
13
12
  throw new Error("Invalid Skill id.");
14
13
  const builtin = BUILTIN_YUI_SKILLS.has(id);
15
14
  return { kind: builtin ? "builtin" : "configured",
16
- path: builtin ? fileURLToPath(new URL(`../../skills/${id}`, import.meta.url)) : resolve(home, "skills", id) };
15
+ path: currentSkillPath(home, id) };
17
16
  }
18
17
  function physical(path) {
19
18
  if (realpathSync(path) !== resolve(path))
@@ -4,14 +4,14 @@ import { taskNotFound, usageError } from "../errors/cliError.js";
4
4
  import { expandTaskMessageResult } from "../message/message.js";
5
5
  import { resolveManagedTaskReader } from "../runtime/managedCaller.js";
6
6
  import { hasManagedIdentity } from "../runtime/managedIdentity.js";
7
- import { providerRetryProjection } from "../runtime/providerRetry.js";
7
+ import { providerRetrySummary } from "../runtime/providerRetry.js";
8
8
  import { projectTaskRemoteDeliveryFromStore } from "../task/remoteDeliveryService.js";
9
9
  import { retiredTaskRecordIds, TASK_RECORD_RETIRED_EVENT } from "../task/taskRecordRetirement.js";
10
10
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
11
11
  import { contextContentDigest } from "./contextSnapshot.js";
12
12
  import { buildRunContextPack } from "./runContextPack.js";
13
13
  import { sourceRunContextValue } from "./sourceRunContext.js";
14
- import { boundedDocument, recordPage } from "../output/boundedRead.js";
14
+ import { boundedDocument, fitsReadBudget, summaryText, recordPage } from "../output/boundedRead.js";
15
15
  const MAX_RECORDS = 64;
16
16
  const MAX_VALUE_BYTES = 2048;
17
17
  const MAX_PAGE_BYTES = 24 * 1024;
@@ -41,13 +41,13 @@ export function readTaskContext(store, taskId, environment = {}) {
41
41
  if (entry.ref.store === "run")
42
42
  runtimeEvents ??= reader.listEventsByType(taskId, ["runtime.agent-error"]);
43
43
  const valueBytes = Buffer.byteLength(JSON.stringify(entry.value));
44
- const content = valueBytes > MAX_VALUE_BYTES || ["task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
44
+ const content = valueBytes > MAX_VALUE_BYTES || ["role", "task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
45
45
  ? { ref: entry.ref, summary: summarize(entry.value), omitted: true }
46
46
  : { ...entry, omitted: false };
47
47
  const record = { ...content, ...(entry.ref.store === "run" ? {
48
48
  execution: runExecutionObservation(entry.value, reader.getTaskRoleSessionSet(taskId, entry.value.roleName)?.providerBinding, runtimeEvents)
49
49
  } : {}), ...(entry.ref.store === "role" ? {
50
- providerRetry: providerRetryProjection(reader.getTaskRoleSessionSet(taskId, entry.value.name)?.providerBinding)
50
+ providerRetry: providerRetrySummary(reader.getTaskRoleSessionSet(taskId, entry.value.name)?.providerBinding)
51
51
  } : {}) };
52
52
  const size = Buffer.byteLength(JSON.stringify(record));
53
53
  if (bytes + size > MAX_PAGE_BYTES)
@@ -55,7 +55,7 @@ export function readTaskContext(store, taskId, environment = {}) {
55
55
  records.push(record);
56
56
  bytes += size;
57
57
  }
58
- return {
58
+ const result = {
59
59
  taskId, coreCursor: encode(cursor), throughCursor: encode(cursor),
60
60
  view: "current",
61
61
  collections: Object.entries(sources.reduce((counts, source) => {
@@ -67,6 +67,8 @@ export function readTaskContext(store, taskId, environment = {}) {
67
67
  discovery: `task context list ${taskId} --store <store>`,
68
68
  commandDiscovery: {
69
69
  help: "yui help", describe: "yui --json help",
70
+ skillCheck: "yui session skills --json",
71
+ ownInbox: "yui inbox list --json",
70
72
  note: "Help and completion share the current caller's offline command view. Follow that view for operation-specific discovery; execution always revalidates authority."
71
73
  },
72
74
  detail: `task context inspect ${taskId} --store <store> --ref <refId> --digest <digest>`,
@@ -77,6 +79,18 @@ export function readTaskContext(store, taskId, environment = {}) {
77
79
  limits: { records: MAX_RECORDS, valueBytes: MAX_VALUE_BYTES, pageBytes: 32 * 1024, recordBytes: MAX_PAGE_BYTES,
78
80
  attentionRefsPerCategory: MAX_ATTENTION_REFS, attentionRefBytesPerCategory: MAX_VALUE_BYTES }
79
81
  };
82
+ // Account for nested retry facts, collection counts, commands and JSON
83
+ // structure together. Exact omitted records stay discoverable by family.
84
+ while (!fitsReadBudget(result) && records.length > 0) {
85
+ records.pop();
86
+ result.collections.forEach(collection => {
87
+ collection.returned = records.filter(record => record.ref.store === collection.store).length;
88
+ });
89
+ result.omitted = { records: count - records.length, values: records.filter(record => record.omitted).length };
90
+ }
91
+ if (!fitsReadBudget(result))
92
+ throw usageError("Context identities exceed the response budget; use scoped discovery.");
93
+ return result;
80
94
  });
81
95
  }
82
96
  function summarizeAttention(store, taskId, allow) {
@@ -91,7 +105,8 @@ function summarizeAttention(store, taskId, allow) {
91
105
  ["messages", "task-message", undefined]
92
106
  ]) {
93
107
  const page = store.queryContextRecords(taskId, {
94
- family, statuses, descending: category === "messages", ids: allowedIds(allow, family), limit: MAX_ATTENTION_REFS
108
+ family, statuses, descending: category === "messages",
109
+ unhandledMessages: category === "messages", ids: allowedIds(allow, family), limit: MAX_ATTENTION_REFS
95
110
  });
96
111
  const summary = attention[category];
97
112
  summary.count = page.count;
@@ -260,11 +275,11 @@ export async function withContextObservations(core, providers, timeoutMs = 250)
260
275
  if (!["known", "partial", "unknown"].includes(value.coverage)
261
276
  || Buffer.byteLength(JSON.stringify(value)) > MAX_VALUE_BYTES)
262
277
  throw new Error("unbounded observation");
263
- return { source: provider.source.slice(0, 200), observedAt: new Date().toISOString(),
278
+ return { source: summaryText(provider.source, 200), observedAt: new Date().toISOString(),
264
279
  coverage: value.coverage, status: "available", value: JSON.parse(JSON.stringify(value.value)) };
265
280
  }
266
281
  catch {
267
- return { source: provider.source.slice(0, 200), observedAt: new Date().toISOString(),
282
+ return { source: summaryText(provider.source, 200), observedAt: new Date().toISOString(),
268
283
  coverage: "unknown", status: "unavailable" };
269
284
  }
270
285
  finally {
@@ -273,7 +288,18 @@ export async function withContextObservations(core, providers, timeoutMs = 250)
273
288
  controller.abort();
274
289
  }
275
290
  }));
276
- return { ...core, observations };
291
+ const result = { ...core, observations: [] };
292
+ for (const observation of observations) {
293
+ result.observations.push(observation);
294
+ if (!fitsReadBudget(result)) {
295
+ result.observations.pop();
296
+ const { value: _value, ...identity } = observation;
297
+ result.observations.push({ ...identity, omitted: true });
298
+ if (!fitsReadBudget(result))
299
+ result.observations.pop();
300
+ }
301
+ }
302
+ return { ...result, omittedObservations: observations.length - result.observations.length };
277
303
  }
278
304
  /** Shared current/historical Session read identity; discovery may only narrow it. */
279
305
  export function resolveContextReader(store, environment) {
@@ -511,7 +537,7 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
511
537
  }
512
538
  if (!current)
513
539
  query("change-set");
514
- query("task-message", { descending: true });
540
+ query("task-message", { descending: true, unhandledMessages: current });
515
541
  query("run", { descending: true, ...(current ? { statuses: ["active"] } : {}) });
516
542
  for (const id of allowedIds(allow, "source-turn") ?? []) {
517
543
  const run = store.getRun(taskId, id);
@@ -583,8 +609,8 @@ function freeze(value) {
583
609
  }
584
610
  function summarize(value) {
585
611
  const record = value;
586
- return ["title", "scope", "expiresWhen", "version", "objective", "question", "summary", "body", "status", "type"]
612
+ return summaryText(["title", "name", "activeAgentId", "scope", "expiresWhen", "version", "objective", "question", "summary", "body", "status", "type"]
587
613
  .flatMap((key) => typeof record[key] === "string" || (key === "version" && typeof record[key] === "number")
588
614
  ? [`${key}: ${record[key]}`] : [])
589
- .join("; ").slice(0, 400);
615
+ .join("; "));
590
616
  }
@@ -22,7 +22,10 @@ import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/i
22
22
  import { routeInvocation } from "./cli/invocationRouter.js";
23
23
  import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
24
24
  import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
25
- import { requireManagedGlobalCaller, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
25
+ import { requireManagedGlobalCaller, requireManagedGlobalReader, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
26
+ import { sessionCallerEnvironment, requireManagedCaller } from "./runtime/sessionCaller.js";
27
+ import { checkCurrentSkills, SKILL_REUSE_RULE } from "./context/currentSkills.js";
28
+ import { roleSkillIds } from "./context/roleSessionContext.js";
26
29
  import { hasManagedIdentity } from "./runtime/managedIdentity.js";
27
30
  import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
28
31
  import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
@@ -38,6 +41,7 @@ import { runDurableJobCommand } from "./commands/durableJobCommands.js";
38
41
  import { parseExecutionAuditOptions, runExecutionAuditCommand } from "./commands/executionAuditCommands.js";
39
42
  import { runGlobalRoleCommand, previewGlobalRoleAgentConfigurationMutation } from "./commands/globalRoleCommands.js";
40
43
  import { runJobCommand } from "./commands/jobCommands.js";
44
+ import { runInboxCommand } from "./commands/inboxCommands.js";
41
45
  import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
42
46
  import { previewProfileAgentConfigurationMutation, runProfileCommand } from "./commands/profileCommands.js";
43
47
  import { runProjectCommand } from "./commands/projectCommands.js";
@@ -61,7 +65,8 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
61
65
  import { FileCompletionManager } from "./completion/fileCompletionManager.js";
62
66
  import { CONFIG_DOMAINS } from "./config/configCatalog.js";
63
67
  import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
64
- import { readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
68
+ import { checkSessionSkills, readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
69
+ import { boundedDocument, readOptions } from "./output/boundedRead.js";
65
70
  import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
66
71
  import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
67
72
  import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
@@ -632,6 +637,11 @@ export async function main() {
632
637
  return;
633
638
  }
634
639
  assertTaskInvocationScope(resolved, process.env);
640
+ if (resolved[0] === "inbox") {
641
+ const result = runInboxCommand(resolved.slice(1), store, process.env);
642
+ emit(JSON.stringify(result, null, 2), false, result);
643
+ return;
644
+ }
635
645
  if (resolved[0] === "task" && resolved[1] === "role" && resolved[2] === "capabilities") {
636
646
  const query = parseTaskAgentCapabilityQuery(resolved.slice(3));
637
647
  const result = await callController(home, "task.role-capabilities", {
@@ -789,6 +799,26 @@ export async function main() {
789
799
  return;
790
800
  }
791
801
  if (resolved[0] === "session") {
802
+ if (resolved[1] === "skills") {
803
+ const parsed = readOptions(resolved.slice(2), ["--cursor"]);
804
+ if (parsed.positionals.length !== 0)
805
+ throw usageError("Usage: yui session skills [--cursor <cursor>] --json");
806
+ const path = process.env.YUI_SESSION_MANIFEST;
807
+ const caller = requireManagedCaller(store, process.env);
808
+ const set = caller.scope === "global" ? store.getGlobalRoleSessionSet(caller.roleName)
809
+ : store.getTaskRoleSessionSet(caller.taskId, caller.roleName);
810
+ const effective = set.sessions[caller.agentId].effective;
811
+ const kind = caller.scope === "global" ? caller.roleName === "operator" ? "operator" : "global"
812
+ : effective.reviewRoundId !== undefined ? "reviewer" : caller.roleName === "leader" ? "leader" : "worker";
813
+ const checked = path === undefined ? {
814
+ skills: checkCurrentSkills(home, roleSkillIds(effective.context, kind)),
815
+ profile: effective.context, identity: caller, rule: SKILL_REUSE_RULE,
816
+ historicalPackagesAreAuditOnly: true
817
+ } : checkSessionSkills(home, path);
818
+ const data = boundedDocument(checked, "session:current-skills", parsed.cursor);
819
+ emit(JSON.stringify(data, null, 2), false, data);
820
+ return;
821
+ }
792
822
  if (resolved[1] === "stop") {
793
823
  const options = parseSessionStopOptions(resolved.slice(2));
794
824
  const result = await runSessionStopCommand({
@@ -1969,6 +1999,19 @@ async function preflightManagedTaskControlPlane() {
1969
1999
  if (taskFinalReviewInvocation.error !== undefined) {
1970
2000
  throw new Error(taskFinalReviewInvocation.error);
1971
2001
  }
2002
+ // Desktop clients may carry only their provider-native identity. Resolve
2003
+ // command-local hints before any public/Task/Global routing. This reads this
2004
+ // Home only, and never creates a Session or mutates its registration.
2005
+ if ((process.env.CODEX_THREAD_ID !== undefined || process.env.YUI_NATIVE_SESSION_ID !== undefined)
2006
+ && (process.env.YUI_SESSION_SCOPE === undefined || process.env.YUI_ROLE === undefined)) {
2007
+ const registry = openCurrentTaskStore(resolveYuiHome(process.env));
2008
+ try {
2009
+ Object.assign(process.env, sessionCallerEnvironment(registry, process.env));
2010
+ }
2011
+ finally {
2012
+ registry.close();
2013
+ }
2014
+ }
1972
2015
  if (process.env.YUI_SESSION_SCOPE !== "task") {
1973
2016
  if (process.env.YUI_SESSION_SCOPE === "global") {
1974
2017
  return await preflightManagedGlobalControlPlane();
@@ -2047,8 +2090,8 @@ async function preflightManagedGlobalControlPlane() {
2047
2090
  const home = resolveYuiHome(process.env);
2048
2091
  const manifest = assertManagedSessionManifest(home, "global");
2049
2092
  const expectedRoleKind = process.env.YUI_ROLE === "operator" ? "operator" : "global";
2050
- if (manifest.owner.scope !== "global"
2051
- || manifest.roleKind !== expectedRoleKind) {
2093
+ if (manifest !== undefined && (manifest.owner.scope !== "global"
2094
+ || manifest.roleKind !== expectedRoleKind)) {
2052
2095
  throw new ManagedRuntimeDriftError("Managed global invocation does not match its Session Manifest.");
2053
2096
  }
2054
2097
  if (expectedRoleKind === "operator" && ["doctor", "upgrade", "update"].includes(args[0] ?? "")) {
@@ -2062,9 +2105,15 @@ async function preflightManagedGlobalControlPlane() {
2062
2105
  const verifiedStore = openCurrentTaskStore(home);
2063
2106
  const internalCallback = args[0] === "internal"
2064
2107
  && ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
2065
- if (!internalCallback && !operatorOfflineCommand(args)) {
2108
+ if (!internalCallback) {
2109
+ const selfRead = args[0] === "session" && args[1] === "context" && args[2] === process.env.YUI_ROLE
2110
+ || args[0] === "role" && args[1] === "message" && ["list", "show"].includes(args[2] ?? "")
2111
+ && args[3] === process.env.YUI_ROLE;
2066
2112
  try {
2067
- requireManagedGlobalCaller(verifiedStore, process.env);
2113
+ if (selfRead)
2114
+ requireManagedGlobalReader(verifiedStore, process.env);
2115
+ else
2116
+ requireManagedGlobalCaller(verifiedStore, process.env);
2068
2117
  }
2069
2118
  catch (error) {
2070
2119
  verifiedStore.close();
@@ -2076,7 +2125,9 @@ async function preflightManagedGlobalControlPlane() {
2076
2125
  function assertManagedSessionManifest(home, scope) {
2077
2126
  const manifestPath = process.env.YUI_SESSION_MANIFEST;
2078
2127
  if (manifestPath === undefined) {
2079
- throw new ManagedRuntimeDriftError("Managed control-plane invocation requires its Session Manifest.");
2128
+ // A Manifest is a context pointer, not an authority credential. Native
2129
+ // identity is validated against the current Home immediately below.
2130
+ return undefined;
2080
2131
  }
2081
2132
  let manifest;
2082
2133
  try {
@@ -692,6 +692,7 @@ export class FileTaskController {
692
692
  #globalInputDelivery;
693
693
  #providerRetry;
694
694
  #continuationReconciler;
695
+ #hostTerminalRecovery;
695
696
  #current;
696
697
  #operatorCurrent;
697
698
  #runtimeObserverCurrent;
@@ -741,6 +742,7 @@ export class FileTaskController {
741
742
  this.#leaderWakeFence = options.leaderWakeFence;
742
743
  this.#jobSupervisor = options.jobSupervisor;
743
744
  this.#continuationReconciler = options.continuationReconciler;
745
+ this.#hostTerminalRecovery = options.hostTerminalRecovery;
744
746
  this.#globalInputDelivery = options.globalInputDelivery;
745
747
  this.#providerRetry = options.providerRetry;
746
748
  this.#signalScheduler = new MailboxScheduler(async (keys) => { await this.#requestPass({ kind: "dirty", keys }); }, {
@@ -995,8 +997,11 @@ export class FileTaskController {
995
997
  }
996
998
  }
997
999
  const firstRuntimeDrain = await this.#drainRuntimeEvents();
1000
+ if (scope.kind === "full" && this.#hostTerminalRecovery !== undefined) {
1001
+ await this.#observeMaintenance("Host terminal recovery", this.#hostTerminalRecovery);
1002
+ }
998
1003
  await this.#providerRetry?.reconcile();
999
- await this.#globalInputDelivery?.();
1004
+ await this.#globalInputDelivery?.({ reconcile: scope.kind === "full", now: this.#now() });
1000
1005
  for (const taskId of runtimeTaskFailureIds(firstRuntimeDrain)) {
1001
1006
  runtimeFailedTaskIds.add(taskId);
1002
1007
  }
@@ -1076,7 +1081,8 @@ export class FileTaskController {
1076
1081
  if (scope.kind === "full"
1077
1082
  || scope.keys.some((key) => key !== "operator")
1078
1083
  || runtimeAdvanced) {
1079
- this.#signalOperatorMailbox(runtimeAdvanced);
1084
+ this.#signalOperatorMailbox(runtimeAdvanced || scope.kind === "full"
1085
+ || scope.kind === "dirty" && scope.keys.some(key => parseMailboxKey(key).kind === "global-role"));
1080
1086
  }
1081
1087
  }
1082
1088
  catch (error) {
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
+ import { deliveredMessageWake, pinMessageRevisions, taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
3
4
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
4
5
  import { freezeRunContextSnapshot } from "../context/runContextPack.js";
5
6
  import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
@@ -28,8 +29,9 @@ import { activeRoleAgentBinding } from "../role/role.js";
28
29
  import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
29
30
  import { standardAgentError } from "../runtime/agentError.js";
30
31
  import { recordTaskAgentError } from "./taskAgentError.js";
31
- import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure } from "./globalRuntimeAttention.js";
32
+ import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure, recordGlobalRuntimeRecovery } from "./globalRuntimeAttention.js";
32
33
  import { queueOperatorNotification } from "./operatorNotification.js";
34
+ import { claimOperatorAttention } from "../scheduler/operatorEvent.js";
33
35
  import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
34
36
  import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
35
37
  import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
@@ -448,6 +450,10 @@ export class FileSchedulerStoreAdapter {
448
450
  binding = settleProviderTurn(binding, { attemptId: receipt, nativeTurnId: fence.nativeTurnId,
449
451
  status: input.kind === "turn.completed" ? "completed" : input.kind === "turn.cancelled" ? "cancelled" : "failed",
450
452
  settledAt: at });
453
+ if (input.kind === "turn.completed")
454
+ recordGlobalRuntimeRecovery(store, fence.roleName, input.eventId, {
455
+ nativeSessionId: fence.nativeSessionId, evidence: `provider-structured:${receipt}`
456
+ }, now);
451
457
  if (fence.nativeTurnId !== undefined)
452
458
  sessions = rememberRoleAgentCompletedTurn(sessions, fence.agentId, fence.nativeSessionId, fence.nativeTurnId, new Date(at));
453
459
  }
@@ -882,6 +888,11 @@ export class FileSchedulerStoreAdapter {
882
888
  return [...this.store.listPendingActivationRequestTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
883
889
  }
884
890
  getTask(taskId) { return this.store.getTask(taskId); }
891
+ getEvent(taskId, eventId) {
892
+ return this.store.queryContextRecords(taskId, {
893
+ family: "task-event", ids: [eventId], limit: 1
894
+ }).records[0]?.value ?? null;
895
+ }
885
896
  getTaskWorkspace(taskId) { return this.store.getTaskWorkspace(taskId); }
886
897
  getTaskBrief(taskId) { return this.store.getTaskBrief(taskId); }
887
898
  listDecisions(taskId) { return this.store.listDecisions(taskId); }
@@ -1146,7 +1157,8 @@ export class FileSchedulerStoreAdapter {
1146
1157
  throw new AgentHostProviderSessionBusyError("The next Global input belongs to an explicit then handoff.");
1147
1158
  }
1148
1159
  if (message !== undefined) {
1149
- if (message.delivery !== undefined || message.notDelivered !== undefined
1160
+ if (store.queryGlobalRoleMessages(input.roleName, { handling: true, limit: 0 }).count > 0
1161
+ || message.inbox !== undefined || message.delivery !== undefined || message.notDelivered !== undefined
1150
1162
  || message.control !== undefined && message.control.outcome !== "rejected"
1151
1163
  || message.deliveryTarget?.agentId !== input.agentId
1152
1164
  || message.deliveryTarget?.nativeSessionId !== input.nativeSessionId) {
@@ -1567,7 +1579,7 @@ export class FileSchedulerStoreAdapter {
1567
1579
  if (mailbox.processing !== null) {
1568
1580
  return { status: "processing", processing: mailbox.processing };
1569
1581
  }
1570
- let claimed = claimPending(mailbox, {
1582
+ let claimed = (input.target.kind === "operator" ? claimOperatorAttention : claimPending)(mailbox, {
1571
1583
  batchId: input.batchId,
1572
1584
  owner: input.owner,
1573
1585
  startedAt: input.now.toISOString()
@@ -1737,7 +1749,7 @@ export class FileSchedulerStoreAdapter {
1737
1749
  clearPendingWakeup(taskId) { this.store.clearPendingWakeup(taskId); }
1738
1750
  getLeaderFailure(taskId) { return this.store.getLeaderFailure(taskId); }
1739
1751
  prepareMessageContinuations(taskId, now) {
1740
- const roles = new Set(this.store.listMessages(taskId).flatMap((message) => message.recipient?.ownerRunId !== undefined && message.continuation?.runId === undefined ? [message.recipient.roleName] : []));
1752
+ const roles = new Set(this.store.listMessages(taskId).flatMap((message) => message.inbox === undefined && message.recipient?.ownerRunId !== undefined && message.continuation?.runId === undefined ? [message.recipient.roleName] : []));
1741
1753
  for (const roleName of roles) {
1742
1754
  try {
1743
1755
  this.store.transaction((store) => prepareMessageContinuations(store, taskId, now, roleName));
@@ -1746,7 +1758,7 @@ export class FileSchedulerStoreAdapter {
1746
1758
  const reason = `preparation-failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 1000);
1747
1759
  this.store.transaction((store) => {
1748
1760
  for (const message of store.listMessages(taskId)) {
1749
- if (message.recipient?.roleName === roleName && message.recipient.ownerRunId !== undefined && message.continuation?.runId === undefined
1761
+ if (message.inbox === undefined && message.recipient?.roleName === roleName && message.recipient.ownerRunId !== undefined && message.continuation?.runId === undefined
1750
1762
  && message.continuation?.notDeliveredReason !== reason) {
1751
1763
  store.updateMessage(taskId, { ...message, continuation: { notDeliveredReason: reason } });
1752
1764
  }
@@ -1855,6 +1867,8 @@ export class FileSchedulerStoreAdapter {
1855
1867
  return { wakeId, attemptId: nextAttempt, disposition: "submit",
1856
1868
  presentation: taskNotificationPresentation(wake, store) };
1857
1869
  }
1870
+ if (unfinishedLeaderInbox(store, taskId))
1871
+ return null;
1858
1872
  if (mailbox.pending === null || (provider !== null && provider !== undefined
1859
1873
  && ["submitting", "accepted", "delivery-unknown"].includes(provider.status)))
1860
1874
  return null;
@@ -1864,6 +1878,12 @@ export class FileSchedulerStoreAdapter {
1864
1878
  const sessions = store.getTaskRoleSessionSet(taskId, "leader");
1865
1879
  const session = sessions?.sessions[sessions.activeAgentId];
1866
1880
  const obsoleteIds = new Set();
1881
+ for (const message of store.listMessages(taskId)) {
1882
+ if (pendingMessageIds.has(message.id) && (taskMessageInboxReceipt(store, message) !== undefined
1883
+ || deliveredMessageWake(store, message) !== undefined)) {
1884
+ obsoleteIds.add(message.id);
1885
+ }
1886
+ }
1867
1887
  for (const message of store.listMessages(taskId)) {
1868
1888
  if (!pendingMessageIds.has(message.id) || message.inputControl?.expectedSessionId === undefined)
1869
1889
  continue;
@@ -1910,7 +1930,7 @@ export class FileSchedulerStoreAdapter {
1910
1930
  const wakeId = store.nextTaskWakeId(taskId);
1911
1931
  const wake = createTaskWake({
1912
1932
  id: wakeId, taskId, reasons: mailbox.pending.reasons,
1913
- refs: selectedRefs,
1933
+ refs: pinMessageRevisions(store, taskId, selectedRefs),
1914
1934
  fromCursor: latest?.toCursor ?? task.createdAt,
1915
1935
  toCursor: new Date(Math.max(now.getTime(), Date.parse(mailbox.pending.lastQueuedAt))).toISOString(), now
1916
1936
  });