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