actoviq-agent-sdk 0.4.11 → 0.4.13

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/CHANGELOG.md +42 -0
  2. package/dist/src/computer/hadamardComputerUse.d.ts +15 -0
  3. package/dist/src/computer/hadamardComputerUse.d.ts.map +1 -1
  4. package/dist/src/computer/hadamardComputerUse.js +120 -4
  5. package/dist/src/computer/hadamardComputerUse.js.map +1 -1
  6. package/dist/src/config/agentDefinitionMigration.d.ts +125 -0
  7. package/dist/src/config/agentDefinitionMigration.d.ts.map +1 -0
  8. package/dist/src/config/agentDefinitionMigration.js +540 -0
  9. package/dist/src/config/agentDefinitionMigration.js.map +1 -0
  10. package/dist/src/config/agentProfiles.d.ts +23 -0
  11. package/dist/src/config/agentProfiles.d.ts.map +1 -1
  12. package/dist/src/config/agentProfiles.js +69 -13
  13. package/dist/src/config/agentProfiles.js.map +1 -1
  14. package/dist/src/config/hadamardHome.js +0 -1
  15. package/dist/src/config/hadamardHome.js.map +1 -1
  16. package/dist/src/config/modelConfigurationCatalog.d.ts +25 -0
  17. package/dist/src/config/modelConfigurationCatalog.d.ts.map +1 -0
  18. package/dist/src/config/modelConfigurationCatalog.js +50 -0
  19. package/dist/src/config/modelConfigurationCatalog.js.map +1 -0
  20. package/dist/src/config/projectSettings.d.ts +62 -0
  21. package/dist/src/config/projectSettings.d.ts.map +1 -0
  22. package/dist/src/config/projectSettings.js +242 -0
  23. package/dist/src/config/projectSettings.js.map +1 -0
  24. package/dist/src/config/resolveRuntimeConfig.d.ts.map +1 -1
  25. package/dist/src/config/resolveRuntimeConfig.js +97 -4
  26. package/dist/src/config/resolveRuntimeConfig.js.map +1 -1
  27. package/dist/src/goal/goalCommandService.d.ts +11 -0
  28. package/dist/src/goal/goalCommandService.d.ts.map +1 -0
  29. package/dist/src/goal/goalCommandService.js +138 -0
  30. package/dist/src/goal/goalCommandService.js.map +1 -0
  31. package/dist/src/goal/goalContinuation.d.ts +50 -0
  32. package/dist/src/goal/goalContinuation.d.ts.map +1 -0
  33. package/dist/src/goal/goalContinuation.js +131 -0
  34. package/dist/src/goal/goalContinuation.js.map +1 -0
  35. package/dist/src/goal/goalController.d.ts +42 -0
  36. package/dist/src/goal/goalController.d.ts.map +1 -0
  37. package/dist/src/goal/goalController.js +281 -0
  38. package/dist/src/goal/goalController.js.map +1 -0
  39. package/dist/src/goal/goalPrompt.d.ts +3 -0
  40. package/dist/src/goal/goalPrompt.d.ts.map +1 -1
  41. package/dist/src/goal/goalPrompt.js +28 -2
  42. package/dist/src/goal/goalPrompt.js.map +1 -1
  43. package/dist/src/goal/goalService.d.ts +68 -2
  44. package/dist/src/goal/goalService.d.ts.map +1 -1
  45. package/dist/src/goal/goalService.js +500 -8
  46. package/dist/src/goal/goalService.js.map +1 -1
  47. package/dist/src/goal/goalStore.d.ts.map +1 -1
  48. package/dist/src/goal/goalStore.js +172 -5
  49. package/dist/src/goal/goalStore.js.map +1 -1
  50. package/dist/src/goal/goalTools.d.ts +2 -1
  51. package/dist/src/goal/goalTools.d.ts.map +1 -1
  52. package/dist/src/goal/goalTools.js +70 -5
  53. package/dist/src/goal/goalTools.js.map +1 -1
  54. package/dist/src/goal/goalWorkCoordinator.d.ts +43 -0
  55. package/dist/src/goal/goalWorkCoordinator.d.ts.map +1 -0
  56. package/dist/src/goal/goalWorkCoordinator.js +72 -0
  57. package/dist/src/goal/goalWorkCoordinator.js.map +1 -0
  58. package/dist/src/goal/index.d.ts +10 -4
  59. package/dist/src/goal/index.d.ts.map +1 -1
  60. package/dist/src/goal/index.js +7 -1
  61. package/dist/src/goal/index.js.map +1 -1
  62. package/dist/src/goal/projectGoalApi.d.ts +82 -0
  63. package/dist/src/goal/projectGoalApi.d.ts.map +1 -0
  64. package/dist/src/goal/projectGoalApi.js +287 -0
  65. package/dist/src/goal/projectGoalApi.js.map +1 -0
  66. package/dist/src/goal/projectGoalStore.d.ts +131 -0
  67. package/dist/src/goal/projectGoalStore.d.ts.map +1 -0
  68. package/dist/src/goal/projectGoalStore.js +808 -0
  69. package/dist/src/goal/projectGoalStore.js.map +1 -0
  70. package/dist/src/goal/types.d.ts +127 -3
  71. package/dist/src/goal/types.d.ts.map +1 -1
  72. package/dist/src/goal/types.js +2 -2
  73. package/dist/src/goal/types.js.map +1 -1
  74. package/dist/src/gui/agentTargetPicker.d.ts +32 -0
  75. package/dist/src/gui/agentTargetPicker.d.ts.map +1 -0
  76. package/dist/src/gui/agentTargetPicker.js +99 -0
  77. package/dist/src/gui/agentTargetPicker.js.map +1 -0
  78. package/dist/src/gui/electronMain.js +10 -3
  79. package/dist/src/gui/electronMain.js.map +1 -1
  80. package/dist/src/gui/hadamardGui.d.ts +24 -0
  81. package/dist/src/gui/hadamardGui.d.ts.map +1 -1
  82. package/dist/src/gui/hadamardGui.js +6321 -1327
  83. package/dist/src/gui/hadamardGui.js.map +1 -1
  84. package/dist/src/gui/projectWorkbench.d.ts +13 -2
  85. package/dist/src/gui/projectWorkbench.d.ts.map +1 -1
  86. package/dist/src/gui/projectWorkbench.js +109 -67
  87. package/dist/src/gui/projectWorkbench.js.map +1 -1
  88. package/dist/src/gui/promptTemplates.d.ts +18 -0
  89. package/dist/src/gui/promptTemplates.d.ts.map +1 -0
  90. package/dist/src/gui/promptTemplates.js +178 -0
  91. package/dist/src/gui/promptTemplates.js.map +1 -0
  92. package/dist/src/gui/terminalManager.d.ts +14 -0
  93. package/dist/src/gui/terminalManager.d.ts.map +1 -1
  94. package/dist/src/gui/terminalManager.js +25 -5
  95. package/dist/src/gui/terminalManager.js.map +1 -1
  96. package/dist/src/gui/transcript/clientBundle.d.ts +6 -1
  97. package/dist/src/gui/transcript/clientBundle.d.ts.map +1 -1
  98. package/dist/src/gui/transcript/clientBundle.js +6 -1
  99. package/dist/src/gui/transcript/clientBundle.js.map +1 -1
  100. package/dist/src/help/productCapabilities.d.ts +17 -0
  101. package/dist/src/help/productCapabilities.d.ts.map +1 -0
  102. package/dist/src/help/productCapabilities.js +146 -0
  103. package/dist/src/help/productCapabilities.js.map +1 -0
  104. package/dist/src/index.d.ts +21 -6
  105. package/dist/src/index.d.ts.map +1 -1
  106. package/dist/src/index.js +15 -5
  107. package/dist/src/index.js.map +1 -1
  108. package/dist/src/issues/issueExecution.d.ts.map +1 -1
  109. package/dist/src/issues/issueExecution.js +29 -9
  110. package/dist/src/issues/issueExecution.js.map +1 -1
  111. package/dist/src/manager/assistantGlobalTools.d.ts +44 -0
  112. package/dist/src/manager/assistantGlobalTools.d.ts.map +1 -1
  113. package/dist/src/manager/assistantGlobalTools.js +565 -34
  114. package/dist/src/manager/assistantGlobalTools.js.map +1 -1
  115. package/dist/src/manager/assistantProposals.d.ts +87 -0
  116. package/dist/src/manager/assistantProposals.d.ts.map +1 -0
  117. package/dist/src/manager/assistantProposals.js +207 -0
  118. package/dist/src/manager/assistantProposals.js.map +1 -0
  119. package/dist/src/manager/referenceIndex.d.ts +82 -0
  120. package/dist/src/manager/referenceIndex.d.ts.map +1 -0
  121. package/dist/src/manager/referenceIndex.js +167 -0
  122. package/dist/src/manager/referenceIndex.js.map +1 -0
  123. package/dist/src/manager/referenceOperations.d.ts +108 -0
  124. package/dist/src/manager/referenceOperations.d.ts.map +1 -0
  125. package/dist/src/manager/referenceOperations.js +821 -0
  126. package/dist/src/manager/referenceOperations.js.map +1 -0
  127. package/dist/src/manager/resolveTargetRef.d.ts +67 -0
  128. package/dist/src/manager/resolveTargetRef.d.ts.map +1 -0
  129. package/dist/src/manager/resolveTargetRef.js +81 -0
  130. package/dist/src/manager/resolveTargetRef.js.map +1 -0
  131. package/dist/src/memory/durableMemoryPipeline.d.ts +51 -0
  132. package/dist/src/memory/durableMemoryPipeline.d.ts.map +1 -0
  133. package/dist/src/memory/durableMemoryPipeline.js +266 -0
  134. package/dist/src/memory/durableMemoryPipeline.js.map +1 -0
  135. package/dist/src/memory/durableMemoryStore.d.ts +59 -0
  136. package/dist/src/memory/durableMemoryStore.d.ts.map +1 -0
  137. package/dist/src/memory/durableMemoryStore.js +194 -0
  138. package/dist/src/memory/durableMemoryStore.js.map +1 -0
  139. package/dist/src/memory/hadamardDream.d.ts +18 -0
  140. package/dist/src/memory/hadamardDream.d.ts.map +1 -1
  141. package/dist/src/memory/hadamardDream.js +119 -39
  142. package/dist/src/memory/hadamardDream.js.map +1 -1
  143. package/dist/src/memory/hadamardMemory.d.ts +20 -39
  144. package/dist/src/memory/hadamardMemory.d.ts.map +1 -1
  145. package/dist/src/memory/hadamardMemory.js +112 -450
  146. package/dist/src/memory/hadamardMemory.js.map +1 -1
  147. package/dist/src/memory/hadamardSessionMemoryState.d.ts +2 -5
  148. package/dist/src/memory/hadamardSessionMemoryState.d.ts.map +1 -1
  149. package/dist/src/memory/hadamardSessionMemoryState.js +5 -61
  150. package/dist/src/memory/hadamardSessionMemoryState.js.map +1 -1
  151. package/dist/src/memory/memoryCommandService.d.ts +25 -0
  152. package/dist/src/memory/memoryCommandService.d.ts.map +1 -0
  153. package/dist/src/memory/memoryCommandService.js +93 -0
  154. package/dist/src/memory/memoryCommandService.js.map +1 -0
  155. package/dist/src/memory/memoryProposalCommandService.d.ts.map +1 -1
  156. package/dist/src/memory/memoryProposalCommandService.js +2 -1
  157. package/dist/src/memory/memoryProposalCommandService.js.map +1 -1
  158. package/dist/src/memory/memoryProposalTools.d.ts +1 -0
  159. package/dist/src/memory/memoryProposalTools.d.ts.map +1 -1
  160. package/dist/src/memory/memoryProposalTools.js +16 -11
  161. package/dist/src/memory/memoryProposalTools.js.map +1 -1
  162. package/dist/src/parity/bridgeConfigs.d.ts +4 -0
  163. package/dist/src/parity/bridgeConfigs.d.ts.map +1 -1
  164. package/dist/src/parity/bridgeConfigs.js +30 -1
  165. package/dist/src/parity/bridgeConfigs.js.map +1 -1
  166. package/dist/src/router/modelRouter.d.ts +22 -2
  167. package/dist/src/router/modelRouter.d.ts.map +1 -1
  168. package/dist/src/router/modelRouter.js +76 -9
  169. package/dist/src/router/modelRouter.js.map +1 -1
  170. package/dist/src/runtime/agentClient.d.ts +24 -4
  171. package/dist/src/runtime/agentClient.d.ts.map +1 -1
  172. package/dist/src/runtime/agentClient.js +581 -262
  173. package/dist/src/runtime/agentClient.js.map +1 -1
  174. package/dist/src/runtime/agentSession.d.ts +8 -3
  175. package/dist/src/runtime/agentSession.d.ts.map +1 -1
  176. package/dist/src/runtime/agentSession.js +10 -3
  177. package/dist/src/runtime/agentSession.js.map +1 -1
  178. package/dist/src/runtime/agentTemplates.d.ts +28 -0
  179. package/dist/src/runtime/agentTemplates.d.ts.map +1 -0
  180. package/dist/src/runtime/agentTemplates.js +73 -0
  181. package/dist/src/runtime/agentTemplates.js.map +1 -0
  182. package/dist/src/runtime/conversationEngine.d.ts +4 -0
  183. package/dist/src/runtime/conversationEngine.d.ts.map +1 -1
  184. package/dist/src/runtime/conversationEngine.js +52 -3
  185. package/dist/src/runtime/conversationEngine.js.map +1 -1
  186. package/dist/src/runtime/defaultHadamardAgents.d.ts.map +1 -1
  187. package/dist/src/runtime/defaultHadamardAgents.js +9 -47
  188. package/dist/src/runtime/defaultHadamardAgents.js.map +1 -1
  189. package/dist/src/runtime/effectiveAgentRunOptions.d.ts +49 -0
  190. package/dist/src/runtime/effectiveAgentRunOptions.d.ts.map +1 -0
  191. package/dist/src/runtime/effectiveAgentRunOptions.js +50 -0
  192. package/dist/src/runtime/effectiveAgentRunOptions.js.map +1 -0
  193. package/dist/src/runtime/externalAgentRunner.d.ts +25 -0
  194. package/dist/src/runtime/externalAgentRunner.d.ts.map +1 -0
  195. package/dist/src/runtime/externalAgentRunner.js +57 -0
  196. package/dist/src/runtime/externalAgentRunner.js.map +1 -0
  197. package/dist/src/runtime/hadamardAgentDefinitions.d.ts +15 -0
  198. package/dist/src/runtime/hadamardAgentDefinitions.d.ts.map +1 -1
  199. package/dist/src/runtime/hadamardAgentDefinitions.js +42 -2
  200. package/dist/src/runtime/hadamardAgentDefinitions.js.map +1 -1
  201. package/dist/src/runtime/hadamardAgents.d.ts +6 -0
  202. package/dist/src/runtime/hadamardAgents.d.ts.map +1 -1
  203. package/dist/src/runtime/hadamardAgents.js +52 -1
  204. package/dist/src/runtime/hadamardAgents.js.map +1 -1
  205. package/dist/src/runtime/hadamardCompact.d.ts +12 -1
  206. package/dist/src/runtime/hadamardCompact.d.ts.map +1 -1
  207. package/dist/src/runtime/hadamardCompact.js +91 -35
  208. package/dist/src/runtime/hadamardCompact.js.map +1 -1
  209. package/dist/src/runtime/hadamardPermissions.d.ts +1 -0
  210. package/dist/src/runtime/hadamardPermissions.d.ts.map +1 -1
  211. package/dist/src/runtime/hadamardPermissions.js +6 -0
  212. package/dist/src/runtime/hadamardPermissions.js.map +1 -1
  213. package/dist/src/runtime/hadamardSlashCommands.d.ts +7 -1
  214. package/dist/src/runtime/hadamardSlashCommands.d.ts.map +1 -1
  215. package/dist/src/runtime/hadamardSlashCommands.js +47 -5
  216. package/dist/src/runtime/hadamardSlashCommands.js.map +1 -1
  217. package/dist/src/runtime/tools.d.ts.map +1 -1
  218. package/dist/src/runtime/tools.js +2 -0
  219. package/dist/src/runtime/tools.js.map +1 -1
  220. package/dist/src/team/executorTarget.d.ts +26 -0
  221. package/dist/src/team/executorTarget.d.ts.map +1 -0
  222. package/dist/src/team/executorTarget.js +15 -0
  223. package/dist/src/team/executorTarget.js.map +1 -0
  224. package/dist/src/team/modelTeam.d.ts +5 -1
  225. package/dist/src/team/modelTeam.d.ts.map +1 -1
  226. package/dist/src/team/modelTeam.js +157 -41
  227. package/dist/src/team/modelTeam.js.map +1 -1
  228. package/dist/src/team/teamComposition.d.ts +20 -0
  229. package/dist/src/team/teamComposition.d.ts.map +1 -0
  230. package/dist/src/team/teamComposition.js +78 -0
  231. package/dist/src/team/teamComposition.js.map +1 -0
  232. package/dist/src/team/teamDefinitions.d.ts +10 -1
  233. package/dist/src/team/teamDefinitions.d.ts.map +1 -1
  234. package/dist/src/team/teamDefinitions.js +123 -16
  235. package/dist/src/team/teamDefinitions.js.map +1 -1
  236. package/dist/src/team/teamGraph.d.ts +1 -1
  237. package/dist/src/team/teamGraph.d.ts.map +1 -1
  238. package/dist/src/team/teamGraph.js +1 -1
  239. package/dist/src/team/teamGraph.js.map +1 -1
  240. package/dist/src/team/teamPrompts.d.ts.map +1 -1
  241. package/dist/src/team/teamPrompts.js +0 -4
  242. package/dist/src/team/teamPrompts.js.map +1 -1
  243. package/dist/src/team/teamProposalService.d.ts +9 -1
  244. package/dist/src/team/teamProposalService.d.ts.map +1 -1
  245. package/dist/src/team/teamProposalService.js +66 -6
  246. package/dist/src/team/teamProposalService.js.map +1 -1
  247. package/dist/src/team/teamPropose.d.ts +3 -1
  248. package/dist/src/team/teamPropose.d.ts.map +1 -1
  249. package/dist/src/team/teamPropose.js +16 -10
  250. package/dist/src/team/teamPropose.js.map +1 -1
  251. package/dist/src/team/teamRuntime.d.ts +5 -0
  252. package/dist/src/team/teamRuntime.d.ts.map +1 -1
  253. package/dist/src/team/teamRuntime.js +23 -5
  254. package/dist/src/team/teamRuntime.js.map +1 -1
  255. package/dist/src/team/workflowSquad.d.ts +10 -2
  256. package/dist/src/team/workflowSquad.d.ts.map +1 -1
  257. package/dist/src/team/workflowSquad.js +250 -12
  258. package/dist/src/team/workflowSquad.js.map +1 -1
  259. package/dist/src/tools/enterWorktree.d.ts.map +1 -1
  260. package/dist/src/tools/enterWorktree.js +3 -0
  261. package/dist/src/tools/enterWorktree.js.map +1 -1
  262. package/dist/src/tools/exaSearch.d.ts +2 -2
  263. package/dist/src/tools/exitWorktree.d.ts.map +1 -1
  264. package/dist/src/tools/exitWorktree.js +3 -1
  265. package/dist/src/tools/exitWorktree.js.map +1 -1
  266. package/dist/src/tools/hadamardCoreTools.d.ts +5 -14
  267. package/dist/src/tools/hadamardCoreTools.d.ts.map +1 -1
  268. package/dist/src/tools/hadamardCoreTools.js +31 -0
  269. package/dist/src/tools/hadamardCoreTools.js.map +1 -1
  270. package/dist/src/tools/hadamardFileTools.d.ts.map +1 -1
  271. package/dist/src/tools/hadamardFileTools.js +116 -38
  272. package/dist/src/tools/hadamardFileTools.js.map +1 -1
  273. package/dist/src/tools/planMode/PlanModeTools.d.ts +22 -0
  274. package/dist/src/tools/planMode/PlanModeTools.d.ts.map +1 -1
  275. package/dist/src/tools/planMode/PlanModeTools.js +102 -5
  276. package/dist/src/tools/planMode/PlanModeTools.js.map +1 -1
  277. package/dist/src/tools/todo/TodoWriteTool.d.ts +1 -1
  278. package/dist/src/tui/hadamardTui.d.ts.map +1 -1
  279. package/dist/src/tui/hadamardTui.js +387 -168
  280. package/dist/src/tui/hadamardTui.js.map +1 -1
  281. package/dist/src/tui/pendingInput.d.ts +17 -0
  282. package/dist/src/tui/pendingInput.d.ts.map +1 -0
  283. package/dist/src/tui/pendingInput.js +23 -0
  284. package/dist/src/tui/pendingInput.js.map +1 -0
  285. package/dist/src/tui/reasoningDisplay.d.ts +13 -0
  286. package/dist/src/tui/reasoningDisplay.d.ts.map +1 -0
  287. package/dist/src/tui/reasoningDisplay.js +45 -0
  288. package/dist/src/tui/reasoningDisplay.js.map +1 -0
  289. package/dist/src/tui/screen.d.ts +11 -1
  290. package/dist/src/tui/screen.d.ts.map +1 -1
  291. package/dist/src/tui/screen.js +64 -34
  292. package/dist/src/tui/screen.js.map +1 -1
  293. package/dist/src/types.d.ts +210 -77
  294. package/dist/src/types.d.ts.map +1 -1
  295. package/dist/src/types.js.map +1 -1
  296. package/dist/src/ui/commandSurface.d.ts +8 -0
  297. package/dist/src/ui/commandSurface.d.ts.map +1 -1
  298. package/dist/src/ui/commandSurface.js +78 -13
  299. package/dist/src/ui/commandSurface.js.map +1 -1
  300. package/dist/src/update/appUpdateService.d.ts +34 -1
  301. package/dist/src/update/appUpdateService.d.ts.map +1 -1
  302. package/dist/src/update/appUpdateService.js +114 -9
  303. package/dist/src/update/appUpdateService.js.map +1 -1
  304. package/package.json +3 -2
  305. package/dist/src/prompts/outputStyles.d.ts +0 -20
  306. package/dist/src/prompts/outputStyles.d.ts.map +0 -1
  307. package/dist/src/prompts/outputStyles.js +0 -35
  308. package/dist/src/prompts/outputStyles.js.map +0 -1
