@zq-silk/yui 2.21.0 → 2.22.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 (125) hide show
  1. package/README.md +5 -0
  2. package/dist/cli/commandCatalog.js +85 -25
  3. package/dist/cli/dynamicCompletion.js +4 -1
  4. package/dist/cli/helpRenderer.js +12 -1
  5. package/dist/cli/interactionPolicy.js +2 -0
  6. package/dist/cli/invocationRouter.js +5 -5
  7. package/dist/cli/roleWizard.js +5 -1
  8. package/dist/commands/globalRoleCommands.js +33 -8
  9. package/dist/commands/inboxCommands.js +39 -12
  10. package/dist/commands/operatorCommands.js +2 -5
  11. package/dist/commands/releaseCommands.js +16 -2
  12. package/dist/commands/roleConfiguration.js +25 -2
  13. package/dist/commands/sessionCommands.js +5 -2
  14. package/dist/commands/taskCommands.js +217 -64
  15. package/dist/commands/taskContextCommand.js +26 -4
  16. package/dist/commands/taskFactCommands.js +15 -2
  17. package/dist/commands/taskIntegrationCommands.js +3 -3
  18. package/dist/commands/taskRoleRuntimeStatus.js +15 -5
  19. package/dist/commands/textInput.js +2 -2
  20. package/dist/context/contextFamilies.js +10 -0
  21. package/dist/context/currentSkills.js +6 -4
  22. package/dist/context/runContextPack.js +44 -5
  23. package/dist/context/runInputContract.js +1 -1
  24. package/dist/context/sessionBootstrapManifest.js +7 -5
  25. package/dist/context/sessionSkillNotice.js +53 -0
  26. package/dist/context/taskContext.js +100 -16
  27. package/dist/controlPlaneCli.js +17 -7
  28. package/dist/controller/agentRuntimeObserver.js +57 -12
  29. package/dist/controller/clientRuntime.js +37 -8
  30. package/dist/controller/controller.js +3 -1
  31. package/dist/controller/diagnosticJournal.js +10 -2
  32. package/dist/controller/fileSchedulerStoreAdapter.js +72 -24
  33. package/dist/controller/messageHealth.js +4 -3
  34. package/dist/controller/providerRetryAdmission.js +15 -1
  35. package/dist/controller/providerRetryDelivery.js +18 -2
  36. package/dist/controller/runtime.js +15 -5
  37. package/dist/controller/runtimeEventInbox.js +107 -13
  38. package/dist/controller/runtimeEventProcessor.js +3 -3
  39. package/dist/controller/taskRunRecovery.js +153 -0
  40. package/dist/core/controllerClient.js +16 -2
  41. package/dist/core/controllerServer.js +14 -5
  42. package/dist/core/controllerTelemetry.js +51 -5
  43. package/dist/errors/cliFailure.js +14 -5
  44. package/dist/executor/agentAdapter.js +32 -4
  45. package/dist/executor/agentConfigurationFields.js +2 -0
  46. package/dist/integration/gitIntegrationService.js +3 -2
  47. package/dist/integration/integrationSourceApplication.js +12 -1
  48. package/dist/interaction/notificationPresentation.js +11 -16
  49. package/dist/interaction/operatorPresentation.js +1 -1
  50. package/dist/message/messageContinuation.js +3 -1
  51. package/dist/output/boundedRead.js +36 -7
  52. package/dist/output/rolePresentation.js +3 -1
  53. package/dist/runtime/agentHost.js +12 -3
  54. package/dist/runtime/codexAppServerRuntime.js +110 -24
  55. package/dist/runtime/codexInteractiveHost.js +46 -0
  56. package/dist/runtime/providerRetry.js +5 -2
  57. package/dist/runtime/providerRuntimeIdentity.js +1 -1
  58. package/dist/runtime/structuredProviderHost.js +32 -34
  59. package/dist/scheduler/wakeupQueue.js +10 -4
  60. package/dist/storage/sqliteSchema.js +9 -0
  61. package/dist/storage/storageVersions.js +1 -1
  62. package/dist/tmux/tmuxManager.js +4 -2
  63. package/dist/web/webSettings.js +2 -0
  64. package/docs/authorization.md +18 -3
  65. package/docs/cli-information-contract.md +228 -6
  66. package/docs/cli-information-contract.zh-CN.md +95 -5
  67. package/docs/managed-turn-and-session-runtime.md +82 -21
  68. package/docs/managed-turn-and-session-runtime.zh-CN.md +56 -13
  69. package/docs/provider-retry.md +11 -0
  70. package/docs/release-workflow.md +37 -0
  71. package/docs/release-workflow.zh-CN.md +28 -0
  72. package/docs/roles-and-configuration.md +28 -0
  73. package/docs/roles-and-configuration.zh-CN.md +21 -0
  74. package/docs/storage-baseline.md +4 -1
  75. package/docs/storage-baseline.zh-CN.md +1 -1
  76. package/docs/task-delivery.md +7 -0
  77. package/docs/task-delivery.zh-CN.md +5 -0
  78. package/docs/testing/review-evaluation.md +7 -7
  79. package/docs/testing/review-evaluation.zh-CN.md +7 -7
  80. package/docs/testing/verification-levels.md +45 -2
  81. package/docs/testing/verification-levels.zh-CN.md +21 -2
  82. package/i18n/README.zh-CN.md +6 -0
  83. package/package.json +1 -1
  84. package/skills/yui-leader/SKILL.md +98 -193
  85. package/skills/yui-leader/VERSION +1 -1
  86. package/skills/yui-leader/references/artifacts.md +41 -0
  87. package/skills/yui-leader/references/assignment-execution.md +140 -0
  88. package/skills/yui-leader/references/completion.md +79 -0
  89. package/skills/yui-leader/references/delivery-scope.md +26 -0
  90. package/skills/yui-leader/references/execution.md +16 -487
  91. package/skills/yui-leader/references/implementation.md +81 -0
  92. package/skills/yui-leader/references/planning.md +7 -45
  93. package/skills/yui-leader/references/review.md +90 -0
  94. package/skills/yui-leader/references/task-progress.md +23 -0
  95. package/skills/yui-leader/references/work-allocation.md +118 -0
  96. package/skills/yui-operator/SKILL.md +77 -333
  97. package/skills/yui-operator/VERSION +1 -1
  98. package/skills/yui-operator/references/body-transport.md +62 -0
  99. package/skills/yui-operator/references/configuration.md +47 -0
  100. package/skills/yui-operator/references/lifecycle.md +41 -0
  101. package/skills/yui-operator/references/progress.md +39 -0
  102. package/skills/yui-operator/references/routing.md +57 -0
  103. package/skills/yui-operator/references/submission.md +56 -0
  104. package/skills/yui-operator/references/task-delivery.md +1 -1
  105. package/skills/yui-operator/references/web-lifecycle.md +19 -0
  106. package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
  107. package/skills/yui-runtime/SKILL.md +177 -531
  108. package/skills/yui-runtime/VERSION +1 -1
  109. package/skills/yui-runtime/references/archive.md +51 -0
  110. package/skills/yui-runtime/references/command-discovery.md +41 -0
  111. package/skills/yui-runtime/references/completed-followup.md +65 -0
  112. package/skills/yui-runtime/references/context-ordering.md +3 -34
  113. package/skills/yui-runtime/references/context-reads.md +61 -0
  114. package/skills/yui-runtime/references/dispatched-run.md +61 -0
  115. package/skills/yui-runtime/references/execution-failure.md +105 -0
  116. package/skills/yui-runtime/references/global-context.md +18 -0
  117. package/skills/yui-runtime/references/global-input-controls.md +44 -0
  118. package/skills/yui-runtime/references/host-custody.md +30 -0
  119. package/skills/yui-runtime/references/inbox-recovery.md +77 -0
  120. package/skills/yui-runtime/references/incremental-context.md +40 -0
  121. package/skills/yui-runtime/references/publication.md +4 -111
  122. package/skills/yui-runtime/references/recovery.md +32 -192
  123. package/skills/yui-runtime/references/session-identity.md +18 -0
  124. package/skills/yui-runtime/references/session-replacement.md +45 -0
  125. package/skills/yui-runtime/references/uncertain-execution.md +29 -0
