@zq-silk/yui 0.15.8 → 0.15.11

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 (151) hide show
  1. package/ARCHITECTURE.md +2 -0
  2. package/ARCHITECTURE.zh-CN.md +151 -0
  3. package/README.md +211 -14
  4. package/dist/agent/launchEnvironment.js +7 -0
  5. package/dist/artifacts/artifactCapability.js +74 -0
  6. package/dist/artifacts/artifactCommitLock.js +249 -0
  7. package/dist/artifacts/artifactPaths.js +151 -0
  8. package/dist/artifacts/gitArtifactRef.js +146 -0
  9. package/dist/artifacts/managedGit.js +332 -0
  10. package/dist/artifacts/taskArtifactRepository.js +277 -0
  11. package/dist/cli/commandCatalog.js +40 -16
  12. package/dist/cli/interactionPolicy.js +3 -3
  13. package/dist/cli/updateOrchestrator.js +24 -1
  14. package/dist/cli/updatePorts.js +7 -3
  15. package/dist/cli/upgradeCommand.js +42 -2
  16. package/dist/cli.js +403 -93
  17. package/dist/commands/globalRoleCommands.js +314 -4
  18. package/dist/commands/operatorCommands.js +33 -2
  19. package/dist/commands/projectCommands.js +6 -7
  20. package/dist/commands/releaseCommands.js +18 -0
  21. package/dist/commands/taskActivationCommands.js +22 -0
  22. package/dist/commands/taskActor.js +25 -0
  23. package/dist/commands/taskCommands.js +846 -155
  24. package/dist/commands/taskIntegrationCommands.js +16 -38
  25. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  26. package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
  27. package/dist/commands/taskRoleRuntimeStatus.js +35 -0
  28. package/dist/context/runContextPack.js +28 -16
  29. package/dist/context/taskContext.js +64 -5
  30. package/dist/controller/agentHostObservation.js +155 -0
  31. package/dist/controller/clientRuntime.js +17 -2
  32. package/dist/controller/controller.js +11 -2
  33. package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
  34. package/dist/controller/globalInputDelivery.js +119 -0
  35. package/dist/controller/jobControl.js +6 -2
  36. package/dist/controller/resourceInventory.js +14 -4
  37. package/dist/controller/resourceInventoryLinux.js +2 -6
  38. package/dist/controller/runtime.js +81 -6
  39. package/dist/controller/runtimeEventInbox.js +32 -3
  40. package/dist/controller/runtimeEventProcessor.js +26 -6
  41. package/dist/controller/runtimeHookRunFence.js +75 -19
  42. package/dist/controller/structuredProviderObservation.js +133 -70
  43. package/dist/coordination/workMailboxQueue.js +5 -0
  44. package/dist/execution/workItemExecutionProjection.js +1 -1
  45. package/dist/executor/agentExecutor.js +64 -4
  46. package/dist/executor/executorRegistry.js +3 -0
  47. package/dist/executor/fileRoleLaunchPlanner.js +78 -118
  48. package/dist/integration/deliveryObligation.js +2 -1
  49. package/dist/integration/gitIntegrationService.js +312 -382
  50. package/dist/integration/integrationAttempt.js +30 -4
  51. package/dist/integration/integrationQueueService.js +7 -7
  52. package/dist/integration/integrationSourceApplication.js +323 -0
  53. package/dist/kernel/builtinCapabilities.js +32 -24
  54. package/dist/message/globalInterrupt.js +33 -0
  55. package/dist/message/inputControlResolution.js +106 -0
  56. package/dist/message/message.js +423 -0
  57. package/dist/message/messageContinuation.js +126 -3
  58. package/dist/message/taskInterrupt.js +34 -0
  59. package/dist/observability/orchestrationMetrics.js +1 -1
  60. package/dist/plugins/pluginService.js +11 -3
  61. package/dist/release/releaseHandover.js +22 -0
  62. package/dist/release/releaseWorkflowPorts.js +15 -7
  63. package/dist/repository/gitWorkspace.js +72 -15
  64. package/dist/repository/taskWorkspaceCoordinator.js +134 -0
  65. package/dist/repository/taskWorkspacePreparer.js +120 -49
  66. package/dist/repository/workItemCandidateSnapshot.js +34 -0
  67. package/dist/resources/projectResource.js +0 -48
  68. package/dist/resources/projectResourceService.js +3 -81
  69. package/dist/resources/resourceDiscovery.js +3 -2
  70. package/dist/runtime/agentHost.js +152 -72
  71. package/dist/runtime/agentHostCompatibility.js +127 -0
  72. package/dist/runtime/agentHostProtocol.js +53 -0
  73. package/dist/runtime/executionEnvironment.js +0 -19
  74. package/dist/runtime/launchBroker.js +6 -0
  75. package/dist/runtime/sessionReconciliation.js +4 -4
  76. package/dist/runtime/taskRuntimeIsolation.js +30 -6
  77. package/dist/runtime/tmuxAdapters.js +5 -3
  78. package/dist/scheduler/operatorEvent.js +4 -0
  79. package/dist/scheduler/taskExecutionProjection.js +12 -1
  80. package/dist/scheduler/wakeReason.js +7 -1
  81. package/dist/scheduler/wakeupQueue.js +2 -0
  82. package/dist/setup/setupCommand.js +29 -16
  83. package/dist/storage/homeLayout.js +130 -0
  84. package/dist/storage/migrations/artifactsToGit.js +338 -0
  85. package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
  86. package/dist/storage/migrations/integrationContinuation.js +104 -0
  87. package/dist/storage/migrations/submitIntent.js +126 -0
  88. package/dist/storage/migrations/unifyHomeLayout.js +925 -0
  89. package/dist/storage/sqliteSchema.js +173 -7
  90. package/dist/storage/sqliteStore.js +41 -22
  91. package/dist/storage/storageVersions.js +1 -1
  92. package/dist/storage/storeRpc.js +2 -1
  93. package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
  94. package/dist/task/archiveDiagnostics.js +128 -0
  95. package/dist/task/nextAction.js +44 -11
  96. package/dist/task/taskActivation.js +26 -0
  97. package/dist/task/taskActivationService.js +85 -69
  98. package/dist/task/taskSubmission.js +236 -0
  99. package/dist/web/assets/client/app.js +58 -2
  100. package/dist/web/assets/client/components.js +1 -0
  101. package/dist/web/assets/client/i18n.js +6 -0
  102. package/dist/web/assets/client/taskSurface.js +202 -7
  103. package/dist/web/assets/client/view.js +7 -4
  104. package/dist/web/assets/shell.js +23 -0
  105. package/dist/web/assets/styles/layout.js +1 -1
  106. package/dist/web/assets/styles/widgets.js +12 -0
  107. package/dist/web/webServer.js +135 -4
  108. package/dist/web/webSnapshot.js +4 -3
  109. package/dist/web/webTaskSurface.js +225 -8
  110. package/dist/workItem/workItem.js +14 -10
  111. package/dist/workspace/workItemChangeSetManager.js +18 -2
  112. package/docs/agent-result-consumption.md +2 -0
  113. package/docs/agent-result-consumption.zh-CN.md +81 -0
  114. package/docs/agent-runtime-drivers.md +2 -0
  115. package/docs/agent-runtime-drivers.zh-CN.md +77 -0
  116. package/docs/architecture/README.md +44 -32
  117. package/docs/architecture/README.zh-CN.md +43 -0
  118. package/docs/architecture/capabilities-and-resources.md +118 -79
  119. package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
  120. package/docs/managed-turn-and-session-runtime.md +2 -0
  121. package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
  122. package/docs/observability/README.md +2 -0
  123. package/docs/observability/README.zh-CN.md +71 -0
  124. package/docs/plugin-sdk.md +320 -217
  125. package/docs/plugin-sdk.zh-CN.md +293 -0
  126. package/docs/provider-runtime.md +2 -0
  127. package/docs/provider-runtime.zh-CN.md +132 -0
  128. package/docs/release-workflow.md +41 -0
  129. package/docs/release-workflow.zh-CN.md +266 -0
  130. package/docs/roles-and-configuration.md +2 -0
  131. package/docs/roles-and-configuration.zh-CN.md +96 -0
  132. package/docs/sqlite-control-plane-design.md +225 -1
  133. package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
  134. package/docs/task-dag-semantics.md +80 -57
  135. package/docs/task-dag-semantics.zh-CN.md +59 -0
  136. package/docs/task-delivery.md +2 -0
  137. package/docs/task-delivery.zh-CN.md +82 -0
  138. package/docs/task-local-identity.md +2 -0
  139. package/docs/task-local-identity.zh-CN.md +58 -0
  140. package/docs/testing/verification-levels.md +26 -0
  141. package/docs/testing/verification-levels.zh-CN.md +80 -0
  142. package/i18n/README.zh-CN.md +199 -10
  143. package/package.json +2 -1
  144. package/skills/yui-leader/SKILL.md +88 -331
  145. package/skills/yui-leader/references/execution.md +405 -0
  146. package/skills/yui-leader/references/integration.md +52 -2
  147. package/skills/yui-leader/references/planning.md +109 -0
  148. package/skills/yui-leader/references/task-plugins.md +8 -4
  149. package/skills/yui-operator/SKILL.md +22 -4
  150. package/skills/yui-runtime/SKILL.md +27 -0
  151. package/skills/yui-runtime/references/publication.md +20 -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,12 +42,14 @@ 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 } from "./commands/taskCommands.js";
