@ixo/oracle-runtime-workers 0.2.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 (215) hide show
  1. package/README.md +110 -0
  2. package/package.json +77 -0
  3. package/src/attachments/classify.ts +114 -0
  4. package/src/attachments/content-blocks.ts +51 -0
  5. package/src/attachments/download.ts +235 -0
  6. package/src/attachments/extract.ts +241 -0
  7. package/src/attachments/index.ts +84 -0
  8. package/src/attachments/magic.ts +133 -0
  9. package/src/attachments/pipeline.ts +409 -0
  10. package/src/attachments/retention.ts +205 -0
  11. package/src/attachments/route.ts +41 -0
  12. package/src/attachments/sandbox-archive.ts +90 -0
  13. package/src/attachments/sanitize.ts +35 -0
  14. package/src/attachments/types.ts +86 -0
  15. package/src/attachments/view.ts +107 -0
  16. package/src/core/env.ts +200 -0
  17. package/src/core/index.ts +513 -0
  18. package/src/core/llm.ts +842 -0
  19. package/src/core/main-agent-types.ts +123 -0
  20. package/src/core/main-agent.ts +399 -0
  21. package/src/core/manifest.ts +365 -0
  22. package/src/core/meta-tools.ts +232 -0
  23. package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
  24. package/src/core/middlewares/capability-gate.ts +76 -0
  25. package/src/core/middlewares/index.ts +18 -0
  26. package/src/core/middlewares/page-context.ts +98 -0
  27. package/src/core/middlewares/safety-guardrail.ts +117 -0
  28. package/src/core/middlewares/summarization.ts +99 -0
  29. package/src/core/middlewares/tool-repetition-guard.ts +125 -0
  30. package/src/core/middlewares/tool-validation.ts +83 -0
  31. package/src/core/middlewares/work-status.ts +57 -0
  32. package/src/core/openrouter-pricing.ts +124 -0
  33. package/src/core/plugin-loader.ts +192 -0
  34. package/src/core/plugins/index.ts +6 -0
  35. package/src/core/plugins/skills/index.ts +8 -0
  36. package/src/core/plugins/skills/skills-tools.ts +356 -0
  37. package/src/core/plugins/skills/skills.plugin.ts +113 -0
  38. package/src/core/plugins/weather/index.ts +8 -0
  39. package/src/core/plugins/weather/weather-client.ts +153 -0
  40. package/src/core/plugins/weather/weather-middleware.ts +27 -0
  41. package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
  42. package/src/core/plugins/weather/weather-tools.ts +103 -0
  43. package/src/core/plugins/weather/weather.plugin.ts +227 -0
  44. package/src/core/prompt-composer.ts +469 -0
  45. package/src/core/registries.ts +642 -0
  46. package/src/core/runtime-context.ts +619 -0
  47. package/src/core/state.ts +108 -0
  48. package/src/core/sub-agent-fallback.ts +187 -0
  49. package/src/core/subagent-as-tool.ts +322 -0
  50. package/src/core/template.ts +245 -0
  51. package/src/core/utils.ts +246 -0
  52. package/src/core/wrap-plugin-tool.ts +100 -0
  53. package/src/do/ambient.ts +266 -0
  54. package/src/do/contracts.ts +562 -0
  55. package/src/do/debug-timers.ts +120 -0
  56. package/src/do/fresh-stub.ts +32 -0
  57. package/src/do/gateway-retry.ts +73 -0
  58. package/src/do/secrets-adapter.ts +32 -0
  59. package/src/do/size-alerts.ts +76 -0
  60. package/src/do/sse-stream.ts +472 -0
  61. package/src/do/transcript.ts +192 -0
  62. package/src/do/turn-body.ts +84 -0
  63. package/src/do/turn-metadata.ts +146 -0
  64. package/src/do/ucan-service.ts +339 -0
  65. package/src/do/user-oracle-do.ts +3035 -0
  66. package/src/gateway-worker.ts +82 -0
  67. package/src/index.ts +237 -0
  68. package/src/llm/byo-adapter.ts +59 -0
  69. package/src/llm/byo-catalog.ts +520 -0
  70. package/src/llm/byo-client.ts +280 -0
  71. package/src/llm/byo-routes.ts +255 -0
  72. package/src/llm/byo-service.ts +836 -0
  73. package/src/llm/chatgpt-oauth.ts +430 -0
  74. package/src/llm/provider-error.ts +283 -0
  75. package/src/matrix/gateway-do.ts +918 -0
  76. package/src/matrix/ingest.ts +209 -0
  77. package/src/matrix/replay-format.ts +34 -0
  78. package/src/matrix/reply-chain.ts +109 -0
  79. package/src/matrix/room-state-codec.ts +115 -0
  80. package/src/matrix/user-homeserver.ts +106 -0
  81. package/src/matrix/work-status.ts +294 -0
  82. package/src/memory/session-history-indexer.ts +328 -0
  83. package/src/owner-store/ixo-vfs-store.ts +570 -0
  84. package/src/owner-store/matrix-media-store.ts +136 -0
  85. package/src/owner-store/migrating-store.ts +185 -0
  86. package/src/owner-store/owner-copy-errors.ts +175 -0
  87. package/src/owner-store/retry.ts +45 -0
  88. package/src/owner-store/tus-upload.ts +333 -0
  89. package/src/owner-store/types.ts +0 -0
  90. package/src/plugin-api/define-plugin.ts +45 -0
  91. package/src/plugin-api/index.ts +56 -0
  92. package/src/plugin-api/oracle-plugin.ts +145 -0
  93. package/src/plugin-api/tool-helper.ts +82 -0
  94. package/src/plugin-api/types.ts +816 -0
  95. package/src/plugin-api/ucan-errors.ts +16 -0
  96. package/src/plugins/agui/agui-agent.ts +115 -0
  97. package/src/plugins/agui/agui.plugin.ts +148 -0
  98. package/src/plugins/agui/index.ts +2 -0
  99. package/src/plugins/attachments/attachments.plugin.ts +54 -0
  100. package/src/plugins/attachments/index.ts +6 -0
  101. package/src/plugins/attachments/view-attachment-tool.ts +91 -0
  102. package/src/plugins/base64.ts +26 -0
  103. package/src/plugins/composio/composio-tools.ts +365 -0
  104. package/src/plugins/composio/composio-ucan.ts +43 -0
  105. package/src/plugins/composio/composio.plugin.ts +214 -0
  106. package/src/plugins/composio/index.ts +15 -0
  107. package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
  108. package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
  109. package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
  110. package/src/plugins/domain-indexer/index.ts +3 -0
  111. package/src/plugins/editor/blocknote-bridge.ts +120 -0
  112. package/src/plugins/editor/content-session.ts +331 -0
  113. package/src/plugins/editor/content-tools.ts +686 -0
  114. package/src/plugins/editor/document-model.ts +0 -0
  115. package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
  116. package/src/plugins/editor/editor-agent.ts +34 -0
  117. package/src/plugins/editor/editor-config.ts +64 -0
  118. package/src/plugins/editor/editor-mx.ts +128 -0
  119. package/src/plugins/editor/editor.plugin.ts +143 -0
  120. package/src/plugins/editor/failures.ts +154 -0
  121. package/src/plugins/editor/index.ts +31 -0
  122. package/src/plugins/editor/jsdom-shim.ts +55 -0
  123. package/src/plugins/editor/prompts.ts +59 -0
  124. package/src/plugins/editor/prop-policy.ts +147 -0
  125. package/src/plugins/editor/provider.ts +386 -0
  126. package/src/plugins/editor/room-membership.ts +95 -0
  127. package/src/plugins/editor/standalone-editor-tool.ts +200 -0
  128. package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
  129. package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
  130. package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
  131. package/src/plugins/firecrawl/index.ts +15 -0
  132. package/src/plugins/flows/action-metadata.ts +3505 -0
  133. package/src/plugins/flows/actions.ts +190 -0
  134. package/src/plugins/flows/edit.ts +314 -0
  135. package/src/plugins/flows/errors.ts +38 -0
  136. package/src/plugins/flows/explain.ts +43 -0
  137. package/src/plugins/flows/flow-doc.ts +111 -0
  138. package/src/plugins/flows/flows.plugin.ts +129 -0
  139. package/src/plugins/flows/forms.ts +330 -0
  140. package/src/plugins/flows/index.ts +2 -0
  141. package/src/plugins/flows/linkage.ts +147 -0
  142. package/src/plugins/flows/port-types.ts +53 -0
  143. package/src/plugins/flows/prompts.ts +65 -0
  144. package/src/plugins/flows/read.ts +392 -0
  145. package/src/plugins/flows/references.ts +66 -0
  146. package/src/plugins/flows/test-support.ts +165 -0
  147. package/src/plugins/flows/tools/authoring.ts +516 -0
  148. package/src/plugins/flows/tools/discovery.ts +119 -0
  149. package/src/plugins/flows/tools/forms.ts +142 -0
  150. package/src/plugins/flows/tools/inspect.ts +165 -0
  151. package/src/plugins/flows/tools/linkage.ts +117 -0
  152. package/src/plugins/flows/tools/settings.ts +184 -0
  153. package/src/plugins/flows/translator.ts +328 -0
  154. package/src/plugins/flows/types.ts +210 -0
  155. package/src/plugins/flows/ydoc-helpers.ts +543 -0
  156. package/src/plugins/index.ts +70 -0
  157. package/src/plugins/mcp-call-timeout.ts +38 -0
  158. package/src/plugins/mcp-tool-adapter.ts +87 -0
  159. package/src/plugins/memory/index.ts +17 -0
  160. package/src/plugins/memory/memory-tools.ts +463 -0
  161. package/src/plugins/memory/memory-ucan.ts +51 -0
  162. package/src/plugins/memory/memory.plugin.ts +122 -0
  163. package/src/plugins/memory/types.ts +147 -0
  164. package/src/plugins/portal/action-log.ts +34 -0
  165. package/src/plugins/portal/index.ts +2 -0
  166. package/src/plugins/portal/portal.plugin.ts +203 -0
  167. package/src/plugins/sandbox/index.ts +34 -0
  168. package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
  169. package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
  170. package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
  171. package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
  172. package/src/plugins/tasks/index.ts +17 -0
  173. package/src/plugins/tasks/manifest.ts +81 -0
  174. package/src/plugins/tasks/middleware.ts +219 -0
  175. package/src/plugins/tasks/tasks-tools.ts +414 -0
  176. package/src/plugins/tasks/tasks.plugin.ts +74 -0
  177. package/src/plugins/ucan-failure.ts +133 -0
  178. package/src/plugins/user-preferences/index.ts +22 -0
  179. package/src/plugins/user-preferences/schema.ts +31 -0
  180. package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
  181. package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
  182. package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
  183. package/src/plugins/vfs/index.ts +40 -0
  184. package/src/plugins/vfs/vfs-auth.ts +56 -0
  185. package/src/plugins/vfs/vfs-client.ts +655 -0
  186. package/src/plugins/vfs/vfs-content.ts +181 -0
  187. package/src/plugins/vfs/vfs-errors.ts +163 -0
  188. package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
  189. package/src/plugins/vfs/vfs-tools.ts +635 -0
  190. package/src/plugins/vfs/vfs.plugin.ts +201 -0
  191. package/src/realtime/frontend-call-registry.ts +163 -0
  192. package/src/realtime/realtime-endpoint.ts +619 -0
  193. package/src/realtime/session-socket-hub.ts +249 -0
  194. package/src/realtime/socket-io-codec.ts +187 -0
  195. package/src/secrets/jwe.ts +74 -0
  196. package/src/secrets/pin-cipher.ts +43 -0
  197. package/src/secrets/secrets-service.ts +319 -0
  198. package/src/shell/app.ts +564 -0
  199. package/src/shell/auth.ts +290 -0
  200. package/src/sqlite/blob-codec.ts +100 -0
  201. package/src/sqlite/blob-compactor.ts +146 -0
  202. package/src/sqlite/cache-config.ts +45 -0
  203. package/src/sqlite/database.ts +643 -0
  204. package/src/sqlite/do-vfs.ts +1485 -0
  205. package/src/sqlite/index.ts +61 -0
  206. package/src/sqlite/serialization.ts +370 -0
  207. package/src/sqlite/sessions-store.ts +311 -0
  208. package/src/sqlite/sqlite-saver.ts +919 -0
  209. package/src/sqlite/vacuum-policy.ts +77 -0
  210. package/src/sqlite/wa-sqlite-loader.ts +123 -0
  211. package/src/sqlite/wasm.d.ts +10 -0
  212. package/src/tasks/schedule.ts +174 -0
  213. package/src/tasks/scheduler.ts +753 -0
  214. package/src/tasks/spec.ts +125 -0
  215. package/src/tasks/store.ts +351 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * `sessions` table access — the Workers counterpart of the Node runtime's
