@zq-silk/yui 2.3.0 → 2.5.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 (197) hide show
  1. package/README.md +15 -1
  2. package/dist/artifacts/artifactCapability.js +39 -10
  3. package/dist/artifacts/imageMetadata.js +120 -0
  4. package/dist/artifacts/managedGit.js +7 -4
  5. package/dist/artifacts/taskArtifactRepository.js +6 -0
  6. package/dist/cli/commandCatalog.js +18 -9
  7. package/dist/cli/invocationAuthority.js +1 -1
  8. package/dist/commands/executionAuditCommands.js +5 -0
  9. package/dist/commands/globalRoleCommands.js +41 -3
  10. package/dist/commands/projectCommands.js +89 -94
  11. package/dist/commands/roleSkillValidation.js +4 -8
  12. package/dist/commands/taskCommands.js +65 -14
  13. package/dist/commands/taskUsageCommand.js +38 -0
  14. package/dist/context/roleSessionContext.js +18 -2
  15. package/dist/context/runContextPack.js +5 -1
  16. package/dist/context/sessionBootstrapManifest.js +18 -3
  17. package/dist/context/skillCatalog.js +108 -0
  18. package/dist/context/skillPackage.js +161 -0
  19. package/dist/context/taskContext.js +3 -2
  20. package/dist/context/taskSearch.js +127 -0
  21. package/dist/controlPlaneCli.js +25 -20
  22. package/dist/controller/fileSchedulerStoreAdapter.js +39 -8
  23. package/dist/controller/globalInputDelivery.js +2 -4
  24. package/dist/controller/jobControl.js +2 -0
  25. package/dist/controller/nativeHostInput.js +37 -0
  26. package/dist/controller/providerRetryDelivery.js +4 -2
  27. package/dist/controller/runtime.js +93 -2
  28. package/dist/controller/runtimeEventInbox.js +2 -2
  29. package/dist/controller/runtimeEventProcessor.js +3 -3
  30. package/dist/doctor/doctor.js +10 -0
  31. package/dist/execution/reviewMainRun.js +1 -0
  32. package/dist/execution/workItemMainRun.js +1 -0
  33. package/dist/executor/agentAdapter.js +3 -5
  34. package/dist/executor/codexConfigConflict.js +6 -4
  35. package/dist/executor/effectiveLaunch.js +24 -1
  36. package/dist/executor/fileRoleLaunchPlanner.js +15 -7
  37. package/dist/integration/gitIntegrationService.js +7 -12
  38. package/dist/integration/integrationSourceApplication.js +4 -2
  39. package/dist/interaction/notificationPresentation.js +85 -0
  40. package/dist/interaction/operatorPresentation.js +1 -2
  41. package/dist/job/httpPreview.js +61 -0
  42. package/dist/job/jobRunner.js +12 -2
  43. package/dist/kernel/builtinCapabilities.js +3 -1
  44. package/dist/message/message.js +15 -1
  45. package/dist/output/rolePresentation.js +4 -1
  46. package/dist/repository/executionLaneGitSnapshot.js +2 -1
  47. package/dist/repository/gitCompatibility.js +62 -0
  48. package/dist/repository/gitWorkspace.js +48 -71
  49. package/dist/repository/gitWorktreeInventory.js +121 -0
  50. package/dist/repository/project.js +74 -25
  51. package/dist/resources/resourceDiscovery.js +2 -2
  52. package/dist/resources/resourceGc.js +7 -4
  53. package/dist/resources/resourceRegistrar.js +2 -1
  54. package/dist/runtime/agentEndpoint.js +8 -0
  55. package/dist/runtime/agentHost.js +170 -3
  56. package/dist/runtime/codexAppServerRuntime.js +1 -1
  57. package/dist/runtime/codexNativeAccess.js +175 -0
  58. package/dist/runtime/controlledCodexTui.js +116 -0
  59. package/dist/runtime/publicReply.js +23 -0
  60. package/dist/runtime/runtimeObservation.js +48 -0
  61. package/dist/runtime/structuredProviderHost.js +16 -7
  62. package/dist/runtime/taskCostMetrics.js +133 -0
  63. package/dist/runtime/taskUsageMetrics.js +11 -2
  64. package/dist/runtime/taskUsageQuery.js +38 -0
  65. package/dist/scheduler/leaderWakeupProcessor.js +1 -9
  66. package/dist/scheduler/taskExecutionProjection.js +2 -0
  67. package/dist/scheduler/taskObservabilityProjection.js +9 -1
  68. package/dist/storage/homeLayout.js +13 -0
  69. package/dist/storage/sqliteSchema.js +44 -0
  70. package/dist/storage/sqliteStore.js +53 -0
  71. package/dist/storage/storageVersions.js +1 -1
  72. package/dist/storage/storeRpc.js +2 -0
  73. package/dist/storage/taskSearch.js +35 -0
  74. package/dist/storage/upgrade/projectKnowledgeMigration.js +52 -0
  75. package/dist/task/ordinaryArchive.js +42 -0
  76. package/dist/tmux/tmuxManager.js +8 -8
  77. package/dist/web/assets/assetManifest.js +11 -51
  78. package/dist/web/assets/client/app/catalog.js +215 -0
  79. package/dist/web/assets/client/app/main.js +148 -0
  80. package/dist/web/assets/client/app/selection.js +139 -0
  81. package/dist/web/assets/client/app/settings.js +326 -0
  82. package/dist/web/assets/client/app/taskView.js +280 -0
  83. package/dist/web/assets/client/domain/context.js +96 -0
  84. package/dist/web/assets/client/domain/records.js +126 -0
  85. package/dist/web/assets/client/domain/runs.js +119 -0
  86. package/dist/web/assets/client/domain/taskForms.js +197 -0
  87. package/dist/web/assets/client/domain/vocab.js +69 -0
  88. package/dist/web/assets/client/domain/work.js +116 -0
  89. package/dist/web/assets/client/i18n/en.js +900 -0
  90. package/dist/web/assets/client/i18n/zh-CN.js +898 -0
  91. package/dist/web/assets/client/index.js +114 -0
  92. package/dist/web/assets/client/layout/resizer.js +37 -0
  93. package/dist/web/assets/client/layout/router.js +35 -0
  94. package/dist/web/assets/client/layout/shortcuts.js +45 -0
  95. package/dist/web/assets/client/layout/sizing.js +95 -0
  96. package/dist/web/assets/client/layout/workspace.js +247 -0
  97. package/dist/web/assets/client/{api.js → lib/api.js} +20 -4
  98. package/dist/web/assets/client/{dom.js → lib/dom.js} +24 -12
  99. package/dist/web/assets/client/{format.js → lib/format.js} +7 -0
  100. package/dist/web/assets/client/lib/i18n.js +54 -0
  101. package/dist/web/assets/client/lib/markdown.js +167 -0
  102. package/dist/web/assets/client/lib/prefs.js +28 -0
  103. package/dist/web/assets/client/lib/theme.js +68 -0
  104. package/dist/web/assets/client/ui/containers.js +101 -0
  105. package/dist/web/assets/client/ui/controls.js +51 -0
  106. package/dist/web/assets/client/ui/forms.js +48 -0
  107. package/dist/web/assets/client/ui/metrics.js +137 -0
  108. package/dist/web/assets/client/ui/primitives.js +92 -0
  109. package/dist/web/assets/client/ui/text.js +33 -0
  110. package/dist/web/assets/client/ui/toast.js +12 -0
  111. package/dist/web/assets/client/views/bodySearch.js +172 -0
  112. package/dist/web/assets/client/views/createTask.js +85 -0
  113. package/dist/web/assets/client/views/dock/conversation.js +612 -0
  114. package/dist/web/assets/client/views/dock/discussion.js +173 -0
  115. package/dist/web/assets/client/views/dock/preview.js +129 -0
  116. package/dist/web/assets/client/views/dock/terminal.js +199 -0
  117. package/dist/web/assets/client/views/globalInput.js +67 -0
  118. package/dist/web/assets/client/{overview.js → views/overview.js} +61 -51
  119. package/dist/web/assets/client/{sidebar.js → views/sidebar.js} +56 -62
  120. package/dist/web/assets/client/views/skills.js +152 -0
  121. package/dist/web/assets/client/views/task/actions.js +36 -0
  122. package/dist/web/assets/client/views/task/delivery.js +185 -0
  123. package/dist/web/assets/client/views/task/deliveryResults.js +159 -0
  124. package/dist/web/assets/client/views/task/files.js +272 -0
  125. package/dist/web/assets/client/views/task/observation.js +153 -0
  126. package/dist/web/assets/client/views/task/overview.js +161 -0
  127. package/dist/web/assets/client/views/task/page.js +131 -0
  128. package/dist/web/assets/client/views/task/records.js +163 -0
  129. package/dist/web/assets/client/views/task/runtime.js +130 -0
  130. package/dist/web/assets/client/views/task/timeline.js +190 -0
  131. package/dist/web/assets/shared/geometry.js +19 -0
  132. package/dist/web/assets/shared/taskTabs.js +8 -0
  133. package/dist/web/assets/shared/themes.js +93 -0
  134. package/dist/web/assets/shell/dialogs.js +63 -0
  135. package/dist/web/assets/shell/dock.js +40 -0
  136. package/dist/web/assets/shell/index.js +40 -0
  137. package/dist/web/assets/shell/settings.js +34 -0
  138. package/dist/web/assets/shell/sidebar.js +45 -0
  139. package/dist/web/assets/styles/index.js +41 -0
  140. package/dist/web/assets/styles/{responsive.js → layout/responsive.js} +10 -10
  141. package/dist/web/assets/styles/{layout.js → layout/workspace.js} +17 -24
  142. package/dist/web/assets/styles/markdown.js +22 -3
  143. package/dist/web/assets/styles/tokens.js +10 -63
  144. package/dist/web/assets/styles/ui/controls.js +76 -0
  145. package/dist/web/assets/styles/ui/forms.js +46 -0
  146. package/dist/web/assets/styles/ui/surfaces.js +122 -0
  147. package/dist/web/assets/styles/views/delivery.js +29 -0
  148. package/dist/web/assets/styles/views/dock.js +71 -0
  149. package/dist/web/assets/styles/views/page.js +32 -0
  150. package/dist/web/assets/styles/views/records.js +70 -0
  151. package/dist/web/assets/styles/views/settings.js +26 -0
  152. package/dist/web/assets/styles/views/sidebar.js +74 -0
  153. package/dist/web/assets/styles/views/task.js +80 -0
  154. package/dist/web/assets/styles/views/terminal.js +38 -0
  155. package/dist/web/taskPreviews.js +205 -0
  156. package/dist/web/tmuxWebTerminal.js +16 -9
  157. package/dist/web/webConversation.js +373 -0
  158. package/dist/web/webMaterials.js +155 -0
  159. package/dist/web/webServer.js +295 -18
  160. package/dist/web/webSettings.js +331 -0
  161. package/dist/web/webSkills.js +86 -0
  162. package/dist/web/webSnapshot.js +40 -8
  163. package/dist/web/webTaskSurface.js +106 -8
  164. package/dist/workspace/gitChangeSetCapture.js +3 -1
  165. package/docs/cli-information-contract.md +1 -1
  166. package/docs/cli-information-contract.zh-CN.md +1 -1
  167. package/docs/managed-turn-and-session-runtime.md +31 -0
  168. package/docs/managed-turn-and-session-runtime.zh-CN.md +22 -0
  169. package/docs/release-workflow.md +54 -0
  170. package/docs/release-workflow.zh-CN.md +39 -0
  171. package/docs/storage-baseline.md +38 -1
  172. package/docs/storage-baseline.zh-CN.md +22 -0
  173. package/docs/task-discovery.md +28 -0
  174. package/docs/testing/verification-levels.md +2 -2
  175. package/i18n/README.zh-CN.md +1 -1
  176. package/package.json +1 -1
  177. package/skills/yui-leader/SKILL.md +5 -4
  178. package/skills/yui-leader/references/execution.md +2 -1
  179. package/skills/yui-operator/SKILL.md +3 -0
  180. package/skills/yui-reviewer/SKILL.md +4 -2
  181. package/skills/yui-runtime/SKILL.md +43 -2
  182. package/skills/yui-worker/SKILL.md +3 -2
  183. package/dist/web/assets/client/app.js +0 -808
  184. package/dist/web/assets/client/components.js +0 -477
  185. package/dist/web/assets/client/detail.js +0 -379
  186. package/dist/web/assets/client/dock.js +0 -309
  187. package/dist/web/assets/client/evidence.js +0 -364
  188. package/dist/web/assets/client/forms.js +0 -230
  189. package/dist/web/assets/client/i18n.js +0 -1277
  190. package/dist/web/assets/client/markdown.js +0 -114
  191. package/dist/web/assets/client/records.js +0 -198
  192. package/dist/web/assets/client/sections.js +0 -349
  193. package/dist/web/assets/client/theme.js +0 -71
  194. package/dist/web/assets/shell.js +0 -161
  195. package/dist/web/assets/styles/components.js +0 -216
  196. package/dist/web/assets/styles/views.js +0 -309
  197. /package/dist/web/assets/{icons.js → shared/icons.js} +0 -0
