@zq-silk/yui 2.20.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 (142) hide show
  1. package/README.md +5 -0
  2. package/dist/cli/commandCatalog.js +88 -28
  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/invocationAuthority.js +2 -6
  7. package/dist/cli/invocationRouter.js +5 -5
  8. package/dist/cli/managedDiagnostics.js +24 -16
  9. package/dist/cli/roleWizard.js +5 -1
  10. package/dist/commands/durableJobCommands.js +2 -3
  11. package/dist/commands/globalRoleCommands.js +33 -8
  12. package/dist/commands/inboxCommands.js +39 -12
  13. package/dist/commands/operatorCommands.js +2 -5
  14. package/dist/commands/releaseCommands.js +16 -2
  15. package/dist/commands/roleConfiguration.js +25 -2
  16. package/dist/commands/sessionCommands.js +5 -2
  17. package/dist/commands/taskCommands.js +225 -99
  18. package/dist/commands/taskContextCommand.js +26 -4
  19. package/dist/commands/taskFactCommands.js +20 -7
  20. package/dist/commands/taskIntegrationCommands.js +3 -3
  21. package/dist/commands/taskRoleRuntimeStatus.js +15 -5
  22. package/dist/commands/taskUsageCommand.js +2 -6
  23. package/dist/commands/textInput.js +2 -2
  24. package/dist/context/contextFamilies.js +10 -0
  25. package/dist/context/currentSkills.js +6 -4
  26. package/dist/context/runContextPack.js +44 -5
  27. package/dist/context/runInputContract.js +1 -1
  28. package/dist/context/sessionBootstrapManifest.js +7 -5
  29. package/dist/context/sessionSkillNotice.js +53 -0
  30. package/dist/context/taskCatalog.js +4 -13
  31. package/dist/context/taskContext.js +135 -103
  32. package/dist/context/taskSearch.js +3 -7
  33. package/dist/controlPlaneCli.js +23 -25
  34. package/dist/controller/agentRuntimeObserver.js +57 -12
  35. package/dist/controller/clientRuntime.js +37 -8
  36. package/dist/controller/controller.js +3 -1
  37. package/dist/controller/diagnosticJournal.js +10 -2
  38. package/dist/controller/fileSchedulerStoreAdapter.js +72 -24
  39. package/dist/controller/jobClient.js +2 -2
  40. package/dist/controller/jobControl.js +2 -2
  41. package/dist/controller/jobDefinitionControl.js +5 -1
  42. package/dist/controller/messageHealth.js +4 -3
  43. package/dist/controller/providerRetryAdmission.js +15 -1
  44. package/dist/controller/providerRetryDelivery.js +18 -2
  45. package/dist/controller/runtime.js +15 -5
  46. package/dist/controller/runtimeEventInbox.js +107 -13
  47. package/dist/controller/runtimeEventProcessor.js +3 -3
  48. package/dist/controller/taskRunRecovery.js +153 -0
  49. package/dist/core/controllerClient.js +16 -2
  50. package/dist/core/controllerServer.js +14 -5
  51. package/dist/core/controllerTelemetry.js +51 -5
  52. package/dist/errors/cliFailure.js +14 -5
  53. package/dist/executor/agentAdapter.js +32 -4
  54. package/dist/executor/agentConfigurationFields.js +2 -0
  55. package/dist/executor/taskAgentCapabilities.js +2 -2
  56. package/dist/integration/gitIntegrationService.js +3 -2
  57. package/dist/integration/integrationSourceApplication.js +12 -1
  58. package/dist/interaction/notificationPresentation.js +11 -16
  59. package/dist/interaction/operatorPresentation.js +1 -1
  60. package/dist/message/messageContinuation.js +3 -1
  61. package/dist/output/boundedRead.js +36 -7
  62. package/dist/output/rolePresentation.js +3 -1
  63. package/dist/runtime/agentHost.js +12 -3
  64. package/dist/runtime/codexAppServerRuntime.js +110 -24
  65. package/dist/runtime/codexInteractiveHost.js +46 -0
  66. package/dist/runtime/providerRetry.js +5 -2
  67. package/dist/runtime/providerRuntimeIdentity.js +1 -1
  68. package/dist/runtime/structuredProviderHost.js +32 -34
  69. package/dist/scheduler/wakeupQueue.js +10 -4
  70. package/dist/storage/sqliteSchema.js +9 -0
  71. package/dist/storage/storageVersions.js +1 -1
  72. package/dist/task/taskAuthority.js +7 -3
  73. package/dist/tmux/tmuxManager.js +4 -2
  74. package/dist/web/webSettings.js +2 -0
  75. package/docs/authorization.md +18 -3
  76. package/docs/cli-information-contract.md +245 -6
  77. package/docs/cli-information-contract.zh-CN.md +107 -5
  78. package/docs/managed-turn-and-session-runtime.md +82 -21
  79. package/docs/managed-turn-and-session-runtime.zh-CN.md +56 -13
  80. package/docs/provider-retry.md +11 -0
  81. package/docs/release-workflow.md +62 -0
  82. package/docs/release-workflow.zh-CN.md +46 -0
  83. package/docs/roles-and-configuration.md +28 -0
  84. package/docs/roles-and-configuration.zh-CN.md +21 -0
  85. package/docs/storage-baseline.md +4 -1
  86. package/docs/storage-baseline.zh-CN.md +1 -1
  87. package/docs/task-delivery.md +7 -0
  88. package/docs/task-delivery.zh-CN.md +5 -0
  89. package/docs/task-discovery.md +5 -5
  90. package/docs/task-discovery.zh-CN.md +4 -4
  91. package/docs/testing/review-evaluation.md +7 -7
  92. package/docs/testing/review-evaluation.zh-CN.md +7 -7
  93. package/docs/testing/verification-levels.md +45 -2
  94. package/docs/testing/verification-levels.zh-CN.md +21 -2
  95. package/i18n/README.zh-CN.md +6 -0
  96. package/package.json +1 -1
  97. package/skills/yui-leader/SKILL.md +98 -189
  98. package/skills/yui-leader/VERSION +1 -1
  99. package/skills/yui-leader/references/artifacts.md +41 -0
  100. package/skills/yui-leader/references/assignment-execution.md +140 -0
  101. package/skills/yui-leader/references/completion.md +79 -0
  102. package/skills/yui-leader/references/delivery-scope.md +26 -0
  103. package/skills/yui-leader/references/execution.md +16 -487
  104. package/skills/yui-leader/references/implementation.md +81 -0
  105. package/skills/yui-leader/references/planning.md +7 -45
  106. package/skills/yui-leader/references/review.md +90 -0
  107. package/skills/yui-leader/references/task-progress.md +23 -0
  108. package/skills/yui-leader/references/work-allocation.md +118 -0
  109. package/skills/yui-operator/SKILL.md +77 -331
  110. package/skills/yui-operator/VERSION +1 -1
  111. package/skills/yui-operator/references/body-transport.md +62 -0
  112. package/skills/yui-operator/references/configuration.md +47 -0
  113. package/skills/yui-operator/references/lifecycle.md +41 -0
  114. package/skills/yui-operator/references/progress.md +39 -0
  115. package/skills/yui-operator/references/routing.md +57 -0
  116. package/skills/yui-operator/references/submission.md +56 -0
  117. package/skills/yui-operator/references/task-delivery.md +5 -2
  118. package/skills/yui-operator/references/web-lifecycle.md +19 -0
  119. package/skills/yui-operator/scripts/verify-submission.mjs +37 -0
  120. package/skills/yui-reviewer/SKILL.md +4 -2
  121. package/skills/yui-reviewer/VERSION +1 -1
  122. package/skills/yui-runtime/SKILL.md +177 -505
  123. package/skills/yui-runtime/VERSION +1 -1
  124. package/skills/yui-runtime/references/archive.md +51 -0
  125. package/skills/yui-runtime/references/command-discovery.md +41 -0
  126. package/skills/yui-runtime/references/completed-followup.md +65 -0
  127. package/skills/yui-runtime/references/context-ordering.md +3 -34
  128. package/skills/yui-runtime/references/context-reads.md +61 -0
  129. package/skills/yui-runtime/references/dispatched-run.md +61 -0
  130. package/skills/yui-runtime/references/execution-failure.md +105 -0
  131. package/skills/yui-runtime/references/global-context.md +18 -0
  132. package/skills/yui-runtime/references/global-input-controls.md +44 -0
  133. package/skills/yui-runtime/references/host-custody.md +30 -0
  134. package/skills/yui-runtime/references/inbox-recovery.md +77 -0
  135. package/skills/yui-runtime/references/incremental-context.md +40 -0
  136. package/skills/yui-runtime/references/publication.md +4 -104
  137. package/skills/yui-runtime/references/recovery.md +32 -192
  138. package/skills/yui-runtime/references/session-identity.md +18 -0
  139. package/skills/yui-runtime/references/session-replacement.md +45 -0
  140. package/skills/yui-runtime/references/uncertain-execution.md +29 -0
  141. package/skills/yui-worker/SKILL.md +7 -4
  142. package/skills/yui-worker/VERSION +1 -1
