@zq-silk/yui 2.8.1 → 2.10.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 (119) hide show
  1. package/README.md +5 -3
  2. package/dist/cli/commandCatalog.js +56 -21
  3. package/dist/cli/helpRenderer.js +3 -1
  4. package/dist/cli/interactionPolicy.js +2 -2
  5. package/dist/commands/durableJobCommands.js +50 -0
  6. package/dist/commands/globalRoleCommands.js +30 -9
  7. package/dist/commands/inboxCommands.js +79 -22
  8. package/dist/commands/projectCommands.js +4 -33
  9. package/dist/commands/scheduleCommands.js +45 -0
  10. package/dist/commands/taskActivationCommands.js +19 -5
  11. package/dist/commands/taskCommands.js +8 -2
  12. package/dist/commands/taskFactCommands.js +24 -12
  13. package/dist/commands/taskProjectDeliveryCommand.js +56 -0
  14. package/dist/commands/taskPublicationCommands.js +6 -3
  15. package/dist/commands/taskPublicationVerifyCommand.js +74 -28
  16. package/dist/commands/taskRemoteDeliveryCommand.js +5 -3
  17. package/dist/context/runContextPack.js +25 -4
  18. package/dist/context/runInputContract.js +1 -0
  19. package/dist/context/taskContext.js +53 -16
  20. package/dist/context/taskPhase.js +99 -0
  21. package/dist/controlPlaneCli.js +38 -17
  22. package/dist/controller/controller.js +35 -0
  23. package/dist/controller/fileSchedulerStoreAdapter.js +75 -16
  24. package/dist/controller/globalInputDelivery.js +17 -7
  25. package/dist/controller/inboxTurnCheck.js +64 -0
  26. package/dist/controller/jobControl.js +42 -12
  27. package/dist/controller/jobDefinitionControl.js +304 -0
  28. package/dist/controller/jobSupervisor.js +47 -32
  29. package/dist/controller/providerRetryAdmission.js +2 -0
  30. package/dist/controller/runtime.js +11 -2
  31. package/dist/controller/scheduleControl.js +176 -0
  32. package/dist/coordination/workMailbox.js +3 -3
  33. package/dist/external/githubPublicationVerifier.js +14 -5
  34. package/dist/external/gitlabPublicationVerifier.js +12 -29
  35. package/dist/grant/taskAuthorization.js +7 -2
  36. package/dist/interaction/notificationPresentation.js +12 -3
  37. package/dist/interaction/operatorPresentation.js +5 -0
  38. package/dist/job/builtinJob.js +58 -0
  39. package/dist/job/durableJob.js +72 -49
  40. package/dist/job/jobDefinition.js +68 -0
  41. package/dist/job/jobRunner.js +2 -1
  42. package/dist/job/projectRefreshRunner.js +48 -0
  43. package/dist/message/globalInboxRecovery.js +44 -0
  44. package/dist/message/globalInterrupt.js +1 -1
  45. package/dist/message/inboxReceipt.js +14 -6
  46. package/dist/message/inboxTurn.js +86 -0
  47. package/dist/message/message.js +11 -0
  48. package/dist/message/taskInboxOwnership.js +20 -6
  49. package/dist/repository/projectRefresh.js +58 -0
  50. package/dist/repository/taskWorkspacePreparer.js +2 -1
  51. package/dist/repository/workspaceCleanupInspection.js +6 -1
  52. package/dist/schedule/schedule.js +108 -0
  53. package/dist/scheduler/operatorEvent.js +2 -0
  54. package/dist/scheduler/operatorInputNotificationProcessor.js +8 -0
  55. package/dist/storage/contextRecords.js +8 -0
  56. package/dist/storage/jobSchema.js +15 -0
  57. package/dist/storage/maintenanceCaller.js +1 -1
  58. package/dist/storage/recordValidation.js +5 -0
  59. package/dist/storage/scheduleSchema.js +12 -0
  60. package/dist/storage/sqliteSchema.js +95 -24
  61. package/dist/storage/sqliteStore.js +123 -10
  62. package/dist/storage/storageVersions.js +1 -1
  63. package/dist/storage/storeRpc.js +5 -0
  64. package/dist/storage/upgrade/jobDefinitionMigration.js +22 -0
  65. package/dist/task/archiveDiagnostics.js +1 -1
  66. package/dist/task/archivePreflight.js +1 -1
  67. package/dist/task/projectDeliveryScope.js +20 -0
  68. package/dist/task/publicationToolObservation.js +126 -0
  69. package/dist/task/remoteDelivery.js +19 -8
  70. package/dist/task/remoteDeliveryService.js +5 -4
  71. package/dist/task/taskActivationService.js +35 -0
  72. package/dist/web/assets/client/app/settings.js +7 -1
  73. package/dist/web/assets/client/i18n/en.js +65 -2
  74. package/dist/web/assets/client/i18n/zh-CN.js +65 -2
  75. package/dist/web/assets/client/index.js +2 -0
  76. package/dist/web/assets/client/views/jobs.js +222 -0
  77. package/dist/web/assets/client/views/task/deliveryResults.js +6 -2
  78. package/dist/web/assets/client/views/task/page.js +11 -1
  79. package/dist/web/assets/shared/manageNav.js +1 -0
  80. package/dist/web/assets/shared/taskTabs.js +2 -2
  81. package/dist/web/assets/shell/settingsContent.js +3 -0
  82. package/dist/web/assets/styles/index.js +2 -0
  83. package/dist/web/assets/styles/views/jobs.js +19 -0
  84. package/dist/web/controllerWeb.js +73 -21
  85. package/dist/web/taskPreviews.js +21 -13
  86. package/dist/web/webAccess.js +109 -0
  87. package/dist/web/webJobs.js +52 -0
  88. package/dist/web/webServer.js +76 -44
  89. package/docs/architecture/capabilities-and-resources.md +6 -4
  90. package/docs/architecture/capabilities-and-resources.zh-CN.md +5 -4
  91. package/docs/cli-information-contract.md +57 -4
  92. package/docs/cli-information-contract.zh-CN.md +41 -2
  93. package/docs/job-runs.md +224 -0
  94. package/docs/project-delivery-scope.md +59 -0
  95. package/docs/release-workflow.md +86 -0
  96. package/docs/release-workflow.zh-CN.md +68 -0
  97. package/docs/schedules.md +132 -0
  98. package/docs/storage-baseline.md +31 -1
  99. package/docs/storage-baseline.zh-CN.md +25 -1
  100. package/docs/task-delivery.md +74 -2
  101. package/docs/task-delivery.zh-CN.md +39 -1
  102. package/docs/testing/verification-levels.md +6 -2
  103. package/docs/testing/verification-levels.zh-CN.md +6 -3
  104. package/i18n/README.zh-CN.md +6 -3
  105. package/package.json +4 -3
  106. package/skills/yui-leader/SKILL.md +44 -1
  107. package/skills/yui-leader/VERSION +1 -1
  108. package/skills/yui-leader/references/planning.md +9 -1
  109. package/skills/yui-operator/SKILL.md +31 -0
  110. package/skills/yui-operator/VERSION +1 -1
  111. package/skills/yui-reviewer/SKILL.md +4 -0
  112. package/skills/yui-reviewer/VERSION +1 -1
  113. package/skills/yui-runtime/SKILL.md +67 -8
  114. package/skills/yui-runtime/VERSION +1 -1
  115. package/skills/yui-runtime/references/context-ordering.md +103 -0
  116. package/skills/yui-runtime/references/publication.md +44 -4
  117. package/skills/yui-runtime/references/recovery.md +21 -0
  118. package/skills/yui-worker/SKILL.md +5 -0
  119. package/skills/yui-worker/VERSION +1 -1
