@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.
- package/ARCHITECTURE.md +2 -0
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +211 -14
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/cli/commandCatalog.js +40 -16
- 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 +403 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/operatorCommands.js +33 -2
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActivationCommands.js +22 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +846 -155
- 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/runContextPack.js +28 -16
- package/dist/context/taskContext.js +64 -5
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +11 -2
- 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/kernel/builtinCapabilities.js +32 -24
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +423 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/plugins/pluginService.js +11 -3
- 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/projectResource.js +0 -48
- package/dist/resources/projectResourceService.js +3 -81
- 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 +29 -16
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +173 -7
- package/dist/storage/sqliteStore.js +41 -22
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -1
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/taskActivation.js +26 -0
- package/dist/task/taskActivationService.js +85 -69
- package/dist/task/taskSubmission.js +236 -0
- package/dist/web/assets/client/app.js +58 -2
- 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 +202 -7
- 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 +135 -4
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +225 -8
- package/dist/workItem/workItem.js +14 -10
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/agent-result-consumption.md +2 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +2 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +44 -32
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -79
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +2 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +2 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +320 -217
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +2 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +41 -0
- package/docs/release-workflow.zh-CN.md +266 -0
- package/docs/roles-and-configuration.md +2 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +225 -1
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -57
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +2 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +2 -0
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +26 -0
- package/docs/testing/verification-levels.zh-CN.md +80 -0
- package/i18n/README.zh-CN.md +199 -10
- package/package.json +2 -1
- package/skills/yui-leader/SKILL.md +88 -331
- package/skills/yui-leader/references/execution.md +405 -0
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/task-plugins.md +8 -4
- package/skills/yui-operator/SKILL.md +22 -4
- 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,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,
|
|
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
|
|
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
|
|
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
|
-
|
|
1070
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
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
|
|
2305
|
-
if (args[0] !== "task" || args[1] !== "role"
|
|
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
|
|
2310
|
-
const
|
|
2311
|
-
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)
|
|
2312
2572
|
return undefined;
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2339
|
-
+ "has reported a configuration yet.");
|
|
2601
|
+
return { snapshot };
|
|
2340
2602
|
}
|
|
2341
2603
|
catch (error) {
|
|
2342
|
-
return
|
|
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;
|