@@ -4,6 +4,7 @@ import { taskRecordList } from "./taskContextCommand.js";
4
4
  import { materialize } from "../context/taskContext.js";
5
5
  import { enqueueWork } from "../coordination/workMailboxQueue.js";
6
6
  import { createDecision, supersedeDecision } from "../decision/decision.js";
7
+ import { readDigest } from "../output/boundedRead.js";
7
8
  import { dataError, usageError } from "../errors/cliError.js";
8
9
  import { createMilestone } from "../milestone/milestone.js";
9
10
  import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, leaderMailbox, notifyMailbox, output, parseMultiValueTail, parseTail, presentTime, recordTaskEvent, requiredOption, requiredText, requireTask, taskActor, taskMailbox, taskRef } from "./taskCommandSupport.js";
@@ -107,6 +108,18 @@ export function taskBriefCommand(args, store, options) {
107
108
  ? "Task brief command is required."
108
109
  : `Unknown command: task brief ${command}`);
109
110
  }
111
+ function decisionReceipt(decision) {
112
+ // A short saved value lets the caller inspect the actual effect without
113
+ // another read. Bound the complete JSON record, including title/reason.
114
+ const contentOmitted = Buffer.byteLength(JSON.stringify(decision)) > 2048;
115
+ const value = contentOmitted ? {
116
+ id: decision.id, taskId: decision.taskId, status: decision.status,
117
+ createdAt: decision.createdAt, updatedAt: decision.updatedAt
118
+ } : decision;
119
+ return { taskId: decision.taskId, effect: "saved", contentOmitted, decision: { ...value,
120
+ digest: readDigest(decision), rationaleBytes: Buffer.byteLength(decision.rationale)
121
+ }, ...(contentOmitted ? { read: `yui task decision show ${decision.taskId} ${decision.id} --json` } : {}) };
122
+ }
110
123
  export function taskDecisionCommand(args, store, options) {
111
124
  const [command, ...rest] = args;
112
125
  if (command === "record") {
@@ -136,7 +149,7 @@ export function taskDecisionCommand(args, store, options) {
136
149
  notifyMailbox(options.runtime, taskMailbox(result.task.id));
137
150
  if (result.task.status === "active")
138
151
  notifyMailbox(options.runtime, leaderMailbox(result.task.id));
139
- return output(`Recorded decision ${result.decision.id} for ${result.task.id}\n`);
152
+ return output(`Recorded decision ${result.decision.id} for ${result.task.id}\n`, decisionReceipt(result.decision));
140
153
  }
141
154
  if (command === "list") {
142
155
  return taskRecordList(rest, store, "task-decision", options.environment);
@@ -190,7 +203,7 @@ export function taskDecisionCommand(args, store, options) {
190
203
  notifyMailbox(options.runtime, taskMailbox(result.task.id));
191
204
  if (result.task.status === "active")
192
205
  notifyMailbox(options.runtime, leaderMailbox(result.task.id));
193
- return output(`Superseded decision ${result.decision.id} for ${result.task.id}\n`);
206
+ return output(`Superseded decision ${result.decision.id} for ${result.task.id}\n`, decisionReceipt(result.decision));
194
207
  }
195
208
  throw usageError(command === undefined
196
209
  ? "Task decision command is required."
@@ -167,10 +167,10 @@ async function continueIntegration(args, store, home, now, options) {
167
167
  || integration.resolution?.action !== "manual-resolution")) {
168
168
  throw usageError(`Integration is not ready to continue: ${integration.id}/${integration.status}.`);
169
169
  }
170
- return runIntegration(store, home, integration, now, options);
170
+ return runIntegration(store, home, integration, now, options, "continue");
171
171
  }
172
- async function runIntegration(store, home, integration, now, options) {
173
- const result = await new GitIntegrationService(home, store, undefined, now, options.environment, undefined, options.jobPort).integrate(integration.taskId, integration.id);
172
+ async function runIntegration(store, home, integration, now, options, intent) {
173
+ const result = await new GitIntegrationService(home, store, undefined, now, options.environment, undefined, options.jobPort).integrate(integration.taskId, integration.id, undefined, intent);
174
174
  const output = result.status === "committed"
175
175
  ? `Integrated ${integrationSourceLabel(result.attempt)} into ${result.attempt.targetRef} with CAS (${result.attempt.id})\n`
176
176
  : result.status === "conflicted"
@@ -49,6 +49,16 @@ export function withTaskRoleHostObservation(status, host) {
49
49
  };
50
50
  }
51
51
  export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now = new Date()) {
52
+ if (roles.length === 0)
53
+ return [];
54
+ // One inspection shares the same Task history across Roles. This is local
55
+ // read reuse, not a cache of runtime health or provider observations.
56
+ const events = store.listEvents(taskId);
57
+ const history = {
58
+ events,
59
+ runs: operationalTaskRecords(store.listRuns(taskId), events, "run"),
60
+ workspaces: store.listManagedWorkspaces(taskId)
61
+ };
52
62
  const taskOpenInputRequestCount = store.listInputRequests(taskId)
53
63
  .filter((request) => request.status === "open").length;
54
64
  const panesByRole = new Map();
@@ -57,7 +67,7 @@ export function inspectTaskRoleRuntimeStatuses(taskId, roles, store, panes, now
57
67
  if (current === undefined || current.dead && !pane.dead)
58
68
  panesByRole.set(pane.roleName, pane);
59
69
  }
60
- return roles.map((role) => inspectTaskRoleRuntimeStatus(taskId, role, store, panesByRole.get(role.name), role.name === "leader" ? taskOpenInputRequestCount : 0, now));
70
+ return roles.map((role) => inspectTaskRoleRuntimeStatus(taskId, role, store, panesByRole.get(role.name), role.name === "leader" ? taskOpenInputRequestCount : 0, now, history));
61
71
  }
62
72
  export function renderTaskRoleRuntimeStatus(status) {
63
73
  const activeRun = status.activeRun === null
@@ -185,12 +195,12 @@ export function taskRoleTmuxLabel(status) {
185
195
  ? "running"
186
196
  : `running (${status.tmux.currentCommand})`;
187
197
  }
188
- function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputRequestCount, now) {
198
+ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputRequestCount, now, history) {
189
199
  const activeRun = store.getActiveRun(taskId, role.name);
190
200
  // The last AgentRun outcome is a separate axis from the Session lifecycle. A
191
201
  // Session that stops after its AgentRun completed must not retroactively turn
192
202
  // that AgentRun into a failure; the status display keeps both visible.
193
- const lastRun = operationalTaskRecords(store.listRuns(taskId), store.listEvents(taskId), "run")
203
+ const lastRun = history.runs
194
204
  .filter((candidate) => candidate.roleName === role.name)
195
205
  .sort((left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt))[0]
196
206
  ?? null;
@@ -221,12 +231,12 @@ function inspectTaskRoleRuntimeStatus(taskId, role, store, pane, openInputReques
221
231
  // distinct from the WorkItem Develop workspace.
222
232
  const actualWorkspaceRoot = effectiveLaunch?.workspace.root ?? role.workspace;
223
233
  const managedWorkspace = activeRun?.workspace
224
- ?? store.listManagedWorkspaces(taskId).find(({ root }) => root === actualWorkspaceRoot)
234
+ ?? history.workspaces.find(({ root }) => root === actualWorkspaceRoot)
225
235
  ?? null;
226
236
  const workspace = managedWorkspace === null
227
237
  ? { managed: false, path: actualWorkspaceRoot }
228
238
  : { ...managedWorkspace, managed: true };
229
- const events = store.listEvents(taskId);
239
+ const { events } = history;
230
240
  const sessionTokens = projectSessionTokenMetrics(events, resolveSessionTokenIdentity(nativeSession === null
231
241
  ? null
232
242
  : { taskId, roleName: role.name, ...nativeSession }));
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { usageError } from "../errors/cliError.js";
3
- export function readCommandText(inline, file, label, usage) {
3
+ export function readCommandText(inline, file, label, usage, options = {}) {
4
4
  if ((inline === undefined) === (file === undefined)) {
5
5
  throw usageError(`Specify exactly one of ${label} or ${label}-file.`, usage);
6
6
  }
@@ -11,5 +11,5 @@ export function readCommandText(inline, file, label, usage) {
11
11
  if (normalized.length === 0 || normalized.includes("\0")) {
12
12
  throw usageError(`${label} is required.`, usage);
13
13
  }
14
- return normalized;
14
+ return file !== undefined && options.preserveFileWhitespace ? value : normalized;
15
15
  }
@@ -0,0 +1,10 @@
1
+ /** Public current-Context family names, shared by reads and CLI discovery.
2
+ * Listing a family grants no permission to read its records.
3
+ */
4
+ export const CONTEXT_FAMILIES = Object.freeze([
5
+ "task", "task-brief", "remote-delivery", "role", "role-profile",
6
+ "project-policy", "project-knowledge", "input-request", "work-item",
7
+ "accepted-work-item", "candidate", "environment-preparation", "managed-workspace",
8
+ "change-set", "task-message", "run", "source-run", "review-round", "mailbox",
9
+ "task-decision", "task-milestone", "job", "publication", "task-event"
10
+ ]);
@@ -42,7 +42,9 @@ export function checkCurrentSkills(home, ids) {
42
42
  });
43
43
  }
44
44
  export const SKILL_CHECK_COMMAND = "yui session skills --json";
45
- export const SKILL_REUSE_RULE = "At the start of every turn, check current Skill versions and availability. "
46
- + "Read full SKILL.md and necessary references on first entry, version change, recovery, "
47
- + "or missing/compacted/uncertain context; reuse only unchanged full instructions available now. "
48
- + "A version check is not a loading receipt or authority.";
45
+ export const SKILL_REUSE_RULE = "The managed Host checks current Skill versions/availability before each input; "
46
+ + "do not repeat an explicit Host check. Without that notice, check through the Manifest's command. "
47
+ + "Reuse unchanged full instructions in context; messages alone require no reload. "
48
+ + "Read affected entries/references fully on change or localized loss. On first entry, recovery or lost context, "
49
+ + "rebuild the current working set from applicable Skill entry points and action-relevant references, not all past procedures. "
50
+ + "Metadata proves neither loading, memory nor authority.";
@@ -371,9 +371,11 @@ export function runContextEntry(pack) {
371
371
  const { skillPackages: _packages, pointers: _pointers, deltaRefs, liveTaskState, input, ...identity } = pack;
372
372
  const { directive, ...inputIdentity } = input;
373
373
  const { items, ...pointerPage } = runContextPointerPage(pack, { limit: 16 });
374
+ const directiveSummary = directive === undefined ? undefined : summaryText(directive);
374
375
  return {
375
376
  ...identity, input: { ...inputIdentity, ...(directive === undefined ? {} : {
376
- directiveSummary: summaryText(directive), directiveOmitted: true
377
+ ...(directiveSummary === directive ? { directive, directiveOmitted: false }
378
+ : { directiveSummary, directiveOmitted: true })
377
379
  }) }, pointers: items, pointerPage,
378
380
  deltaRefs: deltaRefs.slice(0, 16), deltaRefsTotal: deltaRefs.length,
379
381
  inventory: { ...pack.inventory, complete: pointerPage.complete },
@@ -386,9 +388,16 @@ export function runContextEntry(pack) {
386
388
  },
387
389
  reads: {
388
390
  inbox: "yui inbox list --view current --json",
389
- refs: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view refs`,
391
+ ...(items.length > 0 ? {
392
+ expand: `yui task run context expand ${pack.identity.taskId}/${pack.identity.runId} --context-ref <store/refId> [--context-ref <store/refId> ...] --mode full --json`
393
+ } : {}),
394
+ ...(!pointerPage.complete ? {
395
+ refs: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view refs`
396
+ } : {}),
390
397
  full: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view full`,
391
- input: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view input`,
398
+ ...(directiveSummary !== directive ? {
399
+ input: `yui task run context ${pack.identity.taskId}/${pack.identity.runId} --view input`
400
+ } : {}),
392
401
  live: `yui task context ${pack.identity.taskId}`
393
402
  }
394
403
  };
@@ -413,7 +422,7 @@ function readLiveTaskState(store, taskId) {
413
422
  }));
414
423
  return Object.freeze({
415
424
  providerRetries: Object.freeze(store.listRoleSessionSets(taskId).flatMap(set => set.providerBinding?.retry === undefined
416
- ? [] : [{ roleName: set.owner.roleName, retry: providerRetrySummary(set.providerBinding) }])),
425
+ ? [] : [{ roleName: set.owner.roleName, retry: providerRetrySummary(set.providerBinding, `yui task role session retry ${taskId} ${set.owner.roleName} show --json`) }])),
417
426
  activeRuns: Object.freeze(activeRuns),
418
427
  activeTaskReviews: Object.freeze(activeTaskReviews)
419
428
  });
