@zq-silk/yui 0.15.7 → 0.15.8
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 +192 -399
- package/README.md +127 -1149
- package/dist/agent/adapterCatalog.js +15 -2
- package/dist/agent/agent.js +23 -3
- package/dist/agent/argumentPolicy.js +7 -1
- package/dist/agent/connectionPlan.js +62 -0
- package/dist/agent/executionComponents.js +158 -0
- package/dist/agent/launchEnvironment.js +31 -3
- package/dist/agent/managedRuntimeEnvironment.js +3 -5
- package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
- package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +157 -70
- package/dist/cli/interactionCandidates.js +5 -5
- package/dist/cli/interactionPolicy.js +38 -8
- package/dist/cli/invocationRouter.js +1 -1
- package/dist/cli/managedDiagnostics.js +28 -0
- package/dist/cli/operatorWizard.js +1 -7
- package/dist/cli/roleOptionOrder.js +27 -0
- package/dist/cli/roleWizard.js +50 -14
- package/dist/cli/updateOrchestrator.js +1 -1
- package/dist/cli/updatePorts.js +3 -4
- package/dist/cli.js +178 -95
- package/dist/commands/agentCommands.js +72 -14
- package/dist/commands/capabilityCommands.js +9 -6
- package/dist/commands/configCommands.js +20 -20
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +24 -24
- package/dist/commands/globalRoleCommands.js +1 -1
- package/dist/commands/grantCommands.js +4 -4
- package/dist/commands/operatorCommands.js +1 -7
- package/dist/commands/projectCommands.js +4 -4
- package/dist/commands/resourcesCommands.js +2 -2
- package/dist/commands/roleConfiguration.js +25 -5
- package/dist/commands/roleRuntimeGuard.js +4 -5
- package/dist/commands/sessionCommands.js +3 -7
- package/dist/commands/taskActivationCommands.js +259 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1188 -710
- package/dist/commands/taskContextCommand.js +39 -583
- package/dist/commands/taskExecutionCommands.js +32 -32
- package/dist/commands/taskInputCommands.js +40 -104
- package/dist/commands/taskIntegrationCommands.js +3 -2
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -8
- package/dist/commands/taskOverviewCommand.js +33 -45
- package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
- package/dist/commands/taskRoleRuntimeStatus.js +133 -102
- package/dist/commands/telemetryCommands.js +36 -38
- package/dist/config/configCatalog.js +4 -4
- package/dist/config/yuiConfig.js +8 -8
- package/dist/context/contextSnapshot.js +10 -10
- package/dist/context/dispatchContext.js +11 -11
- package/dist/context/roleSessionContext.js +6 -3
- package/dist/context/{turnContextPack.js → runContextPack.js} +146 -81
- package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
- package/dist/context/sessionBootstrapManifest.js +21 -2
- package/dist/context/sourceRunContext.js +30 -0
- package/dist/context/taskContext.js +458 -0
- package/dist/context/wakeNotification.js +27 -27
- package/dist/controller/agentRuntimeObserver.js +21 -24
- package/dist/controller/capabilityBridge.js +17 -6
- package/dist/controller/clientRuntime.js +65 -92
- package/dist/controller/controller.js +114 -188
- package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
- package/dist/controller/jobControl.js +54 -85
- package/dist/controller/resourceInventory.js +8 -27
- package/dist/controller/resourceInventoryLinux.js +12 -13
- package/dist/controller/runtime.js +529 -479
- package/dist/controller/runtimeEventInbox.js +55 -25
- package/dist/controller/runtimeEventProcessor.js +22 -31
- package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
- package/dist/controller/runtimeLaunchCoordinator.js +80 -426
- package/dist/controller/runtimeObservationHook.js +14 -18
- package/dist/controller/sessionNotify.js +16 -24
- package/dist/controller/sessionOwnerReconciliation.js +168 -50
- package/dist/controller/structuredProviderObservation.js +138 -99
- package/dist/coordination/workMailbox.js +3 -3
- package/dist/coordination/workMailboxQueue.js +36 -33
- package/dist/core/boundedRpc.js +8 -1
- package/dist/core/controllerClient.js +20 -1
- package/dist/core/controllerServer.js +4 -4
- package/dist/doctor/doctor.js +13 -2
- package/dist/domain/agentResultTransport.js +9 -9
- package/dist/execution/codexThreadNaming.js +2 -8
- package/dist/execution/executionHealth.js +51 -63
- package/dist/execution/reviewMainRun.js +137 -0
- package/dist/execution/workItemExecution.js +28 -29
- package/dist/execution/workItemExecutionProjection.js +99 -107
- package/dist/execution/workItemMainRun.js +141 -0
- package/dist/executor/agentAdapter.js +227 -20
- package/dist/executor/agentConfigurationCatalog.js +126 -4
- package/dist/executor/agentConfigurationProbe.js +162 -4
- package/dist/executor/agentExecutor.js +79 -78
- package/dist/executor/effectiveLaunch.js +105 -18
- package/dist/executor/executorRegistry.js +29 -44
- package/dist/executor/fileRoleLaunchPlanner.js +229 -154
- package/dist/executor/workspacePreflightClassification.js +16 -16
- package/dist/grant/capabilityGrant.js +6 -3
- package/dist/input/inputRequest.js +12 -10
- package/dist/integration/gitIntegrationService.js +4 -11
- package/dist/integration/integrationQueueService.js +4 -4
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/kernel/builtinCapabilities.js +247 -12
- package/dist/kernel/capabilityRegistry.js +64 -18
- package/dist/kernel/instanceHost.js +12 -1
- package/dist/kernel/kernelPorts.js +2 -2
- package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
- package/dist/lifecycle/exactRunTerminalization.js +449 -0
- package/dist/message/message.js +62 -6
- package/dist/message/messageContinuation.js +204 -0
- package/dist/observability/executionAudit.js +70 -72
- package/dist/observability/faultClassification.js +2 -2
- package/dist/observability/orchestrationMetrics.js +8 -8
- package/dist/operator/operatorSessionHistory.js +1 -7
- package/dist/output/agentConfigurationPresentation.js +8 -3
- package/dist/output/agentRunConfigurationPresentation.js +128 -0
- package/dist/output/rolePresentation.js +54 -3
- package/dist/plugins/pluginChild.js +104 -0
- package/dist/plugins/pluginIntent.js +26 -0
- package/dist/plugins/pluginInterpreter.js +43 -0
- package/dist/plugins/pluginPackage.js +101 -0
- package/dist/plugins/pluginProcess.js +112 -0
- package/dist/plugins/pluginService.js +380 -0
- package/dist/profile/agentProfile.js +1 -1
- package/dist/repository/gitWorkspace.js +26 -4
- package/dist/repository/project.js +19 -4
- package/dist/repository/taskBaseFreshness.js +13 -13
- package/dist/repository/taskWorkspaceCoordinator.js +20 -27
- package/dist/repository/taskWorkspacePreparer.js +344 -83
- package/dist/resources/autoResourceGc.js +3 -3
- package/dist/resources/liveReferences.js +3 -3
- package/dist/resources/projectResource.js +123 -0
- package/dist/resources/projectResourceService.js +421 -0
- package/dist/resources/resourceDiscovery.js +6 -6
- package/dist/resources/resourceGc.js +1 -1
- package/dist/resources/resourceRegistrar.js +1 -1
- package/dist/resources/resourceTypes.js +1 -1
- package/dist/review/deltaRecheck.js +3 -3
- package/dist/review/reviewAcceptance.js +16 -16
- package/dist/review/reviewDecision.js +7 -7
- package/dist/review/reviewRound.js +21 -20
- package/dist/review/reviewerAvailability.js +2 -2
- package/dist/role/role.js +51 -7
- package/dist/role/taskRoleUpdate.js +30 -0
- package/dist/runtime/acpProtocol.js +425 -0
- package/dist/runtime/acpSession.js +731 -0
- package/dist/runtime/acpSessionConfiguration.js +260 -0
- package/dist/runtime/agentDriver.js +30 -11
- package/dist/runtime/agentEndpoint.js +278 -0
- package/dist/runtime/agentEndpointIdentity.js +86 -0
- package/dist/runtime/agentEndpointOwnership.js +239 -0
- package/dist/runtime/agentError.js +2 -10
- package/dist/runtime/agentHost.js +565 -314
- package/dist/runtime/agentRunConfiguration.js +258 -0
- package/dist/runtime/builtinAgentDrivers.js +134 -18
- package/dist/runtime/builtinAgentErrorMappers.js +55 -3
- package/dist/runtime/builtinTranscriptUsage.js +1 -1
- package/dist/runtime/claude-process-owner +0 -0
- package/dist/runtime/codexAppServerRuntime.js +38 -30
- package/dist/runtime/codexInteractiveHost.js +41 -6
- package/dist/runtime/continuationManager.js +2 -6
- package/dist/runtime/executionEnvironment.js +30 -0
- package/dist/runtime/firstProgressAdvisory.js +11 -11
- package/dist/runtime/index.js +4 -3
- package/dist/runtime/jsonLineChannel.js +109 -0
- package/dist/runtime/launchBroker.js +91 -16
- package/dist/runtime/launchDiagnostics.js +2 -2
- package/dist/runtime/lifecycleReservation.js +10 -18
- package/dist/runtime/managedCaller.js +61 -17
- package/dist/runtime/nativeSessionControl.js +102 -0
- package/dist/runtime/ports.js +6 -21
- package/dist/runtime/processExitObservation.js +8 -7
- package/dist/runtime/promptEnvelope.js +17 -6
- package/dist/runtime/providerContinuation.js +3 -9
- package/dist/runtime/providerContinuationReconciliationService.js +4 -13
- package/dist/runtime/providerControl.js +2 -7
- package/dist/runtime/providerRuntimeIdentity.js +110 -222
- package/dist/runtime/providerRuntimeReconciler.js +5 -9
- package/dist/runtime/runtimeBinding.js +0 -1
- package/dist/runtime/runtimeContinuationProjection.js +4 -7
- package/dist/runtime/runtimeDeadlines.js +9 -0
- package/dist/runtime/runtimeHealthPolicy.js +1 -1
- package/dist/runtime/runtimeObservation.js +29 -65
- package/dist/runtime/runtimeProjection.js +43 -51
- package/dist/runtime/runtimeSessionCandidate.js +1 -3
- package/dist/runtime/sessionLaunchRequest.js +3 -7
- package/dist/runtime/sessionOwnerIdentity.js +7 -54
- package/dist/runtime/sessionOwnerRegistry.js +22 -17
- package/dist/runtime/sessionReconciliation.js +4 -8
- package/dist/runtime/sessionTerminationGuard.js +70 -259
- package/dist/runtime/sessionTokenMetrics.js +5 -16
- package/dist/runtime/structuredProviderHost.js +237 -117
- package/dist/runtime/taskRuntimeIsolation.js +39 -122
- package/dist/runtime/tmuxAdapters.js +39 -86
- package/dist/scheduler/activeRoleRunDelivery.js +354 -0
- package/dist/scheduler/leaderWakeupProcessor.js +75 -266
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +80 -9
- package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
- package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
- package/dist/scheduler/taskExecutionProjection.js +120 -124
- package/dist/scheduler/taskObservabilityProjection.js +29 -29
- package/dist/scheduler/taskWake.js +11 -4
- package/dist/scheduler/wakeReason.js +9 -1
- package/dist/setup/setupCommand.js +1 -0
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/sqliteSchema.js +431 -5
- package/dist/storage/sqliteStore.js +375 -220
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +10 -5
- package/dist/storage/taskStore.js +13 -11
- package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
- package/dist/surface/surfaceContributions.js +102 -0
- package/dist/task/completionReadiness.js +32 -6
- package/dist/task/deliveryGuard.js +16 -16
- package/dist/task/draftPlan.js +72 -12
- package/dist/task/nextAction.js +144 -128
- package/dist/task/remoteDelivery.js +6 -6
- package/dist/task/task.js +184 -18
- package/dist/task/taskActivation.js +301 -0
- package/dist/task/taskActivationService.js +392 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/telemetry/sqliteTelemetryStore.js +55 -68
- package/dist/telemetry/telemetryConfig.js +14 -14
- package/dist/telemetry/telemetryWiring.js +2 -2
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +120 -20
- package/dist/web/assets/client/components.js +87 -54
- package/dist/web/assets/client/i18n.js +83 -41
- package/dist/web/assets/client/markdown.js +1 -1
- package/dist/web/assets/client/taskSurface.js +353 -0
- package/dist/web/assets/client/view.js +49 -44
- package/dist/web/assets/shell.js +1 -1
- package/dist/web/assets/styles/cards.js +22 -4
- package/dist/web/controllerWeb.js +60 -0
- package/dist/web/webMutation.js +28 -0
- package/dist/web/webServer.js +118 -8
- package/dist/web/webSnapshot.js +81 -74
- package/dist/web/webTaskSurface.js +64 -0
- package/dist/workItem/dependencyGate.js +1 -1
- package/dist/workItem/workItem.js +80 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +94 -0
- package/docs/agent-runtime-drivers.md +91 -0
- package/docs/architecture/README.md +38 -0
- package/docs/architecture/capabilities-and-resources.md +79 -0
- package/docs/managed-turn-and-session-runtime.md +222 -0
- package/docs/observability/README.md +81 -0
- package/docs/plugin-sdk.md +290 -0
- package/docs/provider-runtime.md +163 -0
- package/docs/release-workflow.md +303 -0
- package/docs/roles-and-configuration.md +113 -0
- package/docs/sqlite-control-plane-design.md +76 -0
- package/docs/task-dag-semantics.md +57 -0
- package/docs/task-delivery.md +103 -0
- package/docs/task-local-identity.md +6 -6
- package/docs/testing/verification-levels.md +86 -0
- package/i18n/README.zh-CN.md +98 -739
- package/package.json +2 -2
- package/skills/yui-leader/SKILL.md +130 -103
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +33 -0
- package/skills/yui-operator/SKILL.md +30 -59
- package/skills/yui-reviewer/SKILL.md +35 -36
- package/skills/yui-runtime/SKILL.md +88 -24
- package/skills/yui-runtime/references/publication.md +22 -0
- package/skills/yui-runtime/references/recovery.md +64 -0
- package/skills/yui-worker/SKILL.md +37 -39
- package/dist/cli/roleOptionCatalog.js +0 -68
- package/dist/context/sourceTurnContext.js +0 -30
- package/dist/execution/reviewMainTurn.js +0 -161
- package/dist/execution/workItemMainTurn.js +0 -164
- package/dist/lifecycle/exactTurnTerminalization.js +0 -407
- package/dist/runtime/preallocatedNativeSession.js +0 -13
- package/dist/runtime/runtimeStopReceipt.js +0 -42
- package/dist/scheduler/activeRoleTurnDelivery.js +0 -315
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { lstat, mkdir, readlink, readdir, rm, rmdir, symlink, unlink } from "node:fs/promises";
|
|
2
2
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { isDeepStrictEqual } from "node:util";
|
|
4
|
-
import { retireTaskRoleSessionsForWorkspace } from "../executor/agentExecutor.js";
|
|
4
|
+
import { retireTaskRoleSessionsForWorkspace, updateRoleAgentSessionStatus } from "../executor/agentExecutor.js";
|
|
5
5
|
import { formatWorkspacePreflightError } from "../executor/workspacePreflightClassification.js";
|
|
6
6
|
import { updateRole } from "../role/role.js";
|
|
7
|
-
import {
|
|
7
|
+
import { taskRoleRuntimeIdentity } from "../runtime/managedCaller.js";
|
|
8
|
+
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget, RUNTIME_HOST_DETACH_REQUIRED_REASON } from "../runtime/lifecycleReservation.js";
|
|
9
|
+
import { taskLocalActor } from "../commands/taskActor.js";
|
|
8
10
|
import { attachReviewRoundWorkspace, recordReviewWorkspaceDisposition } from "../review/reviewRound.js";
|
|
9
11
|
import { StorageConflictError } from "../storage/taskStore.js";
|
|
10
|
-
import { activateTask, bindTaskProjectCommits, bindTaskWorkspaceIdentity, synchronizeTaskProjectCommits } from "../task/task.js";
|
|
12
|
+
import { activateTask, bindTaskProjectCommits, bindTaskWorkspaceIdentity, synchronizeTaskProjectCommits, taskOwnsManagedWorkspace } from "../task/task.js";
|
|
13
|
+
import { admitStoredTaskActivation, adoptTaskActivationResources, recordFailedTaskActivation, recordAdoptedTaskActivation } from "../task/taskActivationService.js";
|
|
11
14
|
import { validateDraftTaskForActivation } from "../task/draftPlan.js";
|
|
15
|
+
import { createProjectResources } from "../resources/projectResourceService.js";
|
|
12
16
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
13
17
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
14
|
-
import { createCandidateGitSnapshot, createDirectTaskMainSnapshot, workItemExecutionGroupById,
|
|
18
|
+
import { createCandidateGitSnapshot, createDirectTaskMainSnapshot, workItemExecutionGroupById, recordWorkItemWorkspaceDisposition } from "../workItem/workItem.js";
|
|
15
19
|
import { createManagedWorkspace, isTaskOwnedWorkspace, managedWorkspaceKey, managedWorktreeName, sameManagedWorkspaceIdentity } from "../worktree/managedWorkspace.js";
|
|
16
20
|
import { NodeGitWorkspace, worktreeIdentity } from "./gitWorkspace.js";
|
|
17
21
|
import { acquireProjectMaintenanceLocks } from "./projectMaintenanceLock.js";
|
|
@@ -103,15 +107,27 @@ export class FileTaskWorkspacePreparer {
|
|
|
103
107
|
* transaction that moves the Task from Draft to active. A Draft never owns a
|
|
104
108
|
* durable writable workspace before this boundary, and any failed attempt
|
|
105
109
|
* discards its unadopted refs/worktrees.
|
|
110
|
+
*
|
|
111
|
+
* An explicit activation request drives the resource configuration. Its
|
|
112
|
+
* environment is prepared and adopted before this boundary, and the request
|
|
113
|
+
* is marked adopted inside the same transaction as the status change, so a
|
|
114
|
+
* later launch failure can never present itself as "activation never
|
|
115
|
+
* happened".
|
|
106
116
|
*/
|
|
107
|
-
async activateTaskWorkspace(taskId) {
|
|
117
|
+
async activateTaskWorkspace(taskId, environment = {}) {
|
|
108
118
|
for (let attempt = 0;; attempt += 1) {
|
|
119
|
+
let attemptedRequest;
|
|
109
120
|
try {
|
|
110
121
|
const task = requireTask(this.store, taskId);
|
|
122
|
+
const caller = taskLocalActor(this.store, environment, task.id);
|
|
123
|
+
const actor = task.activationRequest?.operation.actorId === `task:${task.id}/role:leader`
|
|
124
|
+
? "leader" : caller;
|
|
111
125
|
if (task.status === "active") {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
126
|
+
if (taskOwnsManagedWorkspace(task)) {
|
|
127
|
+
const workspace = this.store.getTaskWorkspace(task.id);
|
|
128
|
+
if (!isTaskOwnedWorkspace(workspace, task.id, task.cwd, task.projectBindings.map(({ projectId, directory }) => ({ projectId, directory })))) {
|
|
129
|
+
throw new Error(`Active Task workspace adoption is invalid: ${task.id}.`);
|
|
130
|
+
}
|
|
115
131
|
}
|
|
116
132
|
return {
|
|
117
133
|
task,
|
|
@@ -121,24 +137,186 @@ export class FileTaskWorkspacePreparer {
|
|
|
121
137
|
changed: false
|
|
122
138
|
};
|
|
123
139
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
finally {
|
|
130
|
-
release();
|
|
140
|
+
const provider = this.store.getTaskRoleSessionSet(task.id, LEADER_ROLE)?.providerBinding;
|
|
141
|
+
if (provider?.run != null
|
|
142
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(provider.run.status)) {
|
|
143
|
+
throw new Error("Planning native input is unsettled; preserve activation intent and retry after its exact terminal.");
|
|
131
144
|
}
|
|
145
|
+
const admission = admitStoredTaskActivation(this.store, task.id);
|
|
146
|
+
if (admission.disposition === "ready")
|
|
147
|
+
attemptedRequest = admission.request;
|
|
148
|
+
validateDraftTaskForActivation(this.store, task);
|
|
149
|
+
const adopted = await this.#adoptActivationEnvironment(task);
|
|
150
|
+
return adopted.workspaceFree
|
|
151
|
+
? this.#activateWorkspaceFreeTask(task.id, adopted, actor)
|
|
152
|
+
: await this.#prepareActivatedTaskWorkspace(task, adopted, actor);
|
|
132
153
|
}
|
|
133
154
|
catch (error) {
|
|
134
155
|
if (error instanceof StorageConflictError
|
|
135
156
|
&& attempt < TASK_WORKSPACE_PREPARE_MAX_CONFLICT_RETRIES) {
|
|
136
157
|
continue;
|
|
137
158
|
}
|
|
159
|
+
if (attemptedRequest !== undefined) {
|
|
160
|
+
const outcome = error instanceof Error ? error.message : String(error);
|
|
161
|
+
const current = this.store.getTask(taskId)?.activationRequest;
|
|
162
|
+
if (current?.disposition !== "failed" || current.outcome !== outcome) {
|
|
163
|
+
recordFailedTaskActivation(this.store, taskId, attemptedRequest, outcome, {}, this.now());
|
|
164
|
+
}
|
|
165
|
+
}
|
|
138
166
|
throw error;
|
|
139
167
|
}
|
|
140
168
|
}
|
|
141
169
|
}
|
|
170
|
+
/** Prepares the managed workspace of a Task being activated, under its fences. */
|
|
171
|
+
async #prepareActivatedTaskWorkspace(task, environment, actor) {
|
|
172
|
+
const { release, current } = this.#acquireTaskProjectMaintenanceLocks(task);
|
|
173
|
+
try {
|
|
174
|
+
return await this.#prepareTaskWorkspaceLocked(current.id, true, environment, actor);
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
release();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Prepares and adopts the resource configuration an explicit activation
|
|
182
|
+
* request named, before any status change.
|
|
183
|
+
*
|
|
184
|
+
* A Task with no request keeps the historical behavior: its managed workspace
|
|
185
|
+
* is the whole environment. A request whose plan is empty and whose Task
|
|
186
|
+
* binds no Project owns nothing physical, and is reported as workspace-free
|
|
187
|
+
* so activation creates no directory at all.
|
|
188
|
+
*
|
|
189
|
+
* The exact request that was adopted travels with the result, so the later
|
|
190
|
+
* status transaction can prove it is completing that request rather than
|
|
191
|
+
* whichever one occupies the slot by then.
|
|
192
|
+
*/
|
|
193
|
+
async #adoptActivationEnvironment(task) {
|
|
194
|
+
const admission = admitStoredTaskActivation(this.store, task.id);
|
|
195
|
+
if (admission.disposition === "absent")
|
|
196
|
+
return { workspaceFree: false };
|
|
197
|
+
if (admission.disposition !== "ready") {
|
|
198
|
+
throw new Error(`Task activation is not adoptable: ${task.id}/${admission.disposition}`
|
|
199
|
+
+ (admission.disposition === "unavailable" ? ` (${admission.reason})` : "")
|
|
200
|
+
+ ".");
|
|
201
|
+
}
|
|
202
|
+
// Resource adoption owns its own grant, directory-identity and conflict
|
|
203
|
+
// rechecks. A failure here leaves the Task a continuable Draft.
|
|
204
|
+
const adopted = adoptTaskActivationResources(this.store, task.id, this.now);
|
|
205
|
+
return {
|
|
206
|
+
workspaceFree: admission.plan.kind === "empty"
|
|
207
|
+
&& task.projectBindings.length === 0,
|
|
208
|
+
// The request whose resources were actually adopted, re-checked at the
|
|
209
|
+
// status transaction: a cancel or replacement landing during workspace
|
|
210
|
+
// preparation must fail rather than silently swap requests.
|
|
211
|
+
request: adopted.request,
|
|
212
|
+
// The exact preparation this request consumed, so the adopted record
|
|
213
|
+
// names the environment it adopted rather than the latest one present.
|
|
214
|
+
...(adopted.preparation === undefined
|
|
215
|
+
? {}
|
|
216
|
+
: { preparationId: adopted.preparation.id })
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Moves a Task that owns nothing physical from Draft to active.
|
|
221
|
+
*
|
|
222
|
+
* An empty environment plan with no bound Project is a legal Task shape, so
|
|
223
|
+
* no workspace view, worktree or cwd is created to satisfy the framework's
|
|
224
|
+
* own model. The status change and the adopted request commit together.
|
|
225
|
+
*/
|
|
226
|
+
#activateWorkspaceFreeTask(taskId, environment, actor) {
|
|
227
|
+
const task = this.store.transaction((tx) => {
|
|
228
|
+
const latest = requireTask(tx, taskId);
|
|
229
|
+
if (latest.status !== "draft") {
|
|
230
|
+
throw new Error(`Task changed while activating it: ${taskId}/${latest.status}.`);
|
|
231
|
+
}
|
|
232
|
+
if (latest.projectBindings.length !== 0
|
|
233
|
+
|| latest.cwd !== undefined
|
|
234
|
+
|| latest.workspaceIdentity !== undefined
|
|
235
|
+
|| tx.getTaskWorkspace(taskId) !== null) {
|
|
236
|
+
throw new Error(`Task acquired workspace state while activating it: ${taskId}.`);
|
|
237
|
+
}
|
|
238
|
+
validateDraftTaskForActivation(tx, latest);
|
|
239
|
+
const timestamp = this.now();
|
|
240
|
+
// Bind before the handover below: the desired environment must be the
|
|
241
|
+
// adopted one before the obligation that ends the old physical Host
|
|
242
|
+
// exists.
|
|
243
|
+
if (environment.preparationId !== undefined) {
|
|
244
|
+
this.#bindAdoptedActivationEnvironment(tx, taskId, environment.preparationId);
|
|
245
|
+
}
|
|
246
|
+
// The physical launch is unchanged, but "unchanged" is not "compatible":
|
|
247
|
+
// the Draft's planning Session runs in the Role's inherited workspace,
|
|
248
|
+
// which is exactly the shared cwd an active Task may not execute in. Hand
|
|
249
|
+
// the conversation over instead of asserting continuity, so the next
|
|
250
|
+
// launch is re-planned under the execution rules and must name a real
|
|
251
|
+
// environment. Its plan and grants are re-proven there, not assumed here.
|
|
252
|
+
handOverPlanningSession(tx, taskId, timestamp);
|
|
253
|
+
const activated = this.#recordAdoptedActivation(tx, activateTask(latest, timestamp), environment, timestamp);
|
|
254
|
+
tx.saveTask(activated);
|
|
255
|
+
recordTaskActivation(tx, latest, activated, timestamp, actor);
|
|
256
|
+
return activated;
|
|
257
|
+
});
|
|
258
|
+
return { task, taskId, status: "ready", changed: true };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Marks the Task's activation request adopted, inside the caller's activation
|
|
262
|
+
* transaction. Returns the Task unchanged when no explicit request exists.
|
|
263
|
+
*
|
|
264
|
+
* The adopted environment and the request identity travel together, so the
|
|
265
|
+
* record can never name a preparation that a different request produced.
|
|
266
|
+
*/
|
|
267
|
+
#recordAdoptedActivation(store, task, environment, now) {
|
|
268
|
+
if (task.activationRequest === undefined)
|
|
269
|
+
return task;
|
|
270
|
+
// Re-prove the adopted environment inside the status transaction. Between
|
|
271
|
+
// resource adoption and here a worktree was prepared, which takes real time:
|
|
272
|
+
// a grant may have been revoked or bounded away, or the directory may no
|
|
273
|
+
// longer be the Resource it was adopted as. `resolveExecutionEnvironment` is
|
|
274
|
+
// the same check every launch performs, including the per-resourceRef grant
|
|
275
|
+
// recheck against the reservation adoption charged, so a Task can never go
|
|
276
|
+
// active claiming an environment its authority no longer covers.
|
|
277
|
+
if (environment.preparationId !== undefined) {
|
|
278
|
+
createProjectResources(store, this.now)
|
|
279
|
+
.resolveExecutionEnvironment(task.id, environment.preparationId);
|
|
280
|
+
}
|
|
281
|
+
return recordAdoptedTaskActivation(store, task, {
|
|
282
|
+
...(environment.preparationId === undefined
|
|
283
|
+
? {}
|
|
284
|
+
: { preparationId: environment.preparationId }),
|
|
285
|
+
...(environment.request === undefined ? {} : { expected: environment.request })
|
|
286
|
+
}, now);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Selects the adopted environment for every Role of a Task, so
|
|
290
|
+
* `preparationId` is the Role's actual `executionEnvironment` rather than a
|
|
291
|
+
* record no launch ever reads.
|
|
292
|
+
*
|
|
293
|
+
* This is the existing public `bindEnvironment` path, so the atomic Role
|
|
294
|
+
* update, its event and its mailbox notification are the ones task-21
|
|
295
|
+
* established — the adopted directory, its ownership, its access and its
|
|
296
|
+
* per-resource grant reservation are rechecked there and shown truthfully at
|
|
297
|
+
* launch.
|
|
298
|
+
*
|
|
299
|
+
* Runs inside the caller's activation transaction (the Store is re-entrant),
|
|
300
|
+
* and specifically *before* the planning-Session handover queues the Host
|
|
301
|
+
* detach. That order is required in both directions: the desired
|
|
302
|
+
* configuration must change before the obligation that ends the old physical
|
|
303
|
+
* Host exists, so runtime cleanup never acts on a launch configuration the
|
|
304
|
+
* Task has already replaced — which is exactly what the runtime-lifecycle
|
|
305
|
+
* guard refuses when the two are inverted. Committing with the status change
|
|
306
|
+
* also means a Task can never be observed active holding an adopted
|
|
307
|
+
* preparation that no Role executes in.
|
|
308
|
+
*/
|
|
309
|
+
#bindAdoptedActivationEnvironment(store, taskId, preparationId) {
|
|
310
|
+
const resources = createProjectResources(store, this.now);
|
|
311
|
+
for (const role of store.listRoles(taskId)) {
|
|
312
|
+
if (role.executionEnvironment?.preparationId === preparationId)
|
|
313
|
+
continue;
|
|
314
|
+
resources.bindEnvironment(taskId, role.name, preparationId, {
|
|
315
|
+
source: "task.activation-adopted",
|
|
316
|
+
preparationId
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
142
320
|
/**
|
|
143
321
|
* Acquire the per-Project maintenance fences for every Project bound to a
|
|
144
322
|
* Task, then re-read the Task under those fences and prove its binding set
|
|
@@ -181,7 +359,9 @@ export class FileTaskWorkspacePreparer {
|
|
|
181
359
|
acquireTaskProjectMaintenanceLocks(taskId) {
|
|
182
360
|
return this.#acquireTaskProjectMaintenanceLocks(requireTask(this.store, taskId));
|
|
183
361
|
}
|
|
184
|
-
async #prepareTaskWorkspaceLocked(taskId, activate
|
|
362
|
+
async #prepareTaskWorkspaceLocked(taskId, activate,
|
|
363
|
+
/** Environment and request identity the activation adopted, when one exists. */
|
|
364
|
+
activationEnvironment = { workspaceFree: false }, actor = "user") {
|
|
185
365
|
const task = requireTask(this.store, taskId);
|
|
186
366
|
if (activate && task.status !== "draft") {
|
|
187
367
|
throw new Error(`Only a Draft Task can atomically adopt a workspace: ${task.id}/${task.status}.`);
|
|
@@ -194,6 +374,20 @@ export class FileTaskWorkspacePreparer {
|
|
|
194
374
|
}
|
|
195
375
|
if (activate)
|
|
196
376
|
validateDraftTaskForActivation(this.store, task);
|
|
377
|
+
if (activate) {
|
|
378
|
+
const planning = planningLeaderSession(this.store, task.id);
|
|
379
|
+
if (planning !== undefined) {
|
|
380
|
+
const target = resolve(this.#taskWorkspaceRoot(task.id));
|
|
381
|
+
const scratch = resolve(planning.effective.workspace.root);
|
|
382
|
+
const within = (parent, child) => {
|
|
383
|
+
const path = relative(parent, child);
|
|
384
|
+
return path === "" || (!path.startsWith("..") && !isAbsolute(path));
|
|
385
|
+
};
|
|
386
|
+
if (within(target, scratch) || within(scratch, target)) {
|
|
387
|
+
throw new Error("Planning and delivery workspaces overlap; preserve the planning Session and select an isolated delivery workspace.");
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
197
391
|
const existing = this.store.getTaskWorkspace(task.id);
|
|
198
392
|
if (activate && (task.workspaceIdentity !== undefined
|
|
199
393
|
|| task.cwd !== undefined
|
|
@@ -240,23 +434,35 @@ export class FileTaskWorkspacePreparer {
|
|
|
240
434
|
throw new Error(`Gitless Task workspace changed during preparation: ${task.id}.`);
|
|
241
435
|
}
|
|
242
436
|
const timestamp = this.now();
|
|
437
|
+
// Activation moves the Leader out of the Draft planning cwd, so its
|
|
438
|
+
// planning Session cannot continue. Hand it over explicitly before
|
|
439
|
+
// the migration below, which refuses to retire a live Session. The
|
|
440
|
+
// adopted environment is bound first, so the desired configuration is
|
|
441
|
+
// already correct when the Host detach is queued.
|
|
442
|
+
if (activate && activationEnvironment.preparationId !== undefined) {
|
|
443
|
+
this.#bindAdoptedActivationEnvironment(tx, task.id, activationEnvironment.preparationId);
|
|
444
|
+
}
|
|
445
|
+
if (activate)
|
|
446
|
+
handOverPlanningSession(tx, task.id, timestamp);
|
|
243
447
|
let next = latest.cwd === root
|
|
244
448
|
? latest
|
|
245
449
|
: { ...latest, cwd: root, updatedAt: timestamp.toISOString() };
|
|
246
450
|
if (activate)
|
|
247
|
-
next = activateTask(next, timestamp);
|
|
451
|
+
next = this.#recordAdoptedActivation(tx, activateTask(next, timestamp), activationEnvironment, timestamp);
|
|
248
452
|
if (!isDeepStrictEqual(next, latest))
|
|
249
453
|
tx.saveTask(next);
|
|
250
454
|
if (current === null)
|
|
251
455
|
tx.saveManagedWorkspace(workspace);
|
|
252
456
|
for (const role of tx.listRoles(task.id)) {
|
|
457
|
+
if (activate && role.name === "leader" && planningLeaderSession(tx, task.id) !== undefined)
|
|
458
|
+
continue;
|
|
253
459
|
if (role.workspace !== root) {
|
|
254
460
|
retireWorkspaceBoundSession(tx, task.id, role.name, timestamp);
|
|
255
461
|
tx.saveRole(task.id, updateRole(role, { workspace: root }, timestamp));
|
|
256
462
|
}
|
|
257
463
|
}
|
|
258
464
|
if (activate)
|
|
259
|
-
recordTaskActivation(tx, latest, next, timestamp);
|
|
465
|
+
recordTaskActivation(tx, latest, next, timestamp, actor);
|
|
260
466
|
return next;
|
|
261
467
|
});
|
|
262
468
|
return {
|
|
@@ -436,6 +642,15 @@ export class FileTaskWorkspacePreparer {
|
|
|
436
642
|
const timestamp = this.now();
|
|
437
643
|
let persistedTask = latest;
|
|
438
644
|
if (activate) {
|
|
645
|
+
// Activation moves the Leader out of the Draft planning cwd, so its
|
|
646
|
+
// planning Session cannot continue. Hand it over explicitly before
|
|
647
|
+
// the migration below, which refuses to retire a live Session. The
|
|
648
|
+
// adopted environment is bound first, so the desired configuration is
|
|
649
|
+
// already correct when the Host detach is queued.
|
|
650
|
+
if (activationEnvironment.preparationId !== undefined) {
|
|
651
|
+
this.#bindAdoptedActivationEnvironment(tx, task.id, activationEnvironment.preparationId);
|
|
652
|
+
}
|
|
653
|
+
handOverPlanningSession(tx, task.id, timestamp);
|
|
439
654
|
persistedTask = bindTaskProjectCommits(persistedTask, prepared.map(({ entry }) => ({
|
|
440
655
|
projectId: entry.projectId,
|
|
441
656
|
commit: entry.baseCommit
|
|
@@ -457,7 +672,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
457
672
|
persistedTask = { ...persistedTask, cwd: root, updatedAt: timestamp.toISOString() };
|
|
458
673
|
}
|
|
459
674
|
if (activate)
|
|
460
|
-
persistedTask = activateTask(persistedTask, timestamp);
|
|
675
|
+
persistedTask = this.#recordAdoptedActivation(tx, activateTask(persistedTask, timestamp), activationEnvironment, timestamp);
|
|
461
676
|
if (!isDeepStrictEqual(persistedTask, latest)) {
|
|
462
677
|
tx.saveTask(persistedTask);
|
|
463
678
|
}
|
|
@@ -469,32 +684,31 @@ export class FileTaskWorkspacePreparer {
|
|
|
469
684
|
recordTaskBaseProvenanceEvents(tx, task.id, adoptedProvenance, timestamp);
|
|
470
685
|
}
|
|
471
686
|
for (const role of tx.listRoles(task.id)) {
|
|
687
|
+
if (activate && role.name === "leader" && planningLeaderSession(tx, task.id) !== undefined)
|
|
688
|
+
continue;
|
|
472
689
|
// The Role field is only a cwd/snapshot hint. Preserve it while a
|
|
473
690
|
// durable WorkItem or ReviewRound workspace owns that cwd; Task-main
|
|
474
691
|
// preparation must not move a Reviewer Session out from under an
|
|
475
692
|
// active or retained ReviewRound. Other Roles use Task main.
|
|
476
|
-
// Prefer the active
|
|
693
|
+
// Prefer the active AgentRun's exact WorkItem, then a retained direct
|
|
477
694
|
// WorkItem owning this cwd, before considering queued assignments.
|
|
478
|
-
const
|
|
479
|
-
const
|
|
480
|
-
&&
|
|
481
|
-
&&
|
|
482
|
-
? tx.getWorkItem(task.id,
|
|
695
|
+
const activeRoleRun = tx.getActiveRun(task.id, role.name);
|
|
696
|
+
const activeRunItem = activeRoleRun !== null
|
|
697
|
+
&& activeRoleRun.purpose === "execution"
|
|
698
|
+
&& activeRoleRun.workItemId !== undefined
|
|
699
|
+
? tx.getWorkItem(task.id, activeRoleRun.workItemId)
|
|
483
700
|
: null;
|
|
484
|
-
//
|
|
485
|
-
// Preserve
|
|
486
|
-
//
|
|
487
|
-
// reading Task context must not migrate its resumable Worker either.
|
|
701
|
+
// Semantic acceptance/retirement does not release a workspace.
|
|
702
|
+
// Preserve its cwd until explicit cleanup/reassignment removes that
|
|
703
|
+
// durable owner; Task-main preparation must not migrate its Session.
|
|
488
704
|
const retainedItem = tx.listWorkItems(task.id).find((candidate) => (candidate.assignee === role.name
|
|
489
|
-
&& candidate.status === "failed"
|
|
490
|
-
&& currentWorkItemExecutionGroup(candidate) === undefined
|
|
491
705
|
&& tx.getWorkItemWorkspace(task.id, candidate.id)?.root === role.workspace));
|
|
492
|
-
const assignedItem =
|
|
493
|
-
&&
|
|
494
|
-
&& !["
|
|
495
|
-
?
|
|
706
|
+
const assignedItem = activeRunItem !== null
|
|
707
|
+
&& activeRunItem.assignee === role.name
|
|
708
|
+
&& !["accepted", "retired"].includes(activeRunItem.status)
|
|
709
|
+
? activeRunItem
|
|
496
710
|
: retainedItem ?? tx.listWorkItems(task.id).find((candidate) => (candidate.assignee === role.name
|
|
497
|
-
&& !["
|
|
711
|
+
&& !["accepted", "retired"]
|
|
498
712
|
.includes(candidate.status)));
|
|
499
713
|
const assignedWorkspace = assignedItem === undefined
|
|
500
714
|
? null
|
|
@@ -517,7 +731,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
517
731
|
}
|
|
518
732
|
}
|
|
519
733
|
if (activate)
|
|
520
|
-
recordTaskActivation(tx, latest, persistedTask, timestamp);
|
|
734
|
+
recordTaskActivation(tx, latest, persistedTask, timestamp, actor);
|
|
521
735
|
return persistedTask;
|
|
522
736
|
});
|
|
523
737
|
return {
|
|
@@ -817,14 +1031,14 @@ export class FileTaskWorkspacePreparer {
|
|
|
817
1031
|
|| !isDeepStrictEqual(latestItem.writeProjectIds, item.writeProjectIds)) {
|
|
818
1032
|
throw new Error(`Work item changed while preparing its workspace: ${item.id}.`);
|
|
819
1033
|
}
|
|
820
|
-
const
|
|
1034
|
+
const activeDevelopRun = tx.listRuns(lockedTask.id)
|
|
821
1035
|
.find((run) => run.status === "active" && run.workItemId === item.id);
|
|
822
|
-
if (
|
|
823
|
-
throw new Error(`Work Item already has an active Develop
|
|
1036
|
+
if (activeDevelopRun !== undefined) {
|
|
1037
|
+
throw new Error(`Work Item already has an active Develop AgentRun: ${activeDevelopRun.id}.`);
|
|
824
1038
|
}
|
|
825
1039
|
if (latestItem.assignee !== undefined
|
|
826
|
-
&& tx.
|
|
827
|
-
throw new Error(`Role has an active
|
|
1040
|
+
&& tx.getActiveRun(lockedTask.id, latestItem.assignee) !== null) {
|
|
1041
|
+
throw new Error(`Role has an active AgentRun: ${lockedTask.id}/${latestItem.assignee}.`);
|
|
828
1042
|
}
|
|
829
1043
|
const existingWorkspace = tx.getWorkItemWorkspace(lockedTask.id, item.id);
|
|
830
1044
|
if (existingWorkspace !== null && (existingWorkspace.owner.type !== "work-item"
|
|
@@ -1431,8 +1645,8 @@ export class FileTaskWorkspacePreparer {
|
|
|
1431
1645
|
|| candidateChanged) {
|
|
1432
1646
|
throw new ReviewRoundWorkspaceEvidenceError(`ReviewRound changed while preparing its workspace: ${round.id}.`);
|
|
1433
1647
|
}
|
|
1434
|
-
if (tx.
|
|
1435
|
-
throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active
|
|
1648
|
+
if (tx.getActiveRun(task.id, reviewer.name) !== null) {
|
|
1649
|
+
throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active AgentRun: ${task.id}/${reviewer.name}.`);
|
|
1436
1650
|
}
|
|
1437
1651
|
const currentWorkspace = tx.getReviewRoundWorkspace(task.id, round.id);
|
|
1438
1652
|
if (existing !== null
|
|
@@ -1497,8 +1711,8 @@ export class FileTaskWorkspacePreparer {
|
|
|
1497
1711
|
|| previousWorkspace.root !== reviewRoot) {
|
|
1498
1712
|
throw new ReviewRoundWorkspaceEvidenceError(`Previous Task-final Review workspace cannot be continued: ${previous.id}.`);
|
|
1499
1713
|
}
|
|
1500
|
-
if (this.store.
|
|
1501
|
-
throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active
|
|
1714
|
+
if (this.store.getActiveRun(task.id, reviewer.name) !== null) {
|
|
1715
|
+
throw new ReviewRoundWorkspaceEvidenceError(`Reviewer Role has an active AgentRun: ${task.id}/${reviewer.name}.`);
|
|
1502
1716
|
}
|
|
1503
1717
|
const expected = new Map(frozenEntries.map((entry) => [entry.projectId, entry]));
|
|
1504
1718
|
if (previousWorkspace.entries.length !== expected.size) {
|
|
@@ -1565,7 +1779,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
1565
1779
|
|| currentRound.workspace !== undefined
|
|
1566
1780
|
|| !isDeepStrictEqual(currentRound.taskCandidate, round.taskCandidate)
|
|
1567
1781
|
|| tx.getReviewRoundWorkspace(task.id, round.id) !== null
|
|
1568
|
-
|| tx.
|
|
1782
|
+
|| tx.getActiveRun(task.id, reviewer.name) !== null) {
|
|
1569
1783
|
throw new ReviewRoundWorkspaceEvidenceError(`Task-final Review workspace changed before reassignment: ${previous.id}/${round.id}.`);
|
|
1570
1784
|
}
|
|
1571
1785
|
const latestReviewer = tx.getRole(task.id, reviewer.name);
|
|
@@ -1693,7 +1907,7 @@ export class FileTaskWorkspacePreparer {
|
|
|
1693
1907
|
async cleanupWorkItemWorkspace(taskId, workItemId, disposition) {
|
|
1694
1908
|
const item = requireWorkItem(this.store, taskId, workItemId);
|
|
1695
1909
|
const task = requireTask(this.store, item.taskId);
|
|
1696
|
-
if (!["
|
|
1910
|
+
if (!["accepted", "retired"].includes(item.status)) {
|
|
1697
1911
|
throw new Error(`Work item must be terminal before cleanup: ${item.id}.`);
|
|
1698
1912
|
}
|
|
1699
1913
|
if (item.workspaceDisposition !== undefined && item.workspaceDisposition !== disposition) {
|
|
@@ -1985,10 +2199,17 @@ export class FileTaskWorkspacePreparer {
|
|
|
1985
2199
|
});
|
|
1986
2200
|
}
|
|
1987
2201
|
}
|
|
1988
|
-
function recordTaskActivation(store, previous, active, now) {
|
|
1989
|
-
|
|
2202
|
+
function recordTaskActivation(store, previous, active, now, actor) {
|
|
2203
|
+
// Activation is the explicit request to enter delivery, not an ordinary
|
|
2204
|
+
// Leader-local edit. Preserve that transition for the replacement Session.
|
|
2205
|
+
enqueueWork(store, { kind: "role", taskId: active.id, roleName: LEADER_ROLE }, "execution-started", now, [{ type: "task", id: active.id }]);
|
|
1990
2206
|
enqueueWork(store, { kind: "task", taskId: active.id }, "task-activated", now, [{ type: "task", id: active.id }]);
|
|
1991
|
-
store.saveEvent(active.id, createTaskEvent(store.nextEventId(active.id), active.id, "task.activated", { fromStatus: previous.status, status: active.status }, now));
|
|
2207
|
+
store.saveEvent(active.id, createTaskEvent(store.nextEventId(active.id), active.id, "task.activated", { fromStatus: previous.status, status: active.status, by: actor }, now));
|
|
2208
|
+
}
|
|
2209
|
+
function planningLeaderSession(store, taskId) {
|
|
2210
|
+
const sessions = store.getTaskRoleSessionSet(taskId, "leader");
|
|
2211
|
+
const current = sessions?.sessions[sessions.activeAgentId];
|
|
2212
|
+
return current?.status === "active" && current.effective.executionAuthority === "planning" ? current : undefined;
|
|
1992
2213
|
}
|
|
1993
2214
|
function requireTask(store, taskId) {
|
|
1994
2215
|
const task = store.getTask(taskId);
|
|
@@ -1997,7 +2218,7 @@ function requireTask(store, taskId) {
|
|
|
1997
2218
|
return task;
|
|
1998
2219
|
}
|
|
1999
2220
|
function assertTaskArchiveState(current, expected) {
|
|
2000
|
-
if ((current.status !== "completed" && current.status !== "
|
|
2221
|
+
if ((current.status !== "completed" && current.status !== "cancelled")
|
|
2001
2222
|
|| !isDeepStrictEqual(current, expected)) {
|
|
2002
2223
|
throw new WorkspaceCleanupBlockedError("task-changed", `task:${expected.id}`, true, `Task changed during archive cleanup: ${expected.id}.`);
|
|
2003
2224
|
}
|
|
@@ -2030,9 +2251,9 @@ function executionLaneLineage(store, task, executionGroupId, executionLaneId, hi
|
|
|
2030
2251
|
throw new Error(`ReviewRound not found: ${hint.reviewRoundId}.`);
|
|
2031
2252
|
return { purpose: "review", reviewRoundId: round.id };
|
|
2032
2253
|
}
|
|
2033
|
-
// Prefer the active
|
|
2034
|
-
// first queued WorkItem only when no active
|
|
2035
|
-
const activeLaneRun = store.
|
|
2254
|
+
// Prefer the active AgentRun's exact WorkItem for this Lane; fall back to the
|
|
2255
|
+
// first queued WorkItem only when no active AgentRun owns the Lane.
|
|
2256
|
+
const activeLaneRun = store.listRuns(task.id)
|
|
2036
2257
|
.find((run) => run.status === "active"
|
|
2037
2258
|
&& run.purpose === "execution"
|
|
2038
2259
|
&& run.executionGroupId === executionGroupId
|
|
@@ -2057,16 +2278,16 @@ function executionLaneLineage(store, task, executionGroupId, executionLaneId, hi
|
|
|
2057
2278
|
function assertWorkItemWorkspaceEligible(store, task, item) {
|
|
2058
2279
|
if (task.status !== "active")
|
|
2059
2280
|
throw new Error(`Task is not active: ${task.id}.`);
|
|
2060
|
-
if (["
|
|
2281
|
+
if (["accepted", "retired"].includes(item.status)) {
|
|
2061
2282
|
throw new Error(`Work item is already terminal: ${item.id}.`);
|
|
2062
2283
|
}
|
|
2063
|
-
const
|
|
2284
|
+
const activeDevelopRun = store.listRuns(task.id)
|
|
2064
2285
|
.find((run) => run.status === "active" && run.workItemId === item.id);
|
|
2065
|
-
if (
|
|
2066
|
-
throw new Error(`Work Item already has an active Develop
|
|
2286
|
+
if (activeDevelopRun !== undefined) {
|
|
2287
|
+
throw new Error(`Work Item already has an active Develop AgentRun: ${activeDevelopRun.id}.`);
|
|
2067
2288
|
}
|
|
2068
|
-
if (item.assignee !== undefined && store.
|
|
2069
|
-
throw new Error(`Role has an active
|
|
2289
|
+
if (item.assignee !== undefined && store.getActiveRun(task.id, item.assignee) !== null) {
|
|
2290
|
+
throw new Error(`Role has an active AgentRun: ${task.id}/${item.assignee}.`);
|
|
2070
2291
|
}
|
|
2071
2292
|
}
|
|
2072
2293
|
function assertWorkItemOwnsWorkspace(item, workspace) {
|
|
@@ -2086,27 +2307,78 @@ function preserveWorkspaceCreatedAt(workspace, previous) {
|
|
|
2086
2307
|
: { ...workspace, createdAt: previous.createdAt };
|
|
2087
2308
|
}
|
|
2088
2309
|
function assertWorkspaceSessionsRetirable(store, taskId, roleName, now) {
|
|
2089
|
-
if (store.
|
|
2090
|
-
throw new Error(`Role has an active
|
|
2310
|
+
if (store.getActiveRun(taskId, roleName) !== null) {
|
|
2311
|
+
throw new Error(`Role has an active AgentRun: ${taskId}/${roleName}.`);
|
|
2091
2312
|
}
|
|
2092
2313
|
const sessions = store.getTaskRoleSessionSet(taskId, roleName);
|
|
2093
2314
|
if (sessions !== null)
|
|
2094
2315
|
retireTaskRoleSessionsForWorkspace(sessions, now);
|
|
2095
2316
|
}
|
|
2096
2317
|
function retireWorkspaceBoundSession(store, taskId, roleName, now) {
|
|
2097
|
-
if (store.
|
|
2098
|
-
throw new Error(`Role has an active
|
|
2318
|
+
if (store.getActiveRun(taskId, roleName) !== null) {
|
|
2319
|
+
throw new Error(`Role has an active AgentRun: ${taskId}/${roleName}.`);
|
|
2099
2320
|
}
|
|
2100
2321
|
const sessions = store.getTaskRoleSessionSet(taskId, roleName);
|
|
2101
2322
|
if (sessions !== null) {
|
|
2102
2323
|
store.saveTaskRoleSessionSet(retireTaskRoleSessionsForWorkspace(sessions, now));
|
|
2103
2324
|
}
|
|
2104
2325
|
}
|
|
2326
|
+
/**
|
|
2327
|
+
* Hands a Draft planning Session over to the activated Task.
|
|
2328
|
+
*
|
|
2329
|
+
* A Leader plans and executes as the same logical Role, so activation never
|
|
2330
|
+
* changes the Leader or Task identity. It can change the *physical* launch
|
|
2331
|
+
* though: adopting a workspace or an execution environment moves the Session's
|
|
2332
|
+
* cwd, and a native conversation is bound to the cwd it started in. Such a
|
|
2333
|
+
* Session cannot continue, and that is not a failure to report — the plan lives
|
|
2334
|
+
* in the Task's own durable facts, so the next Turn takes over from persistent
|
|
2335
|
+
* Context instead of the old terminal or its full tool log.
|
|
2336
|
+
*
|
|
2337
|
+
* Ending the record here is what makes the takeover explicit rather than
|
|
2338
|
+
* silent: the next Leader wake resolves `new`, and the queued cleanup ends the
|
|
2339
|
+
* physical Host that the ended record no longer represents. Without this, a
|
|
2340
|
+
* live planning Session would make workspace migration throw and leave the
|
|
2341
|
+
* Task an un-activatable Draft forever.
|
|
2342
|
+
*/
|
|
2343
|
+
function handOverPlanningSession(store, taskId, now) {
|
|
2344
|
+
const sessions = store.getTaskRoleSessionSet(taskId, LEADER_ROLE);
|
|
2345
|
+
if (sessions === null)
|
|
2346
|
+
return;
|
|
2347
|
+
const live = Object.values(sessions.sessions).find(({ status }) => status !== "ended");
|
|
2348
|
+
if (live === undefined)
|
|
2349
|
+
return;
|
|
2350
|
+
// A planning Turn that is still running owns this Session. Deferred
|
|
2351
|
+
// activation is adopted only after it ends, so reaching here with an active
|
|
2352
|
+
// Turn means the caller raced it; fail closed rather than cut it off.
|
|
2353
|
+
if (store.getActiveRun(taskId, LEADER_ROLE) !== null) {
|
|
2354
|
+
throw new Error(`Leader has an active AgentRun while activating its Task: ${taskId}/${LEADER_ROLE}.`);
|
|
2355
|
+
}
|
|
2356
|
+
let next = sessions;
|
|
2357
|
+
for (const session of Object.values(sessions.sessions)) {
|
|
2358
|
+
if (session.status === "ended")
|
|
2359
|
+
continue;
|
|
2360
|
+
next = updateRoleAgentSessionStatus(next, session.agentId, "ended", now, "stopped");
|
|
2361
|
+
}
|
|
2362
|
+
store.saveTaskRoleSessionSet(next);
|
|
2363
|
+
// The durable record is ended; the physical Host still exists. Queue the
|
|
2364
|
+
// explicit detach so the runtime lane closes it instead of leaking it.
|
|
2365
|
+
enqueueWork(store, runtimeLifecycleTarget({ scope: "task", taskId, roleName: LEADER_ROLE }), RUNTIME_HOST_DETACH_REQUIRED_REASON, now, [{ type: "task", id: taskId }]);
|
|
2366
|
+
store.saveEvent(taskId, createTaskEvent(store.nextEventId(taskId), taskId, "task.planning-session-handover", {
|
|
2367
|
+
roleName: LEADER_ROLE,
|
|
2368
|
+
agentId: live.agentId,
|
|
2369
|
+
adapterId: live.adapterId,
|
|
2370
|
+
...(live.nativeSessionId === undefined
|
|
2371
|
+
? {}
|
|
2372
|
+
: { nativeSessionId: live.nativeSessionId }),
|
|
2373
|
+
reason: "activation-changed-physical-launch",
|
|
2374
|
+
continuity: "persistent-context"
|
|
2375
|
+
}, now));
|
|
2376
|
+
}
|
|
2105
2377
|
function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, workspace) {
|
|
2106
2378
|
if (role.taskId !== taskId
|
|
2107
2379
|
|| item.taskId !== taskId
|
|
2108
2380
|
|| item.assignee !== role.name
|
|
2109
|
-
|| ["
|
|
2381
|
+
|| ["accepted", "retired"].includes(item.status)
|
|
2110
2382
|
|| workspace.owner.type !== "work-item"
|
|
2111
2383
|
|| workspace.owner.taskId !== taskId
|
|
2112
2384
|
|| workspace.owner.workItemId !== item.id)
|
|
@@ -2117,14 +2389,15 @@ function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, wo
|
|
|
2117
2389
|
.sort();
|
|
2118
2390
|
if (!isDeepStrictEqual(writableProjects, [...item.writeProjectIds].sort()))
|
|
2119
2391
|
return false;
|
|
2120
|
-
const run = store.
|
|
2392
|
+
const run = store.getActiveRun(taskId, role.name);
|
|
2393
|
+
const identity = taskRoleRuntimeIdentity(role, run);
|
|
2121
2394
|
if (run === null
|
|
2122
2395
|
|| run.status !== "active"
|
|
2123
2396
|
|| run.purpose !== "execution"
|
|
2124
2397
|
|| run.workItemId !== item.id
|
|
2125
2398
|
|| run.workspace === undefined
|
|
2126
2399
|
|| !sameManagedWorkspaceIdentity(run.workspace, workspace)
|
|
2127
|
-
|| run.effective.agentId !==
|
|
2400
|
+
|| run.effective.agentId !== identity.agentId
|
|
2128
2401
|
|| !sameEffectiveWorkspace(run.effective.workspace, workspace))
|
|
2129
2402
|
return false;
|
|
2130
2403
|
const sessions = store.getTaskRoleSessionSet(taskId, role.name);
|
|
@@ -2133,13 +2406,12 @@ function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, wo
|
|
|
2133
2406
|
|| sessions.owner.scope !== "task"
|
|
2134
2407
|
|| sessions.owner.taskId !== taskId
|
|
2135
2408
|
|| sessions.owner.roleName !== role.name
|
|
2136
|
-
|| sessions.activeAgentId !==
|
|
2409
|
+
|| sessions.activeAgentId !== identity.agentId
|
|
2137
2410
|
|| session === undefined
|
|
2138
|
-
|| session.agentId !==
|
|
2411
|
+
|| session.agentId !== identity.agentId
|
|
2139
2412
|
|| session.adapterId !== run.effective.adapterId
|
|
2140
|
-
|| session.runtimeGenerationId === undefined
|
|
2141
2413
|
|| session.nativeSessionId === undefined
|
|
2142
|
-
||
|
|
2414
|
+
|| session.status !== "active"
|
|
2143
2415
|
|| !isDeepStrictEqual(session.effective, run.effective)
|
|
2144
2416
|
|| !sameEffectiveWorkspace(session.effective.workspace, workspace))
|
|
2145
2417
|
return false;
|
|
@@ -2150,17 +2422,6 @@ function canCorrectActiveWorkItemRoleWorkspaceHint(store, taskId, role, item, wo
|
|
|
2150
2422
|
}));
|
|
2151
2423
|
if (hasRuntimeCleanupObligation(lifecycleMailbox))
|
|
2152
2424
|
return false;
|
|
2153
|
-
const lifecycle = lifecycleMailbox?.processing;
|
|
2154
|
-
if (lifecycle !== null
|
|
2155
|
-
&& lifecycle !== undefined
|
|
2156
|
-
&& isRuntimeLaunchReservation(lifecycle)) {
|
|
2157
|
-
const executionRef = lifecycle.executionRef;
|
|
2158
|
-
if (!isRuntimeLaunchReservation(lifecycle, session.runtimeGenerationId)
|
|
2159
|
-
|| executionRef?.type !== "turn"
|
|
2160
|
-
|| executionRef.taskId !== taskId
|
|
2161
|
-
|| executionRef.id !== run.id)
|
|
2162
|
-
return false;
|
|
2163
|
-
}
|
|
2164
2425
|
return true;
|
|
2165
2426
|
}
|
|
2166
2427
|
function sameEffectiveWorkspace(effective, workspace) {
|