@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
|
@@ -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 { validateArtifact, 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,150 @@ export class SqliteTaskStore {
|
|
|
578
614
|
});
|
|
579
615
|
}
|
|
580
616
|
// -- projects ---------------------------------------------------------------
|
|
617
|
+
saveArtifact(artifact) {
|
|
618
|
+
validateArtifact(artifact);
|
|
619
|
+
this.#mutate(() => {
|
|
620
|
+
const previous = this.getArtifact(artifact.taskId, artifact.id);
|
|
621
|
+
if (previous !== null) {
|
|
622
|
+
if (!isDeepStrictEqual(previous, artifact))
|
|
623
|
+
throw new StorageRecordError("Artifacts are immutable.");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
this.#db.prepare("INSERT INTO artifacts (task_id, id, payload) VALUES (?, ?, ?)")
|
|
627
|
+
.run(artifact.taskId, artifact.id, this.#json(artifact));
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
getArtifact(taskId, artifactId) {
|
|
631
|
+
const artifact = this.#getPayload("artifacts", "task_id = ? AND id = ?", [taskId, artifactId]);
|
|
632
|
+
return artifact === null ? null : validateArtifact(artifact);
|
|
633
|
+
}
|
|
634
|
+
listArtifacts(taskId) {
|
|
635
|
+
return this.#listPayload("artifacts", "task_id = ?", [taskId]).map(validateArtifact);
|
|
636
|
+
}
|
|
637
|
+
savePluginValidation(validation) {
|
|
638
|
+
validatePluginValidation(validation);
|
|
639
|
+
this.#mutate(() => {
|
|
640
|
+
const previous = this.getPluginValidation(validation.taskId, validation.id);
|
|
641
|
+
if (previous !== null) {
|
|
642
|
+
if (!isDeepStrictEqual(previous, validation))
|
|
643
|
+
throw new StorageRecordError("Plugin validation is immutable.");
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
this.#db.prepare("INSERT INTO plugin_validations (task_id, id, payload) VALUES (?, ?, ?)")
|
|
647
|
+
.run(validation.taskId, validation.id, this.#json(validation));
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
savePluginIntent(intent) {
|
|
651
|
+
validatePluginIntent(intent);
|
|
652
|
+
this.#mutate(() => {
|
|
653
|
+
const previous = this.getPluginIntent(intent.taskId, intent.pluginId);
|
|
654
|
+
if (intent.revision !== (previous?.revision ?? 0) + 1 || intent.lastFailure !== undefined) {
|
|
655
|
+
throw new StorageRecordError("Plugin intent must be a new explicit choice without historical failure.");
|
|
656
|
+
}
|
|
657
|
+
if (intent.validationId !== undefined) {
|
|
658
|
+
const validation = this.getPluginValidation(intent.taskId, intent.validationId);
|
|
659
|
+
if (validation?.package.manifest.id !== intent.pluginId)
|
|
660
|
+
throw new StorageRecordError("Plugin validation is outside this intent.");
|
|
661
|
+
}
|
|
662
|
+
this.#db.prepare(`INSERT INTO plugin_intents (task_id, plugin_id, payload) VALUES (?, ?, ?)
|
|
663
|
+
ON CONFLICT(task_id, plugin_id) DO UPDATE SET payload = excluded.payload`)
|
|
664
|
+
.run(intent.taskId, intent.pluginId, this.#json(intent));
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
getPluginIntent(taskId, pluginId) {
|
|
668
|
+
const value = this.#getPayload("plugin_intents", "task_id = ? AND plugin_id = ?", [taskId, pluginId]);
|
|
669
|
+
return value === null ? null : validatePluginIntent(value);
|
|
670
|
+
}
|
|
671
|
+
listPluginIntents(taskId) {
|
|
672
|
+
return this.#listPayload("plugin_intents", "task_id = ?", [taskId]).map(validatePluginIntent);
|
|
673
|
+
}
|
|
674
|
+
recordPluginIntentFailure(taskId, pluginId, revision, failure) {
|
|
675
|
+
validatePluginIntentFailure(failure);
|
|
676
|
+
return this.transaction(() => {
|
|
677
|
+
const current = this.getPluginIntent(taskId, pluginId);
|
|
678
|
+
if (current === null || current.revision !== revision)
|
|
679
|
+
return false;
|
|
680
|
+
this.#mutate(() => {
|
|
681
|
+
this.#db.prepare("UPDATE plugin_intents SET payload = ? WHERE task_id = ? AND plugin_id = ?")
|
|
682
|
+
.run(this.#json({ ...current, lastFailure: failure }), taskId, pluginId);
|
|
683
|
+
});
|
|
684
|
+
return true;
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
getPluginValidation(taskId, id) {
|
|
688
|
+
const value = this.#getPayload("plugin_validations", "task_id = ? AND id = ?", [taskId, id]);
|
|
689
|
+
return value === null ? null : validatePluginValidation(value);
|
|
690
|
+
}
|
|
691
|
+
saveLocalResource(resource) {
|
|
692
|
+
validateLocalResource(resource);
|
|
693
|
+
this.#mutate(() => {
|
|
694
|
+
const previous = this.getLocalResource(resource.id);
|
|
695
|
+
if (previous !== null) {
|
|
696
|
+
if (!isDeepStrictEqual(previous, resource))
|
|
697
|
+
throw new StorageRecordError("Resource identity is immutable.");
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
this.#db.prepare("INSERT INTO local_resources (id, canonical_identity, payload) VALUES (?, ?, ?)")
|
|
701
|
+
.run(resource.id, `${resource.device}:${resource.inode}`, this.#json(resource));
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
getLocalResource(resourceId) {
|
|
705
|
+
const resource = this.#getPayload("local_resources", "id = ?", [resourceId]);
|
|
706
|
+
return resource === null ? null : validateLocalResource(resource);
|
|
707
|
+
}
|
|
708
|
+
listLocalResources() {
|
|
709
|
+
return this.#listPayload("local_resources", "1=1", []).map(validateLocalResource);
|
|
710
|
+
}
|
|
711
|
+
saveEnvironmentPreparation(preparation) {
|
|
712
|
+
validateEnvironmentPreparation(preparation);
|
|
713
|
+
this.#mutate(() => {
|
|
714
|
+
const previous = this.getEnvironmentPreparation(preparation.taskId, preparation.id);
|
|
715
|
+
if (previous) {
|
|
716
|
+
const { disposition: before, updatedAt: _beforeTime, releaseEvidence: _beforeEvidence, ...identity } = previous;
|
|
717
|
+
const { disposition: after, updatedAt: _afterTime, releaseEvidence: _afterEvidence, ...nextIdentity } = preparation;
|
|
718
|
+
if (!isDeepStrictEqual(identity, nextIdentity)
|
|
719
|
+
|| (before === "released" && !isDeepStrictEqual(previous, preparation))
|
|
720
|
+
|| (before === "adopted" && after === "prepared")) {
|
|
721
|
+
throw new StorageRecordError("Preparation ownership/identity cannot be rewritten.");
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
else if (preparation.disposition !== "prepared") {
|
|
725
|
+
throw new StorageRecordError("Environment must be prepared before adoption.");
|
|
726
|
+
}
|
|
727
|
+
this.#db.prepare(`INSERT INTO environment_preparations (task_id, id, payload) VALUES (?, ?, ?)
|
|
728
|
+
ON CONFLICT(task_id, id) DO UPDATE SET payload = excluded.payload`)
|
|
729
|
+
.run(preparation.taskId, preparation.id, this.#json(preparation));
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
getEnvironmentPreparation(taskId, preparationId) {
|
|
733
|
+
const preparation = this.#getPayload("environment_preparations", "task_id = ? AND id = ?", [taskId, preparationId]);
|
|
734
|
+
return preparation === null ? null : validateEnvironmentPreparation(preparation);
|
|
735
|
+
}
|
|
736
|
+
listEnvironmentPreparations(taskId) {
|
|
737
|
+
return this.#listPayload("environment_preparations", "task_id = ?", [taskId])
|
|
738
|
+
.map(validateEnvironmentPreparation);
|
|
739
|
+
}
|
|
581
740
|
nextProjectId() { return this.#nextGlobalId("project"); }
|
|
582
741
|
saveProject(project) {
|
|
742
|
+
validateProject(project);
|
|
583
743
|
this.#mutate(() => {
|
|
584
744
|
this.#db.prepare(`INSERT INTO projects (id, name, path, payload, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
585
745
|
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
746
|
});
|
|
587
747
|
}
|
|
588
748
|
createProjectIfAbsent(project) {
|
|
749
|
+
validateProject(project);
|
|
589
750
|
return this.#mutate(() => {
|
|
590
751
|
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
752
|
return result.changes > 0 ? project : null;
|
|
592
753
|
});
|
|
593
754
|
}
|
|
594
755
|
listProjects() {
|
|
595
|
-
return this.#sortById(this.#listPayload("projects", "1=1", []), (project) => project.id);
|
|
756
|
+
return this.#sortById(this.#listPayload("projects", "1=1", []).map(validateProject), (project) => project.id);
|
|
596
757
|
}
|
|
597
758
|
getProject(id) {
|
|
598
|
-
|
|
759
|
+
const project = this.#getPayload("projects", "id = ?", [id]);
|
|
760
|
+
return project === null ? null : validateProject(project);
|
|
599
761
|
}
|
|
600
762
|
removeProject(id) {
|
|
601
763
|
return this.#mutate(() => {
|
|
@@ -609,17 +771,17 @@ export class SqliteTaskStore {
|
|
|
609
771
|
const boundTasks = this.listTasks().filter((task) => task.projectBindings.some((binding) => binding.projectId === projectId));
|
|
610
772
|
const activeTasks = boundTasks.filter((task) => task.status === "active");
|
|
611
773
|
const unresolvedWorkItemRefs = [];
|
|
612
|
-
const
|
|
774
|
+
const activeRunRefs = [];
|
|
613
775
|
const unresolvedIntegrationRefs = [];
|
|
614
776
|
for (const task of activeTasks) {
|
|
615
777
|
for (const workItem of this.listWorkItems(task.id)) {
|
|
616
|
-
if (workItem.status !== "
|
|
778
|
+
if (workItem.status !== "accepted" && workItem.status !== "retired") {
|
|
617
779
|
unresolvedWorkItemRefs.push(`${task.id}/${workItem.id}`);
|
|
618
780
|
}
|
|
619
781
|
}
|
|
620
|
-
for (const run of this.
|
|
782
|
+
for (const run of this.listRuns(task.id)) {
|
|
621
783
|
if (run.status === "active")
|
|
622
|
-
|
|
784
|
+
activeRunRefs.push(`${task.id}/${run.id}`);
|
|
623
785
|
}
|
|
624
786
|
for (const attempt of this.listIntegrationAttempts(task.id)) {
|
|
625
787
|
if (attempt.status === "running" || attempt.status === "blocked") {
|
|
@@ -632,7 +794,7 @@ export class SqliteTaskStore {
|
|
|
632
794
|
boundTaskIds: boundTasks.map(({ id }) => id),
|
|
633
795
|
activeTaskIds: activeTasks.map(({ id }) => id),
|
|
634
796
|
unresolvedWorkItemRefs,
|
|
635
|
-
|
|
797
|
+
activeRunRefs,
|
|
636
798
|
unresolvedIntegrationRefs
|
|
637
799
|
};
|
|
638
800
|
}
|
|
@@ -716,6 +878,7 @@ export class SqliteTaskStore {
|
|
|
716
878
|
// -- tasks ------------------------------------------------------------------
|
|
717
879
|
nextTaskId() { return this.#nextGlobalId("task"); }
|
|
718
880
|
saveTask(task) {
|
|
881
|
+
validateTask(task);
|
|
719
882
|
if (typeof task.id !== "string" || task.id.length === 0) {
|
|
720
883
|
throw new StorageRecordError("Task id is required.");
|
|
721
884
|
}
|
|
@@ -747,13 +910,13 @@ export class SqliteTaskStore {
|
|
|
747
910
|
if (task === null)
|
|
748
911
|
return null;
|
|
749
912
|
// One indexed query (idx_turns_role_status) covers both the active
|
|
750
|
-
//
|
|
913
|
+
// AgentRuns the projection waits on and the Leader AgentRuns the budget consumes.
|
|
751
914
|
const events = this.#sortById(this.#listPayload("events", "task_id = ? AND type IN (?, ?)", [
|
|
752
915
|
taskId,
|
|
753
916
|
TASK_RECORD_RETIRED_EVENT,
|
|
754
917
|
"review.completed"
|
|
755
918
|
]), (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, "
|
|
919
|
+
const runs = operationalTaskRecords(this.#sortById(this.#listPayload("turns", "task_id = ? AND (status = 'active' OR role_name = 'leader')", [taskId]), (run) => run.id), events, "run");
|
|
757
920
|
return {
|
|
758
921
|
task: {
|
|
759
922
|
id: task.id,
|
|
@@ -765,14 +928,17 @@ export class SqliteTaskStore {
|
|
|
765
928
|
workItems: this.#sortById(this.#listPayload("work_items", "task_id = ?", [taskId]), (item) => item.id),
|
|
766
929
|
changeSets: this.#sortById(this.#listPayload("change_sets", "task_id = ?", [taskId]), (changeSet) => changeSet.id),
|
|
767
930
|
integrations: this.#sortById(this.#listPayload("integration_attempts", "task_id = ?", [taskId]), (attempt) => attempt.id),
|
|
931
|
+
integrationJobs: this.listDurableJobs(taskId)
|
|
932
|
+
.filter(job => job.owner.kind === "integration-attempt")
|
|
933
|
+
.map(job => ({ id: job.id, status: job.status })),
|
|
768
934
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
769
935
|
reviewRounds: this.#sortById(this.#listPayload("review_rounds", "task_id = ?", [taskId]), (round) => round.id),
|
|
770
936
|
reviewConfig: this.getReviewConfig(),
|
|
771
937
|
openInputRequests: this.#sortById(this.#listPayload("input_requests", "task_id = ? AND status = 'open'", [taskId]), (request) => request.id),
|
|
772
|
-
|
|
773
|
-
|
|
938
|
+
activeRuns: runs.filter((run) => run.status === "active"),
|
|
939
|
+
leaderRuns: runs.filter((run) => run.roleName === "leader"),
|
|
774
940
|
reviewOutcomeEvidence: {
|
|
775
|
-
|
|
941
|
+
runs: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
|
|
776
942
|
}
|
|
777
943
|
};
|
|
778
944
|
}
|
|
@@ -782,6 +948,7 @@ export class SqliteTaskStore {
|
|
|
782
948
|
return null;
|
|
783
949
|
return {
|
|
784
950
|
...base,
|
|
951
|
+
pendingUserMessages: pendingCompletionMessages(this, taskId),
|
|
785
952
|
managedWorkspaces: this.#sortById(this.#listPayload("managed_workspaces", "task_id = ?", [taskId]), (workspace) => managedWorkspaceKey(workspace.owner)),
|
|
786
953
|
durableJobs: this.#sortById(this.#listPayload("durable_jobs", "task_id = ?", [taskId]), (job) => job.id),
|
|
787
954
|
integrationQueueEntries: this.#sortById(this.#listPayload("integration_queue", "task_id = ?", [taskId]), (entry) => entry.id),
|
|
@@ -792,13 +959,41 @@ export class SqliteTaskStore {
|
|
|
792
959
|
const rows = this.#db.prepare("SELECT task_id FROM tasks_catalog WHERE is_active = 1 ORDER BY task_id").all();
|
|
793
960
|
return rows.map((row) => row.task_id);
|
|
794
961
|
}
|
|
962
|
+
/**
|
|
963
|
+
* Draft Task ids that carry an active planning Turn.
|
|
964
|
+
*
|
|
965
|
+
* Bounded by the active-Turn pointers rather than Task history: a Draft
|
|
966
|
+
* appears only while one of its Roles actually holds an admitted planning
|
|
967
|
+
* Turn, so an ordinary Draft is never selected for execution phases.
|
|
968
|
+
*/
|
|
969
|
+
listPlanningDraftTaskIds() {
|
|
970
|
+
const rows = this.#db.prepare(`SELECT DISTINCT catalog.task_id AS task_id
|
|
971
|
+
FROM tasks_catalog AS catalog
|
|
972
|
+
JOIN active_turns AS pointers ON pointers.task_id = catalog.task_id
|
|
973
|
+
JOIN turns ON turns.task_id = pointers.task_id AND turns.turn_id = pointers.turn_id
|
|
974
|
+
WHERE catalog.status = 'draft'
|
|
975
|
+
AND turns.status = 'active'
|
|
976
|
+
AND json_extract(turns.payload, '$.purpose') = 'planning'
|
|
977
|
+
ORDER BY catalog.task_id`).all();
|
|
978
|
+
return rows.map((row) => row.task_id);
|
|
979
|
+
}
|
|
980
|
+
listPendingActivationRequestTaskIds() {
|
|
981
|
+
const rows = this.#db.prepare(`SELECT records.task_id AS task_id
|
|
982
|
+
FROM task_records AS records
|
|
983
|
+
JOIN tasks_catalog AS catalog ON catalog.task_id = records.task_id
|
|
984
|
+
WHERE catalog.status = 'draft'
|
|
985
|
+
AND json_extract(records.payload, '$.activationRequest.disposition') = 'pending'
|
|
986
|
+
ORDER BY records.task_id`).all();
|
|
987
|
+
return rows.map((row) => row.task_id);
|
|
988
|
+
}
|
|
795
989
|
getTaskBrief(taskId) {
|
|
796
990
|
const row = this.#db.prepare("SELECT brief FROM task_records WHERE task_id = ?").get(taskId);
|
|
797
991
|
if (row === undefined || row.brief === null)
|
|
798
992
|
return null;
|
|
799
|
-
return this.#parse(row.brief);
|
|
993
|
+
return validateTaskBrief(this.#parse(row.brief));
|
|
800
994
|
}
|
|
801
995
|
saveTaskBrief(taskId, brief) {
|
|
996
|
+
validateTaskBrief(brief);
|
|
802
997
|
this.#requireTask(taskId);
|
|
803
998
|
this.#mutate(() => {
|
|
804
999
|
this.#db.prepare("UPDATE task_records SET brief = ?, updated_at = ? WHERE task_id = ?")
|
|
@@ -932,40 +1127,24 @@ export class SqliteTaskStore {
|
|
|
932
1127
|
const row = this.#db.prepare("SELECT 1 FROM durable_jobs WHERE status IN ('queued', 'running') LIMIT 1").get();
|
|
933
1128
|
return row !== undefined;
|
|
934
1129
|
}
|
|
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
1130
|
// -- session owners (Issue 03) ----------------------------------------------
|
|
952
1131
|
saveSessionOwner(identity) {
|
|
953
1132
|
const owner = identity.owner;
|
|
954
1133
|
this.#mutate(() => {
|
|
955
1134
|
this.#db.prepare(`INSERT INTO session_owners
|
|
956
|
-
(
|
|
1135
|
+
(process_key, scope, task_id, role_name, agent_id, native_session_id,
|
|
957
1136
|
provider_root_pid, payload, recorded_at)
|
|
958
1137
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
959
|
-
ON CONFLICT(
|
|
1138
|
+
ON CONFLICT(process_key) DO UPDATE SET
|
|
960
1139
|
scope = excluded.scope, task_id = excluded.task_id,
|
|
961
1140
|
role_name = excluded.role_name, agent_id = excluded.agent_id,
|
|
962
1141
|
native_session_id = excluded.native_session_id,
|
|
963
1142
|
provider_root_pid = excluded.provider_root_pid,
|
|
964
|
-
payload = excluded.payload, recorded_at = excluded.recorded_at`).run(identity
|
|
1143
|
+
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
1144
|
});
|
|
966
1145
|
}
|
|
967
|
-
getSessionOwner(
|
|
968
|
-
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE
|
|
1146
|
+
getSessionOwner(processKey) {
|
|
1147
|
+
const row = this.#db.prepare("SELECT payload FROM session_owners WHERE process_key = ?").get(processKey);
|
|
969
1148
|
return row === undefined ? null : this.#parse(row.payload);
|
|
970
1149
|
}
|
|
971
1150
|
listSessionOwners() {
|
|
@@ -977,9 +1156,9 @@ export class SqliteTaskStore {
|
|
|
977
1156
|
}
|
|
978
1157
|
return this.#listPayload("session_owners", "scope = 'task' AND task_id = ? AND role_name = ?", [owner.taskId, owner.roleName]);
|
|
979
1158
|
}
|
|
980
|
-
removeSessionOwner(
|
|
1159
|
+
removeSessionOwner(processKey) {
|
|
981
1160
|
this.#mutate(() => {
|
|
982
|
-
this.#db.prepare("DELETE FROM session_owners WHERE
|
|
1161
|
+
this.#db.prepare("DELETE FROM session_owners WHERE process_key = ?").run(processKey);
|
|
983
1162
|
});
|
|
984
1163
|
}
|
|
985
1164
|
// -- task roles -------------------------------------------------------------
|
|
@@ -1078,8 +1257,6 @@ export class SqliteTaskStore {
|
|
|
1078
1257
|
}
|
|
1079
1258
|
const predicates = [];
|
|
1080
1259
|
const parameters = [];
|
|
1081
|
-
if (query.cleanupRequiredOnly)
|
|
1082
|
-
predicates.push("cleanup_required = 1");
|
|
1083
1260
|
if (taskIds !== undefined) {
|
|
1084
1261
|
predicates.push("scope = 'task'");
|
|
1085
1262
|
predicates.push(`task_id IN (${taskIds.map(() => "?").join(", ")})`);
|
|
@@ -1093,8 +1270,7 @@ export class SqliteTaskStore {
|
|
|
1093
1270
|
? ""
|
|
1094
1271
|
: ` WHERE ${predicates.join(" AND ")}`;
|
|
1095
1272
|
const rows = this.#db.prepare(`SELECT scope, task_id, role_name, agent_id, adapter_id,
|
|
1096
|
-
native_session_id,
|
|
1097
|
-
cleanup_required
|
|
1273
|
+
native_session_id, session_updated_at
|
|
1098
1274
|
FROM runtime_session_candidates${where}`).all(...parameters);
|
|
1099
1275
|
const candidates = rows.map((row) => ({
|
|
1100
1276
|
owner: row.scope === "task"
|
|
@@ -1103,9 +1279,7 @@ export class SqliteTaskStore {
|
|
|
1103
1279
|
agentId: row.agent_id,
|
|
1104
1280
|
adapterId: row.adapter_id,
|
|
1105
1281
|
nativeSessionId: row.native_session_id,
|
|
1106
|
-
|
|
1107
|
-
sessionUpdatedAt: row.session_updated_at,
|
|
1108
|
-
cleanupRequired: row.cleanup_required === 1
|
|
1282
|
+
sessionUpdatedAt: row.session_updated_at
|
|
1109
1283
|
})).sort(compareRuntimeSessionCandidates);
|
|
1110
1284
|
for (const candidate of candidates) {
|
|
1111
1285
|
if (!this.#runtimeSessionCandidateMatchesSource(candidate)) {
|
|
@@ -1158,17 +1332,11 @@ export class SqliteTaskStore {
|
|
|
1158
1332
|
json_extract(active_session, '$.agentId') AS agent_id,
|
|
1159
1333
|
json_extract(active_session, '$.adapterId') AS adapter_id,
|
|
1160
1334
|
json_extract(active_session, '$.nativeSessionId') AS native_session_id,
|
|
1161
|
-
json_extract(active_session, '$.runtimeGenerationId') AS runtime_generation_id,
|
|
1162
1335
|
CASE
|
|
1163
1336
|
WHEN json_extract(active_session, '$.status') = 'active'
|
|
1164
1337
|
THEN 1 ELSE 0
|
|
1165
1338
|
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
|
|
1339
|
+
json_extract(active_session, '$.updatedAt') AS session_updated_at
|
|
1172
1340
|
FROM active`).get(...source.parameters);
|
|
1173
1341
|
}
|
|
1174
1342
|
catch (error) {
|
|
@@ -1179,17 +1347,7 @@ export class SqliteTaskStore {
|
|
|
1179
1347
|
}
|
|
1180
1348
|
if (row === undefined)
|
|
1181
1349
|
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);
|
|
1350
|
+
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
1351
|
}
|
|
1194
1352
|
saveRoleSessionSet(sessions) {
|
|
1195
1353
|
const taskId = sessions.owner.taskId;
|
|
@@ -1219,15 +1377,13 @@ export class SqliteTaskStore {
|
|
|
1219
1377
|
const taskId = candidate.owner.scope === "task" ? candidate.owner.taskId : "";
|
|
1220
1378
|
this.#db.prepare(`INSERT INTO runtime_session_candidates (
|
|
1221
1379
|
scope, task_id, role_name, agent_id, adapter_id, native_session_id,
|
|
1222
|
-
|
|
1223
|
-
) VALUES (?, ?, ?, ?, ?, ?,
|
|
1380
|
+
session_updated_at
|
|
1381
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1224
1382
|
ON CONFLICT(scope, task_id, role_name) DO UPDATE SET
|
|
1225
1383
|
agent_id = excluded.agent_id,
|
|
1226
1384
|
adapter_id = excluded.adapter_id,
|
|
1227
1385
|
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);
|
|
1386
|
+
session_updated_at = excluded.session_updated_at`).run(candidate.owner.scope, taskId, candidate.owner.roleName, candidate.agentId, candidate.adapterId, candidate.nativeSessionId, candidate.sessionUpdatedAt);
|
|
1231
1387
|
}
|
|
1232
1388
|
#deleteRuntimeSessionCandidate(owner) {
|
|
1233
1389
|
this.#db.prepare(`DELETE FROM runtime_session_candidates
|
|
@@ -1242,6 +1398,7 @@ export class SqliteTaskStore {
|
|
|
1242
1398
|
return this.#sortById(this.#listPayload("work_items", "task_id = ?", [taskId]), (item) => item.id);
|
|
1243
1399
|
}
|
|
1244
1400
|
saveWorkItem(taskId, item) {
|
|
1401
|
+
validateWorkItem(item);
|
|
1245
1402
|
if (item.taskId !== taskId)
|
|
1246
1403
|
throw new StorageRecordError(`Work item belongs to another Task: ${item.taskId}`);
|
|
1247
1404
|
this.#requireTask(taskId);
|
|
@@ -1495,34 +1652,34 @@ export class SqliteTaskStore {
|
|
|
1495
1652
|
});
|
|
1496
1653
|
}
|
|
1497
1654
|
// -- 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(
|
|
1655
|
+
nextRunId(taskId) { return this.#nextTaskRecordId(taskId, "run"); }
|
|
1656
|
+
peekNextRunId(taskId) { return this.#peekTaskRecordId(taskId, "run"); }
|
|
1657
|
+
getRun(taskId, runId) {
|
|
1658
|
+
return this.#getPayload("turns", "task_id = ? AND turn_id = ?", [taskId, runId]);
|
|
1659
|
+
}
|
|
1660
|
+
listRuns(taskId) {
|
|
1661
|
+
return this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]), (run) => run.id);
|
|
1662
|
+
}
|
|
1663
|
+
saveRun(run) {
|
|
1664
|
+
if (run.taskId !== undefined)
|
|
1665
|
+
this.#requireTask(run.taskId);
|
|
1666
|
+
if (run.reviewRoundId !== undefined) {
|
|
1667
|
+
const round = this.getReviewRound(run.taskId, run.reviewRoundId);
|
|
1511
1668
|
if (round === null) {
|
|
1512
|
-
throw new StorageRecordError(`
|
|
1669
|
+
throw new StorageRecordError(`AgentRun ReviewRound not found: ${run.reviewRoundId}.`);
|
|
1513
1670
|
}
|
|
1514
1671
|
const roundWorkItemId = round.workItemId;
|
|
1515
1672
|
const laneRole = round.executionGroup?.lanes
|
|
1516
|
-
.find(({ id }) => id ===
|
|
1517
|
-
if (roundWorkItemId !==
|
|
1518
|
-
|| (round.reviewerRoleName !==
|
|
1519
|
-
throw new StorageRecordError(`
|
|
1673
|
+
.find(({ id }) => id === run.executionLaneId)?.roleName;
|
|
1674
|
+
if (roundWorkItemId !== run.workItemId
|
|
1675
|
+
|| (round.reviewerRoleName !== run.roleName && laneRole !== run.roleName)) {
|
|
1676
|
+
throw new StorageRecordError(`AgentRun does not match ReviewRound: ${run.id}.`);
|
|
1520
1677
|
}
|
|
1521
1678
|
}
|
|
1522
1679
|
this.#mutate(() => {
|
|
1523
1680
|
this.#db.prepare(`INSERT INTO turns (task_id, turn_id, role_name, status, payload, updated_at) VALUES (?, ?, ?, ?, ?, ?)
|
|
1524
1681
|
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(
|
|
1682
|
+
payload = excluded.payload, updated_at = excluded.updated_at`).run(run.taskId, run.id, run.roleName, run.status, this.#json(run), this.#now());
|
|
1526
1683
|
});
|
|
1527
1684
|
}
|
|
1528
1685
|
// -- review rounds ----------------------------------------------------------
|
|
@@ -1545,180 +1702,178 @@ export class SqliteTaskStore {
|
|
|
1545
1702
|
});
|
|
1546
1703
|
}
|
|
1547
1704
|
// -- active turns ----------------------------------------------------------
|
|
1548
|
-
#
|
|
1705
|
+
#saveActiveRun(taskId, pointer, runId) {
|
|
1549
1706
|
this.#mutate(() => {
|
|
1550
|
-
const payload = this.#json({ schemaVersion: 3,
|
|
1707
|
+
const payload = this.#json({ schemaVersion: 3, runId });
|
|
1551
1708
|
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,
|
|
1709
|
+
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
1710
|
});
|
|
1554
1711
|
}
|
|
1555
|
-
#
|
|
1712
|
+
#readActiveRunPointer(taskId, pointer) {
|
|
1556
1713
|
const row = this.#db.prepare("SELECT turn_id FROM active_turns WHERE task_id = ? AND pointer = ?").get(taskId, pointer);
|
|
1557
1714
|
if (row === undefined)
|
|
1558
1715
|
return null;
|
|
1559
1716
|
return {
|
|
1560
|
-
|
|
1561
|
-
|
|
1717
|
+
runId: row.turn_id,
|
|
1718
|
+
run: this.getRun(taskId, row.turn_id)
|
|
1562
1719
|
};
|
|
1563
1720
|
}
|
|
1564
|
-
#
|
|
1565
|
-
if (
|
|
1566
|
-
throw new StorageRecordError(`Active
|
|
1721
|
+
#assertActiveRunForWrite(run, lane) {
|
|
1722
|
+
if (run.status !== "active") {
|
|
1723
|
+
throw new StorageRecordError(`Active AgentRun must have active status: ${run.id}`);
|
|
1567
1724
|
}
|
|
1568
|
-
const hasGroup =
|
|
1569
|
-
const hasLane =
|
|
1725
|
+
const hasGroup = run.executionGroupId !== undefined;
|
|
1726
|
+
const hasLane = run.executionLaneId !== undefined;
|
|
1570
1727
|
if (hasGroup !== hasLane) {
|
|
1571
|
-
throw new StorageRecordError(`
|
|
1728
|
+
throw new StorageRecordError(`AgentRun execution lineage is incomplete: ${run.id}.`);
|
|
1572
1729
|
}
|
|
1573
1730
|
if (lane !== undefined
|
|
1574
|
-
&& (
|
|
1575
|
-
||
|
|
1576
|
-
throw new StorageRecordError(`Active
|
|
1731
|
+
&& (run.executionGroupId !== lane.executionGroupId
|
|
1732
|
+
|| run.executionLaneId !== lane.executionLaneId)) {
|
|
1733
|
+
throw new StorageRecordError(`Active AgentRun execution lineage does not match its Lane: ${run.id}`);
|
|
1577
1734
|
}
|
|
1578
1735
|
}
|
|
1579
|
-
#
|
|
1580
|
-
const pointer = this.#
|
|
1736
|
+
#getActiveRoleRun(taskId, roleName) {
|
|
1737
|
+
const pointer = this.#readActiveRunPointer(taskId, roleName);
|
|
1581
1738
|
if (pointer === null)
|
|
1582
1739
|
return null;
|
|
1583
1740
|
const task = this.getTask(taskId);
|
|
1584
1741
|
if (task === null) {
|
|
1585
|
-
throw new StorageRecordError(`Active
|
|
1742
|
+
throw new StorageRecordError(`Active AgentRun Task is missing: ${taskId}/${roleName}`);
|
|
1586
1743
|
}
|
|
1587
|
-
if (pointer.
|
|
1744
|
+
if (pointer.run === null) {
|
|
1588
1745
|
// Retired Tasks are an explicit historical isolation boundary. Their
|
|
1589
|
-
// retained pointer rows may intentionally reference a missing
|
|
1590
|
-
if (task.status === "
|
|
1746
|
+
// retained pointer rows may intentionally reference a missing AgentRun.
|
|
1747
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1591
1748
|
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.#
|
|
1749
|
+
throw new StorageRecordError(`Active AgentRun pointer is dangling: ${taskId}/${roleName}`);
|
|
1750
|
+
}
|
|
1751
|
+
const run = pointer.run;
|
|
1752
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1753
|
+
return run;
|
|
1754
|
+
if (run.id !== pointer.runId
|
|
1755
|
+
|| run.taskId !== taskId
|
|
1756
|
+
|| run.roleName !== roleName
|
|
1757
|
+
|| run.status !== "active"
|
|
1758
|
+
|| (run.executionGroupId === undefined) !== (run.executionLaneId === undefined)) {
|
|
1759
|
+
throw new StorageRecordError(`Active AgentRun pointer is invalid: ${taskId}/${roleName}`);
|
|
1760
|
+
}
|
|
1761
|
+
return run;
|
|
1762
|
+
}
|
|
1763
|
+
#getActiveLaneRun(taskId, executionGroupId, executionLaneId) {
|
|
1764
|
+
const pointerKey = executionLaneActiveRunKey(executionGroupId, executionLaneId);
|
|
1765
|
+
const pointer = this.#readActiveRunPointer(taskId, pointerKey);
|
|
1609
1766
|
if (pointer === null)
|
|
1610
1767
|
return null;
|
|
1611
1768
|
const task = this.getTask(taskId);
|
|
1612
1769
|
if (task === null) {
|
|
1613
|
-
throw new StorageRecordError(`Active
|
|
1770
|
+
throw new StorageRecordError(`Active AgentRun Task is missing: ${taskId}/${pointerKey}`);
|
|
1614
1771
|
}
|
|
1615
|
-
if (pointer.
|
|
1616
|
-
if (task.status === "
|
|
1772
|
+
if (pointer.run === null) {
|
|
1773
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1617
1774
|
return null;
|
|
1618
|
-
throw new StorageRecordError(`Active
|
|
1775
|
+
throw new StorageRecordError(`Active AgentRun pointer is dangling: ${taskId}/${pointerKey}`);
|
|
1619
1776
|
}
|
|
1620
|
-
const
|
|
1621
|
-
if (task.status === "
|
|
1622
|
-
return
|
|
1623
|
-
if (
|
|
1624
|
-
||
|
|
1625
|
-
||
|
|
1626
|
-
||
|
|
1627
|
-
||
|
|
1628
|
-
throw new StorageRecordError(`Active
|
|
1777
|
+
const run = pointer.run;
|
|
1778
|
+
if (task.status === "cancelled" && task.retirementIsolation === true)
|
|
1779
|
+
return run;
|
|
1780
|
+
if (run.id !== pointer.runId
|
|
1781
|
+
|| run.taskId !== taskId
|
|
1782
|
+
|| run.status !== "active"
|
|
1783
|
+
|| run.executionGroupId !== executionGroupId
|
|
1784
|
+
|| run.executionLaneId !== executionLaneId) {
|
|
1785
|
+
throw new StorageRecordError(`Active AgentRun pointer is invalid: ${taskId}/${pointerKey}`);
|
|
1629
1786
|
}
|
|
1630
|
-
return
|
|
1787
|
+
return run;
|
|
1631
1788
|
}
|
|
1632
|
-
#
|
|
1789
|
+
#clearActiveRun(taskId, pointer) {
|
|
1633
1790
|
this.#mutate(() => {
|
|
1634
1791
|
this.#db.prepare("DELETE FROM active_turns WHERE task_id = ? AND pointer = ?").run(taskId, pointer);
|
|
1635
1792
|
});
|
|
1636
1793
|
}
|
|
1637
|
-
|
|
1638
|
-
return this.#
|
|
1794
|
+
getActiveRun(taskId, roleName) {
|
|
1795
|
+
return this.#getActiveRoleRun(taskId, roleName);
|
|
1639
1796
|
}
|
|
1640
|
-
|
|
1641
|
-
if (
|
|
1642
|
-
this.
|
|
1797
|
+
saveActiveRun(run) {
|
|
1798
|
+
if (run.executionGroupId !== undefined || run.executionLaneId !== undefined) {
|
|
1799
|
+
this.saveActiveExecutionLaneRun(run);
|
|
1643
1800
|
return;
|
|
1644
1801
|
}
|
|
1645
1802
|
this.transaction((store) => {
|
|
1646
|
-
const task = store.getTask(
|
|
1647
|
-
|
|
1648
|
-
|
|
1803
|
+
const task = store.getTask(run.taskId);
|
|
1804
|
+
assertRunAdmission(task, run);
|
|
1805
|
+
this.#assertActiveRunForWrite(run);
|
|
1806
|
+
const current = store.getActiveRun(run.taskId, run.roleName);
|
|
1807
|
+
if (current !== null && current.id !== run.id) {
|
|
1808
|
+
throw new StorageRecordError(`Role already has an active AgentRun: ${run.taskId}/${run.roleName}`);
|
|
1649
1809
|
}
|
|
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
|
|
1810
|
+
// Keep the AgentRun row and its active pointer in the same transaction. The
|
|
1656
1811
|
// adapter may have already written the row; this upsert remains
|
|
1657
1812
|
// intentionally idempotent for that backend-neutral path.
|
|
1658
|
-
store.
|
|
1659
|
-
this.#
|
|
1813
|
+
store.saveRun(run);
|
|
1814
|
+
this.#saveActiveRun(run.taskId, run.roleName, run.id);
|
|
1660
1815
|
});
|
|
1661
1816
|
}
|
|
1662
|
-
|
|
1663
|
-
// A Role key can point at a lane-backed
|
|
1817
|
+
clearActiveRun(taskId, roleName) {
|
|
1818
|
+
// A Role key can point at a lane-backed AgentRun. Remove the matching lane
|
|
1664
1819
|
// pointer too while preserving every other lane for the same Role.
|
|
1665
1820
|
this.transaction(() => {
|
|
1666
|
-
const rolePointer = this.#
|
|
1667
|
-
this.#
|
|
1821
|
+
const rolePointer = this.#readActiveRunPointer(taskId, roleName);
|
|
1822
|
+
this.#clearActiveRun(taskId, roleName);
|
|
1668
1823
|
if (rolePointer === null)
|
|
1669
1824
|
return;
|
|
1670
1825
|
const laneRows = this.#db.prepare("SELECT pointer, turn_id FROM active_turns WHERE task_id = ?").all(taskId);
|
|
1671
1826
|
for (const row of laneRows) {
|
|
1672
|
-
if (
|
|
1673
|
-
&& row.turn_id === rolePointer.
|
|
1674
|
-
this.#
|
|
1827
|
+
if (executionLaneActiveRunKeyParts(row.pointer) !== null
|
|
1828
|
+
&& row.turn_id === rolePointer.runId) {
|
|
1829
|
+
this.#clearActiveRun(taskId, row.pointer);
|
|
1675
1830
|
}
|
|
1676
1831
|
}
|
|
1677
1832
|
});
|
|
1678
1833
|
}
|
|
1679
|
-
|
|
1680
|
-
return this.#
|
|
1834
|
+
getActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
|
|
1835
|
+
return this.#getActiveLaneRun(taskId, executionGroupId, executionLaneId);
|
|
1681
1836
|
}
|
|
1682
|
-
|
|
1683
|
-
if (
|
|
1684
|
-
throw new StorageRecordError(`Active execution-lane
|
|
1837
|
+
saveActiveExecutionLaneRun(run) {
|
|
1838
|
+
if (run.executionGroupId === undefined || run.executionLaneId === undefined) {
|
|
1839
|
+
throw new StorageRecordError(`Active execution-lane AgentRun requires group and lane ids: ${run.id}`);
|
|
1685
1840
|
}
|
|
1686
1841
|
this.transaction((store) => {
|
|
1687
|
-
const task = store.getTask(
|
|
1842
|
+
const task = store.getTask(run.taskId);
|
|
1688
1843
|
if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
|
|
1689
|
-
throw new StorageRecordError(`Task execution is not enabled: ${
|
|
1844
|
+
throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
|
|
1690
1845
|
}
|
|
1691
|
-
const key =
|
|
1692
|
-
this.#
|
|
1693
|
-
executionGroupId:
|
|
1694
|
-
executionLaneId:
|
|
1846
|
+
const key = executionLaneActiveRunKey(run.executionGroupId, run.executionLaneId);
|
|
1847
|
+
this.#assertActiveRunForWrite(run, {
|
|
1848
|
+
executionGroupId: run.executionGroupId,
|
|
1849
|
+
executionLaneId: run.executionLaneId
|
|
1695
1850
|
});
|
|
1696
|
-
const current = store.
|
|
1697
|
-
if (current !== null && current.id !==
|
|
1698
|
-
throw new StorageRecordError(`Execution Lane already has an active
|
|
1851
|
+
const current = store.getActiveExecutionLaneRun(run.taskId, run.executionGroupId, run.executionLaneId);
|
|
1852
|
+
if (current !== null && current.id !== run.id) {
|
|
1853
|
+
throw new StorageRecordError(`Execution Lane already has an active AgentRun: ${run.taskId}/${key}`);
|
|
1699
1854
|
}
|
|
1700
|
-
const rolePointer = this.#
|
|
1701
|
-
store.
|
|
1702
|
-
this.#
|
|
1855
|
+
const rolePointer = this.#readActiveRunPointer(run.taskId, run.roleName);
|
|
1856
|
+
store.saveRun(run);
|
|
1857
|
+
this.#saveActiveRun(run.taskId, key, run.id);
|
|
1703
1858
|
// Keep the Role pointer for the single-lane delivery path, but never
|
|
1704
1859
|
// replace it when another Lane already owns that Role.
|
|
1705
1860
|
if (rolePointer === null) {
|
|
1706
|
-
this.#
|
|
1861
|
+
this.#saveActiveRun(run.taskId, run.roleName, run.id);
|
|
1707
1862
|
}
|
|
1708
1863
|
});
|
|
1709
1864
|
}
|
|
1710
|
-
|
|
1865
|
+
clearActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
|
|
1711
1866
|
this.transaction(() => {
|
|
1712
|
-
const key =
|
|
1713
|
-
const lanePointer = this.#
|
|
1714
|
-
this.#
|
|
1867
|
+
const key = executionLaneActiveRunKey(executionGroupId, executionLaneId);
|
|
1868
|
+
const lanePointer = this.#readActiveRunPointer(taskId, key);
|
|
1869
|
+
this.#clearActiveRun(taskId, key);
|
|
1715
1870
|
if (lanePointer === null)
|
|
1716
1871
|
return;
|
|
1717
1872
|
const roleRows = this.#db.prepare("SELECT pointer, turn_id FROM active_turns WHERE task_id = ?").all(taskId);
|
|
1718
1873
|
for (const row of roleRows) {
|
|
1719
|
-
if (
|
|
1720
|
-
&& row.turn_id === lanePointer.
|
|
1721
|
-
this.#
|
|
1874
|
+
if (executionLaneActiveRunKeyParts(row.pointer) === null
|
|
1875
|
+
&& row.turn_id === lanePointer.runId) {
|
|
1876
|
+
this.#clearActiveRun(taskId, row.pointer);
|
|
1722
1877
|
}
|
|
1723
1878
|
}
|
|
1724
1879
|
});
|
|
@@ -1862,7 +2017,7 @@ export class SqliteTaskStore {
|
|
|
1862
2017
|
turn_id = excluded.turn_id,
|
|
1863
2018
|
reasons = excluded.reasons,
|
|
1864
2019
|
payload = excluded.payload,
|
|
1865
|
-
consumed_at = excluded.consumed_at`).run(taskId, wake.id, wake.seq, wake.status, wake.
|
|
2020
|
+
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
2021
|
this.#observeHighWater(taskId, "taskWake", seq);
|
|
1867
2022
|
});
|
|
1868
2023
|
}
|
|
@@ -1880,7 +2035,8 @@ export class SqliteTaskStore {
|
|
|
1880
2035
|
// -- high-water maintenance ---------------------------------------------------
|
|
1881
2036
|
/** Extract the numeric suffix of a `<prefix>-<n>` record id. */
|
|
1882
2037
|
#idSequence(id, kind) {
|
|
1883
|
-
const
|
|
2038
|
+
const prefix = kind === "run" ? "(?:run|turn)" : TASK_RECORD_ID_PREFIXES[kind];
|
|
2039
|
+
const match = new RegExp(`^${prefix}-(\\d+)$`).exec(id);
|
|
1884
2040
|
if (match === null)
|
|
1885
2041
|
throw new StorageRecordError(`Task-local ${kind} id is invalid: ${id}.`);
|
|
1886
2042
|
return Number.parseInt(match[1], 10);
|
|
@@ -2050,76 +2206,75 @@ export class SqliteTaskStore {
|
|
|
2050
2206
|
}
|
|
2051
2207
|
// -- telemetry (§4.4) -----------------------------------------------------------
|
|
2052
2208
|
/**
|
|
2053
|
-
* Upsert one progress row. The PK is (task_id, role_name, turn_id,
|
|
2209
|
+
* Upsert one progress row. The PK is (task_id, role_name, turn_id,
|
|
2054
2210
|
* progress_id): a repeated progress id updates in place, so a high-frequency
|
|
2055
2211
|
* runtime telemetry observation is a single-row write that never
|
|
2056
2212
|
* rewrites global state or another Task's rows.
|
|
2057
2213
|
*/
|
|
2058
2214
|
upsertTelemetryProgress(entry) {
|
|
2059
2215
|
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.
|
|
2216
|
+
this.#db.prepare(`INSERT INTO telemetry (task_id, role_name, turn_id, progress_id, sequence, payload, received_at)
|
|
2217
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
2218
|
+
ON CONFLICT(task_id, role_name, turn_id, progress_id)
|
|
2219
|
+
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
2220
|
});
|
|
2065
2221
|
}
|
|
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,
|
|
2222
|
+
listTelemetry(taskId, runId) {
|
|
2223
|
+
const rows = runId === undefined
|
|
2224
|
+
? 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)
|
|
2225
|
+
: 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
2226
|
return rows
|
|
2071
2227
|
.map((row) => ({
|
|
2072
2228
|
taskId: row.task_id,
|
|
2073
2229
|
roleName: row.role_name,
|
|
2074
|
-
|
|
2075
|
-
generation: row.generation,
|
|
2230
|
+
runId: row.turn_id,
|
|
2076
2231
|
progressId: row.progress_id,
|
|
2077
2232
|
sequence: row.sequence ?? undefined,
|
|
2078
2233
|
payload: this.#parse(row.payload),
|
|
2079
2234
|
receivedAt: row.received_at
|
|
2080
2235
|
}));
|
|
2081
2236
|
}
|
|
2082
|
-
countTelemetry(taskId,
|
|
2083
|
-
const row =
|
|
2237
|
+
countTelemetry(taskId, runId) {
|
|
2238
|
+
const row = runId === undefined
|
|
2084
2239
|
? 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,
|
|
2240
|
+
: this.#db.prepare("SELECT COUNT(*) AS n FROM telemetry WHERE task_id = ? AND turn_id = ?").get(taskId, runId);
|
|
2086
2241
|
return row.n;
|
|
2087
2242
|
}
|
|
2088
2243
|
/**
|
|
2089
2244
|
* Bounded retention (§4.4): keep the newest `keep` rows per
|
|
2090
|
-
* (task, role,
|
|
2245
|
+
* (task, role, AgentRun) and delete older ones. The DELETE is scoped
|
|
2091
2246
|
* by task_id; it never rewrites global rows or other Tasks. Returns the number
|
|
2092
2247
|
* of rows deleted. Terminal/semantic events go to `events` and are never pruned.
|
|
2093
2248
|
*/
|
|
2094
|
-
pruneTelemetry(taskId, roleName,
|
|
2249
|
+
pruneTelemetry(taskId, roleName, runId, keep = TELEMETRY_KEEP_PER_RUN) {
|
|
2095
2250
|
return this.#mutate(() => {
|
|
2096
2251
|
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,
|
|
2252
|
+
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2253
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
2254
|
+
SELECT task_id, role_name, turn_id, progress_id
|
|
2100
2255
|
FROM telemetry
|
|
2101
|
-
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2102
|
-
ORDER BY COALESCE(sequence, -1) DESC,
|
|
2256
|
+
WHERE task_id = ? AND role_name = ? AND turn_id = ?
|
|
2257
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
|
|
2103
2258
|
LIMIT ?
|
|
2104
|
-
)`).run(taskId, roleName,
|
|
2259
|
+
)`).run(taskId, roleName, runId, taskId, roleName, runId, keep);
|
|
2105
2260
|
return result.changes;
|
|
2106
2261
|
});
|
|
2107
2262
|
}
|
|
2108
2263
|
/**
|
|
2109
|
-
* Hard cap for an active
|
|
2264
|
+
* Hard cap for an active AgentRun (§4.4): trim oldest rows across the AgentRun beyond
|
|
2110
2265
|
* `cap` (default 50k). Returns the number of rows deleted.
|
|
2111
2266
|
*/
|
|
2112
|
-
|
|
2267
|
+
capTelemetryRun(taskId, runId, cap = TELEMETRY_RUN_CAP) {
|
|
2113
2268
|
return this.#mutate(() => {
|
|
2114
2269
|
const result = this.#db.prepare(`DELETE FROM telemetry
|
|
2115
2270
|
WHERE task_id = ? AND turn_id = ?
|
|
2116
|
-
AND (task_id, role_name, turn_id,
|
|
2117
|
-
SELECT task_id, role_name, turn_id,
|
|
2271
|
+
AND (task_id, role_name, turn_id, progress_id) NOT IN (
|
|
2272
|
+
SELECT task_id, role_name, turn_id, progress_id
|
|
2118
2273
|
FROM telemetry
|
|
2119
2274
|
WHERE task_id = ? AND turn_id = ?
|
|
2120
|
-
ORDER BY COALESCE(sequence, -1) DESC,
|
|
2275
|
+
ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
|
|
2121
2276
|
LIMIT ?
|
|
2122
|
-
)`).run(taskId,
|
|
2277
|
+
)`).run(taskId, runId, taskId, runId, cap);
|
|
2123
2278
|
return result.changes;
|
|
2124
2279
|
});
|
|
2125
2280
|
}
|