3
+ * `SessionManagerService` SQL. Same table (created with the same DDL the Node
4
+ * runtime uses, so files round-trip between runtimes):
5
+ *
6
+ * sessions(session_id PK, title, last_updated_at, created_at, oracle_name,
7
+ * oracle_did, oracle_entity_did, last_processed_count,
8
+ * user_context, room_id, slack_thread_ts)
9
+ *
10
+ * Title generation and Matrix side effects are NOT here — those belong to the
11
+ * turn pipeline; this store only owns the rows.
12
+ */
13
+ import type { DoSqliteDatabase, SqlParam } from './database';
14
+
15
+ export interface SessionRecord {
16
+ sessionId: string;
17
+ title?: string;
18
+ lastUpdatedAt: string;
19
+ createdAt: string;
20
+ oracleName: string;
21
+ oracleDid: string;
22
+ oracleEntityDid: string;
23
+ lastProcessedCount?: number;
24
+ userContext?: Record<string, unknown>;
25
+ roomId?: string;
26
+ slackThreadTs?: string;
27
+ }
28
+
29
+ export interface CreateSessionInput {
30
+ sessionId: string;
31
+ oracleName: string;
32
+ oracleDid: string;
33
+ oracleEntityDid: string;
34
+ title?: string;
35
+ roomId?: string;
36
+ slackThreadTs?: string;
37
+ userContext?: Record<string, unknown>;
38
+ lastProcessedCount?: number;
39
+ }
40
+
41
+ export interface TouchSessionInput {
42
+ lastProcessedCount?: number;
43
+ /** Omit to keep the stored value (callers that don't own the Slack binding). */
44
+ slackThreadTs?: string;
45
+ }
46
+
47
+ export interface ListSessionsResult {
48
+ sessions: SessionRecord[];
49
+ total: number;
50
+ }
51
+
52
+ export const UNTITLED_SESSION = 'Untitled';
53
+
54
+ type SessionRow = {
55
+ session_id: string;
56
+ title: string | null;
57
+ last_updated_at: string;
58
+ created_at: string;
59
+ oracle_name: string;
60
+ oracle_did: string;
61
+ oracle_entity_did: string;
62
+ last_processed_count: number | null;
63
+ user_context: string | null;
64
+ room_id: string | null;
65
+ slack_thread_ts: string | null;
66
+ };
67
+
68
+ type SessionRowWithTotal = SessionRow & { total: number };
69
+
70
+ const SESSION_COLUMNS = `session_id, title, last_updated_at, created_at, oracle_name,
71
+ oracle_did, oracle_entity_did, last_processed_count, user_context, room_id, slack_thread_ts`;
72
+
73
+ function rowToSession(row: SessionRow): SessionRecord {
74
+ const session: SessionRecord = {
75
+ sessionId: row.session_id,
76
+ lastUpdatedAt: row.last_updated_at,
77
+ createdAt: row.created_at,
78
+ oracleName: row.oracle_name,
79
+ oracleDid: row.oracle_did,
80
+ oracleEntityDid: row.oracle_entity_did,
81
+ };
82
+ if (row.title !== null) session.title = row.title;
83
+ if (row.last_processed_count !== null)
84
+ session.lastProcessedCount = row.last_processed_count;
85
+ if (row.user_context !== null)
86
+ session.userContext = parseUserContext(row.user_context);
87
+ if (row.room_id !== null) session.roomId = row.room_id;
88
+ if (row.slack_thread_ts !== null) session.slackThreadTs = row.slack_thread_ts;
89
+ return session;
90
+ }
91
+
92
+ function isRecord(value: unknown): value is Record<string, unknown> {
93
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
94
+ }
95
+
96
+ function parseUserContext(json: string): Record<string, unknown> | undefined {
97
+ try {
98
+ const parsed: unknown = JSON.parse(json);
99
+ return isRecord(parsed) ? parsed : undefined;
100
+ } catch {
101
+ return undefined;
102
+ }
103
+ }
104
+
105
+ export class SessionsStore {
106
+ private setupPromise: Promise<void> | undefined;
107
+
108
+ constructor(readonly db: DoSqliteDatabase) {}
109
+
110
+ /** Create the table + index (same DDL as the Node runtime). Idempotent. */
111
+ setup(): Promise<void> {
112
+ this.setupPromise ??= this.doSetup().catch((error: unknown) => {
113
+ this.setupPromise = undefined;
114
+ throw error;
115
+ });
116
+ return this.setupPromise;
117
+ }
118
+
119
+ private async doSetup(): Promise<void> {
120
+ await this.db.run(`
121
+ CREATE TABLE IF NOT EXISTS sessions (
122
+ session_id TEXT PRIMARY KEY,
123
+ title TEXT,
124
+ last_updated_at TEXT NOT NULL,
125
+ created_at TEXT NOT NULL,
126
+ oracle_name TEXT NOT NULL,
127
+ oracle_did TEXT NOT NULL,
128
+ oracle_entity_did TEXT NOT NULL,
129
+ last_processed_count INTEGER,
130
+ user_context TEXT,
131
+ room_id TEXT,
132
+ slack_thread_ts TEXT
133
+ )`);
134
+ await this.db.run(
135
+ `CREATE INDEX IF NOT EXISTS idx_sessions_updated ON sessions(last_updated_at)`,
136
+ );
137
+ }
138
+
139
+ async createSession(input: CreateSessionInput): Promise<SessionRecord> {
140
+ await this.setup();
141
+ const now = new Date().toISOString();
142
+ const session: SessionRecord = {
143
+ sessionId: input.sessionId,
144
+ title: input.title ?? UNTITLED_SESSION,
145
+ lastUpdatedAt: now,
146
+ createdAt: now,
147
+ oracleName: input.oracleName,
148
+ oracleDid: input.oracleDid,
149
+ oracleEntityDid: input.oracleEntityDid,
150
+ };
151
+ if (input.lastProcessedCount !== undefined)
152
+ session.lastProcessedCount = input.lastProcessedCount;
153
+ if (input.userContext !== undefined)
154
+ session.userContext = input.userContext;
155
+ if (input.roomId !== undefined) session.roomId = input.roomId;
156
+ if (input.slackThreadTs !== undefined)
157
+ session.slackThreadTs = input.slackThreadTs;
158
+ await this.db.run(
159
+ `INSERT INTO sessions (${SESSION_COLUMNS}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
160
+ [
161
+ session.sessionId,
162
+ session.title ?? null,
163
+ session.lastUpdatedAt,
164
+ session.createdAt,
165
+ session.oracleName,
166
+ session.oracleDid,
167
+ session.oracleEntityDid,
168
+ session.lastProcessedCount ?? null,
169
+ session.userContext !== undefined
170
+ ? JSON.stringify(session.userContext)
171
+ : null,
172
+ session.roomId ?? null,
173
+ session.slackThreadTs ?? null,
174
+ ],
175
+ );
176
+ return session;
177
+ }
178
+
179
+ /**
180
+ * Newest first; `roomId` restricts to one room; `excludeIdPrefix` hides
181
+ * rows whose id starts with it (the host's task-run sessions, which the
182
+ * Node runtime likewise keeps out of the user's session list). `total`
183
+ * counts all matching rows.
184
+ */
185
+ async listSessions(
186
+ roomId?: string,
187
+ limit = 20,
188
+ offset = 0,
189
+ excludeIdPrefix?: string,
190
+ ): Promise<ListSessionsResult> {
191
+ await this.setup();
192
+ const clauses: string[] = [];
193
+ const params: SqlParam[] = [];
194
+ if (roomId !== undefined) {
195
+ clauses.push('room_id = ?');
196
+ params.push(roomId);
197
+ }
198
+ if (excludeIdPrefix !== undefined && excludeIdPrefix.length > 0) {
199
+ // substr() rather than LIKE: DO SQL rejects LIKE patterns.
200
+ clauses.push('substr(session_id, 1, ?) <> ?');
201
+ params.push(excludeIdPrefix.length, excludeIdPrefix);
202
+ }
203
+ const where = clauses.length ? `WHERE ${clauses.join(' AND ')}` : '';
204
+ params.push(limit, offset);
205
+ const rows = await this.db.exec<SessionRowWithTotal>(
206
+ `SELECT ${SESSION_COLUMNS}, COUNT(*) OVER() AS total
207
+ FROM sessions ${where}
208
+ ORDER BY last_updated_at DESC, rowid DESC
209
+ LIMIT ? OFFSET ?`,
210
+ params,
211
+ );
212
+ return { sessions: rows.map(rowToSession), total: rows[0]?.total ?? 0 };
213
+ }
214
+
215
+ async getSession(sessionId: string): Promise<SessionRecord | undefined> {
216
+ await this.setup();
217
+ const row = await this.db.get<SessionRow>(
218
+ `SELECT ${SESSION_COLUMNS} FROM sessions WHERE session_id = ?`,
219
+ [sessionId],
220
+ );
221
+ return row === undefined ? undefined : rowToSession(row);
222
+ }
223
+
224
+ /** Bump `last_updated_at` (and optionally the processed count / Slack thread). Returns the updated row. */
225
+ /**
226
+ * Advance the history-indexing watermark WITHOUT bumping `last_updated_at`
227
+ * (which `touchSession` does and which would reorder the session list).
228
+ * The same `last_processed_count` column the Node runtime keeps, so a
229
+ * migrated file continues where it left off in either direction.
230
+ */
231
+ async setLastProcessedCount(sessionId: string, count: number): Promise<void> {
232
+ await this.setup();
233
+ await this.db.exec(
234
+ 'UPDATE sessions SET last_processed_count = ? WHERE session_id = ?',
235
+ [count, sessionId],
236
+ );
237
+ }
238
+
239
+ async touchSession(
240
+ sessionId: string,
241
+ patch: TouchSessionInput = {},
242
+ ): Promise<SessionRecord | undefined> {
243
+ await this.setup();
244
+ const now = new Date().toISOString();
245
+ const sets = ['last_updated_at = ?'];
246
+ const params: SqlParam[] = [now];
247
+ if (patch.lastProcessedCount !== undefined) {
248
+ sets.push('last_processed_count = ?');
249
+ params.push(patch.lastProcessedCount);
250
+ }
251
+ if (patch.slackThreadTs !== undefined) {
252
+ sets.push('slack_thread_ts = ?');
253
+ params.push(patch.slackThreadTs);
254
+ }
255
+ params.push(sessionId);
256
+ const { changes } = await this.db.run(
257
+ `UPDATE sessions SET ${sets.join(', ')} WHERE session_id = ?`,
258
+ params,
259
+ );
260
+ if (changes === 0) return undefined;
261
+ return this.getSession(sessionId);
262
+ }
263
+
264
+ /**
265
+ * Set the title. With `onlyIfUntitled`, the write is conditional on the row
266
+ * still holding the placeholder — the cross-process guard the Node runtime
267
+ * uses so a concurrent generation doesn't overwrite the winner's title.
268
+ * Returns whether a row changed.
269
+ */
270
+ async setTitle(
271
+ sessionId: string,
272
+ title: string,
273
+ options: { onlyIfUntitled?: boolean } = {},
274
+ ): Promise<boolean> {
275
+ await this.setup();
276
+ const guard = options.onlyIfUntitled
277
+ ? ` AND (title IS NULL OR trim(title) = '' OR lower(title) = '${UNTITLED_SESSION.toLowerCase()}')`
278
+ : '';
279
+ const { changes } = await this.db.run(
280
+ `UPDATE sessions SET title = ? WHERE session_id = ?${guard}`,
281
+ [title, sessionId],
282
+ );
283
+ return changes > 0;
284
+ }
285
+
286
+ /**
287
+ * Delete the session and — because the session id doubles as the LangGraph
288
+ * thread id — its checkpointer rows (`checkpoints`, `writes`, `messages`),
289
+ * in one transaction. The checkpointer tables are created on the first graph
290
+ * turn, so only the ones that exist are touched. Returns whether a session
291
+ * row existed.
292
+ */
293
+ async deleteSession(sessionId: string): Promise<boolean> {
294
+ await this.setup();
295
+ const tables = await this.db.exec<{ name: string }>(
296
+ `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('checkpoints', 'writes', 'messages')`,
297
+ );
298
+ return this.db.transaction(async () => {
299
+ const { changes } = await this.db.run(
300
+ 'DELETE FROM sessions WHERE session_id = ?',
301
+ [sessionId],
302
+ );
303
+ for (const { name } of tables) {
304
+ await this.db.run(`DELETE FROM ${name} WHERE thread_id = ?`, [
305
+ sessionId,
306
+ ]);
307
+ }
308
+ return changes > 0;
309
+ });
310
+ }
311
+ }