@@ -422,6 +431,34 @@ export function expandRunContextRef(store, taskId, runId, refId, refStore) {
422
431
  requireIdentity(refStore, "Context ref store");
423
432
  const run = requireExactRun(store, taskId, runId);
424
433
  const snapshot = readRunContextSnapshot(store, run);
434
+ return expandSnapshotRef(snapshot, refId, refStore);
435
+ }
436
+ /** Explicit originals from one already-authorized frozen Run, not live Task
437
+ * discovery. Read the snapshot once and reject the entire selection on drift. */
438
+ export function expandRunContextRefs(store, taskId, runId, selectors) {
439
+ if (selectors.length < 1 || selectors.length > 8)
440
+ throw new Error("Run Context batch requires 1..8 exact references.");
441
+ const seen = new Set();
442
+ for (const ref of selectors) {
443
+ requireIdentity(ref.store, "Context ref store");
444
+ requireIdentity(ref.refId, "Context ref id");
445
+ if (ref.digest !== undefined && !/^[a-f0-9]{64}$/.test(ref.digest))
446
+ throw new Error("An explicit Run Context digest must be exact.");
447
+ const key = JSON.stringify([ref.store, ref.refId]);
448
+ if (seen.has(key))
449
+ throw new Error("Duplicate Run Context batch reference.");
450
+ seen.add(key);
451
+ }
452
+ const snapshot = readRunContextSnapshot(store, requireExactRun(store, taskId, runId));
453
+ const items = selectors.map(ref => {
454
+ const expanded = expandSnapshotRef(snapshot, ref.refId, ref.store);
455
+ if (ref.digest !== undefined && expanded.ref.digest !== ref.digest)
456
+ throw new Error(`Run Context reference digest does not match its frozen Snapshot: ${ref.store}/${ref.refId}.`);
457
+ return expanded;
458
+ });
459
+ return { taskId, runId, items };
460
+ }
461
+ function expandSnapshotRef(snapshot, refId, refStore) {
425
462
  const authorized = snapshot.refs.filter((ref) => (ref.refId === refId && ref.store === refStore));
426
463
  const selector = `${refStore}/${refId}`;
427
464
  if (authorized.length !== 1) {
@@ -770,7 +807,9 @@ function materialize(layer, store, refId, value) {
770
807
  summary: (store === "project-knowledge"
771
808
  ? [title, `version: ${record.version}`, ...(record.scope === undefined ? [] : [`scope: ${record.scope}`]),
772
809
  ...(record.expiresWhen === undefined ? [] : [`expiresWhen: ${record.expiresWhen}`])].join("; ")
773
- : title).slice(0, 400)
810
+ : store === "task-message" && typeof record.body === "string"
811
+ ? summaryText(`Excerpt: ${record.body}`, 160)
812
+ : title).slice(0, 400)
774
813
  }),