@@ -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";
@@ -9,15 +10,22 @@ import { projectTaskRemoteDeliveryFromStore } from "../task/remoteDeliveryServic
9
10
  import { retiredTaskRecordIds, TASK_RECORD_RETIRED_EVENT } from "../task/taskRecordRetirement.js";
10
11
  import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
11
12
  import { contextContentDigest } from "./contextSnapshot.js";
12
- import { buildRunContextPack } from "./runContextPack.js";
13
13
  import { sourceRunContextValue } from "./sourceRunContext.js";
14
- import { boundedDocument, fitsReadBudget, summaryText, recordPage } from "../output/boundedRead.js";
14
+ import { boundedDocument, fitsReadBudget, summaryText, recordPage, readDigest } from "../output/boundedRead.js";
15
15
  import { taskBriefGuard, taskPhase } from "./taskPhase.js";
16
+ import { CONTEXT_FAMILIES } from "./contextFamilies.js";
16
17
  const MAX_RECORDS = 64;
17
18
  const MAX_VALUE_BYTES = 2048;
18
19
  const MAX_PAGE_BYTES = 24 * 1024;
19
20
  const MAX_EVENTS = 100;
20
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
+ ]);
21
29
  /** A current read model, not a second snapshot store or a delivery receipt.
22
30
  * Every public path checks the same scope before reading, counting or paging.
23
31
  */
