@clinebot/core 0.0.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 (200) hide show
  1. package/README.md +88 -0
  2. package/dist/account/cline-account-service.d.ts +34 -0
  3. package/dist/account/index.d.ts +3 -0
  4. package/dist/account/rpc.d.ts +38 -0
  5. package/dist/account/types.d.ts +74 -0
  6. package/dist/agents/agent-config-loader.d.ts +18 -0
  7. package/dist/agents/agent-config-parser.d.ts +25 -0
  8. package/dist/agents/hooks-config-loader.d.ts +23 -0
  9. package/dist/agents/index.d.ts +11 -0
  10. package/dist/agents/plugin-config-loader.d.ts +22 -0
  11. package/dist/agents/plugin-loader.d.ts +9 -0
  12. package/dist/agents/plugin-sandbox.d.ts +12 -0
  13. package/dist/agents/unified-config-file-watcher.d.ts +77 -0
  14. package/dist/agents/user-instruction-config-loader.d.ts +63 -0
  15. package/dist/auth/client.d.ts +11 -0
  16. package/dist/auth/cline.d.ts +41 -0
  17. package/dist/auth/codex.d.ts +39 -0
  18. package/dist/auth/oca.d.ts +22 -0
  19. package/dist/auth/server.d.ts +22 -0
  20. package/dist/auth/types.d.ts +72 -0
  21. package/dist/auth/utils.d.ts +32 -0
  22. package/dist/chat/chat-schema.d.ts +145 -0
  23. package/dist/default-tools/constants.d.ts +23 -0
  24. package/dist/default-tools/definitions.d.ts +96 -0
  25. package/dist/default-tools/executors/apply-patch-parser.d.ts +68 -0
  26. package/dist/default-tools/executors/apply-patch.d.ts +26 -0
  27. package/dist/default-tools/executors/bash.d.ts +49 -0
  28. package/dist/default-tools/executors/editor.d.ts +31 -0
  29. package/dist/default-tools/executors/file-read.d.ts +40 -0
  30. package/dist/default-tools/executors/index.d.ts +44 -0
  31. package/dist/default-tools/executors/search.d.ts +50 -0
  32. package/dist/default-tools/executors/web-fetch.d.ts +58 -0
  33. package/dist/default-tools/index.d.ts +57 -0
  34. package/dist/default-tools/presets.d.ts +124 -0
  35. package/dist/default-tools/schemas.d.ts +121 -0
  36. package/dist/default-tools/types.d.ts +237 -0
  37. package/dist/index.d.ts +23 -0
  38. package/dist/index.js +220 -0
  39. package/dist/input/file-indexer.d.ts +5 -0
  40. package/dist/input/index.d.ts +4 -0
  41. package/dist/input/mention-enricher.d.ts +12 -0
  42. package/dist/mcp/config-loader.d.ts +15 -0
  43. package/dist/mcp/index.d.ts +4 -0
  44. package/dist/mcp/manager.d.ts +24 -0
  45. package/dist/mcp/types.d.ts +66 -0
  46. package/dist/runtime/hook-file-hooks.d.ts +18 -0
  47. package/dist/runtime/rules.d.ts +5 -0
  48. package/dist/runtime/runtime-builder.d.ts +5 -0
  49. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +19 -0
  50. package/dist/runtime/session-runtime.d.ts +36 -0
  51. package/dist/runtime/tool-approval.d.ts +9 -0
  52. package/dist/runtime/workflows.d.ts +13 -0
  53. package/dist/server/index.d.ts +47 -0
  54. package/dist/server/index.js +641 -0
  55. package/dist/session/default-session-manager.d.ts +77 -0
  56. package/dist/session/rpc-session-service.d.ts +12 -0
  57. package/dist/session/runtime-oauth-token-manager.d.ts +28 -0
  58. package/dist/session/session-artifacts.d.ts +19 -0
  59. package/dist/session/session-graph.d.ts +15 -0
  60. package/dist/session/session-host.d.ts +21 -0
  61. package/dist/session/session-manager.d.ts +50 -0
  62. package/dist/session/session-manifest.d.ts +30 -0
  63. package/dist/session/session-service.d.ts +113 -0
  64. package/dist/session/sqlite-rpc-session-backend.d.ts +30 -0
  65. package/dist/session/unified-session-persistence-service.d.ts +93 -0
  66. package/dist/session/workspace-manager.d.ts +28 -0
  67. package/dist/session/workspace-manifest.d.ts +25 -0
  68. package/dist/storage/provider-settings-legacy-migration.d.ts +13 -0
  69. package/dist/storage/provider-settings-manager.d.ts +20 -0
  70. package/dist/storage/sqlite-session-store.d.ts +29 -0
  71. package/dist/storage/sqlite-team-store.d.ts +31 -0
  72. package/dist/storage/team-store.d.ts +2 -0
  73. package/dist/team/index.d.ts +1 -0
  74. package/dist/team/projections.d.ts +8 -0
  75. package/dist/types/common.d.ts +10 -0
  76. package/dist/types/config.d.ts +37 -0
  77. package/dist/types/events.d.ts +54 -0
  78. package/dist/types/provider-settings.d.ts +20 -0
  79. package/dist/types/sessions.d.ts +9 -0
  80. package/dist/types/storage.d.ts +37 -0
  81. package/dist/types/workspace.d.ts +7 -0
  82. package/dist/types.d.ts +26 -0
  83. package/package.json +63 -0
  84. package/src/account/cline-account-service.test.ts +101 -0
  85. package/src/account/cline-account-service.ts +267 -0
  86. package/src/account/index.ts +20 -0
  87. package/src/account/rpc.test.ts +62 -0
  88. package/src/account/rpc.ts +172 -0
  89. package/src/account/types.ts +80 -0
  90. package/src/agents/agent-config-loader.test.ts +234 -0
  91. package/src/agents/agent-config-loader.ts +107 -0
  92. package/src/agents/agent-config-parser.ts +191 -0
  93. package/src/agents/hooks-config-loader.ts +97 -0
  94. package/src/agents/index.ts +84 -0
  95. package/src/agents/plugin-config-loader.test.ts +91 -0
  96. package/src/agents/plugin-config-loader.ts +160 -0
  97. package/src/agents/plugin-loader.test.ts +102 -0
  98. package/src/agents/plugin-loader.ts +105 -0
  99. package/src/agents/plugin-sandbox.test.ts +120 -0
  100. package/src/agents/plugin-sandbox.ts +471 -0
  101. package/src/agents/unified-config-file-watcher.test.ts +196 -0
  102. package/src/agents/unified-config-file-watcher.ts +483 -0
  103. package/src/agents/user-instruction-config-loader.test.ts +158 -0
  104. package/src/agents/user-instruction-config-loader.ts +438 -0
  105. package/src/auth/client.test.ts +40 -0
  106. package/src/auth/client.ts +25 -0
  107. package/src/auth/cline.test.ts +130 -0
  108. package/src/auth/cline.ts +414 -0
  109. package/src/auth/codex.test.ts +170 -0
  110. package/src/auth/codex.ts +466 -0
  111. package/src/auth/oca.test.ts +215 -0
  112. package/src/auth/oca.ts +546 -0
  113. package/src/auth/server.ts +216 -0
  114. package/src/auth/types.ts +78 -0
  115. package/src/auth/utils.test.ts +128 -0
  116. package/src/auth/utils.ts +247 -0
  117. package/src/chat/chat-schema.ts +82 -0
  118. package/src/default-tools/constants.ts +35 -0
  119. package/src/default-tools/definitions.test.ts +233 -0
  120. package/src/default-tools/definitions.ts +632 -0
  121. package/src/default-tools/executors/apply-patch-parser.ts +520 -0
  122. package/src/default-tools/executors/apply-patch.ts +359 -0
  123. package/src/default-tools/executors/bash.ts +205 -0
  124. package/src/default-tools/executors/editor.ts +231 -0
  125. package/src/default-tools/executors/file-read.test.ts +25 -0
  126. package/src/default-tools/executors/file-read.ts +94 -0
  127. package/src/default-tools/executors/index.ts +75 -0
  128. package/src/default-tools/executors/search.ts +278 -0
  129. package/src/default-tools/executors/web-fetch.ts +259 -0
  130. package/src/default-tools/index.ts +161 -0
  131. package/src/default-tools/presets.test.ts +63 -0
  132. package/src/default-tools/presets.ts +168 -0
  133. package/src/default-tools/schemas.ts +228 -0
  134. package/src/default-tools/types.ts +324 -0
  135. package/src/index.ts +119 -0
  136. package/src/input/file-indexer.d.ts +11 -0
  137. package/src/input/file-indexer.test.ts +87 -0
  138. package/src/input/file-indexer.ts +280 -0
  139. package/src/input/index.ts +7 -0
  140. package/src/input/mention-enricher.test.ts +82 -0
  141. package/src/input/mention-enricher.ts +119 -0
  142. package/src/mcp/config-loader.test.ts +238 -0
  143. package/src/mcp/config-loader.ts +219 -0
  144. package/src/mcp/index.ts +26 -0
  145. package/src/mcp/manager.test.ts +106 -0
  146. package/src/mcp/manager.ts +262 -0
  147. package/src/mcp/types.ts +88 -0
  148. package/src/runtime/hook-file-hooks.test.ts +106 -0
  149. package/src/runtime/hook-file-hooks.ts +736 -0
  150. package/src/runtime/index.ts +27 -0
  151. package/src/runtime/rules.ts +34 -0
  152. package/src/runtime/runtime-builder.team-persistence.test.ts +203 -0
  153. package/src/runtime/runtime-builder.test.ts +215 -0
  154. package/src/runtime/runtime-builder.ts +515 -0
  155. package/src/runtime/runtime-parity.test.ts +132 -0
  156. package/src/runtime/sandbox/subprocess-sandbox.ts +207 -0
  157. package/src/runtime/session-runtime.ts +44 -0
  158. package/src/runtime/tool-approval.ts +104 -0
  159. package/src/runtime/workflows.test.ts +119 -0
  160. package/src/runtime/workflows.ts +54 -0
  161. package/src/server/index.ts +282 -0
  162. package/src/session/default-session-manager.e2e.test.ts +354 -0
  163. package/src/session/default-session-manager.test.ts +816 -0
  164. package/src/session/default-session-manager.ts +1286 -0
  165. package/src/session/index.ts +37 -0
  166. package/src/session/rpc-session-service.ts +189 -0
  167. package/src/session/runtime-oauth-token-manager.test.ts +137 -0
  168. package/src/session/runtime-oauth-token-manager.ts +265 -0
  169. package/src/session/session-artifacts.ts +106 -0
  170. package/src/session/session-graph.ts +90 -0
  171. package/src/session/session-host.ts +190 -0
  172. package/src/session/session-manager.ts +56 -0
  173. package/src/session/session-manifest.ts +29 -0
  174. package/src/session/session-service.team-persistence.test.ts +48 -0
  175. package/src/session/session-service.ts +610 -0
  176. package/src/session/sqlite-rpc-session-backend.ts +303 -0
  177. package/src/session/unified-session-persistence-service.ts +781 -0
  178. package/src/session/workspace-manager.ts +98 -0
  179. package/src/session/workspace-manifest.ts +100 -0
  180. package/src/storage/artifact-store.ts +1 -0
  181. package/src/storage/index.ts +11 -0
  182. package/src/storage/provider-settings-legacy-migration.test.ts +175 -0
  183. package/src/storage/provider-settings-legacy-migration.ts +637 -0
  184. package/src/storage/provider-settings-manager.test.ts +111 -0
  185. package/src/storage/provider-settings-manager.ts +129 -0
  186. package/src/storage/session-store.ts +1 -0
  187. package/src/storage/sqlite-session-store.ts +270 -0
  188. package/src/storage/sqlite-team-store.ts +443 -0
  189. package/src/storage/team-store.ts +5 -0
  190. package/src/team/index.ts +4 -0
  191. package/src/team/projections.ts +285 -0
  192. package/src/types/common.ts +14 -0
  193. package/src/types/config.ts +64 -0
  194. package/src/types/events.ts +46 -0
  195. package/src/types/index.ts +24 -0
  196. package/src/types/provider-settings.ts +43 -0
  197. package/src/types/sessions.ts +16 -0
  198. package/src/types/storage.ts +64 -0
  199. package/src/types/workspace.ts +7 -0
  200. package/src/types.ts +127 -0
