@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/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { randomUUID } from "node:crypto";
4
+ import { recordTaskInterruptResult } from "./message/taskInterrupt.js";
4
5
  import { agentAdapterLabel as adapterLabel } from "./agent/adapterCatalog.js";
5
6
  import { readFileSync } from "node:fs";
6
7
  import { resolve } from "node:path";
@@ -12,6 +13,7 @@ import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
12
13
  import { routeInvocation } from "./cli/invocationRouter.js";
13
14
  import { renderCompletion } from "./cli/completion.js";
14
15
  import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
16
+ import { recordGlobalInterruptResult, recordGlobalSteerResult } from "./message/globalInterrupt.js";
15
17
  import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/interactiveSelection.js";
16
18
  import { runCompletionWizard } from "./cli/completionWizard.js";
17
19
  import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
@@ -40,9 +42,13 @@ import { runResourcesCommand } from "./commands/resourcesCommands.js";
40
42
  import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
41
43
  import { runProjectCommand } from "./commands/projectCommands.js";
42
44
  import { previewProfileAgentConfigurationMutation, runProfileCommand } from "./commands/profileCommands.js";
43
- import { assertWorkItemDependenciesCompletedForCommand, dispatchPreparedReviewRound, failPendingReviewRound, preserveReviewRoundWorkspace, parseTaskCompletionRequest, previewTaskRoleAgentConfigurationMutation, preflightTaskCompletion, runTaskCommand, planReplicatedWorkItemLanes, validateTaskArchiveRequest } from "./commands/taskCommands.js";
45
+ import { assertWorkItemDependenciesCompletedForCommand, requireWorkItemAssignee, dispatchPreparedReviewRound, failPendingReviewRound, preserveReviewRoundWorkspace, parseTaskCompletionRequest, previewTaskRoleAgentConfigurationMutation, preflightTaskCompletion, runTaskCommand, planReplicatedWorkItemLanes, validateTaskArchiveRequest, parseTaskArchiveArguments } from "./commands/taskCommands.js";
44
46
  import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./commands/taskRemoteDeliveryCommand.js";
47
+ import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
48
+ import { inspectTaskArchive, renderTaskArchivePreflight } from "./task/archivePreflight.js";
49
+ import { CleanupInspectionError } from "./workspace/cleanupInspection.js";
45
50
  import { runTaskPublicationVerifyCommand } from "./commands/taskPublicationVerifyCommand.js";
51
+ import { runTaskPublicationAdoptCommand } from "./commands/taskPublicationAdoptCommand.js";
46
52
  import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
47
53
  import { createGitLabCliPublicationVerifier } from "./external/gitlabPublicationVerifier.js";
48
54
  import { taskLocalActor, assertTaskDeliveryAuthority } from "./commands/taskActor.js";
@@ -75,10 +81,11 @@ import { runRuntimeObservationHookCommand } from "./controller/runtimeObservatio
75
81
  import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
76
82
  import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