@@ -27,12 +35,12 @@ export function readTaskContext(store, taskId, environment = {}) {
27
35
  const sources = authorizedSources(reader, scope, true);
28
36
  const count = sources.reduce((total, source) => total + source.count, 0);
29
37
  const cursor = currentCursor(reader, taskId);
30
- const phase = scope.allow === undefined ? taskPhase(reader, scope.task) : undefined;
38
+ const phase = taskPhase(reader, scope.task);
31
39
  const phaseSource = phase?.activation?.source;
32
40
  const priorMessageId = phaseSource?.priorMessageId;
33
41
  // Reserve a small, classified view before ordinary history consumes the
34
42
  // page. Counts and samples come from the exact same authorized read.
35
- const attention = summarizeAttention(reader, taskId, scope.allow);
43
+ const attention = summarizeAttention(reader, taskId);
36
44
  let bytes = Buffer.byteLength(JSON.stringify(attention));
37
45
  const records = [];
38
46
  let runtimeEvents;
@@ -45,8 +53,8 @@ export function readTaskContext(store, taskId, environment = {}) {
45
53
  if (entry.ref.store === "run")
46
54
  runtimeEvents ??= reader.listEventsByType(taskId, ["runtime.agent-error"]);
47
55
  const valueBytes = Buffer.byteLength(JSON.stringify(entry.value));
48
- const content = valueBytes > MAX_VALUE_BYTES || ["role", "task-message", "run", "work-item", "project-knowledge"].includes(entry.ref.store)
49
- ? { 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 }
50
58
  : { ...entry, omitted: false };
51
59
  const revisedAfterActivation = entry.ref.store === "task-message" && phaseSource?.ref !== undefined
52
60
  && reader.queryContextRecords(taskId, {
@@ -62,9 +70,9 @@ export function readTaskContext(store, taskId, environment = {}) {
62
70
  ? "recorded-before-activation-request" : "recorded-after-activation-request"
63
71
  } : {}),
64
72
  ...(entry.ref.store === "run" ? {
65
- execution: runExecutionObservation(entry.value, reader.getTaskRoleSessionSet(taskId, entry.value.roleName)?.providerBinding, runtimeEvents)
73
+ execution: currentRunExecution(reader, entry.value, runtimeEvents)
66
74
  } : {}), ...(entry.ref.store === "role" ? {
67
- 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`)
68
76
  } : {}) };
69
77
  const size = Buffer.byteLength(JSON.stringify(record));
70
78
  if (bytes + size > MAX_PAGE_BYTES)
@@ -75,13 +83,13 @@ export function readTaskContext(store, taskId, environment = {}) {
75
83
  const result = {
76
84
  taskId, coreCursor: encode(cursor), throughCursor: encode(cursor),
77
85
  view: "current",
78
- ...(scope.allow === undefined ? {
79
- phase,
80
- briefWriteGuard: taskBriefGuard(reader, scope.task)
81
- } : {}),
86
+ phase,
87
+ briefWriteGuard: taskBriefGuard(reader, scope.task),
82
88
  caller: scope.caller === undefined ? { kind: "unmanaged-or-global" } : {
89
+ taskId: scope.caller.taskId,
83
90
  role: scope.caller.roleName,
84
- executionAuthority: currentManagedRuntime(reader, environment, taskId)?.executionAuthority ?? "historical-read-only"
91
+ executionAuthority: scope.caller.taskId !== taskId ? "reference-only"
92
+ : currentManagedRuntime(reader, environment, taskId)?.executionAuthority ?? "historical-read-only"
85
93
  },
86
94
  collections: Object.entries(sources.reduce((counts, source) => {
87
95
  counts[source.family] = (counts[source.family] ?? 0) + source.count;
@@ -118,7 +126,16 @@ export function readTaskContext(store, taskId, environment = {}) {
118
126
  return result;
119
127
  });
120
128
  }
121
- function summarizeAttention(store, taskId, allow) {
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
+ }
138
+ function summarizeAttention(store, taskId) {
122
139
  const group = () => ({ count: 0, refs: [], omittedRefs: 0 });
123
140
  const attention = {
124
141
  openInputs: group(), pendingOperations: group(), unknownOperations: group(), messages: group()
@@ -131,7 +148,7 @@ function summarizeAttention(store, taskId, allow) {
131
148
  ]) {
132
149
  const page = store.queryContextRecords(taskId, {
133
150
  family, statuses, descending: category === "messages",
134
- unhandledMessages: category === "messages", ids: allowedIds(allow, family), limit: MAX_ATTENTION_REFS
151
+ unhandledMessages: category === "messages", limit: MAX_ATTENTION_REFS
135
152
  });
136
153
  const summary = attention[category];
137
154
  summary.count = page.count;
@@ -154,7 +171,7 @@ function summarizeAttention(store, taskId, allow) {
154
171
  */
155
172
  export function readTaskContextDelta(store, taskId, input, environment = {}) {
156
173
  return store.readTransaction((reader) => {
157
- const { allow } = authorizeContext(reader, taskId, environment);
174
+ authorizeContext(reader, taskId, environment);
158
175
  const start = decode(input.after, taskId);
159
176
  const now = currentCursor(reader, taskId);
160
177
  const continuation = input.continuation === undefined ? undefined : decodePage(input.continuation, taskId);
@@ -174,7 +191,7 @@ export function readTaskContextDelta(store, taskId, input, environment = {}) {
174
191
  throw usageError(`Context delta limit must be between 1 and ${MAX_EVENTS}.`);
175
192
  }
176
193
  const selected = reader.queryContextRecords(taskId, {
177
- family: "task-event", ids: allowedIds(allow, "task-event"),
194
+ family: "task-event",
178
195
  afterSequence: after, throughSequence: bound.sequence, limit
179
196
  });
180
197
  const page = [];
@@ -209,12 +226,48 @@ export function inspectTaskContext(store, taskId, selector, environment = {}) {
209
226
  const response = readTaskContextResource(store, taskId, selector, environment);
210
227
  return boundedDocument(response, `task:${taskId}/${selector.store}/${selector.refId}`, selector.cursor);
211
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
+ }
212
266
  /** The same authorized read as inspect, before transport pagination. */
213
267
  export function readTaskContextResource(store, taskId, selector, environment = {}) {
214
268
  return store.readTransaction((reader) => {
215
269
  const scope = authorizeContext(reader, taskId, environment);
216
- const value = isAllowed(scope.allow, selector.store, selector.refId)
217
- ? inspectValue(reader, scope, selector) : null;
270
+ const value = inspectValue(reader, scope, selector);
218
271
  if (value === null)
219
272
  throw usageError("Context reference is unavailable in the caller's current scope.");
220
273
  const entry = materialize(selector.store, selector.refId, value);
@@ -259,7 +312,7 @@ export function listTaskContext(store, taskId, family, options = {}, environment
259
312
  || options.workItemId !== undefined && value.workItemId !== options.workItemId
260
313
  || options.after !== undefined && Date.parse(String(value.createdAt)) <= Date.parse(options.after))
261
314
  continue;
262
- records.push({ ref: entry.ref, summary: summarize(value),
315
+ records.push({ ref: entry.ref, summary: summarizeEntry(reader, entry),
263
316
  ...(retired === undefined ? {} : { history: retired.has(entry.ref.refId) ? "retired" : "active" }),
264
317
  ...Object.fromEntries(["status", "kind", "type", "createdAt", "roleName", "assignee", "purpose", "workItemId", "reviewRoundId"]
265
318
  .filter(key => typeof value[key] === "string").map(key => [key, value[key]])) });
@@ -271,18 +324,10 @@ export function listTaskContext(store, taskId, family, options = {}, environment
271
324
  })}`, options);
272
325
  });
273
326
  }
274
- export const CONTEXT_FAMILIES = [
275
- "task", "task-brief", "remote-delivery", "role", "role-profile",
276
- "project-policy", "project-knowledge", "input-request", "work-item",
277
- "accepted-work-item", "candidate", "environment-preparation", "managed-workspace",
278
- "change-set", "task-message", "run", "source-run", "review-round", "mailbox",
279
- "task-decision", "task-milestone", "job", "publication", "task-event"
280
- ];
281
327
  export function listContextMessages(store, taskId, environment = {}) {
282
328
  return store.readTransaction((reader) => {
283
- const { allow } = authorizeContext(reader, taskId, environment);
329
+ authorizeContext(reader, taskId, environment);
284
330
  return reader.listMessages(taskId)
285
- .filter((message) => isAllowed(allow, "task-message", message.id))
286
331
  .sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
287
332
  });
288
333
  }
@@ -334,7 +379,11 @@ export async function withContextObservations(core, providers, timeoutMs = 250)
334
379
  }
