stageflow 0.36.0 → 0.40.0

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 (288) hide show
  1. package/README.md +61 -26
  2. package/dist/agent/claudeAdapter.js +21 -1
  3. package/dist/agent/claudeTools.d.ts +13 -0
  4. package/dist/agent/claudeTools.js +98 -0
  5. package/dist/agent/piAdapter.d.ts +9 -1
  6. package/dist/agent/piAdapter.js +59 -18
  7. package/dist/agent/piIsolatedMcp.js +14 -2
  8. package/dist/agent/piIsolatedMcpProbe.d.ts +5 -1
  9. package/dist/agent/piIsolatedMcpProbe.js +16 -30
  10. package/dist/agent/port.d.ts +5 -0
  11. package/dist/agent/sandboxPiTools.d.ts +99 -0
  12. package/dist/agent/sandboxPiTools.js +109 -0
  13. package/dist/agents/agentId.d.ts +8 -0
  14. package/dist/agents/agentId.js +27 -0
  15. package/dist/agents/approvalBroker.d.ts +16 -0
  16. package/dist/agents/approvalBroker.js +45 -0
  17. package/dist/agents/authorChat.d.ts +19 -0
  18. package/dist/agents/authorChat.js +63 -0
  19. package/dist/agents/chatTurn.d.ts +23 -0
  20. package/dist/agents/chatTurn.js +77 -0
  21. package/dist/agents/communications/agentmail.d.ts +19 -0
  22. package/dist/agents/communications/agentmail.js +46 -0
  23. package/dist/agents/communications/agentmailListener.d.ts +59 -0
  24. package/dist/agents/communications/agentmailListener.js +183 -0
  25. package/dist/agents/communications/allowlist.d.ts +10 -0
  26. package/dist/agents/communications/allowlist.js +36 -0
  27. package/dist/agents/communications/channelConnections.d.ts +3 -0
  28. package/dist/agents/communications/channelConnections.js +51 -0
  29. package/dist/agents/communications/delivery.d.ts +53 -0
  30. package/dist/agents/communications/delivery.js +142 -0
  31. package/dist/agents/communications/email.d.ts +26 -0
  32. package/dist/agents/communications/email.js +20 -0
  33. package/dist/agents/communications/identity.d.ts +13 -0
  34. package/dist/agents/communications/identity.js +18 -0
  35. package/dist/agents/communications/imessageListener.d.ts +41 -0
  36. package/dist/agents/communications/imessageListener.js +92 -0
  37. package/dist/agents/communications/inbound.d.ts +63 -0
  38. package/dist/agents/communications/inbound.js +143 -0
  39. package/dist/agents/communications/listener.d.ts +36 -0
  40. package/dist/agents/communications/listener.js +38 -0
  41. package/dist/agents/communications/photon.d.ts +19 -0
  42. package/dist/agents/communications/photon.js +38 -0
  43. package/dist/agents/communications/port.d.ts +27 -0
  44. package/dist/agents/communications/port.js +6 -0
  45. package/dist/agents/communications/slack.d.ts +28 -0
  46. package/dist/agents/communications/slack.js +65 -0
  47. package/dist/agents/communications/supervisor.d.ts +36 -0
  48. package/dist/agents/communications/supervisor.js +96 -0
  49. package/dist/agents/communications/telegram.d.ts +19 -0
  50. package/dist/agents/communications/telegram.js +52 -0
  51. package/dist/agents/definitionSummary.d.ts +13 -0
  52. package/dist/agents/definitionSummary.js +91 -0
  53. package/dist/agents/delegate.d.ts +3 -0
  54. package/dist/agents/delegate.js +8 -0
  55. package/dist/agents/errors.d.ts +10 -0
  56. package/dist/agents/errors.js +32 -0
  57. package/dist/agents/guardrails.d.ts +24 -0
  58. package/dist/agents/guardrails.js +39 -0
  59. package/dist/agents/historyFit.d.ts +10 -0
  60. package/dist/agents/historyFit.js +26 -0
  61. package/dist/agents/hostContext.d.ts +54 -0
  62. package/dist/agents/hostContext.js +156 -0
  63. package/dist/agents/ledgers.d.ts +29 -0
  64. package/dist/agents/ledgers.js +31 -0
  65. package/dist/agents/lineDiff.d.ts +12 -0
  66. package/dist/agents/lineDiff.js +46 -0
  67. package/dist/agents/ndjson.d.ts +9 -0
  68. package/dist/agents/ndjson.js +47 -0
  69. package/dist/agents/otlpExport.d.ts +9 -0
  70. package/dist/agents/otlpExport.js +58 -0
  71. package/dist/agents/otlpSink.d.ts +7 -0
  72. package/dist/agents/otlpSink.js +23 -0
  73. package/dist/agents/policy.d.ts +31 -0
  74. package/dist/agents/policy.js +84 -0
  75. package/dist/agents/projectRoot.d.ts +5 -0
  76. package/dist/agents/projectRoot.js +15 -0
  77. package/dist/agents/runtime/piRuntime.d.ts +52 -0
  78. package/dist/agents/runtime/piRuntime.js +474 -0
  79. package/dist/agents/runtime/types.d.ts +77 -0
  80. package/dist/agents/runtime/types.js +26 -0
  81. package/dist/agents/sandbox.d.ts +17 -0
  82. package/dist/agents/sandbox.js +10 -0
  83. package/dist/agents/scheduleSource.d.ts +32 -0
  84. package/dist/agents/scheduleSource.js +100 -0
  85. package/dist/agents/service.d.ts +298 -0
  86. package/dist/agents/service.js +1105 -0
  87. package/dist/agents/sessions.d.ts +38 -0
  88. package/dist/agents/sessions.js +321 -0
  89. package/dist/agents/store.d.ts +277 -0
  90. package/dist/agents/store.js +643 -0
  91. package/dist/agents/trace.d.ts +27 -0
  92. package/dist/agents/trace.js +29 -0
  93. package/dist/agents/turnLifecycle.d.ts +72 -0
  94. package/dist/agents/turnLifecycle.js +157 -0
  95. package/dist/agents/webChat/publicChat.d.ts +36 -0
  96. package/dist/agents/webChat/publicChat.js +290 -0
  97. package/dist/agents/webChat/rateLimit.d.ts +15 -0
  98. package/dist/agents/webChat/rateLimit.js +33 -0
  99. package/dist/agents/webChat/slug.d.ts +2 -0
  100. package/dist/agents/webChat/slug.js +9 -0
  101. package/dist/channels/providers/agentmail.js +14 -1
  102. package/dist/channels/providers/photonImessage.js +36 -0
  103. package/dist/channels/providers/slack.js +2 -1
  104. package/dist/channels/providers/telegram.js +2 -1
  105. package/dist/channels/types.d.ts +27 -0
  106. package/dist/cli/agentsCommand.d.ts +14 -0
  107. package/dist/cli/agentsCommand.js +291 -0
  108. package/dist/cli.js +9 -0
  109. package/dist/config/browseCatalog.js +4 -11
  110. package/dist/config/draftPackage.d.ts +1 -0
  111. package/dist/config/draftPackage.js +4 -0
  112. package/dist/config/hostConfig.js +9 -0
  113. package/dist/config/loadAgent.d.ts +266 -0
  114. package/dist/config/loadAgent.js +374 -0
  115. package/dist/config/loadPipeline.js +5 -2
  116. package/dist/config/loadTask.js +34 -1
  117. package/dist/config/mcpHostCatalog.d.ts +50 -0
  118. package/dist/config/mcpHostCatalog.js +405 -0
  119. package/dist/config/mcpOAuth.d.ts +37 -0
  120. package/dist/config/mcpOAuth.js +263 -0
  121. package/dist/config/mcpRedaction.d.ts +42 -0
  122. package/dist/config/mcpRedaction.js +314 -0
  123. package/dist/config/mcpToolCache.d.ts +20 -0
  124. package/dist/config/mcpToolCache.js +43 -0
  125. package/dist/config/mergePipelineIncludes.d.ts +2 -0
  126. package/dist/config/mergePipelineIncludes.js +25 -0
  127. package/dist/config/resolveStageMcpServers.d.ts +35 -3
  128. package/dist/config/resolveStageMcpServers.js +124 -9
  129. package/dist/config/validateCatalog.d.ts +1 -1
  130. package/dist/config/validateCatalog.js +22 -1
  131. package/dist/mcp/agentTools.d.ts +8 -0
  132. package/dist/mcp/agentTools.js +132 -0
  133. package/dist/mcp/catalogTools.js +17 -10
  134. package/dist/mcp/deps.d.ts +6 -0
  135. package/dist/mcp/pipelineAuthorSupport.d.ts +19 -0
  136. package/dist/mcp/pipelineAuthorSupport.js +10 -0
  137. package/dist/mcp/pipelineAuthorTools.d.ts +8 -0
  138. package/dist/mcp/pipelineAuthorTools.js +109 -0
  139. package/dist/mcp/server.js +5 -0
  140. package/dist/mcp/tools.js +4 -0
  141. package/dist/mcp/triggerTools.js +32 -0
  142. package/dist/memory/access.d.ts +36 -0
  143. package/dist/memory/access.js +116 -0
  144. package/dist/memory/agentIssues.d.ts +3 -0
  145. package/dist/memory/agentIssues.js +23 -0
  146. package/dist/memory/bashDeny.d.ts +6 -0
  147. package/dist/memory/bashDeny.js +35 -0
  148. package/dist/memory/host.d.ts +51 -0
  149. package/dist/memory/host.js +394 -0
  150. package/dist/memory/ids.d.ts +15 -0
  151. package/dist/memory/ids.js +31 -0
  152. package/dist/memory/worker.d.ts +1 -0
  153. package/dist/memory/worker.js +84 -0
  154. package/dist/models/catalog.d.ts +113 -0
  155. package/dist/models/catalog.js +238 -0
  156. package/dist/models/response.d.ts +29 -0
  157. package/dist/models/response.js +25 -0
  158. package/dist/operatorAgent/agentAuthorContext.d.ts +17 -0
  159. package/dist/operatorAgent/agentAuthorContext.js +32 -0
  160. package/dist/operatorAgent/fakeHost.js +2 -0
  161. package/dist/operatorAgent/host.js +6 -3
  162. package/dist/operatorAgent/index.d.ts +4 -0
  163. package/dist/operatorAgent/index.js +4 -0
  164. package/dist/operatorAgent/piModel.js +2 -0
  165. package/dist/operatorAgent/profiles/agentAuthor.d.ts +11 -0
  166. package/dist/operatorAgent/profiles/agentAuthor.js +192 -0
  167. package/dist/operatorAgent/profiles/agentAuthorPlaybook.d.ts +1 -0
  168. package/dist/operatorAgent/profiles/agentAuthorPlaybook.js +29 -0
  169. package/dist/operatorAgent/profiles/workshopAuthor.js +2 -2
  170. package/dist/operatorAgent/profiles/workshopAuthorTools.js +3 -3
  171. package/dist/operatorAgent/proposals.d.ts +1 -0
  172. package/dist/operatorAgent/proposals.js +23 -2
  173. package/dist/package-meta.d.ts +1 -1
  174. package/dist/package-meta.js +1 -1
  175. package/dist/preflight/pipelinePreflight.js +1 -0
  176. package/dist/runstore/createStore.d.ts +2 -0
  177. package/dist/runstore/createStore.js +8 -0
  178. package/dist/runstore/overviewSummary.d.ts +56 -0
  179. package/dist/runstore/overviewSummary.js +134 -0
  180. package/dist/runstore/port.d.ts +3 -0
  181. package/dist/runstore/sqlite/SqliteRunStore.d.ts +2 -0
  182. package/dist/runstore/sqlite/SqliteRunStore.js +18 -0
  183. package/dist/runstore/sqlite/migrations/013-agents.d.ts +12 -0
  184. package/dist/runstore/sqlite/migrations/013-agents.js +156 -0
  185. package/dist/runstore/sqlite/migrations/014-agent-deliveries.d.ts +11 -0
  186. package/dist/runstore/sqlite/migrations/014-agent-deliveries.js +32 -0
  187. package/dist/runstore/sqlite/migrations/index.js +4 -0
  188. package/dist/runtime/gitCheckoutCapability.d.ts +1 -0
  189. package/dist/runtime/gitCheckoutCapability.js +1 -1
  190. package/dist/runtime/gitWorktreeCheckout.d.ts +22 -0
  191. package/dist/runtime/gitWorktreeCheckout.js +120 -0
  192. package/dist/runtime/sandboxContainer.d.ts +62 -0
  193. package/dist/runtime/sandboxContainer.js +151 -0
  194. package/dist/runtime/sandboxDesk.d.ts +28 -0
  195. package/dist/runtime/sandboxDesk.js +242 -0
  196. package/dist/runtime/sandboxFiles.d.ts +20 -0
  197. package/dist/runtime/sandboxFiles.js +150 -0
  198. package/dist/runtime/stageAttemptBootstrap.js +15 -1
  199. package/dist/runtime/stageEnvironment.d.ts +2 -0
  200. package/dist/runtime/stageEnvironment.js +8 -1
  201. package/dist/runtime/stageProcessLauncher.js +23 -0
  202. package/dist/runtime/stageRoots.d.ts +9 -0
  203. package/dist/runtime/stageRoots.js +14 -0
  204. package/dist/runtime/stageRunner.d.ts +5 -0
  205. package/dist/runtime/stageRunner.js +38 -4
  206. package/dist/runtime/stageSandbox.d.ts +16 -0
  207. package/dist/runtime/stageSandbox.js +54 -0
  208. package/dist/runtime/stageSecrets.d.ts +6 -1
  209. package/dist/runtime/stageSecrets.js +34 -1
  210. package/dist/runtime/stageWorker.js +138 -105
  211. package/dist/runtime/workspaceBinding.d.ts +2 -1
  212. package/dist/runtime/workspaceBinding.js +20 -4
  213. package/dist/server/agentAuthorRoutes.d.ts +5 -0
  214. package/dist/server/agentAuthorRoutes.js +64 -0
  215. package/dist/server/agentRoutes.d.ts +5 -0
  216. package/dist/server/agentRoutes.js +323 -0
  217. package/dist/server/bootstrap.d.ts +8 -0
  218. package/dist/server/bootstrap.js +36 -10
  219. package/dist/server/controlToken.js +8 -0
  220. package/dist/server/http.d.ts +8 -1
  221. package/dist/server/http.js +172 -20
  222. package/dist/server/mcpHost.js +1 -0
  223. package/dist/server/memoryRoutes.d.ts +5 -0
  224. package/dist/server/memoryRoutes.js +146 -0
  225. package/dist/server/projectMcpRoutes.d.ts +1 -0
  226. package/dist/server/projectMcpRoutes.js +241 -3
  227. package/dist/toolkit/guard.d.ts +27 -0
  228. package/dist/toolkit/guard.js +98 -0
  229. package/dist/toolkit/index.d.ts +48 -0
  230. package/dist/toolkit/index.js +82 -0
  231. package/dist/toolkit/meta.d.ts +36 -0
  232. package/dist/toolkit/meta.js +117 -0
  233. package/dist/toolkit/profiles.d.ts +13 -0
  234. package/dist/toolkit/profiles.js +25 -0
  235. package/dist/toolkit/record.d.ts +15 -0
  236. package/dist/toolkit/record.js +17 -0
  237. package/dist/tools/memoryTools.d.ts +95 -0
  238. package/dist/tools/memoryTools.js +179 -0
  239. package/dist/types/pipeline.d.ts +3 -0
  240. package/dist/types/stage.d.ts +2 -0
  241. package/dist/types/task.d.ts +11 -1
  242. package/dist/ui/assets/AssistantRuntimeProvider-CsiPFhCT.js +9 -0
  243. package/dist/ui/assets/MarkdownText-D7tYaIFO.js +29 -0
  244. package/dist/ui/assets/ReadonlyStore-BfXqHykm.js +6 -0
  245. package/dist/ui/assets/Tooltip-DrpoRRNi.js +1 -0
  246. package/dist/ui/assets/chat-BbLukq9W.js +5 -0
  247. package/dist/ui/assets/chat-zIZU_bP1.css +2 -0
  248. package/dist/ui/assets/jsx-runtime-iVoFAvQ_.js +1 -0
  249. package/dist/ui/assets/{index-Clv4sJpU.css → main-BFo79krC.css} +1 -1
  250. package/dist/ui/assets/main-CijYMeM0.js +259 -0
  251. package/dist/ui/chat.html +25 -0
  252. package/dist/ui/index.html +6 -2
  253. package/dist/workshop/modelSettings.d.ts +2 -2
  254. package/dist/workshop/modelSettings.js +5 -11
  255. package/examples/README.md +1 -0
  256. package/examples/channels-smoke/README.md +12 -1
  257. package/examples/github-release/README.md +1 -1
  258. package/examples/memory/README.md +101 -0
  259. package/examples/memory/desk-outage.task.yaml +15 -0
  260. package/examples/memory/desk-refund.task.yaml +15 -0
  261. package/examples/memory/desk-revisit-outage.task.yaml +11 -0
  262. package/examples/memory/desk-revisit.pipeline.yaml +123 -0
  263. package/examples/memory/desk.pipeline.yaml +192 -0
  264. package/examples/memory/file-card.pipeline.yaml +39 -0
  265. package/examples/memory/file-ledger.task.yaml +22 -0
  266. package/examples/memory/file-release.task.yaml +24 -0
  267. package/examples/memory/file-rich.pipeline.yaml +40 -0
  268. package/examples/memory/file-ticket.task.yaml +7 -0
  269. package/examples/memory/file-timeline.task.yaml +28 -0
  270. package/examples/memory/find-card.pipeline.yaml +46 -0
  271. package/examples/memory/find-ticket.task.yaml +7 -0
  272. package/examples/memory/missing.pipeline.yaml +16 -0
  273. package/examples/memory/probe-bash.task.yaml +3 -0
  274. package/examples/memory/recall.pipeline.yaml +24 -0
  275. package/examples/memory/recall.task.yaml +5 -0
  276. package/examples/memory/remember-ada.task.yaml +6 -0
  277. package/examples/memory/remember-grace.task.yaml +6 -0
  278. package/examples/memory/remember.pipeline.yaml +58 -0
  279. package/examples/memory/sandbox-memory-bash.pipeline.yaml +28 -0
  280. package/examples/memory/stageflow.yaml +12 -0
  281. package/examples/memory/update-card.pipeline.yaml +43 -0
  282. package/examples/memory/update-ledger.task.yaml +23 -0
  283. package/examples/memory/update-rich.pipeline.yaml +44 -0
  284. package/examples/memory/update-ticket.task.yaml +7 -0
  285. package/examples/ship-feature/README.md +15 -0
  286. package/examples/stage-mcp/README.md +1 -1
  287. package/package.json +2 -1
  288. package/dist/ui/assets/index-BOhqnPNg.js +0 -260
