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