@zq-silk/yui 0.15.9 → 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.
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/cli/commandCatalog.js +26 -6
- package/dist/cli/interactionPolicy.js +3 -3
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +336 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +511 -83
- package/dist/commands/taskIntegrationCommands.js +16 -38
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/taskContext.js +38 -2
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +3 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
- package/dist/controller/globalInputDelivery.js +119 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +81 -6
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +312 -382
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +367 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +72 -15
- package/dist/repository/taskWorkspaceCoordinator.js +134 -0
- package/dist/repository/taskWorkspacePreparer.js +120 -49
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentHost.js +152 -72
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +12 -1
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +26 -8
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +136 -4
- package/dist/storage/sqliteStore.js +40 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/web/assets/client/app.js +55 -0
- package/dist/web/assets/client/components.js +1 -0
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/assets/client/taskSurface.js +106 -0
- package/dist/web/assets/client/view.js +7 -4
- package/dist/web/assets/shell.js +23 -0
- package/dist/web/assets/styles/layout.js +1 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +117 -1
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +222 -5
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/release-workflow.md +39 -0
- package/docs/release-workflow.zh-CN.md +29 -0
- package/docs/sqlite-control-plane-design.md +223 -1
- package/docs/testing/verification-levels.md +24 -0
- package/docs/testing/verification-levels.zh-CN.md +11 -0
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +151 -49
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-operator/SKILL.md +6 -1
- package/skills/yui-runtime/SKILL.md +27 -0
- 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,8 +42,9 @@ 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";
|
|
@@ -75,10 +78,11 @@ import { runRuntimeObservationHookCommand } from "./controller/runtimeObservatio
|
|
|
75
78
|
import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
|
|
76
79
|
import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
|
|
77
80
|
import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
|
|
78
|
-
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";
|
|
79
82
|
import { unknownAgentRunConfiguration } from "./runtime/agentRunConfiguration.js";
|
|
80
83
|
import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
|
|
81
84
|
import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
|
|
85
|
+
import { snapshotWorkItemCandidate } from "./repository/workItemCandidateSnapshot.js";
|
|
82
86
|
import { inspectStorageSchema } from "./storage/storageSchema.js";
|
|
83
87
|
import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
|
|
84
88
|
import { resolveYuiHome } from "./storage/taskStore.js";
|
|
@@ -748,11 +752,146 @@ export async function main() {
|
|
|
748
752
|
emit(result);
|
|
749
753
|
return;
|
|
750
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
|
+
}
|
|
751
761
|
await ensureFileTaskController(home, { environment: process.env });
|
|
752
762
|
await runtime.prepareGlobalRoleEnter(result.role.name);
|
|
753
763
|
tmux.attachRole("operator", result.role.name, "auto");
|
|
754
764
|
return;
|
|
755
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
|
+
}
|
|
756
895
|
if (resolved[0] === "operator") {
|
|
757
896
|
if (resolved[1] === "enter") {
|
|
758
897
|
if (resolved.length !== 2)
|
|
@@ -1067,7 +1206,7 @@ export async function main() {
|
|
|
1067
1206
|
let archiveRemoteDeliveryProof;
|
|
1068
1207
|
let archiveTaskReviewCandidate;
|
|
1069
1208
|
if (resolved[1] === "archive") {
|
|
1070
|
-
const { taskId, disposition,
|
|
1209
|
+
const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
|
|
1071
1210
|
runtime,
|
|
1072
1211
|
environment: process.env,
|
|
1073
1212
|
yuiHome: home
|
|
@@ -1075,11 +1214,26 @@ export async function main() {
|
|
|
1075
1214
|
const task = store.getTask(taskId);
|
|
1076
1215
|
if (task === null)
|
|
1077
1216
|
throw new Error(`Task disappeared after archive validation: ${taskId}.`);
|
|
1078
|
-
if (task.status
|
|
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
|
+
{
|
|
1079
1233
|
if (disposition === "integrated") {
|
|
1080
1234
|
archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
|
|
1081
1235
|
archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
|
|
1082
|
-
assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery
|
|
1236
|
+
assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
|
|
1083
1237
|
}
|
|
1084
1238
|
const workItemIds = store.listManagedWorkspaces(task.id)
|
|
1085
1239
|
.flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
|
|
@@ -1133,9 +1287,8 @@ export async function main() {
|
|
|
1133
1287
|
// Authority and pure Lane-shape checks precede every physical or
|
|
1134
1288
|
// durable workspace preparation performed for dispatch.
|
|
1135
1289
|
assertTaskDeliveryAuthority(store, process.env, task.id);
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
}
|
|
1290
|
+
requireWorkItemAssignee(item);
|
|
1291
|
+
workItemDispatchLanePlan(resolved, store, item);
|
|
1139
1292
|
}
|
|
1140
1293
|
// A rejected Candidate starts a new execution iteration. Release every
|
|
1141
1294
|
// terminal Lane Role runtime before preparing the new Lane workspaces;
|
|
@@ -1259,17 +1412,17 @@ export async function main() {
|
|
|
1259
1412
|
laneDispatchRelease = preparedLanes.release;
|
|
1260
1413
|
laneDispatchProjectPaths = preparedLanes.projectPaths;
|
|
1261
1414
|
}
|
|
1262
|
-
const
|
|
1263
|
-
const directTaskMainSnapshot = await directTaskMainSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
|
|
1415
|
+
const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
|
|
1264
1416
|
const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
|
|
1265
1417
|
? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
|
|
1266
1418
|
: archiveTaskReviewCandidate;
|
|
1267
1419
|
const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
|
|
1268
|
-
// Read-only
|
|
1420
|
+
// Read-only Host evidence for Session inspect and Role status/list. The command stays
|
|
1269
1421
|
// synchronous over persisted state; this is the live reading it prints
|
|
1270
1422
|
// beside those facts, prepared here because the Host is reached over a
|
|
1271
1423
|
// socket.
|
|
1272
|
-
const
|
|
1424
|
+
const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
|
|
1425
|
+
const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
|
|
1273
1426
|
// Physical preparation may precede the durable write, but Task status,
|
|
1274
1427
|
// workspace identity/cwd, and ManagedWorkspace ownership are adopted by
|
|
1275
1428
|
// one transaction. A failed attempt therefore leaves the Task Draft and
|
|
@@ -1295,11 +1448,10 @@ export async function main() {
|
|
|
1295
1448
|
? {}
|
|
1296
1449
|
: { completionPublishedTreeProof }),
|
|
1297
1450
|
...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
|
|
1298
|
-
...
|
|
1451
|
+
...candidateSnapshots,
|
|
1299
1452
|
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1300
1453
|
...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
|
|
1301
1454
|
...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
|
|
1302
|
-
...(directTaskMainSnapshot === undefined ? {} : { directTaskMainSnapshot }),
|
|
1303
1455
|
...(actualTaskReviewCandidate === undefined
|
|
1304
1456
|
? {}
|
|
1305
1457
|
: { actualTaskReviewCandidate }),
|
|
@@ -1312,6 +1464,7 @@ export async function main() {
|
|
|
1312
1464
|
...(liveRunConfiguration === undefined
|
|
1313
1465
|
? {}
|
|
1314
1466
|
: { liveRunConfiguration }),
|
|
1467
|
+
...(liveHostObservations === undefined ? {} : { liveHostObservations }),
|
|
1315
1468
|
...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
|
|
1316
1469
|
...(validateAgentConfiguration === undefined
|
|
1317
1470
|
? {}
|
|
@@ -1425,12 +1578,19 @@ export async function main() {
|
|
|
1425
1578
|
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1426
1579
|
}
|
|
1427
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
|
+
}
|
|
1428
1587
|
emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
|
|
1429
1588
|
? result.data
|
|
1430
1589
|
: { command: result.data, ...reviewData });
|
|
1431
1590
|
return;
|
|
1432
1591
|
}
|
|
1433
|
-
if (jsonOutput && result.kind !== "session-stop"
|
|
1592
|
+
if (jsonOutput && result.kind !== "session-stop"
|
|
1593
|
+
&& result.kind !== "input-steer" && result.kind !== "input-interrupt") {
|
|
1434
1594
|
throw usageError("Task Role view/takeover requires an interactive terminal.");
|
|
1435
1595
|
}
|
|
1436
1596
|
if (result.kind === "session-stop") {
|
|
@@ -1462,6 +1622,94 @@ export async function main() {
|
|
|
1462
1622
|
tmux.attachRole(result.taskId, result.roleName, "read-only");
|
|
1463
1623
|
return;
|
|
1464
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
|
+
}
|
|
1465
1713
|
const syncAuthority = async (authorityResult) => {
|
|
1466
1714
|
let control;
|
|
1467
1715
|
try {
|
|
@@ -1766,34 +2014,11 @@ function assertWorkItemExecutionDependenciesForCommand(args, store, environment)
|
|
|
1766
2014
|
assertWorkItemDependenciesCompletedForCommand(store, item);
|
|
1767
2015
|
}
|
|
1768
2016
|
async function candidateSnapshotForTaskCommand(args, store, preparer, environment, taskFinalReviewContract) {
|
|
1769
|
-
if (args[0] !== "task"
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
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;
|
|
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);
|
|
1797
2022
|
}
|
|
1798
2023
|
async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, environment) {
|
|
1799
2024
|
const isDispatch = args[0] === "task"
|
|
@@ -1878,37 +2103,6 @@ async function prepareReviewLaneWorkspaces(taskId, reviewRoundId, store, prepare
|
|
|
1878
2103
|
}
|
|
1879
2104
|
return map;
|
|
1880
2105
|
}
|
|
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
2106
|
async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, environment) {
|
|
1913
2107
|
if (args[0] !== "task")
|
|
1914
2108
|
return undefined;
|
|
@@ -2368,18 +2562,22 @@ async function warmLegacyRoleConfigurationMutation(commandArgs, store, catalogs)
|
|
|
2368
2562
|
* no Host is running for most Sessions ever inspected, and the persisted facts
|
|
2369
2563
|
* the command prints are worth showing regardless of whether a live one answered.
|
|
2370
2564
|
*/
|
|
2371
|
-
async function
|
|
2372
|
-
if (args[0] !== "task" || args[1] !== "role"
|
|
2373
|
-
|| args[3] !== "inspect" || args.length !== 6) {
|
|
2565
|
+
async function liveHostObservationsForTaskCommand(args, store, home) {
|
|
2566
|
+
if (args[0] !== "task" || args[1] !== "role")
|
|
2374
2567
|
return undefined;
|
|
2375
|
-
|
|
2376
|
-
const
|
|
2377
|
-
const
|
|
2378
|
-
if (
|
|
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)
|
|
2379
2572
|
return undefined;
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
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) {
|
|
2383
2581
|
const sessions = store.getTaskRoleSessionSet(taskId, roleName);
|
|
2384
2582
|
const active = sessions === null
|
|
2385
2583
|
? undefined
|
|
@@ -2387,8 +2585,7 @@ async function liveRunConfigurationForTaskCommand(args, store, home) {
|
|
|
2387
2585
|
if (active === undefined)
|
|
2388
2586
|
return undefined;
|
|
2389
2587
|
if (active.status !== "active") {
|
|
2390
|
-
return
|
|
2391
|
-
+ "is running under.");
|
|
2588
|
+
return { detail: `This Session is ${active.status}; no live Host reading was requested.` };
|
|
2392
2589
|
}
|
|
2393
2590
|
try {
|
|
2394
2591
|
const snapshot = await inspectAgentHost({
|
|
@@ -2398,18 +2595,20 @@ async function liveRunConfigurationForTaskCommand(args, store, home) {
|
|
|
2398
2595
|
roleName
|
|
2399
2596
|
});
|
|
2400
2597
|
if (snapshot.nativeSessionId !== active.nativeSessionId
|
|
2401
|
-
|| snapshot.adapterId !== active.adapterId
|
|
2402
|
-
|
|
2403
|
-
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." };
|
|
2404
2600
|
}
|
|
2405
|
-
return snapshot
|
|
2406
|
-
+ "has reported a configuration yet.");
|
|
2601
|
+
return { snapshot };
|
|
2407
2602
|
}
|
|
2408
2603
|
catch (error) {
|
|
2409
|
-
return
|
|
2410
|
-
+ `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)}` };
|
|
2411
2605
|
}
|
|
2412
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
|
+
}
|
|
2413
2612
|
function hasModelOrEffortMutation(args) {
|
|
2414
2613
|
const operation = (args[0] === "config" && args[1] === "role")
|
|
2415
2614
|
|| (args[0] === "task" && args[1] === "role");
|
|
@@ -2547,6 +2746,50 @@ function emit(output, literal = false, data) {
|
|
|
2547
2746
|
: { ok: true, data })
|
|
2548
2747
|
: normalized}\n`);
|
|
2549
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
|
+
}
|
|
2550
2793
|
function withControllerRefreshWarning(output, refresh, label) {
|
|
2551
2794
|
if (refresh.status !== "failed")
|
|
2552
2795
|
return output;
|