@@ -3,6 +3,7 @@ import { readdir, rename, rm } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
4
4
  import { CliError, usageError } from "../errors/cliError.js";
5
5
  import { defaultTableWidth, renderTable } from "../output/table.js";
6
+ import { boundedDocument, readOptions, recordPage } from "../output/boundedRead.js";
6
7
  import { healCheckoutSwap, restoreCheckoutSwap, swapManagedCheckout } from "../repository/checkoutSwap.js";
7
8
  import { GitWorkspaceRefreshError, NodeGitWorkspace } from "../repository/gitWorkspace.js";
8
9
  import { acquireProjectMaintenanceLock } from "../repository/projectMaintenanceLock.js";
@@ -74,17 +75,11 @@ export async function runProjectCommand(args, store, options = {}) {
74
75
  }
75
76
  if (command === "knowledge") {
76
77
  const result = projectKnowledge(rest, store, options);
77
- const project = resolveProject(store.listProjects(), result.projectId);
78
- if (project === null)
79
- return { output: result.output };
80
- if (result.knowledgeId !== undefined) {
81
- const knowledge = project.knowledge.find(({ id }) => id === result.knowledgeId);
82
- return { output: result.output, data: { project, knowledge } };
83
- }
84
- const knowledge = result.all
85
- ? project.knowledge
86
- : project.knowledge.filter(({ status }) => status === "active");
87
- return { output: result.output, data: { project, knowledge } };
78
+ return { output: result.output, data: result.data ?? {
79
+ projectId: result.projectId,
80
+ ...(result.knowledgeId === undefined ? {} : { knowledgeId: result.knowledgeId }),
81
+ ...(result.proposalId === undefined ? {} : { proposalId: result.proposalId })
82
+ } };
88
83
  }