package/README.md CHANGED
@@ -30,7 +30,7 @@ not from terminal windows you juggle or details you have to remember.
30
30
  - **Bring your own Agent** — Codex CLI, Claude Code CLI and ACP peers run behind
31
31
  one boundary and stay replaceable without losing the Task.
32
32
  - **Local-first and private** — everything runs on your machine for one trusted
33
- user; Web is loopback-only, with read-only views and authenticated user controls.
33
+ user; Web defaults to loopback, with explicitly enabled authenticated LAN access.
34
34
  - **Isolated by default** — repository work happens in managed Git worktrees;
35
35
  the stable checkout stays read-only.
36
36
 
@@ -181,6 +181,8 @@ For a visual overview, run `yui web` in another terminal. The local Web view
181
181
  shows the same tasks and pending questions and lets you send messages, answer
182
182
  questions and explicitly queue, steer or interrupt Task input. These authenticated
183
183
  Task controls use the same operations as the CLI; Web is not a separate task system.
184
+ For explicit trusted-LAN binding, password protection, access URLs and Operator
185
+ start/status/stop, see [Controlled Web access](docs/web-access.md). HTTP is not encrypted.
184
186
  The task list remains at the left, task details occupy the primary reading area,
185
187
  and recorded Task messages live in a secondary discussion pane. You can resize,
186
188
  swap or hide that pane; on narrow screens it opens over the detail. Native Sessions
@@ -311,7 +313,7 @@ the Controller are what touch the store:
311
313
  it moves work and records facts — it never judges an answer
312
314
 
313
315
  Agents work in Projects: read-only checkout + isolated worktrees.
314
- Web (yui web): loopback views + authenticated user controls.
316
+ Web (yui web): local by default + explicitly authenticated LAN access.
315
317
  ```
316
318
 
317
319
  ### Layered design
@@ -321,7 +323,7 @@ never a fixed workflow:
321
323
 
322
324
  ```text
