@zq-silk/yui 1.1.0 → 2.0.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 (67) hide show
  1. package/ARCHITECTURE.md +2 -1
  2. package/ARCHITECTURE.zh-CN.md +2 -1
  3. package/README.md +3 -1
  4. package/dist/cli/commandCatalog.js +66 -54
  5. package/dist/cli/managedDiagnostics.js +2 -1
  6. package/dist/cli/updatePorts.js +0 -5
  7. package/dist/cli.js +113 -107
  8. package/dist/commands/controllerCommands.js +2 -1
  9. package/dist/commands/globalRoleCommands.js +84 -43
  10. package/dist/commands/grantCommands.js +48 -8
  11. package/dist/commands/taskCommands.js +146 -196
  12. package/dist/commands/taskContextCommand.js +29 -6
  13. package/dist/commands/taskFactCommands.js +10 -69
  14. package/dist/commands/taskInputCommands.js +26 -35
  15. package/dist/commands/taskPublicationCommands.js +2 -35
  16. package/dist/commands/taskPublicationVerifyCommand.js +1 -2
  17. package/dist/commands/taskUpstreamCommands.js +2 -1
  18. package/dist/context/runContextPack.js +8 -29
  19. package/dist/context/sessionBootstrapManifest.js +5 -101
  20. package/dist/context/taskContext.js +98 -39
  21. package/dist/controller/clientRuntime.js +16 -8
  22. package/dist/controller/fileSchedulerStoreAdapter.js +24 -0
  23. package/dist/controller/runtime.js +6 -0
  24. package/dist/core/controllerClient.js +46 -21
  25. package/dist/core/protocol.js +12 -7
  26. package/dist/doctor/doctor.js +6 -3
  27. package/dist/doctor/ptyProbe.js +74 -0
  28. package/dist/doctor/ptyProbeChild.js +90 -0
  29. package/dist/errors/cliError.js +4 -4
  30. package/dist/errors/cliFailure.js +213 -0
  31. package/dist/executor/fileRoleLaunchPlanner.js +1 -4
  32. package/dist/grant/capabilityGrant.js +13 -0
  33. package/dist/grant/taskAuthorization.js +127 -0
  34. package/dist/kernel/builtinCapabilities.js +60 -8
  35. package/dist/kernel/kernelPorts.js +2 -2
  36. package/dist/output/boundedRead.js +118 -0
  37. package/dist/release/releaseWorkflowEngine.js +8 -1
  38. package/dist/runtime/managedCaller.js +2 -2
  39. package/dist/runtime/runtimeCoherence.js +10 -8
  40. package/dist/storage/sqliteSchema.js +10 -0
  41. package/dist/storage/storageVersions.js +1 -1
  42. package/dist/storage/taskStore.js +3 -0
  43. package/dist/task/leaderArchive.js +111 -0
  44. package/dist/task/leaderArchiveAuthority.js +24 -0
  45. package/dist/tmux/commandExecutor.js +7 -5
  46. package/docs/architecture/README.md +1 -0
  47. package/docs/architecture/README.zh-CN.md +1 -0
  48. package/docs/cli-information-contract.md +107 -0
  49. package/docs/cli-information-contract.zh-CN.md +81 -0
  50. package/docs/managed-turn-and-session-runtime.md +9 -0
  51. package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
  52. package/docs/plugin-sdk.md +7 -3
  53. package/docs/plugin-sdk.zh-CN.md +5 -2
  54. package/docs/release-workflow.md +19 -9
  55. package/docs/release-workflow.zh-CN.md +14 -7
  56. package/docs/storage-baseline.md +5 -0
  57. package/docs/testing/verification-levels.md +12 -2
  58. package/docs/testing/verification-levels.zh-CN.md +8 -0
  59. package/i18n/README.zh-CN.md +3 -1
  60. package/package.json +3 -3
  61. package/skills/yui-leader/SKILL.md +8 -1
  62. package/skills/yui-leader/references/authorization.md +64 -0
  63. package/skills/yui-leader/references/execution.md +18 -4
  64. package/skills/yui-leader/references/task-plugins.md +4 -2
  65. package/skills/yui-operator/SKILL.md +9 -2
  66. package/skills/yui-runtime/SKILL.md +51 -9
  67. package/skills/yui-runtime/references/publication.md +3 -1
@@ -1,13 +1,15 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
3
  import { agentExecutionComponentLabel } from "../agent/executionComponents.js";