@@ -0,0 +1,808 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { nodeSqliteDriverFactory } from '../storage-v2/sqliteDriver.js';
4
+ import { createId } from '../runtime/helpers.js';
5
+ import { normalizeGoal } from './goalStore.js';
6
+ export const PROJECT_GOAL_DATABASE = 'goal-state.sqlite';
7
+ /** Project-scoped durable store for Goal snapshots, audit events, and session attachments. */
8
+ export class ProjectGoalStore {
9
+ driver;
10
+ constructor(driver) {
11
+ this.driver = driver;
12
+ }
13
+ static async open(projectStateDirectory) {
14
+ await mkdir(projectStateDirectory, { recursive: true });
15
+ const driver = await nodeSqliteDriverFactory.open(path.join(projectStateDirectory, PROJECT_GOAL_DATABASE));
16
+ initializeSchema(driver);
17
+ return new ProjectGoalStore(driver);
18
+ }
19
+ close() {
20
+ this.driver.close();
21
+ }
22
+ createGoalId() {
23
+ return `goal:${createId()}`;
24
+ }
25
+ /**
26
+ * Goals are session-scoped: a session only sees the goal it is attached to, so
27
+ * a new chat starts without one instead of inheriting the project's last goal.
28
+ */
29
+ portForSession(sessionId, options = {}) {
30
+ const attached = options.forceNew ? undefined : this.goalIdForSession(sessionId);
31
+ return new ProjectGoalSessionPort(this, sessionId, attached);
32
+ }
33
+ importLegacyGoal(sessionId, raw, now = new Date().toISOString()) {
34
+ if (this.goalIdForSession(sessionId))
35
+ return this.goalIdForSession(sessionId);
36
+ const goal = normalizeGoal(raw, now);
37
+ if (!goal)
38
+ return undefined;
39
+ const goalId = this.createGoalId();
40
+ this.writeSnapshot(goalId, goal, 'legacy_imported');
41
+ this.attachSession(sessionId, goalId, now);
42
+ return goalId;
43
+ }
44
+ readForSession(sessionId) {
45
+ const goalId = this.goalIdForSession(sessionId);
46
+ if (!goalId)
47
+ return undefined;
48
+ const goal = this.readSnapshot(goalId);
49
+ return goal ? { id: goalId, goal } : undefined;
50
+ }
51
+ listGoals(options = {}) {
52
+ const rows = this.driver.prepare(`
53
+ SELECT goal_id, objective, status, revision, created_at, updated_at
54
+ FROM goals
55
+ ${options.includeArchived ? '' : 'WHERE archived_at IS NULL'}
56
+ ORDER BY updated_at DESC
57
+ LIMIT ?
58
+ `).all(Math.max(1, Math.min(options.limit ?? 100, 500)));
59
+ const attachments = this.driver.prepare('SELECT session_id FROM goal_sessions WHERE goal_id = ? ORDER BY attached_at ASC');
60
+ return rows.map(row => ({
61
+ id: String(row.goal_id),
62
+ objective: String(row.objective),
63
+ status: String(row.status),
64
+ revision: Number(row.revision),
65
+ createdAt: String(row.created_at),
66
+ updatedAt: String(row.updated_at),
67
+ attachedSessionIds: attachments.all(String(row.goal_id)).map(item => String(item.session_id)),
68
+ }));
69
+ }
70
+ history(goalId, limit = 100) {
71
+ return this.driver.prepare(`
72
+ SELECT event_id, goal_id, event_type, revision, payload_json, created_at
73
+ FROM goal_events
74
+ WHERE goal_id = ?
75
+ ORDER BY event_id DESC
76
+ LIMIT ?
77
+ `).all(goalId, Math.max(1, Math.min(limit, 500))).reverse().map(row => ({
78
+ id: Number(row.event_id),
79
+ goalId: String(row.goal_id),
80
+ type: String(row.event_type),
81
+ at: String(row.created_at),
82
+ ...(typeof row.revision === 'number' ? { revision: row.revision } : {}),
83
+ ...(typeof row.payload_json === 'string'
84
+ ? { payload: parseObject(row.payload_json) }
85
+ : {}),
86
+ }));
87
+ }
88
+ continuationState(goalId) {
89
+ const row = this.driver.prepare(`
90
+ SELECT c.*, (
91
+ SELECT session_id FROM goal_sessions s
92
+ WHERE s.goal_id = c.goal_id ORDER BY attached_at DESC LIMIT 1
93
+ ) AS session_id
94
+ FROM goal_continuation c WHERE c.goal_id = ?
95
+ `).get(goalId);
96
+ return row ? mapContinuation(row) : undefined;
97
+ }
98
+ listScheduledContinuations() {
99
+ return this.driver.prepare(`
100
+ SELECT c.*, (
101
+ SELECT session_id FROM goal_sessions s
102
+ WHERE s.goal_id = c.goal_id ORDER BY attached_at DESC LIMIT 1
103
+ ) AS session_id
104
+ FROM goal_continuation c
105
+ JOIN goals g ON g.goal_id = c.goal_id
106
+ WHERE c.mode = 'scheduled' AND g.archived_at IS NULL AND g.status = 'active'
107
+ ORDER BY COALESCE(c.next_wake_at, g.updated_at) ASC
108
+ `).all().map(mapContinuation);
109
+ }
110
+ configureContinuation(input) {
111
+ const now = input.now ?? new Date();
112
+ const min = clampSeconds(input.minIntervalSeconds ?? 30, 1, 86_400);
113
+ const max = clampSeconds(input.maxIntervalSeconds ?? 1_800, min, 604_800);
114
+ const current = this.continuationState(input.goalId);
115
+ const interval = Math.max(min, Math.min(current?.currentIntervalSeconds ?? min, max));
116
+ const nextWakeAt = input.mode === 'scheduled'
117
+ ? new Date(now.getTime() + interval * 1_000).toISOString()
118
+ : null;
119
+ this.driver.prepare(`
120
+ INSERT INTO goal_continuation (
121
+ goal_id, mode, execution_profile_json, min_interval_seconds, max_interval_seconds,
122
+ current_interval_seconds, next_wake_at, consecutive_no_change, updated_at
123
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)
124
+ ON CONFLICT(goal_id) DO UPDATE SET
125
+ mode = excluded.mode,
126
+ execution_profile_json = excluded.execution_profile_json,
127
+ min_interval_seconds = excluded.min_interval_seconds,
128
+ max_interval_seconds = excluded.max_interval_seconds,
129
+ current_interval_seconds = excluded.current_interval_seconds,
130
+ next_wake_at = excluded.next_wake_at,
131
+ updated_at = excluded.updated_at,
132
+ last_error = NULL
133
+ `).run(input.goalId, input.mode, input.executionProfile ? JSON.stringify(input.executionProfile) : null, min, max, interval, nextWakeAt, now.toISOString());
134
+ this.insertEvent(input.goalId, 'continuation_configured', now.toISOString(), undefined, {
135
+ mode: input.mode,
136
+ ...(input.executionProfile ? { executionProfile: input.executionProfile } : {}),
137
+ });
138
+ return this.continuationState(input.goalId);
139
+ }
140
+ acquireContinuationLease(goalId, owner, now = new Date(), leaseMs = 15 * 60_000) {
141
+ return this.driver.transaction(() => {
142
+ const state = this.continuationState(goalId);
143
+ if (!state)
144
+ return false;
145
+ if (state.leaseOwner && isFuture(state.leaseExpiresAt, now))
146
+ return false;
147
+ const result = this.driver.prepare(`
148
+ UPDATE goal_continuation SET lease_owner = ?, lease_expires_at = ?, updated_at = ?
149
+ WHERE goal_id = ?
150
+ `).run(owner, new Date(now.getTime() + leaseMs).toISOString(), now.toISOString(), goalId);
151
+ return result.changes === 1;
152
+ });
153
+ }
154
+ settleContinuation(input) {
155
+ const now = input.now ?? new Date();
156
+ const current = this.continuationState(input.goalId);
157
+ if (!current || current.leaseOwner !== input.owner)
158
+ return current;
159
+ // Replan attempts are not "no progress" — only true stalls back off.
160
+ const noChange = input.outcome === 'no_change'
161
+ || input.outcome === 'failed'
162
+ || input.outcome === 'interrupted'
163
+ || input.outcome === 'validation_failed'
164
+ || Boolean(input.error);
165
+ const interval = noChange
166
+ ? Math.min(current.maxIntervalSeconds, Math.max(current.minIntervalSeconds, current.currentIntervalSeconds * 2))
167
+ : Math.max(current.minIntervalSeconds, Math.floor(current.currentIntervalSeconds / 2));
168
+ const nextWakeAt = current.mode === 'scheduled'
169
+ ? new Date(now.getTime() + interval * 1_000).toISOString()
170
+ : null;
171
+ this.driver.prepare(`
172
+ UPDATE goal_continuation SET
173
+ current_interval_seconds = ?, next_wake_at = ?, last_wake_at = ?, last_outcome = ?,
174
+ consecutive_no_change = ?, lease_owner = NULL, lease_expires_at = NULL,
175
+ last_error = ?, updated_at = ?
176
+ WHERE goal_id = ? AND lease_owner = ?
177
+ `).run(interval, nextWakeAt, now.toISOString(), input.outcome, noChange ? current.consecutiveNoChange + 1 : 0, input.error?.slice(0, 4_000) ?? null, now.toISOString(), input.goalId, input.owner);
178
+ this.insertEvent(input.goalId, input.error ? 'continuation_failed' : 'continuation_settled', now.toISOString(), undefined, {
179
+ outcome: input.outcome,
180
+ intervalSeconds: interval,
181
+ ...(input.error ? { error: input.error.slice(0, 4_000) } : {}),
182
+ });
183
+ return this.continuationState(input.goalId);
184
+ }
185
+ claimNextWork(input) {
186
+ const now = input.now ?? new Date();
187
+ const roleScopes = unique(input.roleScopes ?? []);
188
+ return this.driver.transaction(() => {
189
+ const goal = this.readSnapshot(input.goalId);
190
+ if (!goal || goal.status !== 'active')
191
+ return undefined;
192
+ const completed = new Set(goal.workItems
193
+ .filter(item => item.status === 'done' || item.status === 'cancelled')
194
+ .map(item => item.id));
195
+ const claims = new Map(this.driver.prepare(`
196
+ SELECT * FROM goal_work_claims WHERE goal_id = ? AND lease_expires_at > ?
197
+ `).all(input.goalId, now.toISOString()).map(row => [String(row.work_item_id), row]));
198
+ const candidate = [...goal.workItems]
199
+ .filter(item => (item.role === 'agent'
200
+ && ['open', 'claimed', 'running'].includes(item.status)
201
+ && item.dependsOn.every(id => completed.has(id))
202
+ && !claims.has(item.id)
203
+ && !(item.excludedAgentIds ?? []).includes(input.agentId)
204
+ && ((item.roleScopes ?? []).length === 0
205
+ || (item.roleScopes ?? []).some(scope => roleScopes.includes(scope)))))
206
+ .sort(compareGoalWorkItems)[0];
207
+ if (!candidate)
208
+ return undefined;
209
+ const claimedAt = now.toISOString();
210
+ const claim = {
211
+ goalId: input.goalId,
212
+ workItemId: candidate.id,
213
+ agentId: input.agentId,
214
+ claimToken: `goal-claim:${createId()}`,
215
+ roleScopes,
216
+ claimedAt,
217
+ leaseExpiresAt: new Date(now.getTime() + Math.max(1_000, input.leaseMs ?? 15 * 60_000)).toISOString(),
218
+ updatedAt: claimedAt,
219
+ };
220
+ this.driver.prepare(`
221
+ INSERT INTO goal_work_claims (
222
+ goal_id, work_item_id, agent_id, claim_token, role_scopes_json,
223
+ claimed_at, lease_expires_at, updated_at
224
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
225
+ ON CONFLICT(goal_id, work_item_id) DO UPDATE SET
226
+ agent_id = excluded.agent_id,
227
+ claim_token = excluded.claim_token,
228
+ role_scopes_json = excluded.role_scopes_json,
229
+ claimed_at = excluded.claimed_at,
230
+ lease_expires_at = excluded.lease_expires_at,
231
+ updated_at = excluded.updated_at
232
+ WHERE goal_work_claims.lease_expires_at <= ?
233
+ `).run(claim.goalId, claim.workItemId, claim.agentId, claim.claimToken, JSON.stringify(claim.roleScopes), claim.claimedAt, claim.leaseExpiresAt, claim.updatedAt, claimedAt);
234
+ this.insertEvent(input.goalId, 'work_claimed', claimedAt, undefined, {
235
+ workItemId: candidate.id,
236
+ agentId: input.agentId,
237
+ leaseExpiresAt: claim.leaseExpiresAt,
238
+ });
239
+ return claim;
240
+ });
241
+ }
242
+ renewWorkClaim(claimToken, leaseMs = 15 * 60_000, now = new Date()) {
243
+ const row = this.driver.prepare('SELECT * FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(claimToken, now.toISOString());
244
+ if (!row)
245
+ return undefined;
246
+ const leaseExpiresAt = new Date(now.getTime() + Math.max(1_000, leaseMs)).toISOString();
247
+ this.driver.prepare(`
248
+ UPDATE goal_work_claims SET lease_expires_at = ?, updated_at = ? WHERE claim_token = ?
249
+ `).run(leaseExpiresAt, now.toISOString(), claimToken);
250
+ return mapWorkClaim({ ...row, lease_expires_at: leaseExpiresAt, updated_at: now.toISOString() });
251
+ }
252
+ markClaimRunning(claimToken, now = new Date()) {
253
+ const row = this.driver.prepare('SELECT goal_id, work_item_id FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(claimToken, now.toISOString());
254
+ if (!row)
255
+ return false;
256
+ let changed = false;
257
+ this.updateSnapshot(String(row.goal_id), raw => {
258
+ const goal = normalizeGoal(raw, now.toISOString());
259
+ if (!goal)
260
+ return undefined;
261
+ const workItems = goal.workItems.map(item => {
262
+ if (item.id !== String(row.work_item_id))
263
+ return item;
264
+ changed = true;
265
+ return { ...item, status: 'running', updatedAt: now.toISOString() };
266
+ });
267
+ return changed ? {
268
+ ...goal,
269
+ workItems,
270
+ updatedAt: now.toISOString(),
271
+ revision: goal.revision + 1,
272
+ } : goal;
273
+ });
274
+ return changed;
275
+ }
276
+ releaseWorkClaim(claimToken, reason = 'released', now = new Date()) {
277
+ const row = this.driver.prepare('SELECT goal_id, work_item_id, agent_id FROM goal_work_claims WHERE claim_token = ?').get(claimToken);
278
+ if (!row)
279
+ return false;
280
+ const result = this.driver.prepare('DELETE FROM goal_work_claims WHERE claim_token = ?').run(claimToken);
281
+ if (result.changes === 1) {
282
+ this.updateSnapshot(String(row.goal_id), raw => {
283
+ const goal = normalizeGoal(raw, now.toISOString());
284
+ if (!goal)
285
+ return undefined;
286
+ let changed = false;
287
+ const workItems = goal.workItems.map(item => {
288
+ if (item.id !== String(row.work_item_id))
289
+ return item;
290
+ if (item.status !== 'running' && item.status !== 'claimed')
291
+ return item;
292
+ changed = true;
293
+ return { ...item, status: 'open', updatedAt: now.toISOString() };
294
+ });
295
+ return changed ? {
296
+ ...goal,
297
+ workItems,
298
+ updatedAt: now.toISOString(),
299
+ revision: goal.revision + 1,
300
+ } : goal;
301
+ });
302
+ this.insertEvent(String(row.goal_id), 'work_claim_released', now.toISOString(), undefined, {
303
+ workItemId: String(row.work_item_id),
304
+ agentId: String(row.agent_id),
305
+ reason,
306
+ });
307
+ }
308
+ return result.changes === 1;
309
+ }
310
+ /** Atomically claim a specific work item if it is runnable and unclaimed. */
311
+ claimWorkItem(input) {
312
+ const now = input.now ?? new Date();
313
+ const roleScopes = unique(input.roleScopes ?? []);
314
+ return this.driver.transaction(() => {
315
+ const goal = this.readSnapshot(input.goalId);
316
+ if (!goal || goal.status !== 'active')
317
+ return undefined;
318
+ const completed = new Set(goal.workItems
319
+ .filter(item => item.status === 'done' || item.status === 'cancelled')
320
+ .map(item => item.id));
321
+ const existing = this.driver.prepare(`
322
+ SELECT * FROM goal_work_claims WHERE goal_id = ? AND work_item_id = ? AND lease_expires_at > ?
323
+ `).get(input.goalId, input.workItemId, now.toISOString());
324
+ if (existing) {
325
+ return String(existing.agent_id) === input.agentId ? mapWorkClaim(existing) : undefined;
326
+ }
327
+ const item = goal.workItems.find(candidate => candidate.id === input.workItemId);
328
+ if (!item
329
+ || item.role !== 'agent'
330
+ || !['open', 'claimed', 'running'].includes(item.status)
331
+ || !item.dependsOn.every(id => completed.has(id))
332
+ || (item.excludedAgentIds ?? []).includes(input.agentId)) {
333
+ return undefined;
334
+ }
335
+ // Empty roleScopes = session owner / unrestricted claimant.
336
+ if (roleScopes.length > 0
337
+ && (item.roleScopes ?? []).length > 0
338
+ && !(item.roleScopes ?? []).some(scope => roleScopes.includes(scope))) {
339
+ return undefined;
340
+ }
341
+ const claimedAt = now.toISOString();
342
+ const claim = {
343
+ goalId: input.goalId,
344
+ workItemId: item.id,
345
+ agentId: input.agentId,
346
+ claimToken: `goal-claim:${createId()}`,
347
+ roleScopes,
348
+ claimedAt,
349
+ leaseExpiresAt: new Date(now.getTime() + Math.max(1_000, input.leaseMs ?? 15 * 60_000)).toISOString(),
350
+ updatedAt: claimedAt,
351
+ };
352
+ const result = this.driver.prepare(`
353
+ INSERT INTO goal_work_claims (
354
+ goal_id, work_item_id, agent_id, claim_token, role_scopes_json,
355
+ claimed_at, lease_expires_at, updated_at
356
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
357
+ ON CONFLICT(goal_id, work_item_id) DO UPDATE SET
358
+ agent_id = excluded.agent_id,
359
+ claim_token = excluded.claim_token,
360
+ role_scopes_json = excluded.role_scopes_json,
361
+ claimed_at = excluded.claimed_at,
362
+ lease_expires_at = excluded.lease_expires_at,
363
+ updated_at = excluded.updated_at
364
+ WHERE goal_work_claims.lease_expires_at <= ?
365
+ `).run(claim.goalId, claim.workItemId, claim.agentId, claim.claimToken, JSON.stringify(claim.roleScopes), claim.claimedAt, claim.leaseExpiresAt, claim.updatedAt, claimedAt);
366
+ if (result.changes !== 1)
367
+ return undefined;
368
+ this.insertEvent(input.goalId, 'work_claimed', claimedAt, undefined, {
369
+ workItemId: claim.workItemId,
370
+ agentId: input.agentId,
371
+ leaseExpiresAt: claim.leaseExpiresAt,
372
+ });
373
+ return claim;
374
+ });
375
+ }
376
+ completeWorkClaim(claimToken, evidenceRefs, now = new Date()) {
377
+ const refs = unique(evidenceRefs);
378
+ if (refs.length === 0)
379
+ return false;
380
+ const row = this.driver.prepare('SELECT goal_id, work_item_id, agent_id FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(claimToken, now.toISOString());
381
+ if (!row)
382
+ return false;
383
+ let changed = false;
384
+ this.updateSnapshot(String(row.goal_id), raw => {
385
+ const goal = normalizeGoal(raw, now.toISOString());
386
+ if (!goal)
387
+ return undefined;
388
+ const workItems = goal.workItems.map(item => {
389
+ if (item.id !== String(row.work_item_id) || item.status === 'done')
390
+ return item;
391
+ changed = true;
392
+ return {
393
+ ...item,
394
+ status: 'done',
395
+ evidenceRefs: unique([...item.evidenceRefs, ...refs]),
396
+ updatedAt: now.toISOString(),
397
+ };
398
+ });
399
+ return changed ? {
400
+ ...goal,
401
+ workItems,
402
+ delivery: {
403
+ ...goal.delivery,
404
+ completedWorkItems: goal.delivery.completedWorkItems + 1,
405
+ },
406
+ planRevision: goal.planRevision + 1,
407
+ updatedAt: now.toISOString(),
408
+ revision: goal.revision + 1,
409
+ } : goal;
410
+ });
411
+ if (!changed)
412
+ return false;
413
+ this.driver.prepare('DELETE FROM goal_work_claims WHERE claim_token = ?').run(claimToken);
414
+ this.insertEvent(String(row.goal_id), 'work_claim_completed', now.toISOString(), undefined, {
415
+ workItemId: String(row.work_item_id),
416
+ agentId: String(row.agent_id),
417
+ evidenceRefs: refs,
418
+ });
419
+ return true;
420
+ }
421
+ handoffWork(input) {
422
+ const now = input.now ?? new Date();
423
+ return this.driver.transaction(() => {
424
+ const row = this.driver.prepare('SELECT * FROM goal_work_claims WHERE claim_token = ? AND lease_expires_at > ?').get(input.claimToken, now.toISOString());
425
+ if (!row)
426
+ return undefined;
427
+ const evidenceRefs = unique(input.evidenceRefs ?? []);
428
+ const goal = this.readSnapshot(String(row.goal_id));
429
+ const workItem = goal?.workItems.find(item => item.id === String(row.work_item_id));
430
+ const toAgentRoleScopes = unique(input.toAgentRoleScopes ?? []);
431
+ if (input.toAgentId
432
+ && (workItem?.excludedAgentIds?.includes(input.toAgentId)
433
+ || ((workItem?.roleScopes?.length ?? 0) > 0
434
+ && !workItem.roleScopes.some(scope => toAgentRoleScopes.includes(scope))))) {
435
+ return undefined;
436
+ }
437
+ const inserted = this.driver.prepare(`
438
+ INSERT INTO goal_handoffs (
439
+ goal_id, work_item_id, from_agent_id, to_agent_id, reason, evidence_refs_json, created_at
440
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
441
+ `).run(row.goal_id, row.work_item_id, row.agent_id, input.toAgentId ?? null, input.reason.trim() || 'handoff', JSON.stringify(evidenceRefs), now.toISOString());
442
+ this.driver.prepare('DELETE FROM goal_work_claims WHERE claim_token = ?').run(input.claimToken);
443
+ let claim;
444
+ if (input.toAgentId) {
445
+ claim = {
446
+ goalId: String(row.goal_id),
447
+ workItemId: String(row.work_item_id),
448
+ agentId: input.toAgentId,
449
+ claimToken: `goal-claim:${createId()}`,
450
+ roleScopes: toAgentRoleScopes,
451
+ claimedAt: now.toISOString(),
452
+ leaseExpiresAt: new Date(now.getTime() + Math.max(1_000, input.leaseMs ?? 15 * 60_000)).toISOString(),
453
+ updatedAt: now.toISOString(),
454
+ };
455
+ this.driver.prepare(`
456
+ INSERT INTO goal_work_claims (
457
+ goal_id, work_item_id, agent_id, claim_token, role_scopes_json,
458
+ claimed_at, lease_expires_at, updated_at
459
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
460
+ `).run(claim.goalId, claim.workItemId, claim.agentId, claim.claimToken, JSON.stringify(claim.roleScopes), claim.claimedAt, claim.leaseExpiresAt, claim.updatedAt);
461
+ }
462
+ const receipt = {
463
+ id: Number(inserted.lastInsertRowid),
464
+ goalId: String(row.goal_id),
465
+ workItemId: String(row.work_item_id),
466
+ fromAgentId: String(row.agent_id),
467
+ ...(input.toAgentId ? { toAgentId: input.toAgentId } : {}),
468
+ reason: input.reason.trim() || 'handoff',
469
+ evidenceRefs,
470
+ at: now.toISOString(),
471
+ };
472
+ this.insertEvent(receipt.goalId, 'work_handed_off', receipt.at, undefined, {
473
+ workItemId: receipt.workItemId,
474
+ fromAgentId: receipt.fromAgentId,
475
+ ...(receipt.toAgentId ? { toAgentId: receipt.toAgentId } : {}),
476
+ });
477
+ return { receipt, ...(claim ? { claim } : {}) };
478
+ });
479
+ }
480
+ listWorkClaims(goalId, options = {}) {
481
+ const rows = this.driver.prepare(`
482
+ SELECT * FROM goal_work_claims WHERE goal_id = ?
483
+ ${options.includeExpired ? '' : 'AND lease_expires_at > ?'}
484
+ ORDER BY claimed_at ASC
485
+ `).all(...(options.includeExpired ? [goalId] : [goalId, (options.now ?? new Date()).toISOString()]));
486
+ return rows.map(mapWorkClaim);
487
+ }
488
+ listHandoffs(goalId, limit = 100) {
489
+ return this.driver.prepare(`
490
+ SELECT * FROM goal_handoffs WHERE goal_id = ? ORDER BY handoff_id DESC LIMIT ?
491
+ `).all(goalId, Math.max(1, Math.min(limit, 500))).reverse().map(row => ({
492
+ id: Number(row.handoff_id),
493
+ goalId: String(row.goal_id),
494
+ workItemId: String(row.work_item_id),
495
+ fromAgentId: String(row.from_agent_id),
496
+ ...(typeof row.to_agent_id === 'string' ? { toAgentId: row.to_agent_id } : {}),
497
+ reason: String(row.reason),
498
+ evidenceRefs: parseStringArray(row.evidence_refs_json),
499
+ at: String(row.created_at),
500
+ }));
501
+ }
502
+ goalIdForSession(sessionId) {
503
+ const row = this.driver.prepare('SELECT goal_id FROM goal_sessions WHERE session_id = ?').get(sessionId);
504
+ return typeof row?.goal_id === 'string' ? row.goal_id : undefined;
505
+ }
506
+ currentGoalId() {
507
+ const row = this.driver.prepare(`
508
+ SELECT goal_id FROM goals
509
+ WHERE archived_at IS NULL AND status NOT IN ('complete', 'cancelled')
510
+ ORDER BY updated_at DESC
511
+ LIMIT 1
512
+ `).get();
513
+ return typeof row?.goal_id === 'string' ? row.goal_id : undefined;
514
+ }
515
+ readSnapshot(goalId) {
516
+ const row = this.driver.prepare('SELECT snapshot_json FROM goals WHERE goal_id = ? AND archived_at IS NULL').get(goalId);
517
+ if (typeof row?.snapshot_json !== 'string')
518
+ return null;
519
+ return normalizeGoal(row.snapshot_json, new Date().toISOString());
520
+ }
521
+ writeSnapshot(goalId, goal, eventType = 'snapshot_updated') {
522
+ const now = goal?.updatedAt ?? new Date().toISOString();
523
+ this.driver.transaction(() => {
524
+ if (!goal) {
525
+ this.driver.prepare('UPDATE goals SET archived_at = ?, updated_at = ? WHERE goal_id = ?').run(now, now, goalId);
526
+ this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
527
+ this.insertEvent(goalId, 'archived', now);
528
+ return;
529
+ }
530
+ this.driver.prepare(`
531
+ INSERT INTO goals (
532
+ goal_id, objective, status, revision, snapshot_json, created_at, updated_at, archived_at
533
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)
534
+ ON CONFLICT(goal_id) DO UPDATE SET
535
+ objective = excluded.objective,
536
+ status = excluded.status,
537
+ revision = excluded.revision,
538
+ snapshot_json = excluded.snapshot_json,
539
+ updated_at = excluded.updated_at,
540
+ archived_at = NULL
541
+ `).run(goalId, goal.objective, goal.status, goal.revision, JSON.stringify(goal), goal.createdAt, goal.updatedAt);
542
+ this.insertEvent(goalId, eventType, now, goal.revision, {
543
+ status: goal.status,
544
+ objective: goal.objective,
545
+ });
546
+ if (goal.status === 'complete' || goal.status === 'cancelled' || goal.status === 'blocked') {
547
+ this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
548
+ }
549
+ });
550
+ }
551
+ updateSnapshot(goalId, mutation) {
552
+ return this.driver.transaction(() => {
553
+ const current = this.driver.prepare('SELECT snapshot_json FROM goals WHERE goal_id = ? AND archived_at IS NULL').get(goalId);
554
+ const next = mutation(current?.snapshot_json);
555
+ if (!next) {
556
+ const now = new Date().toISOString();
557
+ this.driver.prepare('UPDATE goals SET archived_at = ?, updated_at = ? WHERE goal_id = ?').run(now, now, goalId);
558
+ this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
559
+ this.insertEvent(goalId, 'archived', now);
560
+ return undefined;
561
+ }
562
+ this.driver.prepare(`
563
+ INSERT INTO goals (
564
+ goal_id, objective, status, revision, snapshot_json, created_at, updated_at, archived_at
565
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, NULL)
566
+ ON CONFLICT(goal_id) DO UPDATE SET
567
+ objective = excluded.objective,
568
+ status = excluded.status,
569
+ revision = excluded.revision,
570
+ snapshot_json = excluded.snapshot_json,
571
+ updated_at = excluded.updated_at,
572
+ archived_at = NULL
573
+ `).run(goalId, next.objective, next.status, next.revision, JSON.stringify(next), next.createdAt, next.updatedAt);
574
+ this.insertEvent(goalId, 'snapshot_updated', next.updatedAt, next.revision, {
575
+ status: next.status,
576
+ });
577
+ if (next.status === 'complete' || next.status === 'cancelled' || next.status === 'blocked') {
578
+ this.driver.prepare('DELETE FROM goal_work_claims WHERE goal_id = ?').run(goalId);
579
+ }
580
+ return next;
581
+ });
582
+ }
583
+ attachSession(sessionId, goalId, at = new Date().toISOString()) {
584
+ this.driver.prepare(`
585
+ INSERT INTO goal_sessions (session_id, goal_id, attached_at)
586
+ VALUES (?, ?, ?)
587
+ ON CONFLICT(session_id) DO UPDATE SET
588
+ goal_id = excluded.goal_id,
589
+ attached_at = excluded.attached_at
590
+ `).run(sessionId, goalId, at);
591
+ this.insertEvent(goalId, 'session_attached', at, undefined, { sessionId });
592
+ }
593
+ detachSession(sessionId, at = new Date().toISOString()) {
594
+ const goalId = this.goalIdForSession(sessionId);
595
+ this.driver.prepare('DELETE FROM goal_sessions WHERE session_id = ?').run(sessionId);
596
+ if (goalId)
597
+ this.insertEvent(goalId, 'session_detached', at, undefined, { sessionId });
598
+ }
599
+ insertEvent(goalId, type, at, revision, payload) {
600
+ this.driver.prepare(`
601
+ INSERT INTO goal_events (goal_id, event_type, revision, payload_json, created_at)
602
+ VALUES (?, ?, ?, ?, ?)
603
+ `).run(goalId, type, revision ?? null, payload ? JSON.stringify(payload) : null, at);
604
+ }
605
+ }
606
+ class ProjectGoalSessionPort {
607
+ store;
608
+ sessionId;
609
+ goalId;
610
+ constructor(store, sessionId, goalId) {
611
+ this.store = store;
612
+ this.sessionId = sessionId;
613
+ this.goalId = goalId;
614
+ }
615
+ readGoalMetadata() {
616
+ return this.goalId ? this.store.readSnapshot(this.goalId) : undefined;
617
+ }
618
+ async writeGoalMetadata(value) {
619
+ if (!this.goalId && value)
620
+ this.goalId = this.store.createGoalId();
621
+ if (!this.goalId)
622
+ return;
623
+ this.store.writeSnapshot(this.goalId, value);
624
+ if (value)
625
+ this.store.attachSession(this.sessionId, this.goalId);
626
+ }
627
+ async updateGoalMetadata(mutation) {
628
+ if (!this.goalId) {
629
+ const next = mutation(undefined);
630
+ if (!next)
631
+ return undefined;
632
+ this.goalId = this.store.createGoalId();
633
+ this.store.writeSnapshot(this.goalId, next, 'created');
634
+ this.store.attachSession(this.sessionId, this.goalId, next.createdAt);
635
+ return next;
636
+ }
637
+ const next = this.store.updateSnapshot(this.goalId, mutation);
638
+ if (next)
639
+ this.store.attachSession(this.sessionId, this.goalId, next.updatedAt);
640
+ return next;
641
+ }
642
+ }
643
+ function initializeSchema(driver) {
644
+ driver.exec(`
645
+ PRAGMA journal_mode = WAL;
646
+ PRAGMA foreign_keys = ON;
647
+
648
+ CREATE TABLE IF NOT EXISTS goals (
649
+ goal_id TEXT PRIMARY KEY,
650
+ objective TEXT NOT NULL,
651
+ status TEXT NOT NULL,
652
+ revision INTEGER NOT NULL,
653
+ snapshot_json TEXT NOT NULL,
654
+ created_at TEXT NOT NULL,
655
+ updated_at TEXT NOT NULL,
656
+ archived_at TEXT
657
+ );
658
+
659
+ CREATE INDEX IF NOT EXISTS idx_goals_current
660
+ ON goals(archived_at, status, updated_at DESC);
661
+
662
+ CREATE TABLE IF NOT EXISTS goal_sessions (
663
+ session_id TEXT PRIMARY KEY,
664
+ goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
665
+ attached_at TEXT NOT NULL
666
+ );
667
+
668
+ CREATE INDEX IF NOT EXISTS idx_goal_sessions_goal
669
+ ON goal_sessions(goal_id, attached_at);
670
+
671
+ CREATE TABLE IF NOT EXISTS goal_events (
672
+ event_id INTEGER PRIMARY KEY AUTOINCREMENT,
673
+ goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
674
+ event_type TEXT NOT NULL,
675
+ revision INTEGER,
676
+ payload_json TEXT,
677
+ created_at TEXT NOT NULL
678
+ );
679
+
680
+ CREATE INDEX IF NOT EXISTS idx_goal_events_goal
681
+ ON goal_events(goal_id, event_id);
682
+
683
+ CREATE TABLE IF NOT EXISTS goal_continuation (
684
+ goal_id TEXT PRIMARY KEY REFERENCES goals(goal_id) ON DELETE CASCADE,
685
+ mode TEXT NOT NULL,
686
+ execution_profile_json TEXT,
687
+ min_interval_seconds INTEGER NOT NULL,
688
+ max_interval_seconds INTEGER NOT NULL,
689
+ current_interval_seconds INTEGER NOT NULL,
690
+ next_wake_at TEXT,
691
+ last_wake_at TEXT,
692
+ last_outcome TEXT,
693
+ consecutive_no_change INTEGER NOT NULL DEFAULT 0,
694
+ lease_owner TEXT,
695
+ lease_expires_at TEXT,
696
+ last_error TEXT,
697
+ updated_at TEXT NOT NULL
698
+ );
699
+
700
+ CREATE INDEX IF NOT EXISTS idx_goal_continuation_wake
701
+ ON goal_continuation(mode, next_wake_at);
702
+
703
+ CREATE TABLE IF NOT EXISTS goal_work_claims (
704
+ goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
705
+ work_item_id TEXT NOT NULL,
706
+ agent_id TEXT NOT NULL,
707
+ claim_token TEXT NOT NULL UNIQUE,
708
+ role_scopes_json TEXT NOT NULL,
709
+ claimed_at TEXT NOT NULL,
710
+ lease_expires_at TEXT NOT NULL,
711
+ updated_at TEXT NOT NULL,
712
+ PRIMARY KEY(goal_id, work_item_id)
713
+ );
714
+
715
+ CREATE INDEX IF NOT EXISTS idx_goal_work_claims_lease
716
+ ON goal_work_claims(goal_id, lease_expires_at);
717
+
718
+ CREATE TABLE IF NOT EXISTS goal_handoffs (
719
+ handoff_id INTEGER PRIMARY KEY AUTOINCREMENT,
720
+ goal_id TEXT NOT NULL REFERENCES goals(goal_id) ON DELETE CASCADE,
721
+ work_item_id TEXT NOT NULL,
722
+ from_agent_id TEXT NOT NULL,
723
+ to_agent_id TEXT,
724
+ reason TEXT NOT NULL,
725
+ evidence_refs_json TEXT NOT NULL,
726
+ created_at TEXT NOT NULL
727
+ );
728
+
729
+ CREATE INDEX IF NOT EXISTS idx_goal_handoffs_goal
730
+ ON goal_handoffs(goal_id, handoff_id);
731
+ `);
732
+ }
733
+ function parseObject(value) {
734
+ try {
735
+ const parsed = JSON.parse(value);
736
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
737
+ ? parsed
738
+ : {};
739
+ }
740
+ catch {
741
+ return {};
742
+ }
743
+ }
744
+ function mapContinuation(row) {
745
+ const parsedProfile = typeof row.execution_profile_json === 'string'
746
+ ? parseObject(row.execution_profile_json)
747
+ : undefined;
748
+ const executionProfile = parsedProfile
749
+ && (parsedProfile.kind === 'config' || parsedProfile.kind === 'agent')
750
+ && typeof parsedProfile.name === 'string'
751
+ ? { kind: parsedProfile.kind, name: parsedProfile.name }
752
+ : undefined;
753
+ return {
754
+ goalId: String(row.goal_id),
755
+ mode: String(row.mode),
756
+ ...(executionProfile ? { executionProfile } : {}),
757
+ minIntervalSeconds: Number(row.min_interval_seconds),
758
+ maxIntervalSeconds: Number(row.max_interval_seconds),
759
+ currentIntervalSeconds: Number(row.current_interval_seconds),
760
+ ...(typeof row.next_wake_at === 'string' ? { nextWakeAt: row.next_wake_at } : {}),
761
+ ...(typeof row.last_wake_at === 'string' ? { lastWakeAt: row.last_wake_at } : {}),
762
+ ...(typeof row.last_outcome === 'string' ? { lastOutcome: row.last_outcome } : {}),
763
+ consecutiveNoChange: Number(row.consecutive_no_change ?? 0),
764
+ ...(typeof row.lease_owner === 'string' ? { leaseOwner: row.lease_owner } : {}),
765
+ ...(typeof row.lease_expires_at === 'string' ? { leaseExpiresAt: row.lease_expires_at } : {}),
766
+ ...(typeof row.last_error === 'string' ? { lastError: row.last_error } : {}),
767
+ ...(typeof row.session_id === 'string' ? { sessionId: row.session_id } : {}),
768
+ };
769
+ }
770
+ function clampSeconds(value, min, max) {
771
+ if (!Number.isFinite(value))
772
+ return min;
773
+ return Math.max(min, Math.min(max, Math.floor(value)));
774
+ }
775
+ function isFuture(value, now) {
776
+ return Boolean(value && Date.parse(value) > now.getTime());
777
+ }
778
+ function mapWorkClaim(row) {
779
+ return {
780
+ goalId: String(row.goal_id),
781
+ workItemId: String(row.work_item_id),
782
+ agentId: String(row.agent_id),
783
+ claimToken: String(row.claim_token),
784
+ roleScopes: parseStringArray(row.role_scopes_json),
785
+ claimedAt: String(row.claimed_at),
786
+ leaseExpiresAt: String(row.lease_expires_at),
787
+ updatedAt: String(row.updated_at),
788
+ };
789
+ }
790
+ function parseStringArray(value) {
791
+ if (typeof value !== 'string')
792
+ return [];
793
+ try {
794
+ const parsed = JSON.parse(value);
795
+ return Array.isArray(parsed) ? parsed.filter(item => typeof item === 'string') : [];
796
+ }
797
+ catch {
798
+ return [];
799
+ }
800
+ }
801
+ function unique(values) {
802
+ return [...new Set(values.map(value => value.trim()).filter(Boolean))];
803
+ }
804
+ function compareGoalWorkItems(a, b) {
805
+ const rank = { P0: 0, P1: 1, P2: 2 };
806
+ return rank[a.priority] - rank[b.priority] || a.createdAt.localeCompare(b.createdAt);
807
+ }
808
+ //# sourceMappingURL=projectGoalStore.js.map