77
83
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
78
- import { AGENT_HOST_CONTROL_PROTOCOL, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl } from "./runtime/agentHost.js";
84
+ import { AGENT_HOST_CONTROL_PROTOCOL, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl, sendAgentHostSteerControl, sendAgentHostCancelControl, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "./runtime/agentHost.js";
79
85
  import { unknownAgentRunConfiguration } from "./runtime/agentRunConfiguration.js";
80
86
  import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
81
87
  import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
88
+ import { snapshotWorkItemCandidate } from "./repository/workItemCandidateSnapshot.js";
82
89
  import { inspectStorageSchema } from "./storage/storageSchema.js";
83
90
  import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
84
91
  import { resolveYuiHome } from "./storage/taskStore.js";
@@ -111,6 +118,9 @@ const rawArgs = [...taskFinalReviewInvocation.args];
111
118
  const jsonOutput = rawArgs.includes("--json");
112
119
  const args = normalizeAliases(jsonOutput ? rawArgs.filter((argument) => argument !== "--json") : rawArgs);
113
120
  void main().catch((error) => {
121
+ if (error instanceof CleanupInspectionError) {
122
+ error = cleanupCliError(error, error.checks[0]?.resource ?? "workspace");
123
+ }
114
124
  if (error instanceof CliError) {
115
125
  const rendered = jsonOutput
116
126
  ? JSON.stringify({ ok: false, code: error.code, message: error.message, details: error.details })
@@ -748,11 +758,146 @@ export async function main() {
748
758
  emit(result);
749
759
  return;
750
760
  }
761
+ // The session surface (record/replace/enter/context) only ever yields the
762
+ // enter control; the live input actions live under the top-level `role`
763
+ // command below, never here.
764
+ if (result.kind !== "enter") {
765
+ throw new Error("Session commands cannot perform a live input control.");
766
+ }
751
767
  await ensureFileTaskController(home, { environment: process.env });
752
768
  await runtime.prepareGlobalRoleEnter(result.role.name);
753
769
  tmux.attachRole("operator", result.role.name, "auto");
754
770
  return;
755
771
  }
772
+ if (resolved[0] === "role") {
773
+ // decision-3 §7 CLI grammar: the durable Global input actions are top-level
774
+ // `role message queue|steer <global-role> …` and `role interrupt
775
+ // <global-role> …`, distinct from `config role …` (desired configuration)
776
+ // and `session …` (native session lifecycle). Core persists the durable
777
+ // Global-owned Message, proves owner/target/capability/writer-fence from
778
+ // durable state, and returns either a string disposition (queued,
779
+ // idempotent-replay, or an explicit not-steered/not-interrupted failure with
780
+ // its exact code) or a resolved live intent. The CLI performs at most one
781
+ // native edge with scope "global" and taskId omitted — the same shared
782
+ // resolver and transport as a Task Role, never a fabricated Task.
783
+ let globalInputFailure;
784
+ const roleOptions = {
785
+ yuiHome: home,
786
+ env: process.env,
787
+ jsonOutput,
788
+ onInputFailure: failure => { globalInputFailure = failure; }
789
+ };
790
+ const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
791
+ if (resolved[1] === "message" && resolved[2] === "queue" && resolved[3] !== undefined) {
792
+ await callController(home, "scheduler.signal", {
793
+ key: `global-role:${encodeURIComponent(resolved[3])}`
794
+ }).catch(() => { });
795
+ }
796
+ if (typeof result === "string") {
797
+ if (globalInputFailure !== undefined) {
798
+ emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
799
+ return;
800
+ }
801
+ emit(result);
802
+ return;
803
+ }
804
+ if (result.kind === "input-steer") {
805
+ // Core already persisted the durable Global Message and proved target +
806
+ // capability + writer fence from the Global Role's own Session set. This
807
+ // is the single live edge: one native steer of the exact current Turn,
808
+ // scope "global", no retarget and no fallback to interrupt or queue.
809
+ await ensureFileTaskController(home, { environment: process.env });
810
+ let control;
811
+ try {
812
+ control = await sendAgentHostSteerControl({
813
+ home,
814
+ scope: "global",
815
+ roleName: result.roleName,
816
+ control: {
817
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
818
+ type: "steer-turn",
819
+ nativeSessionId: result.target.nativeSessionId,
820
+ nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
821
+ authority: result.target.authority,
822
+ run: { attemptId: result.receiptId, boundedText: result.text }
823
+ }
824
+ });
825
+ }
826
+ catch (error) {
827
+ recordGlobalSteerResult(store, result.roleName, result.messageId, {
828
+ state: "steer-unknown", outcome: "pending",
829
+ detail: error instanceof Error ? error.message : String(error)
830
+ });
831
+ throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
832
+ + `${error instanceof Error ? error.message : String(error)}. `
833
+ + "The Message is retained and its outcome is recorded from the Host; whether the "
834
+ + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
835
+ + "do not reissue the same input under a new requestId or a different action.");
836
+ }
837
+ const steer = foldSteerLiveReceipt(control);
838
+ recordGlobalSteerResult(store, result.roleName, result.messageId, steer);
839
+ if (steer.state !== "steered") {
840
+ emitControlFailure(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_NOT_DELIVERED", { roleName: result.roleName, messageId: result.messageId, steer });
841
+ return;
842
+ }
843
+ emit(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), false, {
844
+ roleName: result.roleName,
845
+ messageId: result.messageId, steer
846
+ });
847
+ return;
848
+ }
849
+ if (result.kind === "input-interrupt") {
850
+ // The single live edge for a Global interrupt: one native cancel of the
851
+ // exact current Turn, scope "global". Never a kill/restart/detach. Any
852
+ // then-handoff was already claimed durably by Core (an existing Global
853
+ // Message owned by this Role) and is delivered once by the ordinary
854
+ // continuation path after this Turn reaches a proven terminal.
855
+ await ensureFileTaskController(home, { environment: process.env });
856
+ let control;
857
+ try {
858
+ control = await sendAgentHostCancelControl({
859
+ home,
860
+ scope: "global",
861
+ roleName: result.roleName,
862
+ control: {
863
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
864
+ type: "cancel",
865
+ nativeOnly: true,
866
+ nativeSessionId: result.target.nativeSessionId,
867
+ attemptId: result.target.attemptId,
868
+ authority: result.target.authority
869
+ }
870
+ });
871
+ }
872
+ catch (error) {
873
+ recordGlobalInterruptResult(store, result.roleName, result.receiptId, {
874
+ state: "interrupt-unknown", outcome: "cancel-requested",
875
+ detail: error instanceof Error ? error.message : String(error)
876
+ });
877
+ throw runtimeError(`Interrupt of global role ${result.roleName} did not complete: `
878
+ + `${error instanceof Error ? error.message : String(error)}. `
879
+ + "No process was killed; re-read the Session before retrying.");
880
+ }
881
+ const interrupt = foldInterruptLiveReceipt(control);
882
+ recordGlobalInterruptResult(store, result.roleName, result.receiptId, interrupt);
883
+ if (interrupt.state !== "interrupt-requested") {
884
+ emitControlFailure(interruptReceiptOutput(result.output, result.roleName, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN" : "INTERRUPT_NOT_DELIVERED", { roleName: result.roleName, interrupt });
885
+ return;
886
+ }
887
+ emit(interruptReceiptOutput(result.output, result.roleName, interrupt), false, {
888
+ roleName: result.roleName,
889
+ ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
890
+ interrupt
891
+ });
892
+ return;
893
+ }
894
+ if (result.kind !== "enter") {
895
+ throw new Error("Role command returned an invalid control result.");
896
+ }
897
+ // A top-level `role` command never enters a runtime Session; that is
898
+ // `session enter`.
899
+ throw usageError("Use 'yui session enter <role>' to attach to a Global Role.");
900
+ }
756
901
  if (resolved[0] === "operator") {
757
902
  if (resolved[1] === "enter") {
758
903
  if (resolved.length !== 2)
@@ -774,6 +919,15 @@ export async function main() {
774
919
  return;
775
920
  }
776
921
  if (resolved[0] === "task") {
922
+ if (resolved[1] === "archive-preflight") {
923
+ const request = parseTaskArchiveArguments(resolved.slice(2), "archive-preflight");
924
+ if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== request.taskId) {
925
+ throw usageError("Archive inspection must remain within this Session's Task.");
926
+ }
927
+ const data = await inspectTaskArchive(workspaceCoordinator, request);
928
+ emit(renderTaskArchivePreflight(data), false, data);
929
+ return;
930
+ }
777
931
  if (resolved[1] === "artifact") {
778
932
  // File/directory artifacts live in the Task's local Git repository, so
779
933
  // their save/read/list are asynchronous and handled here rather than in
@@ -894,6 +1048,13 @@ export async function main() {
894
1048
  emit(result.output, false, result.data);
895
1049
  return;
896
1050
  }
1051
+ if (resolved[1] === "publication" && (resolved[2] === "diff" || resolved[2] === "adopt")) {
1052
+ const result = await runTaskPublicationAdoptCommand(resolved.slice(2), store, {
1053
+ environment: process.env
1054
+ });
1055
+ emit(result.output, false, result.data);
1056
+ return;
1057
+ }
897
1058
  if (resolved[1] === "publication" && resolved[2] === "verify") {
898
1059
  const result = await runTaskPublicationVerifyCommand(resolved.slice(3), store, {
899
1060
  verifiers: {
@@ -904,12 +1065,6 @@ export async function main() {
904
1065
  environmentPath: process.env.PATH
905
1066
  })
906
1067
  },
907
- candidateForTask: async (taskId) => {
908
- const status = store.getTask(taskId)?.status;
909
- return status === "active" || status === "cancelled"
910
- ? snapshotActualTaskReviewCandidate(taskId, store, workspacePreparer)
911
- : null;
912
- },
913
1068
  environment: process.env
914
1069
  });
915
1070
  emit(result.output, false, result.data);
@@ -1015,7 +1170,7 @@ export async function main() {
1015
1170
  await new WorkItemChangeSetManager(store).assertIntegrated(reference.taskId, reference.localId);
1016
1171
  }
1017
1172
  catch (error) {
1018
- throw usageError(error instanceof Error ? error.message : String(error));
1173
+ throw cleanupCliError(error, `work-item:${qualified}`);
1019
1174
  }
1020
1175
  }
1021
1176
  let removal;
@@ -1067,7 +1222,7 @@ export async function main() {
1067
1222
  let archiveRemoteDeliveryProof;
1068
1223
  let archiveTaskReviewCandidate;
1069
1224
  if (resolved[1] === "archive") {
1070
- const { taskId, disposition, forceUnverified } = validateTaskArchiveRequest(resolved.slice(2), store, {
1225
+ const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
1071
1226
  runtime,
1072
1227
  environment: process.env,
1073
1228
  yuiHome: home
@@ -1075,11 +1230,26 @@ export async function main() {
1075
1230
  const task = store.getTask(taskId);
1076
1231
  if (task === null)
1077
1232
  throw new Error(`Task disappeared after archive validation: ${taskId}.`);
1078
- if (task.status !== "archived") {
1233
+ if (force || task.status === "archived") {
1234
+ // Archive admission and mandatory audit commit before any fallible
1235
+ // filesystem/provider work. Repeats report facts, never replay cleanup.
1236
+ const admitted = runTaskCommand(resolved.slice(1), store, {
1237
+ runtime, environment: process.env, yuiHome: home
1238
+ });
1239
+ if (force && admitted.kind === "output"
1240
+ && admitted.data.changed) {
1241
+ await workspaceCoordinator.cleanupArchivedTask(taskId, disposition);
1242
+ }
1243
+ const current = store.getTask(taskId);
1244
+ const archive = taskArchiveDiagnostics(store, current);
1245
+ emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
1246
+ return;
1247
+ }
1248
+ {
1079
1249
  if (disposition === "integrated") {
1080
1250
  archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
1081
1251
  archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
1082
- assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery, { forceUnverified });
1252
+ assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
1083
1253
  }
1084
1254
  const workItemIds = store.listManagedWorkspaces(task.id)
1085
1255
  .flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
@@ -1091,15 +1261,15 @@ export async function main() {
1091
1261
  await new WorkItemChangeSetManager(store).assertIntegrated(task.id, item.id);
1092
1262
  }
1093
1263
  catch (error) {
1094
- throw usageError(error instanceof Error ? error.message : String(error));
1264
+ throw cleanupCliError(error, `work-item:${task.id}/${item.id}`);
1095
1265
  }
1096
1266
  }
1097
1267
  const cleanup = await workspaceCoordinator.cleanupTaskForArchive(task.id, disposition);
1098
1268
  if (cleanup.status === "retained-dirty") {
1099
- throw usageError(cleanup.error ?? `Task ${task.id} has dirty managed worktrees and remains terminal.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "dirty-worktree", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true));
1269
+ throw usageError(cleanup.error ?? `Task ${task.id} has dirty managed worktrees and remains terminal.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "dirty-worktree", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true, cleanup.checks));
1100
1270
  }
1101
1271
  if (cleanup.status === "failed") {
1102
- throw usageError(`Task ${task.id} worktree cleanup failed: ${cleanup.error ?? "unknown error"}.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "cleanup-failed", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true));
1272
+ throw usageError(`Task ${task.id} worktree cleanup failed: ${cleanup.error ?? "unknown error"}.`, undefined, cleanupBlockedDetails(cleanup.reason ?? "cleanup-failed", cleanup.resource ?? `task:${task.id}`, cleanup.retryable ?? true, cleanup.checks));
1103
1273
  }
1104
1274
  }
1105
1275
  }
@@ -1133,9 +1303,8 @@ export async function main() {
1133
1303
  // Authority and pure Lane-shape checks precede every physical or
1134
1304
  // durable workspace preparation performed for dispatch.
1135
1305
  assertTaskDeliveryAuthority(store, process.env, task.id);
1136
- if (item.assignee !== undefined) {
1137
- workItemDispatchLanePlan(resolved, store, item);
1138
- }
1306
+ requireWorkItemAssignee(item);
1307
+ workItemDispatchLanePlan(resolved, store, item);
1139
1308
  }
1140
1309
  // A rejected Candidate starts a new execution iteration. Release every
1141
1310
  // terminal Lane Role runtime before preparing the new Lane workspaces;
@@ -1259,17 +1428,17 @@ export async function main() {
1259
1428
  laneDispatchRelease = preparedLanes.release;
1260
1429
  laneDispatchProjectPaths = preparedLanes.projectPaths;
1261
1430
  }
1262
- const candidateGitSnapshot = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1263
- const directTaskMainSnapshot = await directTaskMainSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1431
+ const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1264
1432
  const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
1265
1433
  ? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
1266
1434
  : archiveTaskReviewCandidate;
1267
1435
  const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
1268
- // Read-only, and only for a Session inspect. The command itself stays
1436
+ // Read-only Host evidence for Session inspect and Role status/list. The command stays
1269
1437
  // synchronous over persisted state; this is the live reading it prints
1270
1438
  // beside those facts, prepared here because the Host is reached over a
1271
1439
  // socket.
1272
- const liveRunConfiguration = await liveRunConfigurationForTaskCommand(resolved, store, home);
1440
+ const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
1441
+ const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
1273
1442
  // Physical preparation may precede the durable write, but Task status,
1274
1443
  // workspace identity/cwd, and ManagedWorkspace ownership are adopted by
1275
1444
  // one transaction. A failed attempt therefore leaves the Task Draft and
@@ -1295,11 +1464,10 @@ export async function main() {
1295
1464
  ? {}
1296
1465
  : { completionPublishedTreeProof }),
1297
1466
  ...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
1298
- ...(candidateGitSnapshot === undefined ? {} : { candidateGitSnapshot }),
1467
+ ...candidateSnapshots,
1299
1468
  ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1300
1469
  ...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
1301
1470
  ...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
1302
- ...(directTaskMainSnapshot === undefined ? {} : { directTaskMainSnapshot }),
1303
1471
  ...(actualTaskReviewCandidate === undefined
1304
1472
  ? {}
1305
1473
  : { actualTaskReviewCandidate }),
@@ -1312,6 +1480,7 @@ export async function main() {
1312
1480
  ...(liveRunConfiguration === undefined
1313
1481
  ? {}
1314
1482
  : { liveRunConfiguration }),
1483
+ ...(liveHostObservations === undefined ? {} : { liveHostObservations }),
1315
1484
  ...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
1316
1485
  ...(validateAgentConfiguration === undefined
1317
1486
  ? {}
@@ -1425,12 +1594,19 @@ export async function main() {
1425
1594
  await workspacePreparer.prepareTaskWorkspace(task.id);
1426
1595
  }
1427
1596
  }
1597
+ const controlData = result.data;
1598
+ const failureCode = controlData?.steer?.code ?? controlData?.interrupt?.code;
1599
+ if (failureCode !== undefined) {
1600
+ emitControlFailure(result.output, failureCode, result.data);
1601
+ return;
1602
+ }
1428
1603
  emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
1429
1604
  ? result.data
1430
1605
  : { command: result.data, ...reviewData });
1431
1606
  return;
1432
1607
  }
1433
- if (jsonOutput && result.kind !== "session-stop") {
1608
+ if (jsonOutput && result.kind !== "session-stop"
1609
+ && result.kind !== "input-steer" && result.kind !== "input-interrupt") {
1434
1610
  throw usageError("Task Role view/takeover requires an interactive terminal.");
1435
1611
  }
1436
1612
  if (result.kind === "session-stop") {
@@ -1462,6 +1638,94 @@ export async function main() {
1462
1638
  tmux.attachRole(result.taskId, result.roleName, "read-only");
1463
1639
  return;
1464
1640
  }
1641
+ if (result.kind === "input-steer") {
1642
+ // Core already persisted the Message and proved target + capability +
1643
+ // writer fence. This is the single live edge: one native steer of the
1644
+ // exact current Turn, with no retarget and no fallback to interrupt or
1645
+ // queue. Its durable settlement flows through the steer receipt fold.
1646
+ await ensureFileTaskController(home, { environment: process.env });
1647
+ let control;
1648
+ try {
1649
+ control = await sendAgentHostSteerControl({
1650
+ home,
1651
+ scope: "task",
1652
+ taskId: result.taskId,
1653
+ roleName: result.roleName,
1654
+ control: {
1655
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1656
+ type: "steer-turn",
1657
+ nativeSessionId: result.target.nativeSessionId,
1658
+ nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
1659
+ authority: result.target.authority,
1660
+ run: { attemptId: result.receiptId, boundedText: result.text }
1661
+ }
1662
+ });
1663
+ }
1664
+ catch (error) {
1665
+ throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
1666
+ + `${error instanceof Error ? error.message : String(error)}. `
1667
+ + "The Message is retained and its outcome is recorded from the Host; whether the "
1668
+ + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
1669
+ + "do not reissue the same input under a new requestId or a different action.");
1670
+ }
1671
+ const steer = foldSteerLiveReceipt(control);
1672
+ if (steer.state !== "steered") {
1673
+ emitControlFailure(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_REJECTED", { taskId: result.taskId, roleName: result.roleName, messageId: result.messageId, steer });
1674
+ return;
1675
+ }
1676
+ emit(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), false, {
1677
+ taskId: result.taskId, roleName: result.roleName,
1678
+ messageId: result.messageId, steer
1679
+ });
1680
+ return;
1681
+ }
1682
+ if (result.kind === "input-interrupt") {
1683
+ // The single live edge for interrupt: one native cancel of the exact
1684
+ // current Turn. Never a kill/restart/detach. Any then-handoff was
1685
+ // already claimed durably by Core and is delivered once by the ordinary
1686
+ // continuation path after this Turn reaches a proven terminal.
1687
+ await ensureFileTaskController(home, { environment: process.env });
1688
+ let control;
1689
+ try {
1690
+ control = await sendAgentHostCancelControl({
1691
+ home,
1692
+ scope: "task",
1693
+ taskId: result.taskId,
1694
+ roleName: result.roleName,
1695
+ control: {
1696
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1697
+ type: "cancel",
1698
+ nativeOnly: true,
1699
+ nativeSessionId: result.target.nativeSessionId,
1700
+ // Native cancel names the exact original execution attempt it stops
1701
+ // (Host matches request.attemptId === activeRunAttemptId). That is
1702
+ // distinct from receiptId, the durable identity of this interrupt
1703
+ // control operation — never send the operation id as the turn id.
1704
+ attemptId: result.target.attemptId,
1705
+ authority: result.target.authority
1706
+ }
1707
+ });
1708
+ }
1709
+ catch (error) {
1710
+ recordTaskInterruptResult(store, result.taskId, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
1711
+ throw runtimeError(`Interrupt ${result.receiptId} of ${result.taskId}/${result.roleName} did not complete: `
1712
+ + `${error instanceof Error ? error.message : String(error)}. `
1713
+ + "No process was killed; re-read the Session before retrying.");
1714
+ }
1715
+ const interrupt = foldInterruptLiveReceipt(control);
1716
+ recordTaskInterruptResult(store, result.taskId, result.receiptId, interrupt);
1717
+ if (interrupt.state !== "interrupt-requested") {
1718
+ emitControlFailure(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN"
1719
+ : interrupt.state === "interrupt-not-active" ? "NO_ACTIVE_TURN" : "INTERRUPT_REJECTED", { taskId: result.taskId, roleName: result.roleName, receiptId: result.receiptId, interrupt });
1720
+ return;
1721
+ }
1722
+ emit(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), false, {
1723
+ taskId: result.taskId, roleName: result.roleName,
1724
+ ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
1725
+ interrupt
1726
+ });
1727
+ return;
1728
+ }
1465
1729
  const syncAuthority = async (authorityResult) => {
1466
1730
  let control;
1467
1731
  try {
@@ -1705,17 +1969,21 @@ function assertManagedSessionManifest(home, scope) {
1705
1969
  return manifest;
1706
1970
  }
1707
1971
  function cleanupCliError(error, fallbackResource) {
1972
+ if (error instanceof CleanupInspectionError) {
1973
+ return usageError(error.message, undefined, cleanupBlockedDetails(error.checks[0]?.reason ?? "cleanup-failed", fallbackResource, true, error.checks));
1974
+ }
1708
1975
  if (error instanceof WorkspaceCleanupBlockedError) {
1709
1976
  return usageError(error.message, undefined, cleanupBlockedDetails(error.reason, error.resource, error.retryable));
1710
1977
  }
1711
1978
  return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true));
1712
1979
  }
1713
- function cleanupBlockedDetails(reason, resource, retryable) {
1980
+ function cleanupBlockedDetails(reason, resource, retryable, checks) {
1714
1981
  return {
1715
1982
  status: "blocked",
1716
1983
  blockedBy: [{ resource, reason, retryable }],
1717
1984
  remainingResources: [resource],
1718
- retryable
1985
+ retryable,
1986
+ ...(checks === undefined ? {} : { checks })
1719
1987
  };
1720
1988
  }
1721
1989
  function cliWorkItemReference(value, environment) {
@@ -1766,34 +2034,11 @@ function assertWorkItemExecutionDependenciesForCommand(args, store, environment)
1766
2034
  assertWorkItemDependenciesCompletedForCommand(store, item);
1767
2035
  }
1768
2036
  async function candidateSnapshotForTaskCommand(args, store, preparer, environment, taskFinalReviewContract) {
1769
- if (args[0] !== "task")
1770
- return undefined;
1771
- const reviewableCandidateCommand = (args[1] === "work" && args[2] === "update"
1772
- && args[3] !== undefined && args[4] === "done") || (args[1] === "work" && args[2] === "group" && args[3] === "resolve"
1773
- && args[4] !== undefined);
1774
- // Explicit Task-final review requests must remain independent of the
1775
- // mutable global review trigger. Candidate snapshots are a delivery
1776
- // boundary for every writable WorkItem, not only review-configured Tasks.
1777
- const groupResolve = args[1] === "work" && args[2] === "group" && args[3] === "resolve";
1778
- if (!reviewableCandidateCommand
1779
- || (groupResolve && args.includes("--decision") && args[args.indexOf("--decision") + 1] !== "accept")) {
1780
- return undefined;
1781
- }
1782
- if (args[1] === "work" && args[2] === "update"
1783
- && args[3] !== undefined && args[4] === "done") {
1784
- const reference = cliWorkItemReference(args[3], environment);
1785
- const workspace = store.getWorkItemWorkspace(reference.taskId, reference.localId);
1786
- if (workspace === null) {
1787
- // The exact Task-final contract intentionally supports a Leader-direct,
1788
- // metadata-only Project Candidate. The command layer performs the full
1789
- // Task/WorkItem/source/contract validation before any aggregate write.
1790
- if (taskFinalReviewContract !== undefined)
1791
- return undefined;
1792
- throw usageError(`Reviewable direct WorkItem has no managed Candidate workspace: ${reference.localId}.`);
1793
- }
1794
- return preparer.snapshotCandidateWorkspace(workspace);
1795
- }
1796
- return undefined;
2037
+ if (args[0] !== "task" || args[1] !== "work" || args[2] !== "update"
2038
+ || args[3] === undefined || args[4] !== "done")
2039
+ return {};
2040
+ const reference = cliWorkItemReference(args[3], environment);
2041
+ return snapshotWorkItemCandidate(store, preparer, reference.taskId, reference.localId, taskFinalReviewContract);
1797
2042
  }
1798
2043
  async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, environment) {
1799
2044
  const isDispatch = args[0] === "task"
@@ -1817,9 +2062,20 @@ async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, e
1817
2062
  throw usageError(`${item.taskId}/${roleName} already has an active turn.`);
1818
2063
  }
1819
2064
  }
1820
- const held = preparer.acquireTaskProjectMaintenanceLocks(item.taskId);
2065
+ const held = await preparer.acquireTaskProjectMaintenanceLocks(item.taskId);
1821
2066
  const map = new Map();
1822
2067
  try {
2068
+ assertTaskDeliveryAuthority(store, environment, item.taskId);
2069
+ const currentItem = store.getWorkItem(item.taskId, item.id);
2070
+ if (currentItem?.revision !== item.revision
2071
+ || JSON.stringify(workItemDispatchLanePlan(args, store, currentItem)) !== JSON.stringify(plan)) {
2072
+ throw new Error(`Work item dispatch changed while waiting for Project maintenance: ${item.id}.`);
2073
+ }
2074
+ if (held.current.status !== "active" || held.current.executionGate.state !== "enabled"
2075
+ || plan.roles.some(roleName => store.getRole(item.taskId, roleName) === null
2076
+ || store.getActiveRun(item.taskId, roleName) !== null)) {
2077
+ throw new Error(`Task/Role dispatch state changed while waiting for Project maintenance: ${item.taskId}.`);
2078
+ }
1823
2079
  const projectPaths = new Map();
1824
2080
  for (const { projectId } of held.current.projectBindings) {
1825
2081
  const project = store.getProject(projectId);
@@ -1878,37 +2134,6 @@ async function prepareReviewLaneWorkspaces(taskId, reviewRoundId, store, prepare
1878
2134
  }
1879
2135
  return map;
1880
2136
  }
1881
- async function directTaskMainSnapshotForTaskCommand(args, store, preparer, environment, taskFinalReviewContract) {
1882
- if (taskFinalReviewContract === undefined
1883
- || args[0] !== "task"
1884
- || args[1] !== "work"
1885
- || args[2] !== "update"
1886
- || args[3] === undefined
1887
- || args[4] !== "done") {
1888
- return undefined;
1889
- }
1890
- const reference = cliWorkItemReference(args[3], environment);
1891
- const item = store.getWorkItem(reference.taskId, reference.localId);
1892
- if (item === null || item.writeProjectIds.length === 0
1893
- || store.getWorkItemWorkspace(reference.taskId, reference.localId) !== null) {
1894
- return undefined;
1895
- }
1896
- const workspace = store.getTaskWorkspace(reference.taskId);
1897
- // Exact Task-final Candidates may intentionally be metadata-only when no
1898
- // Task main exists. They remain review anchors, but are not eligible for the
1899
- // direct ChangeSet capture path.
1900
- if (workspace === null)
1901
- return undefined;
1902
- if (workspace.owner.type !== "task") {
1903
- throw usageError(`Task has no authoritative main workspace: ${reference.taskId}.`);
1904
- }
1905
- try {
1906
- return await preparer.snapshotDirectTaskMain(workspace, item.writeProjectIds);
1907
- }
1908
- catch (error) {
1909
- throw usageError(error instanceof Error ? error.message : String(error));
1910
- }
1911
- }
1912
2137
  async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, environment) {
1913
2138
  if (args[0] !== "task")
1914
2139
  return undefined;
@@ -2368,18 +2593,22 @@ async function warmLegacyRoleConfigurationMutation(commandArgs, store, catalogs)
2368
2593
  * no Host is running for most Sessions ever inspected, and the persisted facts
2369
2594
  * the command prints are worth showing regardless of whether a live one answered.
2370
2595
  */
2371
- async function liveRunConfigurationForTaskCommand(args, store, home) {
2372
- if (args[0] !== "task" || args[1] !== "role" || args[2] !== "session"
2373
- || args[3] !== "inspect" || args.length !== 6) {
2596
+ async function liveHostObservationsForTaskCommand(args, store, home) {
2597
+ if (args[0] !== "task" || args[1] !== "role")
2374
2598
  return undefined;
2375
- }
2376
- const taskId = args[4];
2377
- const roleName = args[5];
2378
- if (taskId === undefined || roleName === undefined)
2599
+ const inspect = args[2] === "session" && args[3] === "inspect" && args.length === 6;
2600
+ const status = args[2] === "status" && args.length === 5;
2601
+ const list = args[2] === "list" && args.length === 4;
2602
+ if (!inspect && !status && !list)
2379
2603
  return undefined;
2380
- // A Session must be recorded as active before asking. Probing a socket for a
2381
- // Role that never ran would report a reach failure as if it were a fact about
2382
- // that Role's Agent.
2604
+ const taskId = args[inspect ? 4 : 3];
2605
+ const roles = list ? store.listRoles(taskId).map(role => role.name) : [args[inspect ? 5 : 4]];
2606
+ const entries = await Promise.all(roles.map(async (roleName) => [
2607
+ roleName, await readLiveHostObservation(store, home, taskId, roleName)
2608
+ ]));
2609
+ return Object.fromEntries(entries.filter((entry) => entry[1] !== undefined));
2610
+ }
2611
+ async function readLiveHostObservation(store, home, taskId, roleName) {
2383
2612
  const sessions = store.getTaskRoleSessionSet(taskId, roleName);
2384
2613
  const active = sessions === null
2385
2614
  ? undefined
@@ -2387,8 +2616,7 @@ async function liveRunConfigurationForTaskCommand(args, store, home) {
2387
2616
  if (active === undefined)
2388
2617
  return undefined;
2389
2618
  if (active.status !== "active") {
2390
- return unknownAgentRunConfiguration(`This Session is ${active.status}, so there is no live Agent to report what it `
2391
- + "is running under.");
2619
+ return { detail: `This Session is ${active.status}; no live Host reading was requested.` };
2392
2620
  }
2393
2621
  try {
2394
2622
  const snapshot = await inspectAgentHost({
@@ -2398,18 +2626,20 @@ async function liveRunConfigurationForTaskCommand(args, store, home) {
2398
2626
  roleName
2399
2627
  });
2400
2628
  if (snapshot.nativeSessionId !== active.nativeSessionId
2401
- || snapshot.adapterId !== active.adapterId
2402
- || snapshot.state === "exited" || snapshot.state === "failed") {
2403
- return unknownAgentRunConfiguration("The Agent Host has no live connection matching the recorded Session.");
2629
+ || snapshot.adapterId !== active.adapterId) {
2630
+ return { detail: "The Agent Host does not match the recorded Session." };
2404
2631
  }
2405
- return snapshot.runConfiguration ?? unknownAgentRunConfiguration("The Agent Host is running but has no Provider Session open, so no Agent "
2406
- + "has reported a configuration yet.");
2632
+ return { snapshot };
2407
2633
  }
2408
2634
  catch (error) {
2409
- return unknownAgentRunConfiguration("The Agent Host for this Session could not be reached, so its live "
2410
- + `configuration is unavailable: ${error instanceof Error ? error.message : String(error)}`);
2635
+ return { detail: `The Agent Host could not be reached: ${error instanceof Error ? error.message : String(error)}` };
2411
2636
  }
2412
2637
  }
2638
+ function runConfigurationForHostObservation(host) {
2639
+ if (host === undefined)
2640
+ return undefined;
2641
+ return host.snapshot?.runConfiguration ?? unknownAgentRunConfiguration(host.detail ?? `Host=${host.snapshot?.state ?? "unknown"}; no live Agent configuration was reported.`);
2642
+ }
2413
2643
  function hasModelOrEffortMutation(args) {
2414
2644
  const operation = (args[0] === "config" && args[1] === "role")
2415
2645
  || (args[0] === "task" && args[1] === "role");
@@ -2468,7 +2698,7 @@ function selectionCall(store, catalogs, method, params) {
2468
2698
  case "role.list": return store.listGlobalRoles();
2469
2699
  case "role.show": return store.getGlobalRole(String(params.name ?? ""));
2470
2700
  case "project.list": return callOptional(reader, "listProjects");
2471
- case "task.list": return callOptional(reader, "listTasks");
2701
+ case "task.list": return store.listTaskChoices();
2472
2702
  case "task.integration.list": return store.listIntegrationAttempts(String(params.taskId ?? ""));
2473
2703
  case "task.change-set.list": return store.listChangeSets(String(params.taskId ?? ""));
2474
2704
  case "task.role.list": return callOptional(reader, "listRoles", [params.taskId]);
@@ -2547,6 +2777,50 @@ function emit(output, literal = false, data) {
2547
2777
  : { ok: true, data })
2548
2778
  : normalized}\n`);
2549
2779
  }
2780
+ function emitControlFailure(message, code, details) {
2781
+ process.exitCode = 2;
2782
+ process.stdout.write(`${jsonOutput
2783
+ ? JSON.stringify({ ok: false, code, message: message.trim(), details })
2784
+ : message.trim()}\n`);
2785
+ }
2786
+ /**
2787
+ * The human line for a live steer, corrected to the *actual* live acceptance
2788
+ * (decision-3 §7). The store command's `base` line is written optimistically
2789
+ * ("Steering …"); only a proven `steered` keeps it. `steer-unknown` (pending) is
2790
+ * delivery-unknown, and a rejected/unavailable steer did not deliver — in both
2791
+ * cases the Message is retained and the operator must not reissue. There is no
2792
+ * retarget, queue, or interrupt fallback here; this only reports.
2793
+ */
2794
+ function steerReceiptOutput(base, target, messageId, receipt) {
2795
+ if (receipt.state === "steered")
2796
+ return base;
2797
+ const detail = receipt.detail === undefined ? "" : ` ${receipt.detail}`;
2798
+ const head = receipt.state === "steer-unknown"
2799
+ ? `Steer message ${messageId} to ${target} is delivery-unknown (${receipt.outcome}): the Host `
2800
+ + "holds it but the Provider has not yet proven acceptance."
2801
+ : `Steer message ${messageId} to ${target} did not deliver (${receipt.outcome}).`;
2802
+ return `${head}${detail} The Message is retained; re-read the Session before acting, and do not `
2803
+ + "reissue the same input under a new requestId or a different action.\n";
2804
+ }
2805
+ /**
2806
+ * The human line for a live interrupt, corrected to `control.cancellation`
2807
+ * (decision-3 §7). Only a proven stop-request keeps the optimistic `base` line.
2808
+ * `not-active`/`unknown`/unavailable each report that nothing was proven stopped;
2809
+ * no process is ever killed. A then-handoff, if any, was already claimed durably
2810
+ * and is delivered once by the ordinary continuation path after a proven terminal.
2811
+ */
2812
+ function interruptReceiptOutput(base, target, receipt) {
2813
+ if (receipt.state === "interrupt-requested")
2814
+ return `${base.trim()}\nNative cancel requested; Turn termination is not yet proven.\n`;
2815
+ const detail = receipt.detail === undefined ? "" : ` ${receipt.detail}`;
2816
+ const reason = receipt.state === "interrupt-not-active"
2817
+ ? "found no active Turn to stop (not-active)"
2818
+ : receipt.state === "interrupt-unknown"
2819
+ ? "could not prove a stop (unknown)"
2820
+ : `did not complete (${receipt.outcome})`;
2821
+ return `Interrupt of ${target} ${reason}.${detail} No process was killed; re-read the Session `
2822
+ + "before retrying.\n";
2823
+ }
2550
2824
  function withControllerRefreshWarning(output, refresh, label) {
2551
2825
  if (refresh.status !== "failed")
2552
2826
  return output;