323
325
  Experience — how you interact
324
- CLI (Operator) · Web (loopback, authenticated) · native Agent sessions
326
+ CLI (Operator) · Web (local / authenticated LAN) · native Agent sessions
325
327
  collect input · show facts · confirm actions · invoke capabilities
326
328
  ▼
327
329
  Intelligence — who decides
@@ -275,7 +275,7 @@ const taskChildren = [
275
275
  {
276
276
  name: "project", discovery: { surface: "managed", audiences: ["operator", "leader"] },
277
277
  summary: "Manage Projects bound to a Task.",
278
- sections: [{ id: "manage", title: "Commands", entries: ["list", "add"] }],
278
+ sections: [{ id: "manage", title: "Commands", entries: ["list", "add", "delivery"] }],
279
279
  children: [
280
280
  {
281
281
  name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
@@ -287,6 +287,13 @@ const taskChildren = [
287
287
  summary: "Add a Project to a Task.",
288
288
  usage: "yui task project add <task> <project> [--base <ref>] [--directory <name>]",
289
289
  options: ["--base", "--directory"]
290
+ },
291
+ {
292
+ name: "delivery", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
293
+ summary: "Record a sourced Task Project delivery requirement; never grants cleanup or execution authority.",
294
+ usage: "yui task project delivery <task> <project-id> --requirement <required|excluded> --reason <text> --source-message <message-id> --purpose <exact-quote> --if-current <event-id|none>",
295
+ options: ["--requirement", "--reason", "--source-message", "--purpose", "--if-current"],
296
+ optionValues: { "--requirement": ["required", "excluded"] }
290
297
  }
291
298
  ]
292
299
  },
@@ -314,8 +321,8 @@ const taskChildren = [
314
321
  name: "request", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
315
322
  summary: "Record an Activation request. Every active Task gets a managed main workspace. empty adds no execution environment; scratch adopts a separate scratch environment; local requires a registered Resource and grant, not a Project ID.",
316
323
  usage: "yui task activation request <task> --request-id <id> "
317
- + "--environment <empty|scratch|local:<resource>:<read|write>>",
318
- options: ["--request-id", "--environment"],
324
+ + "--environment <empty|scratch|local:<resource>:<read|write>> [--source-message <id> | --source-text <exact-input>]",
325
+ options: ["--request-id", "--environment", "--source-message", "--source-text"],
319
326
  examples: [
320
327
  "yui task activation request <task> --request-id start-1 --environment empty",
321
328
  "yui task activation request <task> --request-id start-1 --environment scratch"
@@ -643,8 +650,8 @@ const taskChildren = [
643
650
  {
644
651
  name: "upsert", discovery: { surface: "managed", audiences: ["operator", "leader"] },
645
652
  summary: "Create or immutably update an external PR/MR and its publication state.",
646
- usage: "yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--head-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported|--verified] [--evidence <text>] [--merged-at <iso-timestamp>]",
647
- options: ["--project", "--provider", "--repository", "--kind", "--id", "--url", "--title", "--source-branch", "--target-branch", "--local-commit", "--head-commit", "--remote-commit", "--state", "--reported", "--verified", "--evidence", "--merged-at"]
653
+ usage: "yui task publication upsert <task> --project <project> --provider <github|gitlab> --repository <owner/name> --kind <pull-request|merge-request> --id <external-id> [--url <url>] [--title <text>] [--source-branch <branch>] [--target-branch <branch>] [--local-commit <sha>] [--head-commit <sha>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported] [--evidence <text>] [--merged-at <iso-timestamp>]",
654
+ options: ["--project", "--provider", "--repository", "--kind", "--id", "--url", "--title", "--source-branch", "--target-branch", "--local-commit", "--head-commit", "--remote-commit", "--state", "--reported", "--evidence", "--merged-at"]
648
655
  },
649
656
  {
650
657
  name: "diff", discovery: { surface: "managed", audiences: ["operator", "leader"] },
@@ -660,8 +667,9 @@ const taskChildren = [
660
667
  },
661
668
  {
662
669
  name: "verify", discovery: { surface: "managed", audiences: ["operator", "leader"] },
663
- summary: "Observe the current PR/MR head and merge through its provider; record verification separately from Task coverage.",
664
- usage: "yui task publication verify (<task>/<publication-id> | <task> <publication-id>)"
670
+ summary: "Record exact authorized tool evidence, or query the optional gh/glab adapter; verification is separate from Task coverage.",
671
+ usage: "yui task publication verify (<task>/<publication-id> | <task> <publication-id>) [--observation <json>]",
672
+ options: ["--observation"]
665
673
  },
666
674
  {
667
675
  name: "list", discovery: { surface: "public", audiences: ["public", "operator", "leader"] },
@@ -738,7 +746,7 @@ const taskChildren = [
738
746
  },
739
747
  {
740
748
  name: "new", discovery: { surface: "managed", audiences: ["operator", "leader"] },
741
- summary: "Request replacement even for active or ended Sessions; stop exact execution and preserve Task context, results and workspaces.",
749
+ summary: "Stop exact execution, then initialize a successor without replaying retained inbox obligations; preserve claims, context and workspaces.",
742
750
  usage: "yui task role session new <task> <role> --reason <text>",
743
751
  options: ["--reason"]
744
752
  }
@@ -1016,15 +1024,15 @@ const taskChildren = [
1016
1024
  },
1017
1025
  {
1018
1026
  name: "brief", discovery: { surface: "managed", audiences: ["operator", "leader"] },
1019
- summary: "Manage the Task Brief, the authoritative summary of current task state.",
1027
+ summary: "Manage the Leader-maintained Task summary; live lifecycle and authority remain separate facts.",
1020
1028
  sections: [{ id: "manage", title: "Commands", entries: ["show", "update"] }],
1021
1029
  children: [
1022
1030
  { name: "show", discovery: { surface: "managed", audiences: ["operator", "leader"] }, summary: "Show the Task Brief.", usage: "yui task brief show <task> [--cursor <cursor>]", options: ["--cursor"] },
1023
1031
  {
1024
1032
  name: "update", discovery: { surface: "managed", audiences: ["operator", "leader"] },
1025
1033
  summary: "Create or update the Task Brief.",
1026
- usage: "yui task brief update <task> [--objective <text>] [--boundary <text> ...] [--approach <text>] [--focus <text>] [--leader-summary <text>]",
1027
- options: ["--objective", "--boundary", "--approach", "--focus", "--leader-summary"]
1034
+ usage: "yui task brief update <task> --if-current <writeGuard> [--objective <text>] [--boundary <text> ...] [--approach <text>] [--focus <text>] [--leader-summary <text>]",
1035
+ options: ["--if-current", "--objective", "--boundary", "--approach", "--focus", "--leader-summary"]
1028
1036
  }
1029
1037
  ]
1030
1038
  },
@@ -1136,7 +1144,7 @@ export const ROOT_COMMAND = buildNode({
1136
1144
  ] },
1137
1145
  { id: "workflow", title: "Workflow", entries: ["agent", "operator", "role", "inbox", "project", "task"] },
1138
1146
  { id: "configuration", title: "Configuration", entries: ["config"] },
1139
- { id: "operations", title: "Operations", entries: ["web", "controller", "session", "execution", "capability", "job", "jobs", "telemetry", "release"] },
1147
+ { id: "operations", title: "Operations", entries: ["web", "controller", "session", "execution", "capability", "job", "schedule", "jobs", "telemetry", "release"] },
1140
1148
  { id: "resources", title: "Resources", entries: ["resources"] },
1141
1149
  { id: "internal", title: "Internal", entries: ["internal"] }
1142
1150
  ],
@@ -1163,11 +1171,12 @@ export const ROOT_COMMAND = buildNode({
1163
1171
  {
1164
1172
  name: "inbox",
1165
1173
  summary: "Read and explicitly handle the current managed Agent's own inbox.",
1166
- sections: [{ id: "commands", title: "Commands", entries: ["list", "show", "claim", "ack"] }],
1174
+ sections: [{ id: "commands", title: "Commands", entries: ["list", "show", "claim", "ack", "retire"] }],
1167
1175
  children: [
1168
1176
  { name: "list", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
1169
1177
  summary: "Discover unhandled own-address entries, including accepted delivery, with current identity and separate handling receipts.",
1170
- usage: "yui inbox list [--limit <1..100>] [--cursor <cursor>]", options: ["--limit", "--cursor"] },
1178
+ usage: "yui inbox list [--view current|history|all] [--limit <1..100>] [--cursor <cursor>]",
1179
+ options: ["--view", "--limit", "--cursor"], optionValues: { "--view": ["current", "history", "all"] } },
1171
1180
  { name: "show", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
1172
1181
  summary: "Read an exact original or fixed notification window without acknowledgement.",
1173
1182
  usage: "yui inbox show <entry> [--cursor <cursor>]", options: ["--cursor"] },
@@ -1175,8 +1184,12 @@ export const ROOT_COMMAND = buildNode({
1175
1184
  summary: "Reserve 1..16 exact entries for handling in this turn; never starts a model turn.",
1176
1185
  usage: "yui inbox claim <entry> [<entry> ...] --request-id <id>", options: ["--request-id"] },
1177
1186
  { name: "ack", discovery: { surface: "managed", audiences: ["operator", "global", "leader", "assignment"] },
1178
- summary: "Confirm handling of claimed entries with an auditable disposition, not Provider acceptance.",
1187
+ summary: "Record handling, including a rightful successor's disposition of an unchanged prior claim; never proves native acceptance or authorizes replay.",
1179
1188
  usage: "yui inbox ack <entry> [<entry> ...] --request-id <id> --reason <disposition>",
1189
+ options: ["--request-id", "--reason"] },
1190
+ { name: "retire", discovery: { surface: "managed", audiences: ["operator", "global"] },
1191
+ summary: "Explicitly retire obsolete proven-unsubmitted Global input, preserving its original target and evidence.",
1192
+ usage: "yui inbox retire <entry> [<entry> ...] --request-id <id> --reason <disposition>",
1180
1193
  options: ["--request-id", "--reason"] }
1181
1194
  ]
1182
1195
  },
@@ -1191,8 +1204,8 @@ export const ROOT_COMMAND = buildNode({
1191
1204
  children: [
1192
1205
  {
1193
1206
  name: "list", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Discover Global Messages without consuming input.",
1194
- usage: "yui role message list <role> [--pending|--diagnostics|--diagnostic <key>] [--limit <1..100>] [--cursor <cursor>]",
1195
- options: ["--pending", "--diagnostics", "--diagnostic", "--limit", "--cursor"]
1207
+ usage: "yui role message list <role> [--current|--pending|--diagnostics|--diagnostic <key>] [--limit <1..100>] [--cursor <cursor>]",
1208
+ options: ["--current", "--pending", "--diagnostics", "--diagnostic", "--limit", "--cursor"]
1196
1209
  },
1197
1210
  {
1198
1211
  name: "show", discovery: { surface: "public", audiences: ["public", "operator"] }, summary: "Read one exact Global Message; follow contentPage.nextCursor for long content.",
@@ -1242,8 +1255,8 @@ export const ROOT_COMMAND = buildNode({
1242
1255
  {
1243
1256
  name: "web", discovery: { surface: "public", audiences: ["public", "operator"] },
1244
1257
  summary: "Serve the control room using the running Controller's capability Host.",
1245
- usage: "yui web [--host <loopback>] [--port <port>] | --status | --stop",
1246
- options: ["--host", "--port", "--status", "--stop"]
1258
+ usage: "yui web [--host <IP|localhost>] [--port <port>] [--auth-file <absolute-path>] [--detach] | --status | --stop",
1259
+ options: ["--host", "--port", "--auth-file", "--detach", "--status", "--stop"]
1247
1260
  },
1248
1261
  {
1249
1262
  name: "controller", discovery: { surface: "public", audiences: ["public", "operator"] },
@@ -1641,15 +1654,37 @@ export const ROOT_COMMAND = buildNode({
1641
1654
  },
1642
1655
  {
1643
1656
  name: "job", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
1644
- summary: "Start, inspect, cancel, or acknowledge a Controller-managed DurableJob.",
1645
- sections: [{ id: "manage", title: "Commands", entries: ["start", "get", "cancel", "acknowledge"] }],
1657
+ summary: "Manage stable Job definitions and exact execution Runs (not scheduler wakes).",
1658
+ sections: [{ id: "manage", title: "Commands", entries: ["define", "update", "list", "show", "run", "runs", "run-get", "logs", "run-cancel", "run-acknowledge", "start", "get", "cancel", "acknowledge"] }],
1646
1659
  children: [
1660
+ ...["define", "update", "list", "show", "run", "runs", "run-get", "logs", "run-cancel", "run-acknowledge"].map(name => ({
1661
+ name, discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
1662
+ summary: `${name} a stable Job or exact Run.`,
1663
+ usage: `yui job ${name} (--task <id>|--global)${name === "list" ? "" : " --job <id>"}`
1664
+ + (name === "define" || name === "update" ? " --title <text> --input <configuration-json>" : "")
1665
+ + (name === "update" ? " --revision <current>" : "")
1666
+ + (name === "run" ? " --request-id <key> [--retry-of <run-id>]" : "")
1667
+ + (["run-get", "logs", "run-cancel", "run-acknowledge"].includes(name) ? " --run <run-id>" : "")
1668
+ })),
1647
1669
  { name: "start", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Start a DurableJob under an explicit request identity.", usage: "yui job start --request-id <id> --task <id> --project <project> --head <sha> --workspace <dir> --step <name>=<command> [--step ...] [--owner task|work-item:<id>|integration-attempt:<id>] [--env <k=v>...]" },
1648
1670
  { name: "get", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Show a DurableJob record and its terminal result.", usage: "yui job get --task <id> --job <job-id>" },
1649
1671
  { name: "cancel", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Request cancellation of a running or queued DurableJob.", usage: "yui job cancel --task <id> --job <job-id>" },
1650
1672
  { name: "acknowledge", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] }, summary: "Acknowledge an unknown-needs-attention DurableJob so Task lifecycle gates can proceed.", usage: "yui job acknowledge --task <id> --job <job-id>" }
1651
1673
  ]
1652
1674
  },
1675
+ {
1676
+ name: "schedule", discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
1677
+ summary: "Manage persistent time plans referencing scoped Jobs.",
1678
+ sections: [{ id: "manage", title: "Commands", entries: ["create", "list", "show", "update", "enable", "disable"] }],
1679
+ children: ["create", "list", "show", "update", "enable", "disable"].map(name => ({
1680
+ name, discovery: { surface: "managed", audiences: ["operator", "leader", "assignment"] },
1681
+ summary: `${name} a Schedule; disabling never cancels an admitted Run.`,
1682
+ usage: `yui schedule ${name} (--global|--task <id>)${name === "list" ? "" : " --schedule <id>"}`
1683
+ + (name === "create" ? " --job <id> --rule <json>" : "")
1684
+ + (name === "update" ? " --rule <json> [--job <id>]" : "")
1685
+ + (["update", "enable", "disable"].includes(name) ? " --revision <observed>" : "")
1686
+ }))
1687
+ },
1653
1688
  {
1654
1689
  name: "jobs", discovery: { surface: "public", audiences: ["public", "operator"] },
1655
1690
  summary: "Inspect scheduler wake and recovery records.",
@@ -25,7 +25,9 @@ function describeOption(option) {
25
25
  "--agent": "Select an Agent.",
26
26
  "--base": "Set the base Git reference.",
27
27
  "--input": "Set dispatch input.",
28
- "--host": "Set the loopback listen address.",
28
+ "--host": "Set the listen IP (default 127.0.0.1); non-loopback requires --auth-file.",
29
+ "--auth-file": "Owner-only file with a unique 32–128 character password; browser username is yui. HTTP is not encrypted.",
30
+ "--detach": "Return after Controller starts Web; use --status / --stop. Operator and --json starts always return.",
29
31
  "--port": "Set the local listen port.",
30
32
  "--json": "Emit machine-readable JSON.",
31
33
  "--project": "Select a Project.",
@@ -185,7 +185,7 @@ export const INTERACTION_POLICIES = Object.freeze([
185
185
  actionTarget: true,
186
186
  statuses: ["draft"]
187
187
  }],
188
- trailingOptions: { "--request-id": "value", "--environment": "value" }
188
+ trailingOptions: { "--request-id": "value", "--environment": "value", "--source-message": "value", "--source-text": "value" }
189
189
  },
190
190
  {
191
191
  commandPath: ["task", "activation", "cancel"],
@@ -615,7 +615,7 @@ export const INTERACTION_POLICIES = Object.freeze([
615
615
  ].map(([group, command]) => {
616
616
  const trailingOptions = {};
617
617
  if (group === "brief" && command === "update") {
618
- Object.assign(trailingOptions, { "--objective": "value", "--boundary": "value", "--approach": "value", "--focus": "value", "--leader-summary": "value" });
618
+ Object.assign(trailingOptions, { "--if-current": "value", "--objective": "value", "--boundary": "value", "--approach": "value", "--focus": "value", "--leader-summary": "value" });
619
619
  }
620
620
  else if (group === "decision" && command === "record") {
621
621
  Object.assign(trailingOptions, { "--title": "value", "--rationale": "value" });
@@ -1,5 +1,6 @@
1
1
  import { usageError } from "../errors/cliError.js";
2
2
  import { ensureFileTaskController } from "../controller/clientRuntime.js";
3
+ import { callFileTaskController } from "../controller/clientRuntime.js";
3
4
  import { acknowledgeDurableJob, cancelDurableJob, getDurableJob, startDurableJob } from "../controller/jobClient.js";
4
5
  import { resolveJobCaller, taskLocalActor } from "../task/taskAuthority.js";
5
6
  /**
@@ -14,6 +15,9 @@ export const JOB_OWNER_USAGE = "task|work-item:<id>|integration-attempt:<id>";
14
15
  */
15
16
  export async function runDurableJobCommand(args, options) {
16
17
  const [command, ...rest] = args;
18
+ if (command !== undefined && ["define", "update", "list", "show", "run", "runs", "run-get", "run-cancel", "run-acknowledge", "logs"].includes(command)) {
19
+ return definitionCommand(command, rest, options);
20
+ }
17
21
  if (command === "start")
18
22
  return startJob(rest, options);
19
23
  if (command === "get")
@@ -31,6 +35,52 @@ export async function runDurableJobCommand(args, options) {
31
35
  + "yui job cancel --task <id> --job <job-id>\n"
32
36
  + "yui job acknowledge --task <id> --job <job-id>");
33
37
  }
38
+ async function definitionCommand(command, args, options) {
39
+ const flags = new Map();
40
+ for (let i = 0; i < args.length; i++) {
41
+ const flag = args[i];
42
+ if (!["--global", "--task", "--job", "--title", "--input", "--revision", "--request-id", "--retry-of", "--run"].includes(flag)
43
+ || flags.has(flag))
44
+ throw usageError(`Unknown or repeated Job flag: ${flag}.`);
45
+ if (flag === "--global") {
46
+ flags.set(flag, "true");
47
+ continue;
48
+ }
49
+ const value = args[++i];
50
+ if (value === undefined || value.startsWith("--"))
51
+ throw usageError(`Missing value: ${flag}.`);
52
+ flags.set(flag, value);
53
+ }
54
+ if (flags.has("--global") === flags.has("--task"))
55
+ throw usageError("Choose exactly one of --task <id> or --global.");
56
+ const taskId = flags.get("--task");
57
+ const caller = resolveJobCaller(options.environment, taskId ?? "");
58
+ const scope = taskId === undefined ? { kind: "global" } : { kind: "task", taskId };
59
+ let configuration;
60
+ if (flags.has("--input")) {
61
+ try {
62
+ configuration = JSON.parse(flags.get("--input"));
63
+ }
64
+ catch {
65
+ throw usageError("--input must be a JSON Job configuration.");
66
+ }
67
+ }
68
+ const requestId = flags.get("--request-id");
69
+ if (command === "run" && !requestId)
70
+ throw usageError("job run requires --request-id.");
71
+ const params = {
72
+ op: command, scope, caller,
73
+ ...(flags.has("--job") ? { id: flags.get("--job") } : {}),
74
+ ...(flags.has("--title") ? { title: flags.get("--title") } : {}),
75
+ ...(configuration === undefined ? {} : { configuration }),
76
+ ...(flags.has("--revision") ? { expectedRevision: Number(flags.get("--revision")) } : {}),
77
+ ...(flags.has("--run") ? { runId: flags.get("--run") } : {}),
78
+ ...(flags.has("--retry-of") ? { retryOf: flags.get("--retry-of") } : {}),
79
+ ...(command === "run" ? { trigger: { source: flags.has("--retry-of") ? "retry" : "manual", key: requestId } } : {})
80
+ };
81
+ const result = await callFileTaskController(options.home, "job.definition", params, { environment: options.environment });
82
+ return `${JSON.stringify(result, null, 2)}\n`;
83
+ }
34
84
  async function startJob(args, options) {
35
85
  const parsed = parseStartArgs(args);
36
86
  await ensureFileTaskController(options.home, { environment: options.environment });
@@ -6,6 +6,7 @@ import { controlProviderRetry, providerRetryProjection, providerRetrySummary, re
6
6
  import { pendingGlobalInboxMessages, inboxHandlingState, globalInboxDeliveryState } from "../message/inboxReceipt.js";
7
7
  import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
8
8
  import { globalInputBacklog } from "../message/globalInputBacklog.js";
9
+ import { inboxEntryCategory } from "../message/inboxTurn.js";
9
10
  import { checkCurrentSkills, SKILL_REUSE_RULE } from "../context/currentSkills.js";
10
11
  import { runtimeDiagnosticGroups, runtimeDiagnosticEvidence } from "../controller/globalRuntimeAttention.js";
11
12
  import { roleSkillIds } from "../context/roleSessionContext.js";
@@ -131,10 +132,16 @@ function roleContext(args, store, options) {
131
132
  // through distinct receipts; a context read does neither.
132
133
  const messages = store.listGlobalRoleMessages(name);
133
134
  const queued = messages.filter((message) => message.delivery === undefined
134
- && message.inbox?.ack === undefined
135
+ && message.inbox === undefined
135
136
  && message.notDelivered === undefined
136
137
  && (message.interruptThen !== undefined || message.inputControl?.action === "queue"));
137
- const pending = recordPage(pendingGlobalInboxMessages(messages).map(globalMessageSummary), `global:${name}/messages/pending`, { limit: 8 });
138
+ const unhandled = pendingGlobalInboxMessages(messages);
139
+ const current = currentGlobalInboxMessages(messages, name, sessions);
140
+ const pending = recordPage(current.map(globalMessageSummary), `global:${name}/messages/current`, { limit: 8 }, {
141
+ historyCount: unhandled.length - current.length,
142
+ historyRead: "yui inbox list --view history --json",
143
+ note: "Historical absence of handling evidence is not new work or permission to replay effects."
144
+ });
138
145
  const observedAt = new Date();
139
146
  const { oldestAgeMs, messages: _backlogMessages, ...backlog } = globalInputBacklog(queued, sessions, observedAt, {
140
147
  attention: name === "operator" ? store.getWorkMailbox({ kind: "operator" }) : null,
@@ -175,9 +182,9 @@ function roleContext(args, store, options) {
175
182
  pending,
176
183
  backlog,
177
184
  reads: {
178
- ownInbox: "yui inbox list --json",
185
+ ownInbox: "yui inbox list --view current --json",
179
186
  messages: `role message list ${name}`,
180
- pending: `role message list ${name} --pending`,
187
+ pending: `role message list ${name} --current`,
181
188
  detail: `role message show ${name} <message-id>`,
182
189
  diagnostics: `role message list ${name} --diagnostics`,
183
190
  retry: `session retry ${name} show`
@@ -213,6 +220,16 @@ function roleContext(args, store, options) {
213
220
  ""
214
221
  ].join("\n") };
215
222
  }
223
+ function currentGlobalInboxMessages(messages, name, sessions) {
224
+ const actor = { scope: "global", roleName: name, agentId: sessions?.activeAgentId ?? "",
225
+ nativeSessionId: sessions?.sessions[sessions.activeAgentId]?.nativeSessionId ?? "" };
226
+ const binding = sessions?.providerBinding;
227
+ const turn = binding?.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId === actor.nativeSessionId
228
+ ? binding?.run : undefined;
229
+ const category = (message) => inboxEntryCategory(message, actor, turn);
230
+ return pendingGlobalInboxMessages(messages).filter(message => category(message) !== "history")
231
+ .sort((a, b) => Number(category(b) === "current") - Number(category(a) === "current"));
232
+ }
216
233
  function globalMessageSummary(message) {
217
234
  return {
218
235
  id: message.id, createdAt: message.createdAt, kind: message.kind,
@@ -626,17 +643,18 @@ function globalRoleMessage(args, store, options) {
626
643
  }
627
644
  requireRole(name, store);
628
645
  const pending = tail.includes("--pending");
646
+ const current = tail.includes("--current");
629
647
  const diagnostics = tail.includes("--diagnostics");
630
- const parsed = readOptions(tail.filter(arg => arg !== "--pending" && arg !== "--diagnostics"), action === "list"
648
+ const parsed = readOptions(tail.filter(arg => arg !== "--pending" && arg !== "--current" && arg !== "--diagnostics"), action === "list"
631
649
  ? ["--cursor", "--limit", "--diagnostic"] : ["--cursor"]);
632
650
  const diagnostic = parsed.values.get("--diagnostic");
633
- if (Number(pending) + Number(diagnostics) + Number(diagnostic !== undefined) > 1
651
+ if (Number(pending) + Number(current) + Number(diagnostics) + Number(diagnostic !== undefined) > 1
634
652
  || action === "show" && diagnostics)
635
653
  throw usageError("Choose one Message discovery view.");
636
654
  const messages = store.listGlobalRoleMessages(name);
637
655
  let data;
638
656
  if (action === "show") {
639
- if (parsed.positionals.length !== 1 || pending)
657
+ if (parsed.positionals.length !== 1 || pending || current)
640
658
  throw usageError("Role message show <role> <message-id> [--cursor <cursor>].");
641
659
  const message = messages.find(m => m.id === parsed.positionals[0]);
642
660
  if (message === undefined)
@@ -654,8 +672,9 @@ function globalRoleMessage(args, store, options) {
654
672
  return { kind: "output", output: `${JSON.stringify(data)}\n`, data };
655
673
  }
656
674
  const selected = diagnostic !== undefined ? runtimeDiagnosticEvidence(messages, diagnostic)
657
- : pending ? pendingGlobalInboxMessages(messages) : [...messages].reverse();
658
- data = recordPage(selected.map(globalMessageSummary), `global:${name}/messages/${diagnostic === undefined ? pending ? "pending" : "all" : `diagnostic/${diagnostic}`}`, parsed);
675
+ : current ? currentGlobalInboxMessages(messages, name, store.getGlobalRoleSessionSet(name))
676
+ : pending ? pendingGlobalInboxMessages(messages) : [...messages].reverse();
677
+ data = recordPage(selected.map(globalMessageSummary), `global:${name}/messages/${diagnostic === undefined ? current ? "current" : pending ? "pending" : "all" : `diagnostic/${diagnostic}`}`, parsed);
659
678
  }
660
679
  return { kind: "output", output: `${JSON.stringify(data, null, 2)}\n`, data };
661
680
  }
@@ -711,6 +730,8 @@ function messageGlobalInput(name, input, store, options) {
711
730
  }
712
731
  store.updateGlobalRoleMessage({ ...persisted.message, control: {
713
732
  requestId, receiptId: `steer:${name}/${persisted.message.id}`,
733
+ target: { agentId: resolution.target.agentId, nativeSessionId: resolution.target.nativeSessionId,
734
+ attemptId: resolution.target.attemptId },
714
735
  outcome: "pending", observedAt: now.toISOString()
715
736
  } });
716
737
  return {