89
84
  throw usageError(command === undefined
90
85
  ? "Project command is required."
@@ -1198,19 +1193,22 @@ function projectKnowledge(args, store, options) {
1198
1193
  }
1199
1194
  if (command === "list") {
1200
1195
  const all = rest.includes("--all");
1201
- const positionals = rest.filter((value) => value !== "--all");
1202
- if (positionals.length !== 1 || rest.some((value) => value.startsWith("--") && value !== "--all")) {
1203
- throw usageError("Project knowledge list usage: yui project knowledge list <project> [--all].");
1204
- }
1205
- const project = requireProject(store, positionals[0]);
1206
- const entries = all
1207
- ? project.knowledge
1208
- : project.knowledge.filter(({ status }) => status === "active");
1196
+ const parsed = readOptions(rest.filter((value) => value !== "--all"));
1197
+ if (parsed.positionals.length !== 1) {
1198
+ throw usageError("Project knowledge list usage: yui project knowledge list <project> [--all] [--limit <1..100>] [--cursor <cursor>].");
1199
+ }
1200
+ const project = requireProject(store, parsed.positionals[0]);
1201
+ const page = recordPage(project.knowledge.filter(({ status }) => all || status === "active").map(entry => ({
1202
+ id: entry.id, title: entry.title.slice(0, 200), version: entry.version, status: entry.status,
1203
+ scope: entry.scope?.slice(0, 200), expiresWhen: entry.expiresWhen?.slice(0, 200),
1204
+ updatedAt: entry.updatedAt
1205
+ })), `project:${project.id}/knowledge/${all ? "all" : "active"}`, parsed);
1206
+ const entries = page.items;
1209
1207
  if (entries.length === 0) {
1210
1208
  return {
1211
1209
  output: "No project knowledge found.\n",
1212
1210
  projectId: project.id,
1213
- all
1211
+ data: { projectId: project.id, ...page }
1214
1212
  };
1215
1213
  }
1216
1214
  return {
@@ -1218,25 +1216,32 @@ function projectKnowledge(args, store, options) {
1218
1216
  { header: "Knowledge", minWidth: 9, maxWidth: 24 },
1219
1217
  { header: "Title", minWidth: 8, maxWidth: 64 },
1220
1218
  { header: "Status", minWidth: 6, maxWidth: 10 }
1221
- ], entries.map((entry) => [entry.id, entry.title, entry.status]), defaultTableWidth())}\n`,
1219
+ ], entries.map((entry) => [entry.id, entry.title, entry.status]), defaultTableWidth())}\n${page.nextCursor === null ? "" : `Next cursor: ${page.nextCursor}\n`}`,
1222
1220
  projectId: project.id,
1223
- all
1221
+ data: { projectId: project.id, ...page }
1224
1222
  };
1225
1223
  }