775
814
  value
776
815
  });
@@ -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. Check current Skills: yui session skills --json. Follow the Session Manifest profile and current Skills.",
82
+ "Yui AgentRun input. Follow the Session Manifest profile and current Skills; use the Host check when supplied.",
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
@@ -18,13 +18,15 @@ export function withSessionContextPointer(text, environment) {
18
18
  text
19
19
  ].join("\n");
20
20
  }
21
- /** No remembered/sent flag can prove instructions survived context compaction. */
21
+ /** Host metadata checks do not prove instructions survived context compaction.
22
+ * Unmediated/native clients retain the explicit diagnostic fallback. */
22
23
  export function sessionContextInstructions(manifestPath, digest) {
23
24
  return `Yui Session Manifest: ${manifestPath}${digest === undefined ? "" : ` (digest ${digest})`}. `
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;
25
+ + "For inputs carrying the Yui Host Skill-check notice, engineering has checked current versions; do not repeat that check. "
26
+ + `Otherwise run its contextProtocol.skillCheckCommand (fallback: ${SKILL_CHECK_COMMAND}). `
27
+ + "On first entry or lost instructions, read the current applicable Skill entry points, then only references needed for the current action. "
28
+ + "On change reload affected instructions. Compaction does not require restoring every previously used procedure. "
29
+ + "Preserve the Manifest Role profile; metadata never proves loading or memory.";
28
30
  }