4
+ import { assertLeaderArchiveAdmission } from "../task/leaderArchiveAuthority.js";
4
5
  import { createRun, runExecutionObservation, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
5
6
  import { isGitArtifactRefString, parseGitArtifactRef } from "../artifacts/gitArtifactRef.js";
6
7
  import { contextSnapshotRef } from "../context/contextSnapshot.js";
7
8
  import { buildRunContextDelta, buildRunContextPack, contextSnapshotDeltaRefIds, expandRunContextRef, freezeReviewStageContextSnapshot, freezeRunContextSnapshot, freezeWorkItemExecutionAssignmentContextSnapshot, readRunContextSnapshot, synthesisSourceRunIds } from "../context/runContextPack.js";
8
9
  import { createRunInput } from "../context/runInputContract.js";
9
10
  import { parseTaskCatalogOptions, readTaskCatalog, renderTaskCatalog } from "../context/taskCatalog.js";
10
- import { listContextMessages } from "../context/taskContext.js";
11
+ import { assertContextRecordReadable } from "../context/taskContext.js";
12
+ import { boundedDocument, readOptions, messageReceipt, recordPage } from "../output/boundedRead.js";
11
13
  import { referencedWakeRunIds } from "../context/wakeRunReferences.js";
12
14
  import { completeProcessing, releaseProcessing } from "../coordination/workMailbox.js";
13
15
  import { enqueueRoleRunDispatch, enqueueWork, settleExactWorkExecution } from "../coordination/workMailboxQueue.js";
@@ -36,7 +38,7 @@ import { projectReviewerAvailability } from "../review/reviewerAvailability.js";
36
38
  import { attachReviewExecutionGroup, createReviewRound, createTaskDeltaReviewRound, createTaskReviewRound, finishReviewRound, recordReviewWorkspaceDisposition, retryReviewRound, retryRunningReviewExecutionLane, retryTaskReviewRound, startReplicatedReviewRound, startReviewRound, updateReviewExecutionGroup, validateTaskReviewCandidate } from "../review/reviewRound.js";
37
39
  import { sameTaskFinalReviewContract, taskFinalReviewConfig, validateTaskFinalReviewContract } from "../review/taskFinalReviewContract.js";
38
40
  import { resolveRecordedTaskFinalReviewContract } from "../review/taskFinalReviewContractResolution.js";
39
- import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole } from "../role/role.js";
41
+ import { copyGlobalRoleToTaskRole, activeRoleAgentBinding, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole } from "../role/role.js";
40
42
  import { roleLaunchEventPayload, saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
41
43
  import { RUNTIME_SESSION_REPLACE_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
42
44
  import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
@@ -73,7 +75,7 @@ import { hasRoleLaunchContextOptions, validateConfiguredRoleSkills } from "./rol
73
75
  import { nonLeaderActivationIdentity, runTaskActivationCommand } from "./taskActivationCommands.js";
74
76
  import { assertTaskOpen, clock, exactPositionals, leaderActionEventPayload, leaderMailbox, notifyMailbox, optionalNonEmptyOption, output, parseMultiValueTail, parseTail, presentTime, recordTaskEvent, recordTaskEventRecord, requiredOption, requiredText, requireTask, roleMailbox, taskActor, taskMailbox, taskRef, trimmed } from "./taskCommandSupport.js";
75
77
  import { assertTaskCompletionPublishedTreeProof } from "./taskCompletionGate.js";
76
- import { runTaskContextCommand } from "./taskContextCommand.js";
78
+ import { runTaskContextCommand, taskRecordList } from "./taskContextCommand.js";
77
79
  import { taskBriefCommand, taskDecisionCommand, taskEventCommand, taskMilestoneCommand } from "./taskFactCommands.js";
78
80
  import { openInputRequestCount, runTaskInputCommand } from "./taskInputCommands.js";
79
81
  import { runTaskNextActionCommand } from "./taskNextActionCommand.js";
@@ -300,6 +302,29 @@ function formatCompletionBlockers(taskId, blockers) {
300
302
  return `Task ${taskId} cannot complete: ${blockers.length} blocker(s) remain.\n${lines.join("\n")}`;
301
303
  }
302
304
  export function runTaskCommand(args, store, options = {}) {
305
+ // Read-only detail documents share one bounded transport. This is not a
306
+ // mutation replay mechanism: only the explicit read paths below accept a
307
+ // document cursor; authorization still runs inside each domain command.
308
+ const detail = ["show", "next-action", "remote-delivery"].includes(args[0] ?? "")
309
+ || (["brief", "decision", "milestone", "event", "work", "wake", "review", "change-set"].includes(args[0] ?? "")
310
+ && args[1] === "show")
311
+ || (args[0] === "role" && (["show", "status"].includes(args[1] ?? "")
312
+ || args[1] === "session" && args[2] === "inspect"));
313
+ if (!detail)
314
+ return executeTaskCommand(args, store, options);
315
+ const cursorIndex = args.indexOf("--cursor");
316
+ const cursor = cursorIndex < 0 ? undefined : args[cursorIndex + 1];
317
+ if (cursorIndex >= 0 && (cursor === undefined || cursor.startsWith("--")
318
+ || args.lastIndexOf("--cursor") !== cursorIndex))
319
+ throw usageError("Detail read requires one --cursor value.");
320
+ const command = cursorIndex < 0 ? args : [...args.slice(0, cursorIndex), ...args.slice(cursorIndex + 2)];
321
+ const result = executeTaskCommand(command, store, options);
322
+ if (result.kind !== "output" || result.data === undefined)
323
+ return result;
324
+ const data = boundedDocument(result.data, `task-read:${JSON.stringify(command)}`, cursor);
325
+ return data === result.data ? result : output(`${JSON.stringify(data, null, 2)}\n`, data);
326
+ }
327
+ function executeTaskCommand(args, store, options = {}) {
303
328
  const [command, ...rest] = args;
304
329
  const delivery = command === "complete"
305
330
  || (command === "work" && ["dispatch", "synthesize", "review", "accept"].includes(rest[0] ?? ""))
@@ -1249,7 +1274,8 @@ function archiveTaskCommand(args, store, options) {
1249
1274
  const now = clock(options);
1250
1275
  const result = store.transaction((tx) => {
1251
1276
  const task = requireTask(tx, request.taskId);
1252
- const actor = taskActor(tx, options, task.id);
1277
+ const actor = options.archiveLeaderAdmission === undefined ? taskActor(tx, options, task.id)
1278
+ : (assertLeaderArchiveAdmission(tx, task.id, options.archiveLeaderAdmission), "leader");
1253
1279
  if (task.status === "archived")
1254
1280
  return { task, changed: false };
1255
1281
  if (task.status !== "completed"
@@ -1309,6 +1335,9 @@ function archiveTaskCommand(args, store, options) {
1309
1335
  })));
1310
1336
  recordTaskEvent(tx, task.id, "task.archived", {
1311
1337
  by: actor,
1338
+ ...(options.archiveLeaderAdmission === undefined ? {} : {
1339
+ authorizationSource: JSON.stringify(options.archiveLeaderAdmission.source)
1340
+ }),
1312
1341
  workspaceDisposition: request.disposition,
1313
1342
  ...(request.force ? {
1314
1343
  force: "true", cleanup: "pending",
@@ -1496,7 +1525,22 @@ export function parseTaskArchiveArguments(args, command = "archive") {
1496
1525
  const usage = "Task archive usage: "
1497
1526
  + `yui task ${command} <id> (--integrated|--abandon) [--force].`;
1498
1527
  const taskId = args[0]?.trim();
1499
- const flags = args.slice(1);
1528
+ const flags = [];
1529
+ const values = new Map();
1530
+ for (let index = 1; index < args.length; index++) {
1531
+ const arg = args[index];
1532
+ if (["--source-message", "--purpose", "--request-id"].includes(arg)) {
1533
+ if (command !== "archive" || values.has(arg) || !args[index + 1]?.trim()
1534
+ || args[index + 1].startsWith("--"))
1535
+ throw usageError(usage);
1536
+ values.set(arg, args[++index]);
1537
+ }
1538
+ else
1539
+ flags.push(arg);
1540
+ }
1541
+ if (values.size !== 0 && values.size !== 3) {
1542
+ throw usageError("Leader archive requires --source-message, --purpose and --request-id together.");
1543
+ }
1500
1544
  if (taskId === undefined
1501
1545
  || taskId.length === 0
1502
1546
  || flags.some((flag, index) => (!["--integrated", "--abandon", "--force"].includes(flag)
@@ -1512,7 +1556,11 @@ export function parseTaskArchiveArguments(args, command = "archive") {
1512
1556
  return {
1513
1557
  taskId,
1514
1558
  disposition: integrated ? "integrated" : "abandoned",
1515
- force
1559
+ force,
1560
+ ...(values.size === 0 ? {} : {
1561
+ sourceMessage: values.get("--source-message"), purpose: values.get("--purpose"),
1562
+ requestId: values.get("--request-id")
1563
+ })
1516
1564
  };
1517
1565
  }
1518
1566
  function formatProjectCommits(projects) {
@@ -1522,9 +1570,18 @@ function formatProjectCommits(projects) {
1522
1570
  export function validateTaskArchiveRequest(args, store, options = {}) {
1523
1571
  const request = parseTaskArchiveArguments(args);
1524
1572
  const task = requireTask(store, request.taskId);
1525
- const archiveActor = taskActor(store, options, task.id);
1526
- if (archiveActor === "leader") {
1527
- throw usageError("Task archive requires independent user or Operator authorization.");
1573
+ if (options.archiveLeaderAdmission !== undefined) {
1574
+ assertLeaderArchiveAdmission(store, task.id, options.archiveLeaderAdmission);
1575
+ if (request.force || request.disposition !== "integrated") {
1576
+ throw usageError("Leader ordinary archive admission never authorizes force or abandonment.");
1577
+ }
1578
+ }
1579
+ const archiveActor = options.archiveLeaderAdmission === undefined ? taskActor(store, options, task.id) : "leader";
1580
+ if (archiveActor === "leader" && options.archiveLeaderAdmission === undefined) {
1581
+ if (request.force || request.disposition === "abandoned") {
1582
+ throw usageError("Force or abandon archive requires independent user or Operator authorization.");
1583
+ }
1584
+ throw usageError("Leader archive requires a source-authorized Controller operation; use task archive with --source-message, --purpose and --request-id.");
1528
1585
  }
1529
1586
  if (task.status !== "archived"
1530
1587
  && task.status !== "completed"
@@ -1581,17 +1638,21 @@ function taskMessageCommand(args, store, options) {
1581
1638
  }
1582
1639
  if (command === "show") {
1583
1640
  const usage = "Task message show usage: yui task message show <task/message>.";
1584
- exactPositionals(rest, 1, usage);
1585
- const ref = taskRecordReference(rest[0], "message", "Message reference", options);
1586
- const message = listContextMessages(store, ref.taskId, options.environment)
1587
- .find((entry) => entry.id === ref.localId);
1641
+ const parsed = readOptions(rest, ["--cursor"]);
1642
+ exactPositionals(parsed.positionals, 1, usage);
1643
+ const ref = taskRecordReference(parsed.positionals[0], "message", "Message reference", options);
1644
+ assertContextRecordReadable(store, ref.taskId, "task-message", ref.localId, options.environment);
1645
+ const message = store.queryContextRecords(ref.taskId, {
1646
+ family: "task-message", ids: [ref.localId], limit: 1
1647
+ }).records[0]?.value;
1588
1648
  if (message === undefined)
1589
1649
  throw dataError("Message is unavailable in the caller's scope.");
1590
1650
  const continuationRun = message.continuation?.runId === undefined
1591
1651
  ? null : store.getRun(ref.taskId, message.continuation.runId);
1592
1652
  const expanded = { ...expandTaskMessageResult(message, (task, run) => store.getRun(task, run)),
1593
1653
  ...(continuationRun === null ? {} : { execution: runExecutionObservation(continuationRun, store.getTaskRoleSessionSet(ref.taskId, continuationRun.roleName)?.providerBinding, store.listEvents(ref.taskId)) }) };
1594
- return { kind: "output", output: `${JSON.stringify(expanded, null, 2)}\n`, data: expanded };
1654
+ const data = boundedDocument(expanded, `task:${ref.taskId}/message/${ref.localId}`, parsed.cursor);
1655
+ return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
1595
1656
  }
1596
1657
  if (command === "send") {
1597
1658
  const usage = "Task message send usage: yui task message send <id> (<body>|--body-file <path|->) [--intent record|discuss|develop] [--request-id <key>] [--to <role> --work-item <id>|--review-round <id>].";
@@ -1613,60 +1674,20 @@ function taskMessageCommand(args, store, options) {
1613
1674
  // A user/operator submission returns the unified §2.5 feedback; render each
1614
1675
  // facet on its own line and expose the structure to non-text callers.
1615
1676
  if (result.feedback !== undefined) {
1616
- return output(renderSubmissionFeedback(result.feedback), { taskId: result.task.id, message: result.message, submission: result.feedback });
1677
+ return output(renderSubmissionFeedback(result.feedback), { taskId: result.task.id, message: messageReceipt(result.message), submission: result.feedback });
1617
1678
  }
1618
1679
  const reason = result.message.continuation?.notDeliveredReason;
1619
1680
  const delivery = reason !== undefined ? { state: "not-delivered", reason }
1620
1681
  : recipientRole !== undefined || result.queuedForLeader
1621
1682
  ? { state: "queued" } : { state: "saved" };
1622
- return output(`Saved message ${result.message.id} to ${result.task.id} (${delivery.state}${reason === undefined ? "" : `: ${reason}`}).\n`, { taskId: result.task.id, message: result.message, delivery });
1683
+ return output(`Saved message ${result.message.id} to ${result.task.id} (${delivery.state}${reason === undefined ? "" : `: ${reason}`}).\n`, { taskId: result.task.id, message: messageReceipt(result.message), delivery });
1623
1684
  }
1624
1685
  if (command === "queue")
1625
1686
  return queueTaskMessage(rest, store, options);
1626
1687
  if (command === "steer")
1627
1688
  return steerTaskMessage(rest, store, options);
1628
1689
  if (command === "list") {
1629
- const messageListUsage = "Task message list usage: yui task message list <id> [--after <timestamp>] [--limit <n>].";
1630
- const parsed = parseTail(rest, new Set(["--after", "--limit"]), messageListUsage);
1631
- exactPositionals(parsed.positionals, 1, messageListUsage);
1632
- const task = requireTask(store, parsed.positionals[0]);
1633
- let messages = listContextMessages(store, task.id, options.environment);
1634
- const after = optionalNonEmptyOption(parsed.options, "--after");
1635
- if (after !== undefined) {
1636
- const afterMs = Date.parse(after);
1637
- if (!Number.isFinite(afterMs))
1638
- throw usageError("--after must be a valid timestamp.", messageListUsage);
1639
- messages = messages.filter((m) => Date.parse(m.createdAt) > afterMs);
1640
- }
1641
- const limit = optionalNonEmptyOption(parsed.options, "--limit");
1642
- if (limit !== undefined) {
1643
- const n = Number(limit);
1644
- if (!Number.isSafeInteger(n) || n <= 0)
1645
- throw usageError("--limit must be a positive integer.", messageListUsage);
1646
- messages = messages.slice(-n);
1647
- }
1648
- if (messages.length === 0)
1649
- return "No messages found.\n";
1650
- const retirements = new Map(store.listEvents(task.id).flatMap((event) => {
1651
- const retirement = taskRecordRetirement(event);
1652
- return retirement?.recordKind === "message"
1653
- ? [[retirement.recordId, retirement]]
1654
- : [];
1655
- }));
1656
- const timeZone = store.getConfig().timeZone;
1657
- return `${renderTable(`Task messages: ${task.id}`, [
1658
- { header: "Message", minWidth: 7, maxWidth: 18 },
1659
- { header: "Status", minWidth: 6, maxWidth: 9 },
1660
- { header: "Author", minWidth: 6, maxWidth: 18 },
1661
- { header: "Created", minWidth: 10, maxWidth: 28 },
1662
- { header: "Body", minWidth: 8, maxWidth: 72 }
1663
- ], messages.map((message) => [
1664
- message.id,
1665
- retirements.has(message.id) ? "retired" : "active",
1666
- taskMessageAuthorLabel(message.author),
1667
- presentTime(message.createdAt, timeZone),
1668
- message.body
1669
- ]), defaultTableWidth())}\n`;
1690
+ return taskRecordList(rest, store, "task-message", options.environment);
1670
1691
  }
1671
1692
  if (command === "update") {
1672
1693
  return updateMessage(rest, store, options);
@@ -1719,7 +1740,7 @@ function queueTaskInput(taskId, input, store, options) {
1719
1740
  const delivery = result.idempotentReplay ? { state: "idempotent-replay" }
1720
1741
  : reason !== undefined ? { state: "not-delivered", reason }
1721
1742
  : recipientRole !== undefined || result.actor !== "leader" ? { state: "queued" } : { state: "saved" };
1722
- return output(`Queued message ${result.message.id} to ${result.task.id} (${delivery.state}${reason === undefined ? "" : `: ${reason}`}).\n`, { taskId: result.task.id, message: result.message, delivery });
1743
+ return output(`Queued message ${result.message.id} to ${result.task.id} (${delivery.state}${reason === undefined ? "" : `: ${reason}`}).\n`, { taskId: result.task.id, message: messageReceipt(result.message), delivery });
1723
1744
  }
1724
1745
  /**
1725
1746
  * `steer` targets only the exact current native Turn. It always persists the
@@ -1772,7 +1793,7 @@ function steerTaskInput(taskId, input, store, options) {
1772
1793
  // unknown steer is visibly not-submitted/pending/accepted/rejected/
1773
1794
  // delivery-unknown rather than silently re-attempted (message-5 gap D).
1774
1795
  const controlState = taskMessageInputControlState(result.message);
1775
- return output(`Steer message ${result.message.id} already recorded (idempotent-replay${controlState === undefined ? "" : `; control ${controlState}`}).\n`, { taskId: result.task.id, message: result.message,
1796
+ return output(`Steer message ${result.message.id} already recorded (idempotent-replay${controlState === undefined ? "" : `; control ${controlState}`}).\n`, { taskId: result.task.id, message: messageReceipt(result.message),
1776
1797
  steer: { state: "idempotent-replay",
1777
1798
  ...(controlState === undefined ? {} : { control: controlState }) } });
1778
1799
  }
@@ -1780,7 +1801,7 @@ function steerTaskInput(taskId, input, store, options) {
1780
1801
  // match, and writer fence, all from durable state without a live Endpoint.
1781
1802
  const resolution = resolveTaskInputControl(store, result.task.id, roleName, "steer", expectedTarget);
1782
1803
  if (resolution.outcome !== "ready") {
1783
- return output(`Steer message ${result.message.id} saved but not delivered (${resolution.code}: ${resolution.detail}).\n`, { taskId: result.task.id, message: result.message,
1804
+ return output(`Steer message ${result.message.id} saved but not delivered (${resolution.code}: ${resolution.detail}).\n`, { taskId: result.task.id, message: messageReceipt(result.message),
1784
1805
  steer: { state: "not-steered", code: resolution.code, detail: resolution.detail } });
1785
1806
  }
1786
1807
  if (!leaderTarget) {
@@ -1792,7 +1813,7 @@ function steerTaskInput(taskId, input, store, options) {
1792
1813
  || recipient.workItemId !== current.workItemId
1793
1814
  || recipient.reviewRoundId !== current.reviewRoundId
1794
1815
  || messageContinuationBlocker(store, result.message) !== undefined) {
1795
- return output(`Steer message ${result.message.id} saved but not delivered (TARGET_CHANGED: active Assignment differs).\n`, { taskId: result.task.id, message: result.message,
1816
+ return output(`Steer message ${result.message.id} saved but not delivered (TARGET_CHANGED: active Assignment differs).\n`, { taskId: result.task.id, message: messageReceipt(result.message),
1796
1817
  steer: { state: "not-steered", code: "TARGET_CHANGED", detail: "The input does not belong to the active Turn's Assignment." } });
1797
1818
  }
1798
1819
  }
@@ -2057,7 +2078,7 @@ function updateMessage(args, store, options) {
2057
2078
  ? leaderMailbox(result.task.id) : taskMailbox(result.task.id));
2058
2079
  }
2059
2080
  return output(`${result.changed ? "Updated" : "Unchanged"} Task Message ${result.task.id}/${result.message.id}\n`, {
2060
- message: result.message
2081
+ message: messageReceipt(result.message)
2061
2082
  });
2062
2083
  }
2063
2084
  function retireMessage(args, store, options) {
@@ -2481,10 +2502,16 @@ function taskRoleSessionCommand(args, store, options) {
2481
2502
  ].join("\n") + "\n"
2482
2503
  + `\n${renderRoleLaunchComparison(role, active.effective)}\n`
2483
2504
  + (runConfigurationDetail === "" ? "" : `\n${runConfigurationDetail}\n`), {
2484
- task,
2485
- role,
2505
+ taskId: task.id,
2506
+ roleName: role.name,
2507
+ desired: { launchRevision: role.launchRevision, activeAgentId: role.activeAgentId,
2508
+ binding: activeRoleAgentBinding(role) },
2486
2509
  session: active,
2487
- providerBinding: binding,
2510
+ providerBinding: binding === null ? null : {
2511
+ providerNamespace: binding.providerNamespace, accountScope: binding.accountScope,
2512
+ currentConversation: currentProviderConversation(binding),
2513
+ authority: binding.authority, run: binding.run, goal: binding.goal
2514
+ },
2488
2515
  retry: providerRetryProjection(binding),
2489
2516
  ...(host === undefined ? {} : { host }),
2490
2517
  ...(runConfiguration === undefined ? {} : { runConfiguration })
@@ -2652,31 +2679,20 @@ function addTaskRole(args, store, options) {
2652
2679
  ].join("\n").concat("\n");
2653
2680
  }
2654
2681
  function listTaskRoles(args, store, options) {
2655
- exactPositionals(args, 1, "Task role list usage: yui task role list <task>.");
2656
- const task = requireTask(store, args[0]);
2682
+ const parsed = readOptions(args);
2683
+ exactPositionals(parsed.positionals, 1, "Task role list usage: yui task role list <task> [--limit <n>] [--cursor <cursor>].");
2684
+ const task = requireTask(store, parsed.positionals[0]);
2657
2685
  const roles = store.listRoles(task.id);
2658
- const statuses = inspectTaskRoleRuntimeStatuses(task.id, roles, store, options.runtime?.inspectTaskRolePanes?.(task.id) ?? [], options.now?.() ?? new Date()).map(status => withTaskRoleHostObservation(status, options.liveHostObservations?.[status.roleName]));
2659
- if (statuses.length === 0)
2660
- return output("No roles assigned.\n", { roles: statuses });
2661
- return output(`${renderTable(`Task roles: ${task.id}`, [
2662
- { header: "Role", minWidth: 4, maxWidth: 24 },
2663
- { header: "Agent", minWidth: 5, maxWidth: 20 },
2664
- { header: "Health", minWidth: 6, maxWidth: 15 },
2665
- { header: "Open input", minWidth: 5, maxWidth: 10 },
2666
- { header: "Active work", minWidth: 10, maxWidth: 34 },
2667
- { header: "Last turn", minWidth: 10, maxWidth: 28 },
2668
- { header: "Native session", minWidth: 10, maxWidth: 28 },
2669
- { header: "tmux", minWidth: 6, maxWidth: 22 }
2670
- ], statuses.map((status) => [
2671
- status.roleName,
2672
- status.agentId,
2673
- status.health,
2674
- taskRoleOpenInputLabel(status),
2675
- taskRoleActiveWorkLabel(status),
2676
- taskRoleLastRunLabel(status),
2677
- taskRoleNativeSessionLabel(status),
2678
- taskRoleTmuxLabel(status)
2679
- ]), defaultTableWidth())}\n`, { roles: statuses });
2686
+ const statuses = inspectTaskRoleRuntimeStatuses(task.id, roles, store, options.runtime?.inspectTaskRolePanes?.(task.id) ?? [], options.now?.() ?? new Date());
2687
+ const data = recordPage(statuses.map(status => ({
2688
+ roleName: status.roleName, agentId: status.agentId, recordedHealth: status.health,
2689
+ hostObservation: "not-requested",
2690
+ openInput: taskRoleOpenInputLabel(status), activeWork: taskRoleActiveWorkLabel(status),
2691
+ lastRun: taskRoleLastRunLabel(status), nativeSession: taskRoleNativeSessionLabel(status),
2692
+ tmux: taskRoleTmuxLabel(status),
2693
+ read: `task role status ${task.id} ${status.roleName}`
2694
+ })), `task:${task.id}/role-status`, parsed);
2695
+ return output(`${JSON.stringify(data)}\n`, data);
2680
2696
  }
2681
2697
  function taskRoleStatus(args, store, options) {
2682
2698
  exactPositionals(args, 2, "Task role status usage: yui task role status <task> <role>.");
@@ -3011,7 +3027,7 @@ function taskWorkCommand(args, store, options) {
3011
3027
  if (command === "create")
3012
3028
  return createWork(rest, store, options);
3013
3029
  if (command === "list")
3014
- return listWork(rest, store);
3030
+ return taskRecordList(rest, store, "work-item", options.environment);
3015
3031
  if (command === "show")
3016
3032
  return showWork(rest, store, options);
3017
3033
  if (command === "edit")
@@ -3882,36 +3898,6 @@ function retireWork(args, store, options) {
3882
3898
  options.runtime?.notifyMailboxChanged({ kind: "task", taskId: retired.taskId });
3883
3899
  return output(`Retired Work Item ${retired.id}\n`, { workItem: retired });
3884
3900
  }
3885
- function listWork(args, store) {
3886
- exactPositionals(args, 1, "Task work list usage: yui task work list <task>.");
3887
- const task = requireTask(store, args[0]);
3888
- const items = store.listWorkItems(task.id);
3889
- const runs = store.listRuns(task.id);
3890
- const sessionSets = store.listRoleSessionSets(task.id);
3891
- const executions = items.map((item) => projectWorkItemExecution(item, runs, sessionSets, store));
3892
- const rendered = items.length === 0
3893
- ? "No work items found.\n"
3894
- : `${renderTable(`Task work: ${task.id}`, [
3895
- { header: "Work", minWidth: 6, maxWidth: 20 },
3896
- { header: "Status", minWidth: 6, maxWidth: 12 },
3897
- { header: "Role", minWidth: 4, maxWidth: 18 },
3898
- { header: "Shape", minWidth: 6, maxWidth: 10 },
3899
- { header: "Execution", minWidth: 12, maxWidth: 42 },
3900
- { header: "Next / Owner", minWidth: 12, maxWidth: 36 },
3901
- { header: "Title", minWidth: 8, maxWidth: 64 },
3902
- { header: "Outcome", minWidth: 8, maxWidth: 40 }
3903
- ], items.map((item, index) => [
3904
- item.id,
3905
- presentWorkStatus(item.status),
3906
- item.assignee ?? "Leader",
3907
- executions[index].shape,
3908
- compactWorkItemExecution(executions[index]),
3909
- `${executions[index].nextAction.kind} / ${executions[index].nextAction.owners.join(",") || "none"}`,
3910
- item.title,
3911
- item.outcome ?? "-"
3912
- ]), defaultTableWidth())}\n`;
3913
- return output(rendered, { workItems: items, executions });
3914
- }
3915
3901
  function showWork(args, store, options) {
3916
3902
  exactPositionals(args, 1, "Task work show usage: yui task work show <work>.");
3917
3903
  const item = requireWorkItem(store, args[0], options);
@@ -3934,12 +3920,6 @@ function showWork(args, store, options) {
3934
3920
  ].join("\n");
3935
3921
  return output(`${rendered}\n`, { workItem: item, execution });
3936
3922
  }
3937
- function compactWorkItemExecution(projection) {
3938
- if (projection.shape === "direct")
3939
- return `main=${projection.mainRun.status}`;
3940
- const counts = projection.laneCounts;
3941
- return `lanes ${counts.running}/${counts.succeeded}/${counts.needsAttention}/${counts.failed}/${counts.unknown}; ${projection.synthesis.status}`;
3942
- }
3943
3923
  function renderWorkItemExecutionProjection(projection) {
3944
3924
  return [
3945
3925
  `Execution Shape: ${projection.shape}`,
@@ -4402,7 +4382,7 @@ function retryFailedTaskReviewRound(args, store, options) {
4402
4382
  function taskRunCommand(args, store, options) {
4403
4383
  const [command, ...rest] = args;
4404
4384
  if (command === "list")
4405
- return output(listRuns(rest, store, options));
4385
+ return listRuns(rest, store, options);
4406
4386
  if (command === "show")
4407
4387
  return showRun(rest, store, options);
4408
4388
  if (command === "context")
@@ -4673,7 +4653,7 @@ function runContextCommand(args, store, options) {
4673
4653
  const [first, ...rest] = args;
4674
4654
  if (first === "expand") {
4675
4655
  const usage = "Task run context expand usage: yui task run context expand <task>/<run> <ref-id> --store <store> [--mode full].";
4676
- const parsed = parseTail(rest, new Set(["--store", "--mode"]), usage);
4656
+ const parsed = parseTail(rest, new Set(["--store", "--mode", "--cursor"]), usage);
4677
4657
  exactPositionals(parsed.positionals, 2, usage);
4678
4658
  const refStore = requiredOption(parsed.options, "--store");
4679
4659
  const mode = parsed.options.get("--mode");
@@ -4683,24 +4663,29 @@ function runContextCommand(args, store, options) {
4683
4663
  const { taskId, runId } = parseRunContextReference(parsed.positionals[0]);
4684
4664
  authorizeRunContext(store, taskId, runId, options.environment);
4685
4665
  const expanded = store.transaction((tx) => expandRunContextRef(tx, taskId, runId, parsed.positionals[1], refStore));
4686
- return output(`${JSON.stringify(expanded, null, 2)}\n`, { context: expanded });
4666
+ const data = boundedDocument(expanded, `run:${taskId}/${runId}/${refStore}/${parsed.positionals[1]}`, parsed.options.get("--cursor"));
4667
+ return output(`${JSON.stringify(data, null, 2)}\n`, { context: data });
4687
4668
  }
4688
4669
  if (first === "delta") {
4689
- if (rest.length !== 3 || rest[1] !== "--after") {
4690
- throw usageError("Task run context delta usage: yui task run context delta <task>/<run> --after <cursor>.");
4670
+ const parsed = readOptions(rest, ["--after", "--cursor"]);
4671
+ if (parsed.positionals.length !== 1 || !parsed.values.has("--after")) {
4672
+ throw usageError("Task run context delta usage: yui task run context delta <task>/<run> --after <digest> [--cursor <cursor>].");
4691
4673
  }
4692
- const { taskId, runId } = parseRunContextReference(rest[0]);
4674
+ const { taskId, runId } = parseRunContextReference(parsed.positionals[0]);
4693
4675
  authorizeRunContext(store, taskId, runId, options.environment);
4694
- const delta = store.transaction((tx) => (buildRunContextDelta(tx, taskId, runId, rest[2])));
4695
- return output(`${JSON.stringify(delta, null, 2)}\n`, { contextDelta: delta });
4676
+ const delta = store.transaction((tx) => (buildRunContextDelta(tx, taskId, runId, parsed.values.get("--after"))));
4677
+ const data = boundedDocument(delta, `run:${taskId}/${runId}/delta/${parsed.values.get("--after")}`, parsed.cursor);
4678
+ return output(`${JSON.stringify(data)}\n`, { contextDelta: data });
4696
4679
  }
4697
- if (first === undefined || rest.length !== 0) {
4680
+ const parsed = readOptions(rest, ["--cursor"]);
4681
+ if (first === undefined || parsed.positionals.length !== 0) {
4698
4682
  throw usageError("Task run context usage: yui task run context <task>/<run>.");
4699
4683
  }
4700
4684
  const { taskId, runId } = parseRunContextReference(first);
4701
4685
  authorizeRunContext(store, taskId, runId, options.environment);
4702
4686
  const pack = store.transaction((tx) => buildRunContextPack(tx, taskId, runId));
4703
- return output(`${JSON.stringify(pack, null, 2)}\n`, { context: pack });
4687
+ const data = boundedDocument(pack, `run:${taskId}/${runId}/context`, parsed.cursor);
4688
+ return output(`${JSON.stringify(data)}\n`, { context: data });
4704
4689
  }
4705
4690
  function parseRunContextReference(value) {
4706
4691
  const [taskId, runId, extra] = value.split("/");
@@ -4736,41 +4721,12 @@ function authorizeRunContext(store, taskId, runId, environment) {
4736
4721
  }
4737
4722
  }
4738
4723
  function listRuns(args, store, options) {
4739
- const usage = "Task run list usage: yui task run list <task|task/work>.";
4740
- exactPositionals(args, 1, usage);
4741
4724
  const reference = args[0];
4742
- const task = store.getTask(reference);
4743
- const item = task === null ? requireWorkItem(store, reference, options) : null;
4744
- const taskId = task?.id ?? item.taskId;
4745
- const runs = store.listRuns(taskId).filter((run) => (item === null || run.workItemId === item.id));
4746
- if (runs.length === 0)
4747
- return "No AgentRuns found.\n";
4748
- const events = store.listEvents(taskId);
4749
- return `${renderTable(`AgentRuns: ${item?.id ?? taskId}`, [
4750
- { header: "AgentRun", minWidth: 6, maxWidth: 20 },
4751
- { header: "Role", minWidth: 4, maxWidth: 22 },
4752
- { header: "Subject", minWidth: 7, maxWidth: 24 },
4753
- { header: "Purpose", minWidth: 6, maxWidth: 10 },
4754
- { header: "Mode", minWidth: 4, maxWidth: 8 },
4755
- { header: "Effective", minWidth: 10, maxWidth: 30 },
4756
- { header: "Profile", minWidth: 7, maxWidth: 8 },
4757
- { header: "Permission", minWidth: 8, maxWidth: 16 },
4758
- { header: "Status", minWidth: 6, maxWidth: 12 },
4759
- { header: "History", minWidth: 7, maxWidth: 9 },
4760
- { header: "Summary", minWidth: 8, maxWidth: 58 }
4761
- ], runs.map((run) => [
4762
- run.id,
4763
- run.roleName,
4764
- run.workItemId ?? (run.reviewRoundId === undefined ? "task" : `review:${run.reviewRoundId}`),
4765
- run.purpose,
4766
- run.mode,
4767
- `${run.effective.agentId}/${run.effective.adapterId} r${run.effective.sourceDesiredRevision}`,
4768
- run.effective.profileAccess,
4769
- run.effective.permission.strategy,
4770
- run.status,
4771
- isTaskRecordRetired(events, "run", run.id) ? "retired" : "active",
4772
- run.result?.output ?? run.result?.diagnostic ?? "-"
4773
- ]), defaultTableWidth())}\n`;
4725
+ if (reference?.includes("/")) {
4726
+ const item = requireWorkItem(store, reference, options);
4727
+ return taskRecordList([item.taskId, "--work-item", item.id, ...args.slice(1)], store, "run", options.environment);
4728
+ }
4729
+ return taskRecordList(args, store, "run", options.environment);
4774
4730
  }
4775
4731
  /**
4776
4732
  * Settles only the known bootstrap split where a failed Task-final AgentRun
@@ -5683,8 +5639,8 @@ function retryFailedReviewRun(previous, store, options, now) {
5683
5639
  /** AgentRun details are retained audit evidence; continuation uses durable Task state. */
5684
5640
  function showRun(args, store, options) {
5685
5641
  const usage = "Task run show usage: yui task run show <task>/<run> [--json].";
5686
- const asJson = args.includes("--json");
5687
- const positionals = args.filter((arg) => arg !== "--json");
5642
+ const parsed = readOptions(args.filter((arg) => arg !== "--json"), ["--cursor"]);
5643
+ const positionals = parsed.positionals;
5688
5644
  exactPositionals(positionals, 1, usage);
5689
5645
  const data = store.transaction((tx) => {
5690
5646
  const run = requireRun(tx, positionals[0], options);
@@ -5693,9 +5649,9 @@ function showRun(args, store, options) {
5693
5649
  .find((entry) => entry?.recordKind === "run" && entry.recordId === run.id) ?? null;
5694
5650
  return { run: run, retirement, execution: runExecutionObservation(run, tx.getTaskRoleSessionSet(run.taskId, run.roleName)?.providerBinding, tx.listEvents(run.taskId)) };
5695
5651
  });
5696
- if (asJson) {
5697
- return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
5698
- }
5652
+ const bounded = boundedDocument(data, `run:${data.run.taskId}/${data.run.id}`, parsed.cursor);
5653
+ if ("contentPage" in bounded)
5654
+ return output(`${JSON.stringify(bounded, null, 2)}\n`, bounded);
5699
5655
  return {
5700
5656
  kind: "output",
5701
5657
  output: `Delivery observation: ${data.execution.delivery}\n` + renderRunShow(data.run, data.retirement),
@@ -6874,25 +6830,16 @@ function taskWakeDispatch(args, store, options) {
6874
6830
  function taskWakeInspectionCommand(args, store) {
6875
6831
  const [command, ...rest] = args;
6876
6832
  if (command === "list") {
6877
- const usage = "Task wake list usage: yui task wake list <task>.";
6878
- exactPositionals(rest, 1, usage);
6879
- const task = requireTask(store, rest[0]);
6833
+ const usage = "Task wake list usage: yui task wake list <task> [--limit <n>] [--cursor <cursor>].";
6834
+ const parsed = readOptions(rest);
6835
+ exactPositionals(parsed.positionals, 1, usage);
6836
+ const task = requireTask(store, parsed.positionals[0]);
6880
6837
  const wakes = store.listTaskWakes(task.id);
6881
- if (wakes.length === 0) {
6882
- return output(`No wakes recorded for ${task.id}.\n`, { taskId: task.id, wakes: [] });
6883
- }
6884
- const timeZone = store.getConfig().timeZone;
6885
- return output(`${renderTable(`Wakes: ${task.id}`, [
6886
- { header: "Wake", minWidth: 8, maxWidth: 18 },
6887
- { header: "Status", minWidth: 8, maxWidth: 12 },
6888
- { header: "Reasons", minWidth: 10, maxWidth: 40 },
6889
- { header: "Dispatched", minWidth: 10, maxWidth: 28 }
6890
- ], wakes.map((wake) => [
6891
- wake.id,
6892
- wake.status,
6893
- wake.reasons.map(renderWakeReason).join(", "),
6894
- presentTime(wake.createdAt, timeZone)
6895
- ]), defaultTableWidth())}\n`, { taskId: task.id, wakes });
6838
+ const data = recordPage(wakes.map(wake => ({
6839
+ id: wake.id, status: wake.status, fromCursor: wake.fromCursor, toCursor: wake.toCursor,
6840
+ createdAt: wake.createdAt, read: `task wake show ${task.id} ${wake.id}`
6841
+ })), `task:${task.id}/wake`, parsed);
6842
+ return output(`${JSON.stringify(data)}\n`, data);
6896
6843
  }
6897
6844
  if (command === "show") {
6898
6845
  const usage = "Task wake show usage: yui task wake show <task> <wake>.";
@@ -6962,9 +6909,12 @@ function taskWakeInspectionCommand(args, store) {
6962
6909
  taskId: task.id,
6963
6910
  deliveryEvents,
6964
6911
  wake,
6965
- events,
6966
- messages,
6967
- runs
6912
+ events: events.map(event => ({ id: event.id, type: event.type, createdAt: event.createdAt,
6913
+ read: `task event show ${task.id} ${event.id}` })),
6914
+ messages: messages.map(message => ({ id: message.id, kind: message.kind, author: message.author,
6915
+ resultRef: message.resultRef, read: `task message show ${task.id}/${message.id}` })),
6916
+ runs: runs.map(run => ({ id: run.id, status: run.status, purpose: run.purpose, roleName: run.roleName,
6917
+ read: `task run show ${task.id}/${run.id}` }))
6968
6918
  });
6969
6919
  }
6970
6920
  throw usageError(command === undefined