@zq-silk/yui 0.15.9 → 0.15.12

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 (160) hide show
  1. package/ARCHITECTURE.md +8 -4
  2. package/ARCHITECTURE.zh-CN.md +5 -2
  3. package/README.md +13 -5
  4. package/dist/agent/launchEnvironment.js +7 -0
  5. package/dist/agentRun/agentRun.js +3 -0
  6. package/dist/cli/commandCatalog.js +64 -16
  7. package/dist/cli/interactionPolicy.js +7 -3
  8. package/dist/cli/managedDiagnostics.js +1 -1
  9. package/dist/cli/updateOrchestrator.js +24 -1
  10. package/dist/cli/updatePorts.js +7 -3
  11. package/dist/cli/upgradeCommand.js +42 -2
  12. package/dist/cli.js +381 -107
  13. package/dist/commands/executionAuditCommands.js +10 -0
  14. package/dist/commands/globalRoleCommands.js +339 -4
  15. package/dist/commands/projectCommands.js +50 -22
  16. package/dist/commands/releaseCommands.js +18 -0
  17. package/dist/commands/taskActor.js +25 -0
  18. package/dist/commands/taskCommands.js +586 -96
  19. package/dist/commands/taskIntegrationCommands.js +19 -39
  20. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  21. package/dist/commands/taskOverviewCommand.js +4 -3
  22. package/dist/commands/taskPublicationAdoptCommand.js +127 -0
  23. package/dist/commands/taskPublicationCommands.js +11 -2
  24. package/dist/commands/taskPublicationVerifyCommand.js +23 -39
  25. package/dist/commands/taskRemoteDeliveryCommand.js +22 -11
  26. package/dist/commands/taskRoleRuntimeStatus.js +35 -0
  27. package/dist/context/runContextPack.js +3 -0
  28. package/dist/context/taskCatalog.js +187 -0
  29. package/dist/context/taskContext.js +55 -6
  30. package/dist/controller/agentHostObservation.js +155 -0
  31. package/dist/controller/clientRuntime.js +17 -2
  32. package/dist/controller/controller.js +14 -2
  33. package/dist/controller/fileSchedulerStoreAdapter.js +519 -25
  34. package/dist/controller/globalInputDelivery.js +132 -0
  35. package/dist/controller/jobControl.js +6 -2
  36. package/dist/controller/providerRetryAdmission.js +100 -0
  37. package/dist/controller/providerRetryDelivery.js +218 -0
  38. package/dist/controller/resourceInventory.js +14 -4
  39. package/dist/controller/resourceInventoryLinux.js +2 -6
  40. package/dist/controller/runtime.js +117 -7
  41. package/dist/controller/runtimeEventInbox.js +32 -3
  42. package/dist/controller/runtimeEventProcessor.js +26 -6
  43. package/dist/controller/runtimeHookRunFence.js +75 -19
  44. package/dist/controller/structuredProviderObservation.js +133 -70
  45. package/dist/coordination/workMailboxQueue.js +5 -0
  46. package/dist/execution/workItemExecutionProjection.js +1 -1
  47. package/dist/executor/agentExecutor.js +64 -4
  48. package/dist/executor/executorRegistry.js +3 -0
  49. package/dist/executor/fileRoleLaunchPlanner.js +78 -118
  50. package/dist/integration/deliveryObligation.js +2 -1
  51. package/dist/integration/gitIntegrationService.js +329 -386
  52. package/dist/integration/integrationAttempt.js +30 -4
  53. package/dist/integration/integrationQueueService.js +7 -7
  54. package/dist/integration/integrationSourceApplication.js +323 -0
  55. package/dist/lifecycle/exactRunTerminalization.js +4 -1
  56. package/dist/message/globalInterrupt.js +33 -0
  57. package/dist/message/globalProviderRetry.js +15 -0
  58. package/dist/message/inputControlResolution.js +106 -0
  59. package/dist/message/message.js +367 -0
  60. package/dist/message/messageContinuation.js +126 -3
  61. package/dist/message/taskInterrupt.js +34 -0
  62. package/dist/observability/executionAudit.js +19 -0
  63. package/dist/observability/orchestrationMetrics.js +1 -1
  64. package/dist/release/releaseHandover.js +22 -0
  65. package/dist/release/releaseWorkflowPorts.js +15 -7
  66. package/dist/repository/gitWorkspace.js +430 -107
  67. package/dist/repository/projectMaintenanceLock.js +75 -18
  68. package/dist/repository/taskWorkspaceCoordinator.js +182 -101
  69. package/dist/repository/taskWorkspacePreparer.js +205 -72
  70. package/dist/repository/workItemCandidateSnapshot.js +34 -0
  71. package/dist/repository/workspaceCleanupInspection.js +187 -0
  72. package/dist/resources/resourceDiscovery.js +3 -2
  73. package/dist/runtime/agentError.js +5 -3
  74. package/dist/runtime/agentHost.js +179 -82
  75. package/dist/runtime/agentHostCompatibility.js +127 -0
  76. package/dist/runtime/agentHostProtocol.js +53 -0
  77. package/dist/runtime/builtinAgentErrorMappers.js +91 -0
  78. package/dist/runtime/codexAppServerRuntime.js +34 -3
  79. package/dist/runtime/executionEnvironment.js +0 -19
  80. package/dist/runtime/launchBroker.js +6 -0
  81. package/dist/runtime/providerControl.js +5 -1
  82. package/dist/runtime/providerRetry.js +198 -0
  83. package/dist/runtime/providerRuntimeIdentity.js +28 -2
  84. package/dist/runtime/sessionReconciliation.js +4 -4
  85. package/dist/runtime/sessionTokenMetrics.js +15 -5
  86. package/dist/runtime/structuredProviderHost.js +6 -2
  87. package/dist/runtime/taskRuntimeIsolation.js +30 -6
  88. package/dist/runtime/taskUsageMetrics.js +275 -0
  89. package/dist/runtime/tmuxAdapters.js +5 -3
  90. package/dist/scheduler/activeRoleRunDelivery.js +12 -0
  91. package/dist/scheduler/leaderWakeupProcessor.js +5 -0
  92. package/dist/scheduler/operatorEvent.js +4 -0
  93. package/dist/scheduler/taskExecutionProjection.js +38 -6
  94. package/dist/scheduler/taskObservabilityProjection.js +6 -44
  95. package/dist/scheduler/wakeReason.js +7 -1
  96. package/dist/scheduler/wakeupQueue.js +2 -0
  97. package/dist/setup/setupCommand.js +26 -8
  98. package/dist/storage/homeLayout.js +130 -0
  99. package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
  100. package/dist/storage/migrations/integrationContinuation.js +104 -0
  101. package/dist/storage/migrations/unifyHomeLayout.js +925 -0
  102. package/dist/storage/sqliteSchema.js +167 -4
  103. package/dist/storage/sqliteStore.js +57 -1
  104. package/dist/storage/storageVersions.js +1 -1
  105. package/dist/storage/storeRpc.js +2 -0
  106. package/dist/storage/taskCatalog.js +123 -0
  107. package/dist/storage/taskStore.js +2 -0
  108. package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
  109. package/dist/task/archiveDiagnostics.js +129 -0
  110. package/dist/task/archivePreflight.js +124 -0
  111. package/dist/task/nextAction.js +44 -11
  112. package/dist/task/publicationAdoption.js +56 -0
  113. package/dist/task/publicationReference.js +10 -0
  114. package/dist/task/remoteDelivery.js +31 -16
  115. package/dist/web/assets/client/app.js +147 -17
  116. package/dist/web/assets/client/components.js +56 -13
  117. package/dist/web/assets/client/i18n.js +78 -4
  118. package/dist/web/assets/client/taskSurface.js +108 -1
  119. package/dist/web/assets/client/view.js +39 -8
  120. package/dist/web/assets/shell.js +29 -0
  121. package/dist/web/assets/styles/layout.js +8 -1
  122. package/dist/web/assets/styles/widgets.js +12 -0
  123. package/dist/web/webServer.js +131 -4
  124. package/dist/web/webSnapshot.js +16 -6
  125. package/dist/web/webTaskSurface.js +222 -5
  126. package/dist/workspace/cleanupInspection.js +63 -0
  127. package/dist/workspace/workItemChangeSetManager.js +111 -35
  128. package/docs/agent-result-consumption.md +4 -0
  129. package/docs/agent-result-consumption.zh-CN.md +3 -0
  130. package/docs/agent-runtime-drivers.md +7 -0
  131. package/docs/agent-runtime-drivers.zh-CN.md +5 -0
  132. package/docs/architecture/README.md +2 -0
  133. package/docs/architecture/README.zh-CN.md +3 -1
  134. package/docs/architecture/capabilities-and-resources.md +30 -5
  135. package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
  136. package/docs/managed-turn-and-session-runtime.md +47 -0
  137. package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
  138. package/docs/observability/README.md +62 -0
  139. package/docs/observability/README.zh-CN.md +47 -0
  140. package/docs/project-refresh.md +77 -0
  141. package/docs/project-refresh.zh-CN.md +59 -0
  142. package/docs/provider-retry.md +70 -0
  143. package/docs/release-workflow.md +39 -0
  144. package/docs/release-workflow.zh-CN.md +29 -0
  145. package/docs/sqlite-control-plane-design.md +223 -1
  146. package/docs/task-delivery.md +133 -13
  147. package/docs/task-delivery.zh-CN.md +99 -10
  148. package/docs/task-discovery.md +102 -0
  149. package/docs/task-discovery.zh-CN.md +86 -0
  150. package/docs/testing/verification-levels.md +40 -0
  151. package/docs/testing/verification-levels.zh-CN.md +23 -0
  152. package/i18n/README.zh-CN.md +13 -7
  153. package/package.json +1 -1
  154. package/skills/yui-leader/references/execution.md +154 -51
  155. package/skills/yui-leader/references/integration.md +52 -2
  156. package/skills/yui-operator/SKILL.md +19 -3
  157. package/skills/yui-reviewer/SKILL.md +4 -0
  158. package/skills/yui-runtime/SKILL.md +42 -0
  159. package/skills/yui-runtime/references/publication.md +42 -0
  160. package/skills/yui-runtime/references/recovery.md +24 -0
