@zq-silk/yui 0.15.11 → 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 +11 -6
- package/ARCHITECTURE.zh-CN.md +8 -4
- package/README.md +13 -5
- package/dist/agentRun/agentRun.js +3 -0
- 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 +92 -48
- 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 +11 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +2 -2
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1303 -1235
- 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/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +78 -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 +45 -25
- 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 +355 -732
- 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 +20 -58
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +12 -3
- package/dist/commands/taskPublicationVerifyCommand.js +24 -40
- package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
- 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 +5 -3
- package/dist/context/taskCatalog.js +185 -0
- package/dist/context/taskContext.js +152 -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 +20 -24
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
- package/dist/controller/globalInputDelivery.js +13 -0
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/runtime.js +85 -43
- 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 +79 -90
- 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 +5 -10
- package/dist/message/globalProviderRetry.js +15 -0
- 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/executionAudit.js +19 -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/gitWorkspace.js +371 -105
- package/dist/repository/projectMaintenanceLock.js +98 -82
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +82 -144
- package/dist/repository/taskWorkspacePreparer.js +113 -47
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- 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/agentError.js +5 -3
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +42 -25
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +10 -43
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +19 -7
- package/dist/scheduler/leaderWakeupProcessor.js +24 -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 +49 -36
- package/dist/scheduler/taskObservabilityProjection.js +6 -45
- 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 +173 -1
- package/dist/storage/sqliteStore.js +172 -161
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +16 -7
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +5 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/archiveDiagnostics.js +1 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- 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 +92 -17
- package/dist/web/assets/client/components.js +55 -13
- package/dist/web/assets/client/i18n.js +72 -4
- package/dist/web/assets/client/taskSurface.js +6 -5
- package/dist/web/assets/client/view.js +35 -7
- package/dist/web/assets/shell.js +6 -0
- package/dist/web/assets/styles/layout.js +7 -0
- package/dist/web/webServer.js +12 -3
- package/dist/web/webSnapshot.js +14 -85
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +110 -50
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +42 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
- package/docs/managed-turn-and-session-runtime.md +121 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/plugin-sdk.md +4 -2
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/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 +223 -17
- package/docs/task-delivery.zh-CN.md +171 -14
- package/docs/task-discovery.md +101 -0
- package/docs/task-discovery.zh-CN.md +85 -0
- package/docs/testing/verification-levels.md +161 -9
- package/docs/testing/verification-levels.zh-CN.md +116 -9
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/execution.md +3 -2
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +24 -8
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +17 -2
- package/skills/yui-runtime/references/publication.md +26 -4
- package/skills/yui-runtime/references/recovery.md +71 -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 -376
- 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,120 +1,130 @@
|
|
|
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 { taskLocalActor, assertTaskDeliveryAuthority } from "./commands/taskActor.js";
|
|
52
|
-
import { saveArtifactCapability, readArtifactCapability, listArtifactsCapability } from "./artifacts/artifactCapability.js";
|
|
53
|
-
import { parseTaskExecutionStartRequest, parseTaskExecutionStopRequest, finalizeStoppedTaskExecution, startTaskExecutionCommand, stopTaskExecutionCommand } from "./commands/taskExecutionCommands.js";
|
|
54
|
-
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";
|
|
55
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";
|
|
56
51
|
import { runTaskOverlapCommand } from "./commands/taskOverlapCommands.js";
|
|
57
|
-
import {
|
|
52
|
+
import { runTaskPublicationAdoptCommand } from "./commands/taskPublicationAdoptCommand.js";
|
|
53
|
+
import { runTaskPublicationVerifyCommand } from "./commands/taskPublicationVerifyCommand.js";
|
|
54
|
+
import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
|
|
58
55
|
import { runTaskWorkspaceCommand } from "./commands/taskWorkspaceCommands.js";
|
|
56
|
+
import { runTelemetryCommand } from "./commands/telemetryCommands.js";
|
|
59
57
|
import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
|
|
60
|
-
import {
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
63
|
-
import {
|
|
64
|
-
import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
|
|
65
|
-
import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
|
|
66
|
-
import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
|
|
67
|
-
import { runTaskUpstreamCommand } from "./commands/taskUpstreamCommands.js";
|
|
68
|
-
import { assertTaskBaseFreshnessForCompletion, inspectTaskBaseFreshness } from "./repository/taskBaseFreshness.js";
|
|
69
|
-
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";
|
|
70
62
|
import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
|
|
71
|
-
import { callController, ControllerClientError } from "./core/controllerClient.js";
|
|
72
63
|
import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
|
|
64
|
+
import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
|
|
73
65
|
import { cleanControllerResource } from "./controller/resourceCleanupLinux.js";
|
|
74
66
|
import { scanControllerResourceInventory } from "./controller/resourceInventoryLinux.js";
|
|
75
|
-
import { runSessionNotifyCommand } from "./controller/sessionNotify.js";
|
|
76
|
-
import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
|
|
77
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";
|
|
78
71
|
import { buildDoctorReport, renderDoctor, runDoctorCommand } from "./doctor/doctor.js";
|
|
79
72
|
import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliError.js";
|
|
73
|
+
import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
|
|
80
74
|
import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
|
|
81
|
-
import {
|
|
82
|
-
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";
|
|
83
90
|
import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
|
|
84
91
|
import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
|
|
85
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";
|
|
86
104
|
import { inspectStorageSchema } from "./storage/storageSchema.js";
|
|
87
|
-
import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
|
|
88
105
|
import { resolveYuiHome } from "./storage/taskStore.js";
|
|
89
|
-
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";
|
|
90
109
|
import { resolveTaskRecordReference } from "./task/taskRecordReference.js";
|
|
91
|
-
import {
|
|
110
|
+
import { openSchedulerTelemetry } from "./telemetry/telemetryWiring.js";
|
|
92
111
|
import { NodeCommandExecutor } from "./tmux/commandExecutor.js";
|
|
93
112
|
import { TmuxManager } from "./tmux/tmuxManager.js";
|
|
94
|
-
import { WorkItemChangeSetManager } from "./workspace/workItemChangeSetManager.js";
|
|
95
|
-
import { workspaceProjectEntry } from "./worktree/managedWorkspace.js";
|
|
96
|
-
import { parseWebCommandOptions } from "./web/webServer.js";
|
|
97
|
-
import { AgentConfigurationCatalogService, validateAgentLaunchConfiguration } from "./executor/agentConfigurationCatalog.js";
|
|
98
|
-
import { resolveAgentProfileView } from "./profile/agentProfileRuntime.js";
|
|
99
|
-
import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
|
|
100
113
|
import { YUI_VERSION, yuiVersionIdentity } from "./version.js";
|
|
101
|
-
import {
|
|
102
|
-
import { assertRuntimeCoherence } from "./runtime/runtimeCoherence.js";
|
|
103
|
-
import { requireManagedTaskCaller, resolveManagedTaskReader } from "./runtime/managedCaller.js";
|
|
104
|
-
import { operatorOfflineCommand, taskDiagnosticTarget } from "./cli/managedDiagnostics.js";
|
|
105
|
-
import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
|
|
106
|
-
import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
|
|
107
|
-
import { resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractResolution.js";
|
|
108
|
-
import { assessDeltaRecheck } from "./review/deltaRecheck.js";
|
|
109
|
-
import { isCompletedTaskReviewEvidence } from "./review/reviewAcceptance.js";
|
|
110
|
-
import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
|
|
114
|
+
import { parseWebCommandOptions } from "./web/webServer.js";
|
|
111
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";
|
|
112
119
|
const VERSION = YUI_VERSION;
|
|
113
120
|
const taskFinalReviewInvocation = extractTaskFinalReviewRequest(process.argv.slice(2));
|
|
114
121
|
const rawArgs = [...taskFinalReviewInvocation.args];
|
|
115
122
|
const jsonOutput = rawArgs.includes("--json");
|
|
116
123
|
const args = normalizeAliases(jsonOutput ? rawArgs.filter((argument) => argument !== "--json") : rawArgs);
|
|
117
124
|
void main().catch((error) => {
|
|
125
|
+
if (error instanceof CleanupInspectionError) {
|
|
126
|
+
error = cleanupCliError(error, error.checks[0]?.resource ?? "workspace");
|
|
127
|
+
}
|
|
118
128
|
if (error instanceof CliError) {
|
|
119
129
|
const rendered = jsonOutput
|
|
120
130
|
? JSON.stringify({ ok: false, code: error.code, message: error.message, details: error.details })
|
|
@@ -200,6 +210,18 @@ export async function main() {
|
|
|
200
210
|
if (args[0] === "version")
|
|
201
211
|
throw usageError("Version usage: yui version");
|
|
202
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
|
+
}
|
|
203
225
|
if (args[0] === "update") {
|
|
204
226
|
if (jsonOutput)
|
|
205
227
|
throw usageError("Update does not support --json.");
|
|
@@ -450,38 +472,32 @@ export async function main() {
|
|
|
450
472
|
scope: options.scope,
|
|
451
473
|
environment: process.env
|
|
452
474
|
});
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const result = await callController(home, "controller.status", {}, { timeoutMs: 2_000 });
|
|
465
|
-
runtime = parseControllerRuntimeSnapshot(result, droppedEvents);
|
|
466
|
-
}
|
|
467
|
-
catch {
|
|
468
|
-
runtime = { source: "unsupported", droppedEvents };
|
|
469
|
-
}
|
|
470
|
-
const mismatch = summarizeDurablePhysicalMismatch(snapshot);
|
|
471
|
-
const identitySection = renderRuntimeIdentitySection({
|
|
472
|
-
build,
|
|
473
|
-
storage,
|
|
474
|
-
runtime,
|
|
475
|
-
mismatch,
|
|
476
|
-
inventoryRssBytes: snapshot.summary.rssBytes
|
|
477
|
-
});
|
|
478
|
-
emit(`${renderControllerResourceStatus(snapshot, options.verbose)}\n\n${identitySection}`, false, { ...snapshot, identity: { build, storage, runtime, mismatch } });
|
|
479
|
-
// The exact current storage contract fails closed on contradictions.
|
|
480
|
-
if (evaluateStorageHealth(storage).status === "fail")
|
|
481
|
-
process.exitCode = 5;
|
|
482
|
-
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);
|
|
483
486
|
}
|
|
484
|
-
|
|
487
|
+
catch {
|
|
488
|
+
runtime = { source: "unsupported", droppedEvents };
|
|
489
|
+
}
|
|
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;
|
|
485
501
|
return;
|
|
486
502
|
}
|
|
487
503
|
if (method === "cleanup") {
|
|
@@ -522,7 +538,10 @@ export async function main() {
|
|
|
522
538
|
throw usageError("Controller usage: yui controller status [--all] [--verbose] | "
|
|
523
539
|
+ "cleanup [--all] | stop | restart.");
|
|
524
540
|
}
|
|
525
|
-
|
|
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);
|
|
526
545
|
const controllerMethod = method;
|
|
527
546
|
const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
|
|
528
547
|
// A pre-fix updater cannot pass the owner environment variable to the
|
|
@@ -574,6 +593,17 @@ export async function main() {
|
|
|
574
593
|
emit("Cancelled.");
|
|
575
594
|
return;
|
|
576
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
|
+
}
|
|
577
607
|
const validateAgentConfiguration = await preflightAgentConfigurationMutation(resolved, store, catalogs);
|
|
578
608
|
const executor = new NodeCommandExecutor();
|
|
579
609
|
const tmux = new TmuxManager(resolveTmuxBin(store.getConfig().tmuxBin), executor, {
|
|
@@ -582,1244 +612,1262 @@ export async function main() {
|
|
|
582
612
|
terminalInput: process.stdin,
|
|
583
613
|
onWarning: (message) => process.stderr.write(`Warning: ${message}\n`)
|
|
584
614
|
});
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
const workspaceCoordinator = new TaskWorkspaceCoordinator(store, workspacePreparer, runtime);
|
|
596
|
-
if (resolved[0] === "web") {
|
|
597
|
-
if (managedInvocation || process.env.YUI_ROLE || process.env.YUI_NATIVE_SESSION_ID) {
|
|
598
|
-
throw usageError("The Web user ingress must be started from a local user terminal, not a managed Session.");
|
|
599
|
-
}
|
|
600
|
-
if (resolved.length === 2 && (resolved[1] === "--status" || resolved[1] === "--stop")) {
|
|
601
|
-
const status = await callController(home, "web.status", {});
|
|
602
|
-
if (resolved[1] === "--status")
|
|
603
|
-
emit(status ? `Yui web control room: ${status.url}\n` : "Web is not running.\n", false, status);
|
|
604
|
-
else {
|
|
605
|
-
if (status)
|
|
606
|
-
await callController(home, "web.stop", { id: status.id });
|
|
607
|
-
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`);
|
|
608
625
|
}
|
|
609
|
-
return;
|
|
610
|
-
}
|
|
611
|
-
if (jsonOutput)
|
|
612
|
-
throw usageError("Web start does not support --json; use --status.");
|
|
613
|
-
const options = parseWebCommandOptions(resolved.slice(1));
|
|
614
|
-
const id = randomUUID();
|
|
615
|
-
await callController(home, "web.start", { ...options, id });
|
|
616
|
-
const displayHost = options.host === "::1" ? "[::1]" : options.host;
|
|
617
|
-
process.stdout.write(`Yui web control room: http://${displayHost}:${options.port}\n`);
|
|
618
|
-
await new Promise((resolve) => {
|
|
619
|
-
const stop = () => {
|
|
620
|
-
process.off("SIGINT", stop);
|
|
621
|
-
process.off("SIGTERM", stop);
|
|
622
|
-
resolve();
|
|
623
|
-
};
|
|
624
|
-
process.once("SIGINT", stop);
|
|
625
|
-
process.once("SIGTERM", stop);
|
|
626
626
|
});
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (CONFIG_DOMAINS.includes(domain ?? "")) {
|
|
642
|
-
const configDomain = domain;
|
|
643
|
-
const domainArgs = resolved.slice(2);
|
|
644
|
-
const result = runConfigCommand(configDomain, domainArgs, store);
|
|
645
|
-
if (domainArgs[0] === "set"
|
|
646
|
-
&& domainArgs[1] === "reconciliation-interval-seconds") {
|
|
647
|
-
const refresh = await refreshRunningFileTaskControllerConfiguration(home, { environment: process.env });
|
|
648
|
-
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
|
+
}
|
|
649
641
|
return;
|
|
650
642
|
}
|
|
651
|
-
|
|
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 });
|
|
652
660
|
return;
|
|
653
661
|
}
|
|
654
|
-
if (
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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);
|
|
668
684
|
return;
|
|
669
685
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
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"
|
|
679
705
|
? store.getConfiguredAgent(affectedAgentId)
|
|
680
706
|
: null;
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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);
|
|
691
727
|
return;
|
|
692
728
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const result = runProfileCommand(resolved.slice(2), store, () => new Date(), validateAgentConfiguration === undefined ? {} : { validateAgentConfiguration });
|
|
698
|
-
emit(result.output, false, result.data);
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
if (domain === "role") {
|
|
702
|
-
const result = runGlobalRoleCommand(resolved.slice(2), store, roleOptions);
|
|
703
|
-
if (typeof result !== "string") {
|
|
704
|
-
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;
|
|
705
733
|
}
|
|
706
|
-
|
|
707
|
-
|
|
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 ?? ""}.`);
|
|
708
743
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
if (resolved[0] === "project") {
|
|
712
|
-
const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
|
|
713
|
-
emit(result.output, false, result.data);
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
if (resolved[0] === "session") {
|
|
717
|
-
if (resolved[1] === "stop") {
|
|
718
|
-
const options = parseSessionStopOptions(resolved.slice(2));
|
|
719
|
-
const result = await runSessionStopCommand({
|
|
720
|
-
options,
|
|
721
|
-
runtime: {
|
|
722
|
-
beginMaintenance: () => acquireHandoverLock(home),
|
|
723
|
-
snapshot: () => ({
|
|
724
|
-
candidates: schedulerStore.listRuntimeSessionCandidates(),
|
|
725
|
-
dormant: schedulerStore.listDormantRuntimeOwners()
|
|
726
|
-
}),
|
|
727
|
-
drainController: () => runtime.drainController(),
|
|
728
|
-
stopController: () => stopFileTaskController(home, {
|
|
729
|
-
environment: process.env
|
|
730
|
-
}),
|
|
731
|
-
startController: async () => {
|
|
732
|
-
await ensureFileTaskController(home, { environment: process.env });
|
|
733
|
-
},
|
|
734
|
-
stopDormantSession: (candidate) => runtime.stopDormantSession(candidate)
|
|
735
|
-
},
|
|
736
|
-
environment: process.env
|
|
737
|
-
});
|
|
738
|
-
process.exitCode = result.exitCode;
|
|
744
|
+
if (resolved[0] === "project") {
|
|
745
|
+
const result = await runProjectCommand(resolved.slice(1), store, { environment: process.env });
|
|
739
746
|
emit(result.output, false, result.data);
|
|
740
747
|
return;
|
|
741
748
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
if (resolved[0] === "role") {
|
|
767
|
-
// decision-3 §7 CLI grammar: the durable Global input actions are top-level
|
|
768
|
-
// `role message queue|steer <global-role> …` and `role interrupt
|
|
769
|
-
// <global-role> …`, distinct from `config role …` (desired configuration)
|
|
770
|
-
// and `session …` (native session lifecycle). Core persists the durable
|
|
771
|
-
// Global-owned Message, proves owner/target/capability/writer-fence from
|
|
772
|
-
// durable state, and returns either a string disposition (queued,
|
|
773
|
-
// idempotent-replay, or an explicit not-steered/not-interrupted failure with
|
|
774
|
-
// its exact code) or a resolved live intent. The CLI performs at most one
|
|
775
|
-
// native edge with scope "global" and taskId omitted — the same shared
|
|
776
|
-
// resolver and transport as a Task Role, never a fabricated Task.
|
|
777
|
-
let globalInputFailure;
|
|
778
|
-
const roleOptions = {
|
|
779
|
-
yuiHome: home,
|
|
780
|
-
env: process.env,
|
|
781
|
-
jsonOutput,
|
|
782
|
-
onInputFailure: failure => { globalInputFailure = failure; }
|
|
783
|
-
};
|
|
784
|
-
const result = runGlobalRoleCommand(resolved.slice(1), store, roleOptions);
|
|
785
|
-
if (resolved[1] === "message" && resolved[2] === "queue" && resolved[3] !== undefined) {
|
|
786
|
-
await callController(home, "scheduler.signal", {
|
|
787
|
-
key: `global-role:${encodeURIComponent(resolved[3])}`
|
|
788
|
-
}).catch(() => { });
|
|
789
|
-
}
|
|
790
|
-
if (typeof result === "string") {
|
|
791
|
-
if (globalInputFailure !== undefined) {
|
|
792
|
-
emitControlFailure(globalInputFailure.detail, globalInputFailure.code, globalInputFailure.data);
|
|
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);
|
|
793
773
|
return;
|
|
794
774
|
}
|
|
795
|
-
|
|
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");
|
|
796
797
|
return;
|
|
797
798
|
}
|
|
798
|
-
if (
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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, {
|
|
809
871
|
roleName: result.roleName,
|
|
810
|
-
|
|
811
|
-
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
812
|
-
type: "steer-turn",
|
|
813
|
-
nativeSessionId: result.target.nativeSessionId,
|
|
814
|
-
nativeTurnId: result.target.nativeTurnId ?? result.target.attemptId,
|
|
815
|
-
authority: result.target.authority,
|
|
816
|
-
run: { attemptId: result.receiptId, boundedText: result.text }
|
|
817
|
-
}
|
|
872
|
+
messageId: result.messageId, steer
|
|
818
873
|
});
|
|
874
|
+
return;
|
|
819
875
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
|
824
918
|
});
|
|
825
|
-
throw runtimeError(`Steer message ${result.messageId} is saved but the native steer did not complete: `
|
|
826
|
-
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
827
|
-
+ "The Message is retained and its outcome is recorded from the Host; whether the "
|
|
828
|
-
+ "Provider accepted it may be delivery-unknown. Re-read the Session before acting; "
|
|
829
|
-
+ "do not reissue the same input under a new requestId or a different action.");
|
|
830
|
-
}
|
|
831
|
-
const steer = foldSteerLiveReceipt(control);
|
|
832
|
-
recordGlobalSteerResult(store, result.roleName, result.messageId, steer);
|
|
833
|
-
if (steer.state !== "steered") {
|
|
834
|
-
emitControlFailure(steerReceiptOutput(result.output, result.roleName, result.messageId, steer), steer.state === "steer-unknown" ? "DELIVERY_UNKNOWN" : "STEER_NOT_DELIVERED", { roleName: result.roleName, messageId: result.messageId, steer });
|
|
835
919
|
return;
|
|
836
920
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
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.");
|
|
842
927
|
}
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
try {
|
|
852
|
-
control = await sendAgentHostCancelControl({
|
|
853
|
-
home,
|
|
854
|
-
scope: "global",
|
|
855
|
-
roleName: result.roleName,
|
|
856
|
-
control: {
|
|
857
|
-
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
858
|
-
type: "cancel",
|
|
859
|
-
nativeOnly: true,
|
|
860
|
-
nativeSessionId: result.target.nativeSessionId,
|
|
861
|
-
attemptId: result.target.attemptId,
|
|
862
|
-
authority: result.target.authority
|
|
863
|
-
}
|
|
864
|
-
});
|
|
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;
|
|
865
936
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
870
|
-
});
|
|
871
|
-
throw runtimeError(`Interrupt of global role ${result.roleName} did not complete: `
|
|
872
|
-
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
873
|
-
+ "No process was killed; re-read the Session before retrying.");
|
|
874
|
-
}
|
|
875
|
-
const interrupt = foldInterruptLiveReceipt(control);
|
|
876
|
-
recordGlobalInterruptResult(store, result.roleName, result.receiptId, interrupt);
|
|
877
|
-
if (interrupt.state !== "interrupt-requested") {
|
|
878
|
-
emitControlFailure(interruptReceiptOutput(result.output, result.roleName, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN" : "INTERRUPT_NOT_DELIVERED", { roleName: result.roleName, interrupt });
|
|
937
|
+
const result = runOperatorCommand(resolved.slice(1), store, { runtime, environment: process.env });
|
|
938
|
+
if (result.kind === "output") {
|
|
939
|
+
emit(result.output, false, result.data);
|
|
879
940
|
return;
|
|
880
941
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
});
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
if (result.kind !== "enter") {
|
|
889
|
-
throw new Error("Role command returned an invalid control result.");
|
|
890
|
-
}
|
|
891
|
-
// A top-level `role` command never enters a runtime Session; that is
|
|
892
|
-
// `session enter`.
|
|
893
|
-
throw usageError("Use 'yui session enter <role>' to attach to a Global Role.");
|
|
894
|
-
}
|
|
895
|
-
if (resolved[0] === "operator") {
|
|
896
|
-
if (resolved[1] === "enter") {
|
|
897
|
-
if (resolved.length !== 2)
|
|
898
|
-
throw usageError("Operator enter usage: yui operator enter.");
|
|
899
|
-
await ensureFileTaskController(home, { environment: process.env });
|
|
900
|
-
await runtime.prepareGlobalRoleEnter("operator");
|
|
901
|
-
tmux.attachRole("operator", "operator", "auto");
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
const result = runOperatorCommand(resolved.slice(1), store, { runtime, environment: process.env });
|
|
905
|
-
if (result.kind === "output") {
|
|
906
|
-
emit(result.output, false, result.data);
|
|
942
|
+
if (result.kind !== "session") {
|
|
943
|
+
throw new Error("Operator command returned an invalid control result.");
|
|
944
|
+
}
|
|
945
|
+
await executeOperatorSessionControl(result, home, store, runtime, tmux, catalogs);
|
|
907
946
|
return;
|
|
908
947
|
}
|
|
909
|
-
if (
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
// their save/read/list are asynchronous and handled here rather than in
|
|
919
|
-
// the synchronous runTaskCommand chain. Save commits exactly one path and
|
|
920
|
-
// returns a self-certifying commit-pinned reference; read pins to a commit
|
|
921
|
-
// for frozen evidence; list is an ordinary current read.
|
|
922
|
-
const action = resolved[2];
|
|
923
|
-
const taskId = resolved[3];
|
|
924
|
-
const usage = "Usage: yui task artifact list <task> | read <task> <relative-path> [<commit>] | "
|
|
925
|
-
+ "save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]";
|
|
926
|
-
if (taskId === undefined || action === undefined || !["list", "read", "save"].includes(action)) {
|
|
927
|
-
throw usageError(usage);
|
|
928
|
-
}
|
|
929
|
-
if (process.env.YUI_SESSION_SCOPE === "task" && process.env.YUI_TASK_ID !== taskId) {
|
|
930
|
-
throw usageError("Artifact is outside the managed Task scope.");
|
|
931
|
-
}
|
|
932
|
-
if (store.getTask(taskId) === null)
|
|
933
|
-
throw usageError(`Task not found: ${taskId}.`);
|
|
934
|
-
let data;
|
|
935
|
-
if (action === "list") {
|
|
936
|
-
if (resolved.length !== 4)
|
|
937
|
-
throw usageError(usage);
|
|
938
|
-
data = await listArtifactsCapability(home, taskId);
|
|
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;
|
|
939
957
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
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)) {
|
|
944
969
|
throw usageError(usage);
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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;
|
|
948
1023
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
960
|
-
const value = rest[index + 1];
|
|
961
|
-
if (rest[index] === "--message" && value !== undefined) {
|
|
962
|
-
message = value;
|
|
963
|
-
index += 1;
|
|
964
|
-
continue;
|
|
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);
|
|
965
1034
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
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}`);
|
|
970
1038
|
}
|
|
971
|
-
|
|
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;
|
|
972
1062
|
}
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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 })
|
|
977
1069
|
});
|
|
1070
|
+
emit(result.output, false, result.data);
|
|
1071
|
+
return;
|
|
978
1072
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
const result =
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
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.`);
|
|
992
1118
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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>.");
|
|
996
1131
|
}
|
|
997
|
-
|
|
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 });
|
|
998
1135
|
return;
|
|
999
1136
|
}
|
|
1000
|
-
if (resolved[2] === "
|
|
1001
|
-
|
|
1002
|
-
const
|
|
1003
|
-
if (
|
|
1004
|
-
throw usageError(
|
|
1005
|
-
// Reject managed Task callers before inspecting or starting runtime resources.
|
|
1006
|
-
if (taskLocalActor(store, process.env, taskId) === "leader") {
|
|
1007
|
-
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>.");
|
|
1008
1142
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
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}.`);
|
|
1011
1147
|
}
|
|
1012
|
-
|
|
1013
|
-
const result = startTaskExecutionCommand(taskId, store, { environment: process.env });
|
|
1014
|
-
// Idempotent start is also a reliable kick: if an earlier caller
|
|
1015
|
-
// committed the gate but lost its Controller acknowledgement, retrying
|
|
1016
|
-
// start re-signals the same durable wake without creating another one.
|
|
1017
|
-
await runtime.notifyMailboxChanged?.({ kind: "role", taskId, roleName: "leader" });
|
|
1018
|
-
emit(result.output, false, result);
|
|
1148
|
+
emit(`Cleaned ReviewRound workspace ${reference.taskId}/${reference.localId} (${removal})\n`, false, { reviewRoundRef: reference, workspace: { removal } });
|
|
1019
1149
|
return;
|
|
1020
1150
|
}
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
if (resolved[1] === "publication" && resolved[2] === "verify") {
|
|
1037
|
-
const result = await runTaskPublicationVerifyCommand(resolved.slice(3), store, {
|
|
1038
|
-
verifiers: {
|
|
1039
|
-
github: createGitHubCliPublicationVerifier({
|
|
1040
|
-
environmentPath: process.env.PATH
|
|
1041
|
-
}),
|
|
1042
|
-
gitlab: createGitLabCliPublicationVerifier({
|
|
1043
|
-
environmentPath: process.env.PATH
|
|
1044
|
-
})
|
|
1045
|
-
},
|
|
1046
|
-
candidateForTask: async (taskId) => {
|
|
1047
|
-
const status = store.getTask(taskId)?.status;
|
|
1048
|
-
return status === "active" || status === "cancelled"
|
|
1049
|
-
? snapshotActualTaskReviewCandidate(taskId, store, workspacePreparer)
|
|
1050
|
-
: null;
|
|
1051
|
-
},
|
|
1052
|
-
environment: process.env
|
|
1053
|
-
});
|
|
1054
|
-
emit(result.output, false, result.data);
|
|
1055
|
-
return;
|
|
1056
|
-
}
|
|
1057
|
-
if (resolved[1] === "overlap") {
|
|
1058
|
-
const result = await runTaskOverlapCommand(resolved.slice(2), store);
|
|
1059
|
-
emit(result.output, false, result.data);
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
if (resolved[1] === "workflow"
|
|
1063
|
-
&& (resolved[2] === "run" || resolved[2] === "resume")) {
|
|
1064
|
-
const result = await runWorkflowCommandAsync(resolved.slice(2), store, {
|
|
1065
|
-
environment: process.env,
|
|
1066
|
-
yuiHome: home,
|
|
1067
|
-
ports: createReleaseWorkflowPorts({
|
|
1068
|
-
home,
|
|
1069
|
-
updatePorts: createUpdatePorts(process.env),
|
|
1070
|
-
projectStore: store
|
|
1071
|
-
})
|
|
1072
|
-
});
|
|
1073
|
-
if (result.kind !== "output") {
|
|
1074
|
-
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;
|
|
1075
1166
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1114
|
-
assertTaskDeliveryAuthority(store, process.env, reference.taskId);
|
|
1115
|
-
const changeSets = await new WorkItemChangeSetManager(store).capture(reference.taskId, reference.localId, taskFinalReviewContract === undefined
|
|
1116
|
-
? {}
|
|
1117
|
-
: { taskFinalReviewContract });
|
|
1118
|
-
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
1119
|
-
emit(changeSets.length === 0
|
|
1120
|
-
? `WorkItem workspace has no changes to capture: ${qualified}\n`
|
|
1121
|
-
: `Captured ChangeSets ${changeSets.map(({ id }) => id).join(", ")} from ${qualified}\n`, false, { workItemRef: reference, changeSets });
|
|
1122
|
-
return;
|
|
1123
|
-
}
|
|
1124
|
-
if (resolved[1] === "work" && resolved[2] === "cleanup") {
|
|
1125
|
-
const workItemId = resolved[3];
|
|
1126
|
-
const disposition = resolved[4];
|
|
1127
|
-
if (workItemId === undefined
|
|
1128
|
-
|| !["--runtime-only", "--integrated", "--abandon"].includes(disposition ?? "")
|
|
1129
|
-
|| resolved.length !== 5) {
|
|
1130
|
-
throw usageError("Task work cleanup usage: yui task work cleanup <task>/<work> "
|
|
1131
|
-
+ "(--runtime-only|--integrated|--abandon).");
|
|
1132
|
-
}
|
|
1133
|
-
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1134
|
-
const qualified = `${reference.taskId}/${reference.localId}`;
|
|
1135
|
-
assertTaskDeliveryAuthority(store, process.env, reference.taskId);
|
|
1136
|
-
if (disposition === "--runtime-only") {
|
|
1137
|
-
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;
|
|
1138
1204
|
try {
|
|
1139
|
-
|
|
1205
|
+
removal = await workspaceCoordinator.cleanupWorkItem(reference.taskId, reference.localId, cleanedAs);
|
|
1140
1206
|
}
|
|
1141
1207
|
catch (error) {
|
|
1142
1208
|
throw cleanupCliError(error, `work-item:${qualified}`);
|
|
1143
1209
|
}
|
|
1144
|
-
|
|
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, {
|
|
1145
1214
|
workItem: store.getWorkItem(reference.taskId, reference.localId),
|
|
1146
|
-
|
|
1147
|
-
worktree: { retained: true }
|
|
1215
|
+
worktree: { removal, disposition: cleanedAs }
|
|
1148
1216
|
});
|
|
1149
1217
|
return;
|
|
1150
1218
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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>.");
|
|
1155
1224
|
}
|
|
1156
|
-
|
|
1157
|
-
|
|
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}.`);
|
|
1158
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;
|
|
1159
1235
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
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;
|
|
1169
1248
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
&& resolved[3] === "cleanup") {
|
|
1178
|
-
const reviewRef = resolved[4];
|
|
1179
|
-
if (reviewRef === undefined || resolved.length !== 5) {
|
|
1180
|
-
throw usageError("Task work review cleanup usage: yui task work review cleanup <task>/<review-round>.");
|
|
1181
|
-
}
|
|
1182
|
-
const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
|
|
1183
|
-
const removal = await workspaceCoordinator.cleanupReviewRound(reference.taskId, reference.localId);
|
|
1184
|
-
if (removal === "dirty") {
|
|
1185
|
-
throw usageError(`ReviewRound worktree is dirty and was retained: ${reference.taskId}/${reference.localId}.`);
|
|
1186
|
-
}
|
|
1187
|
-
emit(`Cleaned ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
|
|
1188
|
-
reviewRound: store.getReviewRound(reference.taskId, reference.localId),
|
|
1189
|
-
worktree: { removal }
|
|
1190
|
-
});
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
if (resolved[1] === "work" && resolved[2] === "review"
|
|
1194
|
-
&& resolved[3] === "preserve") {
|
|
1195
|
-
const reviewRef = resolved[4];
|
|
1196
|
-
if (reviewRef === undefined || resolved.length !== 5) {
|
|
1197
|
-
throw usageError("Task work review preserve usage: yui task work review preserve <task>/<review-round>.");
|
|
1198
|
-
}
|
|
1199
|
-
const reference = cliTaskRecordReference(reviewRef, "reviewRound", process.env);
|
|
1200
|
-
const round = preserveReviewRoundWorkspace(reference.taskId, reference.localId, store, { runtime, environment: process.env, yuiHome: home });
|
|
1201
|
-
emit(`Preserved ReviewRound worktree ${reference.taskId}/${reference.localId}\n`, false, {
|
|
1202
|
-
reviewRound: round
|
|
1203
|
-
});
|
|
1204
|
-
return;
|
|
1205
|
-
}
|
|
1206
|
-
let archiveRemoteDeliveryProof;
|
|
1207
|
-
let archiveTaskReviewCandidate;
|
|
1208
|
-
if (resolved[1] === "archive") {
|
|
1209
|
-
const { taskId, disposition, force } = validateTaskArchiveRequest(resolved.slice(2), store, {
|
|
1210
|
-
runtime,
|
|
1211
|
-
environment: process.env,
|
|
1212
|
-
yuiHome: home
|
|
1213
|
-
});
|
|
1214
|
-
const task = store.getTask(taskId);
|
|
1215
|
-
if (task === null)
|
|
1216
|
-
throw new Error(`Task disappeared after archive validation: ${taskId}.`);
|
|
1217
|
-
if (force || task.status === "archived") {
|
|
1218
|
-
// Archive admission and mandatory audit commit before any fallible
|
|
1219
|
-
// filesystem/provider work. Repeats report facts, never replay cleanup.
|
|
1220
|
-
const admitted = runTaskCommand(resolved.slice(1), store, {
|
|
1221
|
-
runtime, environment: process.env, yuiHome: home
|
|
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
|
|
1222
1256
|
});
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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;
|
|
1274
|
+
}
|
|
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);
|
|
1280
|
+
}
|
|
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
|
+
}
|
|
1226
1301
|
}
|
|
1227
|
-
const current = store.getTask(taskId);
|
|
1228
|
-
const archive = taskArchiveDiagnostics(store, current);
|
|
1229
|
-
emit(`Archived task ${taskId}\n${renderArchiveDiagnostics(archive)}`, false, { task: current, ...archive });
|
|
1230
|
-
return;
|
|
1231
1302
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
await new WorkItemChangeSetManager(store).assertIntegrated(task.id, item.id);
|
|
1246
|
-
}
|
|
1247
|
-
catch (error) {
|
|
1248
|
-
throw usageError(error instanceof Error ? error.message : String(error));
|
|
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
|
+
}
|
|
1249
1316
|
}
|
|
1250
1317
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
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);
|
|
1348
|
+
}
|
|
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);
|
|
1254
1358
|
}
|
|
1255
|
-
if (
|
|
1256
|
-
|
|
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.
|
|
1257
1363
|
}
|
|
1258
1364
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
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("--")) {
|
|
1266
1375
|
try {
|
|
1267
|
-
|
|
1268
|
-
|
|
1376
|
+
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1377
|
+
workItemIntegrationProof = await new WorkItemChangeSetManager(store)
|
|
1378
|
+
.assertIntegrated(reference.taskId, reference.localId, optionValue(resolved, "--candidate")) ?? undefined;
|
|
1269
1379
|
}
|
|
1270
1380
|
catch (error) {
|
|
1271
1381
|
throw usageError(error instanceof Error ? error.message : String(error));
|
|
1272
1382
|
}
|
|
1273
1383
|
}
|
|
1274
1384
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
: cliWorkItemReference(workItemId, process.env);
|
|
1282
|
-
const item = reference === null
|
|
1283
|
-
? null
|
|
1284
|
-
: store.getWorkItem(reference.taskId, reference.localId);
|
|
1285
|
-
const task = item === null ? null : store.getTask(item.taskId);
|
|
1286
|
-
if (item !== null && task !== null) {
|
|
1287
|
-
// Authority and pure Lane-shape checks precede every physical or
|
|
1288
|
-
// durable workspace preparation performed for dispatch.
|
|
1289
|
-
assertTaskDeliveryAuthority(store, process.env, task.id);
|
|
1290
|
-
requireWorkItemAssignee(item);
|
|
1291
|
-
workItemDispatchLanePlan(resolved, store, item);
|
|
1292
|
-
}
|
|
1293
|
-
// A rejected Candidate starts a new execution iteration. Release every
|
|
1294
|
-
// terminal Lane Role runtime before preparing the new Lane workspaces;
|
|
1295
|
-
// durable AgentRuns, Groups, Candidates, and workspace owners remain intact.
|
|
1296
|
-
if (item?.status === "open"
|
|
1297
|
-
&& currentWorkItemExecutionGroup(item)?.lanes.every(({ disposition }) => disposition !== "open")) {
|
|
1298
|
-
await workspaceCoordinator.cleanupWorkItemRuntime(item.taskId, item.id);
|
|
1299
|
-
}
|
|
1300
|
-
// Every Task needs an authoritative runtime owner before dispatch. A
|
|
1301
|
-
// Gitless Task uses an empty Task-owned view; Project-backed WorkItems
|
|
1302
|
-
// additionally receive their isolated Develop owner below.
|
|
1303
|
-
if (item !== null && task !== null) {
|
|
1304
|
-
await workspacePreparer.prepareTaskWorkspace(task.id);
|
|
1305
|
-
}
|
|
1306
|
-
// A Project-backed Worker WorkItem gets an isolated Develop owner before
|
|
1307
|
-
// its Lane is prepared. A Leader-owned WorkItem intentionally executes
|
|
1308
|
-
// in the Task main worktree and must not enter this isolation path.
|
|
1309
|
-
if (item !== null
|
|
1310
|
-
&& task !== null
|
|
1311
|
-
&& task.projectBindings.length > 0
|
|
1312
|
-
&& item.assignee !== "leader"
|
|
1313
|
-
&& store.getWorkItemWorkspace(task.id, item.id) === null) {
|
|
1314
|
-
await workspaceCoordinator.isolateWorkItem(item.taskId, item.id);
|
|
1315
|
-
}
|
|
1316
|
-
if (item !== null && task !== null) {
|
|
1317
|
-
// For a new Group the preparer has already created deterministic
|
|
1318
|
-
// worktrees, but the owner record is adopted by dispatch's aggregate
|
|
1319
|
-
// 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;
|
|
1320
1391
|
}
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
let workItemIntegrationProof;
|
|
1329
|
-
if (resolved[1] === "work" && resolved[2] === "accept") {
|
|
1330
|
-
const workItemId = resolved[3];
|
|
1331
|
-
if (workItemId !== undefined && !workItemId.startsWith("--")) {
|
|
1332
|
-
try {
|
|
1333
|
-
const reference = cliWorkItemReference(workItemId, process.env);
|
|
1334
|
-
workItemIntegrationProof = await new WorkItemChangeSetManager(store)
|
|
1335
|
-
.assertIntegrated(reference.taskId, reference.localId, optionValue(resolved, "--candidate")) ?? undefined;
|
|
1336
|
-
}
|
|
1337
|
-
catch (error) {
|
|
1338
|
-
throw usageError(error instanceof Error ? error.message : String(error));
|
|
1339
|
-
}
|
|
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;
|
|
1340
1399
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
}
|
|
1349
|
-
if (resolved[1] === "upstream") {
|
|
1350
|
-
const result = await runTaskUpstreamCommand(resolved.slice(2), store, home, {
|
|
1351
|
-
environment: process.env
|
|
1352
|
-
});
|
|
1353
|
-
emit(result.output, false, result.data);
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
if (resolved[1] === "complete" && resolved[2] !== undefined) {
|
|
1357
|
-
const completionRequest = parseTaskCompletionRequest(resolved.slice(2));
|
|
1358
|
-
completionSummary = completionRequest.summary;
|
|
1359
|
-
const refreshRemote = resolved.includes("--refresh-remote");
|
|
1360
|
-
const completion = preflightTaskCompletion(resolved[2], store, {
|
|
1361
|
-
environment: process.env,
|
|
1362
|
-
...(taskFinalReviewContract === undefined
|
|
1363
|
-
? {}
|
|
1364
|
-
: { taskFinalReviewContract })
|
|
1365
|
-
}, completionRequest);
|
|
1366
|
-
if (!completion.completed && !completion.activeTaskReview) {
|
|
1367
|
-
// An explicit refresh must fetch the remote object graph before the
|
|
1368
|
-
// Publication proof resolves its exact commit. Without the flag the
|
|
1369
|
-
// command remains offline and preserves the existing proof-first path.
|
|
1370
|
-
const refreshedFreshness = refreshRemote
|
|
1371
|
-
? await inspectTaskBaseFreshness(resolved[2], store, { refresh: true })
|
|
1372
|
-
: undefined;
|
|
1373
|
-
if (completionRequest.acceptedPublishedTreePublicationId !== undefined) {
|
|
1374
|
-
completionPublishedTreeProof = await verifyTaskCompletionPublishedTree(completionRequest.taskId, completionRequest.acceptedPublishedTreePublicationId, store);
|
|
1375
|
-
}
|
|
1376
|
-
const freshness = refreshedFreshness
|
|
1377
|
-
?? await inspectTaskBaseFreshness(resolved[2], store);
|
|
1378
|
-
for (const warning of assertTaskBaseFreshnessForCompletion(freshness, {
|
|
1379
|
-
...(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
|
|
1380
1407
|
? {}
|
|
1381
|
-
: {
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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
|
+
}
|
|
1397
1442
|
}
|
|
1398
1443
|
}
|
|
1399
1444
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
releaseReviewHandoverLock = handoverLock.release;
|
|
1407
|
-
}
|
|
1408
|
-
try {
|
|
1409
|
-
const preparedLanes = await prepareExecutionLaneWorkspacesForCommand(resolved, store, workspacePreparer, process.env);
|
|
1410
|
-
if (preparedLanes !== undefined) {
|
|
1411
|
-
executionLaneWorkspaces = preparedLanes.workspaces;
|
|
1412
|
-
laneDispatchRelease = preparedLanes.release;
|
|
1413
|
-
laneDispatchProjectPaths = preparedLanes.projectPaths;
|
|
1414
|
-
}
|
|
1415
|
-
const candidateSnapshots = await candidateSnapshotForTaskCommand(resolved, store, workspacePreparer, process.env, taskFinalReviewContract);
|
|
1416
|
-
const actualTaskReviewCandidate = archiveRemoteDeliveryProof === undefined
|
|
1417
|
-
? await actualTaskReviewCandidateForTaskCommand(resolved, store, workspacePreparer, process.env)
|
|
1418
|
-
: archiveTaskReviewCandidate;
|
|
1419
|
-
const deltaRecheckPreflight = await deltaRecheckPreflightForTaskCommand(resolved.slice(1), store, actualTaskReviewCandidate);
|
|
1420
|
-
// Read-only Host evidence for Session inspect and Role status/list. The command stays
|
|
1421
|
-
// synchronous over persisted state; this is the live reading it prints
|
|
1422
|
-
// beside those facts, prepared here because the Host is reached over a
|
|
1423
|
-
// socket.
|
|
1424
|
-
const liveHostObservations = await liveHostObservationsForTaskCommand(resolved, store, home);
|
|
1425
|
-
const liveRunConfiguration = runConfigurationForHostObservation(liveHostObservations?.[resolved[5] ?? ""]);
|
|
1426
|
-
// Physical preparation may precede the durable write, but Task status,
|
|
1427
|
-
// workspace identity/cwd, and ManagedWorkspace ownership are adopted by
|
|
1428
|
-
// one transaction. A failed attempt therefore leaves the Task Draft and
|
|
1429
|
-
// owning no writable workspace.
|
|
1430
|
-
let taskWorkspaceActivation;
|
|
1431
|
-
if (resolved[1] === "activate" && resolved.length === 3) {
|
|
1432
|
-
const taskId = resolved[2];
|
|
1433
|
-
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1434
|
-
if (task !== null && task.status === "draft") {
|
|
1435
|
-
taskLocalActor(store, process.env, task.id);
|
|
1436
|
-
taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id, process.env);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
const result = runTaskCommand(resolved.slice(1), store, {
|
|
1440
|
-
runtime,
|
|
1441
|
-
environment: process.env,
|
|
1442
|
-
yuiHome: home,
|
|
1443
|
-
...(taskFinalReviewContract === undefined
|
|
1444
|
-
? {}
|
|
1445
|
-
: { taskFinalReviewContract }),
|
|
1446
|
-
...(completionSummary === undefined ? {} : { completionSummary }),
|
|
1447
|
-
...(completionPublishedTreeProof === undefined
|
|
1448
|
-
? {}
|
|
1449
|
-
: { completionPublishedTreeProof }),
|
|
1450
|
-
...(workItemIntegrationProof === undefined ? {} : { workItemIntegrationProof }),
|
|
1451
|
-
...candidateSnapshots,
|
|
1452
|
-
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1453
|
-
...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
|
|
1454
|
-
...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
|
|
1455
|
-
...(actualTaskReviewCandidate === undefined
|
|
1456
|
-
? {}
|
|
1457
|
-
: { actualTaskReviewCandidate }),
|
|
1458
|
-
...(archiveRemoteDeliveryProof === undefined
|
|
1459
|
-
? {}
|
|
1460
|
-
: { archiveRemoteDeliveryProof }),
|
|
1461
|
-
...(deltaRecheckPreflight === undefined
|
|
1462
|
-
? {}
|
|
1463
|
-
: { deltaRecheckPreflight }),
|
|
1464
|
-
...(liveRunConfiguration === undefined
|
|
1465
|
-
? {}
|
|
1466
|
-
: { liveRunConfiguration }),
|
|
1467
|
-
...(liveHostObservations === undefined ? {} : { liveHostObservations }),
|
|
1468
|
-
...(taskRetirementProof === undefined ? {} : { taskRetirementProof }),
|
|
1469
|
-
...(validateAgentConfiguration === undefined
|
|
1470
|
-
? {}
|
|
1471
|
-
: { validateAgentConfiguration })
|
|
1472
|
-
});
|
|
1473
|
-
// The dispatch transaction has now adopted (or rejected) the prepared
|
|
1474
|
-
// Lane workspaces. Release the held fence so later output/review
|
|
1475
|
-
// handling can take the per-Project fence itself.
|
|
1476
|
-
if (laneDispatchRelease !== undefined) {
|
|
1477
|
-
laneDispatchRelease();
|
|
1478
|
-
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;
|
|
1479
1451
|
}
|
|
1480
|
-
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
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
|
+
};
|
|
1507
1595
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
: { actualTaskReviewCandidate: freshTaskCandidate }),
|
|
1522
|
-
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1523
|
-
...(storedRound?.deltaRecheck === undefined
|
|
1524
|
-
|| deltaRecheckPreflight === undefined
|
|
1525
|
-
? {}
|
|
1526
|
-
: {
|
|
1527
|
-
deltaRecheckDiff: deltaRecheckPreflight.diffByProject
|
|
1528
|
-
})
|
|
1529
|
-
});
|
|
1530
|
-
reviewOutput = run === null
|
|
1531
|
-
? `Review ${requestedRound.id} remains running\n`
|
|
1532
|
-
: `Review queued as ${requestedRound.id} (${run.id})\n`;
|
|
1533
|
-
reviewData = {
|
|
1534
|
-
reviewRequest: run === null
|
|
1535
|
-
? {
|
|
1536
|
-
kind: "running",
|
|
1537
|
-
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1538
|
-
activeReviewRoundId: requestedRound.id,
|
|
1539
|
-
retryable: false
|
|
1540
|
-
}
|
|
1541
|
-
: {
|
|
1542
|
-
kind: "started",
|
|
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",
|
|
1543
1609
|
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1544
|
-
reviewRoundId:
|
|
1545
|
-
|
|
1610
|
+
reviewRoundId: failed.id,
|
|
1611
|
+
reason: message,
|
|
1612
|
+
retryable: true
|
|
1546
1613
|
},
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
};
|
|
1614
|
+
reviewRound: failed
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1551
1617
|
}
|
|
1552
|
-
|
|
1553
|
-
const
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
yuiHome: home
|
|
1559
|
-
});
|
|
1560
|
-
reviewOutput = `Review could not start: ${message}\n`
|
|
1561
|
-
+ "The failed ReviewRound was retained for Leader routing.\n";
|
|
1562
|
-
reviewData = {
|
|
1563
|
-
reviewRequest: {
|
|
1564
|
-
kind: "unavailable",
|
|
1565
|
-
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1566
|
-
reviewRoundId: failed.id,
|
|
1567
|
-
reason: message,
|
|
1568
|
-
retryable: true
|
|
1569
|
-
},
|
|
1570
|
-
reviewRound: failed
|
|
1571
|
-
};
|
|
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
|
+
}
|
|
1572
1624
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
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;
|
|
1579
1630
|
}
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
if (failureCode !== undefined) {
|
|
1584
|
-
emitControlFailure(result.output, failureCode, result.data);
|
|
1631
|
+
emit(`${result.output}${reviewOutput}`, false, reviewData === undefined
|
|
1632
|
+
? result.data
|
|
1633
|
+
: { command: result.data, ...reviewData });
|
|
1585
1634
|
return;
|
|
1586
1635
|
}
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
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, {
|
|
1600
1656
|
taskId: result.taskId,
|
|
1601
1657
|
roleName: result.roleName,
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
nativeSessionId: result.nativeSessionId,
|
|
1605
|
-
sessionUpdatedAt: result.sessionUpdatedAt
|
|
1658
|
+
stopped: true,
|
|
1659
|
+
reason: result.reason
|
|
1606
1660
|
});
|
|
1661
|
+
return;
|
|
1607
1662
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
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;
|
|
1610
1668
|
}
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
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
|
|
1646
1707
|
});
|
|
1708
|
+
return;
|
|
1647
1709
|
}
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
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
|
+
});
|
|
1658
1755
|
return;
|
|
1659
1756
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
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",
|
|
1677
1798
|
taskId: result.taskId,
|
|
1678
|
-
roleName: result.roleName
|
|
1679
|
-
control: {
|
|
1680
|
-
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
1681
|
-
type: "cancel",
|
|
1682
|
-
nativeOnly: true,
|
|
1683
|
-
nativeSessionId: result.target.nativeSessionId,
|
|
1684
|
-
// Native cancel names the exact original execution attempt it stops
|
|
1685
|
-
// (Host matches request.attemptId === activeRunAttemptId). That is
|
|
1686
|
-
// distinct from receiptId, the durable identity of this interrupt
|
|
1687
|
-
// control operation — never send the operation id as the turn id.
|
|
1688
|
-
attemptId: result.target.attemptId,
|
|
1689
|
-
authority: result.target.authority
|
|
1690
|
-
}
|
|
1799
|
+
roleName: result.roleName
|
|
1691
1800
|
});
|
|
1692
|
-
}
|
|
1693
|
-
catch (error) {
|
|
1694
|
-
recordTaskInterruptResult(store, result.taskId, result.receiptId, { state: "interrupt-unknown", outcome: "cancel-requested" });
|
|
1695
|
-
throw runtimeError(`Interrupt ${result.receiptId} of ${result.taskId}/${result.roleName} did not complete: `
|
|
1696
|
-
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1697
|
-
+ "No process was killed; re-read the Session before retrying.");
|
|
1698
|
-
}
|
|
1699
|
-
const interrupt = foldInterruptLiveReceipt(control);
|
|
1700
|
-
recordTaskInterruptResult(store, result.taskId, result.receiptId, interrupt);
|
|
1701
|
-
if (interrupt.state !== "interrupt-requested") {
|
|
1702
|
-
emitControlFailure(interruptReceiptOutput(result.output, `${result.taskId}/${result.roleName}`, interrupt), interrupt.state === "interrupt-unknown" ? "DELIVERY_UNKNOWN"
|
|
1703
|
-
: interrupt.state === "interrupt-not-active" ? "NO_ACTIVE_TURN" : "INTERRUPT_REJECTED", { taskId: result.taskId, roleName: result.roleName, receiptId: result.receiptId, interrupt });
|
|
1704
1801
|
return;
|
|
1705
1802
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
...(result.thenMessageId === undefined ? {} : { thenMessageId: result.thenMessageId }),
|
|
1709
|
-
interrupt
|
|
1710
|
-
});
|
|
1711
|
-
return;
|
|
1712
|
-
}
|
|
1713
|
-
const syncAuthority = async (authorityResult) => {
|
|
1714
|
-
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");
|
|
1715
1805
|
try {
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
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.");
|
|
1726
1817
|
}
|
|
1727
|
-
|
|
1818
|
+
await syncAuthority(released);
|
|
1819
|
+
emit(released.output);
|
|
1820
|
+
runtime.notifyMailboxChanged({
|
|
1821
|
+
kind: "role",
|
|
1822
|
+
taskId: released.taskId,
|
|
1823
|
+
roleName: released.roleName
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1728
1826
|
}
|
|
1729
|
-
catch (error) {
|
|
1730
|
-
throw runtimeError(`Agent Host authority synchronization failed at epoch ${authorityResult.authority.epoch}: `
|
|
1731
|
-
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
1732
|
-
+ `Durable authority is ${authorityResult.authority.owner}-owned; retry `
|
|
1733
|
-
+ `'yui task role release ${authorityResult.taskId} ${authorityResult.roleName}' `
|
|
1734
|
-
+ "to reconcile the Host.");
|
|
1735
|
-
}
|
|
1736
|
-
if (control.outcome !== "accepted"
|
|
1737
|
-
|| control.snapshot.nativeSessionId !== authorityResult.nativeSessionId
|
|
1738
|
-
|| control.snapshot.authorityEpoch !== authorityResult.authority.epoch
|
|
1739
|
-
|| control.snapshot.authorityOwner !== authorityResult.authority.owner
|
|
1740
|
-
|| control.snapshot.authorityHolderId !== authorityResult.authority.holderId) {
|
|
1741
|
-
throw runtimeError(`Agent Host did not accept Provider authority epoch ${authorityResult.authority.epoch}: `
|
|
1742
|
-
+ (control.snapshot.detail ?? control.outcome)
|
|
1743
|
-
+ `. Durable authority is ${authorityResult.authority.owner}-owned; `
|
|
1744
|
-
+ "retry 'yui task role release "
|
|
1745
|
-
+ `${authorityResult.taskId} ${authorityResult.roleName}' to reconcile the Host.`);
|
|
1746
|
-
}
|
|
1747
|
-
return control;
|
|
1748
|
-
};
|
|
1749
|
-
await syncAuthority(result);
|
|
1750
|
-
emit(result.output);
|
|
1751
|
-
if (result.action === "release") {
|
|
1752
|
-
runtime.notifyMailboxChanged({
|
|
1753
|
-
kind: "role",
|
|
1754
|
-
taskId: result.taskId,
|
|
1755
|
-
roleName: result.roleName
|
|
1756
|
-
});
|
|
1757
1827
|
return;
|
|
1758
1828
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1829
|
+
catch (error) {
|
|
1830
|
+
await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
|
|
1831
|
+
if (laneDispatchRelease !== undefined) {
|
|
1832
|
+
laneDispatchRelease();
|
|
1833
|
+
laneDispatchRelease = undefined;
|
|
1834
|
+
}
|
|
1835
|
+
throw error;
|
|
1763
1836
|
}
|
|
1764
1837
|
finally {
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
// successful terminal transition into a failing best-effort release.
|
|
1769
|
-
if (currentTask?.status === "active") {
|
|
1770
|
-
const released = runTaskCommand(["role", "release", result.taskId, result.roleName], store, { runtime, environment: process.env, yuiHome: home });
|
|
1771
|
-
if (released.kind !== "authority" || released.action !== "release") {
|
|
1772
|
-
throw runtimeError("Provider authority release returned an invalid result.");
|
|
1773
|
-
}
|
|
1774
|
-
await syncAuthority(released);
|
|
1775
|
-
emit(released.output);
|
|
1776
|
-
runtime.notifyMailboxChanged({
|
|
1777
|
-
kind: "role",
|
|
1778
|
-
taskId: released.taskId,
|
|
1779
|
-
roleName: released.roleName
|
|
1780
|
-
});
|
|
1838
|
+
if (releaseReviewHandoverLock !== undefined) {
|
|
1839
|
+
releaseReviewHandoverLock();
|
|
1840
|
+
releaseReviewHandoverLock = undefined;
|
|
1781
1841
|
}
|
|
1782
1842
|
}
|
|
1843
|
+
}
|
|
1844
|
+
if (resolved[0] === "jobs") {
|
|
1845
|
+
emit(runJobCommand(resolved.slice(1), store, { runtime }));
|
|
1783
1846
|
return;
|
|
1784
1847
|
}
|
|
1785
|
-
|
|
1786
|
-
await
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
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;
|
|
1792
1856
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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;
|
|
1798
1865
|
}
|
|
1866
|
+
throw usageError(`Command is not connected to the current TaskStore command routing: ${resolved[0]}.`, renderCommandHelp(invocation.node, VERSION));
|
|
1799
1867
|
}
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
return;
|
|
1803
|
-
}
|
|
1804
|
-
if (resolved[0] === "job") {
|
|
1805
|
-
emit(await runDurableJobCommand(resolved.slice(1), {
|
|
1806
|
-
home,
|
|
1807
|
-
json: jsonOutput,
|
|
1808
|
-
environment: process.env,
|
|
1809
|
-
store
|
|
1810
|
-
}));
|
|
1811
|
-
return;
|
|
1812
|
-
}
|
|
1813
|
-
if (resolved[0] === "telemetry") {
|
|
1814
|
-
emit(await runTelemetryCommand(resolved.slice(1), {
|
|
1815
|
-
home,
|
|
1816
|
-
json: jsonOutput,
|
|
1817
|
-
environment: process.env,
|
|
1818
|
-
store
|
|
1819
|
-
}));
|
|
1820
|
-
return;
|
|
1868
|
+
finally {
|
|
1869
|
+
await telemetry?.sink.close();
|
|
1821
1870
|
}
|
|
1822
|
-
throw usageError(`Command is not connected to the current TaskStore command routing: ${resolved[0]}.`, renderCommandHelp(invocation.node, VERSION));
|
|
1823
1871
|
}
|
|
1824
1872
|
function explicitReleaseActivationDriver() {
|
|
1825
1873
|
if (args.length !== 3
|
|
@@ -1846,7 +1894,7 @@ async function preflightManagedTaskControlPlane() {
|
|
|
1846
1894
|
const internalCallback = args[0] === "internal"
|
|
1847
1895
|
&& ["agent-host", "session-notify", "runtime-hook"].includes(args[1] ?? "");
|
|
1848
1896
|
const home = resolveYuiHome(process.env);
|
|
1849
|
-
|
|
1897
|
+
assertManagedSessionManifest(home, "task");
|
|
1850
1898
|
const diagnosticTarget = taskDiagnosticTarget(args);
|
|
1851
1899
|
const diagnostic = diagnosticTarget !== undefined
|
|
1852
1900
|
&& diagnosticTarget === process.env.YUI_TASK_ID;
|
|
@@ -1921,7 +1969,19 @@ async function preflightManagedGlobalControlPlane() {
|
|
|
1921
1969
|
await assertRuntimeCoherence({ actualHome: home }, {
|
|
1922
1970
|
checkController: !(expectedRoleKind === "operator" && operatorOfflineCommand(args))
|
|
1923
1971
|
});
|
|
1924
|
-
|
|
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 };
|
|
1925
1985
|
}
|
|
1926
1986
|
/**
|
|
1927
1987
|
* The entry point the current invocation resolves to. Only its executable and
|
|
@@ -1953,17 +2013,21 @@ function assertManagedSessionManifest(home, scope) {
|
|
|
1953
2013
|
return manifest;
|
|
1954
2014
|
}
|
|
1955
2015
|
function cleanupCliError(error, fallbackResource) {
|
|
2016
|
+
if (error instanceof CleanupInspectionError) {
|
|
2017
|
+
return usageError(error.message, undefined, cleanupBlockedDetails(error.checks[0]?.reason ?? "cleanup-failed", fallbackResource, true, error.checks));
|
|
2018
|
+
}
|
|
1956
2019
|
if (error instanceof WorkspaceCleanupBlockedError) {
|
|
1957
2020
|
return usageError(error.message, undefined, cleanupBlockedDetails(error.reason, error.resource, error.retryable));
|
|
1958
2021
|
}
|
|
1959
2022
|
return new CliError("RUNTIME_ERROR", error instanceof Error ? error.message : String(error), undefined, cleanupBlockedDetails("cleanup-failed", fallbackResource, true));
|
|
1960
2023
|
}
|
|
1961
|
-
function cleanupBlockedDetails(reason, resource, retryable) {
|
|
2024
|
+
function cleanupBlockedDetails(reason, resource, retryable, checks) {
|
|
1962
2025
|
return {
|
|
1963
2026
|
status: "blocked",
|
|
1964
2027
|
blockedBy: [{ resource, reason, retryable }],
|
|
1965
2028
|
remainingResources: [resource],
|
|
1966
|
-
retryable
|
|
2029
|
+
retryable,
|
|
2030
|
+
...(checks === undefined ? {} : { checks })
|
|
1967
2031
|
};
|
|
1968
2032
|
}
|
|
1969
2033
|
function cliWorkItemReference(value, environment) {
|
|
@@ -2042,9 +2106,20 @@ async function prepareExecutionLaneWorkspacesForCommand(args, store, preparer, e
|
|
|
2042
2106
|
throw usageError(`${item.taskId}/${roleName} already has an active turn.`);
|
|
2043
2107
|
}
|
|
2044
2108
|
}
|
|
2045
|
-
const held = preparer.acquireTaskProjectMaintenanceLocks(item.taskId);
|
|
2109
|
+
const held = await preparer.acquireTaskProjectMaintenanceLocks(item.taskId);
|
|
2046
2110
|
const map = new Map();
|
|
2047
2111
|
try {
|
|
2112
|
+
assertTaskDeliveryAuthority(store, environment, item.taskId);
|
|
2113
|
+
const currentItem = store.getWorkItem(item.taskId, item.id);
|
|
2114
|
+
if (currentItem?.revision !== item.revision
|
|
2115
|
+
|| JSON.stringify(workItemDispatchLanePlan(args, store, currentItem)) !== JSON.stringify(plan)) {
|
|
2116
|
+
throw new Error(`Work item dispatch changed while waiting for Project maintenance: ${item.id}.`);
|
|
2117
|
+
}
|
|
2118
|
+
if (held.current.status !== "active" || held.current.executionGate.state !== "enabled"
|
|
2119
|
+
|| plan.roles.some(roleName => store.getRole(item.taskId, roleName) === null
|
|
2120
|
+
|| store.getActiveRun(item.taskId, roleName) !== null)) {
|
|
2121
|
+
throw new Error(`Task/Role dispatch state changed while waiting for Project maintenance: ${item.taskId}.`);
|
|
2122
|
+
}
|
|
2048
2123
|
const projectPaths = new Map();
|
|
2049
2124
|
for (const { projectId } of held.current.projectBindings) {
|
|
2050
2125
|
const project = store.getProject(projectId);
|
|
@@ -2128,7 +2203,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
2128
2203
|
&& args[3] !== undefined) {
|
|
2129
2204
|
const reference = cliTaskRecordReference(args[3], "reviewRound", environment);
|
|
2130
2205
|
const round = store.getReviewRound(reference.taskId, reference.localId);
|
|
2131
|
-
if (round !== null &&
|
|
2206
|
+
if (round !== null && round.scope === "task") {
|
|
2132
2207
|
taskId = reference.taskId;
|
|
2133
2208
|
}
|
|
2134
2209
|
}
|
|
@@ -2138,7 +2213,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
2138
2213
|
&& args[4] !== undefined) {
|
|
2139
2214
|
const reference = cliTaskRecordReference(args[4], "reviewRound", environment);
|
|
2140
2215
|
const round = store.getReviewRound(reference.taskId, reference.localId);
|
|
2141
|
-
if (round !== null &&
|
|
2216
|
+
if (round !== null && round.scope === "task") {
|
|
2142
2217
|
taskId = reference.taskId;
|
|
2143
2218
|
}
|
|
2144
2219
|
}
|
|
@@ -2152,7 +2227,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
2152
2227
|
: store.getReviewRound(reference.taskId, run.reviewRoundId);
|
|
2153
2228
|
if (run?.purpose === "review"
|
|
2154
2229
|
&& round !== null
|
|
2155
|
-
&&
|
|
2230
|
+
&& round.scope === "task") {
|
|
2156
2231
|
taskId = reference.taskId;
|
|
2157
2232
|
}
|
|
2158
2233
|
}
|
|
@@ -2409,7 +2484,7 @@ async function completionCommand(home, node) {
|
|
|
2409
2484
|
const store = openCurrentTaskStore(home);
|
|
2410
2485
|
const ioHandle = terminalIo();
|
|
2411
2486
|
try {
|
|
2412
|
-
const manager = new FileCompletionManager(store, process.env
|
|
2487
|
+
const manager = new FileCompletionManager(store, process.env);
|
|
2413
2488
|
emit(await runCompletionWizard(manager, ioHandle.io, shell === undefined ? {} : { shell }));
|
|
2414
2489
|
}
|
|
2415
2490
|
finally {
|
|
@@ -2490,7 +2565,7 @@ async function preflightAgentConfigurationMutation(commandArgs, store, catalogs)
|
|
|
2490
2565
|
const mutation = profileAgentConfigurationMutation(commandArgs, store)
|
|
2491
2566
|
?? taskRoleAgentConfigurationMutation(commandArgs, store);
|
|
2492
2567
|
if (mutation === undefined) {
|
|
2493
|
-
await
|
|
2568
|
+
await warmConfigurationMutationCatalog(commandArgs, store, catalogs);
|
|
2494
2569
|
return undefined;
|
|
2495
2570
|
}
|
|
2496
2571
|
const agent = store.getConfiguredAgent(mutation.agentId);
|
|
@@ -2531,7 +2606,7 @@ function taskRoleAgentConfigurationMutation(args, store) {
|
|
|
2531
2606
|
? previewTaskRoleAgentConfigurationMutation(args.slice(1), store)
|
|
2532
2607
|
: undefined;
|
|
2533
2608
|
}
|
|
2534
|
-
async function
|
|
2609
|
+
async function warmConfigurationMutationCatalog(commandArgs, store, catalogs) {
|
|
2535
2610
|
if (!hasModelOrEffortMutation(commandArgs))
|
|
2536
2611
|
return;
|
|
2537
2612
|
const agentId = configurationMutationAgentId(commandArgs, store);
|
|
@@ -2667,7 +2742,7 @@ function selectionCall(store, catalogs, method, params) {
|
|
|
2667
2742
|
case "role.list": return store.listGlobalRoles();
|
|
2668
2743
|
case "role.show": return store.getGlobalRole(String(params.name ?? ""));
|
|
2669
2744
|
case "project.list": return callOptional(reader, "listProjects");
|
|
2670
|
-
case "task.list": return
|
|
2745
|
+
case "task.list": return store.listTaskChoices();
|
|
2671
2746
|
case "task.integration.list": return store.listIntegrationAttempts(String(params.taskId ?? ""));
|
|
2672
2747
|
case "task.change-set.list": return store.listChangeSets(String(params.taskId ?? ""));
|
|
2673
2748
|
case "task.role.list": return callOptional(reader, "listRoles", [params.taskId]);
|
|
@@ -2818,15 +2893,8 @@ function agentEnvironmentRefreshScope(previous, current, configured) {
|
|
|
2818
2893
|
nativeNames: [...new Set([...currentNative, ...previousOnlyNative])]
|
|
2819
2894
|
};
|
|
2820
2895
|
}
|
|
2821
|
-
export function cliIdentity(env) {
|
|
2822
|
-
return env.YUI_CLI_NAME === "yui-dev" ? "yui-dev" : "yui";
|
|
2823
|
-
}
|
|
2824
2896
|
function normalizeAliases(input) {
|
|
2825
2897
|
const normalized = [...input];
|
|
2826
|
-
// Existing immutable Session Manifests (through 0.15.8) name this entry.
|
|
2827
|
-
// Remove once those Sessions are retired; both names share one handler.
|
|
2828
|
-
if (normalized[0] === "task" && normalized[1] === "turn")
|
|
2829
|
-
normalized[1] = "run";
|
|
2830
2898
|
if (normalized.length === 1 && (normalized[0] === "-v" || normalized[0] === "--version")) {
|
|
2831
2899
|
return ["version"];
|
|
2832
2900
|
}
|