335
380
  return { ...result, omittedObservations: observations.length - result.observations.length };
336
381
  }
337
- /** Shared current/historical Session read identity; discovery may only narrow it. */
382
+ /** Authenticate ordinary reads of the local user's resource. Task/Assignment
383
+ * ownership is execution scope, not a confidentiality boundary. This does not
384
+ * switch Home, impersonate a target Role, or relax explicit global Job limits.
385
+ * Filesystem access and the authenticated Web/Controller transport remain the
386
+ * resource boundary of this single-user local product. */
338
387
  export function resolveContextReader(store, environment) {
339
388
  const caller = resolveManagedTaskReader(store, environment);
340
389
  if (caller === undefined && environment.YUI_SESSION_SCOPE === "global" && environment.YUI_ROLE !== "operator") {
@@ -351,57 +400,14 @@ export function resolveContextReader(store, environment) {
351
400
  }
352
401
  function authorizeContext(store, taskId, environment) {
353
402
  const caller = resolveContextReader(store, environment);
354
- if (caller !== undefined && caller.taskId !== taskId)
355
- throw usageError("Context is outside the caller's Task.");
356
403
  const task = store.getTask(taskId);
357
404
  if (task === null)
358
405
  throw taskNotFound(taskId);
359
- let allow;
360
- if (caller !== undefined && caller.roleName !== "leader") {
361
- if (caller.currentRunId === undefined)
362
- throw usageError("A managed Role needs a current AgentRun to read its scoped Context.");
363
- const pack = buildRunContextPack(store, taskId, caller.currentRunId);
364
- allow = new Set(pack.pointers.map((ref) => `${ref.store}:${ref.refId}`));
365
- allow.add(`role:${caller.roleName}`);
366
- allow.add(`turn:${caller.currentRunId}`);
367
- // Shared current user intent and this exact Assignment's live steers augment
368
- // the frozen refs. Query identities only: another Role's results, ordinary
369
- // addressed messages and Leader steers remain outside this scope.
370
- const run = store.getRun(taskId, caller.currentRunId);
371
- const current = store.contextInputReferences(taskId, {
372
- roleName: caller.roleName, workItemId: run?.workItemId, reviewRoundId: run?.reviewRoundId
373
- });
374
- for (const id of current.messages)
375
- allow.add(`task-message:${id}`);
376
- for (const id of current.events)
377
- allow.add(`task-event:${id}`);
378
- if (run?.workItemId !== undefined) {
379
- for (const job of store.listDurableJobs(taskId)) {
380
- if (job.owner.kind === "work-item" && job.owner.workItemId === run.workItemId)
381
- allow.add(`job:${job.id}`);
382
- }
383
- }
384
- }
385
- return { task, allow, caller };
386
- }
387
- /** Independent record commands share Context's exact read boundary, not a
388
- * second role-based approximation of Assignment visibility. */
389
- export function contextRecordReader(store, taskId, environment = {}) {
390
- const { allow } = authorizeContext(store, taskId, environment);
391
- return (family, id) => isAllowed(allow, family, id);
392
- }
393
- export function assertContextRecordReadable(store, taskId, family, id, environment = {}) {
394
- if (!contextRecordReader(store, taskId, environment)(family, id)) {
395
- throw usageError("Context reference is outside the caller's Assignment.");
396
- }
397
- }
398
- function isAllowed(allow, family, id) {
399
- return allow === undefined || allow.has(`${family}:${id}`);
406
+ return { task, caller };
400
407
  }
401
- function allowedIds(allow, family) {
402
- const prefix = `${family}:`;
403
- return allow === undefined ? undefined : [...allow]
404
- .filter(ref => ref.startsWith(prefix)).map(ref => ref.slice(prefix.length));
408
+ /** Independent record commands authenticate against the same resource boundary. */
409
+ export function assertTaskReadable(store, taskId, environment = {}) {
410
+ authorizeContext(store, taskId, environment);
405
411
  }
406
412
  function roleProfile(role) {
407
413
  return {
@@ -413,12 +419,12 @@ function roleProfile(role) {
413
419
  /** Resolve just the requested family. Families without a singular Store read
414
420
  * use their existing list primitive, without constructing unrelated Context.
415
421
  */
416
- function inspectValue(store, { task, allow, caller }, { store: family, refId }) {
422
+ function inspectValue(store, { task, caller }, { store: family, refId }) {
417
423
  const taskId = task.id;
418
424
  switch (family) {
419
425
  case "task": return refId === taskId ? task : null;
420
426
  case "task-brief": return refId === taskId ? store.getTaskBrief(taskId) : null;
421
- case "remote-delivery": return refId === taskId && allow === undefined
427
+ case "remote-delivery": return refId === taskId
422
428
  ? projectTaskRemoteDeliveryFromStore(store, task) : null;
423
429
  case "role": return store.getRole(taskId, refId);
424
430
  case "role-profile": {
@@ -442,8 +448,10 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
442
448
  }
443
449
  case "input-request": return store.getInputRequest(taskId, refId);
444
450
  case "work-item": return store.getWorkItem(taskId, refId);
445
- case "accepted-work-item":
446
- return allow?.has(`accepted-work-item:${refId}`) ? store.getWorkItem(taskId, refId) : null;
451
+ case "accepted-work-item": {
452
+ const item = store.getWorkItem(taskId, refId);
453
+ return item?.status === "accepted" ? item : null;
454
+ }
447
455
  case "candidate": {
448
456
  const parts = refId.split("/");
449
457
  if (parts.length !== 2)
@@ -457,8 +465,6 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
457
465
  return store.queryContextRecords(taskId, { family, ids: [refId], limit: 1 }).records[0]?.value ?? null;
458
466
  case "run": return store.getRun(taskId, refId);
459
467
  case "source-run": {
460
- if (!allow?.has(`source-turn:${refId}`))
461
- return null;
462
468
  const run = store.getRun(taskId, refId);
463
469
  return run === null ? null : sourceRunContextValue(run);
464
470
  }
@@ -488,15 +494,13 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
488
494
  case "managed-workspace": {
489
495
  // Existing frozen AgentRun overlays use the Task/Role alias; owner keys
490
496
  // identify the durable workspace in current Task reads.
491
- if (caller?.currentRunId !== undefined && refId === `${taskId}/${caller.roleName}`) {
497
+ if (caller?.taskId === taskId && caller.currentRunId !== undefined && refId === `${taskId}/${caller.roleName}`) {
492
498
  return store.getRun(taskId, caller.currentRunId)?.workspace ?? null;
493
499
  }
494
500
  return store.listManagedWorkspaces(taskId)
495
501
  .find((workspace) => managedWorkspaceKey(workspace.owner) === refId) ?? null;
496
502
  }
497
503
  case "mailbox": {
498
- if (allow !== undefined)
499
- return null;
500
504
  if (refId === "task")
501
505
  return store.getWorkMailbox({ kind: "task", taskId });
502
506
  const roleName = refId.startsWith("role:") ? refId.slice("role:".length) : undefined;
@@ -509,29 +513,39 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
509
513
  default: return null;
510
514
  }
511
515
  }
512
- function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily, discovery = false) {
516
+ function authorizedSources(store, { task, caller }, current = false, requestedFamily, discovery = false) {
513
517
  const taskId = task.id;
514
518
  const sources = [];
515
519
  const add = (family, id, value) => {
516
- if ((requestedFamily === undefined || requestedFamily === family) && value !== null && isAllowed(allow, family, id)) {
520
+ if ((requestedFamily === undefined || requestedFamily === family) && value !== null) {
517
521
  sources.push({ family, count: 1, read: () => [materialize(family, id, value)] });
518
522
  }
519
523
  };
520
524
  const query = (family, options = {}) => {
521
525
  if (requestedFamily !== undefined && requestedFamily !== family)
522
526
  return;
523
- const selection = { ...options, family, ids: allowedIds(allow, family) };
527
+ const selection = { ...options, family };
524
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;
525
534
  sources.push({ family, count, *read(offset = 0) {
535
+ if (preferredWorkspace !== null)
536
+ yield materialize(family, taskWorkspaceKey, preferredWorkspace);
526
537
  for (const { id, value } of store.queryContextRecords(taskId, {
527
538
  ...selection, offset, limit: current && !discovery ? 8 : MAX_RECORDS
528
- }).records)
539
+ }).records) {
540
+ if (preferredWorkspace !== null && id === taskWorkspaceKey)
541
+ continue;
529
542
  yield materialize(family, id, value);
543
+ }
530
544
  } });
531
545
  };
532
546
  add("task", taskId, task);
533
547
  add("task-brief", taskId, store.getTaskBrief(taskId));
534
- if (allow === undefined && (requestedFamily === undefined || requestedFamily === "remote-delivery")) {
548
+ if (requestedFamily === undefined || requestedFamily === "remote-delivery") {
535
549
  add("remote-delivery", taskId, projectTaskRemoteDeliveryFromStore(store, task));
536
550
  }
537
551
  for (const role of store.listRoles(taskId)) {
@@ -552,9 +566,6 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
552
566
  // Current responsibilities and unanswered inputs precede historical bulk.
553
567
  query("input-request", { statuses: ["open"] });
554
568
  query("work-item", current ? { excludeStatuses: ["accepted", "retired"] } : {});
555
- for (const id of allowedIds(allow, "accepted-work-item") ?? []) {
556
- add("accepted-work-item", id, store.getWorkItem(taskId, id));
557
- }
558
569
  if (!current)
559
570
  query("candidate");
560
571
  // File artifacts are not enumerated into the synchronous Context listing: the
@@ -564,23 +575,21 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
564
575
  // artifacts remain reachable as commit-pinned pointers under their Candidate.
565
576
  if (!current)
566
577
  query("environment-preparation");
567
- query("managed-workspace");
568
- if (caller?.currentRunId !== undefined) {
578
+ if (!current || discovery)
579
+ query("managed-workspace");
580
+ if (caller?.taskId === taskId && caller.currentRunId !== undefined) {
569
581
  add("managed-workspace", `${taskId}/${caller.roleName}`, store.getRun(taskId, caller.currentRunId)?.workspace ?? null);
570
582
  }
583
+ if (current && !discovery)
584
+ query("managed-workspace");
571
585
  if (!current)
572
586
  query("change-set");
573
587
  query("task-message", { descending: true, unhandledMessages: current });
574
588
  query("run", { descending: true, ...(current ? { statuses: ["active"] } : {}) });
575
- for (const id of allowedIds(allow, "source-turn") ?? []) {
576
- const run = store.getRun(taskId, id);
577
- if (run !== null)
578
- add("source-run", id, sourceRunContextValue(run));
579
- }
580
589
  query("review-round", { descending: true, ...(current ? { statuses: ["pending", "running"] } : {}) });
581
590
  if (!current)
582
591
  query("input-request", { excludeStatuses: ["open"] });
583
- if (!current && allow === undefined) {
592
+ if (!current) {
584
593
  add("mailbox", "task", store.getWorkMailbox({ kind: "task", taskId }));
585
594
  for (const role of store.listRoles(taskId)) {
586
595
  add("mailbox", `role:${role.name}`, store.getWorkMailbox({ kind: "role", taskId, roleName: role.name }));
@@ -646,8 +655,31 @@ function freeze(value) {
646
655
  }
647
656
  function summarize(value) {
648
657
  const record = value;
649
- 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"]
650
659
  .flatMap((key) => typeof record[key] === "string" || (key === "version" && typeof record[key] === "number")
651
660
  ? [`${key}: ${record[key]}`] : [])
652
661
  .join("; "));
653
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
+ }
@@ -1,8 +1,7 @@
1
1
  import { usageError } from "../errors/cliError.js";
2
2
  import { TASK_SEARCH_KINDS } from "../storage/taskSearch.js";
3
3
  import { contextContentDigest } from "./contextSnapshot.js";
4
- import { taskCatalogScope } from "./taskCatalog.js";
5
- import { readTaskContextResource } from "./taskContext.js";
4
+ import { readTaskContextResource, resolveContextReader } from "./taskContext.js";
6
5
  const PAGE_BYTES = 32 * 1024;
7
6
  /** Human-readable field pages, using the same source authorization and version
8
7
  * checks as Context. Offsets are Unicode code points, like search snippets. */
@@ -67,11 +66,8 @@ export function searchTaskBodies(store, options, environment = {}) {
67
66
  ...(options.kind === undefined ? [] : ["--kind", options.kind]),
68
67
  ...(options.cursor === undefined ? [] : ["--cursor", options.cursor])]);
69
68
  return store.readTransaction(reader => {
70
- const callerTask = taskCatalogScope(reader, environment);
71
- if (callerTask !== undefined && valid.taskId !== undefined && callerTask !== valid.taskId) {
72
- throw usageError("Search target is outside the caller's Task.");
73
- }
74
- const taskId = callerTask ?? valid.taskId;
69
+ resolveContextReader(reader, environment);
70
+ const taskId = valid.taskId;
75
71
  const scope = contextContentDigest({ taskId: taskId ?? null, query: valid.query, kind: valid.kind ?? null });
76
72
  const after = valid.cursor === undefined ? undefined : decodeCursor(valid.cursor, scope);
77
73
  const rows = reader.queryTaskSearch({ ...valid, taskId, after, limit: valid.limit + 1 });
@@ -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,17 +12,17 @@ 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
- import { contextRecordReader } from "./context/taskContext.js";
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
- import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
25
+ import { operatorOfflineCommand, isTaskReadCommand } from "./cli/managedDiagnostics.js";
25
26
  import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
26
27
  import { requireManagedGlobalCaller, requireManagedGlobalReader, ManagedRuntimeDriftError } from "./runtime/managedCaller.js";
27
28
  import { assertJobAgentCommand } from "./job/agentRoleScope.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());
@@ -1094,7 +1102,7 @@ export async function main() {
1094
1102
  if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
1095
1103
  throw usageError(usage);
1096
1104
  }
1097
- if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
1105
+ if (action === "save" && process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
1098
1106
  throw usageError("Artifact is outside the managed Task scope.");
1099
1107
  }
1100
1108
  if (store.getTask(taskId) === null)
@@ -2065,12 +2073,7 @@ async function preflightManagedTaskControlPlane() {
2065
2073
  && ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
2066
2074
  const home = resolveYuiHome(process.env);
2067
2075
  assertManagedSessionManifest(home, "task");
2068
- const diagnosticTarget = taskDiagnosticTarget(args);
2069
- const diagnostic = diagnosticTarget !== undefined
2070
- && diagnosticTarget === process.env.YUI_TASK_ID;
2071
- if (diagnosticTarget !== undefined && !diagnostic) {
2072
- throw usageError("Diagnostics must remain within this Session's Task.");
2073
- }
2076
+ const diagnostic = isTaskReadCommand(args);
2074
2077
  // One gate for every managed command: the current CLI, Home, and Controller
2075
2078
  // must agree. Internal callbacks must still be able to append their immutable
2076
2079
  // fact while the Controller is offline.
@@ -2087,13 +2090,6 @@ async function preflightManagedTaskControlPlane() {
2087
2090
  : diagnostic
2088
2091
  ? resolveManagedTaskReader(verifiedStore, process.env)
2089
2092
  : requireManagedTaskCaller(verifiedStore, process.env);
2090
- if (diagnostic && runtime?.roleName !== "leader"
2091
- && !(args[1] === "show"
2092
- || (args[1] === "role" && args[2] === "session" && args[3] === "inspect"
2093
- && args[5] === runtime?.roleName))) {
2094
- // Non-Leaders keep their Assignment-scoped read authorization.
2095
- requireManagedTaskCaller(verifiedStore, process.env);
2096
- }
2097
2093
  const request = taskFinalReviewInvocation.request;
2098
2094
  if (request !== undefined && diagnostic)
2099
2095
  requireManagedTaskCaller(verifiedStore, process.env);
@@ -2762,7 +2758,9 @@ function completionShell(value) {
2762
2758
  throw usageError("Completion shell must be one of bash, zsh, fish.");
2763
2759
  }
2764
2760
  async function resolveTerminalArguments(commandArgs, node, store, catalogs) {
2765
- 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;
2766
2764
  if (!interactive || !allowsInteractiveSelection(commandArgs, jsonOutput)) {
2767
2765
  return [...commandArgs];
2768
2766
  }
@@ -3079,7 +3077,7 @@ function completionSelectionPorts(home) {
3079
3077
  if (process.env.YUI_SESSION_SCOPE !== "task")
3080
3078
  return ports;
3081
3079
  const taskId = process.env.YUI_TASK_ID;
3082
- const readable = contextRecordReader(store, taskId, process.env);
3080
+ assertTaskReadable(store, taskId, process.env);
3083
3081
  const families = {
3084
3082
  "task.list": "task", "project.list": "project-policy", "task.role.list": "role",
3085
3083
  "task.work.list": "work-item", "task.message.list": "task-message", "task.input.list": "input-request",
@@ -3103,7 +3101,7 @@ function completionSelectionPorts(home) {
3103
3101
  return false;
3104
3102
  if (method === "project.list" && !store.getTask(taskId)?.projectBindings.some(binding => binding.projectId === id))
3105
3103
  return false;
3106
- return readable(family, id);
3104
+ return true;
3107
3105
  });
3108
3106
  } };
3109
3107
  }