@zq-silk/yui 0.15.7 → 0.15.9
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 +194 -399
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +306 -1131
- 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/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/brief/taskBrief.js +12 -0
- package/dist/cli/agentConfigurationPicker.js +13 -0
- package/dist/cli/commandCatalog.js +165 -74
- 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 +245 -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 +34 -9
- 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 +281 -0
- package/dist/commands/taskActor.js +28 -49
- package/dist/commands/taskCommands.js +1461 -720
- 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} +158 -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 +481 -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 +120 -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 +255 -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 +118 -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 +388 -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 +75 -0
- package/dist/resources/projectResourceService.js +343 -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 +3 -7
- package/dist/storage/migrations/agentRunContract.js +159 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/sqliteSchema.js +467 -7
- package/dist/storage/sqliteStore.js +355 -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 +327 -0
- package/dist/task/taskActivationService.js +408 -0
- package/dist/task/taskRecordReference.js +5 -4
- package/dist/task/taskRecordRetirement.js +1 -1
- package/dist/task/taskSubmission.js +236 -0
- 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 +121 -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 +442 -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 +133 -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 +84 -48
- package/dist/workspace/workItemChangeSetManager.js +16 -9
- package/docs/agent-result-consumption.md +96 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +93 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +50 -0
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -0
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +224 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +83 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +393 -0
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +165 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +305 -0
- package/docs/release-workflow.zh-CN.md +237 -0
- package/docs/roles-and-configuration.md +115 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +78 -0
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -0
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +105 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +8 -6
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +88 -0
- package/docs/testing/verification-levels.zh-CN.md +69 -0
- package/i18n/README.zh-CN.md +270 -722
- package/package.json +3 -2
- package/skills/yui-leader/SKILL.md +88 -304
- package/skills/yui-leader/references/execution.md +303 -0
- package/skills/yui-leader/references/integration.md +39 -0
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/replicated-execution.md +42 -0
- package/skills/yui-leader/references/task-plugins.md +37 -0
- package/skills/yui-operator/SKILL.md +46 -62
- 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
|
@@ -38,21 +38,33 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
38
38
|
import { join } from "node:path";
|
|
39
39
|
import { isDeepStrictEqual } from "node:util";
|
|
40
40
|
import Database from "better-sqlite3";
|
|
41
|
+
import { validatePluginValidation } from "../plugins/pluginPackage.js";
|
|
42
|
+
import { validatePluginIntent, validatePluginIntentFailure } from "../plugins/pluginIntent.js";
|
|
43
|
+
import { validateLocalResource, validateEnvironmentPreparation } from "../resources/projectResource.js";
|
|
44
|
+
import { validateConfiguredAgent } from "../agent/agent.js";
|
|
45
|
+
import { validateTaskBrief } from "../brief/taskBrief.js";
|
|
41
46
|
import { consumePendingBatch, mailboxTargetKey, validateWorkMailbox } from "../coordination/workMailbox.js";
|
|
42
47
|
import { validateContextSnapshot } from "../context/contextSnapshot.js";
|
|
48
|
+
import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
43
49
|
import { compareRuntimeSessionCandidates, projectRuntimeSessionCandidate } from "../runtime/runtimeSessionCandidate.js";
|
|
50
|
+
import { sessionOwnerProcessKey } from "../runtime/sessionOwnerIdentity.js";
|
|
44
51
|
import { validateReviewRound } from "../review/reviewRound.js";
|
|
52
|
+
import { validateProject } from "../repository/project.js";
|
|
45
53
|
import { generateHomeIdentity, validateHomeIdentity } from "../repository/homeIdentity.js";
|
|
46
54
|
import { validateIntegrationQueueEntry } from "../integration/integrationQueueEntry.js";
|
|
47
55
|
import { validDurableJobTransition, validateDurableJob } from "../job/durableJob.js";
|
|
56
|
+
import { validateGlobalRole, validateTaskRole } from "../role/role.js";
|
|
48
57
|
import { validateTaskWake } from "../scheduler/taskWake.js";
|
|
58
|
+
import { validateTask } from "../task/task.js";
|
|
59
|
+
import { pendingCompletionMessages } from "../task/completionReadiness.js";
|
|
49
60
|
import { operationalTaskRecords, TASK_RECORD_RETIRED_EVENT } from "../task/taskRecordRetirement.js";
|
|
50
61
|
import { TASK_RECORD_ID_PREFIXES } from "../task/taskRecordReference.js";
|
|
62
|
+
import { validateWorkItem } from "../workItem/workItem.js";
|
|
51
63
|
import { managedWorkspaceKey } from "../worktree/managedWorkspace.js";
|
|
52
|
-
import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION,
|
|
64
|
+
import { CURRENT_CONFIG_SCHEMA_VERSION, CURRENT_WORK_MAILBOX_SCHEMA_VERSION, executionLaneActiveRunKey, executionLaneActiveRunKeyParts, StorageConflictError, StorageCancelledError, StorageRecordError, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, pendingWakeupProjection, validateYuiConfig } from "./taskStore.js";
|
|
53
65
|
import { publicationExternalKey } from "../task/publicationReference.js";
|
|
54
66
|
import { gateArtifactKey, validateGateArtifact } from "../verification/gateArtifact.js";
|
|
55
|
-
import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError,
|
|
67
|
+
import { inspectSqliteSchemaMigrations, migrateSqliteSchema, SqliteSchemaMigrationError, TELEMETRY_KEEP_PER_RUN, TELEMETRY_RUN_CAP } from "./sqliteSchema.js";
|
|
56
68
|
import { StorageSchemaError } from "./storageSchema.js";
|
|
57
69
|
/** Read the immutable Home identity without opening a writable Store connection. */
|
|
58
70
|
export function readSqliteHomeIdentity(rootDir, databaseFilename = "yui.db") {
|
|
@@ -113,6 +125,12 @@ function isUniqueConstraint(error) {
|
|
|
113
125
|
&& error.code === "SQLITE_CONSTRAINT_PRIMARYKEY"
|
|
114
126
|
|| error?.code === "SQLITE_CONSTRAINT_UNIQUE";
|
|
115
127
|
}
|
|
128
|
+
/** One admission rule for every active-Turn write path in this store. */
|
|
129
|
+
function assertRunAdmission(task, run) {
|
|
130
|
+
if (task === null || !runPurposeAdmitsTaskState(run.purpose, task)) {
|
|
131
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
116
134
|
export class SqliteTaskStore {
|
|
117
135
|
#db;
|
|
118
136
|
#rootDir;
|
|
@@ -508,11 +526,29 @@ export class SqliteTaskStore {
|
|
|
508
526
|
// -- generic payload helpers ------------------------------------------------
|
|
509
527
|
#getPayload(table, where, params) {
|
|
510
528
|
const row = this.#db.prepare(`SELECT payload FROM ${table} WHERE ${where}`).get(...params);
|
|
511
|
-
return row === undefined ? null : this.#
|
|
529
|
+
return row === undefined ? null : this.#validateTaskPayload(table, row.payload);
|
|
512
530
|
}
|
|
513
531
|
#listPayload(table, where, params) {
|
|
514
532
|
const rows = this.#db.prepare(`SELECT payload FROM ${table} WHERE ${where}`).all(...params);
|
|
515
|
-
return rows.map((row) => this.#
|
|
533
|
+
return rows.map((row) => this.#validateTaskPayload(table, row.payload));
|
|
534
|
+
}
|
|
535
|
+
#validateTaskPayload(table, payload) {
|
|
536
|
+
const record = this.#parse(payload);
|
|
537
|
+
if (table === "task_records")
|
|
538
|
+
validateTask(record);
|
|
539
|
+
if (table === "work_items")
|
|
540
|
+
validateWorkItem(record);
|
|
541
|
+
// Agents and Roles carry the execution component, and their validators
|
|
542
|
+
// refuse a record that lost it. Reading them unchecked defeated that: a
|
|
543
|
+
// component-less row reached display and rendered `undefined` as if it
|
|
544
|
+
// were a product, and reached launch as a binding nothing had vetted.
|
|
545
|
+
if (table === "configured_agents")
|
|
546
|
+
validateConfiguredAgent(record);
|
|
547
|
+
if (table === "task_roles")
|
|
548
|
+
validateTaskRole(record);
|
|
549
|
+
if (table === "global_roles")
|
|
550
|
+
validateGlobalRole(record);
|
|
551
|
+
return record;
|
|
516
552
|
}
|
|
517
553
|
#sortById(rows, idOf) {
|
|
518
554
|
return [...rows].sort((left, right) => numericCompare(idOf(left), idOf(right)));
|
|
@@ -578,24 +614,130 @@ export class SqliteTaskStore {
|
|
|
578
614
|
});
|
|
579
615
|
}
|
|
580
616
|
// -- projects ---------------------------------------------------------------
|
|
617
|
+
savePluginValidation(validation) {
|
|
618
|
+
validatePluginValidation(validation);
|
|
619
|
+
this.#mutate(() => {
|
|
620
|
+
const previous = this.getPluginValidation(validation.taskId, validation.id);
|
|
621
|
+
if (previous !== null) {
|
|
622
|
+
if (!isDeepStrictEqual(previous, validation))
|
|
623
|
+
throw new StorageRecordError("Plugin validation is immutable.");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
this.#db.prepare("INSERT INTO plugin_validations (task_id, id, payload) VALUES (?, ?, ?)")
|
|
627
|
+
.run(validation.taskId, validation.id, this.#json(validation));
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
savePluginIntent(intent) {
|
|
631
|
+
validatePluginIntent(intent);
|
|
632
|
+
this.#mutate(() => {
|
|
633
|
+
const previous = this.getPluginIntent(intent.taskId, intent.pluginId);
|
|
634
|
+
if (intent.revision !== (previous?.revision ?? 0) + 1 || intent.lastFailure !== undefined) {
|
|
635
|
+
throw new StorageRecordError("Plugin intent must be a new explicit choice without historical failure.");
|
|
636
|
+
}
|
|
637
|
+
if (intent.validationId !== undefined) {
|
|
638
|
+
const validation = this.getPluginValidation(intent.taskId, intent.validationId);
|
|
639
|
+
if (validation?.package.manifest.id !== intent.pluginId)
|
|
640
|
+
throw new StorageRecordError("Plugin validation is outside this intent.");
|
|
641
|
+
}
|
|
642
|
+
this.#db.prepare(`INSERT INTO plugin_intents (task_id, plugin_id, payload) VALUES (?, ?, ?)
|
|
643
|
+
ON CONFLICT(task_id, plugin_id) DO UPDATE SET payload = excluded.payload`)
|
|
644
|
+
.run(intent.taskId, intent.pluginId, this.#json(intent));
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
getPluginIntent(taskId, pluginId) {
|
|
648
|
+
const value = this.#getPayload("plugin_intents", "task_id = ? AND plugin_id = ?", [taskId, pluginId]);
|
|
649
|
+
return value === null ? null : validatePluginIntent(value);
|
|
650
|
+
}
|
|
651
|
+
listPluginIntents(taskId) {
|
|
652
|
+
return this.#listPayload("plugin_intents", "task_id = ?", [taskId]).map(validatePluginIntent);
|
|
653
|
+
}
|
|
654
|
+
recordPluginIntentFailure(taskId, pluginId, revision, failure) {
|
|
655
|
+
validatePluginIntentFailure(failure);
|
|
656
|
+
return this.transaction(() => {
|
|
657
|
+
const current = this.getPluginIntent(taskId, pluginId);
|
|
658
|
+
if (current === null || current.revision !== revision)
|
|
659
|
+
return false;
|
|
660
|
+
this.#mutate(() => {
|
|
661
|
+
this.#db.prepare("UPDATE plugin_intents SET payload = ? WHERE task_id = ? AND plugin_id = ?")
|
|
662
|
+
.run(this.#json({ ...current, lastFailure: failure }), taskId, pluginId);
|
|
663
|
+
});
|
|
664
|
+
return true;
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
getPluginValidation(taskId, id) {
|
|
668
|
+
const value = this.#getPayload("plugin_validations", "task_id = ? AND id = ?", [taskId, id]);
|
|
669
|
+
return value === null ? null : validatePluginValidation(value);
|
|
670
|
+
}
|
|
671
|
+
saveLocalResource(resource) {
|
|
672
|
+
validateLocalResource(resource);
|
|
673
|
+
this.#mutate(() => {
|
|
674
|
+
const previous = this.getLocalResource(resource.id);
|
|
675
|
+
if (previous !== null) {
|
|
676
|
+
if (!isDeepStrictEqual(previous, resource))
|
|
677
|
+
throw new StorageRecordError("Resource identity is immutable.");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.#db.prepare("INSERT INTO local_resources (id, canonical_identity, payload) VALUES (?, ?, ?)")
|
|
681
|
+
.run(resource.id, `${resource.device}:${resource.inode}`, this.#json(resource));
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
getLocalResource(resourceId) {
|
|
685
|
+
const resource = this.#getPayload("local_resources", "id = ?", [resourceId]);
|
|
686
|
+
return resource === null ? null : validateLocalResource(resource);
|
|
687
|
+
}
|
|
688
|
+
listLocalResources() {
|
|
689
|
+
return this.#listPayload("local_resources", "1=1", []).map(validateLocalResource);
|
|
690
|
+
}
|
|
691
|
+
saveEnvironmentPreparation(preparation) {
|
|
692
|
+
validateEnvironmentPreparation(preparation);
|
|
693
|
+
this.#mutate(() => {
|
|
694
|
+
const previous = this.getEnvironmentPreparation(preparation.taskId, preparation.id);
|
|
695
|
+
if (previous) {
|
|
696
|
+
const { disposition: before, updatedAt: _beforeTime, releaseEvidence: _beforeEvidence, ...identity } = previous;
|
|
697
|
+
const { disposition: after, updatedAt: _afterTime, releaseEvidence: _afterEvidence, ...nextIdentity } = preparation;
|
|
698
|
+
if (!isDeepStrictEqual(identity, nextIdentity)
|
|
699
|
+
|| (before === "released" && !isDeepStrictEqual(previous, preparation))
|
|
700
|
+
|| (before === "adopted" && after === "prepared")) {
|
|
701
|
+
throw new StorageRecordError("Preparation ownership/identity cannot be rewritten.");
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
else if (preparation.disposition !== "prepared") {
|
|
705
|
+
throw new StorageRecordError("Environment must be prepared before adoption.");
|
|
706
|
+
}
|
|
707
|
+
this.#db.prepare(`INSERT INTO environment_preparations (task_id, id, payload) VALUES (?, ?, ?)
|
|
708
|
+
ON CONFLICT(task_id, id) DO UPDATE SET payload = excluded.payload`)
|
|
709
|
+
.run(preparation.taskId, preparation.id, this.#json(preparation));
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
getEnvironmentPreparation(taskId, preparationId) {
|
|
713
|
+
const preparation = this.#getPayload("environment_preparations", "task_id = ? AND id = ?", [taskId, preparationId]);
|
|
714
|
+
return preparation === null ? null : validateEnvironmentPreparation(preparation);
|
|
715
|
+
}
|
|
716
|
+
listEnvironmentPreparations(taskId) {
|
|
717
|
+
return this.#listPayload("environment_preparations", "task_id = ?", [taskId])
|
|
718
|
+
.map(validateEnvironmentPreparation);
|
|
719
|
+
}
|
|
581
720
|
nextProjectId() { return this.#nextGlobalId("project"); }
|
|
582
721
|
saveProject(project) {
|
|
722
|
+
validateProject(project);
|
|
583
723
|
this.#mutate(() => {
|
|
584
724
|
this.#db.prepare(`INSERT INTO projects (id, name, path, payload, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
585
725
|
ON CONFLICT(id) DO UPDATE SET name = excluded.name, path = excluded.path, payload = excluded.payload, updated_at = excluded.updated_at`).run(project.id, project.name, project.path, this.#json(project), project.createdAt, project.updatedAt);
|
|
586
726
|
});
|
|
587
727
|
}
|
|
588
728
|
createProjectIfAbsent(project) {
|
|
729
|
+
validateProject(project);
|
|
589
730
|
return this.#mutate(() => {
|
|
590
731
|
const result = this.#db.prepare("INSERT OR IGNORE INTO projects (id, name, path, payload, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(project.id, project.name, project.path, this.#json(project), project.createdAt, project.updatedAt);
|
|
591
732
|
return result.changes > 0 ? project : null;
|
|
592
733
|
});
|
|
593
734
|
}
|
|
594
735
|
listProjects() {
|
|
595
|
-
return this.#sortById(this.#listPayload("projects", "1=1", []), (project) => project.id);
|
|
736
|
+
return this.#sortById(this.#listPayload("projects", "1=1", []).map(validateProject), (project) => project.id);
|
|
596
737
|
}
|
|
597
738
|
getProject(id) {
|
|
598
|
-
|
|
739
|
+
const project = this.#getPayload("projects", "id = ?", [id]);
|
|
740
|
+
return project === null ? null : validateProject(project);
|
|
599
741
|
}
|
|
600
742
|
removeProject(id) {
|
|
601
743
|
return this.#mutate(() => {
|
|
@@ -609,17 +751,17 @@ export class SqliteTaskStore {
|
|
|
609
751
|
const boundTasks = this.listTasks().filter((task) => task.projectBindings.some((binding) => binding.projectId === projectId));
|
|
610
752
|
const activeTasks = boundTasks.filter((task) => task.status === "active");
|
|
611
753
|
const unresolvedWorkItemRefs = [];
|
|
612
|
-
const
|
|
754
|
+
const activeRunRefs = [];
|
|
613
755
|
const unresolvedIntegrationRefs = [];
|
|
614
756
|
for (const task of activeTasks) {
|
|
615
757
|
for (const workItem of this.listWorkItems(task.id)) {
|
|
616
|
-
if (workItem.status !== "
|
|
758
|
+
if (workItem.status !== "accepted" && workItem.status !== "retired") {
|
|
617
759
|
unresolvedWorkItemRefs.push(`${task.id}/${workItem.id}`);
|
|
618
760
|
}
|
|
619
761
|
}
|
|
620
|
-
for (const run of this.
|
|
762
|
+
for (const run of this.listRuns(task.id)) {
|
|
621
763
|
if (run.status === "active")
|
|
622
|
-
|
|
764
|
+
activeRunRefs.push(`${task.id}/${run.id}`);
|
|
623
765
|
}
|
|
624
766
|
for (const attempt of this.listIntegrationAttempts(task.id)) {
|
|
625
767
|
if (attempt.status === "running" || attempt.status === "blocked") {
|
|
@@ -632,7 +774,7 @@ export class SqliteTaskStore {
|
|
|
632
774
|
boundTaskIds: boundTasks.map(({ id }) => id),
|
|
633
775
|
activeTaskIds: activeTasks.map(({ id }) => id),
|
|
634
776
|
unresolvedWorkItemRefs,
|
|
635
|
-
|
|
777
|
+
activeRunRefs,
|
|
636
778
|
unresolvedIntegrationRefs
|
|
637
779
|
};
|
|
638
780
|
}
|
|
@@ -716,6 +858,7 @@ export class SqliteTaskStore {
|
|
|
716
858
|
// -- tasks ------------------------------------------------------------------
|
|
717
859
|
nextTaskId() { return this.#nextGlobalId("task"); }
|
|
718
860
|
saveTask(task) {
|
|
861
|
+
validateTask(task);
|
|
719
862
|
if (typeof task.id !== "string" || task.id.length === 0) {
|
|
720
863
|
throw new StorageRecordError("Task id is required.");
|
|
721
864
|
}
|
|
@@ -747,13 +890,13 @@ export class SqliteTaskStore {
|
|
|
747
890
|
if (task === null)
|
|
748
891
|
return null;
|
|
749
892
|
// One indexed query (idx_turns_role_status) covers both the active
|
|
750
|
-
//
|
|
893
|
+
// AgentRuns the projection waits on and the Leader AgentRuns the budget consumes.
|
|
751
894
|
const events = this.#sortById(this.#listPayload("events", "task_id = ? AND type IN (?, ?)", [
|
|
752
895
|
taskId,
|
|
753
896
|
TASK_RECORD_RETIRED_EVENT,
|
|
754
897
|
"review.completed"
|
|
755
898
|
]), (event) => event.id);
|
|
756
|
-
const runs = operationalTaskRecords(this.#sortById(this.#listPayload("turns", "task_id = ? AND (status = 'active' OR role_name = 'leader')", [taskId]), (run) => run.id), events, "
|
|
899
|
+
const runs = operationalTaskRecords(this.#sortById(this.#listPayload("turns", "task_id = ? AND (status = 'active' OR role_name = 'leader')", [taskId]), (run) => run.id), events, "run");
|
|
757
900
|
return {
|
|
758
901
|
task: {
|
|
759
902
|
id: task.id,
|
|
@@ -765,14 +908,17 @@ export class SqliteTaskStore {
|
|
|
765
908
|
workItems: this.#sortById(this.#listPayload("work_items", "task_id = ?", [taskId]), (item) => item.id),
|
|
766
909
|
changeSets: this.#sortById(this.#listPayload("change_sets", "task_id = ?", [taskId]), (changeSet) => changeSet.id),
|
|
767
910
|
integrations: this.#sortById(this.#listPayload("integration_attempts", "task_id = ?", [taskId]), (attempt) => attempt.id),
|
|
911
|
+
integrationJobs: this.listDurableJobs(taskId)
|
|
912
|
+
.filter(job => job.owner.kind === "integration-attempt")
|
|
913
|
+
.map(job => ({ id: job.id, status: job.status })),
|
|
768
914
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
769
915
|
reviewRounds: this.#sortById(this.#listPayload("review_rounds", "task_id = ?", [taskId]), (round) => round.id),
|
|
770
916
|
reviewConfig: this.getReviewConfig(),
|
|
771
917
|
openInputRequests: this.#sortById(this.#listPayload("input_requests", "task_id = ? AND status = 'open'", [taskId]), (request) => request.id),
|
|
772
|
-
|
|
773
|
-
|
|
918
|
+
activeRuns: runs.filter((run) => run.status === "active"),
|
|
919
|
+
leaderRuns: runs.filter((run) => run.roleName === "leader"),
|
|
774
920
|
reviewOutcomeEvidence: {
|
|
775
|
-
|
|
921
|
+
runs: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
|
|
776
922
|
}
|
|
777
923
|
};
|
|
778
924
|
}
|
|
@@ -782,6 +928,7 @@ export class SqliteTaskStore {
|
|
|
782
928
|
return null;
|
|
783
929
|
return {
|
|
784
930
|
...base,
|
|
931
|
+
pendingUserMessages: pendingCompletionMessages(this, taskId),
|
|
785
932
|
managedWorkspaces: this.#sortById(this.#listPayload("managed_workspaces", "task_id = ?", [taskId]), (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
786
933
|
durableJobs: this.#sortById(this.#listPayload("durable_jobs", "task_id = ?", [taskId]), (job) => job.id),
|
|
787
934
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
@@ -792,13 +939,41 @@ export class SqliteTaskStore {
|
|
|
792
939
|
const rows = this.#db.prepare("SELECT task_id FROM tasks_catalog WHERE is_active = 1 ORDER BY task_id").all();
|
|
793
940
|
return rows.map((row) => row.task_id);
|
|
794
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Draft Task ids that carry an active planning Turn.
|
|
944
|
+
*
|
|
945
|
+
* Bounded by the active-Turn pointers rather than Task history: a Draft
|
|
946
|
+
* appears only while one of its Roles actually holds an admitted planning
|
|
947
|
+
* Turn, so an ordinary Draft is never selected for execution phases.
|
|
948
|
+
*/
|
|
949
|
+
listPlanningDraftTaskIds() {
|
|
950
|
+
const rows = this.#db.prepare(`SELECT DISTINCT catalog.task_id AS task_id
|
|
951
|
+
FROM tasks_catalog AS catalog
|
|
952
|
+
JOIN active_turns AS pointers ON pointers.task_id = catalog.task_id
|
|
953
|
+
JOIN turns ON turns.task_id = pointers.task_id AND turns.turn_id = pointers.turn_id
|
|
954
|
+
WHERE catalog.status = 'draft'
|
|
955
|
+
AND turns.status = 'active'
|
|
956
|
+
AND json_extract(turns.payload, '$.purpose') = 'planning'
|
|
957
|
+
ORDER BY catalog.task_id`).all();
|
|
958
|
+
return rows.map((row) => row.task_id);
|
|
959
|
+
}
|
|
960
|
+
listPendingActivationRequestTaskIds() {
|
|
961
|
+
const rows = this.#db.prepare(`SELECT records.task_id AS task_id
|
|
962
|
+
FROM task_records AS records
|
|
963
|
+
JOIN tasks_catalog AS catalog ON catalog.task_id = records.task_id
|
|
964
|
+
WHERE catalog.status = 'draft'
|
|
965
|
+
AND json_extract(records.payload, '$.activationRequest.disposition') = 'pending'
|
|
966
|
+
ORDER BY records.task_id`).all();
|
|
967
|
+
return rows.map((row) => row.task_id);
|
|
968
|
+
}
|
|
795
969
|
getTaskBrief(taskId) {
|
|
796
970
|
const row = this.#db.prepare("SELECT brief FROM task_records WHERE task_id = ?").get(taskId);
|
|
797
971
|
if (row === undefined || row.brief === null)
|
|
798
972
|
return null;
|
|
799
|
-
return this.#parse(row.brief);
|
|
973
|
+
return validateTaskBrief(this.#parse(row.brief));
|
|
800
974
|
}
|
|
801
975
|
saveTaskBrief(taskId, brief) {
|
|
976
|
+
validateTaskBrief(brief);
|
|
802
977
|
this.#requireTask(taskId);
|
|
803
978
|
this.#mutate(() => {
|
|
804
979
|
this.#db.prepare("UPDATE task_records SET brief = ?, updated_at = ? WHERE task_id = ?")
|
|
@@ -932,40 +1107,24 @@ export class SqliteTaskStore {
|
|
|
932
1107
|
const row = this.#db.prepare("SELECT 1 FROM durable_jobs WHERE status IN ('queued', 'running') LIMIT 1").get();
|
|
933
1108
|
return row !== undefined;
|
|
934
1109
|
}
|
|
935
|
-
// -- job caller key hashes (rr13) -------------------------------------------
|
|
936
|
-
getJobCallerKeyHash(taskId, roleName, agentId) {
|
|
937
|
-
const row = this.#db.prepare("SELECT hash FROM job_caller_key_hashes WHERE task_id = ? AND role_name = ? AND agent_id = ?").get(taskId, roleName, agentId);
|
|
938
|
-
return row === undefined ? null : row.hash;
|
|
939
|
-
}
|
|
940
|
-
setJobCallerKeyHash(taskId, roleName, agentId, hash) {
|
|
941
|
-
this.#requireTask(taskId);
|
|
942
|
-
if (!/^[a-f0-9]{64}$/u.test(hash)) {
|
|
943
|
-
throw new StorageRecordError(`Job caller key hash is invalid: ${taskId}/${roleName}.`);
|
|
944
|
-
}
|
|
945
|
-
this.#mutate(() => {
|
|
946
|
-
this.#db.prepare(`INSERT INTO job_caller_key_hashes (task_id, role_name, agent_id, hash, updated_at)
|
|
947
|
-
VALUES (?, ?, ?, ?, ?)
|
|
948
|
-
ON CONFLICT(task_id, role_name, agent_id) DO UPDATE SET hash = excluded.hash, updated_at = excluded.updated_at`).run(taskId, roleName, agentId, hash, this.#now());
|
|
949
|
-
});
|
|
950
|
-
}
|
|
951
1110
|
// -- session owners (Issue 03) ----------------------------------------------
|
|
952
1111
|
saveSessionOwner(identity) {
|
|
953
1112
|
const owner = identity.owner;
|
|
954
1113
|
this.#mutate(() => {
|
|
955
1114
|
this.#db.prepare(`INSERT INTO session_owners
|
|
956
|
-
(
|
|
1115
|
+
(process_key, scope, task_id, role_name, agent_id, native_session_id,
|
|
957
1116
|
provider_root_pid, payload, recorded_at)
|
|
958
1117
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
959
|
-
ON CONFLICT(
|
|
1118
|
+
ON CONFLICT(process_key) DO UPDATE SET
|
|
960
1119
|
scope = excluded.scope, task_id = excluded.task_id,
|
|
961
1120
|
role_name = excluded.role_name, agent_id = excluded.agent_id,
|
|
962
1121
|
native_session_id = excluded.native_session_id,
|
|
963
1122
|
provider_root_pid = excluded.provider_root_pid,
|
|
964
|
-
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(identity
|
|
1123
|
+
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(sessionOwnerProcessKey(identity), owner.scope, owner.scope === "task" ? owner.taskId : null, owner.roleName, identity.agentId, identity.nativeSessionId ?? null, identity.providerRoot.pid, this.#json(identity), identity.recordedAt);
|
|
965
1124
|
});
|
|
966
1125
|
}
|
|
967
|
-
getSessionOwner(
|
|
968
|
-
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE
|
|
1126
|
+
getSessionOwner(processKey) {
|
|
1127
|
+
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE process_key = ?").get(processKey);
|
|
969
1128
|
return row === undefined ? null : this.#parse(row.payload);
|
|
970
1129
|
}
|
|
971
1130
|
listSessionOwners() {
|
|
@@ -977,9 +1136,9 @@ export class SqliteTaskStore {
|
|
|
977
1136
|
}
|
|
978
1137
|
return this.#listPayload("session_owners", "scope = 'task' AND task_id = ? AND role_name = ?", [owner.taskId, owner.roleName]);
|
|
979
1138
|
}
|
|
980
|
-
removeSessionOwner(
|
|
1139
|
+
removeSessionOwner(processKey) {
|
|
981
1140
|
this.#mutate(() => {
|
|
982
|
-
this.#db.prepare("DELETE FROM session_owners WHERE
|
|
1141
|
+
this.#db.prepare("DELETE FROM session_owners WHERE process_key = ?").run(processKey);
|
|
983
1142
|
});
|
|
984
1143
|
}
|
|
985
1144
|
// -- task roles -------------------------------------------------------------
|
|
@@ -1078,8 +1237,6 @@ export class SqliteTaskStore {
|
|
|
1078
1237
|
}
|
|
1079
1238
|
const predicates = [];
|
|
1080
1239
|
const parameters = [];
|
|
1081
|
-
if (query.cleanupRequiredOnly)
|
|
1082
|
-
predicates.push("cleanup_required = 1");
|
|
1083
1240
|
if (taskIds !== undefined) {
|
|
1084
1241
|
predicates.push("scope = 'task'");
|
|
1085
1242
|
predicates.push(`task_id IN (${taskIds.map(() => "?").join(", ")})`);
|
|
@@ -1093,8 +1250,7 @@ export class SqliteTaskStore {
|
|
|
1093
1250
|
? ""
|
|
1094
1251
|
: ` WHERE ${predicates.join(" AND ")}`;
|
|
1095
1252
|
const rows = this.#db.prepare(`SELECT scope, task_id, role_name, agent_id, adapter_id,
|
|
1096
|
-
native_session_id,
|
|
1097
|
-
cleanup_required
|
|
1253
|
+
native_session_id, session_updated_at
|
|
1098
1254
|
FROM runtime_session_candidates${where}`).all(...parameters);
|
|
1099
1255
|
const candidates = rows.map((row) => ({
|
|
1100
1256
|
owner: row.scope === "task"
|
|
@@ -1103,9 +1259,7 @@ export class SqliteTaskStore {
|
|
|
1103
1259
|
agentId: row.agent_id,
|
|
1104
1260
|
adapterId: row.adapter_id,
|
|
1105
1261
|
nativeSessionId: row.native_session_id,
|
|
1106
|
-
|
|
1107
|
-
sessionUpdatedAt: row.session_updated_at,
|
|
1108
|
-
cleanupRequired: row.cleanup_required === 1
|
|
1262
|
+
sessionUpdatedAt: row.session_updated_at
|
|
1109
1263
|
})).sort(compareRuntimeSessionCandidates);
|
|
1110
1264
|
for (const candidate of candidates) {
|
|
1111
1265
|
if (!this.#runtimeSessionCandidateMatchesSource(candidate)) {
|
|
@@ -1158,17 +1312,11 @@ export class SqliteTaskStore {
|
|
|
1158
1312
|
json_extract(active_session, '$.agentId') AS agent_id,
|
|
1159
1313
|
json_extract(active_session, '$.adapterId') AS adapter_id,
|
|
1160
1314
|
json_extract(active_session, '$.nativeSessionId') AS native_session_id,
|
|
1161
|
-
json_extract(active_session, '$.runtimeGenerationId') AS runtime_generation_id,
|
|
1162
1315
|
CASE
|
|
1163
1316
|
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1164
1317
|
THEN 1 ELSE 0
|
|
1165
1318
|
END AS is_active,
|
|
1166
|
-
json_extract(active_session, '$.updatedAt') AS session_updated_at
|
|
1167
|
-
CASE
|
|
1168
|
-
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1169
|
-
AND json_type(active_session, '$.runtimeGenerationId') = 'text'
|
|
1170
|
-
THEN 1 ELSE 0
|
|
1171
|
-
END AS cleanup_required
|
|
1319
|
+
json_extract(active_session, '$.updatedAt') AS session_updated_at
|
|
1172
1320
|
FROM active`).get(...source.parameters);
|
|
1173
1321
|
}
|
|
1174
1322
|
catch (error) {
|
|
@@ -1179,17 +1327,7 @@ export class SqliteTaskStore {
|
|
|
1179
1327
|
}
|
|
1180
1328
|
if (row === undefined)
|
|
1181
1329
|
return false;
|
|
1182
|
-
return row.owner_scope === candidate.owner.scope
|
|
1183
|
-
&& row.owner_task_id === (candidate.owner.scope === "task" ? candidate.owner.taskId : null)
|
|
1184
|
-
&& row.owner_role_name === candidate.owner.roleName
|
|
1185
|
-
&& row.active_agent_id === candidate.agentId
|
|
1186
|
-
&& row.agent_id === candidate.agentId
|
|
1187
|
-
&& row.adapter_id === candidate.adapterId
|
|
1188
|
-
&& row.native_session_id === candidate.nativeSessionId
|
|
1189
|
-
&& (row.runtime_generation_id ?? undefined) === candidate.runtimeGenerationId
|
|
1190
|
-
&& row.is_active === 1
|
|
1191
|
-
&& row.session_updated_at === candidate.sessionUpdatedAt
|
|
1192
|
-
&& row.cleanup_required === (candidate.cleanupRequired ? 1 : 0);
|
|
1330
|
+
return row.owner_scope === candidate.owner.scope && row.owner_task_id === (candidate.owner.scope === "task" ? candidate.owner.taskId : null) && row.owner_role_name === candidate.owner.roleName && row.active_agent_id === candidate.agentId && row.agent_id === candidate.agentId && row.adapter_id === candidate.adapterId && row.native_session_id === candidate.nativeSessionId && row.is_active === 1 && row.session_updated_at === candidate.sessionUpdatedAt;
|
|
1193
1331
|
}
|
|
1194
1332
|
saveRoleSessionSet(sessions) {
|
|
1195
1333
|
const taskId = sessions.owner.taskId;
|
|
@@ -1219,15 +1357,13 @@ export class SqliteTaskStore {
|
|
|
1219
1357
|
const taskId = candidate.owner.scope === "task" ? candidate.owner.taskId : "";
|
|
1220
1358
|
this.#db.prepare(`INSERT INTO runtime_session_candidates (
|
|
1221
1359
|
scope, task_id, role_name, agent_id, adapter_id, native_session_id,
|
|
1222
|
-
|
|
1223
|
-
) VALUES (?, ?, ?, ?, ?, ?,
|
|
1360
|
+
session_updated_at
|
|
1361
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1224
1362
|
ON CONFLICT(scope, task_id, role_name) DO UPDATE SET
|
|
1225
1363
|
agent_id = excluded.agent_id,
|
|
1226
1364
|
adapter_id = excluded.adapter_id,
|
|
1227
1365
|
native_session_id = excluded.native_session_id,
|
|
1228
|
-
|
|
1229
|
-
session_updated_at = excluded.session_updated_at,
|
|
1230
|
-
cleanup_required = excluded.cleanup_required`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.runtimeGenerationId ?? null, candidate.sessionUpdatedAt, candidate.cleanupRequired ? 1 : 0);
|
|
1366
|
+
session_updated_at = excluded.session_updated_at`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.sessionUpdatedAt);
|
|
1231
1367
|
}
|
|
1232
1368
|
#deleteRuntimeSessionCandidate(owner) {
|
|
1233
1369
|
this.#db.prepare(`DELETE FROM runtime_session_candidates
|
|
@@ -1242,6 +1378,7 @@ export class SqliteTaskStore {
|
|
|
1242
1378
|
return this.#sortById(this.#listPayload("work_items", "task_id = ?", [taskId]), (item) => item.id);
|
|
1243
1379
|
}
|
|
1244
1380
|
saveWorkItem(taskId, item) {
|
|
1381
|
+
validateWorkItem(item);
|
|
1245
1382
|
if (item.taskId !== taskId)
|
|
1246
1383
|
throw new StorageRecordError(`Work item belongs to another Task: ${item.taskId}`);
|
|
1247
1384
|
this.#requireTask(taskId);
|
|
@@ -1495,34 +1632,34 @@ export class SqliteTaskStore {
|
|
|
1495
1632
|
});
|
|
1496
1633
|
}
|
|
1497
1634
|
// -- turns -----------------------------------------------------------------
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
return this.#getPayload("turns", "task_id = ? AND turn_id = ?", [taskId,
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
return this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]), (
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
if (
|
|
1508
|
-
this.#requireTask(
|
|
1509
|
-
if (
|
|
1510
|
-
const round = this.getReviewRound(
|
|
1635
|
+
nextRunId(taskId) { return this.#nextTaskRecordId(taskId, "run"); }
|
|
1636
|
+
peekNextRunId(taskId) { return this.#peekTaskRecordId(taskId, "run"); }
|
|
1637
|
+
getRun(taskId, runId) {
|
|
1638
|
+
return this.#getPayload("turns", "task_id = ? AND turn_id = ?", [taskId, runId]);
|
|
1639
|
+
}
|
|
1640
|
+
listRuns(taskId) {
|
|
1641
|
+
return this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]), (run) => run.id);
|
|
1642
|
+
}
|
|
1643
|
+
saveRun(run) {
|
|
1644
|
+
if (run.taskId !== undefined)
|
|
1645
|
+
this.#requireTask(run.taskId);
|
|
1646
|
+
if (run.reviewRoundId !== undefined) {
|
|
1647
|
+
const round = this.getReviewRound(run.taskId, run.reviewRoundId);
|
|
1511
1648
|
if (round === null) {
|
|
1512
|
-
throw new StorageRecordError(`
|
|
1649
|
+
throw new StorageRecordError(`AgentRun ReviewRound not found: ${run.reviewRoundId}.`);
|
|
1513
1650
|
}
|
|
1514
1651
|
const roundWorkItemId = round.workItemId;
|
|
1515
1652
|
const laneRole = round.executionGroup?.lanes
|
|
1516
|
-
.find(({ id }) => id ===
|
|
1517
|
-
if (roundWorkItemId !==
|
|
1518
|
-
|| (round.reviewerRoleName !==
|
|
1519
|
-
throw new StorageRecordError(`
|
|
1653
|
+
.find(({ id }) => id === run.executionLaneId)?.roleName;
|
|
1654
|
+
if (roundWorkItemId !== run.workItemId
|
|
1655
|
+
|| (round.reviewerRoleName !== run.roleName && laneRole !== run.roleName)) {
|
|
1656
|
+
throw new StorageRecordError(`AgentRun does not match ReviewRound: ${run.id}.`);
|
|
1520
1657
|
}
|
|
1521
1658
|
}
|
|
1522
1659
|
this.#mutate(() => {
|
|
1523
1660
|
this.#db.prepare(`INSERT INTO turns (task_id, turn_id, role_name, status, payload, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
1524
1661
|
ON CONFLICT(task_id, turn_id) DO UPDATE SET role_name = excluded.role_name, status = excluded.status,
|
|
1525
|
-
payload = excluded.payload, updated_at = excluded.updated_at`).run(
|
|
1662
|
+
payload = excluded.payload, updated_at = excluded.updated_at`).run(run.taskId, run.id, run.roleName, run.status, this.#json(run), this.#now());
|
|
1526
1663
|
});
|
|
1527
1664
|
}
|
|
1528
1665
|
// -- review rounds ----------------------------------------------------------
|
|
@@ -1545,180 +1682,178 @@ export class SqliteTaskStore {
|
|
|
1545
1682
|
});
|
|
1546
1683
|
}
|
|
1547
1684
|
// -- active turns ----------------------------------------------------------
|
|
1548
|
-
#
|
|
1685
|
+
#saveActiveRun(taskId, pointer, runId) {
|
|
1549
1686
|
this.#mutate(() => {
|
|
1550
|
-
const payload = this.#json({ schemaVersion: 3,
|
|
1687
|
+
const payload = this.#json({ schemaVersion: 3, runId });
|
|
1551
1688
|
this.#db.prepare(`INSERT INTO active_turns (task_id, pointer, turn_id, payload, updated_at) VALUES (?, ?, ?, ?, ?)
|
|
1552
|
-
ON CONFLICT(task_id, pointer) DO UPDATE SET turn_id = excluded.turn_id, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, pointer,
|
|
1689
|
+
ON CONFLICT(task_id, pointer) DO UPDATE SET turn_id = excluded.turn_id, payload = excluded.payload, updated_at = excluded.updated_at`).run(taskId, pointer, runId, payload, this.#now());
|
|
1553
1690
|
});
|
|
1554
1691
|
}
|
|
1555
|
-
#
|
|
1692
|
+
#readActiveRunPointer(taskId, pointer) {
|
|
1556
1693
|
const row = this.#db.prepare("SELECT turn_id FROM active_turns WHERE task_id = ? AND pointer = ?").get(taskId, pointer);
|
|
1557
1694
|
if (row === undefined)
|
|
1558
1695
|
return null;
|
|
1559
1696
|
return {
|
|
1560
|
-
|
|
1561
|
-
|
|
1697
|
+
runId: row.turn_id,
|
|
1698
|
+
run: this.getRun(taskId, row.turn_id)
|
|
1562
1699
|
};
|
|
1563
1700
|
}
|
|
1564
|
-
#
|
|
1565
|
-
if (
|
|
1566
|
-
throw new StorageRecordError(`Active
|
|
1701
|
+
#assertActiveRunForWrite(run, lane) {
|
|
1702
|
+
if (run.status !== "active") {
|
|
1703
|
+
throw new StorageRecordError(`Active AgentRun must have active status: ${run.id}`);
|
|
1567
1704
|
}
|
|
1568
|
-
const hasGroup =
|
|
1569
|
-
const hasLane =
|
|
1705
|
+
const hasGroup = run.executionGroupId !== undefined;
|
|
1706
|
+
const hasLane = run.executionLaneId !== undefined;
|
|
1570
1707
|
if (hasGroup !== hasLane) {
|
|
1571
|
-
throw new StorageRecordError(`
|
|
1708
|
+
throw new StorageRecordError(`AgentRun execution lineage is incomplete: ${run.id}.`);
|
|
1572
1709
|
}
|
|
1573
1710
|
if (lane !== undefined
|
|
1574
|
-
&& (
|
|
1575
|
-
||
|
|
1576
|
-
throw new StorageRecordError(`Active
|
|
1711
|
+
&& (run.executionGroupId !== lane.executionGroupId
|
|
1712
|
+
|| run.executionLaneId !== lane.executionLaneId)) {
|
|
1713
|
+
throw new StorageRecordError(`Active AgentRun execution lineage does not match its Lane: ${run.id}`);
|
|
1577
1714
|
}
|
|
1578
1715
|
}
|
|
1579
|
-
#
|
|
1580
|
-
const pointer = this.#
|
|
1716
|
+
#getActiveRoleRun(taskId, roleName) {
|
|
1717
|
+
const pointer = this.#readActiveRunPointer(taskId, roleName);
|
|
1581
1718
|
if (pointer === null)
|
|
1582
1719
|
return null;
|
|
1583
1720
|
const task = this.getTask(taskId);
|
|
1584
1721
|
if (task === null) {
|
|
1585
|
-
throw new StorageRecordError(`Active
|
|
1722
|
+
throw new StorageRecordError(`Active AgentRun Task is missing: ${taskId}/${roleName}`);
|
|
1586
1723
|
}
|
|
1587
|
-
if (pointer.
|
|
1724
|
+
if (pointer.run === null) {
|
|
1588
1725
|
// Retired Tasks are an explicit historical isolation boundary. Their
|
|
1589
|
-
// retained pointer rows may intentionally reference a missing
|
|
1590
|
-
if (task.status === "
|
|
1726
|
+
// retained pointer rows may intentionally reference a missing AgentRun.
|
|
1727
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1591
1728
|
return null;
|
|
1592
|
-
throw new StorageRecordError(`Active
|
|
1593
|
-
}
|
|
1594
|
-
const
|
|
1595
|
-
if (task.status === "
|
|
1596
|
-
return
|
|
1597
|
-
if (
|
|
1598
|
-
||
|
|
1599
|
-
||
|
|
1600
|
-
||
|
|
1601
|
-
|| (
|
|
1602
|
-
throw new StorageRecordError(`Active
|
|
1603
|
-
}
|
|
1604
|
-
return
|
|
1605
|
-
}
|
|
1606
|
-
#
|
|
1607
|
-
const pointerKey =
|
|
1608
|
-
const pointer = this.#
|
|
1729
|
+
throw new StorageRecordError(`Active AgentRun pointer is dangling: ${taskId}/${roleName}`);
|
|
1730
|
+
}
|
|
1731
|
+
const run = pointer.run;
|
|
1732
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1733
|
+
return run;
|
|
1734
|
+
if (run.id !== pointer.runId
|
|
1735
|
+
|| run.taskId !== taskId
|
|
1736
|
+
|| run.roleName !== roleName
|
|
1737
|
+
|| run.status !== "active"
|
|
1738
|
+
|| (run.executionGroupId === undefined) !== (run.executionLaneId === undefined)) {
|
|
1739
|
+
throw new StorageRecordError(`Active AgentRun pointer is invalid: ${taskId}/${roleName}`);
|
|
1740
|
+
}
|
|
1741
|
+
return run;
|
|
1742
|
+
}
|
|
1743
|
+
#getActiveLaneRun(taskId, executionGroupId, executionLaneId) {
|
|
1744
|
+
const pointerKey = executionLaneActiveRunKey(executionGroupId, executionLaneId);
|
|
1745
|
+
const pointer = this.#readActiveRunPointer(taskId, pointerKey);
|
|
1609
1746
|
if (pointer === null)
|
|
1610
1747
|
return null;
|
|
1611
1748
|
const task = this.getTask(taskId);
|
|
1612
1749
|
if (task === null) {
|
|
1613
|
-
throw new StorageRecordError(`Active
|
|
1750
|
+
throw new StorageRecordError(`Active AgentRun Task is missing: ${taskId}/${pointerKey}`);
|
|
1614
1751
|
}
|
|
1615
|
-
if (pointer.
|
|
1616
|
-
if (task.status === "
|
|
1752
|
+
if (pointer.run === null) {
|
|
1753
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1617
1754
|
return null;
|
|
1618
|
-
throw new StorageRecordError(`Active
|
|
1755
|
+
throw new StorageRecordError(`Active AgentRun pointer is dangling: ${taskId}/${pointerKey}`);
|
|
1619
1756
|
}
|
|
1620
|
-
const
|
|
1621
|
-
if (task.status === "
|
|
1622
|
-
return
|
|
1623
|
-
if (
|
|
1624
|
-
||
|
|
1625
|
-
||
|
|
1626
|
-
||
|
|
1627
|
-
||
|
|
1628
|
-
throw new StorageRecordError(`Active
|
|
1757
|
+
const run = pointer.run;
|
|
1758
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1759
|
+
return run;
|
|
1760
|
+
if (run.id !== pointer.runId
|
|
1761
|
+
|| run.taskId !== taskId
|
|
1762
|
+
|| run.status !== "active"
|
|
1763
|
+
|| run.executionGroupId !== executionGroupId
|
|
1764
|
+
|| run.executionLaneId !== executionLaneId) {
|
|
1765
|
+
throw new StorageRecordError(`Active AgentRun pointer is invalid: ${taskId}/${pointerKey}`);
|
|
1629
1766
|
}
|
|
1630
|
-
return
|
|
1767
|
+
return run;
|
|
1631
1768
|
}
|
|
1632
|
-
#
|
|
1769
|
+
#clearActiveRun(taskId, pointer) {
|
|
1633
1770
|
this.#mutate(() => {
|
|
1634
1771
|
this.#db.prepare("DELETE FROM active_turns WHERE task_id = ? AND pointer = ?").run(taskId, pointer);
|
|
1635
1772
|
});
|
|
1636
1773
|
}
|
|
1637
|
-
|
|
1638
|
-
return this.#
|
|
1774
|
+
getActiveRun(taskId, roleName) {
|
|
1775
|
+
return this.#getActiveRoleRun(taskId, roleName);
|
|
1639
1776
|
}
|
|
1640
|
-
|
|
1641
|
-
if (
|
|
1642
|
-
this.
|
|
1777
|
+
saveActiveRun(run) {
|
|
1778
|
+
if (run.executionGroupId !== undefined || run.executionLaneId !== undefined) {
|
|
1779
|
+
this.saveActiveExecutionLaneRun(run);
|
|
1643
1780
|
return;
|
|
1644
1781
|
}
|
|
1645
1782
|
this.transaction((store) => {
|
|
1646
|
-
const task = store.getTask(
|
|
1647
|
-
|
|
1648
|
-
|
|
1783
|
+
const task = store.getTask(run.taskId);
|
|
1784
|
+
assertRunAdmission(task, run);
|
|
1785
|
+
this.#assertActiveRunForWrite(run);
|
|
1786
|
+
const current = store.getActiveRun(run.taskId, run.roleName);
|
|
1787
|
+
if (current !== null && current.id !== run.id) {
|
|
1788
|
+
throw new StorageRecordError(`Role already has an active AgentRun: ${run.taskId}/${run.roleName}`);
|
|
1649
1789
|
}
|
|
1650
|
-
|
|
1651
|
-
const current = store.getActiveTurn(turn.taskId, turn.roleName);
|
|
1652
|
-
if (current !== null && current.id !== turn.id) {
|
|
1653
|
-
throw new StorageRecordError(`Role already has an active Turn: ${turn.taskId}/${turn.roleName}`);
|
|
1654
|
-
}
|
|
1655
|
-
// Keep the Turn row and its active pointer in the same transaction. The
|
|
1790
|
+
// Keep the AgentRun row and its active pointer in the same transaction. The
|
|
1656
1791
|
// adapter may have already written the row; this upsert remains
|
|
1657
1792
|
// intentionally idempotent for that backend-neutral path.
|
|
1658
|
-
store.
|
|
1659
|
-
this.#
|
|
1793
|
+
store.saveRun(run);
|
|
1794
|
+
this.#saveActiveRun(run.taskId, run.roleName, run.id);
|
|
1660
1795
|
});
|
|
1661
1796
|
}
|
|
1662
|
-
|
|
1663
|
-
// A Role key can point at a lane-backed
|
|
1797
|
+
clearActiveRun(taskId, roleName) {
|
|
1798
|
+
// A Role key can point at a lane-backed AgentRun. Remove the matching lane
|
|
1664
1799
|
// pointer too while preserving every other lane for the same Role.
|
|
1665
1800
|
this.transaction(() => {
|
|
1666
|
-
const rolePointer = this.#
|
|
1667
|
-
this.#
|
|
1801
|
+
const rolePointer = this.#readActiveRunPointer(taskId, roleName);
|
|
1802
|
+
this.#clearActiveRun(taskId, roleName);
|
|
1668
1803
|
if (rolePointer === null)
|
|
1669
1804
|
return;
|
|
1670
1805
|
const laneRows = this.#db.prepare("SELECT pointer, turn_id FROM active_turns WHERE task_id = ?").all(taskId);
|
|
1671
1806
|
for (const row of laneRows) {
|
|
1672
|
-
if (
|
|
1673
|
-
&& row.turn_id === rolePointer.
|
|
1674
|
-
this.#
|
|
1807
|
+
if (executionLaneActiveRunKeyParts(row.pointer) !== null
|
|
1808
|
+
&& row.turn_id === rolePointer.runId) {
|
|
1809
|
+
this.#clearActiveRun(taskId, row.pointer);
|
|
1675
1810
|
}
|
|
1676
1811
|
}
|
|
1677
1812
|
});
|
|
1678
1813
|
}
|
|
1679
|
-
|
|
1680
|
-
return this.#
|
|
1814
|
+
getActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
|
|
1815
|
+
return this.#getActiveLaneRun(taskId, executionGroupId, executionLaneId);
|
|
1681
1816
|
}
|
|
1682
|
-
|
|
1683
|
-
if (
|
|
1684
|
-
throw new StorageRecordError(`Active execution-lane
|
|
1817
|
+
saveActiveExecutionLaneRun(run) {
|
|
1818
|
+
if (run.executionGroupId === undefined || run.executionLaneId === undefined) {
|
|
1819
|
+
throw new StorageRecordError(`Active execution-lane AgentRun requires group and lane ids: ${run.id}`);
|
|
1685
1820
|
}
|
|
1686
1821
|
this.transaction((store) => {
|
|
1687
|
-
const task = store.getTask(
|
|
1822
|
+
const task = store.getTask(run.taskId);
|
|
1688
1823
|
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1689
|
-
throw new StorageRecordError(`Task execution is not enabled: ${
|
|
1824
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
1690
1825
|
}
|
|
1691
|
-
const key =
|
|
1692
|
-
this.#
|
|
1693
|
-
executionGroupId:
|
|
1694
|
-
executionLaneId:
|
|
1826
|
+
const key = executionLaneActiveRunKey(run.executionGroupId, run.executionLaneId);
|
|
1827
|
+
this.#assertActiveRunForWrite(run, {
|
|
1828
|
+
executionGroupId: run.executionGroupId,
|
|
1829
|
+
executionLaneId: run.executionLaneId
|
|
1695
1830
|
});
|
|
1696
|
-
const current = store.
|
|
1697
|
-
if (current !== null && current.id !==
|
|
1698
|
-
throw new StorageRecordError(`Execution Lane already has an active
|
|
1831
|
+
const current = store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId);
|
|
1832
|
+
if (current !== null && current.id !== run.id) {
|
|
1833
|
+
throw new StorageRecordError(`Execution Lane already has an active AgentRun: ${run.taskId}/${key}`);
|
|
1699
1834
|
}
|
|
1700
|
-
const rolePointer = this.#
|
|
1701
|
-
store.
|
|
1702
|
-
this.#
|
|
1835
|
+
const rolePointer = this.#readActiveRunPointer(run.taskId, run.roleName);
|
|
1836
|
+
store.saveRun(run);
|
|
1837
|
+
this.#saveActiveRun(run.taskId, key, run.id);
|
|
1703
1838
|
// Keep the Role pointer for the single-lane delivery path, but never
|
|
1704
1839
|
// replace it when another Lane already owns that Role.
|
|
1705
1840
|
if (rolePointer === null) {
|
|
1706
|
-
this.#
|
|
1841
|
+
this.#saveActiveRun(run.taskId, run.roleName, run.id);
|
|
1707
1842
|
}
|
|
1708
1843
|
});
|
|
1709
1844
|
}
|
|
1710
|
-
|
|
1845
|
+
clearActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
|
|
1711
1846
|
this.transaction(() => {
|
|
1712
|
-
const key =
|
|
1713
|
-
const lanePointer = this.#
|
|
1714
|
-
this.#
|
|
1847
|
+
const key = executionLaneActiveRunKey(executionGroupId, executionLaneId);
|
|
1848
|
+
const lanePointer = this.#readActiveRunPointer(taskId, key);
|
|
1849
|
+
this.#clearActiveRun(taskId, key);
|
|
1715
1850
|
if (lanePointer === null)
|
|
1716
1851
|
return;
|
|
1717
1852
|
const roleRows = this.#db.prepare("SELECT pointer, turn_id FROM active_turns WHERE task_id = ?").all(taskId);
|
|
1718
1853
|
for (const row of roleRows) {
|
|
1719
|
-
if (
|
|
1720
|
-
&& row.turn_id === lanePointer.
|
|
1721
|
-
this.#
|
|
1854
|
+
if (executionLaneActiveRunKeyParts(row.pointer) === null
|
|
1855
|
+
&& row.turn_id === lanePointer.runId) {
|
|
1856
|
+
this.#clearActiveRun(taskId, row.pointer);
|
|
1722
1857
|
}
|
|
1723
1858
|
}
|
|
1724
1859
|
});
|
|
@@ -1862,7 +1997,7 @@ export class SqliteTaskStore {
|
|
|
1862
1997
|
turn_id = excluded.turn_id,
|
|
1863
1998
|
reasons = excluded.reasons,
|
|
1864
1999
|
payload = excluded.payload,
|
|
1865
|
-
consumed_at = excluded.consumed_at`).run(taskId, wake.id, wake.seq, wake.status, wake.
|
|
2000
|
+
consumed_at = excluded.consumed_at`).run(taskId, wake.id, wake.seq, wake.status, wake.runId ?? null, wake.fromCursor, wake.toCursor, this.#json([...wake.reasons]), this.#json(wake), wake.createdAt, wake.consumedAt ?? null);
|
|
1866
2001
|
this.#observeHighWater(taskId, "taskWake", seq);
|
|
1867
2002
|
});
|
|
1868
2003
|
}
|
|
@@ -1880,7 +2015,8 @@ export class SqliteTaskStore {
|
|
|
1880
2015
|
// -- high-water maintenance ---------------------------------------------------
|
|
1881
2016
|
/** Extract the numeric suffix of a `<prefix>-<n>` record id. */
|
|
1882
2017
|
#idSequence(id, kind) {
|
|
1883
|
-
const
|
|
2018
|
+
const prefix = kind === "run" ? "(?:run|turn)" : TASK_RECORD_ID_PREFIXES[kind];
|
|
2019
|
+
const match = new RegExp(`^${prefix}-(\\d+)$`).exec(id);
|
|
1884
2020
|
if (match === null)
|
|
1885
2021
|
throw new StorageRecordError(`Task-local ${kind} id is invalid: ${id}.`);
|
|
1886
2022
|
return Number.parseInt(match[1], 10);
|
|
@@ -2050,76 +2186,75 @@ export class SqliteTaskStore {
|
|
|
2050
2186
|
}
|
|
2051
2187
|
// -- telemetry (§4.4) -----------------------------------------------------------
|
|
2052
2188
|
/**
|
|
2053
|
-
* Upsert one progress row. The PK is (task_id, role_name, turn_id,
|
|
2189
|
+
* Upsert one progress row. The PK is (task_id, role_name, turn_id,
|
|
2054
2190
|
* progress_id): a repeated progress id updates in place, so a high-frequency
|
|
2055
2191
|
* runtime telemetry observation is a single-row write that never
|
|
2056
2192
|
* rewrites global state or another Task's rows.
|
|
2057
2193
|
*/
|
|
2058
2194
|
upsertTelemetryProgress(entry) {
|
|
2059
2195
|
this.#mutate(() => {
|
|
2060
|
-
this.#db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id,
|
|
2061
|
-
VALUES (?, ?, ?, ?, ?, ?,
|
|
2062
|
-
ON CONFLICT(task_id, role_name, turn_id,
|
|
2063
|
-
DO UPDATE SET sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at`).run(entry.taskId, entry.roleName, entry.
|
|
2196
|
+
this.#db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, progress_id, sequence, payload, received_at)
|
|
2197
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2198
|
+
ON CONFLICT(task_id, role_name, turn_id, progress_id)
|
|
2199
|
+
DO UPDATE SET sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at`).run(entry.taskId, entry.roleName, entry.runId, entry.progressId, entry.sequence ?? null, this.#json(entry.payload), entry.receivedAt);
|
|
2064
2200
|
});
|
|
2065
2201
|
}
|
|
2066
|
-
listTelemetry(taskId,
|
|
2067
|
-
const rows =
|
|
2068
|
-
? this.#db.prepare("SELECT task_id, role_name, turn_id,
|
|
2069
|
-
: this.#db.prepare("SELECT task_id, role_name, turn_id,
|
|
2202
|
+
listTelemetry(taskId, runId) {
|
|
2203
|
+
const rows = runId === undefined
|
|
2204
|
+
? this.#db.prepare("SELECT task_id, role_name, turn_id, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? ORDER BY received_at").all(taskId)
|
|
2205
|
+
: this.#db.prepare("SELECT task_id, role_name, turn_id, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? AND turn_id = ? ORDER BY received_at").all(taskId, runId);
|
|
2070
2206
|
return rows
|
|
2071
2207
|
.map((row) => ({
|
|
2072
2208
|
taskId: row.task_id,
|
|
2073
2209
|
roleName: row.role_name,
|
|
2074
|
-
|
|
2075
|
-
generation: row.generation,
|
|
2210
|
+
runId: row.turn_id,
|
|
2076
2211
|
progressId: row.progress_id,
|
|
2077
2212
|
sequence: row.sequence ?? undefined,
|
|
2078
2213
|
payload: this.#parse(row.payload),
|
|
2079
2214
|
receivedAt: row.received_at
|
|
2080
2215
|
}));
|
|
2081
2216
|
}
|
|
2082
|
-
countTelemetry(taskId,
|
|
2083
|
-
const row =
|
|
2217
|
+
countTelemetry(taskId, runId) {
|
|
2218
|
+
const row = runId === undefined
|
|
2084
2219
|
? this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ?").get(taskId)
|
|
2085
|
-
: this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND turn_id = ?").get(taskId,
|
|
2220
|
+
: this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND turn_id = ?").get(taskId, runId);
|
|
2086
2221
|
return row.n;
|
|
2087
2222
|
}
|
|
2088
2223
|
/**
|
|
2089
2224
|
* Bounded retention (§4.4): keep the newest `keep` rows per
|
|
2090
|
-
* (task, role,
|
|
2225
|
+
* (task, role, AgentRun) and delete older ones. The DELETE is scoped
|
|
2091
2226
|
* by task_id; it never rewrites global rows or other Tasks. Returns the number
|
|
2092
2227
|
* of rows deleted. Terminal/semantic events go to `events` and are never pruned.
|
|
2093
2228
|
*/
|
|
2094
|
-
pruneTelemetry(taskId, roleName,
|
|
2229
|
+
pruneTelemetry(taskId, roleName, runId, keep = TELEMETRY_KEEP_PER_RUN) {
|
|
2095
2230
|
return this.#mutate(() => {
|
|
2096
2231
|
const result = this.#db.prepare(`DELETE FROM telemetry
|
|
2097
|
-
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2098
|
-
AND (task_id, role_name, turn_id,
|
|
2099
|
-
SELECT task_id, role_name, turn_id,
|
|
2232
|
+
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2233
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
2234
|
+
SELECT task_id, role_name, turn_id, progress_id
|
|
2100
2235
|
FROM telemetry
|
|
2101
|
-
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2102
|
-
ORDER BY COALESCE(sequence, -1) DESC,
|
|
2236
|
+
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2237
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
|
|
2103
2238
|
LIMIT ?
|
|
2104
|
-
)`).run(taskId, roleName,
|
|
2239
|
+
)`).run(taskId, roleName, runId, taskId, roleName, runId, keep);
|
|
2105
2240
|
return result.changes;
|
|
2106
2241
|
});
|
|
2107
2242
|
}
|
|
2108
2243
|
/**
|
|
2109
|
-
* Hard cap for an active
|
|
2244
|
+
* Hard cap for an active AgentRun (§4.4): trim oldest rows across the AgentRun beyond
|
|
2110
2245
|
* `cap` (default 50k). Returns the number of rows deleted.
|
|
2111
2246
|
*/
|
|
2112
|
-
|
|
2247
|
+
capTelemetryRun(taskId, runId, cap = TELEMETRY_RUN_CAP) {
|
|
2113
2248
|
return this.#mutate(() => {
|
|
2114
2249
|
const result = this.#db.prepare(`DELETE FROM telemetry
|
|
2115
2250
|
WHERE task_id = ? AND turn_id = ?
|
|
2116
|
-
AND (task_id, role_name, turn_id,
|
|
2117
|
-
SELECT task_id, role_name, turn_id,
|
|
2251
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
2252
|
+
SELECT task_id, role_name, turn_id, progress_id
|
|
2118
2253
|
FROM telemetry
|
|
2119
2254
|
WHERE task_id = ? AND turn_id = ?
|
|
2120
|
-
ORDER BY COALESCE(sequence, -1) DESC,
|
|
2255
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
|
|
2121
2256
|
LIMIT ?
|
|
2122
|
-
)`).run(taskId,
|
|
2257
|
+
)`).run(taskId, runId, taskId, runId, cap);
|
|
2123
2258
|
return result.changes;
|
|
2124
2259
|
});
|
|
2125
2260
|
}
|