@@ -0,0 +1,111 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { ProviderSettingsManager } from "./provider-settings-manager";
6
+
7
+ describe("ProviderSettingsManager", () => {
8
+ const tempDirs: string[] = [];
9
+
10
+ afterEach(() => {
11
+ for (const dir of tempDirs.splice(0)) {
12
+ rmSync(dir, { recursive: true, force: true });
13
+ }
14
+ });
15
+
16
+ it("persists and restores provider settings", () => {
17
+ const tempDir = mkdtempSync(
18
+ path.join(os.tmpdir(), "core-provider-settings-"),
19
+ );
20
+ tempDirs.push(tempDir);
21
+ const filePath = path.join(tempDir, "provider-settings.json");
22
+ const manager = new ProviderSettingsManager({ filePath });
23
+
24
+ manager.saveProviderSettings(
25
+ {
26
+ provider: "anthropic",
27
+ model: "claude-sonnet-4-6",
28
+ apiKey: "test-key",
29
+ },
30
+ { setLastUsed: true },
31
+ );
32
+
33
+ const reloaded = new ProviderSettingsManager({ filePath });
34
+ expect(reloaded.getLastUsedProviderSettings()).toEqual({
35
+ provider: "anthropic",
36
+ model: "claude-sonnet-4-6",
37
+ apiKey: "test-key",
38
+ });
39
+ expect(reloaded.getProviderConfig("anthropic")?.providerId).toBe(
40
+ "anthropic",
41
+ );
42
+ expect(reloaded.getProviderConfig("anthropic")?.modelId).toBe(
43
+ "claude-sonnet-4-6",
44
+ );
45
+ expect(reloaded.read().providers.anthropic?.tokenSource).toBe("manual");
46
+ });
47
+
48
+ it("tracks provider-specific settings while preserving last-used provider", () => {
49
+ const tempDir = mkdtempSync(
50
+ path.join(os.tmpdir(), "core-provider-settings-"),
51
+ );
52
+ tempDirs.push(tempDir);
53
+ const filePath = path.join(tempDir, "provider-settings.json");
54
+ const manager = new ProviderSettingsManager({ filePath });
55
+
56
+ manager.saveProviderSettings({
57
+ provider: "anthropic",
58
+ model: "claude-sonnet-4-6",
59
+ });
60
+ manager.saveProviderSettings(
61
+ {
62
+ provider: "openai-native",
63
+ model: "gpt-5",
64
+ },
65
+ { setLastUsed: false },
66
+ );
67
+
68
+ expect(manager.getProviderSettings("anthropic")?.model).toBe(
69
+ "claude-sonnet-4-6",
70
+ );
71
+ expect(manager.getProviderSettings("openai-native")?.model).toBe("gpt-5");
72
+ expect(manager.getLastUsedProviderSettings()?.provider).toBe("anthropic");
73
+ expect(manager.read().providers["openai-native"]?.tokenSource).toBe(
74
+ "manual",
75
+ );
76
+ });
77
+
78
+ it("allows overriding token source metadata", () => {
79
+ const tempDir = mkdtempSync(
80
+ path.join(os.tmpdir(), "core-provider-settings-"),
81
+ );
82
+ tempDirs.push(tempDir);
83
+ const filePath = path.join(tempDir, "provider-settings.json");
84
+ const manager = new ProviderSettingsManager({ filePath });
85
+
86
+ manager.saveProviderSettings(
87
+ {
88
+ provider: "openai-codex",
89
+ apiKey: "oauth-token",
90
+ },
91
+ { tokenSource: "oauth" },
92
+ );
93
+
94
+ expect(manager.read().providers["openai-codex"]?.tokenSource).toBe("oauth");
95
+ });
96
+
97
+ it("ignores invalid persisted JSON and falls back to empty state", () => {
98
+ const tempDir = mkdtempSync(
99
+ path.join(os.tmpdir(), "core-provider-settings-"),
100
+ );
101
+ tempDirs.push(tempDir);
102
+ const filePath = path.join(tempDir, "provider-settings.json");
103
+ writeFileSync(filePath, "{ not-json", "utf8");
104
+
105
+ const manager = new ProviderSettingsManager({ filePath });
106
+ expect(manager.read()).toEqual({
107
+ version: 1,
108
+ providers: {},
109
+ });
110
+ });
111
+ });
@@ -0,0 +1,129 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { resolveProviderSettingsPath } from "@clinebot/shared/storage";
4
+ import {
5
+ emptyStoredProviderSettings,
6
+ type ProviderConfig,
7
+ type ProviderSettings,
8
+ ProviderSettingsSchema,
9
+ type ProviderTokenSource,
10
+ type StoredProviderSettings,
11
+ StoredProviderSettingsSchema,
12
+ toProviderConfig,
13
+ } from "../types/provider-settings";
14
+
15
+ function nowIso(): string {
16
+ return new Date().toISOString();
17
+ }
18
+
19
+ export interface ProviderSettingsManagerOptions {
20
+ filePath?: string;
21
+ }
22
+
23
+ export interface SaveProviderSettingsOptions {
24
+ setLastUsed?: boolean;
25
+ tokenSource?: ProviderTokenSource;
26
+ }
27
+
28
+ export class ProviderSettingsManager {
29
+ private readonly filePath: string;
30
+
31
+ constructor(options: ProviderSettingsManagerOptions = {}) {
32
+ this.filePath = options.filePath ?? resolveProviderSettingsPath();
33
+ }
34
+
35
+ getFilePath(): string {
36
+ return this.filePath;
37
+ }
38
+
39
+ read(): StoredProviderSettings {
40
+ if (!existsSync(this.filePath)) {
41
+ return emptyStoredProviderSettings();
42
+ }
43
+
44
+ try {
45
+ const raw = readFileSync(this.filePath, "utf8");
46
+ const parsed = JSON.parse(raw) as unknown;
47
+ const result = StoredProviderSettingsSchema.safeParse(parsed);
48
+ if (result.success) {
49
+ return result.data;
50
+ }
51
+ } catch {
52
+ // Invalid content falls back to a clean state.
53
+ }
54
+
55
+ return emptyStoredProviderSettings();
56
+ }
57
+
58
+ write(state: StoredProviderSettings): void {
59
+ const normalized = StoredProviderSettingsSchema.parse(state);
60
+ const dir = dirname(this.filePath);
61
+ if (!existsSync(dir)) {
62
+ mkdirSync(dir, { recursive: true });
63
+ }
64
+ writeFileSync(
65
+ this.filePath,
66
+ `${JSON.stringify(normalized, null, 2)}\n`,
67
+ "utf8",
68
+ );
69
+ }
70
+
71
+ saveProviderSettings(
72
+ settings: unknown,
73
+ options: SaveProviderSettingsOptions = {},
74
+ ): StoredProviderSettings {
75
+ const validatedSettings = ProviderSettingsSchema.parse(settings);
76
+ const previous = this.read();
77
+ const providerId = validatedSettings.provider;
78
+ const shouldSetLastUsed = options.setLastUsed !== false;
79
+ const previousEntry = previous.providers[providerId];
80
+ const tokenSource =
81
+ options.tokenSource ?? previousEntry?.tokenSource ?? "manual";
82
+ const next: StoredProviderSettings = {
83
+ ...previous,
84
+ providers: {
85
+ ...previous.providers,
86
+ [providerId]: {
87
+ settings: validatedSettings,
88
+ updatedAt: nowIso(),
89
+ tokenSource,
90
+ },
91
+ },
92
+ lastUsedProvider: shouldSetLastUsed
93
+ ? providerId
94
+ : previous.lastUsedProvider,
95
+ };
96
+ this.write(next);
97
+ return next;
98
+ }
99
+
100
+ getProviderSettings(providerId: string): ProviderSettings | undefined {
101
+ const state = this.read();
102
+ return state.providers[providerId]?.settings;
103
+ }
104
+
105
+ getLastUsedProviderSettings(): ProviderSettings | undefined {
106
+ const state = this.read();
107
+ const providerId = state.lastUsedProvider;
108
+ if (!providerId) {
109
+ return undefined;
110
+ }
111
+ return state.providers[providerId]?.settings;
112
+ }
113
+
114
+ getProviderConfig(providerId: string): ProviderConfig | undefined {
115
+ const settings = this.getProviderSettings(providerId);
116
+ if (!settings) {
117
+ return undefined;
118
+ }
119
+ return toProviderConfig(settings);
120
+ }
121
+
122
+ getLastUsedProviderConfig(): ProviderConfig | undefined {
123
+ const settings = this.getLastUsedProviderSettings();
124
+ if (!settings) {
125
+ return undefined;
126
+ }
127
+ return toProviderConfig(settings);
128
+ }
129
+ }
@@ -0,0 +1 @@
1
+ export type { SessionStore } from "../types/storage";
@@ -0,0 +1,270 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import {
4
+ asBool,
5
+ asOptionalString,
6
+ asString,
7
+ ensureSessionSchema,
8
+ loadSqliteDb,
9
+ nowIso,
10
+ type SqliteDb,
11
+ toBoolInt,
12
+ } from "@clinebot/shared/db";
13
+ import { resolveSessionDataDir } from "@clinebot/shared/storage";
14
+ import type { SessionStatus } from "../types/common";
15
+ import type { SessionRecord } from "../types/sessions";
16
+ import type { SessionStore } from "../types/storage";
17
+
18
+ function defaultSessionsDir(): string {
19
+ return resolveSessionDataDir();
20
+ }
21
+
22
+ export interface SqliteSessionStoreOptions {
23
+ sessionsDir?: string;
24
+ }
25
+
26
+ export class SqliteSessionStore implements SessionStore {
27
+ private readonly sessionsDirPath: string;
28
+ private db: SqliteDb | undefined;
29
+
30
+ constructor(options: SqliteSessionStoreOptions = {}) {
31
+ this.sessionsDirPath = options.sessionsDir ?? defaultSessionsDir();
32
+ }
33
+
34
+ init(): void {
35
+ this.getRawDb();
36
+ }
37
+
38
+ ensureSessionsDir(): string {
39
+ if (!existsSync(this.sessionsDirPath)) {
40
+ mkdirSync(this.sessionsDirPath, { recursive: true });
41
+ }
42
+ return this.sessionsDirPath;
43
+ }
44
+
45
+ sessionDbPath(): string {
46
+ return join(this.ensureSessionsDir(), "sessions.db");
47
+ }
48
+
49
+ getRawDb(): SqliteDb {
50
+ if (this.db) {
51
+ return this.db;
52
+ }
53
+ const db = loadSqliteDb(this.sessionDbPath());
54
+ ensureSessionSchema(db, { includeLegacyMigrations: true });
55
+
56
+ this.db = db;
57
+ return db;
58
+ }
59
+
60
+ run(sql: string, params: unknown[] = []): { changes?: number } {
61
+ return this.getRawDb()
62
+ .prepare(sql)
63
+ .run(...params);
64
+ }
65
+
66
+ queryOne<T>(sql: string, params: unknown[] = []): T | undefined {
67
+ const row = this.getRawDb()
68
+ .prepare(sql)
69
+ .get(...params);
70
+ return (row as T | null) ?? undefined;
71
+ }
72
+
73
+ queryAll<T>(sql: string, params: unknown[] = []): T[] {
74
+ return this.getRawDb()
75
+ .prepare(sql)
76
+ .all(...params) as T[];
77
+ }
78
+
79
+ create(record: SessionRecord): void {
80
+ const now = nowIso();
81
+ this.run(
82
+ `INSERT OR REPLACE INTO sessions (
83
+ session_id, source, pid, started_at, ended_at, exit_code, status, status_lock, interactive,
84
+ provider, model, cwd, workspace_root, team_name, enable_tools, enable_spawn, enable_teams,
85
+ parent_session_id, parent_agent_id, agent_id, conversation_id, is_subagent, prompt,
86
+ metadata_json, transcript_path, hook_path, messages_path, updated_at
87
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
88
+ [
89
+ record.sessionId,
90
+ record.source,
91
+ record.pid,
92
+ record.startedAt,
93
+ record.endedAt ?? null,
94
+ record.exitCode ?? null,
95
+ record.status,
96
+ 0,
97
+ toBoolInt(record.interactive),
98
+ record.provider,
99
+ record.model,
100
+ record.cwd,
101
+ record.workspaceRoot,
102
+ record.teamName ?? null,
103
+ toBoolInt(record.enableTools),
104
+ toBoolInt(record.enableSpawn),
105
+ toBoolInt(record.enableTeams),
106
+ record.parentSessionId ?? null,
107
+ record.parentAgentId ?? null,
108
+ record.agentId ?? null,
109
+ record.conversationId ?? null,
110
+ toBoolInt(record.isSubagent),
111
+ record.prompt ?? null,
112
+ record.metadata ? JSON.stringify(record.metadata) : null,
113
+ record.transcriptPath ?? "",
114
+ record.hookPath ?? "",
115
+ record.messagesPath ?? null,
116
+ now,
117
+ ],
118
+ );
119
+ }
120
+
121
+ update(record: Partial<SessionRecord> & { sessionId: string }): void {
122
+ const fields: string[] = [];
123
+ const params: unknown[] = [];
124
+ if (record.endedAt !== undefined) {
125
+ fields.push("ended_at = ?");
126
+ params.push(record.endedAt);
127
+ }
128
+ if (record.exitCode !== undefined) {
129
+ fields.push("exit_code = ?");
130
+ params.push(record.exitCode);
131
+ }
132
+ if (record.status !== undefined) {
133
+ fields.push("status = ?");
134
+ params.push(record.status);
135
+ }
136
+ if (record.prompt !== undefined) {
137
+ fields.push("prompt = ?");
138
+ params.push(record.prompt);
139
+ }
140
+ if (record.metadata !== undefined) {
141
+ fields.push("metadata_json = ?");
142
+ params.push(record.metadata ? JSON.stringify(record.metadata) : null);
143
+ }
144
+ if (record.parentSessionId !== undefined) {
145
+ fields.push("parent_session_id = ?");
146
+ params.push(record.parentSessionId);
147
+ }
148
+ if (record.parentAgentId !== undefined) {
149
+ fields.push("parent_agent_id = ?");
150
+ params.push(record.parentAgentId);
151
+ }
152
+ if (record.agentId !== undefined) {
153
+ fields.push("agent_id = ?");
154
+ params.push(record.agentId);
155
+ }
156
+ if (record.conversationId !== undefined) {
157
+ fields.push("conversation_id = ?");
158
+ params.push(record.conversationId);
159
+ }
160
+ if (fields.length === 0) {
161
+ return;
162
+ }
163
+ fields.push("updated_at = ?");
164
+ params.push(nowIso());
165
+ params.push(record.sessionId);
166
+ this.run(
167
+ `UPDATE sessions SET ${fields.join(", ")} WHERE session_id = ?`,
168
+ params,
169
+ );
170
+ }
171
+
172
+ updateStatus(
173
+ sessionId: string,
174
+ status: SessionStatus,
175
+ exitCode?: number | null,
176
+ ): void {
177
+ this.update({
178
+ sessionId,
179
+ status,
180
+ endedAt: status === "running" ? null : nowIso(),
181
+ exitCode:
182
+ status === "running"
183
+ ? null
184
+ : (exitCode ?? (status === "failed" ? 1 : 0)),
185
+ });
186
+ }
187
+
188
+ get(sessionId: string): SessionRecord | undefined {
189
+ const row = this.queryOne<Record<string, unknown>>(
190
+ `SELECT session_id, source, pid, started_at, ended_at, exit_code, status, interactive,
191
+ provider, model, cwd, workspace_root, team_name,
192
+ enable_tools, enable_spawn, enable_teams,
193
+ parent_session_id, parent_agent_id, agent_id, conversation_id, is_subagent,
194
+ prompt, metadata_json, transcript_path, hook_path, messages_path, updated_at
195
+ FROM sessions WHERE session_id = ?`,
196
+ [sessionId],
197
+ );
198
+ if (!row) {
199
+ return undefined;
200
+ }
201
+ return {
202
+ sessionId: asString(row.session_id),
203
+ source: asString(row.source) as SessionRecord["source"],
204
+ pid: Number(row.pid ?? 0),
205
+ startedAt: asString(row.started_at),
206
+ endedAt: (row.ended_at as string | null | undefined) ?? null,
207
+ exitCode: (row.exit_code as number | null | undefined) ?? null,
208
+ status: asString(row.status) as SessionRecord["status"],
209
+ interactive: asBool(row.interactive),
210
+ provider: asString(row.provider),
211
+ model: asString(row.model),
212
+ cwd: asString(row.cwd),
213
+ workspaceRoot: asString(row.workspace_root),
214
+ teamName: asOptionalString(row.team_name),
215
+ enableTools: asBool(row.enable_tools),
216
+ enableSpawn: asBool(row.enable_spawn),
217
+ enableTeams: asBool(row.enable_teams),
218
+ parentSessionId: asOptionalString(row.parent_session_id),
219
+ parentAgentId: asOptionalString(row.parent_agent_id),
220
+ agentId: asOptionalString(row.agent_id),
221
+ conversationId: asOptionalString(row.conversation_id),
222
+ isSubagent: asBool(row.is_subagent),
223
+ prompt: asOptionalString(row.prompt),
224
+ metadata: (() => {
225
+ const raw = asOptionalString(row.metadata_json);
226
+ if (!raw) {
227
+ return undefined;
228
+ }
229
+ try {
230
+ const parsed = JSON.parse(raw) as unknown;
231
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
232
+ return parsed as Record<string, unknown>;
233
+ }
234
+ } catch {
235
+ // Ignore malformed metadata payloads.
236
+ }
237
+ return undefined;
238
+ })(),
239
+ transcriptPath: asOptionalString(row.transcript_path),
240
+ hookPath: asOptionalString(row.hook_path),
241
+ messagesPath: asOptionalString(row.messages_path),
242
+ updatedAt: asOptionalString(row.updated_at) ?? nowIso(),
243
+ };
244
+ }
245
+
246
+ list(limit = 200): SessionRecord[] {
247
+ const rows = this.queryAll<Record<string, unknown>>(
248
+ `SELECT session_id FROM sessions ORDER BY started_at DESC LIMIT ?`,
249
+ [limit],
250
+ );
251
+ const result: SessionRecord[] = [];
252
+ for (const row of rows) {
253
+ const item = this.get(asString(row.session_id));
254
+ if (item) {
255
+ result.push(item);
256
+ }
257
+ }
258
+ return result;
259
+ }
260
+
261
+ delete(sessionId: string, cascade = false): boolean {
262
+ const changed =
263
+ this.run(`DELETE FROM sessions WHERE session_id = ?`, [sessionId])
264
+ .changes ?? 0;
265
+ if (cascade) {
266
+ this.run(`DELETE FROM sessions WHERE parent_session_id = ?`, [sessionId]);
267
+ }
268
+ return changed > 0;
269
+ }
270
+ }