44
46
  import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./commands/taskRemoteDeliveryCommand.js";
47
+ import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
45
48
  import { runTaskPublicationVerifyCommand } from "./commands/taskPublicationVerifyCommand.js";
46
49
  import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
47
50
  import { createGitLabCliPublicationVerifier } from "./external/gitlabPublicationVerifier.js";
48
51
  import { taskLocalActor, assertTaskDeliveryAuthority } from "./commands/taskActor.js";
52
+ import { saveArtifactCapability, readArtifactCapability, listArtifactsCapability } from "./artifacts/artifactCapability.js";
49
53
  import { parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, finalizeStoppedTaskExecution, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
50
54
  import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
51
55
  import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
@@ -74,10 +78,11 @@ import { runRuntimeObservationHookCommand } from "./controller/runtimeObservatio
74
78
  import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
75
79
  import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
76
80
  import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
77
- import { AGENT_HOST_CONTROL_PROTOCOL, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl } from "./runtime/agentHost.js";
81
+ import { AGENT_HOST_CONTROL_PROTOCOL, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl, sendAgentHostSteerControl, sendAgentHostCancelControl, foldSteerLiveReceipt, foldInterruptLiveReceipt } from "./runtime/agentHost.js";
78
82
  import { unknownAgentRunConfiguration } from "./runtime/agentRunConfiguration.js";
79
83
  import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
80
84
  import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
85
+ import { snapshotWorkItemCandidate } from "./repository/workItemCandidateSnapshot.js";
81
86
  import { inspectStorageSchema } from "./storage/storageSchema.js";
82
87
  import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
83
88
  import { resolveYuiHome } from "./storage/taskStore.js";
@@ -747,11 +752,146 @@ export async function main() {
747
752
  emit(result);
748
753
  return;
749
754
  }
755
+ // The session surface (record/replace/enter/context) only ever yields the
756
+ // enter control; the live input actions live under the top-level `role`
757
+ // command below, never here.
758
+ if (result.kind !== "enter") {
759
+ throw new Error("Session commands cannot perform a live input control.");
760
+ }
750
761
  await ensureFileTaskController(home, { environment: process.env });
751
762
  await runtime.prepareGlobalRoleEnter(result.role.name);
752
763
  tmux.attachRole("operator", result.role.name, "auto");
753
764
  return;
754
765
  }