@@ -0,0 +1,643 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { agentDefinitionSchema } from "../config/loadAgent.js";
4
+ /**
5
+ * The one list of session statuses. The database CHECK (migration 013), the HTTP filter and the MCP tool schema all
6
+ * read this. `held` is kept because the CHECK allows it and old rows may carry it; nothing writes it any more.
7
+ */
8
+ export const SESSION_STATUSES = ["open", "running", "waiting", "held", "closed"];
9
+ export function isSessionStatus(value) {
10
+ return SESSION_STATUSES.includes(value);
11
+ }
12
+ export function agentProjectKey(projectRoot) {
13
+ return createHash("sha256").update(path.resolve(projectRoot)).digest("hex").slice(0, 16);
14
+ }
15
+ const str = (value) => value;
16
+ const strOrNull = (value) => (value === null || value === undefined ? null : value);
17
+ const numOrNull = (value) => (value === null || value === undefined ? null : Number(value));
18
+ function toDefinitionRow(row) {
19
+ return {
20
+ agentId: str(row.agent_id),
21
+ currentRevision: Number(row.current_revision),
22
+ archived: row.archived === 1,
23
+ fileHash: strOrNull(row.file_hash),
24
+ issues: row.issues_json ? JSON.parse(str(row.issues_json)) : [],
25
+ createdAt: str(row.created_at),
26
+ updatedAt: str(row.updated_at),
27
+ };
28
+ }
29
+ function toRevisionMeta(row) {
30
+ return {
31
+ agentId: str(row.agent_id),
32
+ revision: Number(row.revision),
33
+ source: str(row.source),
34
+ author: strOrNull(row.author),
35
+ summary: strOrNull(row.summary),
36
+ restoredFrom: numOrNull(row.restored_from),
37
+ createdAt: str(row.created_at),
38
+ };
39
+ }
40
+ /** Revisions saved before a block existed read back with that block's defaults. */
41
+ function withCurrentDefaults(stored) {
42
+ const parsed = agentDefinitionSchema.safeParse(stored);
43
+ return parsed.success ? parsed.data : stored;
44
+ }
45
+ function toRevision(row) {
46
+ return {
47
+ ...toRevisionMeta(row),
48
+ definition: withCurrentDefaults(JSON.parse(str(row.definition_json))),
49
+ yaml: str(row.yaml_text),
50
+ };
51
+ }
52
+ function toSession(row) {
53
+ return {
54
+ id: str(row.session_id),
55
+ agentId: str(row.agent_id),
56
+ revision: Number(row.revision),
57
+ backend: str(row.backend),
58
+ status: str(row.status),
59
+ source: str(row.source),
60
+ sourceRef: strOrNull(row.source_ref),
61
+ threadKey: strOrNull(row.thread_key),
62
+ title: strOrNull(row.title),
63
+ createdAt: str(row.created_at),
64
+ updatedAt: str(row.updated_at),
65
+ };
66
+ }
67
+ function toMessage(row) {
68
+ return {
69
+ id: str(row.message_id),
70
+ sessionId: str(row.session_id),
71
+ seq: Number(row.seq),
72
+ turnId: strOrNull(row.turn_id),
73
+ role: str(row.role),
74
+ author: strOrNull(row.author),
75
+ text: str(row.text),
76
+ createdAt: str(row.created_at),
77
+ };
78
+ }
79
+ function toTurn(row) {
80
+ return {
81
+ id: str(row.turn_id),
82
+ sessionId: str(row.session_id),
83
+ seq: Number(row.seq),
84
+ revision: Number(row.revision),
85
+ traceId: str(row.trace_id),
86
+ status: str(row.status),
87
+ errorCode: strOrNull(row.error_code),
88
+ inputTokens: numOrNull(row.input_tokens),
89
+ outputTokens: numOrNull(row.output_tokens),
90
+ costUsd: numOrNull(row.cost_usd),
91
+ startedAt: str(row.started_at),
92
+ endedAt: strOrNull(row.ended_at),
93
+ };
94
+ }
95
+ function toDelivery(row) {
96
+ return {
97
+ turnId: str(row.turn_id),
98
+ sessionId: str(row.session_id),
99
+ connectionId: str(row.connection_id),
100
+ provider: str(row.provider),
101
+ replyTo: str(row.reply_to),
102
+ text: str(row.text),
103
+ state: str(row.state),
104
+ attempts: Number(row.attempts),
105
+ errorCode: strOrNull(row.error_code),
106
+ errorMessage: strOrNull(row.error_message),
107
+ retryable: Number(row.retryable) === 1,
108
+ nextAttemptAt: strOrNull(row.next_attempt_at),
109
+ createdAt: str(row.created_at),
110
+ updatedAt: str(row.updated_at),
111
+ };
112
+ }
113
+ function toSpan(row) {
114
+ return {
115
+ id: str(row.span_id),
116
+ turnId: str(row.turn_id),
117
+ parentSpanId: strOrNull(row.parent_span_id),
118
+ name: str(row.name),
119
+ kind: str(row.kind),
120
+ status: str(row.status),
121
+ startedAt: str(row.started_at),
122
+ endedAt: strOrNull(row.ended_at),
123
+ durationMs: numOrNull(row.duration_ms),
124
+ inputTokens: numOrNull(row.input_tokens),
125
+ outputTokens: numOrNull(row.output_tokens),
126
+ attributes: JSON.parse(str(row.attributes_json)),
127
+ inputText: strOrNull(row.input_text),
128
+ outputText: strOrNull(row.output_text),
129
+ };
130
+ }
131
+ function toApproval(row) {
132
+ return {
133
+ id: str(row.approval_id),
134
+ sessionId: str(row.session_id),
135
+ turnId: strOrNull(row.turn_id),
136
+ tool: str(row.tool),
137
+ payload: JSON.parse(str(row.payload_json)),
138
+ payloadHash: str(row.payload_hash),
139
+ status: str(row.status),
140
+ createdAt: str(row.created_at),
141
+ expiresAt: str(row.expires_at),
142
+ decidedAt: strOrNull(row.decided_at),
143
+ actor: row.actor_json ? JSON.parse(str(row.actor_json)) : null,
144
+ };
145
+ }
146
+ function encodeCursor(updatedAt, sessionId) {
147
+ return Buffer.from(JSON.stringify([updatedAt, sessionId])).toString("base64url");
148
+ }
149
+ function decodeCursor(cursor) {
150
+ try {
151
+ const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
152
+ if (Array.isArray(value) && value.length === 2 && value.every((item) => typeof item === "string")) {
153
+ return value;
154
+ }
155
+ }
156
+ catch {
157
+ return undefined;
158
+ }
159
+ return undefined;
160
+ }
161
+ export class AgentStore {
162
+ db;
163
+ constructor(db) {
164
+ this.db = db;
165
+ }
166
+ transaction(work) {
167
+ return this.db.transaction(work)();
168
+ }
169
+ /* ---------- definitions and revisions ---------- */
170
+ listDefinitions(projectKey) {
171
+ return this.db
172
+ .prepare(`SELECT * FROM agent_definitions WHERE project_key = ? ORDER BY agent_id`)
173
+ .all(projectKey).map(toDefinitionRow);
174
+ }
175
+ getDefinition(projectKey, agentId) {
176
+ const row = this.db
177
+ .prepare(`SELECT * FROM agent_definitions WHERE project_key = ? AND agent_id = ?`)
178
+ .get(projectKey, agentId);
179
+ return row ? toDefinitionRow(row) : undefined;
180
+ }
181
+ /** Record the file's latest observed state without changing the revision (invalid edit, archive, restore). */
182
+ updateDefinitionState(projectKey, agentId, patch, now) {
183
+ const current = this.getDefinition(projectKey, agentId);
184
+ if (!current)
185
+ return;
186
+ this.db
187
+ .prepare(`UPDATE agent_definitions
188
+ SET archived = ?, file_hash = ?, issues_json = ?, updated_at = ?
189
+ WHERE project_key = ? AND agent_id = ?`)
190
+ .run((patch.archived ?? current.archived) ? 1 : 0, patch.fileHash === undefined ? current.fileHash : patch.fileHash, JSON.stringify(patch.issues ?? current.issues), now, projectKey, agentId);
191
+ }
192
+ /** Append the next revision and make it current. Returns the new revision number. */
193
+ appendRevision(projectKey, input) {
194
+ return this.transaction(() => {
195
+ const agentId = input.definition.id;
196
+ const existing = this.getDefinition(projectKey, agentId);
197
+ const revision = (existing?.currentRevision ?? 0) + 1;
198
+ this.db
199
+ .prepare(`INSERT INTO agent_revisions
200
+ (project_key, agent_id, revision, definition_json, yaml_text, source, author, summary, restored_from, created_at)
201
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
202
+ .run(projectKey, agentId, revision, JSON.stringify(input.definition), input.yaml, input.source, input.author ?? null, input.summary ?? null, input.restoredFrom ?? null, input.now);
203
+ if (existing) {
204
+ this.db
205
+ .prepare(`UPDATE agent_definitions
206
+ SET current_revision = ?, archived = 0, file_hash = ?, issues_json = '[]', updated_at = ?
207
+ WHERE project_key = ? AND agent_id = ?`)
208
+ .run(revision, input.fileHash, input.now, projectKey, agentId);
209
+ }
210
+ else {
211
+ this.db
212
+ .prepare(`INSERT INTO agent_definitions
213
+ (project_key, agent_id, current_revision, archived, file_hash, issues_json, created_at, updated_at)
214
+ VALUES (?, ?, ?, 0, ?, '[]', ?, ?)`)
215
+ .run(projectKey, agentId, revision, input.fileHash, input.now, input.now);
216
+ }
217
+ return revision;
218
+ });
219
+ }
220
+ /** First sighting of a file that never parsed: no revision yet, only issues. */
221
+ recordInvalidNewDefinition(projectKey, agentId, fileHash, issues, now) {
222
+ this.db
223
+ .prepare(`INSERT INTO agent_definitions
224
+ (project_key, agent_id, current_revision, archived, file_hash, issues_json, created_at, updated_at)
225
+ VALUES (?, ?, 0, 0, ?, ?, ?, ?)
226
+ ON CONFLICT (project_key, agent_id) DO UPDATE
227
+ SET file_hash = excluded.file_hash, issues_json = excluded.issues_json, archived = 0,
228
+ updated_at = excluded.updated_at`)
229
+ .run(projectKey, agentId, fileHash, JSON.stringify(issues), now, now);
230
+ }
231
+ getRevision(projectKey, agentId, revision) {
232
+ const row = this.db
233
+ .prepare(`SELECT * FROM agent_revisions WHERE project_key = ? AND agent_id = ? AND revision = ?`)
234
+ .get(projectKey, agentId, revision);
235
+ return row ? toRevision(row) : undefined;
236
+ }
237
+ listRevisions(projectKey, agentId) {
238
+ return this.db
239
+ .prepare(`SELECT agent_id, revision, source, author, summary, restored_from, created_at
240
+ FROM agent_revisions WHERE project_key = ? AND agent_id = ? ORDER BY revision DESC`)
241
+ .all(projectKey, agentId).map(toRevisionMeta);
242
+ }
243
+ /* ---------- sessions and messages ---------- */
244
+ insertSession(projectKey, session) {
245
+ this.db
246
+ .prepare(`INSERT INTO agent_sessions
247
+ (session_id, project_key, agent_id, revision, backend, status, source, source_ref, thread_key, title,
248
+ created_at, updated_at)
249
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
250
+ .run(session.id, projectKey, session.agentId, session.revision, session.backend, session.status, session.source, session.sourceRef, session.threadKey, session.title, session.createdAt, session.updatedAt);
251
+ }
252
+ getSession(sessionId) {
253
+ const row = this.db.prepare(`SELECT * FROM agent_sessions WHERE session_id = ?`).get(sessionId);
254
+ return row ? { ...toSession(row), projectKey: str(row.project_key) } : undefined;
255
+ }
256
+ findSessionByThread(projectKey, agentId, threadKey) {
257
+ const row = this.db
258
+ .prepare(`SELECT * FROM agent_sessions WHERE project_key = ? AND agent_id = ? AND thread_key = ?`)
259
+ .get(projectKey, agentId, threadKey);
260
+ return row ? toSession(row) : undefined;
261
+ }
262
+ updateSession(sessionId, patch, now) {
263
+ const sets = ["updated_at = @now"];
264
+ if (patch.status !== undefined)
265
+ sets.push("status = @status");
266
+ if (patch.backend !== undefined)
267
+ sets.push("backend = @backend");
268
+ if (patch.title !== undefined)
269
+ sets.push("title = @title");
270
+ this.db
271
+ .prepare(`UPDATE agent_sessions SET ${sets.join(", ")} WHERE session_id = @sessionId`)
272
+ .run({ ...patch, now, sessionId });
273
+ }
274
+ /** One agent's sessions, or every agent's when `agentId` is omitted. */
275
+ listSessions(projectKey, agentId, query) {
276
+ const where = ["project_key = @projectKey"];
277
+ const params = { projectKey, limit: query.limit + 1 };
278
+ if (agentId !== undefined) {
279
+ where.push("agent_id = @agentId");
280
+ params.agentId = agentId;
281
+ }
282
+ if (query.status) {
283
+ where.push("status = @status");
284
+ params.status = query.status;
285
+ }
286
+ if (query.source) {
287
+ where.push("source = @source");
288
+ params.source = query.source;
289
+ }
290
+ if (query.search) {
291
+ where.push(`(title LIKE @search ESCAPE '\\' OR session_id LIKE @search ESCAPE '\\'
292
+ OR EXISTS (SELECT 1 FROM agent_messages m WHERE m.session_id = agent_sessions.session_id
293
+ AND m.text LIKE @search ESCAPE '\\'))`);
294
+ params.search = `%${query.search.replace(/[\\%_]/g, (char) => `\\${char}`)}%`;
295
+ }
296
+ const cursor = query.cursor ? decodeCursor(query.cursor) : undefined;
297
+ if (cursor) {
298
+ where.push("(updated_at < @cursorAt OR (updated_at = @cursorAt AND session_id < @cursorId))");
299
+ params.cursorAt = cursor[0];
300
+ params.cursorId = cursor[1];
301
+ }
302
+ const rows = this.db
303
+ .prepare(`SELECT agent_sessions.*,
304
+ (SELECT COUNT(*) FROM agent_turns t WHERE t.session_id = agent_sessions.session_id) AS turn_count,
305
+ (SELECT COALESCE(SUM(input_tokens), 0) FROM agent_turns t
306
+ WHERE t.session_id = agent_sessions.session_id) AS input_tokens,
307
+ (SELECT COALESCE(SUM(output_tokens), 0) FROM agent_turns t
308
+ WHERE t.session_id = agent_sessions.session_id) AS output_tokens,
309
+ (SELECT SUM(cost_usd) FROM agent_turns t WHERE t.session_id = agent_sessions.session_id) AS cost_usd,
310
+ (SELECT status FROM agent_turns t WHERE t.session_id = agent_sessions.session_id
311
+ ORDER BY seq DESC LIMIT 1) AS last_turn_status
312
+ FROM agent_sessions WHERE ${where.join(" AND ")}
313
+ ORDER BY updated_at DESC, session_id DESC LIMIT @limit`)
314
+ .all(params);
315
+ const sessions = rows.slice(0, query.limit).map((row) => ({
316
+ ...toSession(row),
317
+ turnCount: Number(row.turn_count),
318
+ inputTokens: Number(row.input_tokens),
319
+ outputTokens: Number(row.output_tokens),
320
+ costUsd: numOrNull(row.cost_usd),
321
+ lastTurnStatus: strOrNull(row.last_turn_status),
322
+ }));
323
+ const last = sessions.at(-1);
324
+ return {
325
+ sessions,
326
+ nextCursor: rows.length > query.limit && last ? encodeCursor(last.updatedAt, last.id) : null,
327
+ };
328
+ }
329
+ /** One visitor's sessions on one agent, newest first (public web chat). */
330
+ listSessionsForRef(projectKey, agentId, source, sourceRef, limit) {
331
+ return this.db
332
+ .prepare(`SELECT * FROM agent_sessions
333
+ WHERE project_key = ? AND agent_id = ? AND source = ? AND source_ref = ?
334
+ ORDER BY updated_at DESC, session_id DESC LIMIT ?`)
335
+ .all(projectKey, agentId, source, sourceRef, limit).map(toSession);
336
+ }
337
+ /** Stats for one agent, or for every agent when `agentId` is omitted. */
338
+ sessionStats(projectKey, agentId, since) {
339
+ const agentFilter = agentId === undefined ? "" : " AND agent_id = @agentId";
340
+ const sessionAgentFilter = agentId === undefined ? "" : " AND s.agent_id = @agentId";
341
+ const bind = (extra) => (agentId === undefined ? extra : { ...extra, agentId });
342
+ const row = this.db
343
+ .prepare(`SELECT COUNT(*) AS total,
344
+ SUM(CASE WHEN status IN ('running', 'waiting') THEN 1 ELSE 0 END) AS active,
345
+ SUM(CASE WHEN status = 'waiting' THEN 1 ELSE 0 END) AS waiting,
346
+ SUM(CASE WHEN created_at >= @since THEN 1 ELSE 0 END) AS recent,
347
+ MAX(updated_at) AS last_activity
348
+ FROM agent_sessions WHERE project_key = @projectKey${agentFilter}`)
349
+ .get(bind({ projectKey, since }));
350
+ const turns = this.db
351
+ .prepare(`SELECT SUM(CASE WHEN t.started_at >= @since AND t.status IN ('succeeded', 'failed', 'cancelled') THEN 1 ELSE 0 END) AS finished,
352
+ SUM(CASE WHEN t.started_at >= @since AND t.status = 'succeeded' THEN 1 ELSE 0 END) AS succeeded,
353
+ SUM(CASE WHEN t.started_at >= @since THEN t.cost_usd END) AS cost,
354
+ SUM(t.cost_usd) AS cost_all
355
+ FROM agent_turns t JOIN agent_sessions s ON s.session_id = t.session_id
356
+ WHERE s.project_key = @projectKey${sessionAgentFilter}`)
357
+ .get(bind({ projectKey, since }));
358
+ return {
359
+ total: Number(row.total ?? 0),
360
+ active: Number(row.active ?? 0),
361
+ waiting: Number(row.waiting ?? 0),
362
+ last7Days: Number(row.recent ?? 0),
363
+ lastActivityAt: strOrNull(row.last_activity),
364
+ turnsFinished7d: Number(turns.finished ?? 0),
365
+ turnsSucceeded7d: Number(turns.succeeded ?? 0),
366
+ cost7d: numOrNull(turns.cost),
367
+ costTotal: numOrNull(turns.cost_all),
368
+ };
369
+ }
370
+ /** Ids of every session an agent has had, for releasing what a runtime holds for them. */
371
+ sessionIdsOf(projectKey, agentId) {
372
+ return this.db.prepare(`SELECT session_id FROM agent_sessions WHERE project_key = ? AND agent_id = ?`).all(projectKey, agentId).map((row) => str(row.session_id));
373
+ }
374
+ appendMessage(message) {
375
+ const seq = Number(this.db
376
+ .prepare(`SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM agent_messages WHERE session_id = ?`)
377
+ .get(message.sessionId).next);
378
+ this.db
379
+ .prepare(`INSERT INTO agent_messages (message_id, session_id, seq, turn_id, role, author, text, created_at)
380
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
381
+ .run(message.id, message.sessionId, seq, message.turnId, message.role, message.author, message.text, message.createdAt);
382
+ return { ...message, seq };
383
+ }
384
+ listMessages(sessionId) {
385
+ return this.db.prepare(`SELECT * FROM agent_messages WHERE session_id = ? ORDER BY seq`).all(sessionId).map(toMessage);
386
+ }
387
+ /* ---------- turns, spans, evals ---------- */
388
+ insertTurn(turn) {
389
+ const seq = Number(this.db
390
+ .prepare(`SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM agent_turns WHERE session_id = ?`)
391
+ .get(turn.sessionId).next);
392
+ this.db
393
+ .prepare(`INSERT INTO agent_turns
394
+ (turn_id, session_id, seq, revision, trace_id, status, error_code, input_tokens, output_tokens, cost_usd,
395
+ started_at, ended_at)
396
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
397
+ .run(turn.id, turn.sessionId, seq, turn.revision, turn.traceId, turn.status, turn.errorCode, turn.inputTokens, turn.outputTokens, turn.costUsd, turn.startedAt, turn.endedAt);
398
+ return { ...turn, seq };
399
+ }
400
+ finishTurn(turnId, patch) {
401
+ this.db
402
+ .prepare(`UPDATE agent_turns
403
+ SET status = @status, error_code = @errorCode, input_tokens = @inputTokens,
404
+ output_tokens = @outputTokens, cost_usd = @costUsd, ended_at = @endedAt
405
+ WHERE turn_id = @turnId`)
406
+ .run({ ...patch, turnId });
407
+ }
408
+ /** Change a finished turn's outcome after the fact, for example when its reply could not be delivered. */
409
+ setTurnOutcome(turnId, status, errorCode) {
410
+ this.db.prepare(`UPDATE agent_turns SET status = ?, error_code = ? WHERE turn_id = ?`).run(status, errorCode, turnId);
411
+ }
412
+ insertDelivery(delivery) {
413
+ this.db
414
+ .prepare(`INSERT INTO agent_deliveries
415
+ (turn_id, session_id, connection_id, provider, reply_to, text, state, attempts, error_code, error_message, retryable, next_attempt_at, created_at, updated_at)
416
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
417
+ .run(delivery.turnId, delivery.sessionId, delivery.connectionId, delivery.provider, delivery.replyTo, delivery.text, delivery.state, delivery.attempts, delivery.errorCode, delivery.errorMessage, delivery.retryable ? 1 : 0, delivery.nextAttemptAt, delivery.createdAt, delivery.updatedAt);
418
+ }
419
+ getDelivery(turnId) {
420
+ const row = this.db.prepare(`SELECT * FROM agent_deliveries WHERE turn_id = ?`).get(turnId);
421
+ return row ? toDelivery(row) : undefined;
422
+ }
423
+ listDeliveries(sessionId) {
424
+ return this.db.prepare(`SELECT * FROM agent_deliveries WHERE session_id = ? ORDER BY created_at`).all(sessionId).map(toDelivery);
425
+ }
426
+ updateDelivery(turnId, patch, now) {
427
+ const current = this.getDelivery(turnId);
428
+ if (!current)
429
+ return;
430
+ const next = { ...current, ...patch };
431
+ this.db
432
+ .prepare(`UPDATE agent_deliveries SET state = ?, attempts = ?, error_code = ?, error_message = ?, retryable = ?, next_attempt_at = ?, updated_at = ? WHERE turn_id = ?`)
433
+ .run(next.state, next.attempts, next.errorCode, next.errorMessage, next.retryable ? 1 : 0, next.nextAttemptAt, now, turnId);
434
+ }
435
+ /**
436
+ * Atomically take a delivery for sending: one UPDATE that only matches while it is still in an allowed state (and,
437
+ * for a retry, still due), so two overlapping attempts cannot both win. Returns the claimed row or undefined.
438
+ */
439
+ claimDelivery(turnId, states, now, options = {}) {
440
+ const placeholders = states.map(() => "?").join(", ");
441
+ const due = options.dueRetry ? " AND retryable = 1 AND next_attempt_at IS NOT NULL AND next_attempt_at <= ?" : "";
442
+ const result = this.db
443
+ .prepare(`UPDATE agent_deliveries
444
+ SET state = 'sending', attempts = attempts + 1, error_code = NULL, error_message = NULL,
445
+ retryable = 0, next_attempt_at = NULL, updated_at = ?
446
+ WHERE turn_id = ? AND state IN (${placeholders})${due}`)
447
+ .run(now, turnId, ...states, ...(options.dueRetry ? [now] : []));
448
+ return result.changes === 1 ? this.getDelivery(turnId) : undefined;
449
+ }
450
+ /** Failed deliveries a retry is due for. */
451
+ dueDeliveries(now) {
452
+ return this.db.prepare(`SELECT * FROM agent_deliveries WHERE state = 'failed' AND retryable = 1 AND next_attempt_at IS NOT NULL AND next_attempt_at <= ? ORDER BY next_attempt_at`).all(now).map(toDelivery);
453
+ }
454
+ /** Sends cut off mid-flight by a restart: whether they arrived is unknown. Returns the turns affected. */
455
+ settleInterruptedDeliveries(now) {
456
+ const ids = this.db.prepare(`SELECT turn_id FROM agent_deliveries WHERE state = 'sending'`).all().map((row) => str(row.turn_id));
457
+ this.db
458
+ .prepare(`UPDATE agent_deliveries SET state = 'unknown', error_code = 'CHANNEL_SEND_OUTCOME_UNKNOWN', error_message = 'The Host restarted while the reply was being sent', retryable = 0, next_attempt_at = NULL, updated_at = ? WHERE state = 'sending'`)
459
+ .run(now);
460
+ return ids;
461
+ }
462
+ /** The most recent delivery of an agent that did not get through, for the channel status line. */
463
+ latestUndelivered(agentId, projectKey) {
464
+ const row = this.db
465
+ .prepare(`SELECT d.* FROM agent_deliveries d JOIN agent_sessions s ON s.session_id = d.session_id
466
+ WHERE s.project_key = ? AND s.agent_id = ? AND d.state IN ('failed', 'unknown')
467
+ ORDER BY d.updated_at DESC LIMIT 1`)
468
+ .get(projectKey, agentId);
469
+ return row ? toDelivery(row) : undefined;
470
+ }
471
+ setTurnStatus(turnId, status) {
472
+ this.db.prepare(`UPDATE agent_turns SET status = ? WHERE turn_id = ?`).run(status, turnId);
473
+ }
474
+ getTurn(turnId) {
475
+ const row = this.db.prepare(`SELECT * FROM agent_turns WHERE turn_id = ?`).get(turnId);
476
+ return row ? toTurn(row) : undefined;
477
+ }
478
+ listTurns(sessionId) {
479
+ return this.db.prepare(`SELECT * FROM agent_turns WHERE session_id = ? ORDER BY seq`).all(sessionId).map(toTurn);
480
+ }
481
+ /** Turns left `running` by a host that stopped mid-turn. */
482
+ /** Turns a restart is about to fail: they were mid-flight when the Host went down. */
483
+ interruptedTurnIds() {
484
+ return this.db.prepare(`SELECT turn_id FROM agent_turns WHERE status IN ('running', 'waiting')`).all().map((row) => str(row.turn_id));
485
+ }
486
+ /** Give an unanswered user message to a new turn, so a retry does not repeat it in the transcript. */
487
+ moveMessageToTurn(messageId, turnId) {
488
+ this.db.prepare(`UPDATE agent_messages SET turn_id = ? WHERE message_id = ?`).run(turnId, messageId);
489
+ }
490
+ failInterruptedTurns(now) {
491
+ return this.transaction(() => {
492
+ const result = this.db
493
+ .prepare(`UPDATE agent_turns SET status = 'failed', error_code = 'agent.host_restarted', ended_at = ?
494
+ WHERE status IN ('running', 'waiting')`)
495
+ .run(now);
496
+ this.db
497
+ .prepare(`UPDATE agent_sessions SET status = 'open', updated_at = ? WHERE status IN ('running', 'waiting')`)
498
+ .run(now);
499
+ this.db
500
+ .prepare(`UPDATE agent_approvals SET status = 'expired' WHERE status = 'pending'`)
501
+ .run();
502
+ return result.changes;
503
+ });
504
+ }
505
+ insertSpan(span) {
506
+ this.db
507
+ .prepare(`INSERT INTO agent_spans
508
+ (span_id, turn_id, parent_span_id, name, kind, status, started_at, ended_at, duration_ms,
509
+ input_tokens, output_tokens, attributes_json, input_text, output_text)
510
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
511
+ .run(span.id, span.turnId, span.parentSpanId, span.name, span.kind, span.status, span.startedAt, span.endedAt, span.durationMs, span.inputTokens, span.outputTokens, JSON.stringify(span.attributes), span.inputText, span.outputText);
512
+ }
513
+ listSpansForTurn(turnId) {
514
+ return this.db.prepare(`SELECT * FROM agent_spans WHERE turn_id = ? ORDER BY started_at, span_id`).all(turnId).map(toSpan);
515
+ }
516
+ listSpansForSession(sessionId) {
517
+ return this.db
518
+ .prepare(`SELECT s.* FROM agent_spans s JOIN agent_turns t ON t.turn_id = s.turn_id
519
+ WHERE t.session_id = ? ORDER BY t.seq, s.started_at, s.span_id`)
520
+ .all(sessionId).map(toSpan);
521
+ }
522
+ recordEvalScore(score) {
523
+ this.db
524
+ .prepare(`INSERT INTO agent_eval_scores (turn_id, metric, score, recorded_at) VALUES (?, ?, ?, ?)
525
+ ON CONFLICT (turn_id, metric) DO UPDATE SET score = excluded.score, recorded_at = excluded.recorded_at`)
526
+ .run(score.turnId, score.metric, score.score, score.recordedAt);
527
+ }
528
+ listEvalScores(sessionId) {
529
+ return this.db
530
+ .prepare(`SELECT e.* FROM agent_eval_scores e JOIN agent_turns t ON t.turn_id = e.turn_id
531
+ WHERE t.session_id = ? ORDER BY t.seq`)
532
+ .all(sessionId).map((row) => ({
533
+ turnId: str(row.turn_id),
534
+ metric: str(row.metric),
535
+ score: Number(row.score),
536
+ recordedAt: str(row.recorded_at),
537
+ }));
538
+ }
539
+ /* ---------- approvals ---------- */
540
+ insertApproval(approval) {
541
+ this.db
542
+ .prepare(`INSERT INTO agent_approvals
543
+ (approval_id, session_id, turn_id, tool, payload_json, payload_hash, status, created_at, expires_at,
544
+ decided_at, actor_json)
545
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
546
+ .run(approval.id, approval.sessionId, approval.turnId, approval.tool, JSON.stringify(approval.payload), approval.payloadHash, approval.status, approval.createdAt, approval.expiresAt, approval.decidedAt, approval.actor ? JSON.stringify(approval.actor) : null);
547
+ }
548
+ getPendingApproval(sessionId) {
549
+ const row = this.db
550
+ .prepare(`SELECT * FROM agent_approvals WHERE session_id = ? AND status = 'pending'`)
551
+ .get(sessionId);
552
+ return row ? toApproval(row) : undefined;
553
+ }
554
+ findApprovalByPayload(sessionId, payloadHash) {
555
+ const row = this.db
556
+ .prepare(`SELECT * FROM agent_approvals WHERE session_id = ? AND payload_hash = ? ORDER BY created_at DESC LIMIT 1`)
557
+ .get(sessionId, payloadHash);
558
+ return row ? toApproval(row) : undefined;
559
+ }
560
+ listApprovals(sessionId) {
561
+ return this.db
562
+ .prepare(`SELECT * FROM agent_approvals WHERE session_id = ? ORDER BY created_at`)
563
+ .all(sessionId).map(toApproval);
564
+ }
565
+ listPendingApprovals(projectKey) {
566
+ return this.db
567
+ .prepare(`SELECT a.*, s.agent_id FROM agent_approvals a JOIN agent_sessions s ON s.session_id = a.session_id
568
+ WHERE s.project_key = ? AND a.status = 'pending' ORDER BY a.created_at`)
569
+ .all(projectKey).map((row) => ({ ...toApproval(row), agentId: str(row.agent_id) }));
570
+ }
571
+ decideApproval(approvalId, patch) {
572
+ this.db
573
+ .prepare(`UPDATE agent_approvals SET status = ?, decided_at = ?, actor_json = ?
574
+ WHERE approval_id = ? AND status = 'pending'`)
575
+ .run(patch.status, patch.decidedAt, patch.actor ? JSON.stringify(patch.actor) : null, approvalId);
576
+ }
577
+ /** Expire every pending approval past its deadline; returns the affected approvals. */
578
+ expireApprovals(now) {
579
+ return this.transaction(() => {
580
+ const rows = this.db
581
+ .prepare(`SELECT approval_id, session_id FROM agent_approvals WHERE status = 'pending' AND expires_at <= ?`)
582
+ .all(now);
583
+ for (const row of rows) {
584
+ this.decideApproval(str(row.approval_id), { status: "expired", decidedAt: now, actor: null });
585
+ }
586
+ return rows.map((row) => ({ approvalId: str(row.approval_id), sessionId: str(row.session_id) }));
587
+ });
588
+ }
589
+ /** Close (as expired) the pending approvals raised by a turn that is over; returns their ids. */
590
+ closePendingApprovalsOfTurn(turnId, now) {
591
+ return this.transaction(() => {
592
+ const rows = this.db.prepare(`SELECT approval_id FROM agent_approvals WHERE turn_id = ? AND status = 'pending'`).all(turnId);
593
+ for (const row of rows)
594
+ this.decideApproval(str(row.approval_id), { status: "expired", decidedAt: now, actor: null });
595
+ return rows.map((row) => str(row.approval_id));
596
+ });
597
+ }
598
+ getApproval(approvalId) {
599
+ const row = this.db.prepare(`SELECT * FROM agent_approvals WHERE approval_id = ?`).get(approvalId);
600
+ return row ? toApproval(row) : undefined;
601
+ }
602
+ markPayloadExecuted(sessionId, payloadHash, now) {
603
+ this.db
604
+ .prepare(`INSERT OR IGNORE INTO agent_executed_payloads (session_id, payload_hash, executed_at) VALUES (?, ?, ?)`)
605
+ .run(sessionId, payloadHash, now);
606
+ }
607
+ hasExecutedPayload(sessionId, payloadHash) {
608
+ return (this.db
609
+ .prepare(`SELECT 1 FROM agent_executed_payloads WHERE session_id = ? AND payload_hash = ?`)
610
+ .get(sessionId, payloadHash) !== undefined);
611
+ }
612
+ /* ---------- communications and schedules ---------- */
613
+ /** True the first time a provider event is seen for this project. */
614
+ rememberInboundEvent(projectKey, dedupeKey, now) {
615
+ return (this.db
616
+ .prepare(`INSERT OR IGNORE INTO agent_inbound_events (project_key, dedupe_key, received_at) VALUES (?, ?, ?)`)
617
+ .run(projectKey, dedupeKey, now).changes === 1);
618
+ }
619
+ getOutboundState(sessionId, operationKey) {
620
+ const row = this.db
621
+ .prepare(`SELECT state FROM agent_outbound_sends WHERE session_id = ? AND operation_key = ?`)
622
+ .get(sessionId, operationKey);
623
+ return row ? str(row.state) : undefined;
624
+ }
625
+ setOutboundState(sessionId, operationKey, state, now) {
626
+ this.db
627
+ .prepare(`INSERT INTO agent_outbound_sends (session_id, operation_key, state, updated_at) VALUES (?, ?, ?, ?)
628
+ ON CONFLICT (session_id, operation_key) DO UPDATE SET state = excluded.state, updated_at = excluded.updated_at`)
629
+ .run(sessionId, operationKey, state, now);
630
+ }
631
+ getScheduleNextRun(projectKey, agentId) {
632
+ const row = this.db
633
+ .prepare(`SELECT next_run_at FROM agent_schedules WHERE project_key = ? AND agent_id = ?`)
634
+ .get(projectKey, agentId);
635
+ return row ? str(row.next_run_at) : undefined;
636
+ }
637
+ setScheduleNextRun(projectKey, agentId, nextRunAt) {
638
+ this.db
639
+ .prepare(`INSERT INTO agent_schedules (project_key, agent_id, next_run_at) VALUES (?, ?, ?)
640
+ ON CONFLICT (project_key, agent_id) DO UPDATE SET next_run_at = excluded.next_run_at`)
641
+ .run(projectKey, agentId, nextRunAt);
642
+ }
643
+ }
@@ -0,0 +1,27 @@
1
+ import type { AgentTraceCapture } from "../config/loadAgent.js";
2
+ import type { AgentSpan } from "./store.js";
3
+ export type SpanKind = "agent" | "model" | "tool" | "sub_agent" | "guardrail" | "gate" | "internal";
4
+ export type SpanStatus = "ok" | "error" | "cancelled";
5
+ export type SpanDraft = {
6
+ turnId: string;
7
+ parentSpanId?: string | null;
8
+ name: string;
9
+ kind: SpanKind;
10
+ status: SpanStatus;
11
+ startedAt: Date;
12
+ endedAt?: Date;
13
+ usage?: {
14
+ inputTokens?: number;
15
+ outputTokens?: number;
16
+ };
17
+ attributes?: Record<string, unknown>;
18
+ input?: string;
19
+ output?: string;
20
+ };
21
+ /**
22
+ * Apply the revision's capture mode to a span before it is stored:
23
+ * `off` records nothing, `metadata` drops prompt/argument/result bodies, and
24
+ * `full` keeps them after secret redaction. Redaction happens here, before
25
+ * storage, so export never sees an unredacted body.
26
+ */
27
+ export declare function prepareSpan(capture: AgentTraceCapture, draft: SpanDraft): AgentSpan | null;