@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.
Files changed (308) hide show
  1. package/ARCHITECTURE.md +194 -399
  2. package/ARCHITECTURE.zh-CN.md +151 -0
  3. package/README.md +306 -1131
  4. package/dist/agent/adapterCatalog.js +15 -2
  5. package/dist/agent/agent.js +23 -3
  6. package/dist/agent/argumentPolicy.js +7 -1
  7. package/dist/agent/connectionPlan.js +62 -0
  8. package/dist/agent/executionComponents.js +158 -0
  9. package/dist/agent/launchEnvironment.js +31 -3
  10. package/dist/agent/managedRuntimeEnvironment.js +3 -5
  11. package/dist/{turn/turn.js → agentRun/agentRun.js} +166 -109
  12. package/dist/{turn/turnIdentity.js → agentRun/runIdentity.js} +4 -4
  13. package/dist/artifacts/artifactCapability.js +74 -0
  14. package/dist/artifacts/artifactCommitLock.js +249 -0
  15. package/dist/artifacts/artifactPaths.js +151 -0
  16. package/dist/artifacts/gitArtifactRef.js +146 -0
  17. package/dist/artifacts/managedGit.js +332 -0
  18. package/dist/artifacts/taskArtifactRepository.js +277 -0
  19. package/dist/brief/taskBrief.js +12 -0
  20. package/dist/cli/agentConfigurationPicker.js +13 -0
  21. package/dist/cli/commandCatalog.js +165 -74
  22. package/dist/cli/interactionCandidates.js +5 -5
  23. package/dist/cli/interactionPolicy.js +38 -8
  24. package/dist/cli/invocationRouter.js +1 -1
  25. package/dist/cli/managedDiagnostics.js +28 -0
  26. package/dist/cli/operatorWizard.js +1 -7
  27. package/dist/cli/roleOptionOrder.js +27 -0
  28. package/dist/cli/roleWizard.js +50 -14
  29. package/dist/cli/updateOrchestrator.js +1 -1
  30. package/dist/cli/updatePorts.js +3 -4
  31. package/dist/cli.js +245 -95
  32. package/dist/commands/agentCommands.js +72 -14
  33. package/dist/commands/capabilityCommands.js +9 -6
  34. package/dist/commands/configCommands.js +20 -20
  35. package/dist/commands/deliveryGuardPreflight.js +2 -2
  36. package/dist/commands/executionAuditCommands.js +24 -24
  37. package/dist/commands/globalRoleCommands.js +1 -1
  38. package/dist/commands/grantCommands.js +4 -4
  39. package/dist/commands/operatorCommands.js +34 -9
  40. package/dist/commands/projectCommands.js +4 -4
  41. package/dist/commands/resourcesCommands.js +2 -2
  42. package/dist/commands/roleConfiguration.js +25 -5
  43. package/dist/commands/roleRuntimeGuard.js +4 -5
  44. package/dist/commands/sessionCommands.js +3 -7
  45. package/dist/commands/taskActivationCommands.js +281 -0
  46. package/dist/commands/taskActor.js +28 -49
  47. package/dist/commands/taskCommands.js +1461 -720
  48. package/dist/commands/taskContextCommand.js +39 -583
  49. package/dist/commands/taskExecutionCommands.js +32 -32
  50. package/dist/commands/taskInputCommands.js +40 -104
  51. package/dist/commands/taskIntegrationCommands.js +3 -2
  52. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  53. package/dist/commands/taskNextActionCommand.js +8 -8
  54. package/dist/commands/taskOverviewCommand.js +33 -45
  55. package/dist/commands/taskRemoteDeliveryCommand.js +2 -2
  56. package/dist/commands/taskRoleRuntimeStatus.js +133 -102
  57. package/dist/commands/telemetryCommands.js +36 -38
  58. package/dist/config/configCatalog.js +4 -4
  59. package/dist/config/yuiConfig.js +8 -8
  60. package/dist/context/contextSnapshot.js +10 -10
  61. package/dist/context/dispatchContext.js +11 -11
  62. package/dist/context/roleSessionContext.js +6 -3
  63. package/dist/context/{turnContextPack.js → runContextPack.js} +158 -81
  64. package/dist/context/{turnInputContract.js → runInputContract.js} +73 -60
  65. package/dist/context/sessionBootstrapManifest.js +21 -2
  66. package/dist/context/sourceRunContext.js +30 -0
  67. package/dist/context/taskContext.js +481 -0
  68. package/dist/context/wakeNotification.js +27 -27
  69. package/dist/controller/agentRuntimeObserver.js +21 -24
  70. package/dist/controller/capabilityBridge.js +17 -6
  71. package/dist/controller/clientRuntime.js +65 -92
  72. package/dist/controller/controller.js +120 -188
  73. package/dist/controller/fileSchedulerStoreAdapter.js +787 -887
  74. package/dist/controller/jobControl.js +54 -85
  75. package/dist/controller/resourceInventory.js +8 -27
  76. package/dist/controller/resourceInventoryLinux.js +12 -13
  77. package/dist/controller/runtime.js +529 -479
  78. package/dist/controller/runtimeEventInbox.js +55 -25
  79. package/dist/controller/runtimeEventProcessor.js +22 -31
  80. package/dist/controller/{runtimeHookTurnFence.js → runtimeHookRunFence.js} +91 -115
  81. package/dist/controller/runtimeLaunchCoordinator.js +80 -426
  82. package/dist/controller/runtimeObservationHook.js +14 -18
  83. package/dist/controller/sessionNotify.js +16 -24
  84. package/dist/controller/sessionOwnerReconciliation.js +168 -50
  85. package/dist/controller/structuredProviderObservation.js +138 -99
  86. package/dist/coordination/workMailbox.js +3 -3
  87. package/dist/coordination/workMailboxQueue.js +36 -33
  88. package/dist/core/boundedRpc.js +8 -1
  89. package/dist/core/controllerClient.js +20 -1
  90. package/dist/core/controllerServer.js +4 -4
  91. package/dist/doctor/doctor.js +13 -2
  92. package/dist/domain/agentResultTransport.js +9 -9
  93. package/dist/execution/codexThreadNaming.js +2 -8
  94. package/dist/execution/executionHealth.js +51 -63
  95. package/dist/execution/reviewMainRun.js +137 -0
  96. package/dist/execution/workItemExecution.js +28 -29
  97. package/dist/execution/workItemExecutionProjection.js +99 -107
  98. package/dist/execution/workItemMainRun.js +141 -0
  99. package/dist/executor/agentAdapter.js +227 -20
  100. package/dist/executor/agentConfigurationCatalog.js +126 -4
  101. package/dist/executor/agentConfigurationProbe.js +162 -4
  102. package/dist/executor/agentExecutor.js +79 -78
  103. package/dist/executor/effectiveLaunch.js +105 -18
  104. package/dist/executor/executorRegistry.js +29 -44
  105. package/dist/executor/fileRoleLaunchPlanner.js +229 -154
  106. package/dist/executor/workspacePreflightClassification.js +16 -16
  107. package/dist/grant/capabilityGrant.js +6 -3
  108. package/dist/input/inputRequest.js +12 -10
  109. package/dist/integration/gitIntegrationService.js +4 -11
  110. package/dist/integration/integrationQueueService.js +4 -4
  111. package/dist/interaction/operatorPresentation.js +1 -1
  112. package/dist/kernel/builtinCapabilities.js +255 -12
  113. package/dist/kernel/capabilityRegistry.js +64 -18
  114. package/dist/kernel/instanceHost.js +12 -1
  115. package/dist/kernel/kernelPorts.js +2 -2
  116. package/dist/lifecycle/canonicalLifecycleEvent.js +44 -49
  117. package/dist/lifecycle/exactRunTerminalization.js +449 -0
  118. package/dist/message/message.js +118 -6
  119. package/dist/message/messageContinuation.js +204 -0
  120. package/dist/observability/executionAudit.js +70 -72
  121. package/dist/observability/faultClassification.js +2 -2
  122. package/dist/observability/orchestrationMetrics.js +8 -8
  123. package/dist/operator/operatorSessionHistory.js +1 -7
  124. package/dist/output/agentConfigurationPresentation.js +8 -3
  125. package/dist/output/agentRunConfigurationPresentation.js +128 -0
  126. package/dist/output/rolePresentation.js +54 -3
  127. package/dist/plugins/pluginChild.js +104 -0
  128. package/dist/plugins/pluginIntent.js +26 -0
  129. package/dist/plugins/pluginInterpreter.js +43 -0
  130. package/dist/plugins/pluginPackage.js +101 -0
  131. package/dist/plugins/pluginProcess.js +112 -0
  132. package/dist/plugins/pluginService.js +388 -0
  133. package/dist/profile/agentProfile.js +1 -1
  134. package/dist/repository/gitWorkspace.js +26 -4
  135. package/dist/repository/project.js +19 -4
  136. package/dist/repository/taskBaseFreshness.js +13 -13
  137. package/dist/repository/taskWorkspaceCoordinator.js +20 -27
  138. package/dist/repository/taskWorkspacePreparer.js +344 -83
  139. package/dist/resources/autoResourceGc.js +3 -3
  140. package/dist/resources/liveReferences.js +3 -3
  141. package/dist/resources/projectResource.js +75 -0
  142. package/dist/resources/projectResourceService.js +343 -0
  143. package/dist/resources/resourceDiscovery.js +6 -6
  144. package/dist/resources/resourceGc.js +1 -1
  145. package/dist/resources/resourceRegistrar.js +1 -1
  146. package/dist/resources/resourceTypes.js +1 -1
  147. package/dist/review/deltaRecheck.js +3 -3
  148. package/dist/review/reviewAcceptance.js +16 -16
  149. package/dist/review/reviewDecision.js +7 -7
  150. package/dist/review/reviewRound.js +21 -20
  151. package/dist/review/reviewerAvailability.js +2 -2
  152. package/dist/role/role.js +51 -7
  153. package/dist/role/taskRoleUpdate.js +30 -0
  154. package/dist/runtime/acpProtocol.js +425 -0
  155. package/dist/runtime/acpSession.js +731 -0
  156. package/dist/runtime/acpSessionConfiguration.js +260 -0
  157. package/dist/runtime/agentDriver.js +30 -11
  158. package/dist/runtime/agentEndpoint.js +278 -0
  159. package/dist/runtime/agentEndpointIdentity.js +86 -0
  160. package/dist/runtime/agentEndpointOwnership.js +239 -0
  161. package/dist/runtime/agentError.js +2 -10
  162. package/dist/runtime/agentHost.js +565 -314
  163. package/dist/runtime/agentRunConfiguration.js +258 -0
  164. package/dist/runtime/builtinAgentDrivers.js +134 -18
  165. package/dist/runtime/builtinAgentErrorMappers.js +55 -3
  166. package/dist/runtime/builtinTranscriptUsage.js +1 -1
  167. package/dist/runtime/claude-process-owner +0 -0
  168. package/dist/runtime/codexAppServerRuntime.js +38 -30
  169. package/dist/runtime/codexInteractiveHost.js +41 -6
  170. package/dist/runtime/continuationManager.js +2 -6
  171. package/dist/runtime/executionEnvironment.js +30 -0
  172. package/dist/runtime/firstProgressAdvisory.js +11 -11
  173. package/dist/runtime/index.js +4 -3
  174. package/dist/runtime/jsonLineChannel.js +109 -0
  175. package/dist/runtime/launchBroker.js +91 -16
  176. package/dist/runtime/launchDiagnostics.js +2 -2
  177. package/dist/runtime/lifecycleReservation.js +10 -18
  178. package/dist/runtime/managedCaller.js +61 -17
  179. package/dist/runtime/nativeSessionControl.js +102 -0
  180. package/dist/runtime/ports.js +6 -21
  181. package/dist/runtime/processExitObservation.js +8 -7
  182. package/dist/runtime/promptEnvelope.js +17 -6
  183. package/dist/runtime/providerContinuation.js +3 -9
  184. package/dist/runtime/providerContinuationReconciliationService.js +4 -13
  185. package/dist/runtime/providerControl.js +2 -7
  186. package/dist/runtime/providerRuntimeIdentity.js +110 -222
  187. package/dist/runtime/providerRuntimeReconciler.js +5 -9
  188. package/dist/runtime/runtimeBinding.js +0 -1
  189. package/dist/runtime/runtimeContinuationProjection.js +4 -7
  190. package/dist/runtime/runtimeDeadlines.js +9 -0
  191. package/dist/runtime/runtimeHealthPolicy.js +1 -1
  192. package/dist/runtime/runtimeObservation.js +29 -65
  193. package/dist/runtime/runtimeProjection.js +43 -51
  194. package/dist/runtime/runtimeSessionCandidate.js +1 -3
  195. package/dist/runtime/sessionLaunchRequest.js +3 -7
  196. package/dist/runtime/sessionOwnerIdentity.js +7 -54
  197. package/dist/runtime/sessionOwnerRegistry.js +22 -17
  198. package/dist/runtime/sessionReconciliation.js +4 -8
  199. package/dist/runtime/sessionTerminationGuard.js +70 -259
  200. package/dist/runtime/sessionTokenMetrics.js +5 -16
  201. package/dist/runtime/structuredProviderHost.js +237 -117
  202. package/dist/runtime/taskRuntimeIsolation.js +39 -122
  203. package/dist/runtime/tmuxAdapters.js +39 -86
  204. package/dist/scheduler/activeRoleRunDelivery.js +354 -0
  205. package/dist/scheduler/leaderWakeupProcessor.js +75 -266
  206. package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
  207. package/dist/scheduler/ports.js +80 -9
  208. package/dist/scheduler/{roleTurnLiveness.js → roleRunLiveness.js} +26 -30
  209. package/dist/scheduler/{roleTurnStall.js → roleRunStall.js} +128 -139
  210. package/dist/scheduler/taskExecutionProjection.js +120 -124
  211. package/dist/scheduler/taskObservabilityProjection.js +29 -29
  212. package/dist/scheduler/taskWake.js +11 -4
  213. package/dist/scheduler/wakeReason.js +9 -1
  214. package/dist/setup/setupCommand.js +3 -7
  215. package/dist/storage/migrations/agentRunContract.js +159 -0
  216. package/dist/storage/migrations/artifactsToGit.js +338 -0
  217. package/dist/storage/migrations/removeRuntimeGeneration.js +207 -0
  218. package/dist/storage/migrations/submitIntent.js +126 -0
  219. package/dist/storage/sqliteSchema.js +467 -7
  220. package/dist/storage/sqliteStore.js +355 -220
  221. package/dist/storage/storageVersions.js +1 -1
  222. package/dist/storage/storeRpc.js +10 -5
  223. package/dist/storage/taskStore.js +13 -11
  224. package/dist/storage/upgrade/upgradeOrchestrator.js +5 -7
  225. package/dist/surface/surfaceContributions.js +102 -0
  226. package/dist/task/completionReadiness.js +32 -6
  227. package/dist/task/deliveryGuard.js +16 -16
  228. package/dist/task/draftPlan.js +72 -12
  229. package/dist/task/nextAction.js +144 -128
  230. package/dist/task/remoteDelivery.js +6 -6
  231. package/dist/task/task.js +184 -18
  232. package/dist/task/taskActivation.js +327 -0
  233. package/dist/task/taskActivationService.js +408 -0
  234. package/dist/task/taskRecordReference.js +5 -4
  235. package/dist/task/taskRecordRetirement.js +1 -1
  236. package/dist/task/taskSubmission.js +236 -0
  237. package/dist/telemetry/sqliteTelemetryStore.js +55 -68
  238. package/dist/telemetry/telemetryConfig.js +14 -14
  239. package/dist/telemetry/telemetryWiring.js +2 -2
  240. package/dist/web/assets/assetManifest.js +2 -0
  241. package/dist/web/assets/client/app.js +121 -20
  242. package/dist/web/assets/client/components.js +87 -54
  243. package/dist/web/assets/client/i18n.js +83 -41
  244. package/dist/web/assets/client/markdown.js +1 -1
  245. package/dist/web/assets/client/taskSurface.js +442 -0
  246. package/dist/web/assets/client/view.js +49 -44
  247. package/dist/web/assets/shell.js +1 -1
  248. package/dist/web/assets/styles/cards.js +22 -4
  249. package/dist/web/controllerWeb.js +60 -0
  250. package/dist/web/webMutation.js +28 -0
  251. package/dist/web/webServer.js +133 -8
  252. package/dist/web/webSnapshot.js +81 -74
  253. package/dist/web/webTaskSurface.js +64 -0
  254. package/dist/workItem/dependencyGate.js +1 -1
  255. package/dist/workItem/workItem.js +84 -48
  256. package/dist/workspace/workItemChangeSetManager.js +16 -9
  257. package/docs/agent-result-consumption.md +96 -0
  258. package/docs/agent-result-consumption.zh-CN.md +81 -0
  259. package/docs/agent-runtime-drivers.md +93 -0
  260. package/docs/agent-runtime-drivers.zh-CN.md +77 -0
  261. package/docs/architecture/README.md +50 -0
  262. package/docs/architecture/README.zh-CN.md +43 -0
  263. package/docs/architecture/capabilities-and-resources.md +118 -0
  264. package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
  265. package/docs/managed-turn-and-session-runtime.md +224 -0
  266. package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
  267. package/docs/observability/README.md +83 -0
  268. package/docs/observability/README.zh-CN.md +71 -0
  269. package/docs/plugin-sdk.md +393 -0
  270. package/docs/plugin-sdk.zh-CN.md +293 -0
  271. package/docs/provider-runtime.md +165 -0
  272. package/docs/provider-runtime.zh-CN.md +132 -0
  273. package/docs/release-workflow.md +305 -0
  274. package/docs/release-workflow.zh-CN.md +237 -0
  275. package/docs/roles-and-configuration.md +115 -0
  276. package/docs/roles-and-configuration.zh-CN.md +96 -0
  277. package/docs/sqlite-control-plane-design.md +78 -0
  278. package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
  279. package/docs/task-dag-semantics.md +80 -0
  280. package/docs/task-dag-semantics.zh-CN.md +59 -0
  281. package/docs/task-delivery.md +105 -0
  282. package/docs/task-delivery.zh-CN.md +82 -0
  283. package/docs/task-local-identity.md +8 -6
  284. package/docs/task-local-identity.zh-CN.md +58 -0
  285. package/docs/testing/verification-levels.md +88 -0
  286. package/docs/testing/verification-levels.zh-CN.md +69 -0
  287. package/i18n/README.zh-CN.md +270 -722
  288. package/package.json +3 -2
  289. package/skills/yui-leader/SKILL.md +88 -304
  290. package/skills/yui-leader/references/execution.md +303 -0
  291. package/skills/yui-leader/references/integration.md +39 -0
  292. package/skills/yui-leader/references/planning.md +109 -0
  293. package/skills/yui-leader/references/replicated-execution.md +42 -0
  294. package/skills/yui-leader/references/task-plugins.md +37 -0
  295. package/skills/yui-operator/SKILL.md +46 -62
  296. package/skills/yui-reviewer/SKILL.md +35 -36
  297. package/skills/yui-runtime/SKILL.md +88 -24
  298. package/skills/yui-runtime/references/publication.md +22 -0
  299. package/skills/yui-runtime/references/recovery.md +64 -0
  300. package/skills/yui-worker/SKILL.md +37 -39
  301. package/dist/cli/roleOptionCatalog.js +0 -68
  302. package/dist/context/sourceTurnContext.js +0 -30
  303. package/dist/execution/reviewMainTurn.js +0 -161
  304. package/dist/execution/workItemMainTurn.js +0 -164
  305. package/dist/lifecycle/exactTurnTerminalization.js +0 -407
  306. package/dist/runtime/preallocatedNativeSession.js +0 -13
  307. package/dist/runtime/runtimeStopReceipt.js +0 -42
  308. 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, executionLaneActiveTurnKey, executionLaneActiveTurnKeyParts, StorageConflictError, StorageCancelledError, StorageRecordError, storedCapabilityGrant, storedPublicationReference, storedReleaseWorkflow, isValidCapabilityGrantTransition, isValidReleaseWorkflowTransition, pendingWakeupProjection, validateYuiConfig } from "./taskStore.js";
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, TELEMETRY_KEEP_PER_GENERATION, TELEMETRY_TURN_CAP } from "./sqliteSchema.js";
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.#parse(row.payload);
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.#parse(row.payload));
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
- return this.#getPayload("projects", "id = ?", [id]);
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 activeTurnRefs = [];
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 !== "completed" && workItem.status !== "retired") {
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.listTurns(task.id)) {
762
+ for (const run of this.listRuns(task.id)) {
621
763
  if (run.status === "active")
622
- activeTurnRefs.push(`${task.id}/${run.id}`);
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
- activeTurnRefs,
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
- // Turns the projection waits on and the Leader Turns the budget consumes.
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, "turn");
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
- activeTurns: runs.filter((run) => run.status === "active"),
773
- leaderTurns: runs.filter((run) => run.roleName === "leader"),
918
+ activeRuns: runs.filter((run) => run.status === "active"),
919
+ leaderRuns: runs.filter((run) => run.roleName === "leader"),
774
920
  reviewOutcomeEvidence: {
775
- turns: this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]).filter((run) => run.purpose === "review"), (run) => run.id)
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
- (launch_id, scope, task_id, role_name, agent_id, native_session_id,
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(launch_id) DO UPDATE SET
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.runtimeGenerationId, owner.scope, owner.scope === "task" ? owner.taskId : null, owner.roleName, identity.agentId, identity.nativeSessionId ?? null, identity.providerRoot.pid, this.#json(identity), identity.recordedAt);
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(runtimeGenerationId) {
968
- const row = this.#db.prepare("SELECT payload FROM session_owners WHERE launch_id = ?").get(runtimeGenerationId);
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(runtimeGenerationId) {
1139
+ removeSessionOwner(processKey) {
981
1140
  this.#mutate(() => {
982
- this.#db.prepare("DELETE FROM session_owners WHERE launch_id = ?").run(runtimeGenerationId);
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, launch_id AS runtime_generation_id, session_updated_at,
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
- ...(row.runtime_generation_id === null ? {} : { runtimeGenerationId: row.runtime_generation_id }),
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
- launch_id, session_updated_at, cleanup_required
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
- launch_id = excluded.launch_id,
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
- nextTurnId(taskId) { return this.#nextTaskRecordId(taskId, "turn"); }
1499
- peekNextTurnId(taskId) { return this.#peekTaskRecordId(taskId, "turn"); }
1500
- getTurn(taskId, turnId) {
1501
- return this.#getPayload("turns", "task_id = ? AND turn_id = ?", [taskId, turnId]);
1502
- }
1503
- listTurns(taskId) {
1504
- return this.#sortById(this.#listPayload("turns", "task_id = ?", [taskId]), (turn) => turn.id);
1505
- }
1506
- saveTurn(turn) {
1507
- if (turn.taskId !== undefined)
1508
- this.#requireTask(turn.taskId);
1509
- if (turn.reviewRoundId !== undefined) {
1510
- const round = this.getReviewRound(turn.taskId, turn.reviewRoundId);
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(`Turn ReviewRound not found: ${turn.reviewRoundId}.`);
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 === turn.executionLaneId)?.roleName;
1517
- if (roundWorkItemId !== turn.workItemId
1518
- || (round.reviewerRoleName !== turn.roleName && laneRole !== turn.roleName)) {
1519
- throw new StorageRecordError(`Turn does not match ReviewRound: ${turn.id}.`);
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(turn.taskId, turn.id, turn.roleName, turn.status, this.#json(turn), this.#now());
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
- #saveActiveTurn(taskId, pointer, turnId) {
1685
+ #saveActiveRun(taskId, pointer, runId) {
1549
1686
  this.#mutate(() => {
1550
- const payload = this.#json({ schemaVersion: 3, turnId });
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, turnId, payload, this.#now());
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
- #readActiveTurnPointer(taskId, pointer) {
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
- turnId: row.turn_id,
1561
- turn: this.getTurn(taskId, row.turn_id)
1697
+ runId: row.turn_id,
1698
+ run: this.getRun(taskId, row.turn_id)
1562
1699
  };
1563
1700
  }
1564
- #assertActiveTurnForWrite(turn, lane) {
1565
- if (turn.status !== "active") {
1566
- throw new StorageRecordError(`Active Turn must have active status: ${turn.id}`);
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 = turn.executionGroupId !== undefined;
1569
- const hasLane = turn.executionLaneId !== undefined;
1705
+ const hasGroup = run.executionGroupId !== undefined;
1706
+ const hasLane = run.executionLaneId !== undefined;
1570
1707
  if (hasGroup !== hasLane) {
1571
- throw new StorageRecordError(`Turn execution lineage is incomplete: ${turn.id}.`);
1708
+ throw new StorageRecordError(`AgentRun execution lineage is incomplete: ${run.id}.`);
1572
1709
  }
1573
1710
  if (lane !== undefined
1574
- && (turn.executionGroupId !== lane.executionGroupId
1575
- || turn.executionLaneId !== lane.executionLaneId)) {
1576
- throw new StorageRecordError(`Active Turn execution lineage does not match its Lane: ${turn.id}`);
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
- #getActiveRoleTurn(taskId, roleName) {
1580
- const pointer = this.#readActiveTurnPointer(taskId, roleName);
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 Turn Task is missing: ${taskId}/${roleName}`);
1722
+ throw new StorageRecordError(`Active AgentRun Task is missing: ${taskId}/${roleName}`);
1586
1723
  }
1587
- if (pointer.turn === null) {
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 Turn.
1590
- if (task.status === "retired")
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 Turn pointer is dangling: ${taskId}/${roleName}`);
1593
- }
1594
- const turn = pointer.turn;
1595
- if (task.status === "retired")
1596
- return turn;
1597
- if (turn.id !== pointer.turnId
1598
- || turn.taskId !== taskId
1599
- || turn.roleName !== roleName
1600
- || turn.status !== "active"
1601
- || (turn.executionGroupId === undefined) !== (turn.executionLaneId === undefined)) {
1602
- throw new StorageRecordError(`Active Turn pointer is invalid: ${taskId}/${roleName}`);
1603
- }
1604
- return turn;
1605
- }
1606
- #getActiveLaneTurn(taskId, executionGroupId, executionLaneId) {
1607
- const pointerKey = executionLaneActiveTurnKey(executionGroupId, executionLaneId);
1608
- const pointer = this.#readActiveTurnPointer(taskId, pointerKey);
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 Turn Task is missing: ${taskId}/${pointerKey}`);
1750
+ throw new StorageRecordError(`Active AgentRun Task is missing: ${taskId}/${pointerKey}`);
1614
1751
  }
1615
- if (pointer.turn === null) {
1616
- if (task.status === "retired")
1752
+ if (pointer.run === null) {
1753
+ if (task.status === "cancelled" && task.retirementIsolation === true)
1617
1754
  return null;
1618
- throw new StorageRecordError(`Active Turn pointer is dangling: ${taskId}/${pointerKey}`);
1755
+ throw new StorageRecordError(`Active AgentRun pointer is dangling: ${taskId}/${pointerKey}`);
1619
1756
  }
1620
- const turn = pointer.turn;
1621
- if (task.status === "retired")
1622
- return turn;
1623
- if (turn.id !== pointer.turnId
1624
- || turn.taskId !== taskId
1625
- || turn.status !== "active"
1626
- || turn.executionGroupId !== executionGroupId
1627
- || turn.executionLaneId !== executionLaneId) {
1628
- throw new StorageRecordError(`Active Turn pointer is invalid: ${taskId}/${pointerKey}`);
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 turn;
1767
+ return run;
1631
1768
  }
1632
- #clearActiveTurn(taskId, pointer) {
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
- getActiveTurn(taskId, roleName) {
1638
- return this.#getActiveRoleTurn(taskId, roleName);
1774
+ getActiveRun(taskId, roleName) {
1775
+ return this.#getActiveRoleRun(taskId, roleName);
1639
1776
  }
1640
- saveActiveTurn(turn) {
1641
- if (turn.executionGroupId !== undefined || turn.executionLaneId !== undefined) {
1642
- this.saveActiveExecutionLaneTurn(turn);
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(turn.taskId);
1647
- if (task === null || task.status !== "active" || task.executionGate.state !== "enabled") {
1648
- throw new StorageRecordError(`Task execution is not enabled: ${turn.taskId}.`);
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
- this.#assertActiveTurnForWrite(turn);
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.saveTurn(turn);
1659
- this.#saveActiveTurn(turn.taskId, turn.roleName, turn.id);
1793
+ store.saveRun(run);
1794
+ this.#saveActiveRun(run.taskId, run.roleName, run.id);
1660
1795
  });
1661
1796
  }
1662
- clearActiveTurn(taskId, roleName) {
1663
- // A Role key can point at a lane-backed Turn. Remove the matching lane
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.#readActiveTurnPointer(taskId, roleName);
1667
- this.#clearActiveTurn(taskId, roleName);
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 (executionLaneActiveTurnKeyParts(row.pointer) !== null
1673
- && row.turn_id === rolePointer.turnId) {
1674
- this.#clearActiveTurn(taskId, row.pointer);
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
- getActiveExecutionLaneTurn(taskId, executionGroupId, executionLaneId) {
1680
- return this.#getActiveLaneTurn(taskId, executionGroupId, executionLaneId);
1814
+ getActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
1815
+ return this.#getActiveLaneRun(taskId, executionGroupId, executionLaneId);
1681
1816
  }
1682
- saveActiveExecutionLaneTurn(turn) {
1683
- if (turn.executionGroupId === undefined || turn.executionLaneId === undefined) {
1684
- throw new StorageRecordError(`Active execution-lane Turn requires group and lane ids: ${turn.id}`);
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(turn.taskId);
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: ${turn.taskId}.`);
1824
+ throw new StorageRecordError(`Task execution is not enabled: ${run.taskId}.`);
1690
1825
  }
1691
- const key = executionLaneActiveTurnKey(turn.executionGroupId, turn.executionLaneId);
1692
- this.#assertActiveTurnForWrite(turn, {
1693
- executionGroupId: turn.executionGroupId,
1694
- executionLaneId: turn.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.getActiveExecutionLaneTurn(turn.taskId, turn.executionGroupId, turn.executionLaneId);
1697
- if (current !== null && current.id !== turn.id) {
1698
- throw new StorageRecordError(`Execution Lane already has an active Turn: ${turn.taskId}/${key}`);
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.#readActiveTurnPointer(turn.taskId, turn.roleName);
1701
- store.saveTurn(turn);
1702
- this.#saveActiveTurn(turn.taskId, key, turn.id);
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.#saveActiveTurn(turn.taskId, turn.roleName, turn.id);
1841
+ this.#saveActiveRun(run.taskId, run.roleName, run.id);
1707
1842
  }
1708
1843
  });
1709
1844
  }
1710
- clearActiveExecutionLaneTurn(taskId, executionGroupId, executionLaneId) {
1845
+ clearActiveExecutionLaneRun(taskId, executionGroupId, executionLaneId) {
1711
1846
  this.transaction(() => {
1712
- const key = executionLaneActiveTurnKey(executionGroupId, executionLaneId);
1713
- const lanePointer = this.#readActiveTurnPointer(taskId, key);
1714
- this.#clearActiveTurn(taskId, key);
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 (executionLaneActiveTurnKeyParts(row.pointer) === null
1720
- && row.turn_id === lanePointer.turnId) {
1721
- this.#clearActiveTurn(taskId, row.pointer);
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.turnId ?? null, wake.fromCursor, wake.toCursor, this.#json([...wake.reasons]), this.#json(wake), wake.createdAt, wake.consumedAt ?? null);
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 match = new RegExp(`^${TASK_RECORD_ID_PREFIXES[kind]}-(\\d+)$`).exec(id);
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, generation,
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, generation, progress_id, sequence, payload, received_at)
2061
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
2062
- ON CONFLICT(task_id, role_name, turn_id, generation, progress_id)
2063
- DO UPDATE SET sequence = excluded.sequence, payload = excluded.payload, received_at = excluded.received_at`).run(entry.taskId, entry.roleName, entry.turnId, entry.generation, entry.progressId, entry.sequence ?? null, this.#json(entry.payload), entry.receivedAt);
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, turnId) {
2067
- const rows = turnId === undefined
2068
- ? this.#db.prepare("SELECT task_id, role_name, turn_id, generation, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? ORDER BY received_at").all(taskId)
2069
- : this.#db.prepare("SELECT task_id, role_name, turn_id, generation, progress_id, sequence, payload, received_at FROM telemetry WHERE task_id = ? AND turn_id = ? ORDER BY received_at").all(taskId, turnId);
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
- turnId: row.turn_id,
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, turnId) {
2083
- const row = turnId === undefined
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, turnId);
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, run, generation) and delete older ones. The DELETE is scoped
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, turnId, generation, keep = TELEMETRY_KEEP_PER_GENERATION) {
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 = ? AND generation = ?
2098
- AND (task_id, role_name, turn_id, generation, progress_id) NOT IN (
2099
- SELECT task_id, role_name, turn_id, generation, progress_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 = ? AND generation = ?
2102
- ORDER BY COALESCE(sequence, -1) DESC, received_at DESC, progress_id ASC
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, turnId, generation, taskId, roleName, turnId, generation, keep);
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 Turn (§4.4): trim oldest rows across the Turn beyond
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
- capTelemetryTurn(taskId, turnId, cap = TELEMETRY_TURN_CAP) {
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, generation, progress_id) NOT IN (
2117
- SELECT task_id, role_name, turn_id, generation, progress_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, received_at DESC, progress_id ASC
2255
+ ORDER BY received_at DESC, COALESCE(sequence, -1) DESC, progress_id ASC
2121
2256
  LIMIT ?
2122
- )`).run(taskId, turnId, taskId, turnId, cap);
2257
+ )`).run(taskId, runId, taskId, runId, cap);
2123
2258
  return result.changes;
2124
2259
  });
2125
2260
  }