package/ARCHITECTURE.md CHANGED
@@ -169,8 +169,9 @@ Leader management is limited to its Task; executable code still requires exact
169
169
  Operator-issued grants. Plugin validation is not a security certification.
170
170
 
171
171
  CLI contributions and controlled Web panels are projections of the Registry.
172
- Web is loopback-only and Controller-owned; browser credentials do not become
173
- Operator authority. See [Plugin SDK](docs/plugin-sdk.md) and
172
+ Web is loopback-only and Controller-owned. Its authenticated local-user controls
173
+ share CLI domain operations; query panels remain read-only and cannot borrow
174
+ that user authority for mutations or plugin management. See [Plugin SDK](docs/plugin-sdk.md) and
174
175
  [capabilities and resources](docs/architecture/capabilities-and-resources.md).
175
176
 
176
177
  ## Persistence, completion and operation
@@ -181,8 +182,11 @@ Homes; malformed state is diagnosed, not automatically repaired.
181
182
 
182
183
  Completion freezes the delivery result and checks applicable acceptance,
183
184
  integration and review contracts. It is distinct from publication, verified
184
- remote merge, physical quiescence and archive. Archive requires settled work
185
- and clean removable managed resources, preserves Task history, and cannot reopen.
185
+ remote merge, physical quiescence and archive. Ordinary archive requires settled
186
+ work and clean removable managed resources. Explicitly authorized force archive
187
+ can retain unresolved evidence and unsafe resources without claiming delivery or
188
+ quiescence. Both preserve Task history and cannot reopen.
189
+ See [Task delivery and archive](docs/task-delivery.md#archive).
186
190
 
187
191
  Runtime health and cost are observations, not semantic verdicts. The Agent reads
188
192
  exact faults and current intent to choose retry, repair or abandonment. Yui does
@@ -132,7 +132,8 @@ Registry 暴露 context、消息、artifact、环境、插件以及部分 Task/J
132
132
  由 Operator 签发的 grant。插件验证不是安全认证。
133
133
 
134
134
  CLI 贡献和受控的 Web 面板是 Registry 的投影。Web 仅本地回环、由 Controller 拥有;浏览器
135
- 凭据不会变成 Operator 权限。参见[插件 SDK](docs/plugin-sdk.zh-CN.md)和
135
+ 凭据认证本地用户控制,这些控制复用 CLI 领域操作;查询面板仍只读,不能借用该用户权限
136
+ 执行修改或插件管理。参见[插件 SDK](docs/plugin-sdk.zh-CN.md)和
136
137
  [能力与资源](docs/architecture/capabilities-and-resources.zh-CN.md)。
137
138
 
138
139
  ## 持久化、完成与运维
@@ -141,7 +142,9 @@ Home 有一条只追加的存储迁移链。普通运行时只接受当前记录
141
142
  有效 Home;畸形状态被诊断,而不是自动修复。
142
143
 
143
144
  完成会冻结交付结果,并检查适用的验收、集成和审查合同。它区别于发布、已验证的远程合并、
144
- 物理静止和归档。归档要求工作已了结、受管资源干净可移除,保留 Task 历史,且不可重开。
145
+ 物理静止和归档。普通归档要求工作已了结、受管资源干净可移除。明确授权的 force 归档
146
+ 可以保留未解决证据与不安全资源,但不宣称交付或物理静止。两者都保留 Task 历史且不可
147
+ 重开。参见[Task 交付与归档](docs/task-delivery.zh-CN.md#归档)。
145
148
 
146
149
  运行时健康度和成本是观察,而不是语义判定。Agent 读取精确的故障和当前意图,以选择重试、
147
150
  修复或放弃。Yui 不会自动创建救援 Worker 或选择另一个模型。
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; the Web view is loopback and read-only.
33
+ user; Web is loopback-only, with read-only views and authenticated user controls.
34
34
  - **Isolated by default** — repository work happens in managed Git worktrees;
35
35
  the stable checkout stays read-only.
36
36
 
@@ -137,7 +137,11 @@ when necessary. A failed process does not erase the Task, and an uncertain
137
137
  submission is not silently repeated.
138
138
 
139
139
  For a visual overview, run `yui web` in another terminal. The local Web view
140
- shows the same tasks and pending questions; it is not a separate task system.
140
+ shows the same tasks and pending questions and lets you send messages, answer
141
+ questions and explicitly queue, steer or interrupt Task input. These authenticated
142
+ Task controls use the same operations as the CLI; Web is not a separate task system.
143
+ See [Web permissions](docs/architecture/capabilities-and-resources.md#cli-and-web)
144
+ and [input timing](docs/managed-turn-and-session-runtime.md#input-timing-queue-steer-and-interrupt).
141
145
 
142
146
  ## Architecture
143
147
 
@@ -243,7 +247,7 @@ the Controller are what touch the store:
243
247
  it moves work and records facts — it never judges an answer
244
248
 
245
249
  Agents work in Projects: read-only checkout + isolated worktrees.
246
- Web view (yui web): a loopback, read-only projection of the store.
250
+ Web (yui web): loopback views + authenticated user controls.
247
251
  ```
248
252
 
249
253
  ### Layered design
@@ -253,7 +257,7 @@ never a fixed workflow:
253
257
 
254
258
  ```text
255
259
  Experience — how you interact
256
- CLI (Operator) · Web (loopback, read-only) · native Agent sessions
260
+ CLI (Operator) · Web (loopback, authenticated) · native Agent sessions
257
261
  collect input · show facts · confirm actions · invoke capabilities
258
262
 
259
263
  Intelligence — who decides
@@ -290,9 +294,13 @@ extra states:
290
294
  WorkItem open ─▶ accepted ─▶ retired
291
295
 
292
296
  Draft holds planning only; activation adopts a delivery workspace.
293
- Archive needs settled work and clean worktrees; it cannot reopen.
297
+ Ordinary archive needs settled work and clean worktrees; it cannot reopen.
294
298
  ```
295
299
 
300
+ Explicitly authorized force archive can retain unresolved evidence and unsafe
301
+ resources; it does not prove delivery or permission to delete them.
302
+ See the [archive contract](docs/task-delivery.md#archive).
303
+
296
304
  ## Design principles
297
305
 
298
306
  ### Agents make decisions; Yui makes work durable
@@ -122,6 +122,13 @@ export function operationalAgentEnvironment(adapterId, source) {
122
122
  || `${dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`,
123
123
  HOME: source.HOME || homedir(),
124
124
  TERM: usableInteractiveTerminal(source.TERM),
125
+ // Base fallback only. Every managed Task launch has its isolation
126
+ // environment (TMPDIR = the Home-side runtime `roots.temporary`) assigned
127
+ // OVER this by the launch planner, so this branch is reached only by
128
+ // non-isolated global/ad-hoc launches with no managed runtime. Those run in
129
+ // the operator's own context, where their inherited TMPDIR (or the system
130
+ // temp) is the correct scratch — forcing it under control Home would
131
+ // misroute user scratch into Yui-managed storage.
125
132
  TMPDIR: source.TMPDIR || tmpdir()
126
133
  };
127
134
  }
@@ -2,6 +2,7 @@ import { validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js"
2
2
  import { validateTaskRecordReference } from "../task/taskRecordReference.js";
3
3
  import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
4
4
  import { createRunInput, createRunInputEnvelope, validateRunInput } from "../context/runInputContract.js";
5
+ import { providerRetryProjection } from "../runtime/providerRetry.js";
5
6
  import { boundedRunFailureDiagnostic, MAX_RUN_FAILURE_DIAGNOSTIC_BYTES, MAX_RUN_RESULT_OUTPUT_BYTES } from "../domain/agentResultTransport.js";
6
7
  export { boundedRunFailureDiagnostic, MAX_RUN_FAILURE_DIAGNOSTIC_BYTES, MAX_RUN_RESULT_OUTPUT_BYTES, transportAgentResult } from "../domain/agentResultTransport.js";
7
8
  /** Record lifecycle and observed delivery are separate read-only facts. */
@@ -17,6 +18,8 @@ export function runExecutionObservation(run, binding, events = []) {
17
18
  return {
18
19
  recordStatus: run.status,
19
20
  delivery,
21
+ retry: binding?.retry?.previousRunId === run.id || binding?.retry?.successorRunId === run.id
22
+ ? providerRetryProjection(binding) : null,
20
23
  ...(current === undefined ? {} : {
21
24
  attemptId: current.attemptId, observedAt: current.updatedAt,
22
25
  ...(current.nativeTurnId === undefined ? {} : { nativeTurnId: current.nativeTurnId })
@@ -215,6 +215,11 @@ const globalSessionChildren = [
215
215
  summary: "Reconcile durable Session owners with native sessions.",
216
216
  usage: "yui session reconcile [--report] [--cleanup]",
217
217
  options: ["--report", "--cleanup"]
218
+ },
219
+ {
220
+ name: "retry",
221
+ summary: "Inspect or control bounded Provider recovery for this Global Session.",
222
+ usage: "yui session retry <role> [show|cancel|disable|enable]"
218
223
  }
219
224
  ];
220
225
  const profileChildren = [
@@ -373,9 +378,9 @@ const taskChildren = [
373
378
  },
374
379
  {
375
380
  name: "list",
376
- summary: "List unarchived Task overviews.",
377
- usage: "yui task list [--all] [--verbose]",
378
- options: ["--all", "--verbose"]
381
+ summary: "List Task overviews or a bounded discovery catalog.",
382
+ usage: "yui task list [--all] [--verbose] | --view compact [--all] [--status <status>] [--project <id>] [--search <text>] [--attention <category>] [--limit <1..100>] [--cursor <cursor>]",
383
+ options: ["--all", "--verbose", "--view", "--status", "--project", "--search", "--attention", "--limit", "--cursor"]
379
384
  },
380
385
  { name: "show", summary: "Show a Task.", usage: "yui task show <id>" },
381
386
  {
@@ -406,10 +411,16 @@ const taskChildren = [
406
411
  usage: "yui task remote-delivery <task> [--json]",
407
412
  options: ["--json"]
408
413
  },
414
+ {
415
+ name: "archive-preflight",
416
+ summary: "Inspect current archive and exact-owner cleanup blockers without changing state or authorizing removal.",
417
+ usage: "yui task archive-preflight <id> (--integrated|--abandon) [--force] [--json]",
418
+ options: ["--integrated", "--abandon", "--force", "--json"]
419
+ },
409
420
  {
410
421
  name: "archive",
411
- summary: "Archive a Task after confirming the main worktree outcome.",
412
- usage: "yui task archive <id> (--integrated [--force]|--abandon)",
422
+ summary: "Archive a terminal Task; explicit --force commits despite delivery/cleanup warnings, retaining unsafe resources.",
423
+ usage: "yui task archive <id> (--integrated|--abandon) [--force]",
413
424
  options: ["--integrated", "--abandon", "--force"]
414
425
  },
415
426
  { name: "reconcile", summary: "Run one immediate Controller reconciliation.", usage: "yui task reconcile <id>" },
@@ -435,7 +446,7 @@ const taskChildren = [
435
446
  {
436
447
  name: "message",
437
448
  summary: "Manage durable Task messages.",
438
- sections: [{ id: "manage", title: "Commands", entries: ["send", "handoff", "list", "show", "update", "retire"] }],
449
+ sections: [{ id: "manage", title: "Commands", entries: ["send", "queue", "steer", "handoff", "list", "show", "update", "retire"] }],
439
450
  children: [
440
451
  {
441
452
  name: "handoff",
@@ -454,6 +465,20 @@ const taskChildren = [
454
465
  },
455
466
  fileOptions: ["--body-file"]
456
467
  },
468
+ {
469
+ name: "queue",
470
+ summary: "Queue an input for delivery at the recipient's next legal opportunity (idempotent by request id).",
471
+ usage: "yui task message queue <id> (<body>|--body-file <path|->) --request-id <id> [--to <role> --work-item <id>|--review-round <id>]",
472
+ options: ["--body-file", "--request-id", "--to", "--work-item", "--review-round"],
473
+ fileOptions: ["--body-file"]
474
+ },
475
+ {
476
+ name: "steer",
477
+ summary: "Steer only a Role's exact current native Turn; saved and reported without fallback when unsupported.",
478
+ usage: "yui task message steer <id> (<body>|--body-file <path|->) --request-id <id> --expected-target <turn> --to <role> [--work-item <id>|--review-round <id>]",
479
+ options: ["--body-file", "--request-id", "--expected-target", "--to", "--work-item", "--review-round"],
480
+ fileOptions: ["--body-file"]
481
+ },
457
482
  {
458
483
  name: "list",
459
484
  summary: "List Task messages.",
@@ -592,17 +617,29 @@ const taskChildren = [
592
617
  {
593
618
  name: "publication",
594
619
  summary: "Create or update external PR/MR publication evidence for a Task.",
595
- sections: [{ id: "manage", title: "Commands", entries: ["upsert", "verify", "list", "show"] }],
620
+ sections: [{ id: "manage", title: "Commands", entries: ["upsert", "diff", "adopt", "verify", "list", "show"] }],
596
621
  children: [
597
622
  {
598
623
  name: "upsert",
599
624
  summary: "Create or immutably update an external PR/MR and its publication state.",
600
- 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>] [--remote-commit <sha>] [--state <open|merged|closed>] [--reported|--verified] [--evidence <text>] [--merged-at <iso-timestamp>]",
601
- options: ["--project", "--provider", "--repository", "--kind", "--id", "--url", "--title", "--source-branch", "--target-branch", "--local-commit", "--remote-commit", "--state", "--reported", "--verified", "--evidence", "--merged-at"]
625
+ 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>]",
626
+ options: ["--project", "--provider", "--repository", "--kind", "--id", "--url", "--title", "--source-branch", "--target-branch", "--local-commit", "--head-commit", "--remote-commit", "--state", "--reported", "--verified", "--evidence", "--merged-at"]
627
+ },
628
+ {
629
+ name: "diff",
630
+ summary: "Read a completed Task's fixed acceptance-to-publication candidate diff using local Git only.",
631
+ usage: "yui task publication diff <task>/<publication> [--integration <id>]",
632
+ options: ["--integration"]
633
+ },
634
+ {
635
+ name: "adopt",
636
+ summary: "Explicitly accept the reviewed publication candidate as covering the original completion.",
637
+ usage: "yui task publication adopt <task>/<publication> --reviewed-diff <sha256> --acceptance <text> [--integration <id>]",
638
+ options: ["--reviewed-diff", "--acceptance", "--integration"]
602
639
  },
603
640
  {
604
641
  name: "verify",
605
- summary: "Verify one current GitHub PR against the exact Task delivery head through gh.",
642
+ summary: "Observe the current PR/MR head and merge through its provider; record verification separately from Task coverage.",
606
643
  usage: "yui task publication verify (<task>/<publication-id> | <task> <publication-id>)"
607
644
  },
608
645
  {
@@ -622,7 +659,7 @@ const taskChildren = [
622
659
  summary: "Manage Roles within a Task.",
623
660
  sections: [{ id: "manage", title: "Commands", entries: [
624
661
  "add", "list", "status", "show", "update", "remove", "bind", "unbind",
625
- "session", "view", "takeover", "release"
662
+ "session", "interrupt", "view", "takeover", "release"
626
663
  ] }],
627
664
  children: [
628
665
  {
@@ -654,8 +691,13 @@ const taskChildren = [
654
691
  name: "session",
655
692
  summary: "Inspect, stop or explicitly select a new Task Role Session.",
656
693
  executable: true,
657
- sections: [{ id: "manage", title: "Commands", entries: ["inspect", "stop", "new"] }],
694
+ sections: [{ id: "manage", title: "Commands", entries: ["inspect", "retry", "stop", "new"] }],
658
695
  children: [
696
+ {
697
+ name: "retry",
698
+ summary: "Inspect or control Provider recovery without stopping an admitted Turn.",
699
+ usage: "yui task role session retry <task> <role> [show|cancel|disable|enable]"
700
+ },
659
701
  {
660
702
  name: "inspect",
661
703
  summary: "Read the current Session, Host process, and AgentRun facts.",
@@ -680,6 +722,12 @@ const taskChildren = [
680
722
  summary: "Attach read-only to an independent Provider presentation surface.",
681
723
  usage: "yui task role view <task> <role>"
682
724
  },
725
+ {
726
+ name: "interrupt",
727
+ summary: "Interrupt a Role's exact current native Turn via native cancel; optionally deliver a saved Message once after a proven terminal.",
728
+ usage: "yui task role interrupt <task> <role> --expected-target <turn> [--then-message <task/message>] [--request-id <id>]",
729
+ options: ["--expected-target", "--then-message", "--request-id"]
730
+ },
683
731
  {
684
732
  name: "takeover",
685
733
  summary: "Enter the PTY input gateway for an independent Provider process.",
@@ -706,7 +754,7 @@ const taskChildren = [
706
754
  children: [
707
755
  {
708
756
  name: "create",
709
- summary: "Create a work item.",
757
+ summary: "Create a separately managed result; a coherent Task may use zero WorkItems. Omit --role for Leader-direct execution; --role (including leader) selects a managed AgentRun executor. Isolate writable direct work before editing, then submit, integrate and accept its Candidate.",
710
758
  usage: "yui task work create <task> <title> [--project <project> ...] [--base-ref <project>=<ref> ...] [--objective <text>] [--accept <criterion> ...] [--after <work> ...] [--role <name>]",
711
759
  options: ["--project", "--base-ref", "--objective", "--accept", "--after", "--role"]
712
760
  },
@@ -922,7 +970,7 @@ const taskChildren = [
922
970
  },
923
971
  {
924
972
  name: "abort",
925
- summary: "Abandon a running or blocked Integration Attempt.",
973
+ summary: "Abandon an unadvanced Integration while preserving evidence; reconcile an already-applied target.",
926
974
  usage: "yui task integration abort <task>/<integration> --reason <text>",
927
975
  options: ["--reason"]
928
976
  },
@@ -1461,7 +1509,7 @@ export const ROOT_COMMAND = buildNode({
1461
1509
  sections: [
1462
1510
  { id: "global", title: "Global Role sessions", entries: ["context", "enter", "record", "replace"] },
1463
1511
  { id: "maintenance", title: "Maintenance", entries: ["stop"] },
1464
- { id: "recovery", title: "Recovery", entries: ["reconcile"] }
1512
+ { id: "recovery", title: "Recovery", entries: ["reconcile", "retry"] }
1465
1513
  ],
1466
1514
  children: globalSessionChildren
1467
1515
  },
@@ -1469,7 +1517,7 @@ export const ROOT_COMMAND = buildNode({
1469
1517
  name: "task",
1470
1518
  summary: "Manage Tasks, WorkItems, AgentRuns, and integration.",
1471
1519
  sections: [
1472
- { id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "activation", "execution", "complete", "cancel", "reopen", "retire", "list", "show", "context", "next-action", "remote-delivery", "archive", "replace", "reconcile", "upstream", "artifact"] },
1520
+ { id: "lifecycle", title: "Lifecycle", entries: ["create", "project", "base", "update", "activate", "activation", "execution", "complete", "cancel", "reopen", "retire", "list", "show", "context", "next-action", "remote-delivery", "archive-preflight", "archive", "replace", "reconcile", "upstream", "artifact"] },
1473
1521
  { id: "collaboration", title: "Collaboration", entries: ["message", "input", "grant", "workflow", "publication", "work", "run", "review", "integration", "role", "overlap", "change-set"] },
1474
1522
  { id: "knowledge", title: "Task Knowledge", entries: ["brief", "decision", "milestone", "event", "continuation", "wake"] }
1475
1523
  ],
@@ -229,6 +229,10 @@ export const INTERACTION_POLICIES = Object.freeze([
229
229
  ...taskTarget("remote-delivery"),
230
230
  trailingOptions: { "--json": "flag" }
231
231
  },
232
+ {
233
+ ...taskTarget("archive-preflight"),
234
+ trailingOptions: { "--integrated": "flag", "--abandon": "flag", "--force": "flag", "--json": "flag" }
235
+ },
232
236
  {
233
237
  ...taskTarget("archive", 2, ["completed", "cancelled"]),
234
238
  trailingOptions: { "--integrated": "flag", "--abandon": "flag", "--force": "flag" },
@@ -574,9 +578,9 @@ export const INTERACTION_POLICIES = Object.freeze([
574
578
  entity: "integration-attempt",
575
579
  provider: "integration-attempts",
576
580
  actionTarget: true,
577
- ...(command === "continue" ? { statuses: ["blocked", "validating"] } : {}),
578
- ...(command === "resolve" ? { statuses: ["blocked"] } : {}),
579
- ...(command === "abort" ? { statuses: ["running", "blocked"] } : {})
581
+ ...(command === "continue" ? { statuses: ["running", "conflicted", "blocked", "validating"] } : {}),
582
+ ...(command === "resolve" ? { statuses: ["blocked", "conflicted"] } : {}),
583
+ ...(command === "abort" ? { statuses: ["running", "conflicted", "blocked", "validating"] } : {})
580
584
  }],
581
585
  ...(command === "resolve"
582
586
  ? { trailingOptions: { "--option": "value", "--rationale": "value" } }
@@ -3,7 +3,7 @@
3
3
  export function taskDiagnosticTarget(args) {
4
4
  if (args[0] !== "task")
5
5
  return undefined;
6
- if (["show", "context", "next-action"].includes(args[1] ?? "")) {
6
+ if (["show", "context", "next-action", "archive-preflight"].includes(args[1] ?? "")) {
7
7
  return args[1] === "context" && ["inspect", "delta"].includes(args[2] ?? "")
8
8
  ? args[3] : args[2];
9
9
  }
@@ -101,7 +101,30 @@ function runStagedUpdate(ports, staged, home) {
101
101
  const captured = captureControllerLifecycle(ports, staged.version, home);
102
102
  if ("outcome" in captured)
103
103
  return captured;
104
- return activateAndVerify(ports, staged, home, captured.lifecycle, preflight);
104
+ // An older Controller can finish a launch between preflight and drain.
105
+ // Recheck after its exact stop, before changing the install or storage.
106
+ let fencedPreflight;
107
+ try {
108
+ fencedPreflight = ports.preflight(staged, home);
109
+ }
110
+ catch (error) {
111
+ return restoreControllerOrReport(ports, home, captured.lifecycle, {
112
+ outcome: "aborted", phase: "preflight",
113
+ message: `Quiesced compatibility preflight failed: ${messageOf(error)}`,
114
+ action: "The install and storage are unchanged; inspect the compatibility failure before retrying.",
115
+ recoverable: true, version: staged.version
116
+ });
117
+ }
118
+ if (fencedPreflight.status === "blocked") {
119
+ return restoreControllerOrReport(ports, home, captured.lifecycle, {
120
+ outcome: "aborted", phase: "preflight",
121
+ message: fencedPreflight.message, action: fencedPreflight.action,
122
+ recoverable: true, version: staged.version,
123
+ ...(fencedPreflight.blockers === undefined ? {} : { blockers: fencedPreflight.blockers }),
124
+ ...(fencedPreflight.sceneUnchanged === true ? { sceneUnchanged: true } : {})
125
+ });
126
+ }
127
+ return activateAndVerify(ports, staged, home, captured.lifecycle, fencedPreflight);
105
128
  }
106
129
  finally {
107
130
  releaseHandover?.();
@@ -25,14 +25,15 @@
25
25
  * mismatch fails closed.
26
26
  */
27
27
  import { spawnSync } from "node:child_process";
28
- import { accessSync, constants, existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
29
- import { tmpdir } from "node:os";
28
+ import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
30
29
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
31
30
  import { fileURLToPath } from "node:url";
32
31
  import { runtimeError } from "../errors/cliError.js";
33
32
  import { isConcreteVersion } from "../domain/validation.js";
34
33
  import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
35
34
  import { acquireHandoverLock } from "../release/runtimeRelease.js";
35
+ import { updateStagingRoot } from "../storage/homeLayout.js";
36
+ import { resolveYuiHome } from "../storage/taskStore.js";
36
37
  const PACKAGE_NAME = "@zq-silk/yui";
37
38
  const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
38
39
  function resolveExecutable(command, environmentPath) {
@@ -64,7 +65,7 @@ function failedSpawnResult(message) {
64
65
  };
65
66
  }
66
67
  /** Build the real ports. `spawn` is injectable so tests avoid real installs. */
67
- export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot = tmpdir()) {
68
+ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot = updateStagingRoot(resolveYuiHome(environment))) {
68
69
  // The production adapter must not let a later PATH change select a
69
70
  // different npm during staging, activation, or recovery. Test doubles are
70
71
  // intentionally left untouched so deterministic tests can dispatch on the
@@ -105,6 +106,9 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
105
106
  throw runtimeError(`Refusing to stage a non-concrete version (${String(version)}): only an exact `
106
107
  + "major.minor.patch version can be pinned for an update.");
107
108
  }
109
+ // The Home staging parent may not exist yet on a Home that has never
110
+ // run an update; create it (private) before minting a throwaway prefix.
111
+ mkdirSync(stagingRoot, { recursive: true, mode: 0o700 });
108
112
  const stagingPath = mkdtempSync(join(stagingRoot, "yui-update-stage-"));
109
113
  let ownsStaging = true;
110
114
  try {
@@ -1,6 +1,8 @@
1
1
  /** `yui upgrade` plans or applies the supported linear storage migration chain. */
2
2
  import { runStorageUpgrade } from "../storage/upgrade/upgradeOrchestrator.js";
3
- import { ensureFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
3
+ import { ensureFileTaskController, ensureFileTaskControllerIdentity, stopFileTaskController } from "../controller/clientRuntime.js";
4
+ import { callController, ControllerClientError } from "../core/controllerClient.js";
5
+ import { spawn } from "node:child_process";
4
6
  import { runtimeError, usageError } from "../errors/cliError.js";
5
7
  import { acquireHandoverLock, isForeignHandoverLockHeld, isHandoverLockHeld } from "../release/runtimeRelease.js";
6
8
  /**
@@ -43,12 +45,29 @@ async function runInteractiveUpgrade(home, environment) {
43
45
  const handover = acquireHandoverLock(home);
44
46
  let controllerWasRunning = false;
45
47
  try {
48
+ const previous = await captureUpgradeController(home);
46
49
  const stopped = await stopFileTaskController(home, {
47
50
  environment,
48
- handoverOwnerPid: process.pid
51
+ handoverOwnerPid: process.pid,
52
+ ...(previous === undefined ? {} : { expectedPid: previous.pid })
49
53
  });
50
54
  controllerWasRunning = stopped.stopped;
51
55
  const result = await runStorageUpgrade({ home, mode: "execute" });
56
+ if (controllerWasRunning && result.outcome === "blocked") {
57
+ if (previous === undefined) {
58
+ throw runtimeError("Upgrade was blocked without changing storage; the stopped Controller identity is unknown. Keep the Home quiesced.");
59
+ }
60
+ await ensureFileTaskControllerIdentity(home, previous.identity, {
61
+ environment, handoverOwnerPid: process.pid,
62
+ spawnController: (_home, launchEnvironment) => {
63
+ const child = spawn(previous.identity.executablePath, [...previous.identity.args], {
64
+ env: launchEnvironment, detached: true, stdio: "ignore"
65
+ });
66
+ child.unref();
67
+ return child.pid;
68
+ }
69
+ });
70
+ }
52
71
  if (controllerWasRunning
53
72
  && (result.outcome === "upgraded" || result.outcome === "already-current")) {
54
73
  try {
@@ -70,6 +89,27 @@ async function runInteractiveUpgrade(home, environment) {
70
89
  handover.release();
71
90
  }
72
91
  }
92
+ async function captureUpgradeController(home) {
93
+ let value;
94
+ try {
95
+ value = await callController(home, "controller.identity", {});
96
+ }
97
+ catch (error) {
98
+ if (error instanceof ControllerClientError && error.code === "CONTROLLER_NOT_RUNNING")
99
+ return undefined;
100
+ throw error;
101
+ }
102
+ const identity = value;
103
+ if (!Number.isSafeInteger(identity.pid) || identity.pid < 1
104
+ || typeof identity.executablePath !== "string" || !identity.executablePath
105
+ || !Array.isArray(identity.args) || identity.args.some(arg => typeof arg !== "string")
106
+ || typeof identity.version !== "string" || !identity.version) {
107
+ throw runtimeError("Cannot capture the exact Controller for a reversible upgrade preflight.");
108
+ }
109
+ return { pid: identity.pid, identity: {
110
+ executablePath: identity.executablePath, args: identity.args, version: identity.version
111
+ } };
112
+ }
73
113
  async function runUpdateOwnedUpgrade(home, environment) {
74
114
  const ownerText = environment.YUI_UPDATE_HANDOVER_OWNER_PID;
75
115
  const ownerPid = ownerText === undefined ? Number.NaN : Number(ownerText);