766
+ if (resolved[0] === "role") {
767
+ // decision-3 §7 CLI grammar: the durable Global input actions are top-level
768
+ // `role message queue|steer <global-role> …` and `role interrupt
769
+ // <global-role> …`, distinct from `config role …` (desired configuration)
770
+ // and `session …` (native session lifecycle). Core persists the durable
771
+ // Global-owned Message, proves owner/target/capability/writer-fence from
772
+ // durable state, and returns either a string disposition (queued,
773
+ // idempotent-replay, or an explicit not-steered/not-interrupted failure with
774
+ // its exact code) or a resolved live intent. The CLI performs at most one
775
+ // native edge with scope "global" and taskId omitted — the same shared
776
+ // resolver and transport as a Task Role, never a fabricated Task.
777
+ let globalInputFailure;
778
+ const roleOptions = {
779
+ yuiHome: home,
780
+ env: process.env,
781
+ jsonOutput,
782
+ onInputFailure: failure => { globalInputFailure = failure; }
783
+ };
784
+ const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
785
+ if (resolved[1] === "message" && resolved[2] === "queue" && resolved[3] !== undefined) {
786
+ await callController(home, "scheduler.signal", {
787
+ key: `global-role:${encodeURIComponent(resolved[3])}`
788
+ }).catch(() => { });
789
+ }
790
+ if (typeof result === "string") {
791
+ if (globalInputFailure !== undefined) {
792
+ emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
793
+ return;
794
+ }
795
+ emit(result);
796
+ return;
797
+ }
798
+ if (result.kind === "input-steer") {
799
+ // Core already persisted the durable Global Message and proved target +
800
+ // capability + writer fence from the Global Role's own Session set. This
801
+ // is the single live edge: one native steer of the exact current Turn,
802
+ // scope "global", no retarget and no fallback to interrupt or queue.
803
+ await ensureFileTaskController(home, { environment: process.env });
804
+ let control;
805
+ try {
806
+ control = await sendAgentHostSteerControl({
807
+ home,
808
+ scope: "global",
809
+ roleName: result.roleName,
810
+ control: {
811
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
812
+ type: "steer-turn",
813
+ nativeSessionId: result.target.nativeSessionId,
814
+ nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
815
+ authority: result.target.authority,
816
+ run: { attemptId: result.receiptId, boundedText: result.text }
817
+ }
818
+ });
819
+ }
820
+ catch (error) {
821
+ recordGlobalSteerResult(store, result.roleName, result.messageId, {
822
+ state: "steer-unknown", outcome: "pending",
823
+ detail: error instanceof Error ? error.message : String(error)
824
+ });
825
+ throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
826
+ + `${error instanceof Error ? error.message : String(error)}. `
827
+ + "The Message is retained and its outcome is recorded from the Host; whether the "
828
+ + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
829
+ + "do not reissue the same input under a new requestId or a different action.");
830
+ }
831
+ const steer = foldSteerLiveReceipt(control);
832
+ recordGlobalSteerResult(store, result.roleName, result.messageId, steer);
833
+ if (steer.state !== "steered") {
834
+ 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 });
835
+ return;
836
+ }
837
+ emit(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), false, {
838
+ roleName: result.roleName,
839
+ messageId: result.messageId, steer
840
+ });
841
+ return;
842
+ }
843
+ if (result.kind === "input-interrupt") {
844
+ // The single live edge for a Global interrupt: one native cancel of the
845
+ // exact current Turn, scope "global". Never a kill/restart/detach. Any
846
+ // then-handoff was already claimed durably by Core (an existing Global
847
+ // Message owned by this Role) and is delivered once by the ordinary
848
+ // continuation path after this Turn reaches a proven terminal.
849
+ await ensureFileTaskController(home, { environment: process.env });
850
+ let control;
851
+ try {
852
+ control = await sendAgentHostCancelControl({
853
+ home,
854
+ scope: "global",
855
+ roleName: result.roleName,
856
+ control: {
857
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
858
+ type: "cancel",
859
+ nativeOnly: true,
860
+ nativeSessionId: result.target.nativeSessionId,
861
+ attemptId: result.target.attemptId,
862
+ authority: result.target.authority
863
+ }
864
+ });
865
+ }
866
+ catch (error) {
867
+ recordGlobalInterruptResult(store, result.roleName, result.receiptId, {
868
+ state: "interrupt-unknown", outcome: "cancel-requested",
869
+ detail: error instanceof Error ? error.message : String(error)
870
+ });
871
+ throw runtimeError(`Interrupt of global role ${result.roleName} did not complete: `
872
+ + `${error instanceof Error ? error.message : String(error)}. `
873
+ + "No process was killed; re-read the Session before retrying.");
874
+ }
875
+ const interrupt = foldInterruptLiveReceipt(control);
876
+ recordGlobalInterruptResult(store, result.roleName, result.receiptId, interrupt);
877
+ if (interrupt.state !== "interrupt-requested") {
878
+ emitControlFailure(interruptReceiptOutput(result.output, result.roleName, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN" : "INTERRUPT_NOT_DELIVERED", { roleName: result.roleName, interrupt });
879
+ return;
880
+ }
881
+ emit(interruptReceiptOutput(result.output, result.roleName, interrupt), false, {
882
+ roleName: result.roleName,
883
+ ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
884
+ interrupt
885
+ });
886
+ return;
887
+ }
888
+ if (result.kind !== "enter") {
889
+ throw new Error("Role command returned an invalid control result.");
890
+ }
891
+ // A top-level `role` command never enters a runtime Session; that is
892
+ // `session enter`.
893
+ throw usageError("Use 'yui session enter <role>' to attach to a Global Role.");
894
+ }
755
895
  if (resolved[0] === "operator") {
756
896
  if (resolved[1] === "enter") {
757
897
  if (resolved.length !== 2)
@@ -773,6 +913,72 @@ export async function main() {
773
913
  return;
774
914
  }
775
915
  if (resolved[0] === "task") {
916
+ if (resolved[1] === "artifact") {
917
+ // File/directory artifacts live in the Task's local Git repository, so
918
+ // their save/read/list are asynchronous and handled here rather than in
919
+ // the synchronous runTaskCommand chain. Save commits exactly one path and
920
+ // returns a self-certifying commit-pinned reference; read pins to a commit
921
+ // for frozen evidence; list is an ordinary current read.
922
+ const action = resolved[2];
923
+ const taskId = resolved[3];
924
+ const usage = "Usage: yui task artifact list <task> | read <task> <relative-path> [<commit>] | "
925
+ + "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
926
+ if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
927
+ throw usageError(usage);
928
+ }
929
+ if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
930
+ throw usageError("Artifact is outside the managed Task scope.");
931
+ }
932
+ if (store.getTask(taskId) === null)
933
+ throw usageError(`Task not found: ${taskId}.`);
934
+ let data;
935
+ if (action === "list") {
936
+ if (resolved.length !== 4)
937
+ throw usageError(usage);
938
+ data = await listArtifactsCapability(home, taskId);
939
+ }
940
+ else if (action === "read") {
941
+ const relativePath = resolved[4];
942
+ const commit = resolved[5];
943
+ if (relativePath === undefined || resolved.length > 6)
944
+ throw usageError(usage);
945
+ data = await readArtifactCapability(home, taskId, {
946
+ relativePath, ...(commit === undefined ? {} : { commit })
947
+ });
948
+ }
949
+ else {
950
+ // save: a delivery-authoritative action; a managed Task caller must be the current Leader.
951
+ taskLocalActor(store, process.env, taskId);
952
+ const relativePath = resolved[4];
953
+ const content = resolved[5];
954
+ if (relativePath === undefined || content === undefined)
955
+ throw usageError(usage);
956
+ const rest = resolved.slice(6);
957
+ let message;
958
+ let expectedHead;
959
+ for (let index = 0; index < rest.length; index += 1) {
960
+ const value = rest[index + 1];
961
+ if (rest[index] === "--message" && value !== undefined) {
962
+ message = value;
963
+ index += 1;
964
+ continue;
965
+ }
966
+ if (rest[index] === "--expected-head" && value !== undefined) {
967
+ expectedHead = value;
968
+ index += 1;
969
+ continue;
970
+ }
971
+ throw usageError(usage);
972
+ }
973
+ data = await saveArtifactCapability(home, taskId, {
974
+ relativePath, content,
975
+ ...(message === undefined ? {} : { message }),
976
+ ...(expectedHead === undefined ? {} : { expectedHead })
977
+ });
978
+ }
979
+ emit(JSON.stringify(data, null, 2), false, data);
980
+ return;
981
+ }
776
982
  if (resolved[1] === "execution") {
777
983
  if (resolved[2] === "stop") {
778
984
  const request = parseTaskExecutionStopRequest(resolved.slice(3));
@@ -1000,7 +1206,7 @@ export async function main() {
1000
1206
  let archiveRemoteDeliveryProof;
1001
1207
  let archiveTaskReviewCandidate;
1002
1208
  if (resolved[1] === "archive") {
1003
- const { taskId, disposition, forceUnverified } = validateTaskArchiveRequest(resolved.slice(2), store, {
1209
+ const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
1004
1210
  runtime,
1005
1211
  environment: process.env,
1006
1212
  yuiHome: home
@@ -1008,11 +1214,26 @@ export async function main() {
1008
1214
  const task = store.getTask(taskId);
1009
1215
  if (task === null)
1010
1216
  throw new Error(`Task disappeared after archive validation: ${taskId}.`);
1011
- if (task.status !== "archived") {
1217
+ if (force || task.status === "archived") {
1218
+ // Archive admission and mandatory audit commit before any fallible
1219
+ // filesystem/provider work. Repeats report facts, never replay cleanup.
1220
+ const admitted = runTaskCommand(resolved.slice(1), store, {
1221
+ runtime, environment: process.env, yuiHome: home
1222
+ });
1223
+ if (force && admitted.kind === "output"
1224
+ && admitted.data.changed) {
1225
+ await workspaceCoordinator.cleanupArchivedTask(taskId, disposition);
1226
+ }
1227
+ const current = store.getTask(taskId);
1228
+ const archive = taskArchiveDiagnostics(store, current);
1229
+ emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
1230
+ return;
1231
+ }
1232
+ {
1012
1233
  if (disposition === "integrated") {
1013
1234
  archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
1014
1235
  archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
1015
- assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery, { forceUnverified });
1236
+ assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
1016
1237
  }
1017
1238
  const workItemIds = store.listManagedWorkspaces(task.id)
1018
1239
  .flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
@@ -1066,9 +1287,8 @@ export async function main() {
1066
1287
  // Authority and pure Lane-shape checks precede every physical or
1067
1288
  // durable workspace preparation performed for dispatch.
1068
1289
  assertTaskDeliveryAuthority(store, process.env, task.id);
1069
- if (item.assignee !== undefined) {
1070
- workItemDispatchLanePlan(resolved, store, item);
1071
- }
1290
+ requireWorkItemAssignee(item);
1291
+ workItemDispatchLanePlan(resolved, store, item);
1072
1292
  }
1073
1293
  // A rejected Candidate starts a new execution iteration. Release every
1074
1294
  // terminal Lane Role runtime before preparing the new Lane workspaces;
@@ -1192,17 +1412,17 @@ export async function main() {
1192
1412
  laneDispatchRelease = preparedLanes.release;
1193
1413
  laneDispatchProjectPaths = preparedLanes.projectPaths;
1194
1414
  }
1195
- const candidateGitSnapshot = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1196
- const directTaskMainSnapshot = await directTaskMainSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1415
+ const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
1197
1416
  const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
1198
1417
  ? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
1199
1418
  : archiveTaskReviewCandidate;
1200
1419
  const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
1201
- // Read-only, and only for a Session inspect. The command itself stays
1420
+ // Read-only Host evidence for Session inspect and Role status/list. The command stays
1202
1421
  // synchronous over persisted state; this is the live reading it prints
1203
1422
  // beside those facts, prepared here because the Host is reached over a
1204
1423
  // socket.
1205
- const liveRunConfiguration = await liveRunConfigurationForTaskCommand(resolved, store, home);
1424
+ const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
1425
+ const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
1206
1426
  // Physical preparation may precede the durable write, but Task status,
1207
1427
  // workspace identity/cwd, and ManagedWorkspace ownership are adopted by
1208
1428
  // one transaction. A failed attempt therefore leaves the Task Draft and
@@ -1228,11 +1448,10 @@ export async function main() {
1228
1448
  ? {}
1229
1449
  : { completionPublishedTreeProof }),
1230
1450
  ...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
1231
- ...(candidateGitSnapshot === undefined ? {} : { candidateGitSnapshot }),
1451
+ ...candidateSnapshots,
1232
1452
  ...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
1233
1453
  ...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
1234
1454
  ...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
1235
- ...(directTaskMainSnapshot === undefined ? {} : { directTaskMainSnapshot }),
1236
1455
  ...(actualTaskReviewCandidate === undefined
1237
1456
  ? {}
1238
1457
  : { actualTaskReviewCandidate }),
@@ -1245,6 +1464,7 @@ export async function main() {
1245
1464
  ...(liveRunConfiguration === undefined
1246
1465
  ? {}
1247
1466
  : { liveRunConfiguration }),
1467
+ ...(liveHostObservations === undefined ? {} : { liveHostObservations }),
1248
1468
  ...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
1249
1469
  ...(validateAgentConfiguration === undefined
1250
1470
  ? {}
@@ -1358,12 +1578,19 @@ export async function main() {
1358
1578
  await workspacePreparer.prepareTaskWorkspace(task.id);
1359
1579
  }
1360
1580
  }
1581
+ const controlData = result.data;
1582
+ const failureCode = controlData?.steer?.code ?? controlData?.interrupt?.code;
1583
+ if (failureCode !== undefined) {
1584
+ emitControlFailure(result.output, failureCode, result.data);
1585
+ return;
1586
+ }
1361
1587
  emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
1362
1588
  ? result.data
1363
1589
  : { command: result.data, ...reviewData });
1364
1590
  return;
1365
1591
  }
1366
- if (jsonOutput && result.kind !== "session-stop") {
1592
+ if (jsonOutput && result.kind !== "session-stop"
1593
+ && result.kind !== "input-steer" && result.kind !== "input-interrupt") {
1367
1594
  throw usageError("Task Role view/takeover requires an interactive terminal.");
1368
1595
  }
1369
1596
  if (result.kind === "session-stop") {
@@ -1395,6 +1622,94 @@ export async function main() {
1395
1622
  tmux.attachRole(result.taskId, result.roleName, "read-only");
1396
1623
  return;
1397
1624
  }
1625
+ if (result.kind === "input-steer") {
1626
+ // Core already persisted the Message and proved target + capability +
1627
+ // writer fence. This is the single live edge: one native steer of the
1628
+ // exact current Turn, with no retarget and no fallback to interrupt or
1629
+ // queue. Its durable settlement flows through the steer receipt fold.
1630
+ await ensureFileTaskController(home, { environment: process.env });
1631
+ let control;
1632
+ try {
1633
+ control = await sendAgentHostSteerControl({
1634
+ home,
1635
+ scope: "task",
1636
+ taskId: result.taskId,
1637
+ roleName: result.roleName,
1638
+ control: {
1639
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1640
+ type: "steer-turn",
1641
+ nativeSessionId: result.target.nativeSessionId,
1642
+ nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
1643
+ authority: result.target.authority,
1644
+ run: { attemptId: result.receiptId, boundedText: result.text }
1645
+ }
1646
+ });
1647
+ }
1648
+ catch (error) {
1649
+ throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
1650
+ + `${error instanceof Error ? error.message : String(error)}. `
1651
+ + "The Message is retained and its outcome is recorded from the Host; whether the "
1652
+ + "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
1653
+ + "do not reissue the same input under a new requestId or a different action.");
1654
+ }
1655
+ const steer = foldSteerLiveReceipt(control);
1656
+ if (steer.state !== "steered") {
1657
+ 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 });
1658
+ return;
1659
+ }
1660
+ emit(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), false, {
1661
+ taskId: result.taskId, roleName: result.roleName,
1662
+ messageId: result.messageId, steer
1663
+ });
1664
+ return;
1665
+ }
1666
+ if (result.kind === "input-interrupt") {
1667
+ // The single live edge for interrupt: one native cancel of the exact
1668
+ // current Turn. Never a kill/restart/detach. Any then-handoff was
1669
+ // already claimed durably by Core and is delivered once by the ordinary
1670
+ // continuation path after this Turn reaches a proven terminal.
1671
+ await ensureFileTaskController(home, { environment: process.env });
1672
+ let control;
1673
+ try {
1674
+ control = await sendAgentHostCancelControl({
1675
+ home,
1676
+ scope: "task",
1677
+ taskId: result.taskId,
1678
+ roleName: result.roleName,
1679
+ control: {
1680
+ protocol: AGENT_HOST_CONTROL_PROTOCOL,
1681
+ type: "cancel",
1682
+ nativeOnly: true,
1683
+ nativeSessionId: result.target.nativeSessionId,
1684
+ // Native cancel names the exact original execution attempt it stops
1685
+ // (Host matches request.attemptId === activeRunAttemptId). That is
1686
+ // distinct from receiptId, the durable identity of this interrupt
1687
+ // control operation — never send the operation id as the turn id.
1688
+ attemptId: result.target.attemptId,
1689
+ authority: result.target.authority
1690
+ }
1691
+ });
1692
+ }
1693
+ catch (error) {
1694
+ recordTaskInterruptResult(store, result.taskId, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
1695
+ throw runtimeError(`Interrupt ${result.receiptId} of ${result.taskId}/${result.roleName} did not complete: `
1696
+ + `${error instanceof Error ? error.message : String(error)}. `
1697
+ + "No process was killed; re-read the Session before retrying.");
1698
+ }
1699
+ const interrupt = foldInterruptLiveReceipt(control);
1700
+ recordTaskInterruptResult(store, result.taskId, result.receiptId, interrupt);
1701
+ if (interrupt.state !== "interrupt-requested") {
1702
+ emitControlFailure(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN"
1703
+ : interrupt.state === "interrupt-not-active" ? "NO_ACTIVE_TURN" : "INTERRUPT_REJECTED", { taskId: result.taskId, roleName: result.roleName, receiptId: result.receiptId, interrupt });
1704
+ return;
1705
+ }
1706
+ emit(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), false, {
1707
+ taskId: result.taskId, roleName: result.roleName,
1708
+ ...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
1709
+ interrupt
1710
+ });
1711
+ return;
1712
+ }
1398
1713
  const syncAuthority = async (authorityResult) => {
1399
1714
  let control;
1400
1715
  try {
@@ -1699,34 +2014,11 @@ function assertWorkItemExecutionDependenciesForCommand(args, store, environment)
1699
2014
  assertWorkItemDependenciesCompletedForCommand(store, item);
1700
2015
  }
1701
2016
  async function candidateSnapshotForTaskCommand(args, store, preparer, environment, taskFinalReviewContract) {
1702
- if (args[0] !== "task")
1703
- return undefined;
1704
- const reviewableCandidateCommand = (args[1] === "work" && args[2] === "update"
1705
- && args[3] !== undefined && args[4] === "done") || (args[1] === "work" && args[2] === "group" && args[3] === "resolve"
1706
- && args[4] !== undefined);
1707
- // Explicit Task-final review requests must remain independent of the
1708
- // mutable global review trigger. Candidate snapshots are a delivery
1709
- // boundary for every writable WorkItem, not only review-configured Tasks.
1710
- const groupResolve = args[1] === "work" && args[2] === "group" && args[3] === "resolve";
1711
- if (!reviewableCandidateCommand
1712
- || (groupResolve && args.includes("--decision") && args[args.indexOf("--decision") + 1] !== "accept")) {
1713
- return undefined;
1714
- }
1715
- if (args[1] === "work" && args[2] === "update"
1716
- && args[3] !== undefined && args[4] === "done") {
1717
- const reference = cliWorkItemReference(args[3], environment);
1718
- const workspace = store.getWorkItemWorkspace(reference.taskId, reference.localId);
1719
- if (workspace === null) {
1720
- // The exact Task-final contract intentionally supports a Leader-direct,
1721
- // metadata-only Project Candidate. The command layer performs the full
1722
- // Task/WorkItem/source/contract validation before any aggregate write.
1723
- if (taskFinalReviewContract !== undefined)
1724
- return undefined;
1725
- throw usageError(`Reviewable direct WorkItem has no managed Candidate workspace: ${reference.localId}.`);
1726
- }
1727
- return preparer.snapshotCandidateWorkspace(workspace);
1728
- }
1729
- return undefined;
2017
+ if (args[0] !== "task" || args[1] !== "work" || args[2] !== "update"
2018
+ || args[3] === undefined || args[4] !== "done")
2019
+ return {};
2020
+ const reference = cliWorkItemReference(args[3], environment);
2021
+ return snapshotWorkItemCandidate(store, preparer, reference.taskId, reference.localId, taskFinalReviewContract);
1730
2022
  }
1731
2023
  async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, environment) {
1732
2024
  const isDispatch = args[0] === "task"
@@ -1811,37 +2103,6 @@ async function prepareReviewLaneWorkspaces(taskId, reviewRoundId, store, prepare
1811
2103
  }
1812
2104
  return map;
1813
2105
  }
1814
- async function directTaskMainSnapshotForTaskCommand(args, store, preparer, environment, taskFinalReviewContract) {
1815
- if (taskFinalReviewContract === undefined
1816
- || args[0] !== "task"
1817
- || args[1] !== "work"
1818
- || args[2] !== "update"
1819
- || args[3] === undefined
1820
- || args[4] !== "done") {
1821
- return undefined;
1822
- }
1823
- const reference = cliWorkItemReference(args[3], environment);
1824
- const item = store.getWorkItem(reference.taskId, reference.localId);
1825
- if (item === null || item.writeProjectIds.length === 0
1826
- || store.getWorkItemWorkspace(reference.taskId, reference.localId) !== null) {
1827
- return undefined;
1828
- }
1829
- const workspace = store.getTaskWorkspace(reference.taskId);
1830
- // Exact Task-final Candidates may intentionally be metadata-only when no
1831
- // Task main exists. They remain review anchors, but are not eligible for the
1832
- // direct ChangeSet capture path.
1833
- if (workspace === null)
1834
- return undefined;
1835
- if (workspace.owner.type !== "task") {
1836
- throw usageError(`Task has no authoritative main workspace: ${reference.taskId}.`);
1837
- }
1838
- try {
1839
- return await preparer.snapshotDirectTaskMain(workspace, item.writeProjectIds);
1840
- }
1841
- catch (error) {
1842
- throw usageError(error instanceof Error ? error.message : String(error));
1843
- }
1844
- }
1845
2106
  async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, environment) {
1846
2107
  if (args[0] !== "task")
1847
2108
  return undefined;
@@ -2301,18 +2562,22 @@ async function warmLegacyRoleConfigurationMutation(commandArgs, store, catalogs)
2301
2562
  * no Host is running for most Sessions ever inspected, and the persisted facts
2302
2563
  * the command prints are worth showing regardless of whether a live one answered.
2303
2564
  */
2304
- async function liveRunConfigurationForTaskCommand(args, store, home) {
2305
- if (args[0] !== "task" || args[1] !== "role" || args[2] !== "session"
2306
- || args[3] !== "inspect" || args.length !== 6) {
2565
+ async function liveHostObservationsForTaskCommand(args, store, home) {
2566
+ if (args[0] !== "task" || args[1] !== "role")
2307
2567
  return undefined;
2308
- }
2309
- const taskId = args[4];
2310
- const roleName = args[5];
2311
- if (taskId === undefined || roleName === undefined)
2568
+ const inspect = args[2] === "session" && args[3] === "inspect" && args.length === 6;
2569
+ const status = args[2] === "status" && args.length === 5;
2570
+ const list = args[2] === "list" && args.length === 4;
2571
+ if (!inspect && !status && !list)
2312
2572
  return undefined;
2313
- // A Session must be recorded as active before asking. Probing a socket for a
2314
- // Role that never ran would report a reach failure as if it were a fact about
2315
- // that Role's Agent.
2573
+ const taskId = args[inspect ? 4 : 3];
2574
+ const roles = list ? store.listRoles(taskId).map(role => role.name) : [args[inspect ? 5 : 4]];
2575
+ const entries = await Promise.all(roles.map(async (roleName) => [
2576
+ roleName, await readLiveHostObservation(store, home, taskId, roleName)
2577
+ ]));
2578
+ return Object.fromEntries(entries.filter((entry) => entry[1] !== undefined));
2579
+ }
2580
+ async function readLiveHostObservation(store, home, taskId, roleName) {
2316
2581
  const sessions = store.getTaskRoleSessionSet(taskId, roleName);
2317
2582
  const active = sessions === null
2318
2583
  ? undefined
@@ -2320,8 +2585,7 @@ async function liveRunConfigurationForTaskCommand(args, store, home) {
2320
2585
  if (active === undefined)
2321
2586
  return undefined;
2322
2587
  if (active.status !== "active") {
2323
- return unknownAgentRunConfiguration(`This Session is ${active.status}, so there is no live Agent to report what it `
2324
- + "is running under.");
2588
+ return { detail: `This Session is ${active.status}; no live Host reading was requested.` };
2325
2589
  }
2326
2590
  try {
2327
2591
  const snapshot = await inspectAgentHost({
@@ -2331,18 +2595,20 @@ async function liveRunConfigurationForTaskCommand(args, store, home) {
2331
2595
  roleName
2332
2596
  });
2333
2597
  if (snapshot.nativeSessionId !== active.nativeSessionId
2334
- || snapshot.adapterId !== active.adapterId
2335
- || snapshot.state === "exited" || snapshot.state === "failed") {
2336
- return unknownAgentRunConfiguration("The Agent Host has no live connection matching the recorded Session.");
2598
+ || snapshot.adapterId !== active.adapterId) {
2599
+ return { detail: "The Agent Host does not match the recorded Session." };
2337
2600
  }
2338
- return snapshot.runConfiguration ?? unknownAgentRunConfiguration("The Agent Host is running but has no Provider Session open, so no Agent "
2339
- + "has reported a configuration yet.");
2601
+ return { snapshot };
2340
2602
  }
2341
2603
  catch (error) {
2342
- return unknownAgentRunConfiguration("The Agent Host for this Session could not be reached, so its live "
2343
- + `configuration is unavailable: ${error instanceof Error ? error.message : String(error)}`);
2604
+ return { detail: `The Agent Host could not be reached: ${error instanceof Error ? error.message : String(error)}` };
2344
2605
  }
2345
2606
  }
2607
+ function runConfigurationForHostObservation(host) {
2608
+ if (host === undefined)
2609
+ return undefined;
2610
+ return host.snapshot?.runConfiguration ?? unknownAgentRunConfiguration(host.detail ?? `Host=${host.snapshot?.state ?? "unknown"}; no live Agent configuration was reported.`);
2611
+ }
2346
2612
  function hasModelOrEffortMutation(args) {
2347
2613
  const operation = (args[0] === "config" && args[1] === "role")
2348
2614
  || (args[0] === "task" && args[1] === "role");
@@ -2480,6 +2746,50 @@ function emit(output, literal = false, data) {
2480
2746
  : { ok: true, data })
2481
2747
  : normalized}\n`);
2482
2748
  }
2749
+ function emitControlFailure(message, code, details) {
2750
+ process.exitCode = 2;
2751
+ process.stdout.write(`${jsonOutput
2752
+ ? JSON.stringify({ ok: false, code, message: message.trim(), details })
2753
+ : message.trim()}\n`);
2754
+ }
2755
+ /**
2756
+ * The human line for a live steer, corrected to the *actual* live acceptance
2757
+ * (decision-3 §7). The store command's `base` line is written optimistically
2758
+ * ("Steering …"); only a proven `steered` keeps it. `steer-unknown` (pending) is
2759
+ * delivery-unknown, and a rejected/unavailable steer did not deliver — in both
2760
+ * cases the Message is retained and the operator must not reissue. There is no
2761
+ * retarget, queue, or interrupt fallback here; this only reports.
2762
+ */
2763
+ function steerReceiptOutput(base, target, messageId, receipt) {
2764
+ if (receipt.state === "steered")
2765
+ return base;
2766
+ const detail = receipt.detail === undefined ? "" : ` ${receipt.detail}`;
2767
+ const head = receipt.state === "steer-unknown"
2768
+ ? `Steer message ${messageId} to ${target} is delivery-unknown (${receipt.outcome}): the Host `
2769
+ + "holds it but the Provider has not yet proven acceptance."
2770
+ : `Steer message ${messageId} to ${target} did not deliver (${receipt.outcome}).`;
2771
+ return `${head}${detail} The Message is retained; re-read the Session before acting, and do not `
2772
+ + "reissue the same input under a new requestId or a different action.\n";
2773
+ }
2774
+ /**
2775
+ * The human line for a live interrupt, corrected to `control.cancellation`
2776
+ * (decision-3 §7). Only a proven stop-request keeps the optimistic `base` line.
2777
+ * `not-active`/`unknown`/unavailable each report that nothing was proven stopped;
2778
+ * no process is ever killed. A then-handoff, if any, was already claimed durably
2779
+ * and is delivered once by the ordinary continuation path after a proven terminal.
2780
+ */
2781
+ function interruptReceiptOutput(base, target, receipt) {
2782
+ if (receipt.state === "interrupt-requested")
2783
+ return `${base.trim()}\nNative cancel requested; Turn termination is not yet proven.\n`;
2784
+ const detail = receipt.detail === undefined ? "" : ` ${receipt.detail}`;
2785
+ const reason = receipt.state === "interrupt-not-active"
2786
+ ? "found no active Turn to stop (not-active)"
2787
+ : receipt.state === "interrupt-unknown"
2788
+ ? "could not prove a stop (unknown)"
2789
+ : `did not complete (${receipt.outcome})`;
2790
+ return `Interrupt of ${target} ${reason}.${detail} No process was killed; re-read the Session `
2791
+ + "before retrying.\n";
2792
+ }
2483
2793
  function withControllerRefreshWarning(output, refresh, label) {
2484
2794
  if (refresh.status !== "failed")
2485
2795
  return output;