29
31
  /** The caller must already pass the ordinary managed Session/CLI/Home checks.
30
32
  * Old manifests remain immutable audit records, while this read resolves their
@@ -0,0 +1,53 @@
1
+ import { checkSessionSkills } from "./sessionBootstrapManifest.js";
2
+ /** Host-local last accepted metadata, never a model loading/memory receipt.
3
+ * A new Host/attachment starts cold. No durable schema or input replay is added. */
4
+ export function createSessionSkillNotice(home) {
5
+ let previous;
6
+ let generation = 0;
7
+ const invalidate = () => { previous = undefined; generation += 1; };
8
+ return {
9
+ invalidate,
10
+ prepare(text, environment, sessionKey, options = {}) {
11
+ const manifest = environment.YUI_SESSION_MANIFEST;
12
+ if (manifest === undefined)
13
+ return { text, commit() { } };
14
+ if (options.recovery)
15
+ invalidate();
16
+ let current;
17
+ try {
18
+ current = checkSessionSkills(home, manifest);
19
+ }
20
+ catch (error) {
21
+ invalidate();
22
+ throw error; // Do not submit business input with an invalid current installation.
23
+ }
24
+ const key = JSON.stringify([sessionKey, manifest, current.roleProfileRef]);
25
+ const skills = new Map(current.skills.map(skill => [skill.id, JSON.stringify({
26
+ id: skill.id, path: skill.path, version: skill.version
27
+ })]));
28
+ const cold = previous?.key !== key;
29
+ const changed = [...skills].filter(([id, value]) => cold || previous.skills.get(id) !== value)
30
+ .map(([, value]) => value);
31
+ const removed = !cold && [...previous.skills.keys()].some(id => !skills.has(id));
32
+ // Every input is checked, but only a lifecycle/metadata event needs an
33
+ // instruction-loading notice. This marker distinguishes Host-mediated
34
+ // input from an unmediated client without restating the operating manual.
35
+ const lines = ["Yui Host checked current Skills; no Agent check needed."];
36
+ if (cold || changed.length > 0 || removed) {
37
+ lines.push(`Yui Session Manifest: ${manifest}`, cold
38
+ ? `First input or recovered attachment: read the exact Role profile at ${current.roleProfileRef.path} and the full current listed Skill entry points. Follow references only for the action you are taking, not to restore every previously used procedure. A Task role-profile projection is not this profile.`
39
+ : "Skill metadata changed: load affected entries and applicable references; retain other complete instructions.", `Current Skill pointers: [${(removed ? [...skills.values()] : changed).join(",")}]`, "Metadata is not a loading receipt. Read each required file through EOF; if output is truncated, finish the missing ranges. Recover missing instructions for the current task, not the whole manual.");
40
+ }
41
+ const preparedGeneration = generation;
42
+ return {
43
+ text: `${lines.join("\n")}\n\n${text}`,
44
+ commit() {
45
+ if (generation !== preparedGeneration)
46
+ return;
47
+ previous = { key, skills };
48
+ generation += 1;
49
+ }
50
+ };
51
+ }
52
+ };
53
+ }
@@ -2,6 +2,7 @@ import { runExecutionObservation } from "../agentRun/agentRun.js";
2
2
  import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
3
3
  import { taskNotFound, usageError } from "../errors/cliError.js";
4
4
  import { expandTaskMessageResult } from "../message/message.js";
5
+ import { taskMessagePresentation } from "../interaction/notificationPresentation.js";
5
6
  import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
6
7
  import { hasManagedIdentity } from "../runtime/managedIdentity.js";
7
8
  import { providerRetrySummary } from "../runtime/providerRetry.js";
@@ -10,13 +11,21 @@ import { retiredTaskRecordIds, TASK_RECORD_RETIRED_EVENT } from "../task/taskRec
10
11
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
11
12
  import { contextContentDigest } from "./contextSnapshot.js";
12
13
  import { sourceRunContextValue } from "./sourceRunContext.js";