1226
1224
  if (command === "show") {
1227
- if (rest.length !== 2) {
1228
- throw usageError("Project knowledge show usage: yui project knowledge show <project> <knowledge>.");
1225
+ const parsed = readOptions(rest, ["--cursor"]);
1226
+ if (parsed.positionals.length !== 2) {
1227
+ throw usageError("Project knowledge show usage: yui project knowledge show <project> <knowledge> [--cursor <cursor>].");
1229
1228
  }
1230
- const project = requireProject(store, rest[0]);
1231
- const entry = project.knowledge.find(({ id }) => id === rest[1]);
1229
+ const project = requireProject(store, parsed.positionals[0]);
1230
+ const entry = project.knowledge.find(({ id }) => id === parsed.positionals[1]);
1232
1231
  if (entry === undefined)
1233
- throw usageError(`Project knowledge not found: ${rest[1]}.`);
1232
+ throw usageError(`Project knowledge not found: ${parsed.positionals[1]}.`);
1233
+ const data = boundedDocument({ projectId: project.id, knowledge: entry }, `project:${project.id}/knowledge/${entry.id}`, parsed.cursor);
1234
+ if ("contentPage" in data)
1235
+ return { output: `${JSON.stringify(data)}\n`, projectId: project.id, data };
1234
1236
  return {
1235
1237
  output: [
1236
1238
  `Knowledge: ${entry.id}`,
1237
1239
  `Project: ${project.id}`,
1238
1240
  `Title: ${entry.title}`,
1239
1241
  `Status: ${entry.status}`,
1242
+ `Version: ${entry.version}`,
1243
+ ...(entry.scope === undefined ? [] : [`Scope: ${entry.scope}`]),
1244
+ ...(entry.expiresWhen === undefined ? [] : [`Expires when: ${entry.expiresWhen}`]),
1240
1245
  ...(entry.provenance === undefined ? [] : [
1241
1246
  `Source: ${entry.provenance.taskId}`
1242
1247
  + `${entry.provenance.decisionId === undefined ? "" : `/${entry.provenance.decisionId}`}`
@@ -1253,10 +1258,14 @@ function projectKnowledge(args, store, options) {
1253
1258
  : [`Promoted by: ${entry.provenance.promotedBy} at ${entry.provenance.promotedAt ?? "?"}`])
1254
1259
  ]),
1255
1260
  "",
1256
- entry.body
1261
+ entry.body,
1262
+ ...(entry.history.length === 0 ? [] : [
1263
+ "", "Prior versions (exact records):", JSON.stringify(entry.history, null, 2)
1264
+ ])
1257
1265
  ].join("\n").concat("\n"),
1258
1266
  projectId: project.id,
1259
- knowledgeId: entry.id
1267
+ knowledgeId: entry.id,
1268
+ data
1260
1269
  };
1261
1270
  }
1262
1271
  if (command === "update") {
@@ -1401,7 +1410,7 @@ function parseKnowledgeProposalOptions(args, usage) {
1401
1410
  * Leader-proposed promotion candidate. The proposal is workflow state, not
1402
1411
  * Knowledge: it never appears in `project knowledge list/show` until an
1403
1412
  * Operator accepts it. A managed Task Session may propose only for its own
1404
- * Task. The same candidate (source identity + title + body) is deduplicated to
1413
+ * Task. The same candidate (source, content, applicability and replacement intent) is deduplicated to
1405
1414
  * the existing pending proposal instead of creating a second copy.
1406
1415
  */
1407
1416
  function proposeKnowledge(args, store, options) {
@@ -1430,7 +1439,10 @@ function proposeKnowledge(args, store, options) {
1430
1439
  projectId: project.id,
1431
1440
  source,
1432
1441
  title: parsed.title,
1433
- body: parsed.body
1442
+ body: parsed.body,
1443
+ ...(parsed.scope === undefined ? {} : { scope: parsed.scope }),
1444
+ ...(parsed.expiresWhen === undefined ? {} : { expiresWhen: parsed.expiresWhen }),
1445
+ ...(parsed.supersedes === undefined ? {} : { supersedesKnowledgeId: parsed.supersedes })
1434
1446
  });
1435
1447
  const pending = findKnowledgeProposalByFingerprint(project, fingerprint, "pending");
1436
1448
  if (pending !== null) {
@@ -1452,7 +1464,7 @@ function proposeKnowledge(args, store, options) {
1452
1464
  };
1453
1465
  }
1454
1466
  const proposal = {
1455
- schemaVersion: 1,
1467
+ schemaVersion: 2,
1456
1468
  id: nextProposalId(project),
1457
1469
  projectId: project.id,
1458
1470
  title: parsed.title,
@@ -1528,50 +1540,29 @@ function requireProposalEvidence(store, parsed) {
1528
1540
  }
1529
1541
  function listKnowledgeProposals(args, store) {
1530
1542
  const usage = "Project knowledge proposals list usage: yui project knowledge proposals list <project>"
1531
- + " [--status pending|accepted|rejected] [--all].";
1532
- const positionals = [];
1533
- let status = "pending";
1534
- let sawAll = false;
1535
- let sawStatus = false;
1536
- for (let index = 0; index < args.length; index += 1) {
1537
- const value = args[index];
1538
- if (value === "--all") {
1539
- if (sawStatus)
1540
- throw usageError(`--all and --status are mutually exclusive. ${usage}`);
1541
- sawAll = true;
1542
- status = undefined;
1543
- }
1544
- else if (value === "--status") {
1545
- if (sawAll)
1546
- throw usageError(`--all and --status are mutually exclusive. ${usage}`);
1547
- sawStatus = true;
1548
- const next = args[index + 1];
1549
- if (next === undefined || next.startsWith("--"))
1550
- throw usageError(usage);
1551
- if (next !== "pending" && next !== "accepted" && next !== "rejected")
1552
- throw usageError(usage);
1553
- status = next;
1554
- index += 1;
1555
- }
1556
- else if (value.startsWith("--")) {
1557
- throw usageError(`Unknown option: ${value}. ${usage}`);
1558
- }
1559
- else {
1560
- positionals.push(value);
1561
- }
1562
- }
1563
- if (positionals.length !== 1)
1543
+ + " [--status pending|accepted|rejected] [--all] [--limit <1..100>] [--cursor <cursor>].";
1544
+ const parsed = readOptions(args.filter(value => value !== "--all"), ["--cursor", "--limit", "--status"]);
1545
+ const all = args.includes("--all");
1546
+ if (all && parsed.values.has("--status"))
1547
+ throw usageError(`--all and --status are mutually exclusive. ${usage}`);
1548
+ const status = all ? undefined : parsed.values.get("--status") ?? "pending";
1549
+ if (parsed.positionals.length !== 1
1550
+ || (status !== undefined && !["pending", "accepted", "rejected"].includes(status)))
1564
1551
  throw usageError(usage);
1565
- const project = requireProject(store, positionals[0]);
1566
- const entries = project.knowledgeProposals
1552
+ const project = requireProject(store, parsed.positionals[0]);
1553
+ const page = recordPage(project.knowledgeProposals
1567
1554
  .filter((entry) => status === undefined || entry.status === status)
1568
1555
  .slice()
1569
- .sort((a, b) => a.id.localeCompare(b.id));
1556
+ .sort((a, b) => a.id.localeCompare(b.id))
1557
+ .map(entry => ({ id: entry.id, title: entry.title.slice(0, 200), status: entry.status,
1558
+ source: entry.source, knowledgeId: entry.knowledgeId,
1559
+ scope: entry.scope?.slice(0, 200), expiresWhen: entry.expiresWhen?.slice(0, 200) })), `project:${project.id}/knowledge-proposals/${status ?? "all"}`, parsed);
1560
+ const entries = page.items;
1570
1561
  if (entries.length === 0) {
1571
1562
  return {
1572
1563
  output: "No knowledge proposals found.\n",
1573
1564
  projectId: project.id,
1574
- proposals: []
1565
+ data: { projectId: project.id, ...page }
1575
1566
  };
1576
1567
  }
1577
1568
  return {
@@ -1589,20 +1580,24 @@ function listKnowledgeProposals(args, store) {
1589
1580
  + `${entry.source.decisionId === undefined ? "" : `/${entry.source.decisionId}`}`
1590
1581
  + `${entry.source.milestoneId === undefined ? "" : `/${entry.source.milestoneId}`}`,
1591
1582
  entry.knowledgeId ?? "-"
1592
- ]), defaultTableWidth())}\n`,
1583
+ ]), defaultTableWidth())}\n${page.nextCursor === null ? "" : `Next cursor: ${page.nextCursor}\n`}`,
1593
1584
  projectId: project.id,
1594
- proposals: entries
1585
+ data: { projectId: project.id, ...page }
1595
1586
  };
1596
1587
  }
1597
1588
  function showKnowledgeProposal(args, store) {
1598
- const usage = "Project knowledge proposals show usage: yui project knowledge proposals show <project> <proposal>.";
1599
- if (args.length !== 2)
1589
+ const usage = "Project knowledge proposals show usage: yui project knowledge proposals show <project> <proposal> [--cursor <cursor>].";
1590
+ const parsed = readOptions(args, ["--cursor"]);
1591
+ if (parsed.positionals.length !== 2)
1600
1592
  throw usageError(usage);
1601
- const project = requireProject(store, args[0]);
1602
- const proposal = findKnowledgeProposal(project, args[1]);
1593
+ const project = requireProject(store, parsed.positionals[0]);
1594
+ const proposal = findKnowledgeProposal(project, parsed.positionals[1]);
1603
1595
  if (proposal === null) {
1604
- throw usageError(`Knowledge proposal not found: ${args[1]}.`);
1596
+ throw usageError(`Knowledge proposal not found: ${parsed.positionals[1]}.`);
1605
1597
  }
1598
+ const data = boundedDocument({ projectId: project.id, proposal }, `project:${project.id}/knowledge-proposals/${proposal.id}`, parsed.cursor);
1599
+ if ("contentPage" in data)
1600
+ return { output: `${JSON.stringify(data)}\n`, projectId: project.id, data };
1606
1601
  const lines = [
1607
1602
  `Proposal: ${proposal.id}`,
1608
1603
  `Project: ${project.id}`,
@@ -1629,7 +1624,7 @@ function showKnowledgeProposal(args, store) {
1629
1624
  output: `${lines.join("\n")}\n`,
1630
1625
  projectId: project.id,
1631
1626
  proposalId: proposal.id,
1632
- proposals: [proposal]
1627
+ data
1633
1628
  };
1634
1629
  }
1635
1630
  /**
@@ -1654,6 +1649,10 @@ function acceptKnowledgeProposal(args, store, options) {
1654
1649
  if (proposal === null) {
1655
1650
  throw usageError(`Knowledge proposal not found: ${parsed.proposal}.`);
1656
1651
  }
1652
+ if (proposal.status === "accepted"
1653
+ && (parsed.update === undefined || parsed.update === proposal.knowledgeId)) {
1654
+ return { projectId: project.id, proposalId: proposal.id, knowledgeId: proposal.knowledgeId, duplicate: true };
1655
+ }
1657
1656
  if (proposal.status !== "pending") {
1658
1657
  throw usageError(`Knowledge proposal is already ${proposal.status}: ${proposal.id}.`);
1659
1658
  }
@@ -1675,20 +1674,24 @@ function acceptKnowledgeProposal(args, store, options) {
1675
1674
  || previousProposal.status !== "accepted"
1676
1675
  || previousProposal.knowledgeId !== target.id
1677
1676
  || previousProposal.title !== target.title
1678
- || previousProposal.body !== target.body) {
1677
+ || previousProposal.body !== target.body
1678
+ || previousProposal.scope !== target.scope
1679
+ || previousProposal.expiresWhen !== target.expiresWhen) {
1679
1680
  throw usageError(`Knowledge ${target.id} has no complete proposal-backed version history. `
1680
1681
  + "Create the replacement proposal with --supersedes instead of --update.");
1681
1682
  }
1682
1683
  knowledgeId = target.id;
1683
1684
  next = updateProjectKnowledge(next, knowledgeId, {
1684
1685
  title: proposal.title,
1685
- body: proposal.body
1686
- }, now);
1687
- next = withKnowledgeProvenance(next, knowledgeId, {
1688
- ...provenanceFromProposal(proposal),
1689
- proposalId: proposal.id,
1690
- promotedBy: decidedBy,
1691
- promotedAt: now.toISOString()
1686
+ body: proposal.body,
1687
+ scope: proposal.scope ?? null,
1688
+ expiresWhen: proposal.expiresWhen ?? null,
1689
+ provenance: {
1690
+ ...provenanceFromProposal(proposal),
1691
+ proposalId: proposal.id,
1692
+ promotedBy: decidedBy,
1693
+ promotedAt: now.toISOString()
1694
+ }
1692
1695
  }, now);
1693
1696
  }
1694
1697
  else {
@@ -1707,7 +1710,7 @@ function acceptKnowledgeProposal(args, store, options) {
1707
1710
  proposalId: proposal.id,
1708
1711
  promotedBy: decidedBy,
1709
1712
  promotedAt: now.toISOString()
1710
- });
1713
+ }, proposal);
1711
1714
  }
1712
1715
  }
1713
1716
  next = decideKnowledgeProposal(next, proposal.id, {
@@ -1768,14 +1771,6 @@ function provenanceFromProposal(proposal) {
1768
1771
  fingerprint: proposal.fingerprint
1769
1772
  };
1770
1773
  }
1771
- /** Replace a Knowledge entry's provenance (used by accept --update). */
1772
- function withKnowledgeProvenance(project, knowledgeId, provenance, now) {
1773
- const timestamp = now.toISOString();
1774
- const knowledge = project.knowledge.map((entry) => (entry.id === knowledgeId
1775
- ? { ...entry, provenance, updatedAt: timestamp }
1776
- : entry));
1777
- return validateProject({ ...project, knowledge, updatedAt: timestamp });
1778
- }
1779
1774
  function parseAcceptArguments(args, usage) {
1780
1775
  const positionals = [];
1781
1776
  let update;
@@ -1,6 +1,5 @@
1
- import { accessSync, constants, statSync } from "node:fs";
2
- import { resolve } from "node:path";
3
1
  import { usageError } from "../errors/cliError.js";
2
+ import { loadYuiSkillContexts } from "../context/roleSessionContext.js";
4
3
  const ROLE_LAUNCH_CONTEXT_OPTIONS = new Set([
5
4
  "--workspace",
6
5
  "--description",
@@ -34,14 +33,11 @@ export function validateConfiguredRoleSkills(yuiHome, skillIds) {
34
33
  if (!SAFE_SKILL_ID.test(id)) {
35
34
  throw usageError(`Configured Skill id is invalid: ${id}.`);
36
35
  }
37
- const path = resolve(yuiHome, "skills", id, "SKILL.md");
38
36
  try {
39
- if (!statSync(path).isFile())
40
- throw new Error("not a file");
41
- accessSync(path, constants.R_OK);
37
+ loadYuiSkillContexts(yuiHome, [id]);
42
38
  }
43
- catch {
44
- throw usageError(`Configured Skill not found or unreadable: ${id}.`);
39
+ catch (error) {
40
+ throw usageError(`Configured Skill not found, unreadable or invalid: ${id}. ${error instanceof Error ? error.message : ""}`);
45
41
  }
46
42
  }
47
43
  }
@@ -10,8 +10,10 @@ import { buildRunContextDelta, buildRunContextPack, contextSnapshotDeltaRefIds,
10
10
  import { createRunInput } from "../context/runInputContract.js";
11
11
  import { parseTaskCatalogOptions, readTaskCatalog, renderTaskCatalog } from "../context/taskCatalog.js";
12
12
  import { assertContextRecordReadable, readTaskContextResource } from "../context/taskContext.js";
13
+ import { parseTaskSearchOptions, searchTaskBodies } from "../context/taskSearch.js";
13
14
  import { boundedDocument, readOptions, messageReceipt, recordPage } from "../output/boundedRead.js";
14
15
  import { referencedWakeRunIds } from "../context/wakeRunReferences.js";
16
+ import { taskMessagePresentation, wakeChangeLabels, wakeMessages } from "../interaction/notificationPresentation.js";
15
17
  import { completeProcessing, releaseProcessing } from "../coordination/workMailbox.js";
16
18
  import { enqueueRoleRunDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
17
19
  import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageError } from "../errors/cliError.js";
@@ -50,7 +52,7 @@ import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.j
50
52
  import { failureCapabilitiesCommand } from "../runtime/agentFailureContext.js";
51
53
  import { enqueueOperatorEvent } from "../scheduler/operatorEvent.js";
52
54
  import { isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
53
- import { renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
55
+ import { parseWakeReason, renderWakeReason, wakeReason } from "../scheduler/wakeReason.js";
54
56
  import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
55
57
  import { planningRuntimeCwd } from "../storage/homeLayout.js";
56
58
  import { StorageConflictError } from "../storage/taskStore.js";
@@ -80,6 +82,7 @@ import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, lead
80
82
  import { assertTaskCompletionPublishedTreeProof } from "./taskCompletionGate.js";
81
83
  import { runTaskContextCommand, taskRecordList } from "./taskContextCommand.js";
82
84
  import { taskBriefCommand, taskDecisionCommand, taskEventCommand, taskMilestoneCommand } from "./taskFactCommands.js";
85
+ import { taskUsageCommand } from "./taskUsageCommand.js";
83
86
  import { openInputRequestCount, runTaskInputCommand } from "./taskInputCommands.js";
84
87
  import { runTaskNextActionCommand } from "./taskNextActionCommand.js";
85
88
  import { runPublicationCommand } from "./taskPublicationCommands.js";
@@ -341,6 +344,10 @@ function executeTaskCommand(args, store, options = {}) {
341
344
  case "create": return createTaskCommand(rest, store, options);
342
345
  case "update": return output(updateTaskCommand(rest, store, options));
343
346
  case "list": return listTaskCommand(rest, store, options.environment);
347
+ case "search": {
348
+ const result = searchTaskBodies(store, parseTaskSearchOptions(rest), options.environment);
349
+ return output(`${JSON.stringify(result, null, 2)}\n`, result);
350
+ }
344
351
  case "show": return showTaskCommand(rest, store, options.actualTaskReviewCandidate ?? null);
345
352
  case "context": return runTaskContextCommand(rest, store, options.environment);
346
353
  case "next-action": return runTaskNextActionCommand(rest, store, options.actualTaskReviewCandidate ?? null);
@@ -368,6 +375,7 @@ function executeTaskCommand(args, store, options = {}) {
368
375
  case "review": return taskReviewCommand(rest, store, options);
369
376
  case "run": return taskRunCommand(rest, store, options);
370
377
  case "brief": return taskBriefCommand(rest, store, options);
378
+ case "usage": return taskUsageCommand(rest, store, options);
371
379
  case "decision": return taskDecisionCommand(rest, store, options);
372
380
  case "milestone": return taskMilestoneCommand(rest, store, options);
373
381
  case "event": return taskEventCommand(rest, store, options);
@@ -1720,6 +1728,13 @@ function taskMessageCommand(args, store, options) {
1720
1728
  * input (decision-3 §5/§6).
1721
1729
  */
1722
1730
  export function applyTaskInputControl(taskId, input, store, options) {
1731
+ if (input.expectedSessionId !== undefined) {
1732
+ const roleName = input.action === "interrupt" ? input.role : input.to ?? "leader";
1733
+ const set = store.getTaskRoleSessionSet(taskId, roleName);
1734
+ if (set?.sessions[set.activeAgentId]?.nativeSessionId !== input.expectedSessionId) {
1735
+ throw usageError("Selected native Session changed; refresh before sending.");
1736
+ }
1737
+ }
1723
1738
  if (input.action === "queue")
1724
1739
  return queueTaskInput(taskId, input, store, options);
1725
1740
  if (input.action === "steer")
@@ -1745,7 +1760,8 @@ function queueTaskInput(taskId, input, store, options) {
1745
1760
  if (recipientRole === undefined && (workItemId !== undefined || reviewRoundId !== undefined)) {
1746
1761
  throw usageError("--to is required for scoped Message delivery.");
1747
1762
  }
1748
- const result = sendTaskMessageCommand(store, taskId, body, options, recipientRole === undefined ? undefined : { roleName: recipientRole, workItemId, reviewRoundId }, undefined, undefined, { action: "queue", requestId });
1763
+ const result = sendTaskMessageCommand(store, taskId, body, options, recipientRole === undefined ? undefined : { roleName: recipientRole, workItemId, reviewRoundId }, undefined, undefined, { action: "queue", requestId,
1764
+ ...(input.expectedSessionId === undefined ? {} : { expectedSessionId: input.expectedSessionId }) });
1749
1765
  const reason = result.message.continuation?.notDeliveredReason;
1750
1766
  const delivery = result.idempotentReplay ? { state: "idempotent-replay" }
1751
1767
  : reason !== undefined ? { state: "not-delivered", reason }
@@ -1794,7 +1810,8 @@ function steerTaskInput(taskId, input, store, options) {
1794
1810
  throw usageError("Steering the Leader targets its current turn directly; it takes no --work-item/--review-round Assignment.");
1795
1811
  }
1796
1812
  const result = leaderTarget
1797
- ? sendTaskMessageCommand(store, taskId, body, options, undefined, undefined, undefined, { action: "steer", requestId, expectedTarget })
1813
+ ? sendTaskMessageCommand(store, taskId, body, options, undefined, undefined, undefined, { action: "steer", requestId, expectedTarget,
1814
+ ...(input.expectedSessionId === undefined ? {} : { expectedSessionId: input.expectedSessionId }) })
1798
1815
  : sendTaskMessageCommand(store, taskId, body, options, { roleName: recipientRole, workItemId, reviewRoundId }, undefined, undefined, { action: "steer", requestId, expectedTarget });
1799
1816
  const roleName = result.message.recipient?.roleName ?? recipientRole;
1800
1817
  if (result.idempotentReplay) {
@@ -1921,6 +1938,7 @@ export function sendTaskMessageCommand(store, taskId, body, options = {}, recipi
1921
1938
  : { roleName: recipient.roleName, workItemId: recipient.workItemId, reviewRoundId: recipient.reviewRoundId };
1922
1939
  if (prior?.action !== inputControl.action || existing.body !== body
1923
1940
  || prior.expectedTarget !== inputControl.expectedTarget
1941
+ || prior.expectedSessionId !== inputControl.expectedSessionId
1924
1942
  || !isDeepStrictEqual(priorRecipient, nextRecipient)) {
1925
1943
  throw usageError(`Input requestId ${inputControl.requestId} was already used with different content or target; use a new requestId for a new input.`);
1926
1944
  }
@@ -3519,6 +3537,7 @@ function dispatchWork(args, store, options) {
3519
3537
  throw usageError(`${task.id}/${role.name} already has an active run.`);
3520
3538
  }
3521
3539
  const effective = resolveEffectiveLaunch({
3540
+ store: tx,
3522
3541
  role,
3523
3542
  purpose: "execution",
3524
3543
  workspace,
@@ -3575,6 +3594,7 @@ function dispatchWork(args, store, options) {
3575
3594
  }
3576
3595
  const laneWorkspace = managedWorkspace ?? workspace;
3577
3596
  const effective = resolveEffectiveLaunch({
3597
+ store: tx,
3578
3598
  role,
3579
3599
  purpose: "execution",
3580
3600
  workspace: laneWorkspace,
@@ -5101,6 +5121,7 @@ function retryRunOperation(args, store, options) {
5101
5121
  ?? (directSession === undefined ? undefined
5102
5122
  : effectiveLaunchWithTaskMainWorkspace(directSession.effective, retryManagedWorkspace))
5103
5123
  ?? resolveEffectiveLaunch({
5124
+ store: tx,
5104
5125
  role,
5105
5126
  purpose: previous.purpose,
5106
5127
  ...(retryManagedWorkspace === undefined ? {} : { workspace: retryManagedWorkspace }),
@@ -5636,6 +5657,7 @@ function retryFailedReviewRun(previous, store, options, now) {
5636
5657
  }
5637
5658
  selectedReviewSynthesisProducers(tx, round, group, synthesisSourceRunIds(tx, run));
5638
5659
  const effective = resolveEffectiveLaunch({
5660
+ store: tx,
5639
5661
  role: reviewer,
5640
5662
  purpose: "review",
5641
5663
  workspace: run.workspace,
@@ -6129,6 +6151,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
6129
6151
  }
6130
6152
  const runId = tx.nextRunId(taskId);
6131
6153
  const effective = resolveEffectiveLaunch({
6154
+ store: tx,
6132
6155
  role: reviewer,
6133
6156
  purpose: "review",
6134
6157
  workspace: round.workspace,
@@ -6191,6 +6214,7 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
6191
6214
  }
6192
6215
  const laneManagedWorkspace = preparedLaneWorkspaces.get(lane.id);
6193
6216
  const effective = lane.effective ?? resolveEffectiveLaunch({
6217
+ store: tx,
6194
6218
  role: laneReviewer,
6195
6219
  purpose: "review",
6196
6220
  workspace: laneManagedWorkspace,
@@ -6909,12 +6933,16 @@ function taskWakeInspectionCommand(args, store) {
6909
6933
  return output(`${JSON.stringify(data)}\n`, data);
6910
6934
  }
6911
6935
  if (command === "show") {
6912
- const usage = "Task wake show usage: yui task wake show <task> <wake>.";
6913
- exactPositionals(rest, 2, usage);
6914
- const task = requireTask(store, rest[0]);
6915
- const wake = store.getTaskWake(task.id, rest[1]);
6936
+ const usage = "Task wake show usage: yui task wake show <task> <wake> [--history].";
6937
+ const history = rest.includes("--history");
6938
+ const positional = history ? rest.filter(arg => arg !== "--history") : rest;
6939
+ exactPositionals(positional, 2, usage);
6940
+ if (rest.filter(arg => arg === "--history").length > 1)
6941
+ throw usageError(usage);
6942
+ const task = requireTask(store, positional[0]);
6943
+ const wake = store.getTaskWake(task.id, positional[1]);
6916
6944
  if (wake === null)
6917
- throw dataError(`Wake not found: ${rest[1]}.`);
6945
+ throw dataError(`Wake not found: ${positional[1]}.`);
6918
6946
  const timeZone = store.getConfig().timeZone;
6919
6947
  const fromMs = Date.parse(wake.fromCursor);
6920
6948
  const toMs = Date.parse(wake.toCursor);
@@ -6930,8 +6958,20 @@ function taskWakeInspectionCommand(args, store) {
6930
6958
  const currentClaim = store.getWorkMailbox(leaderMailbox(task.id))?.processing;
6931
6959
  const referenced = (type, id) => wake.refs?.some(ref => ref.type === type && ref.id === id
6932
6960
  && (!("taskId" in ref) || ref.taskId === task.id)) === true;
6933
- const events = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id));
6934
- const messages = store.listMessages(task.id).filter((m) => inWindow(m.createdAt) || referenced("message", m.id));
6961
+ // Some existing producers carry the exact source only in a reason tag
6962
+ // (e.g. input-timeout:<request>). Do not hide its decision behind --history.
6963
+ const reasonRefs = new Set(wake.reasons.flatMap(tag => {
6964
+ const reason = parseWakeReason(tag);
6965
+ return reason.kind !== "force-wake" && reason.ref !== undefined ? [reason.ref] : [];
6966
+ }));
6967
+ const reasonEvent = (event) => Date.parse(event.createdAt) <= toMs && [
6968
+ event.id, event.payload.runId, event.payload.requestId, event.payload.jobId,
6969
+ event.payload.workItemId, event.payload.reviewRoundId
6970
+ ].some(id => id !== undefined && reasonRefs.has(id));
6971
+ const windowEvents = allEvents.filter((e) => inWindow(e.createdAt) || referenced("event", e.id) || reasonEvent(e));
6972
+ const events = history ? windowEvents : windowEvents.filter(e => referenced("event", e.id)
6973
+ || reasonEvent(e) || ["run.completed", "run.failed", "run.cancelled"].includes(e.type));
6974
+ const messages = wakeMessages(wake, store.listMessages(task.id));
6935
6975
  const allRuns = store.listRuns(task.id);
6936
6976
  const referencedRunIds = new Set(referencedWakeRunIds(allRuns, allEvents, events));
6937
6977
  for (const run of allRuns)
@@ -6940,7 +6980,7 @@ function taskWakeInspectionCommand(args, store) {
6940
6980
  for (const message of messages)
6941
6981
  if (message.resultRef?.type === "agent-run-result")
6942
6982
  referencedRunIds.add(message.resultRef.runId);
6943
- const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6983
+ const runs = operationalTaskRecords(allRuns, allEvents, "run").filter((run) => (history && inWindow(run.createdAt) || referencedRunIds.has(run.id)));
6944
6984
  const lines = [
6945
6985
  `Wake: ${wake.id}`,
6946
6986
  `Task: ${task.id}`,
@@ -6956,29 +6996,40 @@ function taskWakeInspectionCommand(args, store) {
6956
6996
  `Recovery: correct the cause, then yui task wake retry ${task.id} ${wake.id} --reason <correction>.`
6957
6997
  ]),
6958
6998
  `Reasons: ${wake.reasons.map(renderWakeReason).join(", ")}`,
6999
+ `Changes: ${wakeChangeLabels(wake.reasons).join("; ")}`,
6959
7000
  `Delta window: ${wake.fromCursor} → ${wake.toCursor}`,
6960
7001
  `Dispatched: ${presentTime(wake.createdAt, timeZone)}`,
6961
7002
  ...(wake.consumedAt === undefined
6962
7003
  ? []
6963
7004
  : [`Consumed: ${presentTime(wake.consumedAt, timeZone)}`]),
6964
7005
  `Events (${events.length}):`,
6965
- ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)}`),
7006
+ ...events.map((e) => ` ${e.id} ${e.type} ${presentTime(e.createdAt, timeZone)} → yui task event show ${task.id} ${e.id}`),
6966
7007
  `Messages (${messages.length}):`,
6967
- ...messages.map((m) => ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${presentTime(m.createdAt, timeZone)}`),
7008
+ ...messages.map((m) => {
7009
+ const item = taskMessagePresentation(m, (taskId, runId) => store.getRun(taskId, runId));
7010
+ return ` ${m.id} [${taskMessageAuthorLabel(m.author)}] ${item.fact}`
7011
+ + (item.summary === undefined ? "" : `; author summary (${item.summarySource}): ${JSON.stringify(item.summary)}`)
7012
+ + ` → yui task message show ${task.id}/${m.id}`;
7013
+ }),
6968
7014
  `AgentRuns (${runs.length}):`,
6969
7015
  ...runs.map((run) => (` ${run.id} [${run.status}/${run.purpose}] ${run.roleName} `
6970
7016
  + `${presentTime(run.createdAt, timeZone)}`
6971
7017
  + `${referencedRunIds.has(run.id)
6972
7018
  ? ` → yui task run show ${task.id}/${run.id}`
6973
- : ""}`))
7019
+ : ""}`)),
7020
+ ...(!history ? [`History/diagnostics: yui task wake show ${task.id} ${wake.id} --history`,
7021
+ "Read relevant originals in full; follow contentPage.nextCursor when present."] : [])
6974
7022
  ];
6975
7023
  return output(lines.join("\n").concat("\n"), {
6976
7024
  taskId: task.id,
6977
7025
  deliveryEvents,
6978
7026
  wake,
7027
+ view: history ? "history" : "focus",
7028
+ historyRead: `task wake show ${task.id} ${wake.id} --history`,
6979
7029
  events: events.map(event => ({ id: event.id, type: event.type, createdAt: event.createdAt,
6980
7030
  read: `task event show ${task.id} ${event.id}` })),
6981
7031
  messages: messages.map(message => ({ id: message.id, kind: message.kind, author: message.author,
7032
+ ...taskMessagePresentation(message, (taskId, runId) => store.getRun(taskId, runId)),
6982
7033
  resultRef: message.resultRef, read: `task message show ${task.id}/${message.id}` })),
6983
7034
  runs: runs.map(run => ({ id: run.id, status: run.status, purpose: run.purpose, roleName: run.roleName,
6984
7035
  read: `task run show ${task.id}/${run.id}` }))
@@ -0,0 +1,38 @@
1
+ import { assertContextRecordReadable } from "../context/taskContext.js";
2
+ import { readTaskUsage } from "../runtime/taskUsageQuery.js";
3
+ import { formatUsageMetric } from "../runtime/taskUsageMetrics.js";
4
+ import { exactPositionals, output, parseTail, requireTask } from "./taskCommandSupport.js";
5
+ import { usageError } from "../errors/cliError.js";
6
+ import { taskActor } from "../task/taskAuthority.js";
7
+ export function taskUsageCommand(args, store, options) {
8
+ const usage = "yui task usage <task> [--limit <0..50>] [--offset <n>]";
9
+ const parsed = parseTail(args, new Set(["--limit", "--offset"]), usage);
10
+ exactPositionals(parsed.positionals, 1, usage);
11
+ const task = requireTask(store, parsed.positionals[0]);
12
+ // Task-lifetime aggregation spans every Role, beyond a Worker/Reviewer
13
+ // Assignment. Reading the Task record alone does not authorize these facts.
14
+ taskActor(options.environment, task.id);
15
+ assertContextRecordReadable(store, task.id, "task", task.id, options.environment);
16
+ const limit = Number(parsed.options.get("--limit") ?? 0), offset = Number(parsed.options.get("--offset") ?? 0);
17
+ if (!Number.isSafeInteger(limit) || limit < 0 || limit > 50 || !Number.isSafeInteger(offset) || offset < 0) {
18
+ throw usageError(usage);
19
+ }
20
+ const result = readTaskUsage(store, task.id, { limit, offset, now: options.now?.() });
21
+ const money = (kind) => {
22
+ const metric = result.costs[kind];
23
+ return metric.amounts.length === 0 ? `unknown (${metric.reasons.join(", ")})`
24
+ : metric.amounts.map(amount => `${formatUsageMetric(amount)} ${amount.currency}`).join("; ");
25
+ };
26
+ return output([
27
+ `Task: ${task.id} — task lifetime, observed sources only; not a bill`,
28
+ `Tokens: ${formatUsageMetric(result.tokens)}`,
29
+ `Tool calls: ${formatUsageMetric(result.toolCalls)}`,
30
+ `Elapsed: ${formatUsageMetric(result.elapsedSeconds, "s")}`,
31
+ `Native execution: ${formatUsageMetric(result.executionSeconds, "s")} (not billable duration)`,
32
+ `Upstream actual: ${money("actual")}`,
33
+ `Estimated: ${money("estimated")}`,
34
+ `History: ${result.history.complete ? "all retained facts" : "bounded partial facts"}`,
35
+ `Details: --limit 20 --offset ${result.details.nextOffset ?? 0}`,
36
+ ...(limit === 0 ? [] : [JSON.stringify({ sessions: result.sessions, costs: result.costs }, null, 2)])
37
+ ].join("\n") + "\n", result);
38
+ }