@zq-silk/yui 0.15.12 → 0.16.0
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 +3 -2
- package/ARCHITECTURE.zh-CN.md +3 -2
- package/dist/artifacts/artifactCommitLock.js +16 -55
- package/dist/artifacts/taskArtifactRepository.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +57 -41
- package/dist/cli/completion.js +18 -18
- package/dist/cli/completionWizard.js +1 -1
- package/dist/cli/dynamicCompletion.js +1 -1
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1280 -1243
- package/dist/commands/agentCommands.js +4 -4
- package/dist/commands/capabilityCommands.js +1 -1
- package/dist/commands/configCommands.js +6 -57
- package/dist/commands/configOverview.js +2 -2
- package/dist/commands/durableJobCommands.js +12 -6
- package/dist/commands/globalRoleCommands.js +53 -29
- package/dist/commands/grantCommands.js +0 -3
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/operatorCommands.js +3 -3
- package/dist/commands/projectCommands.js +1 -10
- package/dist/commands/resourcesCommands.js +10 -40
- package/dist/commands/roleConfiguration.js +2 -6
- package/dist/commands/taskActivationCommands.js +4 -8
- package/dist/commands/taskAgentCapabilities.js +9 -0
- package/dist/commands/taskCommandSupport.js +155 -0
- package/dist/commands/taskCommandTypes.js +1 -0
- package/dist/commands/taskCommands.js +271 -710
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskExecutionCommands.js +1 -1
- package/dist/commands/taskFactCommands.js +325 -0
- package/dist/commands/taskInputCommands.js +12 -42
- package/dist/commands/taskIntegrationCommands.js +17 -57
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +3 -3
- package/dist/commands/taskPublicationCommands.js +1 -1
- package/dist/commands/taskPublicationVerifyCommand.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -94
- package/dist/commands/taskUpstreamCommands.js +19 -8
- package/dist/commands/workflowCommands.js +6 -1
- package/dist/completion/completionInstaller.js +26 -26
- package/dist/completion/completionState.js +26 -26
- package/dist/completion/fileCompletionManager.js +6 -11
- package/dist/config/configCatalog.js +0 -2
- package/dist/config/timeZone.js +14 -0
- package/dist/config/yuiConfig.js +0 -29
- package/dist/context/runContextPack.js +2 -3
- package/dist/context/taskCatalog.js +3 -5
- package/dist/context/taskContext.js +139 -151
- package/dist/context/wakeRunReferences.js +11 -0
- package/dist/controller/agentCapabilities.js +58 -0
- package/dist/controller/agentRuntimeObserver.js +6 -11
- package/dist/controller/clientRuntime.js +22 -14
- package/dist/controller/controller.js +9 -22
- package/dist/controller/fileSchedulerStoreAdapter.js +117 -212
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryDelivery.js +7 -7
- package/dist/controller/runtime.js +51 -44
- package/dist/controller/runtimeEventProcessor.js +0 -5
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/controller/taskAgentError.js +35 -0
- package/dist/coordination/workMailboxQueue.js +2 -24
- package/dist/core/controllerClient.js +2 -2
- package/dist/core/fileLockOwner.js +74 -0
- package/dist/decision/decision.js +17 -0
- package/dist/doctor/doctor.js +18 -9
- package/dist/event/taskEvent.js +12 -0
- package/dist/execution/workItemExecutionProjection.js +2 -2
- package/dist/executor/agentCapabilityConfig.js +43 -0
- package/dist/executor/agentConfigurationCatalog.js +49 -11
- package/dist/executor/agentExecutor.js +5 -8
- package/dist/executor/fileRoleLaunchPlanner.js +11 -53
- package/dist/executor/taskAgentCapabilities.js +64 -0
- package/dist/integration/deliveryObligation.js +1 -69
- package/dist/integration/gitIntegrationService.js +60 -84
- package/dist/integration/integrationAttempt.js +3 -14
- package/dist/integration/integrationSourceApplication.js +4 -12
- package/dist/integration/manifestTags.js +2 -2
- package/dist/job/durableJob.js +4 -25
- package/dist/job/jobAssignmentScope.js +22 -0
- package/dist/job/jobOperation.js +16 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/stepDirectory.js +15 -0
- package/dist/kernel/builtinCapabilities.js +15 -11
- package/dist/kernel/kernelPorts.js +1 -17
- package/dist/lifecycle/exactRunTerminalization.js +1 -9
- package/dist/message/inputControlResolution.js +3 -4
- package/dist/message/message.js +17 -20
- package/dist/message/messageContinuation.js +1 -1
- package/dist/milestone/milestone.js +11 -0
- package/dist/observability/orchestrationMetrics.js +24 -1
- package/dist/observability/runtimeIdentity.js +1 -17
- package/dist/output/agentConfigurationPresentation.js +2 -0
- package/dist/output/timePresentation.js +1 -14
- package/dist/plugins/pluginService.js +12 -4
- package/dist/release/releaseWorkflowPorts.js +45 -96
- package/dist/release/runtimeRelease.js +9 -15
- package/dist/repository/projectMaintenanceLock.js +23 -64
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +13 -22
- package/dist/repository/taskWorkspacePreparer.js +29 -25
- package/dist/repository/workspaceCleanupInspection.js +5 -5
- package/dist/resources/autoResourceGc.js +4 -77
- package/dist/resources/liveReferences.js +7 -54
- package/dist/resources/resourceDiscovery.js +5 -118
- package/dist/resources/resourceGc.js +233 -181
- package/dist/resources/resourceRegistrar.js +5 -4
- package/dist/resources/resourceRegistry.js +3 -37
- package/dist/resources/resourceRegistryStore.js +13 -36
- package/dist/resources/sqliteResourceRegistry.js +43 -26
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewAcceptance.js +1 -1
- package/dist/review/reviewDecision.js +1 -1
- package/dist/review/reviewRound.js +8 -7
- package/dist/review/taskFinalReviewContractResolution.js +1 -1
- package/dist/runtime/acpProtocol.js +4 -51
- package/dist/runtime/acpSession.js +10 -75
- package/dist/runtime/acpSessionConfiguration.js +1 -8
- package/dist/runtime/agentEndpoint.js +3 -2
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +15 -15
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +4 -41
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +7 -7
- package/dist/scheduler/leaderWakeupProcessor.js +19 -6
- package/dist/scheduler/operatorEvent.js +12 -20
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +5 -8
- package/dist/scheduler/roleRunLiveness.js +4 -4
- package/dist/scheduler/roleRunStall.js +16 -23
- package/dist/scheduler/taskExecutionProjection.js +25 -33
- package/dist/scheduler/taskObservabilityProjection.js +0 -1
- package/dist/scheduler/taskWake.js +5 -10
- package/dist/scheduler/wakeReason.js +2 -0
- package/dist/scheduler/wakeupQueue.js +2 -4
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/contextRecords.js +106 -0
- package/dist/storage/currentTaskStore.js +1 -1
- package/dist/storage/homeLayout.js +13 -17
- package/dist/storage/migrations/agentFailureContext.js +22 -0
- package/dist/storage/migrations/currentInputContract.js +86 -0
- package/dist/storage/migrations/currentRuntimeContract.js +228 -0
- package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
- package/dist/storage/migrations/integrationContinuation.js +5 -4
- package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
- package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
- package/dist/storage/migrations/verificationPlanV1.js +162 -0
- package/dist/storage/migrations/verificationPolicy.js +74 -0
- package/dist/storage/migrations/workItemHistory.js +46 -0
- package/dist/storage/persistenceWorker.js +12 -4
- package/dist/storage/recordValidation.js +87 -0
- package/dist/storage/sqliteSchema.js +142 -1
- package/dist/storage/sqliteStore.js +156 -162
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +15 -7
- package/dist/storage/taskStore.js +3 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/remoteDeliveryService.js +89 -0
- package/dist/task/taskActivation.js +2 -25
- package/dist/task/taskActivationService.js +0 -2
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/task/taskSubmission.js +3 -14
- package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
- package/dist/telemetry/sqliteTelemetryStore.js +49 -49
- package/dist/telemetry/telemetryWiring.js +4 -3
- package/dist/tmux/tmuxManager.js +29 -20
- package/dist/verification/gateArtifact.js +15 -24
- package/dist/verification/gateArtifactStore.js +14 -7
- package/dist/verification/verificationGateService.js +29 -88
- package/dist/verification/verificationPlan.js +27 -72
- package/dist/web/assets/client/app.js +1 -1
- package/dist/web/assets/client/taskSurface.js +4 -4
- package/dist/web/webServer.js +4 -6
- package/dist/web/webSnapshot.js +3 -83
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/docs/architecture/capabilities-and-resources.md +14 -2
- package/docs/architecture/capabilities-and-resources.zh-CN.md +11 -1
- package/docs/managed-turn-and-session-runtime.md +83 -10
- package/docs/managed-turn-and-session-runtime.zh-CN.md +65 -11
- package/docs/plugin-sdk.md +4 -2
- package/docs/provider-runtime.md +3 -1
- package/docs/provider-runtime.zh-CN.md +4 -2
- package/docs/release-workflow.md +190 -7
- package/docs/release-workflow.zh-CN.md +139 -5
- package/docs/roles-and-configuration.md +29 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/sqlite-control-plane-design.md +16 -3
- package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
- package/docs/task-dag-semantics.md +3 -3
- package/docs/task-dag-semantics.zh-CN.md +2 -2
- package/docs/task-delivery.md +91 -5
- package/docs/task-delivery.zh-CN.md +72 -4
- package/docs/task-discovery.md +10 -11
- package/docs/task-discovery.zh-CN.md +8 -9
- package/docs/testing/verification-levels.md +145 -9
- package/docs/testing/verification-levels.zh-CN.md +103 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +15 -10
- package/skills/yui-runtime/SKILL.md +6 -6
- package/skills/yui-runtime/references/recovery.md +47 -0
- package/dist/agent/agentRegistry.js +0 -10
- package/dist/agentRun/runIdentity.js +0 -22
- package/dist/commands/deliveryGuardPreflight.js +0 -30
- package/dist/commands/taskIntegrationQueueCommands.js +0 -226
- package/dist/commands/taskOverviewCommand.js +0 -377
- package/dist/completion/completionWizard.js +0 -125
- package/dist/context/dispatchContext.js +0 -110
- package/dist/context/wakeNotification.js +0 -144
- package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
- package/dist/integration/integrationQueueEntry.js +0 -191
- package/dist/integration/integrationQueueService.js +0 -810
- package/dist/release/fakeReleasePorts.js +0 -55
- package/dist/runtime/sessionOwnerRegistry.js +0 -137
- package/dist/task/deliveryGuard.js +0 -226
- /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,117 +1,121 @@
|
|
|
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";
|
|
5
|
-
import { agentAdapterLabel as adapterLabel } from "./agent/adapterCatalog.js";
|
|
6
4
|
import { readFileSync } from "node:fs";
|
|
7
5
|
import { resolve } from "node:path";
|
|
8
6
|
import { createInterface } from "node:readline/promises";
|
|
9
7
|
import { fileURLToPath } from "node:url";
|
|
10
8
|
import { isDeepStrictEqual } from "node:util";
|
|
11
|
-
import {
|
|
9
|
+
import { agentAdapterLabel as adapterLabel } from "./agent/adapterCatalog.js";
|
|
10
|
+
import { recordTaskInterruptResult } from "./message/taskInterrupt.js";
|
|
11
|
+
import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
|
|
12
|
+
import { listArtifactsCapability, readArtifactCapability, saveArtifactCapability } from "./artifacts/artifactCapability.js";
|
|
13
|
+
import { renderAgentConfigurationResolutionNotice } from "./cli/agentConfigurationPicker.js";
|
|
12
14
|
import { describeCommandTree, findCommandNode } from "./cli/commandCatalog.js";
|
|
13
|
-
import { routeInvocation } from "./cli/invocationRouter.js";
|
|
14
15
|
import { renderCompletion } from "./cli/completion.js";
|
|
16
|
+
import { runCompletionWizard } from "./cli/completionWizard.js";
|
|
15
17
|
import { resolveCompletionCandidates } from "./cli/dynamicCompletion.js";
|
|
16
|
-
import {
|
|
18
|
+
import { renderCommandHelp } from "./cli/helpRenderer.js";
|
|
17
19
|
import { allowsInteractiveSelection, resolveInteractiveArguments } from "./cli/interactiveSelection.js";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
20
|
+
import { routeInvocation } from "./cli/invocationRouter.js";
|
|
21
|
+
import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
|
|
22
|
+
import { assertConfigurationAuthority, assertTaskInvocationScope } from "./cli/invocationAuthority.js";
|
|
23
|
+
import { requireManagedGlobalCaller } from "./runtime/managedCaller.js";
|
|
21
24
|
import { resolveOperatorWizardArguments } from "./cli/operatorWizard.js";
|
|
25
|
+
import { resolveGlobalRoleAgentConfigurationArguments, resolveRoleWizardArguments } from "./cli/roleWizard.js";
|
|
22
26
|
import { runUpdateCommand } from "./cli/updateCommand.js";
|
|
27
|
+
import { createUpdatePorts } from "./cli/updatePorts.js";
|
|
23
28
|
import { runUpgradeCommand } from "./cli/upgradeCommand.js";
|
|
24
|
-
import { formatTimestamp } from "./output/timePresentation.js";
|
|
25
|
-
import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
|
|
26
|
-
import { renderAgentConfigurationCatalog } from "./output/agentConfigurationPresentation.js";
|
|
27
|
-
import { nativeAgentEnvironmentNames } from "./agent/launchEnvironment.js";
|
|
28
29
|
import { runAgentCommand } from "./commands/agentCommands.js";
|
|
29
|
-
import { runGlobalRoleCommand } from "./commands/globalRoleCommands.js";
|
|
30
|
-
import { runConfigCommand } from "./commands/configCommands.js";
|
|
31
30
|
import { runCapabilityCommand } from "./commands/capabilityCommands.js";
|
|
32
|
-
import {
|
|
31
|
+
import { runConfigCommand } from "./commands/configCommands.js";
|
|
33
32
|
import { runConfigOverview } from "./commands/configOverview.js";
|
|
34
|
-
import { parseControllerCleanupOptions,
|
|
33
|
+
import { parseControllerCleanupOptions, parseControllerRuntimeSnapshot, parseControllerStatusOptions, renderControllerResourceStatus, renderRuntimeIdentitySection, runInteractiveControllerCleanup, summarizeDurablePhysicalMismatch } from "./commands/controllerCommands.js";
|
|
34
|
+
import { runDurableJobCommand } from "./commands/durableJobCommands.js";
|
|
35
35
|
import { parseExecutionAuditOptions, runExecutionAuditCommand } from "./commands/executionAuditCommands.js";
|
|
36
|
-
import {
|
|
37
|
-
import { SessionOwnerReconciliation } from "./controller/sessionOwnerReconciliation.js";
|
|
36
|
+
import { runGlobalRoleCommand } from "./commands/globalRoleCommands.js";
|
|
38
37
|
import { runJobCommand } from "./commands/jobCommands.js";
|
|
39
|
-
import { runDurableJobCommand } from "./commands/durableJobCommands.js";
|
|
40
|
-
import { runTelemetryCommand } from "./commands/telemetryCommands.js";
|
|
41
|
-
import { runResourcesCommand } from "./commands/resourcesCommands.js";
|
|
42
38
|
import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
|
|
43
|
-
import { runProjectCommand } from "./commands/projectCommands.js";
|
|
44
39
|
import { previewProfileAgentConfigurationMutation, runProfileCommand } from "./commands/profileCommands.js";
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import { runTaskPublicationAdoptCommand } from "./commands/taskPublicationAdoptCommand.js";
|
|
52
|
-
import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
|
|
53
|
-
import { createGitLabCliPublicationVerifier } from "./external/gitlabPublicationVerifier.js";
|
|
54
|
-
import { taskLocalActor, assertTaskDeliveryAuthority } from "./commands/taskActor.js";
|
|
55
|
-
import { saveArtifactCapability, readArtifactCapability, listArtifactsCapability } from "./artifacts/artifactCapability.js";
|
|
56
|
-
import { parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, finalizeStoppedTaskExecution, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
|
|
57
|
-
import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
|
|
40
|
+
import { runProjectCommand } from "./commands/projectCommands.js";
|
|
41
|
+
import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
|
|
42
|
+
import { runResourcesCommand } from "./commands/resourcesCommands.js";
|
|
43
|
+
import { parseSessionReconcileOptions, parseSessionStopOptions, runSessionReconcileCommand, runSessionStopCommand } from "./commands/sessionCommands.js";
|
|
44
|
+
import { assertTaskDeliveryAuthority, taskLocalActor } from "./task/taskAuthority.js";
|
|
45
|
+
import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
|
|
58
46
|
import { runTaskChangeSetCommand } from "./commands/taskChangeSetCommands.js";
|
|
47
|
+
import { assertWorkItemDependenciesCompletedForCommand, dispatchPreparedReviewRound, failPendingReviewRound, parseTaskArchiveArguments, parseTaskCompletionRequest, planReplicatedWorkItemLanes, preflightTaskCompletion, preserveReviewRoundWorkspace, previewTaskRoleAgentConfigurationMutation, requireWorkItemAssignee, runTaskCommand, validateTaskArchiveRequest } from "./commands/taskCommands.js";
|
|
48
|
+
import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
|
|
49
|
+
import { finalizeStoppedTaskExecution, parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
|
|
50
|
+
import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
|
|
59
51
|
import { runTaskOverlapCommand } from "./commands/taskOverlapCommands.js";
|
|
60
|
-
import {
|
|
52
|
+
import { runTaskPublicationAdoptCommand } from "./commands/taskPublicationAdoptCommand.js";
|
|
53
|
+
import { runTaskPublicationVerifyCommand } from "./commands/taskPublicationVerifyCommand.js";
|
|
54
|
+
import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
|
|
61
55
|
import { runTaskWorkspaceCommand } from "./commands/taskWorkspaceCommands.js";
|
|
56
|
+
import { runTelemetryCommand } from "./commands/telemetryCommands.js";
|
|
62
57
|
import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
|
|
63
|
-
import {
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
|
|
68
|
-
import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
|
|
69
|
-
import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
|
|
70
|
-
import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
|
|
71
|
-
import { assertTaskBaseFreshnessForCompletion, inspectTaskBaseFreshness } from "./repository/taskBaseFreshness.js";
|
|
72
|
-
import { FileCompletionManager, resolveCliIdentity } from "./completion/fileCompletionManager.js";
|
|
58
|
+
import { FileCompletionManager } from "./completion/fileCompletionManager.js";
|
|
59
|
+
import { CONFIG_DOMAINS } from "./config/configCatalog.js";
|
|
60
|
+
import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
|
|
61
|
+
import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
|
|
73
62
|
import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
|
|
74
|
-
import { callController, ControllerClientError } from "./core/controllerClient.js";
|
|
75
63
|
import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
|
|
64
|
+
import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
|
|
76
65
|
import { cleanControllerResource } from "./controller/resourceCleanupLinux.js";
|
|
77
66
|
import { scanControllerResourceInventory } from "./controller/resourceInventoryLinux.js";
|
|
78
|
-
import { runSessionNotifyCommand } from "./controller/sessionNotify.js";
|
|
79
|
-
import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
|
|
80
67
|
import { runRuntimeObservationHookCommand } from "./controller/runtimeObservationHook.js";
|
|
68
|
+
import { runSessionNotifyCommand } from "./controller/sessionNotify.js";
|
|
69
|
+
import { SessionOwnerReconciliation } from "./controller/sessionOwnerReconciliation.js";
|
|
70
|
+
import { callController, ControllerClientError } from "./core/controllerClient.js";
|
|
81
71
|
import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
|
|
82
72
|
import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
|
|
73
|
+
import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
|
|
83
74
|
import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
|
|
84
|
-
import {
|
|
85
|
-
import {
|
|
75
|
+
import { createGitHubCliPublicationVerifier } from "./external/githubPublicationVerifier.js";
|
|
76
|
+
import { createGitLabCliPublicationVerifier } from "./external/gitlabPublicationVerifier.js";
|
|
77
|
+
import { recordGlobalInterruptResult, recordGlobalSteerResult } from "./message/globalInterrupt.js";
|
|
78
|
+
import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth } from "./observability/runtimeIdentity.js";
|
|
79
|
+
import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
|
|
80
|
+
import { renderAgentConfigurationCatalog } from "./output/agentConfigurationPresentation.js";
|
|
81
|
+
import { parseTaskAgentCapabilityQuery } from "./commands/taskAgentCapabilities.js";
|
|
82
|
+
import { capabilityReaderIdentity } from "./controller/agentCapabilities.js";
|
|
83
|
+
import { formatTimestamp } from "./output/timePresentation.js";
|
|
84
|
+
import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
|
|
85
|
+
import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
|
|
86
|
+
import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
|
|
87
|
+
import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
|
|
88
|
+
import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
|
|
89
|
+
import { assertTaskBaseFreshnessForCompletion, inspectTaskBaseFreshness } from "./repository/taskBaseFreshness.js";
|
|
86
90
|
import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
|
|
87
91
|
import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
|
|
88
92
|
import { snapshotWorkItemCandidate } from "./repository/workItemCandidateSnapshot.js";
|
|
93
|
+
import { assessDeltaRecheck } from "./review/deltaRecheck.js";
|
|
94
|
+
import { isCompletedTaskReviewEvidence } from "./review/reviewAcceptance.js";
|
|
95
|
+
import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
|
|
96
|
+
import { resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractResolution.js";
|
|
97
|
+
import { AGENT_HOST_CONTROL_PROTOCOL, foldInterruptLiveReceipt, foldSteerLiveReceipt, inspectAgentHost, runAgentHost, sendAgentHostAuthorityControl, sendAgentHostCancelControl, sendAgentHostSteerControl } from "./runtime/agentHost.js";
|
|
98
|
+
import { unknownAgentRunConfiguration } from "./runtime/agentRunConfiguration.js";
|
|
99
|
+
import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/managedCaller.js";
|
|
100
|
+
import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
|
|
101
|
+
import { runSetupCommand, validateSetupInvocation } from "./setup/setupCommand.js";
|
|
102
|
+
import { openCurrentTaskStore, validateCurrentTaskStore } from "./storage/currentTaskStore.js";
|
|
103
|
+
import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
|
|
89
104
|
import { inspectStorageSchema } from "./storage/storageSchema.js";
|
|
90
|
-
import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
|
|
91
105
|
import { resolveYuiHome } from "./storage/taskStore.js";
|
|
92
|
-
import {
|
|
106
|
+
import { renderArchiveDiagnostics, taskArchiveDiagnostics } from "./task/archiveDiagnostics.js";
|
|
107
|
+
import { inspectTaskArchive, renderTaskArchivePreflight } from "./task/archivePreflight.js";
|
|
108
|
+
import { assertTaskRemoteDeliveryIntegrated, createTaskRemoteDeliveryProof } from "./task/remoteDeliveryService.js";
|
|
93
109
|
import { resolveTaskRecordReference } from "./task/taskRecordReference.js";
|
|
94
|
-
import {
|
|
110
|
+
import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
|
|
95
111
|
import { NodeCommandExecutor } from "./tmux/commandExecutor.js";
|
|
96
112
|
import { TmuxManager } from "./tmux/tmuxManager.js";
|
|
97
|
-
import { WorkItemChangeSetManager } from "./workspace/workItemChangeSetManager.js";
|
|
98
|
-
import { workspaceProjectEntry } from "./worktree/managedWorkspace.js";
|
|
99
|
-
import { parseWebCommandOptions } from "./web/webServer.js";
|
|
100
|
-
import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
|
|
101
|
-
import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
|
|
102
|
-
import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
|
|
103
113
|
import { YUI_VERSION, yuiVersionIdentity } from "./version.js";
|
|
104
|
-
import {
|
|
105
|
-
import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
|
|
106
|
-
import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/managedCaller.js";
|
|
107
|
-
import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
|
|
108
|
-
import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
|
|
109
|
-
import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
|
|
110
|
-
import { resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractResolution.js";
|
|
111
|
-
import { assessDeltaRecheck } from "./review/deltaRecheck.js";
|
|
112
|
-
import { isCompletedTaskReviewEvidence } from "./review/reviewAcceptance.js";
|
|
113
|
-
import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
|
|
114
|
+
import { parseWebCommandOptions } from "./web/webServer.js";
|
|
114
115
|
import { currentWorkItemExecutionGroup } from "./workItem/workItem.js";
|
|
116
|
+
import { CleanupInspectionError } from "./workspace/cleanupInspection.js";
|
|
117
|
+
import { WorkItemChangeSetManager } from "./workspace/workItemChangeSetManager.js";
|
|
118
|
+
import { workspaceProjectEntry } from "./worktree/managedWorkspace.js";
|
|
115
119
|
const VERSION = YUI_VERSION;
|
|
116
120
|
const taskFinalReviewInvocation = extractTaskFinalReviewRequest(process.argv.slice(2));
|
|
117
121
|
const rawArgs = [...taskFinalReviewInvocation.args];
|
|
@@ -206,6 +210,18 @@ export async function main() {
|
|
|
206
210
|
if (args[0] === "version")
|
|
207
211
|
throw usageError("Version usage: yui version");
|
|
208
212
|
const { contract: taskFinalReviewContract, verifiedStore } = await preflightManagedTaskControlPlane();
|
|
213
|
+
assertTaskInvocationScope(args, process.env);
|
|
214
|
+
if (["config", "resources"].includes(args[0] ?? "") && (managedInvocation
|
|
215
|
+
|| process.env.YUI_ROLE !== undefined || process.env.YUI_AGENT_ID !== undefined
|
|
216
|
+
|| process.env.YUI_NATIVE_SESSION_ID !== undefined)) {
|
|
217
|
+
const ownedStore = verifiedStore === undefined ? openCurrentTaskStore(home) : undefined;
|
|
218
|
+
try {
|
|
219
|
+
assertConfigurationAuthority(args, verifiedStore ?? ownedStore, process.env);
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
ownedStore?.close();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
209
225
|
if (args[0] === "update") {
|
|
210
226
|
if (jsonOutput)
|
|
211
227
|
throw usageError("Update does not support --json.");
|
|
@@ -456,38 +472,32 @@ export async function main() {
|
|
|
456
472
|
scope: options.scope,
|
|
457
473
|
environment: process.env
|
|
458
474
|
});
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
catch {
|
|
474
|
-
runtime = { source: "unsupported", droppedEvents };
|
|
475
|
-
}
|
|
476
|
-
const mismatch = summarizeDurablePhysicalMismatch(snapshot);
|
|
477
|
-
const identitySection = renderRuntimeIdentitySection({
|
|
478
|
-
build,
|
|
479
|
-
storage,
|
|
480
|
-
runtime,
|
|
481
|
-
mismatch,
|
|
482
|
-
inventoryRssBytes: snapshot.summary.rssBytes
|
|
483
|
-
});
|
|
484
|
-
emit(`${renderControllerResourceStatus(snapshot, options.verbose)}\n\n${identitySection}`, false, { ...snapshot, identity: { build, storage, runtime, mismatch } });
|
|
485
|
-
// The exact current storage contract fails closed on contradictions.
|
|
486
|
-
if (evaluateStorageHealth(storage).status === "fail")
|
|
487
|
-
process.exitCode = 5;
|
|
488
|
-
return;
|
|
475
|
+
// Status always includes identity/health. Missing producers stay
|
|
476
|
+
// `unsupported`; storage contradictions retain the nonzero health exit.
|
|
477
|
+
const cliEntry = fileURLToPath(import.meta.url);
|
|
478
|
+
const packageRoot = resolve(cliEntry, "..", "..");
|
|
479
|
+
const build = collectRuntimeBuildIdentity(createProductionRuntimeIdentityPorts(packageRoot, cliEntry, process.env));
|
|
480
|
+
const storage = collectStorageIdentity(home);
|
|
481
|
+
const droppedEvents = countDroppedInboxEvents(home);
|
|
482
|
+
let runtime;
|
|
483
|
+
try {
|
|
484
|
+
const result = await callController(home, "controller.status", {}, { timeoutMs: 2_000 });
|
|
485
|
+
runtime = parseControllerRuntimeSnapshot(result, droppedEvents);
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
runtime = { source: "unsupported", droppedEvents };
|
|
489
489
|
}
|
|
490
|
-
|
|
490
|
+
const mismatch = summarizeDurablePhysicalMismatch(snapshot);
|
|
491
|
+
const identitySection = renderRuntimeIdentitySection({
|
|
492
|
+
build,
|
|
493
|
+
storage,
|
|
494
|
+
runtime,
|
|
495
|
+
mismatch,
|
|
496
|
+
inventoryRssBytes: snapshot.summary.rssBytes
|
|
497
|
+
});
|
|
498
|
+
emit(`${renderControllerResourceStatus(snapshot, options.verbose)}\n\n${identitySection}`, false, { ...snapshot, identity: { build, storage, runtime, mismatch } });
|
|
499
|
+
if (evaluateStorageHealth(storage).status === "fail")
|
|
500
|
+
process.exitCode = 5;
|
|
491
501
|
return;
|
|
492
502
|
}
|
|
493
503
|
if (method === "cleanup") {
|
|
@@ -528,7 +538,10 @@ export async function main() {
|
|
|
528
538
|
throw usageError("Controller usage: yui controller status [--all] [--verbose] | "
|
|
529
539
|
+ "cleanup [--all] | stop | restart.");
|
|
530
540
|
}
|
|
531
|
-
|
|
541
|
+
// Stopping an exactly identified Controller is a recovery operation:
|
|
542
|
+
// malformed domain records must not prevent quiescing its process.
|
|
543
|
+
if (method === "restart")
|
|
544
|
+
validateCurrentTaskStore(home);
|
|
532
545
|
const controllerMethod = method;
|
|
533
546
|
const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
|
|
534
547
|
// A pre-fix updater cannot pass the owner environment variable to the
|
|
@@ -580,6 +593,17 @@ export async function main() {
|
|
|
580
593
|
emit("Cancelled.");
|
|
581
594
|
return;
|
|
582
595
|
}
|
|
596
|
+
assertTaskInvocationScope(resolved, process.env);
|
|
597
|
+
if (resolved[0] === "task" && resolved[1] === "role" && resolved[2] === "capabilities") {
|
|
598
|
+
const query = parseTaskAgentCapabilityQuery(resolved.slice(3));
|
|
599
|
+
const result = await callController(home, "task.role-capabilities", {
|
|
600
|
+
...query, reader: capabilityReaderIdentity(process.env)
|
|
601
|
+
}, { timeoutMs: 15_000 });
|
|
602
|
+
emit(`Configuration: ${result.context.selection} ${result.context.taskId}/${result.context.roleName}\n`
|
|
603
|
+
+ "Discovery uses the Controller's current native account environment; settings files and secret values are not historical snapshots.\n"
|
|
604
|
+
+ renderAgentConfigurationCatalog(result), false, result);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
583
607
|
const validateAgentConfiguration = await preflightAgentConfigurationMutation(resolved, store, catalogs);
|
|
584
608
|
const executor = new NodeCommandExecutor();
|
|
585
609
|
const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), executor, {
|
|
@@ -588,1254 +612,1262 @@ export async function main() {
|
|
|
588
612
|
terminalInput: process.stdin,
|
|
589
613
|
onWarning: (message) => process.stderr.write(`Warning: ${message}\n`)
|
|
590
614
|
});
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
|
|
602
|
-
if (resolved[0] === "web") {
|
|
603
|
-
if (managedInvocation || process.env.YUI_ROLE || process.env.YUI_NATIVE_SESSION_ID) {
|
|
604
|
-
throw usageError("The Web user ingress must be started from a local user terminal, not a managed Session.");
|
|
605
|
-
}
|
|
606
|
-
if (resolved.length === 2 && (resolved[1] === "--status" || resolved[1] === "--stop")) {
|
|
607
|
-
const status = await callController(home, "web.status", {});
|
|
608
|
-
if (resolved[1] === "--status")
|
|
609
|
-
emit(status ? `Yui web control room: ${status.url}\n` : "Web is not running.\n", false, status);
|
|
610
|
-
else {
|
|
611
|
-
if (status)
|
|
612
|
-
await callController(home, "web.stop", { id: status.id });
|
|
613
|
-
emit("Web listener stopped; Controller and Agents are unchanged.\n");
|
|
615
|
+
const telemetry = openSchedulerTelemetry(home, store.getConfig());
|
|
616
|
+
try {
|
|
617
|
+
const schedulerStore = new FileSchedulerStoreAdapter(store, telemetry);
|
|
618
|
+
const planner = new FileRoleLaunchPlanner(home, store, { environment: process.env });
|
|
619
|
+
const workspacePreparer = new FileTaskWorkspacePreparer(home, store);
|
|
620
|
+
const runtime = new FileTaskWorkflowRuntime(home, store, schedulerStore, planner, tmux, workspacePreparer, {
|
|
621
|
+
environment: process.env,
|
|
622
|
+
onError: (error) => {
|
|
623
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
624
|
+
process.stderr.write(`Controller runtime error: ${message}\n`);
|
|
614
625
|
}
|
|
615
|
-
return;
|
|
616
|
-
}
|
|
617
|
-
if (jsonOutput)
|
|
618
|
-
throw usageError("Web start does not support --json; use --status.");
|
|
619
|
-
const options = parseWebCommandOptions(resolved.slice(1));
|
|
620
|
-
const id = randomUUID();
|
|
621
|
-
await callController(home, "web.start", { ...options, id });
|
|
622
|
-
const displayHost = options.host === "::1" ? "[::1]" : options.host;
|
|
623
|
-
process.stdout.write(`Yui web control room: http://${displayHost}:${options.port}\n`);
|
|
624
|
-
await new Promise((resolve) => {
|
|
625
|
-
const stop = () => {
|
|
626
|
-
process.off("SIGINT", stop);
|
|
627
|
-
process.off("SIGTERM", stop);
|
|
628
|
-
resolve();
|
|
629
|
-
};
|
|
630
|
-
process.once("SIGINT", stop);
|
|
631
|
-
process.once("SIGTERM", stop);
|
|
632
626
|
});
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
if (CONFIG_DOMAINS.includes(domain ?? "")) {
|
|
648
|
-
const configDomain = domain;
|
|
649
|
-
const domainArgs = resolved.slice(2);
|
|
650
|
-
const result = runConfigCommand(configDomain, domainArgs, store);
|
|
651
|
-
if (domainArgs[0] === "set"
|
|
652
|
-
&& domainArgs[1] === "reconciliation-interval-seconds") {
|
|
653
|
-
const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
|
|
654
|
-
emit(withControllerRefreshWarning(result.output, refresh, "Controller configuration"));
|
|
627
|
+
const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
|
|
628
|
+
if (resolved[0] === "web") {
|
|
629
|
+
if (managedInvocation || process.env.YUI_ROLE || process.env.YUI_NATIVE_SESSION_ID) {
|
|
630
|
+
throw usageError("The Web user ingress must be started from a local user terminal, not a managed Session.");
|
|
631
|
+
}
|
|
632
|
+
if (resolved.length === 2 && (resolved[1] === "--status" || resolved[1] === "--stop")) {
|
|
633
|
+
const status = await callController(home, "web.status", {});
|
|
634
|
+
if (resolved[1] === "--status")
|
|
635
|
+
emit(status ? `Yui web control room: ${status.url}\n` : "Web is not running.\n", false, status);
|
|
636
|
+
else {
|
|
637
|
+
if (status)
|
|
638
|
+
await callController(home, "web.stop", { id: status.id });
|
|
639
|
+
emit("Web listener stopped; Controller and Agents are unchanged.\n");
|
|
640
|
+
}
|
|
655
641
|
return;
|
|
656
642
|
}
|
|
657
|
-
|
|
643
|
+
if (jsonOutput)
|
|
644
|
+
throw usageError("Web start does not support --json; use --status.");
|
|
645
|
+
const options = parseWebCommandOptions(resolved.slice(1));
|
|
646
|
+
const id = randomUUID();
|
|
647
|
+
await callController(home, "web.start", { ...options, id });
|
|
648
|
+
const displayHost = options.host === "::1" ? "[::1]" : options.host;
|
|
649
|
+
process.stdout.write(`Yui web control room: http://${displayHost}:${options.port}\n`);
|
|
650
|
+
await new Promise((resolve) => {
|
|
651
|
+
const stop = () => {
|
|
652
|
+
process.off("SIGINT", stop);
|
|
653
|
+
process.off("SIGTERM", stop);
|
|
654
|
+
resolve();
|
|
655
|
+
};
|
|
656
|
+
process.once("SIGINT", stop);
|
|
657
|
+
process.once("SIGTERM", stop);
|
|
658
|
+
});
|
|
659
|
+
await callController(home, "web.stop", { id });
|
|
658
660
|
return;
|
|
659
661
|
}
|
|
660
|
-
if (
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
662
|
+
if (resolved[0] === "config") {
|
|
663
|
+
const domain = resolved[1];
|
|
664
|
+
const roleOptions = {
|
|
665
|
+
yuiHome: home,
|
|
666
|
+
env: process.env
|
|
667
|
+
};
|
|
668
|
+
if (domain === "show") {
|
|
669
|
+
const result = runConfigOverview(resolved.slice(2), store, process.env, roleOptions);
|
|
670
|
+
emit(result.output, false, result.data);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (CONFIG_DOMAINS.includes(domain ?? "")) {
|
|
674
|
+
const configDomain = domain;
|
|
675
|
+
const domainArgs = resolved.slice(2);
|
|
676
|
+
const result = runConfigCommand(configDomain, domainArgs, store);
|
|
677
|
+
if (domainArgs[0] === "set"
|
|
678
|
+
&& domainArgs[1] === "reconciliation-interval-seconds") {
|
|
679
|
+
const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
|
|
680
|
+
emit(withControllerRefreshWarning(result.output, refresh, "Controller configuration"));
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
emit(result.output, false, result.data);
|
|
674
684
|
return;
|
|
675
685
|
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
686
|
+
if (domain === "agent") {
|
|
687
|
+
const agentArgs = resolved.slice(2);
|
|
688
|
+
if (agentArgs[0] === "capabilities") {
|
|
689
|
+
if (agentArgs.length !== 2 && !(agentArgs.length === 3 && agentArgs[2] === "--refresh")) {
|
|
690
|
+
throw usageError("Agent capabilities usage: yui config agent capabilities <agent-id> [--refresh]");
|
|
691
|
+
}
|
|
692
|
+
const agent = store.getConfiguredAgent(agentArgs[1] ?? "");
|
|
693
|
+
if (agent === null)
|
|
694
|
+
throw agentNotFound(agentArgs[1] ?? "");
|
|
695
|
+
const result = await catalogs.resolve({
|
|
696
|
+
agent,
|
|
697
|
+
cwd: store.getConfig().defaultWorkspace ?? process.cwd(),
|
|
698
|
+
refresh: agentArgs[2] === "--refresh"
|
|
699
|
+
});
|
|
700
|
+
emit(renderAgentConfigurationCatalog(result), false, result);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const affectedAgentId = agentArgs[1];
|
|
704
|
+
const previousAgent = typeof affectedAgentId === "string"
|
|
685
705
|
? store.getConfiguredAgent(affectedAgentId)
|
|
686
706
|
: null;
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
707
|
+
const output = runAgentCommand(agentArgs, store);
|
|
708
|
+
if (agentArgs[0] === "add"
|
|
709
|
+
|| agentArgs[0] === "update"
|
|
710
|
+
|| agentArgs[0] === "remove") {
|
|
711
|
+
const currentAgent = typeof affectedAgentId === "string"
|
|
712
|
+
? store.getConfiguredAgent(affectedAgentId)
|
|
713
|
+
: null;
|
|
714
|
+
const capabilityNotice = currentAgent !== null
|
|
715
|
+
&& (agentArgs[0] === "add" || agentArgs[0] === "update")
|
|
716
|
+
? renderAgentConfigurationResolutionNotice(await catalogs.resolve({
|
|
717
|
+
agent: currentAgent,
|
|
718
|
+
cwd: store.getConfig().defaultWorkspace ?? process.cwd()
|
|
719
|
+
}))
|
|
720
|
+
: "";
|
|
721
|
+
const scope = agentEnvironmentRefreshScope(previousAgent, currentAgent, store.listConfiguredAgents());
|
|
722
|
+
const refresh = await refreshRunningFileTaskControllerEnvironment(home, store, process.env, scope);
|
|
723
|
+
emit(withControllerRefreshWarning(`${output.trimEnd()}${capabilityNotice.length === 0 ? "\n" : `\n${capabilityNotice}`}`, refresh, "Agent environment"));
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
emit(output);
|
|
697
727
|
return;
|
|
698
728
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const result = runProfileCommand(resolved.slice(2), store, () => new Date(), validateAgentConfiguration === undefined ? {} : { validateAgentConfiguration });
|
|
704
|
-
emit(result.output, false, result.data);
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
if (domain === "role") {
|
|
708
|
-
const result = runGlobalRoleCommand(resolved.slice(2), store, roleOptions);
|
|
709
|
-
if (typeof result !== "string") {
|
|
710
|
-
throw new Error("Config Role commands cannot enter a runtime Session.");
|
|
729
|
+
if (domain === "profile") {
|
|
730
|
+
const result = runProfileCommand(resolved.slice(2), store, () => new Date(), validateAgentConfiguration === undefined ? {} : { validateAgentConfiguration });
|
|
731
|
+
emit(result.output, false, result.data);
|
|
732
|
+
return;
|
|
711
733
|
}
|
|
712
|
-
|
|
713
|
-
|
|
734
|
+
if (domain === "role") {
|
|
735
|
+
const result = runGlobalRoleCommand(resolved.slice(2), store, roleOptions);
|
|
736
|
+
if (typeof result !== "string") {
|
|
737
|
+
throw new Error("Config Role commands cannot enter a runtime Session.");
|
|
738
|
+
}
|
|
739
|
+
emit(result);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
throw usageError(`Unknown configuration domain: ${domain ?? ""}.`);
|
|
714
743
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
if (resolved[0] === "project") {
|
|
718
|
-
const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
|
|
719
|
-
emit(result.output, false, result.data);
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
if (resolved[0] === "session") {
|
|
723
|
-
if (resolved[1] === "stop") {
|
|
724
|
-
const options = parseSessionStopOptions(resolved.slice(2));
|
|
725
|
-
const result = await runSessionStopCommand({
|
|
726
|
-
options,
|
|
727
|
-
runtime: {
|
|
728
|
-
beginMaintenance: () => acquireHandoverLock(home),
|
|
729
|
-
snapshot: () => ({
|
|
730
|
-
candidates: schedulerStore.listRuntimeSessionCandidates(),
|
|
731
|
-
dormant: schedulerStore.listDormantRuntimeOwners()
|
|
732
|
-
}),
|
|
733
|
-
drainController: () => runtime.drainController(),
|
|
734
|
-
stopController: () => stopFileTaskController(home, {
|
|
735
|
-
environment: process.env
|
|
736
|
-
}),
|
|
737
|
-
startController: async () => {
|
|
738
|
-
await ensureFileTaskController(home, { environment: process.env });
|
|
739
|
-
},
|
|
740
|
-
stopDormantSession: (candidate) => runtime.stopDormantSession(candidate)
|
|
741
|
-
},
|
|
742
|
-
environment: process.env
|
|
743
|
-
});
|
|
744
|
-
process.exitCode = result.exitCode;
|
|
744
|
+
if (resolved[0] === "project") {
|
|
745
|
+
const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
|
|
745
746
|
emit(result.output, false, result.data);
|
|
746
747
|
return;
|
|
747
748
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
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);
|
|
749
|
+
if (resolved[0] === "session") {
|
|
750
|
+
if (resolved[1] === "stop") {
|
|
751
|
+
const options = parseSessionStopOptions(resolved.slice(2));
|
|
752
|
+
const result = await runSessionStopCommand({
|
|
753
|
+
options,
|
|
754
|
+
runtime: {
|
|
755
|
+
beginMaintenance: () => acquireHandoverLock(home),
|
|
756
|
+
snapshot: () => ({
|
|
757
|
+
candidates: schedulerStore.listRuntimeSessionCandidates(),
|
|
758
|
+
dormant: schedulerStore.listDormantRuntimeOwners()
|
|
759
|
+
}),
|
|
760
|
+
drainController: () => runtime.drainController(),
|
|
761
|
+
stopController: () => stopFileTaskController(home, {
|
|
762
|
+
environment: process.env
|
|
763
|
+
}),
|
|
764
|
+
startController: async () => {
|
|
765
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
766
|
+
},
|
|
767
|
+
stopDormantSession: (candidate) => runtime.stopDormantSession(candidate)
|
|
768
|
+
},
|
|
769
|
+
environment: process.env
|
|
770
|
+
});
|
|
771
|
+
process.exitCode = result.exitCode;
|
|
772
|
+
emit(result.output, false, result.data);
|
|
799
773
|
return;
|
|
800
774
|
}
|
|
801
|
-
|
|
775
|
+
const roleOptions = {
|
|
776
|
+
yuiHome: home,
|
|
777
|
+
env: process.env,
|
|
778
|
+
jsonOutput
|
|
779
|
+
};
|
|
780
|
+
const sessionArgs = resolved[1] === "enter" || resolved[1] === "context"
|
|
781
|
+
? [resolved[1], ...resolved.slice(2)]
|
|
782
|
+
: ["session", resolved[1] ?? "", ...resolved.slice(2)];
|
|
783
|
+
const result = runGlobalRoleCommand(sessionArgs, store, roleOptions);
|
|
784
|
+
if (typeof result === "string") {
|
|
785
|
+
emit(result);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
// The session surface (record/replace/enter/context) only ever yields the
|
|
789
|
+
// enter control; the live input actions live under the top-level `role`
|
|
790
|
+
// command below, never here.
|
|
791
|
+
if (result.kind !== "enter") {
|
|
792
|
+
throw new Error("Session commands cannot perform a live input control.");
|
|
793
|
+
}
|
|
794
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
795
|
+
await runtime.prepareGlobalRoleEnter(result.role.name);
|
|
796
|
+
tmux.attachRole("operator", result.role.name, "auto");
|
|
802
797
|
return;
|
|
803
798
|
}
|
|
804
|
-
if (
|
|
805
|
-
//
|
|
806
|
-
//
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
799
|
+
if (resolved[0] === "role") {
|
|
800
|
+
// decision-3 §7 CLI grammar: the durable Global input actions are top-level
|
|
801
|
+
// `role message queue|steer <global-role> …` and `role interrupt
|
|
802
|
+
// <global-role> …`, distinct from `config role …` (desired configuration)
|
|
803
|
+
// and `session …` (native session lifecycle). Core persists the durable
|
|
804
|
+
// Global-owned Message, proves owner/target/capability/writer-fence from
|
|
805
|
+
// durable state, and returns either a string disposition (queued,
|
|
806
|
+
// idempotent-replay, or an explicit not-steered/not-interrupted failure with
|
|
807
|
+
// its exact code) or a resolved live intent. The CLI performs at most one
|
|
808
|
+
// native edge with scope "global" and taskId omitted — the same shared
|
|
809
|
+
// resolver and transport as a Task Role, never a fabricated Task.
|
|
810
|
+
let globalInputFailure;
|
|
811
|
+
const roleOptions = {
|
|
812
|
+
yuiHome: home,
|
|
813
|
+
env: process.env,
|
|
814
|
+
jsonOutput,
|
|
815
|
+
onInputFailure: failure => { globalInputFailure = failure; }
|
|
816
|
+
};
|
|
817
|
+
const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
|
|
818
|
+
if (resolved[1] === "message" && resolved[2] === "queue" && resolved[3] !== undefined) {
|
|
819
|
+
await callController(home, "scheduler.signal", {
|
|
820
|
+
key: `global-role:${encodeURIComponent(resolved[3])}`
|
|
821
|
+
}).catch(() => { });
|
|
822
|
+
}
|
|
823
|
+
if (typeof result === "string") {
|
|
824
|
+
if (globalInputFailure !== undefined) {
|
|
825
|
+
emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
emit(result);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
if (result.kind === "input-steer") {
|
|
832
|
+
// Core already persisted the durable Global Message and proved target +
|
|
833
|
+
// capability + writer fence from the Global Role's own Session set. This
|
|
834
|
+
// is the single live edge: one native steer of the exact current Turn,
|
|
835
|
+
// scope "global", no retarget and no fallback to interrupt or queue.
|
|
836
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
837
|
+
let control;
|
|
838
|
+
try {
|
|
839
|
+
control = await sendAgentHostSteerControl({
|
|
840
|
+
home,
|
|
841
|
+
scope: "global",
|
|
842
|
+
roleName: result.roleName,
|
|
843
|
+
control: {
|
|
844
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
845
|
+
type: "steer-turn",
|
|
846
|
+
nativeSessionId: result.target.nativeSessionId,
|
|
847
|
+
nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
|
|
848
|
+
authority: result.target.authority,
|
|
849
|
+
run: { attemptId: result.receiptId, boundedText: result.text }
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
recordGlobalSteerResult(store, result.roleName, result.messageId, {
|
|
855
|
+
state: "steer-unknown", outcome: "pending",
|
|
856
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
857
|
+
});
|
|
858
|
+
throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
|
|
859
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
860
|
+
+ "The Message is retained and its outcome is recorded from the Host; whether the "
|
|
861
|
+
+ "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
|
|
862
|
+
+ "do not reissue the same input under a new requestId or a different action.");
|
|
863
|
+
}
|
|
864
|
+
const steer = foldSteerLiveReceipt(control);
|
|
865
|
+
recordGlobalSteerResult(store, result.roleName, result.messageId, steer);
|
|
866
|
+
if (steer.state !== "steered") {
|
|
867
|
+
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 });
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
emit(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), false, {
|
|
815
871
|
roleName: result.roleName,
|
|
816
|
-
|
|
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
|
-
}
|
|
872
|
+
messageId: result.messageId, steer
|
|
824
873
|
});
|
|
874
|
+
return;
|
|
825
875
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
876
|
+
if (result.kind === "input-interrupt") {
|
|
877
|
+
// The single live edge for a Global interrupt: one native cancel of the
|
|
878
|
+
// exact current Turn, scope "global". Never a kill/restart/detach. Any
|
|
879
|
+
// then-handoff was already claimed durably by Core (an existing Global
|
|
880
|
+
// Message owned by this Role) and is delivered once by the ordinary
|
|
881
|
+
// continuation path after this Turn reaches a proven terminal.
|
|
882
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
883
|
+
let control;
|
|
884
|
+
try {
|
|
885
|
+
control = await sendAgentHostCancelControl({
|
|
886
|
+
home,
|
|
887
|
+
scope: "global",
|
|
888
|
+
roleName: result.roleName,
|
|
889
|
+
control: {
|
|
890
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
891
|
+
type: "cancel",
|
|
892
|
+
nativeOnly: true,
|
|
893
|
+
nativeSessionId: result.target.nativeSessionId,
|
|
894
|
+
attemptId: result.target.attemptId,
|
|
895
|
+
authority: result.target.authority
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
catch (error) {
|
|
900
|
+
recordGlobalInterruptResult(store, result.roleName, result.receiptId, {
|
|
901
|
+
state: "interrupt-unknown", outcome: "cancel-requested",
|
|
902
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
903
|
+
});
|
|
904
|
+
throw runtimeError(`Interrupt of global role ${result.roleName} did not complete: `
|
|
905
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
906
|
+
+ "No process was killed; re-read the Session before retrying.");
|
|
907
|
+
}
|
|
908
|
+
const interrupt = foldInterruptLiveReceipt(control);
|
|
909
|
+
recordGlobalInterruptResult(store, result.roleName, result.receiptId, interrupt);
|
|
910
|
+
if (interrupt.state !== "interrupt-requested") {
|
|
911
|
+
emitControlFailure(interruptReceiptOutput(result.output, result.roleName, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN" : "INTERRUPT_NOT_DELIVERED", { roleName: result.roleName, interrupt });
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
emit(interruptReceiptOutput(result.output, result.roleName, interrupt), false, {
|
|
915
|
+
roleName: result.roleName,
|
|
916
|
+
...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
|
|
917
|
+
interrupt
|
|
830
918
|
});
|
|
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
919
|
return;
|
|
842
920
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
921
|
+
if (result.kind !== "enter") {
|
|
922
|
+
throw new Error("Role command returned an invalid control result.");
|
|
923
|
+
}
|
|
924
|
+
// A top-level `role` command never enters a runtime Session; that is
|
|
925
|
+
// `session enter`.
|
|
926
|
+
throw usageError("Use 'yui session enter <role>' to attach to a Global Role.");
|
|
848
927
|
}
|
|
849
|
-
if (
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
});
|
|
928
|
+
if (resolved[0] === "operator") {
|
|
929
|
+
if (resolved[1] === "enter") {
|
|
930
|
+
if (resolved.length !== 2)
|
|
931
|
+
throw usageError("Operator enter usage: yui operator enter.");
|
|
932
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
933
|
+
await runtime.prepareGlobalRoleEnter("operator");
|
|
934
|
+
tmux.attachRole("operator", "operator", "auto");
|
|
935
|
+
return;
|
|
871
936
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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 });
|
|
937
|
+
const result = runOperatorCommand(resolved.slice(1), store, { runtime, environment: process.env });
|
|
938
|
+
if (result.kind === "output") {
|
|
939
|
+
emit(result.output, false, result.data);
|
|
885
940
|
return;
|
|
886
941
|
}
|
|
887
|
-
|
|
888
|
-
|
|
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
|
-
}
|
|
901
|
-
if (resolved[0] === "operator") {
|
|
902
|
-
if (resolved[1] === "enter") {
|
|
903
|
-
if (resolved.length !== 2)
|
|
904
|
-
throw usageError("Operator enter usage: yui operator enter.");
|
|
905
|
-
await ensureFileTaskController(home, { environment: process.env });
|
|
906
|
-
await runtime.prepareGlobalRoleEnter("operator");
|
|
907
|
-
tmux.attachRole("operator", "operator", "auto");
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
const result = runOperatorCommand(resolved.slice(1), store, { runtime, environment: process.env });
|
|
911
|
-
if (result.kind === "output") {
|
|
912
|
-
emit(result.output, false, result.data);
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
if (result.kind !== "session") {
|
|
916
|
-
throw new Error("Operator command returned an invalid control result.");
|
|
917
|
-
}
|
|
918
|
-
await executeOperatorSessionControl(result, home, store, runtime, tmux, catalogs);
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
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.");
|
|
942
|
+
if (result.kind !== "session") {
|
|
943
|
+
throw new Error("Operator command returned an invalid control result.");
|
|
926
944
|
}
|
|
927
|
-
|
|
928
|
-
emit(renderTaskArchivePreflight(data), false, data);
|
|
945
|
+
await executeOperatorSessionControl(result, home, store, runtime, tmux, catalogs);
|
|
929
946
|
return;
|
|
930
947
|
}
|
|
931
|
-
if (resolved[
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
+ "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
|
|
941
|
-
if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
|
|
942
|
-
throw usageError(usage);
|
|
943
|
-
}
|
|
944
|
-
if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
|
|
945
|
-
throw usageError("Artifact is outside the managed Task scope.");
|
|
946
|
-
}
|
|
947
|
-
if (store.getTask(taskId) === null)
|
|
948
|
-
throw usageError(`Task not found: ${taskId}.`);
|
|
949
|
-
let data;
|
|
950
|
-
if (action === "list") {
|
|
951
|
-
if (resolved.length !== 4)
|
|
952
|
-
throw usageError(usage);
|
|
953
|
-
data = await listArtifactsCapability(home, taskId);
|
|
948
|
+
if (resolved[0] === "task") {
|
|
949
|
+
if (resolved[1] === "archive-preflight") {
|
|
950
|
+
const request = parseTaskArchiveArguments(resolved.slice(2), "archive-preflight");
|
|
951
|
+
if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== request.taskId) {
|
|
952
|
+
throw usageError("Archive inspection must remain within this Session's Task.");
|
|
953
|
+
}
|
|
954
|
+
const data = await inspectTaskArchive(workspaceCoordinator, request);
|
|
955
|
+
emit(renderTaskArchivePreflight(data), false, data);
|
|
956
|
+
return;
|
|
954
957
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
958
|
+
if (resolved[1] === "artifact") {
|
|
959
|
+
// File/directory artifacts live in the Task's local Git repository, so
|
|
960
|
+
// their save/read/list are asynchronous and handled here rather than in
|
|
961
|
+
// the synchronous runTaskCommand chain. Save commits exactly one path and
|
|
962
|
+
// returns a self-certifying commit-pinned reference; read pins to a commit
|
|
963
|
+
// for frozen evidence; list is an ordinary current read.
|
|
964
|
+
const action = resolved[2];
|
|
965
|
+
const taskId = resolved[3];
|
|
966
|
+
const usage = "Usage: yui task artifact list <task> | read <task> <relative-path> [<commit>] | "
|
|
967
|
+
+ "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
|
|
968
|
+
if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
|
|
959
969
|
throw usageError(usage);
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
970
|
+
}
|
|
971
|
+
if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
|
|
972
|
+
throw usageError("Artifact is outside the managed Task scope.");
|
|
973
|
+
}
|
|
974
|
+
if (store.getTask(taskId) === null)
|
|
975
|
+
throw usageError(`Task not found: ${taskId}.`);
|
|
976
|
+
let data;
|
|
977
|
+
if (action === "list") {
|
|
978
|
+
if (resolved.length !== 4)
|
|
979
|
+
throw usageError(usage);
|
|
980
|
+
data = await listArtifactsCapability(home, taskId);
|
|
981
|
+
}
|
|
982
|
+
else if (action === "read") {
|
|
983
|
+
const relativePath = resolved[4];
|
|
984
|
+
const commit = resolved[5];
|
|
985
|
+
if (relativePath === undefined || resolved.length > 6)
|
|
986
|
+
throw usageError(usage);
|
|
987
|
+
data = await readArtifactCapability(home, taskId, {
|
|
988
|
+
relativePath, ...(commit === undefined ? {} : { commit })
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
else {
|
|
992
|
+
// save: a delivery-authoritative action; a managed Task caller must be the current Leader.
|
|
993
|
+
taskLocalActor(store, process.env, taskId);
|
|
994
|
+
const relativePath = resolved[4];
|
|
995
|
+
const content = resolved[5];
|
|
996
|
+
if (relativePath === undefined || content === undefined)
|
|
997
|
+
throw usageError(usage);
|
|
998
|
+
const rest = resolved.slice(6);
|
|
999
|
+
let message;
|
|
1000
|
+
let expectedHead;
|
|
1001
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
1002
|
+
const value = rest[index + 1];
|
|
1003
|
+
if (rest[index] === "--message" && value !== undefined) {
|
|
1004
|
+
message = value;
|
|
1005
|
+
index += 1;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (rest[index] === "--expected-head" && value !== undefined) {
|
|
1009
|
+
expectedHead = value;
|
|
1010
|
+
index += 1;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
throw usageError(usage);
|
|
1014
|
+
}
|
|
1015
|
+
data = await saveArtifactCapability(home, taskId, {
|
|
1016
|
+
relativePath, content,
|
|
1017
|
+
...(message === undefined ? {} : { message }),
|
|
1018
|
+
...(expectedHead === undefined ? {} : { expectedHead })
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
emit(JSON.stringify(data, null, 2), false, data);
|
|
1022
|
+
return;
|
|
963
1023
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
975
|
-
const value = rest[index + 1];
|
|
976
|
-
if (rest[index] === "--message" && value !== undefined) {
|
|
977
|
-
message = value;
|
|
978
|
-
index += 1;
|
|
979
|
-
continue;
|
|
1024
|
+
if (resolved[1] === "execution") {
|
|
1025
|
+
if (resolved[2] === "stop") {
|
|
1026
|
+
const request = parseTaskExecutionStopRequest(resolved.slice(3));
|
|
1027
|
+
const result = stopTaskExecutionCommand(request, store, { environment: process.env });
|
|
1028
|
+
try {
|
|
1029
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
1030
|
+
await runtime.stopTaskDurableJobs(result.taskId);
|
|
1031
|
+
await runtime.stopTaskRoleSessions(result.taskId, result.roleNames);
|
|
1032
|
+
await runtime.assertTaskPhysicalResourcesReleased(result.taskId);
|
|
1033
|
+
finalizeStoppedTaskExecution(result.taskId, store);
|
|
980
1034
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
continue;
|
|
1035
|
+
catch (error) {
|
|
1036
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1037
|
+
throw runtimeError(`Task execution is stopped and durable progress is preserved, but physical runtime cleanup failed: ${message}`);
|
|
985
1038
|
}
|
|
986
|
-
|
|
1039
|
+
emit(result.output, false, result);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (resolved[2] === "start") {
|
|
1043
|
+
const taskId = parseTaskExecutionStartRequest(resolved.slice(3));
|
|
1044
|
+
const task = store.getTask(taskId);
|
|
1045
|
+
if (task === null)
|
|
1046
|
+
throw usageError(`Task not found: ${taskId}.`);
|
|
1047
|
+
// Reject managed Task callers before inspecting or starting runtime resources.
|
|
1048
|
+
if (taskLocalActor(store, process.env, taskId) === "leader") {
|
|
1049
|
+
throw usageError("Task execution stop/start requires the global Operator or a human user.");
|
|
1050
|
+
}
|
|
1051
|
+
if (task.executionGate.state === "stopped") {
|
|
1052
|
+
await runtime.assertTaskPhysicalResourcesReleased(taskId);
|
|
1053
|
+
}
|
|
1054
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
1055
|
+
const result = startTaskExecutionCommand(taskId, store, { environment: process.env });
|
|
1056
|
+
// Idempotent start is also a reliable kick: if an earlier caller
|
|
1057
|
+
// committed the gate but lost its Controller acknowledgement, retrying
|
|
1058
|
+
// start re-signals the same durable wake without creating another one.
|
|
1059
|
+
await runtime.notifyMailboxChanged({ kind: "role", taskId, roleName: "leader" });
|
|
1060
|
+
emit(result.output, false, result);
|
|
1061
|
+
return;
|
|
987
1062
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1063
|
+
throw usageError("Task execution usage: yui task execution <stop|start> ...");
|
|
1064
|
+
}
|
|
1065
|
+
if (resolved[1] === "integration") {
|
|
1066
|
+
const result = await runTaskIntegrationCommand(resolved.slice(2), store, home, {
|
|
1067
|
+
environment: process.env,
|
|
1068
|
+
jobPort: createControllerIntegrationJobPort(home, { environment: process.env })
|
|
992
1069
|
});
|
|
1070
|
+
emit(result.output, false, result.data);
|
|
1071
|
+
return;
|
|
993
1072
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const result =
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1073
|
+
if (resolved[1] === "change-set") {
|
|
1074
|
+
const result = await runTaskChangeSetCommand(resolved.slice(2), store);
|
|
1075
|
+
emit(result.output, false, result.data);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (resolved[1] === "publication" && (resolved[2] === "diff" || resolved[2] === "adopt")) {
|
|
1079
|
+
const result = await runTaskPublicationAdoptCommand(resolved.slice(2), store, {
|
|
1080
|
+
environment: process.env
|
|
1081
|
+
});
|
|
1082
|
+
emit(result.output, false, result.data);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (resolved[1] === "publication" && resolved[2] === "verify") {
|
|
1086
|
+
const result = await runTaskPublicationVerifyCommand(resolved.slice(3), store, {
|
|
1087
|
+
verifiers: {
|
|
1088
|
+
github: createGitHubCliPublicationVerifier({
|
|
1089
|
+
environmentPath: process.env.PATH
|
|
1090
|
+
}),
|
|
1091
|
+
gitlab: createGitLabCliPublicationVerifier({
|
|
1092
|
+
environmentPath: process.env.PATH
|
|
1093
|
+
})
|
|
1094
|
+
},
|
|
1095
|
+
environment: process.env
|
|
1096
|
+
});
|
|
1097
|
+
emit(result.output, false, result.data);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (resolved[1] === "overlap") {
|
|
1101
|
+
const result = await runTaskOverlapCommand(resolved.slice(2), store);
|
|
1102
|
+
emit(result.output, false, result.data);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (resolved[1] === "workflow"
|
|
1106
|
+
&& (resolved[2] === "run" || resolved[2] === "resume")) {
|
|
1107
|
+
const result = await runWorkflowCommandAsync(resolved.slice(2), store, {
|
|
1108
|
+
environment: process.env,
|
|
1109
|
+
yuiHome: home,
|
|
1110
|
+
ports: createReleaseWorkflowPorts({
|
|
1111
|
+
home,
|
|
1112
|
+
updatePorts: createUpdatePorts(process.env),
|
|
1113
|
+
projectStore: store
|
|
1114
|
+
})
|
|
1115
|
+
});
|
|
1116
|
+
if (result.kind !== "output") {
|
|
1117
|
+
throw new Error(`Task workflow ${resolved[2]} returned an invalid control result.`);
|
|
1007
1118
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1119
|
+
emit(result.output, false, result.data);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
if (resolved[1] === "replace") {
|
|
1123
|
+
const result = await runTaskWorkspaceCommand(resolved.slice(1), store, workspacePreparer);
|
|
1124
|
+
emit(result.output, false, result.data);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
if (resolved[1] === "work" && resolved[2] === "isolate") {
|
|
1128
|
+
const workItemId = resolved[3];
|
|
1129
|
+
if (workItemId === undefined || resolved.length !== 4) {
|
|
1130
|
+
throw usageError("Task work isolate usage: yui task work isolate <task>/<work>.");
|
|
1011
1131
|
}
|
|
1012
|
-
|
|
1132
|
+
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1133
|
+
const workspace = await workspaceCoordinator.isolateWorkItem(reference.taskId, reference.localId);
|
|
1134
|
+
emit(`Created WorkItem workspace for ${reference.taskId}/${reference.localId}\nWorkspace: ${workspace.root}\n`, false, { workItemRef: reference, workspace });
|
|
1013
1135
|
return;
|
|
1014
1136
|
}
|
|
1015
|
-
if (resolved[2] === "
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
if (
|
|
1019
|
-
throw usageError(
|
|
1020
|
-
// Reject managed Task callers before inspecting or starting runtime resources.
|
|
1021
|
-
if (taskLocalActor(store, process.env, taskId) === "leader") {
|
|
1022
|
-
throw usageError("Task execution stop/start requires the global Operator or a human user.");
|
|
1137
|
+
if (resolved[1] === "work" && resolved[2] === "review"
|
|
1138
|
+
&& resolved[3] === "cleanup") {
|
|
1139
|
+
const reviewRoundId = resolved[4];
|
|
1140
|
+
if (reviewRoundId === undefined || resolved.length !== 5) {
|
|
1141
|
+
throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
|
|
1023
1142
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1143
|
+
const reference = cliTaskRecordReference(reviewRoundId, "reviewRound", process.env);
|
|
1144
|
+
const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
|
|
1145
|
+
if (removal === "dirty") {
|
|
1146
|
+
throw usageError(`ReviewRound workspace is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
|
|
1026
1147
|
}
|
|
1027
|
-
|
|
1028
|
-
const result = startTaskExecutionCommand(taskId, store, { environment: process.env });
|
|
1029
|
-
// Idempotent start is also a reliable kick: if an earlier caller
|
|
1030
|
-
// committed the gate but lost its Controller acknowledgement, retrying
|
|
1031
|
-
// start re-signals the same durable wake without creating another one.
|
|
1032
|
-
await runtime.notifyMailboxChanged?.({ kind: "role", taskId, roleName: "leader" });
|
|
1033
|
-
emit(result.output, false, result);
|
|
1148
|
+
emit(`Cleaned ReviewRound workspace ${reference.taskId}/${reference.localId} (${removal})\n`, false, { reviewRoundRef: reference, workspace: { removal } });
|
|
1034
1149
|
return;
|
|
1035
1150
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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
|
-
}
|
|
1058
|
-
if (resolved[1] === "publication" && resolved[2] === "verify") {
|
|
1059
|
-
const result = await runTaskPublicationVerifyCommand(resolved.slice(3), store, {
|
|
1060
|
-
verifiers: {
|
|
1061
|
-
github: createGitHubCliPublicationVerifier({
|
|
1062
|
-
environmentPath: process.env.PATH
|
|
1063
|
-
}),
|
|
1064
|
-
gitlab: createGitLabCliPublicationVerifier({
|
|
1065
|
-
environmentPath: process.env.PATH
|
|
1066
|
-
})
|
|
1067
|
-
},
|
|
1068
|
-
environment: process.env
|
|
1069
|
-
});
|
|
1070
|
-
emit(result.output, false, result.data);
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
if (resolved[1] === "overlap") {
|
|
1074
|
-
const result = await runTaskOverlapCommand(resolved.slice(2), store);
|
|
1075
|
-
emit(result.output, false, result.data);
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
if (resolved[1] === "workflow"
|
|
1079
|
-
&& (resolved[2] === "run" || resolved[2] === "resume")) {
|
|
1080
|
-
const result = await runWorkflowCommandAsync(resolved.slice(2), store, {
|
|
1081
|
-
environment: process.env,
|
|
1082
|
-
yuiHome: home,
|
|
1083
|
-
ports: createReleaseWorkflowPorts({
|
|
1084
|
-
home,
|
|
1085
|
-
updatePorts: createUpdatePorts(process.env),
|
|
1086
|
-
projectStore: store
|
|
1087
|
-
})
|
|
1088
|
-
});
|
|
1089
|
-
if (result.kind !== "output") {
|
|
1090
|
-
throw new Error(`Task workflow ${resolved[2]} returned an invalid control result.`);
|
|
1151
|
+
if (resolved[1] === "work" && resolved[2] === "capture") {
|
|
1152
|
+
const workItemId = resolved[3];
|
|
1153
|
+
if (workItemId === undefined || resolved.length !== 4) {
|
|
1154
|
+
throw usageError("Task work capture usage: yui task work capture <task>/<work>.");
|
|
1155
|
+
}
|
|
1156
|
+
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1157
|
+
assertTaskDeliveryAuthority(store, process.env, reference.taskId);
|
|
1158
|
+
const changeSets = await new WorkItemChangeSetManager(store).capture(reference.taskId, reference.localId, taskFinalReviewContract === undefined
|
|
1159
|
+
? {}
|
|
1160
|
+
: { taskFinalReviewContract });
|
|
1161
|
+
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
1162
|
+
emit(changeSets.length === 0
|
|
1163
|
+
? `WorkItem workspace has no changes to capture: ${qualified}\n`
|
|
1164
|
+
: `Captured ChangeSets ${changeSets.map(({ id }) => id).join(", ")} from ${qualified}\n`, false, { workItemRef: reference, changeSets });
|
|
1165
|
+
return;
|
|
1091
1166
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1130
|
-
assertTaskDeliveryAuthority(store, process.env, reference.taskId);
|
|
1131
|
-
const changeSets = await new WorkItemChangeSetManager(store).capture(reference.taskId, reference.localId, taskFinalReviewContract === undefined
|
|
1132
|
-
? {}
|
|
1133
|
-
: { taskFinalReviewContract });
|
|
1134
|
-
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
1135
|
-
emit(changeSets.length === 0
|
|
1136
|
-
? `WorkItem workspace has no changes to capture: ${qualified}\n`
|
|
1137
|
-
: `Captured ChangeSets ${changeSets.map(({ id }) => id).join(", ")} from ${qualified}\n`, false, { workItemRef: reference, changeSets });
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
if (resolved[1] === "work" && resolved[2] === "cleanup") {
|
|
1141
|
-
const workItemId = resolved[3];
|
|
1142
|
-
const disposition = resolved[4];
|
|
1143
|
-
if (workItemId === undefined
|
|
1144
|
-
|| !["--runtime-only", "--integrated", "--abandon"].includes(disposition ?? "")
|
|
1145
|
-
|| resolved.length !== 5) {
|
|
1146
|
-
throw usageError("Task work cleanup usage: yui task work cleanup <task>/<work> "
|
|
1147
|
-
+ "(--runtime-only|--integrated|--abandon).");
|
|
1148
|
-
}
|
|
1149
|
-
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1150
|
-
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
1151
|
-
assertTaskDeliveryAuthority(store, process.env, reference.taskId);
|
|
1152
|
-
if (disposition === "--runtime-only") {
|
|
1153
|
-
let runtimeCleanup;
|
|
1167
|
+
if (resolved[1] === "work" && resolved[2] === "cleanup") {
|
|
1168
|
+
const workItemId = resolved[3];
|
|
1169
|
+
const disposition = resolved[4];
|
|
1170
|
+
if (workItemId === undefined
|
|
1171
|
+
|| !["--runtime-only", "--integrated", "--abandon"].includes(disposition ?? "")
|
|
1172
|
+
|| resolved.length !== 5) {
|
|
1173
|
+
throw usageError("Task work cleanup usage: yui task work cleanup <task>/<work> "
|
|
1174
|
+
+ "(--runtime-only|--integrated|--abandon).");
|
|
1175
|
+
}
|
|
1176
|
+
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1177
|
+
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
1178
|
+
assertTaskDeliveryAuthority(store, process.env, reference.taskId);
|
|
1179
|
+
if (disposition === "--runtime-only") {
|
|
1180
|
+
let runtimeCleanup;
|
|
1181
|
+
try {
|
|
1182
|
+
runtimeCleanup = await workspaceCoordinator.cleanupWorkItemRuntime(reference.taskId, reference.localId);
|
|
1183
|
+
}
|
|
1184
|
+
catch (error) {
|
|
1185
|
+
throw cleanupCliError(error, `work-item:${qualified}`);
|
|
1186
|
+
}
|
|
1187
|
+
emit(`Released WorkItem runtime ${qualified}; retained its Session and worktree\n`, false, {
|
|
1188
|
+
workItem: store.getWorkItem(reference.taskId, reference.localId),
|
|
1189
|
+
runtime: { cleanup: runtimeCleanup },
|
|
1190
|
+
worktree: { retained: true }
|
|
1191
|
+
});
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const cleanedAs = disposition === "--integrated" ? "integrated" : "abandoned";
|
|
1195
|
+
if (cleanedAs === "integrated") {
|
|
1196
|
+
try {
|
|
1197
|
+
await new WorkItemChangeSetManager(store).assertIntegrated(reference.taskId, reference.localId);
|
|
1198
|
+
}
|
|
1199
|
+
catch (error) {
|
|
1200
|
+
throw cleanupCliError(error, `work-item:${qualified}`);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
let removal;
|
|
1154
1204
|
try {
|
|
1155
|
-
|
|
1205
|
+
removal = await workspaceCoordinator.cleanupWorkItem(reference.taskId, reference.localId, cleanedAs);
|
|
1156
1206
|
}
|
|
1157
1207
|
catch (error) {
|
|
1158
1208
|
throw cleanupCliError(error, `work-item:${qualified}`);
|
|
1159
1209
|
}
|
|
1160
|
-
|
|
1210
|
+
if (removal === "dirty") {
|
|
1211
|
+
throw usageError(`WorkItem worktree is dirty and was retained: ${qualified}.`, undefined, cleanupBlockedDetails("dirty-worktree", `work-item:${qualified}`, true));
|
|
1212
|
+
}
|
|
1213
|
+
emit(`Cleaned WorkItem worktree ${qualified} (${cleanedAs})\n`, false, {
|
|
1161
1214
|
workItem: store.getWorkItem(reference.taskId, reference.localId),
|
|
1162
|
-
|
|
1163
|
-
worktree: { retained: true }
|
|
1215
|
+
worktree: { removal, disposition: cleanedAs }
|
|
1164
1216
|
});
|
|
1165
1217
|
return;
|
|
1166
1218
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1219
|
+
if (resolved[1] === "work" && resolved[2] === "review"
|
|
1220
|
+
&& resolved[3] === "cleanup") {
|
|
1221
|
+
const reviewRef = resolved[4];
|
|
1222
|
+
if (reviewRef === undefined || resolved.length !== 5) {
|
|
1223
|
+
throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
|
|
1171
1224
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1225
|
+
const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
|
|
1226
|
+
const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
|
|
1227
|
+
if (removal === "dirty") {
|
|
1228
|
+
throw usageError(`ReviewRound worktree is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
|
|
1174
1229
|
}
|
|
1230
|
+
emit(`Cleaned ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
|
|
1231
|
+
reviewRound: store.getReviewRound(reference.taskId, reference.localId),
|
|
1232
|
+
worktree: { removal }
|
|
1233
|
+
});
|
|
1234
|
+
return;
|
|
1175
1235
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1236
|
+
if (resolved[1] === "work" && resolved[2] === "review"
|
|
1237
|
+
&& resolved[3] === "preserve") {
|
|
1238
|
+
const reviewRef = resolved[4];
|
|
1239
|
+
if (reviewRef === undefined || resolved.length !== 5) {
|
|
1240
|
+
throw usageError("Task work review preserve usage: yui task work review preserve <task>/<review-round>.");
|
|
1241
|
+
}
|
|
1242
|
+
const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
|
|
1243
|
+
const round = preserveReviewRoundWorkspace(reference.taskId, reference.localId, store, { runtime, environment: process.env, yuiHome: home });
|
|
1244
|
+
emit(`Preserved ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
|
|
1245
|
+
reviewRound: round
|
|
1246
|
+
});
|
|
1247
|
+
return;
|
|
1185
1248
|
}
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
&& resolved[3] === "cleanup") {
|
|
1194
|
-
const reviewRef = resolved[4];
|
|
1195
|
-
if (reviewRef === undefined || resolved.length !== 5) {
|
|
1196
|
-
throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
|
|
1197
|
-
}
|
|
1198
|
-
const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
|
|
1199
|
-
const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
|
|
1200
|
-
if (removal === "dirty") {
|
|
1201
|
-
throw usageError(`ReviewRound worktree is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
|
|
1202
|
-
}
|
|
1203
|
-
emit(`Cleaned ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
|
|
1204
|
-
reviewRound: store.getReviewRound(reference.taskId, reference.localId),
|
|
1205
|
-
worktree: { removal }
|
|
1206
|
-
});
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
|
-
if (resolved[1] === "work" && resolved[2] === "review"
|
|
1210
|
-
&& resolved[3] === "preserve") {
|
|
1211
|
-
const reviewRef = resolved[4];
|
|
1212
|
-
if (reviewRef === undefined || resolved.length !== 5) {
|
|
1213
|
-
throw usageError("Task work review preserve usage: yui task work review preserve <task>/<review-round>.");
|
|
1214
|
-
}
|
|
1215
|
-
const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
|
|
1216
|
-
const round = preserveReviewRoundWorkspace(reference.taskId, reference.localId, store, { runtime, environment: process.env, yuiHome: home });
|
|
1217
|
-
emit(`Preserved ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
|
|
1218
|
-
reviewRound: round
|
|
1219
|
-
});
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
let archiveRemoteDeliveryProof;
|
|
1223
|
-
let archiveTaskReviewCandidate;
|
|
1224
|
-
if (resolved[1] === "archive") {
|
|
1225
|
-
const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
|
|
1226
|
-
runtime,
|
|
1227
|
-
environment: process.env,
|
|
1228
|
-
yuiHome: home
|
|
1229
|
-
});
|
|
1230
|
-
const task = store.getTask(taskId);
|
|
1231
|
-
if (task === null)
|
|
1232
|
-
throw new Error(`Task disappeared after archive validation: ${taskId}.`);
|
|
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
|
|
1249
|
+
let archiveRemoteDeliveryProof;
|
|
1250
|
+
let archiveTaskReviewCandidate;
|
|
1251
|
+
if (resolved[1] === "archive") {
|
|
1252
|
+
const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
|
|
1253
|
+
runtime,
|
|
1254
|
+
environment: process.env,
|
|
1255
|
+
yuiHome: home
|
|
1238
1256
|
});
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1257
|
+
const task = store.getTask(taskId);
|
|
1258
|
+
if (task === null)
|
|
1259
|
+
throw new Error(`Task disappeared after archive validation: ${taskId}.`);
|
|
1260
|
+
if (force || task.status === "archived") {
|
|
1261
|
+
// Archive admission and mandatory audit commit before any fallible
|
|
1262
|
+
// filesystem/provider work. Repeats report facts, never replay cleanup.
|
|
1263
|
+
const admitted = runTaskCommand(resolved.slice(1), store, {
|
|
1264
|
+
runtime, environment: process.env, yuiHome: home
|
|
1265
|
+
});
|
|
1266
|
+
if (force && admitted.kind === "output"
|
|
1267
|
+
&& admitted.data.changed) {
|
|
1268
|
+
await workspaceCoordinator.cleanupArchivedTask(taskId, disposition);
|
|
1269
|
+
}
|
|
1270
|
+
const current = store.getTask(taskId);
|
|
1271
|
+
const archive = taskArchiveDiagnostics(store, current);
|
|
1272
|
+
emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
|
|
1273
|
+
return;
|
|
1242
1274
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
{
|
|
1249
|
-
if (disposition === "integrated") {
|
|
1250
|
-
archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
|
|
1251
|
-
archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
|
|
1252
|
-
assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
|
|
1253
|
-
}
|
|
1254
|
-
const workItemIds = store.listManagedWorkspaces(task.id)
|
|
1255
|
-
.flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
|
|
1256
|
-
for (const workItemId of workItemIds) {
|
|
1257
|
-
const item = store.getWorkItem(task.id, workItemId);
|
|
1258
|
-
if (item?.status !== "accepted" || disposition !== "integrated")
|
|
1259
|
-
continue;
|
|
1260
|
-
try {
|
|
1261
|
-
await new WorkItemChangeSetManager(store).assertIntegrated(task.id, item.id);
|
|
1275
|
+
{
|
|
1276
|
+
if (disposition === "integrated") {
|
|
1277
|
+
archiveTaskReviewCandidate = await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env);
|
|
1278
|
+
archiveRemoteDeliveryProof = createTaskRemoteDeliveryProof(store, task, archiveTaskReviewCandidate ?? null);
|
|
1279
|
+
assertTaskRemoteDeliveryIntegrated(archiveRemoteDeliveryProof.delivery);
|
|
1262
1280
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1281
|
+
const workItemIds = store.listManagedWorkspaces(task.id)
|
|
1282
|
+
.flatMap(({ owner }) => owner.type === "work-item" ? [owner.workItemId] : []);
|
|
1283
|
+
for (const workItemId of workItemIds) {
|
|
1284
|
+
const item = store.getWorkItem(task.id, workItemId);
|
|
1285
|
+
if (item?.status !== "accepted" || disposition !== "integrated")
|
|
1286
|
+
continue;
|
|
1287
|
+
try {
|
|
1288
|
+
await new WorkItemChangeSetManager(store).assertIntegrated(task.id, item.id);
|
|
1289
|
+
}
|
|
1290
|
+
catch (error) {
|
|
1291
|
+
throw cleanupCliError(error, `work-item:${task.id}/${item.id}`);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
const cleanup = await workspaceCoordinator.cleanupTaskForArchive(task.id, disposition);
|
|
1295
|
+
if (cleanup.status === "retained-dirty") {
|
|
1296
|
+
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));
|
|
1297
|
+
}
|
|
1298
|
+
if (cleanup.status === "failed") {
|
|
1299
|
+
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));
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
let taskRetirementProof;
|
|
1304
|
+
if (resolved[1] === "retire") {
|
|
1305
|
+
const taskId = resolved[2];
|
|
1306
|
+
if (taskId !== undefined && !taskId.startsWith("--")) {
|
|
1307
|
+
const task = store.getTask(taskId);
|
|
1308
|
+
if (task?.status === "active" || task?.status === "draft") {
|
|
1309
|
+
try {
|
|
1310
|
+
taskRetirementProof = await new WorkItemChangeSetManager(store)
|
|
1311
|
+
.assertRetirable(taskId);
|
|
1312
|
+
}
|
|
1313
|
+
catch (error) {
|
|
1314
|
+
throw usageError(error instanceof Error ? error.message : String(error));
|
|
1315
|
+
}
|
|
1265
1316
|
}
|
|
1266
1317
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1318
|
+
}
|
|
1319
|
+
assertWorkItemExecutionDependenciesForCommand(resolved, store, process.env);
|
|
1320
|
+
if (resolved[1] === "work" && resolved[2] === "dispatch") {
|
|
1321
|
+
const workItemId = resolved[3];
|
|
1322
|
+
const reference = workItemId === undefined
|
|
1323
|
+
? null
|
|
1324
|
+
: cliWorkItemReference(workItemId, process.env);
|
|
1325
|
+
const item = reference === null
|
|
1326
|
+
? null
|
|
1327
|
+
: store.getWorkItem(reference.taskId, reference.localId);
|
|
1328
|
+
const task = item === null ? null : store.getTask(item.taskId);
|
|
1329
|
+
if (item !== null && task !== null) {
|
|
1330
|
+
// Authority and pure Lane-shape checks precede every physical or
|
|
1331
|
+
// durable workspace preparation performed for dispatch.
|
|
1332
|
+
assertTaskDeliveryAuthority(store, process.env, task.id);
|
|
1333
|
+
requireWorkItemAssignee(item);
|
|
1334
|
+
workItemDispatchLanePlan(resolved, store, item);
|
|
1335
|
+
}
|
|
1336
|
+
// A rejected Candidate starts a new execution iteration. Release every
|
|
1337
|
+
// terminal Lane Role runtime before preparing the new Lane workspaces;
|
|
1338
|
+
// durable AgentRuns, Groups, Candidates, and workspace owners remain intact.
|
|
1339
|
+
if (item?.status === "open"
|
|
1340
|
+
&& currentWorkItemExecutionGroup(item)?.lanes.every(({ disposition }) => disposition !== "open")) {
|
|
1341
|
+
await workspaceCoordinator.cleanupWorkItemRuntime(item.taskId, item.id);
|
|
1342
|
+
}
|
|
1343
|
+
// Every Task needs an authoritative runtime owner before dispatch. A
|
|
1344
|
+
// Gitless Task uses an empty Task-owned view; Project-backed WorkItems
|
|
1345
|
+
// additionally receive their isolated Develop owner below.
|
|
1346
|
+
if (item !== null && task !== null) {
|
|
1347
|
+
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1270
1348
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1349
|
+
// A Project-backed Worker WorkItem gets an isolated Develop owner before
|
|
1350
|
+
// its Lane is prepared. A Leader-owned WorkItem intentionally executes
|
|
1351
|
+
// in the Task main worktree and must not enter this isolation path.
|
|
1352
|
+
if (item !== null
|
|
1353
|
+
&& task !== null
|
|
1354
|
+
&& task.projectBindings.length > 0
|
|
1355
|
+
&& item.assignee !== "leader"
|
|
1356
|
+
&& store.getWorkItemWorkspace(task.id, item.id) === null) {
|
|
1357
|
+
await workspaceCoordinator.isolateWorkItem(item.taskId, item.id);
|
|
1358
|
+
}
|
|
1359
|
+
if (item !== null && task !== null) {
|
|
1360
|
+
// For a new Group the preparer has already created deterministic
|
|
1361
|
+
// worktrees, but the owner record is adopted by dispatch's aggregate
|
|
1362
|
+
// transaction once its exact Lane ids exist.
|
|
1273
1363
|
}
|
|
1274
1364
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1365
|
+
let executionLaneWorkspaces;
|
|
1366
|
+
// Held only for a new Group's dispatch: the per-Project maintenance fence
|
|
1367
|
+
// spans Lane preparation and the adoption transaction, and projectPaths is
|
|
1368
|
+
// the under-fence snapshot the adoption CAS revalidates.
|
|
1369
|
+
let laneDispatchRelease;
|
|
1370
|
+
let laneDispatchProjectPaths;
|
|
1371
|
+
let workItemIntegrationProof;
|
|
1372
|
+
if (resolved[1] === "work" && resolved[2] === "accept") {
|
|
1373
|
+
const workItemId = resolved[3];
|
|
1374
|
+
if (workItemId !== undefined && !workItemId.startsWith("--")) {
|
|
1282
1375
|
try {
|
|
1283
|
-
|
|
1284
|
-
|
|
1376
|
+
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1377
|
+
workItemIntegrationProof = await new WorkItemChangeSetManager(store)
|
|
1378
|
+
.assertIntegrated(reference.taskId, reference.localId, optionValue(resolved, "--candidate")) ?? undefined;
|
|
1285
1379
|
}
|
|
1286
1380
|
catch (error) {
|
|
1287
1381
|
throw usageError(error instanceof Error ? error.message : String(error));
|
|
1288
1382
|
}
|
|
1289
1383
|
}
|
|
1290
1384
|
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
: cliWorkItemReference(workItemId, process.env);
|
|
1298
|
-
const item = reference === null
|
|
1299
|
-
? null
|
|
1300
|
-
: store.getWorkItem(reference.taskId, reference.localId);
|
|
1301
|
-
const task = item === null ? null : store.getTask(item.taskId);
|
|
1302
|
-
if (item !== null && task !== null) {
|
|
1303
|
-
// Authority and pure Lane-shape checks precede every physical or
|
|
1304
|
-
// durable workspace preparation performed for dispatch.
|
|
1305
|
-
assertTaskDeliveryAuthority(store, process.env, task.id);
|
|
1306
|
-
requireWorkItemAssignee(item);
|
|
1307
|
-
workItemDispatchLanePlan(resolved, store, item);
|
|
1308
|
-
}
|
|
1309
|
-
// A rejected Candidate starts a new execution iteration. Release every
|
|
1310
|
-
// terminal Lane Role runtime before preparing the new Lane workspaces;
|
|
1311
|
-
// durable AgentRuns, Groups, Candidates, and workspace owners remain intact.
|
|
1312
|
-
if (item?.status === "open"
|
|
1313
|
-
&& currentWorkItemExecutionGroup(item)?.lanes.every(({ disposition }) => disposition !== "open")) {
|
|
1314
|
-
await workspaceCoordinator.cleanupWorkItemRuntime(item.taskId, item.id);
|
|
1315
|
-
}
|
|
1316
|
-
// Every Task needs an authoritative runtime owner before dispatch. A
|
|
1317
|
-
// Gitless Task uses an empty Task-owned view; Project-backed WorkItems
|
|
1318
|
-
// additionally receive their isolated Develop owner below.
|
|
1319
|
-
if (item !== null && task !== null) {
|
|
1320
|
-
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1321
|
-
}
|
|
1322
|
-
// A Project-backed Worker WorkItem gets an isolated Develop owner before
|
|
1323
|
-
// its Lane is prepared. A Leader-owned WorkItem intentionally executes
|
|
1324
|
-
// in the Task main worktree and must not enter this isolation path.
|
|
1325
|
-
if (item !== null
|
|
1326
|
-
&& task !== null
|
|
1327
|
-
&& task.projectBindings.length > 0
|
|
1328
|
-
&& item.assignee !== "leader"
|
|
1329
|
-
&& store.getWorkItemWorkspace(task.id, item.id) === null) {
|
|
1330
|
-
await workspaceCoordinator.isolateWorkItem(item.taskId, item.id);
|
|
1331
|
-
}
|
|
1332
|
-
if (item !== null && task !== null) {
|
|
1333
|
-
// For a new Group the preparer has already created deterministic
|
|
1334
|
-
// worktrees, but the owner record is adopted by dispatch's aggregate
|
|
1335
|
-
// transaction once its exact Lane ids exist.
|
|
1385
|
+
let completionSummary;
|
|
1386
|
+
let completionPublishedTreeProof;
|
|
1387
|
+
if (resolved[1] === "base" && resolved[2] === "status") {
|
|
1388
|
+
const result = await runTaskBaseStatusCommand(resolved.slice(3), store);
|
|
1389
|
+
emit(result.output, false, result.data);
|
|
1390
|
+
return;
|
|
1336
1391
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
let workItemIntegrationProof;
|
|
1345
|
-
if (resolved[1] === "work" && resolved[2] === "accept") {
|
|
1346
|
-
const workItemId = resolved[3];
|
|
1347
|
-
if (workItemId !== undefined && !workItemId.startsWith("--")) {
|
|
1348
|
-
try {
|
|
1349
|
-
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1350
|
-
workItemIntegrationProof = await new WorkItemChangeSetManager(store)
|
|
1351
|
-
.assertIntegrated(reference.taskId, reference.localId, optionValue(resolved, "--candidate")) ?? undefined;
|
|
1352
|
-
}
|
|
1353
|
-
catch (error) {
|
|
1354
|
-
throw usageError(error instanceof Error ? error.message : String(error));
|
|
1355
|
-
}
|
|
1392
|
+
if (resolved[1] === "upstream") {
|
|
1393
|
+
const result = await runTaskUpstreamCommand(resolved.slice(2), store, home, {
|
|
1394
|
+
environment: process.env,
|
|
1395
|
+
jobPort: createControllerIntegrationJobPort(home, { environment: process.env })
|
|
1396
|
+
});
|
|
1397
|
+
emit(result.output, false, result.data);
|
|
1398
|
+
return;
|
|
1356
1399
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
}
|
|
1365
|
-
if (resolved[1] === "upstream") {
|
|
1366
|
-
const result = await runTaskUpstreamCommand(resolved.slice(2), store, home, {
|
|
1367
|
-
environment: process.env
|
|
1368
|
-
});
|
|
1369
|
-
emit(result.output, false, result.data);
|
|
1370
|
-
return;
|
|
1371
|
-
}
|
|
1372
|
-
if (resolved[1] === "complete" && resolved[2] !== undefined) {
|
|
1373
|
-
const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
|
|
1374
|
-
completionSummary = completionRequest.summary;
|
|
1375
|
-
const refreshRemote = resolved.includes("--refresh-remote");
|
|
1376
|
-
const completion = preflightTaskCompletion(resolved[2], store, {
|
|
1377
|
-
environment: process.env,
|
|
1378
|
-
...(taskFinalReviewContract === undefined
|
|
1379
|
-
? {}
|
|
1380
|
-
: { taskFinalReviewContract })
|
|
1381
|
-
}, completionRequest);
|
|
1382
|
-
if (!completion.completed && !completion.activeTaskReview) {
|
|
1383
|
-
// An explicit refresh must fetch the remote object graph before the
|
|
1384
|
-
// Publication proof resolves its exact commit. Without the flag the
|
|
1385
|
-
// command remains offline and preserves the existing proof-first path.
|
|
1386
|
-
const refreshedFreshness = refreshRemote
|
|
1387
|
-
? await inspectTaskBaseFreshness(resolved[2], store, { refresh: true })
|
|
1388
|
-
: undefined;
|
|
1389
|
-
if (completionRequest.acceptedPublishedTreePublicationId !== undefined) {
|
|
1390
|
-
completionPublishedTreeProof = await verifyTaskCompletionPublishedTree(completionRequest.taskId, completionRequest.acceptedPublishedTreePublicationId, store);
|
|
1391
|
-
}
|
|
1392
|
-
const freshness = refreshedFreshness
|
|
1393
|
-
?? await inspectTaskBaseFreshness(resolved[2], store);
|
|
1394
|
-
for (const warning of assertTaskBaseFreshnessForCompletion(freshness, {
|
|
1395
|
-
...(completionPublishedTreeProof === undefined
|
|
1400
|
+
if (resolved[1] === "complete" && resolved[2] !== undefined) {
|
|
1401
|
+
const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
|
|
1402
|
+
completionSummary = completionRequest.summary;
|
|
1403
|
+
const refreshRemote = resolved.includes("--refresh-remote");
|
|
1404
|
+
const completion = preflightTaskCompletion(resolved[2], store, {
|
|
1405
|
+
environment: process.env,
|
|
1406
|
+
...(taskFinalReviewContract === undefined
|
|
1396
1407
|
? {}
|
|
1397
|
-
: {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1408
|
+
: { taskFinalReviewContract })
|
|
1409
|
+
}, completionRequest);
|
|
1410
|
+
if (!completion.completed && !completion.activeTaskReview) {
|
|
1411
|
+
// An explicit refresh must fetch the remote object graph before the
|
|
1412
|
+
// Publication proof resolves its exact commit. Without the flag the
|
|
1413
|
+
// command remains offline and preserves the existing proof-first path.
|
|
1414
|
+
const refreshedFreshness = refreshRemote
|
|
1415
|
+
? await inspectTaskBaseFreshness(resolved[2], store, { refresh: true })
|
|
1416
|
+
: undefined;
|
|
1417
|
+
if (completionRequest.acceptedPublishedTreePublicationId !== undefined) {
|
|
1418
|
+
completionPublishedTreeProof = await verifyTaskCompletionPublishedTree(completionRequest.taskId, completionRequest.acceptedPublishedTreePublicationId, store);
|
|
1419
|
+
}
|
|
1420
|
+
const freshness = refreshedFreshness
|
|
1421
|
+
?? await inspectTaskBaseFreshness(resolved[2], store);
|
|
1422
|
+
for (const warning of assertTaskBaseFreshnessForCompletion(freshness, {
|
|
1423
|
+
...(completionPublishedTreeProof === undefined
|
|
1424
|
+
? {}
|
|
1425
|
+
: {
|
|
1426
|
+
acceptedPublishedTreeProjectId: completionPublishedTreeProof.projectId
|
|
1427
|
+
})
|
|
1428
|
+
})) {
|
|
1429
|
+
process.stderr.write(`Warning: ${warning}\n`);
|
|
1430
|
+
}
|
|
1431
|
+
// Keep completion offline by default. An explicit refresh is the only
|
|
1432
|
+
// path that may fetch and reconcile a moved remote baseline.
|
|
1433
|
+
if (refreshRemote) {
|
|
1434
|
+
const reconciled = await reconcileTaskRemoteBaselines(resolved[2], store, home, { environment: process.env, jobPort: createControllerIntegrationJobPort(home, { environment: process.env }) });
|
|
1435
|
+
if (reconciled.length > 0) {
|
|
1436
|
+
const updates = reconciled.map((entry) => (`${entry.projectId}: ${entry.fromCommit} -> ${entry.toCommit} `
|
|
1437
|
+
+ `(Integration ${entry.integrationId})`)).join("; ");
|
|
1438
|
+
throw usageError(`Remote baseline reconciliation advanced Task ${resolved[2]} (${updates}). `
|
|
1439
|
+
+ "The Task remains active so the Leader can inspect the new authoritative head, "
|
|
1440
|
+
+ "decide how prior Review evidence applies, and retry task complete.");
|
|
1441
|
+
}
|
|
1413
1442
|
}
|
|
1414
1443
|
}
|
|
1415
1444
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
releaseReviewHandoverLock = handoverLock.release;
|
|
1423
|
-
}
|
|
1424
|
-
try {
|
|
1425
|
-
const preparedLanes = await prepareExecutionLaneWorkspacesForCommand(resolved, store, workspacePreparer, process.env);
|
|
1426
|
-
if (preparedLanes !== undefined) {
|
|
1427
|
-
executionLaneWorkspaces = preparedLanes.workspaces;
|
|
1428
|
-
laneDispatchRelease = preparedLanes.release;
|
|
1429
|
-
laneDispatchProjectPaths = preparedLanes.projectPaths;
|
|
1430
|
-
}
|
|
1431
|
-
const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
|
|
1432
|
-
const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
|
|
1433
|
-
? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
|
|
1434
|
-
: archiveTaskReviewCandidate;
|
|
1435
|
-
const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
|
|
1436
|
-
// Read-only Host evidence for Session inspect and Role status/list. The command stays
|
|
1437
|
-
// synchronous over persisted state; this is the live reading it prints
|
|
1438
|
-
// beside those facts, prepared here because the Host is reached over a
|
|
1439
|
-
// socket.
|
|
1440
|
-
const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
|
|
1441
|
-
const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
|
|
1442
|
-
// Physical preparation may precede the durable write, but Task status,
|
|
1443
|
-
// workspace identity/cwd, and ManagedWorkspace ownership are adopted by
|
|
1444
|
-
// one transaction. A failed attempt therefore leaves the Task Draft and
|
|
1445
|
-
// owning no writable workspace.
|
|
1446
|
-
let taskWorkspaceActivation;
|
|
1447
|
-
if (resolved[1] === "activate" && resolved.length === 3) {
|
|
1448
|
-
const taskId = resolved[2];
|
|
1449
|
-
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1450
|
-
if (task !== null && task.status === "draft") {
|
|
1451
|
-
taskLocalActor(store, process.env, task.id);
|
|
1452
|
-
taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id, process.env);
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
const result = runTaskCommand(resolved.slice(1), store, {
|
|
1456
|
-
runtime,
|
|
1457
|
-
environment: process.env,
|
|
1458
|
-
yuiHome: home,
|
|
1459
|
-
...(taskFinalReviewContract === undefined
|
|
1460
|
-
? {}
|
|
1461
|
-
: { taskFinalReviewContract }),
|
|
1462
|
-
...(completionSummary === undefined ? {} : { completionSummary }),
|
|
1463
|
-
...(completionPublishedTreeProof === undefined
|
|
1464
|
-
? {}
|
|
1465
|
-
: { completionPublishedTreeProof }),
|
|
1466
|
-
...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
|
|
1467
|
-
...candidateSnapshots,
|
|
1468
|
-
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1469
|
-
...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
|
|
1470
|
-
...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
|
|
1471
|
-
...(actualTaskReviewCandidate === undefined
|
|
1472
|
-
? {}
|
|
1473
|
-
: { actualTaskReviewCandidate }),
|
|
1474
|
-
...(archiveRemoteDeliveryProof === undefined
|
|
1475
|
-
? {}
|
|
1476
|
-
: { archiveRemoteDeliveryProof }),
|
|
1477
|
-
...(deltaRecheckPreflight === undefined
|
|
1478
|
-
? {}
|
|
1479
|
-
: { deltaRecheckPreflight }),
|
|
1480
|
-
...(liveRunConfiguration === undefined
|
|
1481
|
-
? {}
|
|
1482
|
-
: { liveRunConfiguration }),
|
|
1483
|
-
...(liveHostObservations === undefined ? {} : { liveHostObservations }),
|
|
1484
|
-
...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
|
|
1485
|
-
...(validateAgentConfiguration === undefined
|
|
1486
|
-
? {}
|
|
1487
|
-
: { validateAgentConfiguration })
|
|
1488
|
-
});
|
|
1489
|
-
// The dispatch transaction has now adopted (or rejected) the prepared
|
|
1490
|
-
// Lane workspaces. Release the held fence so later output/review
|
|
1491
|
-
// handling can take the per-Project fence itself.
|
|
1492
|
-
if (laneDispatchRelease !== undefined) {
|
|
1493
|
-
laneDispatchRelease();
|
|
1494
|
-
laneDispatchRelease = undefined;
|
|
1445
|
+
let releaseReviewHandoverLock;
|
|
1446
|
+
if ((resolved[1] === "review"
|
|
1447
|
+
&& ["request", "retry"].includes(resolved[2] ?? ""))
|
|
1448
|
+
|| resolved[1] === "complete") {
|
|
1449
|
+
const handoverLock = acquireHandoverLock(home);
|
|
1450
|
+
releaseReviewHandoverLock = handoverLock.release;
|
|
1495
1451
|
}
|
|
1496
|
-
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1452
|
+
try {
|
|
1453
|
+
const preparedLanes = await prepareExecutionLaneWorkspacesForCommand(resolved, store, workspacePreparer, process.env);
|
|
1454
|
+
if (preparedLanes !== undefined) {
|
|
1455
|
+
executionLaneWorkspaces = preparedLanes.workspaces;
|
|
1456
|
+
laneDispatchRelease = preparedLanes.release;
|
|
1457
|
+
laneDispatchProjectPaths = preparedLanes.projectPaths;
|
|
1458
|
+
}
|
|
1459
|
+
const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
|
|
1460
|
+
const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
|
|
1461
|
+
? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
|
|
1462
|
+
: archiveTaskReviewCandidate;
|
|
1463
|
+
const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
|
|
1464
|
+
// Read-only Host evidence for Session inspect and Role status/list. The command stays
|
|
1465
|
+
// synchronous over persisted state; this is the live reading it prints
|
|
1466
|
+
// beside those facts, prepared here because the Host is reached over a
|
|
1467
|
+
// socket.
|
|
1468
|
+
const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
|
|
1469
|
+
const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
|
|
1470
|
+
// Physical preparation may precede the durable write, but Task status,
|
|
1471
|
+
// workspace identity/cwd, and ManagedWorkspace ownership are adopted by
|
|
1472
|
+
// one transaction. A failed attempt therefore leaves the Task Draft and
|
|
1473
|
+
// owning no writable workspace.
|
|
1474
|
+
let taskWorkspaceActivation;
|
|
1475
|
+
if (resolved[1] === "activate" && resolved.length === 3) {
|
|
1476
|
+
const taskId = resolved[2];
|
|
1477
|
+
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1478
|
+
if (task !== null && task.status === "draft") {
|
|
1479
|
+
taskLocalActor(store, process.env, task.id);
|
|
1480
|
+
taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id, process.env);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
const result = runTaskCommand(resolved.slice(1), store, {
|
|
1484
|
+
runtime,
|
|
1485
|
+
environment: process.env,
|
|
1486
|
+
yuiHome: home,
|
|
1487
|
+
...(taskFinalReviewContract === undefined
|
|
1488
|
+
? {}
|
|
1489
|
+
: { taskFinalReviewContract }),
|
|
1490
|
+
...(completionSummary === undefined ? {} : { completionSummary }),
|
|
1491
|
+
...(completionPublishedTreeProof === undefined
|
|
1492
|
+
? {}
|
|
1493
|
+
: { completionPublishedTreeProof }),
|
|
1494
|
+
...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
|
|
1495
|
+
...candidateSnapshots,
|
|
1496
|
+
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1497
|
+
...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
|
|
1498
|
+
...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
|
|
1499
|
+
...(actualTaskReviewCandidate === undefined
|
|
1500
|
+
? {}
|
|
1501
|
+
: { actualTaskReviewCandidate }),
|
|
1502
|
+
...(archiveRemoteDeliveryProof === undefined
|
|
1503
|
+
? {}
|
|
1504
|
+
: { archiveRemoteDeliveryProof }),
|
|
1505
|
+
...(deltaRecheckPreflight === undefined
|
|
1506
|
+
? {}
|
|
1507
|
+
: { deltaRecheckPreflight }),
|
|
1508
|
+
...(liveRunConfiguration === undefined
|
|
1509
|
+
? {}
|
|
1510
|
+
: { liveRunConfiguration }),
|
|
1511
|
+
...(liveHostObservations === undefined ? {} : { liveHostObservations }),
|
|
1512
|
+
...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
|
|
1513
|
+
...(validateAgentConfiguration === undefined
|
|
1514
|
+
? {}
|
|
1515
|
+
: { validateAgentConfiguration })
|
|
1516
|
+
});
|
|
1517
|
+
// The dispatch transaction has now adopted (or rejected) the prepared
|
|
1518
|
+
// Lane workspaces. Release the held fence so later output/review
|
|
1519
|
+
// handling can take the per-Project fence itself.
|
|
1520
|
+
if (laneDispatchRelease !== undefined) {
|
|
1521
|
+
laneDispatchRelease();
|
|
1522
|
+
laneDispatchRelease = undefined;
|
|
1523
|
+
}
|
|
1524
|
+
if (result.kind === "output") {
|
|
1525
|
+
const requestedRound = reviewRoundFromCommandData(result.data);
|
|
1526
|
+
const persistedRequestedRound = requestedRound === undefined
|
|
1527
|
+
? null
|
|
1528
|
+
: store.getReviewRound(requestedRound.taskId, requestedRound.id);
|
|
1529
|
+
let reviewOutput = "";
|
|
1530
|
+
let reviewData;
|
|
1531
|
+
const resumesReviewDispatch = (resolved[1] === "review" && resolved[2] === "request")
|
|
1532
|
+
|| (resolved[1] === "work" && resolved[2] === "review")
|
|
1533
|
+
|| (resolved[1] === "run" && resolved[2] === "retry");
|
|
1534
|
+
const reviewDispatchNeeded = requestedRound?.status === "pending"
|
|
1535
|
+
|| (requestedRound?.status === "running"
|
|
1536
|
+
&& resumesReviewDispatch
|
|
1537
|
+
&& persistedRequestedRound?.executionGroup?.lanes.some((lane) => (lane.disposition === "open"
|
|
1538
|
+
&& (lane.currentRunId === undefined
|
|
1539
|
+
|| store.getRun(requestedRound.taskId, lane.currentRunId)?.status === "failed"))) === true);
|
|
1540
|
+
if (reviewDispatchNeeded) {
|
|
1541
|
+
try {
|
|
1542
|
+
const workspace = requestedRound.status === "running"
|
|
1543
|
+
? store.getReviewRoundWorkspace(requestedRound.taskId, requestedRound.id)
|
|
1544
|
+
: await workspacePreparer.prepareReviewRoundWorkspace(requestedRound.taskId, requestedRound.id);
|
|
1545
|
+
if (workspace === null) {
|
|
1546
|
+
throw new Error(`ReviewRound workspace is not ready: ${requestedRound.id}.`);
|
|
1547
|
+
}
|
|
1548
|
+
const reviewLaneWorkspaces = await prepareReviewLaneWorkspaces(requestedRound.taskId, requestedRound.id, store, workspacePreparer);
|
|
1549
|
+
if (reviewLaneWorkspaces !== undefined) {
|
|
1550
|
+
executionLaneWorkspaces = reviewLaneWorkspaces;
|
|
1551
|
+
}
|
|
1552
|
+
const storedRound = store.getReviewRound(requestedRound.taskId, requestedRound.id);
|
|
1553
|
+
const freshTaskCandidate = storedRound?.scope === "task"
|
|
1554
|
+
? await snapshotActualTaskReviewCandidate(requestedRound.taskId, store, workspacePreparer)
|
|
1555
|
+
: undefined;
|
|
1556
|
+
const run = dispatchPreparedReviewRound(requestedRound.taskId, requestedRound.id, store, {
|
|
1557
|
+
runtime,
|
|
1558
|
+
environment: process.env,
|
|
1559
|
+
yuiHome: home,
|
|
1560
|
+
...(taskFinalReviewContract === undefined
|
|
1561
|
+
? {}
|
|
1562
|
+
: { taskFinalReviewContract }),
|
|
1563
|
+
...(freshTaskCandidate === undefined
|
|
1564
|
+
? {}
|
|
1565
|
+
: { actualTaskReviewCandidate: freshTaskCandidate }),
|
|
1566
|
+
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1567
|
+
...(storedRound?.deltaRecheck === undefined
|
|
1568
|
+
|| deltaRecheckPreflight === undefined
|
|
1569
|
+
? {}
|
|
1570
|
+
: {
|
|
1571
|
+
deltaRecheckDiff: deltaRecheckPreflight.diffByProject
|
|
1572
|
+
})
|
|
1573
|
+
});
|
|
1574
|
+
reviewOutput = run === null
|
|
1575
|
+
? `Review ${requestedRound.id} remains running\n`
|
|
1576
|
+
: `Review queued as ${requestedRound.id} (${run.id})\n`;
|
|
1577
|
+
reviewData = {
|
|
1578
|
+
reviewRequest: run === null
|
|
1579
|
+
? {
|
|
1580
|
+
kind: "running",
|
|
1581
|
+
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1582
|
+
activeReviewRoundId: requestedRound.id,
|
|
1583
|
+
retryable: false
|
|
1584
|
+
}
|
|
1585
|
+
: {
|
|
1586
|
+
kind: "started",
|
|
1587
|
+
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1588
|
+
reviewRoundId: requestedRound.id,
|
|
1589
|
+
runId: run.id
|
|
1590
|
+
},
|
|
1591
|
+
reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
|
|
1592
|
+
...(run === null ? {} : { reviewRun: run }),
|
|
1593
|
+
workspace
|
|
1594
|
+
};
|
|
1523
1595
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
: { actualTaskReviewCandidate: freshTaskCandidate }),
|
|
1538
|
-
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1539
|
-
...(storedRound?.deltaRecheck === undefined
|
|
1540
|
-
|| deltaRecheckPreflight === undefined
|
|
1541
|
-
? {}
|
|
1542
|
-
: {
|
|
1543
|
-
deltaRecheckDiff: deltaRecheckPreflight.diffByProject
|
|
1544
|
-
})
|
|
1545
|
-
});
|
|
1546
|
-
reviewOutput = run === null
|
|
1547
|
-
? `Review ${requestedRound.id} remains running\n`
|
|
1548
|
-
: `Review queued as ${requestedRound.id} (${run.id})\n`;
|
|
1549
|
-
reviewData = {
|
|
1550
|
-
reviewRequest: run === null
|
|
1551
|
-
? {
|
|
1552
|
-
kind: "running",
|
|
1596
|
+
catch (error) {
|
|
1597
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1598
|
+
await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
|
|
1599
|
+
const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
|
|
1600
|
+
runtime,
|
|
1601
|
+
environment: process.env,
|
|
1602
|
+
yuiHome: home
|
|
1603
|
+
});
|
|
1604
|
+
reviewOutput = `Review could not start: ${message}\n`
|
|
1605
|
+
+ "The failed ReviewRound was retained for Leader routing.\n";
|
|
1606
|
+
reviewData = {
|
|
1607
|
+
reviewRequest: {
|
|
1608
|
+
kind: "unavailable",
|
|
1553
1609
|
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
: {
|
|
1558
|
-
kind: "started",
|
|
1559
|
-
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1560
|
-
reviewRoundId: requestedRound.id,
|
|
1561
|
-
runId: run.id
|
|
1610
|
+
reviewRoundId: failed.id,
|
|
1611
|
+
reason: message,
|
|
1612
|
+
retryable: true
|
|
1562
1613
|
},
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
};
|
|
1614
|
+
reviewRound: failed
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1567
1617
|
}
|
|
1568
|
-
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
yuiHome: home
|
|
1575
|
-
});
|
|
1576
|
-
reviewOutput = `Review could not start: ${message}\n`
|
|
1577
|
-
+ "The failed ReviewRound was retained for Leader routing.\n";
|
|
1578
|
-
reviewData = {
|
|
1579
|
-
reviewRequest: {
|
|
1580
|
-
kind: "unavailable",
|
|
1581
|
-
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1582
|
-
reviewRoundId: failed.id,
|
|
1583
|
-
reason: message,
|
|
1584
|
-
retryable: true
|
|
1585
|
-
},
|
|
1586
|
-
reviewRound: failed
|
|
1587
|
-
};
|
|
1618
|
+
if (resolved[1] === "project" && resolved[2] === "add") {
|
|
1619
|
+
const taskId = resolved[3];
|
|
1620
|
+
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1621
|
+
if (task?.status === "active") {
|
|
1622
|
+
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1623
|
+
}
|
|
1588
1624
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1625
|
+
const controlData = result.data;
|
|
1626
|
+
const failureCode = controlData?.steer?.code ?? controlData?.interrupt?.code;
|
|
1627
|
+
if (failureCode !== undefined) {
|
|
1628
|
+
emitControlFailure(result.output, failureCode, result.data);
|
|
1629
|
+
return;
|
|
1595
1630
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
if (failureCode !== undefined) {
|
|
1600
|
-
emitControlFailure(result.output, failureCode, result.data);
|
|
1631
|
+
emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
|
|
1632
|
+
? result.data
|
|
1633
|
+
: { command: result.data, ...reviewData });
|
|
1601
1634
|
return;
|
|
1602
1635
|
}
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1636
|
+
if (jsonOutput && result.kind !== "session-stop"
|
|
1637
|
+
&& result.kind !== "input-steer" && result.kind !== "input-interrupt") {
|
|
1638
|
+
throw usageError("Task Role view/takeover requires an interactive terminal.");
|
|
1639
|
+
}
|
|
1640
|
+
if (result.kind === "session-stop") {
|
|
1641
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
1642
|
+
try {
|
|
1643
|
+
await runtime.stopExactTaskRoleSession({
|
|
1644
|
+
taskId: result.taskId,
|
|
1645
|
+
roleName: result.roleName,
|
|
1646
|
+
agentId: result.agentId,
|
|
1647
|
+
adapterId: result.adapterId,
|
|
1648
|
+
nativeSessionId: result.nativeSessionId,
|
|
1649
|
+
sessionUpdatedAt: result.sessionUpdatedAt
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
catch (error) {
|
|
1653
|
+
throw runtimeError(`Session stop was requested but physical Host cleanup did not complete: ${error instanceof Error ? error.message : String(error)}`);
|
|
1654
|
+
}
|
|
1655
|
+
emit(result.output, false, {
|
|
1616
1656
|
taskId: result.taskId,
|
|
1617
1657
|
roleName: result.roleName,
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
nativeSessionId: result.nativeSessionId,
|
|
1621
|
-
sessionUpdatedAt: result.sessionUpdatedAt
|
|
1658
|
+
stopped: true,
|
|
1659
|
+
reason: result.reason
|
|
1622
1660
|
});
|
|
1661
|
+
return;
|
|
1623
1662
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1663
|
+
if (result.kind === "view") {
|
|
1664
|
+
if (result.output !== undefined)
|
|
1665
|
+
emit(result.output);
|
|
1666
|
+
tmux.attachRole(result.taskId, result.roleName, "read-only");
|
|
1667
|
+
return;
|
|
1626
1668
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1669
|
+
if (result.kind === "input-steer") {
|
|
1670
|
+
// Core already persisted the Message and proved target + capability +
|
|
1671
|
+
// writer fence. This is the single live edge: one native steer of the
|
|
1672
|
+
// exact current Turn, with no retarget and no fallback to interrupt or
|
|
1673
|
+
// queue. Its durable settlement flows through the steer receipt fold.
|
|
1674
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
1675
|
+
let control;
|
|
1676
|
+
try {
|
|
1677
|
+
control = await sendAgentHostSteerControl({
|
|
1678
|
+
home,
|
|
1679
|
+
scope: "task",
|
|
1680
|
+
taskId: result.taskId,
|
|
1681
|
+
roleName: result.roleName,
|
|
1682
|
+
control: {
|
|
1683
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
1684
|
+
type: "steer-turn",
|
|
1685
|
+
nativeSessionId: result.target.nativeSessionId,
|
|
1686
|
+
nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
|
|
1687
|
+
authority: result.target.authority,
|
|
1688
|
+
run: { attemptId: result.receiptId, boundedText: result.text }
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1692
|
+
catch (error) {
|
|
1693
|
+
throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
|
|
1694
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1695
|
+
+ "The Message is retained and its outcome is recorded from the Host; whether the "
|
|
1696
|
+
+ "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
|
|
1697
|
+
+ "do not reissue the same input under a new requestId or a different action.");
|
|
1698
|
+
}
|
|
1699
|
+
const steer = foldSteerLiveReceipt(control);
|
|
1700
|
+
if (steer.state !== "steered") {
|
|
1701
|
+
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 });
|
|
1702
|
+
return;
|
|
1703
|
+
}
|
|
1704
|
+
emit(steerReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, result.messageId, steer), false, {
|
|
1705
|
+
taskId: result.taskId, roleName: result.roleName,
|
|
1706
|
+
messageId: result.messageId, steer
|
|
1662
1707
|
});
|
|
1708
|
+
return;
|
|
1663
1709
|
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1710
|
+
if (result.kind === "input-interrupt") {
|
|
1711
|
+
// The single live edge for interrupt: one native cancel of the exact
|
|
1712
|
+
// current Turn. Never a kill/restart/detach. Any then-handoff was
|
|
1713
|
+
// already claimed durably by Core and is delivered once by the ordinary
|
|
1714
|
+
// continuation path after this Turn reaches a proven terminal.
|
|
1715
|
+
await ensureFileTaskController(home, { environment: process.env });
|
|
1716
|
+
let control;
|
|
1717
|
+
try {
|
|
1718
|
+
control = await sendAgentHostCancelControl({
|
|
1719
|
+
home,
|
|
1720
|
+
scope: "task",
|
|
1721
|
+
taskId: result.taskId,
|
|
1722
|
+
roleName: result.roleName,
|
|
1723
|
+
control: {
|
|
1724
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
1725
|
+
type: "cancel",
|
|
1726
|
+
nativeOnly: true,
|
|
1727
|
+
nativeSessionId: result.target.nativeSessionId,
|
|
1728
|
+
// Native cancel names the exact original execution attempt it stops
|
|
1729
|
+
// (Host matches request.attemptId === activeRunAttemptId). That is
|
|
1730
|
+
// distinct from receiptId, the durable identity of this interrupt
|
|
1731
|
+
// control operation — never send the operation id as the turn id.
|
|
1732
|
+
attemptId: result.target.attemptId,
|
|
1733
|
+
authority: result.target.authority
|
|
1734
|
+
}
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
catch (error) {
|
|
1738
|
+
recordTaskInterruptResult(store, result.taskId, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
|
|
1739
|
+
throw runtimeError(`Interrupt ${result.receiptId} of ${result.taskId}/${result.roleName} did not complete: `
|
|
1740
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1741
|
+
+ "No process was killed; re-read the Session before retrying.");
|
|
1742
|
+
}
|
|
1743
|
+
const interrupt = foldInterruptLiveReceipt(control);
|
|
1744
|
+
recordTaskInterruptResult(store, result.taskId, result.receiptId, interrupt);
|
|
1745
|
+
if (interrupt.state !== "interrupt-requested") {
|
|
1746
|
+
emitControlFailure(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN"
|
|
1747
|
+
: interrupt.state === "interrupt-not-active" ? "NO_ACTIVE_TURN" : "INTERRUPT_REJECTED", { taskId: result.taskId, roleName: result.roleName, receiptId: result.receiptId, interrupt });
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
emit(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), false, {
|
|
1751
|
+
taskId: result.taskId, roleName: result.roleName,
|
|
1752
|
+
...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
|
|
1753
|
+
interrupt
|
|
1754
|
+
});
|
|
1674
1755
|
return;
|
|
1675
1756
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1757
|
+
const syncAuthority = async (authorityResult) => {
|
|
1758
|
+
let control;
|
|
1759
|
+
try {
|
|
1760
|
+
control = await sendAgentHostAuthorityControl({
|
|
1761
|
+
home,
|
|
1762
|
+
scope: "task",
|
|
1763
|
+
taskId: authorityResult.taskId,
|
|
1764
|
+
roleName: authorityResult.roleName,
|
|
1765
|
+
control: {
|
|
1766
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
1767
|
+
type: "set-authority",
|
|
1768
|
+
nativeSessionId: authorityResult.nativeSessionId,
|
|
1769
|
+
authority: authorityResult.authority
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
catch (error) {
|
|
1774
|
+
throw runtimeError(`Agent Host authority synchronization failed at epoch ${authorityResult.authority.epoch}: `
|
|
1775
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1776
|
+
+ `Durable authority is ${authorityResult.authority.owner}-owned; retry `
|
|
1777
|
+
+ `'yui task role release ${authorityResult.taskId} ${authorityResult.roleName}' `
|
|
1778
|
+
+ "to reconcile the Host.");
|
|
1779
|
+
}
|
|
1780
|
+
if (control.outcome !== "accepted"
|
|
1781
|
+
|| control.snapshot.nativeSessionId !== authorityResult.nativeSessionId
|
|
1782
|
+
|| control.snapshot.authorityEpoch !== authorityResult.authority.epoch
|
|
1783
|
+
|| control.snapshot.authorityOwner !== authorityResult.authority.owner
|
|
1784
|
+
|| control.snapshot.authorityHolderId !== authorityResult.authority.holderId) {
|
|
1785
|
+
throw runtimeError(`Agent Host did not accept Provider authority epoch ${authorityResult.authority.epoch}: `
|
|
1786
|
+
+ (control.snapshot.detail ?? control.outcome)
|
|
1787
|
+
+ `. Durable authority is ${authorityResult.authority.owner}-owned; `
|
|
1788
|
+
+ "retry 'yui task role release "
|
|
1789
|
+
+ `${authorityResult.taskId} ${authorityResult.roleName}' to reconcile the Host.`);
|
|
1790
|
+
}
|
|
1791
|
+
return control;
|
|
1792
|
+
};
|
|
1793
|
+
await syncAuthority(result);
|
|
1794
|
+
emit(result.output);
|
|
1795
|
+
if (result.action === "release") {
|
|
1796
|
+
runtime.notifyMailboxChanged({
|
|
1797
|
+
kind: "role",
|
|
1693
1798
|
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
|
-
}
|
|
1799
|
+
roleName: result.roleName
|
|
1707
1800
|
});
|
|
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
1801
|
return;
|
|
1721
1802
|
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
|
|
1725
|
-
interrupt
|
|
1726
|
-
});
|
|
1727
|
-
return;
|
|
1728
|
-
}
|
|
1729
|
-
const syncAuthority = async (authorityResult) => {
|
|
1730
|
-
let control;
|
|
1803
|
+
process.stdout.write("Provider input is now routed through the Agent Host PTY gateway. "
|
|
1804
|
+
+ "Use tmux detach (Ctrl-b d) to return authority to the Controller.\n");
|
|
1731
1805
|
try {
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1806
|
+
tmux.attachRole(result.taskId, result.roleName, "read-write");
|
|
1807
|
+
}
|
|
1808
|
+
finally {
|
|
1809
|
+
const currentTask = store.getTask(result.taskId);
|
|
1810
|
+
// Completing or retiring the Task from inside the takeover AgentRun owns
|
|
1811
|
+
// Provider shutdown and clears the live binding. Do not turn that
|
|
1812
|
+
// successful terminal transition into a failing best-effort release.
|
|
1813
|
+
if (currentTask?.status === "active") {
|
|
1814
|
+
const released = runTaskCommand(["role", "release", result.taskId, result.roleName], store, { runtime, environment: process.env, yuiHome: home });
|
|
1815
|
+
if (released.kind !== "authority" || released.action !== "release") {
|
|
1816
|
+
throw runtimeError("Provider authority release returned an invalid result.");
|
|
1742
1817
|
}
|
|
1743
|
-
|
|
1818
|
+
await syncAuthority(released);
|
|
1819
|
+
emit(released.output);
|
|
1820
|
+
runtime.notifyMailboxChanged({
|
|
1821
|
+
kind: "role",
|
|
1822
|
+
taskId: released.taskId,
|
|
1823
|
+
roleName: released.roleName
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1744
1826
|
}
|
|
1745
|
-
catch (error) {
|
|
1746
|
-
throw runtimeError(`Agent Host authority synchronization failed at epoch ${authorityResult.authority.epoch}: `
|
|
1747
|
-
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1748
|
-
+ `Durable authority is ${authorityResult.authority.owner}-owned; retry `
|
|
1749
|
-
+ `'yui task role release ${authorityResult.taskId} ${authorityResult.roleName}' `
|
|
1750
|
-
+ "to reconcile the Host.");
|
|
1751
|
-
}
|
|
1752
|
-
if (control.outcome !== "accepted"
|
|
1753
|
-
|| control.snapshot.nativeSessionId !== authorityResult.nativeSessionId
|
|
1754
|
-
|| control.snapshot.authorityEpoch !== authorityResult.authority.epoch
|
|
1755
|
-
|| control.snapshot.authorityOwner !== authorityResult.authority.owner
|
|
1756
|
-
|| control.snapshot.authorityHolderId !== authorityResult.authority.holderId) {
|
|
1757
|
-
throw runtimeError(`Agent Host did not accept Provider authority epoch ${authorityResult.authority.epoch}: `
|
|
1758
|
-
+ (control.snapshot.detail ?? control.outcome)
|
|
1759
|
-
+ `. Durable authority is ${authorityResult.authority.owner}-owned; `
|
|
1760
|
-
+ "retry 'yui task role release "
|
|
1761
|
-
+ `${authorityResult.taskId} ${authorityResult.roleName}' to reconcile the Host.`);
|
|
1762
|
-
}
|
|
1763
|
-
return control;
|
|
1764
|
-
};
|
|
1765
|
-
await syncAuthority(result);
|
|
1766
|
-
emit(result.output);
|
|
1767
|
-
if (result.action === "release") {
|
|
1768
|
-
runtime.notifyMailboxChanged({
|
|
1769
|
-
kind: "role",
|
|
1770
|
-
taskId: result.taskId,
|
|
1771
|
-
roleName: result.roleName
|
|
1772
|
-
});
|
|
1773
1827
|
return;
|
|
1774
1828
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1829
|
+
catch (error) {
|
|
1830
|
+
await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
|
|
1831
|
+
if (laneDispatchRelease !== undefined) {
|
|
1832
|
+
laneDispatchRelease();
|
|
1833
|
+
laneDispatchRelease = undefined;
|
|
1834
|
+
}
|
|
1835
|
+
throw error;
|
|
1779
1836
|
}
|
|
1780
1837
|
finally {
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
// successful terminal transition into a failing best-effort release.
|
|
1785
|
-
if (currentTask?.status === "active") {
|
|
1786
|
-
const released = runTaskCommand(["role", "release", result.taskId, result.roleName], store, { runtime, environment: process.env, yuiHome: home });
|
|
1787
|
-
if (released.kind !== "authority" || released.action !== "release") {
|
|
1788
|
-
throw runtimeError("Provider authority release returned an invalid result.");
|
|
1789
|
-
}
|
|
1790
|
-
await syncAuthority(released);
|
|
1791
|
-
emit(released.output);
|
|
1792
|
-
runtime.notifyMailboxChanged({
|
|
1793
|
-
kind: "role",
|
|
1794
|
-
taskId: released.taskId,
|
|
1795
|
-
roleName: released.roleName
|
|
1796
|
-
});
|
|
1838
|
+
if (releaseReviewHandoverLock !== undefined) {
|
|
1839
|
+
releaseReviewHandoverLock();
|
|
1840
|
+
releaseReviewHandoverLock = undefined;
|
|
1797
1841
|
}
|
|
1798
1842
|
}
|
|
1843
|
+
}
|
|
1844
|
+
if (resolved[0] === "jobs") {
|
|
1845
|
+
emit(runJobCommand(resolved.slice(1), store, { runtime }));
|
|
1799
1846
|
return;
|
|
1800
1847
|
}
|
|
1801
|
-
|
|
1802
|
-
await
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1848
|
+
if (resolved[0] === "job") {
|
|
1849
|
+
emit(await runDurableJobCommand(resolved.slice(1), {
|
|
1850
|
+
home,
|
|
1851
|
+
json: jsonOutput,
|
|
1852
|
+
environment: process.env,
|
|
1853
|
+
store
|
|
1854
|
+
}));
|
|
1855
|
+
return;
|
|
1808
1856
|
}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1857
|
+
if (resolved[0] === "telemetry") {
|
|
1858
|
+
emit(await runTelemetryCommand(resolved.slice(1), {
|
|
1859
|
+
home,
|
|
1860
|
+
json: jsonOutput,
|
|
1861
|
+
environment: process.env,
|
|
1862
|
+
store
|
|
1863
|
+
}));
|
|
1864
|
+
return;
|
|
1814
1865
|
}
|
|
1866
|
+
throw usageError(`Command is not connected to the current TaskStore command routing: ${resolved[0]}.`, renderCommandHelp(invocation.node, VERSION));
|
|
1815
1867
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
if (resolved[0] === "job") {
|
|
1821
|
-
emit(await runDurableJobCommand(resolved.slice(1), {
|
|
1822
|
-
home,
|
|
1823
|
-
json: jsonOutput,
|
|
1824
|
-
environment: process.env,
|
|
1825
|
-
store
|
|
1826
|
-
}));
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
if (resolved[0] === "telemetry") {
|
|
1830
|
-
emit(await runTelemetryCommand(resolved.slice(1), {
|
|
1831
|
-
home,
|
|
1832
|
-
json: jsonOutput,
|
|
1833
|
-
environment: process.env,
|
|
1834
|
-
store
|
|
1835
|
-
}));
|
|
1836
|
-
return;
|
|
1868
|
+
finally {
|
|
1869
|
+
await telemetry?.sink.close();
|
|
1837
1870
|
}
|
|
1838
|
-
throw usageError(`Command is not connected to the current TaskStore command routing: ${resolved[0]}.`, renderCommandHelp(invocation.node, VERSION));
|
|
1839
1871
|
}
|
|
1840
1872
|
function explicitReleaseActivationDriver() {
|
|
1841
1873
|
if (args.length !== 3
|
|
@@ -1862,7 +1894,7 @@ async function preflightManagedTaskControlPlane() {
|
|
|
1862
1894
|
const internalCallback = args[0] === "internal"
|
|
1863
1895
|
&& ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
|
|
1864
1896
|
const home = resolveYuiHome(process.env);
|
|
1865
|
-
|
|
1897
|
+
assertManagedSessionManifest(home, "task");
|
|
1866
1898
|
const diagnosticTarget = taskDiagnosticTarget(args);
|
|
1867
1899
|
const diagnostic = diagnosticTarget !== undefined
|
|
1868
1900
|
&& diagnosticTarget === process.env.YUI_TASK_ID;
|
|
@@ -1937,7 +1969,19 @@ async function preflightManagedGlobalControlPlane() {
|
|
|
1937
1969
|
await assertRuntimeCoherence({ actualHome: home }, {
|
|
1938
1970
|
checkController: !(expectedRoleKind === "operator" && operatorOfflineCommand(args))
|
|
1939
1971
|
});
|
|
1940
|
-
|
|
1972
|
+
const verifiedStore = openCurrentTaskStore(home);
|
|
1973
|
+
const internalCallback = args[0] === "internal"
|
|
1974
|
+
&& ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
|
|
1975
|
+
if (!internalCallback && !operatorOfflineCommand(args)) {
|
|
1976
|
+
try {
|
|
1977
|
+
requireManagedGlobalCaller(verifiedStore, process.env);
|
|
1978
|
+
}
|
|
1979
|
+
catch (error) {
|
|
1980
|
+
verifiedStore.close();
|
|
1981
|
+
throw error;
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
return { contract: undefined, verifiedStore };
|
|
1941
1985
|
}
|
|
1942
1986
|
/**
|
|
1943
1987
|
* The entry point the current invocation resolves to. Only its executable and
|
|
@@ -2159,7 +2203,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
2159
2203
|
&& args[3] !== undefined) {
|
|
2160
2204
|
const reference = cliTaskRecordReference(args[3], "reviewRound", environment);
|
|
2161
2205
|
const round = store.getReviewRound(reference.taskId, reference.localId);
|
|
2162
|
-
if (round !== null &&
|
|
2206
|
+
if (round !== null && round.scope === "task") {
|
|
2163
2207
|
taskId = reference.taskId;
|
|
2164
2208
|
}
|
|
2165
2209
|
}
|
|
@@ -2169,7 +2213,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
2169
2213
|
&& args[4] !== undefined) {
|
|
2170
2214
|
const reference = cliTaskRecordReference(args[4], "reviewRound", environment);
|
|
2171
2215
|
const round = store.getReviewRound(reference.taskId, reference.localId);
|
|
2172
|
-
if (round !== null &&
|
|
2216
|
+
if (round !== null && round.scope === "task") {
|
|
2173
2217
|
taskId = reference.taskId;
|
|
2174
2218
|
}
|
|
2175
2219
|
}
|
|
@@ -2183,7 +2227,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
2183
2227
|
: store.getReviewRound(reference.taskId, run.reviewRoundId);
|
|
2184
2228
|
if (run?.purpose === "review"
|
|
2185
2229
|
&& round !== null
|
|
2186
|
-
&&
|
|
2230
|
+
&& round.scope === "task") {
|
|
2187
2231
|
taskId = reference.taskId;
|
|
2188
2232
|
}
|
|
2189
2233
|
}
|
|
@@ -2440,7 +2484,7 @@ async function completionCommand(home, node) {
|
|
|
2440
2484
|
const store = openCurrentTaskStore(home);
|
|
2441
2485
|
const ioHandle = terminalIo();
|
|
2442
2486
|
try {
|
|
2443
|
-
const manager = new FileCompletionManager(store, process.env
|
|
2487
|
+
const manager = new FileCompletionManager(store, process.env);
|
|
2444
2488
|
emit(await runCompletionWizard(manager, ioHandle.io, shell === undefined ? {} : { shell }));
|
|
2445
2489
|
}
|
|
2446
2490
|
finally {
|
|
@@ -2521,7 +2565,7 @@ async function preflightAgentConfigurationMutation(commandArgs, store, catalogs)
|
|
|
2521
2565
|
const mutation = profileAgentConfigurationMutation(commandArgs, store)
|
|
2522
2566
|
?? taskRoleAgentConfigurationMutation(commandArgs, store);
|
|
2523
2567
|
if (mutation === undefined) {
|
|
2524
|
-
await
|
|
2568
|
+
await warmConfigurationMutationCatalog(commandArgs, store, catalogs);
|
|
2525
2569
|
return undefined;
|
|
2526
2570
|
}
|
|
2527
2571
|
const agent = store.getConfiguredAgent(mutation.agentId);
|
|
@@ -2562,7 +2606,7 @@ function taskRoleAgentConfigurationMutation(args, store) {
|
|
|
2562
2606
|
? previewTaskRoleAgentConfigurationMutation(args.slice(1), store)
|
|
2563
2607
|
: undefined;
|
|
2564
2608
|
}
|
|
2565
|
-
async function
|
|
2609
|
+
async function warmConfigurationMutationCatalog(commandArgs, store, catalogs) {
|
|
2566
2610
|
if (!hasModelOrEffortMutation(commandArgs))
|
|
2567
2611
|
return;
|
|
2568
2612
|
const agentId = configurationMutationAgentId(commandArgs, store);
|
|
@@ -2849,15 +2893,8 @@ function agentEnvironmentRefreshScope(previous, current, configured) {
|
|
|
2849
2893
|
nativeNames: [...new Set([...currentNative, ...previousOnlyNative])]
|
|
2850
2894
|
};
|
|
2851
2895
|
}
|
|
2852
|
-
export function cliIdentity(env) {
|
|
2853
|
-
return env.YUI_CLI_NAME === "yui-dev" ? "yui-dev" : "yui";
|
|
2854
|
-
}
|
|
2855
2896
|
function normalizeAliases(input) {
|
|
2856
2897
|
const normalized = [...input];
|
|
2857
|
-
// Existing immutable Session Manifests (through 0.15.8) name this entry.
|
|
2858
|
-
// Remove once those Sessions are retired; both names share one handler.
|
|
2859
|
-
if (normalized[0] === "task" && normalized[1] === "turn")
|
|
2860
|
-
normalized[1] = "run";
|
|
2861
2898
|
if (normalized.length === 1 && (normalized[0] === "-v" || normalized[0] === "--version")) {
|
|
2862
2899
|
return ["version"];
|
|
2863
2900
|
}
|