13
- import { boundedDocument, fitsReadBudget, summaryText, recordPage } from "../output/boundedRead.js";
14
+ import { boundedDocument, fitsReadBudget, summaryText, recordPage, readDigest } from "../output/boundedRead.js";
14
15
  import { taskBriefGuard, taskPhase } from "./taskPhase.js";
16
+ import { CONTEXT_FAMILIES } from "./contextFamilies.js";
15
17
  const MAX_RECORDS = 64;
16
18
  const MAX_VALUE_BYTES = 2048;
17
19
  const MAX_PAGE_BYTES = 24 * 1024;
18
20
  const MAX_EVENTS = 100;
19
21
  const MAX_ATTENTION_REFS = 8;
22
+ // The current read is a working index, not a replay of configuration, workspace
23
+ // inventories and requirements on every wake. Exact bodies use the same scoped,
24
+ // digest-checked inspect path regardless of how small a record happens to be.
25
+ const INDEX_ONLY_FAMILIES = new Set([
26
+ "task", "task-brief", "remote-delivery", "role", "project-policy",
27
+ "managed-workspace", "task-decision", "task-message", "run", "work-item", "project-knowledge"
28
+ ]);
20
29
  /** A current read model, not a second snapshot store or a delivery receipt.
21
30
  * Every public path checks the same scope before reading, counting or paging.
22
31
  */
@@ -44,8 +53,8 @@ export function readTaskContext(store, taskId, environment = {}) {
44
53
  if (entry.ref.store === "run")
45
54
  runtimeEvents ??= reader.listEventsByType(taskId, ["runtime.agent-error"]);
46
55
  const valueBytes = Buffer.byteLength(JSON.stringify(entry.value));
47
- const content = valueBytes > MAX_VALUE_BYTES || ["role", "task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
48
- ? { ref: entry.ref, summary: summarize(entry.value), omitted: true }
56
+ const content = valueBytes > MAX_VALUE_BYTES || INDEX_ONLY_FAMILIES.has(entry.ref.store)
57
+ ? { ref: entry.ref, summary: summarizeEntry(reader, entry), omitted: true }
49
58
  : { ...entry, omitted: false };
50
59
  const revisedAfterActivation = entry.ref.store === "task-message" && phaseSource?.ref !== undefined
51
60
  && reader.queryContextRecords(taskId, {
@@ -61,9 +70,9 @@ export function readTaskContext(store, taskId, environment = {}) {
61
70
  ? "recorded-before-activation-request" : "recorded-after-activation-request"
62
71
  } : {}),
63
72
  ...(entry.ref.store === "run" ? {
64
- execution: runExecutionObservation(entry.value, reader.getTaskRoleSessionSet(taskId, entry.value.roleName)?.providerBinding, runtimeEvents)
73
+ execution: currentRunExecution(reader, entry.value, runtimeEvents)
65
74
  } : {}), ...(entry.ref.store === "role" ? {
66
- providerRetry: providerRetrySummary(reader.getTaskRoleSessionSet(taskId, entry.value.name)?.providerBinding)
75
+ providerRetry: providerRetrySummary(reader.getTaskRoleSessionSet(taskId, entry.value.name)?.providerBinding, `yui task role session retry ${taskId} ${entry.value.name} show --json`)
67
76
  } : {}) };
68
77
  const size = Buffer.byteLength(JSON.stringify(record));
69
78
  if (bytes + size > MAX_PAGE_BYTES)
@@ -117,6 +126,15 @@ export function readTaskContext(store, taskId, environment = {}) {
117
126
  return result;
118
127
  });
119
128
  }
129
+ /** Keep live delivery/attempt facts, but do not replay retry diagnostics and
130
+ * history into the working index. Exact Run inspect below retains the full
131
+ * observation, under the same authorization and document-pagination contract.
132
+ */
133
+ function currentRunExecution(store, run, events) {
134
+ const binding = store.getTaskRoleSessionSet(run.taskId, run.roleName)?.providerBinding;
135
+ const execution = runExecutionObservation(run, binding, events);
136
+ return { ...execution, retry: execution.retry === null ? null : providerRetrySummary(binding, `yui task role session retry ${run.taskId} ${run.roleName} show --json`) };
137
+ }
120
138
  function summarizeAttention(store, taskId) {
121
139
  const group = () => ({ count: 0, refs: [], omittedRefs: 0 });
122
140
  const attention = {
@@ -208,6 +226,43 @@ export function inspectTaskContext(store, taskId, selector, environment = {}) {
208
226
  const response = readTaskContextResource(store, taskId, selector, environment);
209
227
  return boundedDocument(response, `task:${taskId}/${selector.store}/${selector.refId}`, selector.cursor);
210
228
  }
229
+ /** CLI spelling shared by explicit dispatch selection and grouped reading. */
230
+ export function parseExactContextRef(selector) {
231
+ const match = /^([^/]+)\/(.+)@([0-9a-f]{64})$/u.exec(selector);
232
+ if (match === null)
233
+ throw usageError(`Invalid required Context reference: ${selector}. Use store/refId@digest from the applicable Context pointers or list/inspect.`);
234
+ return { store: match[1], refId: match[2], digest: match[3] };
235
+ }
236
+ /** Explicit independent originals, one shared transport budget. Reuse the
237
+ * single-reference authorization/digest checks on every page. No implicit
238
+ * discovery, partial success, cached values or read/handling receipts.
239
+ */
240
+ export function inspectTaskContextBatch(store, taskId, selectors, cursor, environment = {}) {
241
+ if (!Array.isArray(selectors) || selectors.length < 1 || selectors.length > 8)
242
+ throw usageError("Context batch requires 1..8 exact references.");
243
+ const seen = new Set();
244
+ const refs = selectors.map((value) => {
245
+ if (value === null || typeof value !== "object" || Array.isArray(value))
246
+ throw usageError("Context batch reference requires store, refId and digest.");
247
+ const ref = value;
248
+ if (Object.keys(ref).some(key => !["store", "refId", "digest", "revision"].includes(key))
249
+ || typeof ref.store !== "string" || !ref.store
250
+ || typeof ref.refId !== "string" || !ref.refId
251
+ || typeof ref.digest !== "string" || !/^[a-f0-9]{64}$/.test(ref.digest)
252
+ || ref.revision !== undefined && typeof ref.revision !== "string")
253
+ throw usageError("Context batch reference requires exact store, refId and digest; revision is optional metadata.");
254
+ const key = JSON.stringify([ref.store, ref.refId]);
255
+ if (seen.has(key))
256
+ throw usageError("Duplicate Context batch reference.");
257
+ seen.add(key);
258
+ return { store: ref.store, refId: ref.refId, digest: ref.digest };
259
+ });
260
+ // All-or-error: a stale/forbidden source cannot be silently skipped. Each
261
+ // source is independently digest-pinned; this is not a new cross-record
262
+ // snapshot. A continuation is additionally bound to the complete response.
263
+ const items = refs.map(ref => readTaskContextResource(store, taskId, ref, environment));
264
+ return boundedDocument({ taskId, items }, `task:${taskId}/context-batch:${readDigest(refs)}`, cursor);
265
+ }
211
266
  /** The same authorized read as inspect, before transport pagination. */
212
267
  export function readTaskContextResource(store, taskId, selector, environment = {}) {
213
268
  return store.readTransaction((reader) => {
@@ -257,7 +312,7 @@ export function listTaskContext(store, taskId, family, options = {}, environment
257
312
  || options.workItemId !== undefined && value.workItemId !== options.workItemId
258
313
  || options.after !== undefined && Date.parse(String(value.createdAt)) <= Date.parse(options.after))
259
314
  continue;
260
- records.push({ ref: entry.ref, summary: summarize(value),
315
+ records.push({ ref: entry.ref, summary: summarizeEntry(reader, entry),
261
316
  ...(retired === undefined ? {} : { history: retired.has(entry.ref.refId) ? "retired" : "active" }),
262
317
  ...Object.fromEntries(["status", "kind", "type", "createdAt", "roleName", "assignee", "purpose", "workItemId", "reviewRoundId"]
263
318
  .filter(key => typeof value[key] === "string").map(key => [key, value[key]])) });
@@ -269,13 +324,6 @@ export function listTaskContext(store, taskId, family, options = {}, environment
269
324
  })}`, options);
270
325
  });
271
326
  }
272
- export const CONTEXT_FAMILIES = [
273
- "task", "task-brief", "remote-delivery", "role", "role-profile",
274
- "project-policy", "project-knowledge", "input-request", "work-item",
275
- "accepted-work-item", "candidate", "environment-preparation", "managed-workspace",
276
- "change-set", "task-message", "run", "source-run", "review-round", "mailbox",
277
- "task-decision", "task-milestone", "job", "publication", "task-event"
278
- ];
279
327
  export function listContextMessages(store, taskId, environment = {}) {
280
328
  return store.readTransaction((reader) => {
281
329
  authorizeContext(reader, taskId, environment);
@@ -478,11 +526,21 @@ function authorizedSources(store, { task, caller }, current = false, requestedFa
478
526
  return;
479
527
  const selection = { ...options, family };
480
528
  const { count } = store.queryContextRecords(taskId, { ...selection, limit: 0 });
529
+ // The bounded current index must not bury Task main behind alphabetically
530
+ // earlier integration/review history. Full discovery keeps its stable order.
531
+ const taskWorkspaceKey = managedWorkspaceKey({ type: "task", taskId });
532
+ const preferredWorkspace = current && !discovery && family === "managed-workspace"
533
+ ? store.getManagedWorkspace({ type: "task", taskId }) : null;
481
534
  sources.push({ family, count, *read(offset = 0) {
535
+ if (preferredWorkspace !== null)
536
+ yield materialize(family, taskWorkspaceKey, preferredWorkspace);
482
537
  for (const { id, value } of store.queryContextRecords(taskId, {
483
538
  ...selection, offset, limit: current && !discovery ? 8 : MAX_RECORDS
484
- }).records)
539
+ }).records) {
540
+ if (preferredWorkspace !== null && id === taskWorkspaceKey)
541
+ continue;
485
542
  yield materialize(family, id, value);
543
+ }
486
544
  } });
487
545
  };
488
546
  add("task", taskId, task);
@@ -517,10 +575,13 @@ function authorizedSources(store, { task, caller }, current = false, requestedFa
517
575
  // artifacts remain reachable as commit-pinned pointers under their Candidate.
518
576
  if (!current)
519
577
  query("environment-preparation");
520
- query("managed-workspace");
578
+ if (!current || discovery)
579
+ query("managed-workspace");
521
580
  if (caller?.taskId === taskId && caller.currentRunId !== undefined) {
522
581
  add("managed-workspace", `${taskId}/${caller.roleName}`, store.getRun(taskId, caller.currentRunId)?.workspace ?? null);
523
582
  }
583
+ if (current && !discovery)
584
+ query("managed-workspace");
524
585
  if (!current)
525
586
  query("change-set");
526
587
  query("task-message", { descending: true, unhandledMessages: current });
@@ -594,8 +655,31 @@ function freeze(value) {
594
655
  }
595
656
  function summarize(value) {
596
657
  const record = value;
597
- return summaryText(["title", "name", "activeAgentId", "scope", "expiresWhen", "version", "objective", "question", "summary", "body", "status", "type"]
658
+ return summaryText(["title", "name", "status", "type", "currentFocus", "root", "activeAgentId", "scope", "expiresWhen", "version", "objective", "question", "summary", "body"]
598
659
  .flatMap((key) => typeof record[key] === "string" || (key === "version" && typeof record[key] === "number")
599
660
  ? [`${key}: ${record[key]}`] : [])
600
661
  .join("; "));
601
662
  }
663
+ /** Reuse producer-authored navigation, never generate a verdict or copy a
664
+ * report. The authorized Message already permits its matching terminal result
665
+ * through exact inspect; unrelated/malformed links cannot supply summaries.
666
+ */
667
+ function summarizeEntry(store, entry) {
668
+ if (entry.ref.store === "task-message") {
669
+ const message = entry.value;
670
+ const presentation = taskMessagePresentation(message, (taskId, runId) => {
671
+ const run = store.getRun(taskId, runId);
672
+ return run !== null && run.taskId === message.taskId
673
+ && message.author.type === "role" && run.roleName === message.author.roleName
674
+ && run.status !== "active" && run.result !== undefined ? run : null;
675
+ });
676
+ if (presentation.summary !== undefined) {
677
+ // Include attribution in the existing budget without clipping qualifiers.
678
+ const label = `Author summary (${presentation.summarySource})`;
679
+ const text = `${label}: ${presentation.summary}`;
680
+ return Buffer.byteLength(text) <= 512 ? text
681
+ : `${label} omitted: exceeds discovery budget; read original.`;
682
+ }
683
+ }
684
+ return summarize(entry.value);
685
+ }
@@ -4,6 +4,7 @@ import { randomUUID } from "node:crypto";
4
4
  import { readFileSync, realpathSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
6
  import { createInterface } from "node:readline/promises";
7
+ import { isatty } from "node:tty";
7
8
  import { fileURLToPath } from "node:url";
8
9
  import { isDeepStrictEqual } from "node:util";
9
10
  import { agentAdapterLabel as adapterLabel } from "./agent/adapterCatalog.js";
@@ -11,14 +12,14 @@ import { recordTaskInterruptResult } from "./message/taskInterrupt.js";
11
12
  import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
12
13
  import { listArtifactsCapability, readArtifactCapability, saveArtifactCapability } from "./artifacts/artifactCapability.js";
13
14
  import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
14
- import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
15
+ import { describeCommandHelp, describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
15
16
  import { discoveryCommandTree } from "./cli/commandDiscovery.js";
16
17
  import { callerDiscoveryAudience } from "./cli/callerDiscovery.js";
17
18
  import { assertTaskReadable } from "./context/taskContext.js";
18
19
  import { renderCompletion } from "./cli/completion.js";
19
20
  import { runCompletionWizard } from "./cli/completionWizard.js";
20
21
  import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
21
- import { renderCommandHelp } from "./cli/helpRenderer.js";
22
+ import { renderCommandHelp, renderCommandPathHint } from "./cli/helpRenderer.js";
22
23
  import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/interactiveSelection.js";
23
24
  import { routeInvocation } from "./cli/invocationRouter.js";
24
25
  import { operatorOfflineCommand, isTaskReadCommand } from "./cli/managedDiagnostics.js";
@@ -202,7 +203,7 @@ export async function main() {
202
203
  return;
203
204
  }
204
205
  if (args.length === 0) {
205
- emit(renderCommandHelp(discovery, VERSION), false, jsonOutput ? describeCommandTree(discovery) : undefined);
206
+ emit(renderCommandHelp(discovery, VERSION), false, jsonOutput ? describeCommandHelp(discovery) : undefined);
206
207
  return;
207
208
  }
208
209
  if (args[0] === "version" && args.length === 1) {
@@ -211,11 +212,19 @@ export async function main() {
211
212
  }
212
213
  const invocation = routeInvocation(args, discovery);
213
214
  if (invocation.kind === "help") {
214
- emit(renderCommandHelp(invocation.node, VERSION), true, jsonOutput ? describeCommandTree(invocation.node) : undefined);
215
+ if (invocation.recursive && !jsonOutput)
216
+ throw usageError("Recursive help requires --json.");
217
+ emit(renderCommandHelp(invocation.node, VERSION), true, jsonOutput
218
+ ? invocation.recursive ? describeCommandTree(invocation.node) : describeCommandHelp(invocation.node)
219
+ : undefined);
215
220
  return;
216
221
  }
217
222
  if (invocation.kind === "path-error") {
218
- throw usageError(`Unknown command: ${invocation.typedPath}`, renderCommandHelp(invocation.helpNode, VERSION));
223
+ throw usageError(invocation.discoveryOnly
224
+ ? `Command not listed in the current caller's help view: ${invocation.typedPath}. `
225
+ + "Help is filtered discovery, not an execution or permission check. "
226
+ + "Follow current instructions and exact read links; execution still validates authority."
227
+ : `Unknown command: ${invocation.typedPath}`, renderCommandPathHint(invocation.helpNode));
219
228
  }
220
229
  if (invocation.kind === "incomplete") {
221
230
  throw usageError(`Command required after: ${invocation.typedPath}`, renderCommandHelp(invocation.helpNode, VERSION));
@@ -660,7 +669,6 @@ export async function main() {
660
669
  const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), executor, {
661
670
  yuiHome: home,
662
671
  historyLimit: resolveTmuxHistoryLimit(store.getConfig().tmuxHistoryLimit),
663
- terminalInput: process.stdin,
664
672
  onWarning: (message) => writeCliWarning(message)
665
673
  });
666
674
  const telemetry = openSchedulerTelemetry(home, store.getConfig());
@@ -2750,7 +2758,9 @@ function completionShell(value) {
2750
2758
  throw usageError("Completion shell must be one of bash, zsh, fish.");
2751
2759
  }
2752
2760
  async function resolveTerminalArguments(commandArgs, node, store, catalogs) {
2753
- const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
2761
+ // Probe the descriptor without constructing a stdin stream: Node makes
2762
+ // pipes nonblocking on stream creation, breaking later --body-file - reads.
2763
+ const interactive = isatty(0) && process.stdout.isTTY === true;
2754
2764
  if (!interactive || !allowsInteractiveSelection(commandArgs, jsonOutput)) {
2755
2765
  return [...commandArgs];
2756
2766
  }