c0de-agent 0.0.0 → 1.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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +101 -0
  3. package/dist/cli/commands/acp.d.ts +19 -0
  4. package/dist/cli/commands/acp.js +50 -0
  5. package/dist/cli/commands/chat.d.ts +13 -0
  6. package/dist/cli/commands/chat.js +26 -0
  7. package/dist/cli/commands/config.d.ts +17 -0
  8. package/dist/cli/commands/config.js +77 -0
  9. package/dist/cli/commands/init.d.ts +9 -0
  10. package/dist/cli/commands/init.js +14 -0
  11. package/dist/cli/commands/plugin.d.ts +11 -0
  12. package/dist/cli/commands/plugin.js +31 -0
  13. package/dist/cli/commands/serve.d.ts +18 -0
  14. package/dist/cli/commands/serve.js +35 -0
  15. package/dist/cli/commands/update.d.ts +24 -0
  16. package/dist/cli/commands/update.js +40 -0
  17. package/dist/cli/deps.d.ts +19 -0
  18. package/dist/cli/deps.js +57 -0
  19. package/dist/cli/index.d.ts +8 -0
  20. package/dist/cli/index.js +149 -0
  21. package/dist/cli/modes/acp.d.ts +18 -0
  22. package/dist/cli/modes/acp.js +57 -0
  23. package/dist/cli/modes/print.d.ts +16 -0
  24. package/dist/cli/modes/print.js +30 -0
  25. package/dist/cli/parser.d.ts +19 -0
  26. package/dist/cli/parser.js +28 -0
  27. package/dist/cli/utils/output.d.ts +11 -0
  28. package/dist/cli/utils/output.js +23 -0
  29. package/dist/cli/utils/prompt.d.ts +12 -0
  30. package/dist/cli/utils/prompt.js +18 -0
  31. package/dist/core/agent.d.ts +12 -0
  32. package/dist/core/agent.js +95 -0
  33. package/dist/core/agents/builtin.d.ts +6 -0
  34. package/dist/core/agents/builtin.js +76 -0
  35. package/dist/core/agents/discovery.d.ts +6 -0
  36. package/dist/core/agents/discovery.js +96 -0
  37. package/dist/core/agents/index.d.ts +6 -0
  38. package/dist/core/agents/index.js +4 -0
  39. package/dist/core/agents/parallel.d.ts +13 -0
  40. package/dist/core/agents/parallel.js +37 -0
  41. package/dist/core/agents/registry.d.ts +4 -0
  42. package/dist/core/agents/registry.js +23 -0
  43. package/dist/core/agents/types.d.ts +37 -0
  44. package/dist/core/agents/types.js +1 -0
  45. package/dist/core/compact.d.ts +11 -0
  46. package/dist/core/compact.js +31 -0
  47. package/dist/core/config.d.ts +7 -0
  48. package/dist/core/config.js +84 -0
  49. package/dist/core/context.d.ts +23 -0
  50. package/dist/core/context.js +93 -0
  51. package/dist/core/index.d.ts +13 -0
  52. package/dist/core/index.js +11 -0
  53. package/dist/core/loop.d.ts +25 -0
  54. package/dist/core/loop.js +651 -0
  55. package/dist/core/metrics.d.ts +27 -0
  56. package/dist/core/metrics.js +94 -0
  57. package/dist/core/prompt-registry.d.ts +21 -0
  58. package/dist/core/prompt-registry.js +200 -0
  59. package/dist/core/prompt.d.ts +11 -0
  60. package/dist/core/prompt.js +13 -0
  61. package/dist/core/secret.d.ts +12 -0
  62. package/dist/core/secret.js +51 -0
  63. package/dist/core/slash.d.ts +15 -0
  64. package/dist/core/slash.js +114 -0
  65. package/dist/core/steering.d.ts +8 -0
  66. package/dist/core/steering.js +15 -0
  67. package/dist/core/title.d.ts +42 -0
  68. package/dist/core/title.js +115 -0
  69. package/dist/core/tool-exec.d.ts +20 -0
  70. package/dist/core/tool-exec.js +64 -0
  71. package/dist/core/types.d.ts +95 -0
  72. package/dist/core/types.js +1 -0
  73. package/dist/core/worktree.d.ts +24 -0
  74. package/dist/core/worktree.js +68 -0
  75. package/dist/dap/index.d.ts +6 -0
  76. package/dist/dap/index.js +4 -0
  77. package/dist/dap/protocol.d.ts +40 -0
  78. package/dist/dap/protocol.js +132 -0
  79. package/dist/dap/session.d.ts +23 -0
  80. package/dist/dap/session.js +130 -0
  81. package/dist/dap/transport.d.ts +8 -0
  82. package/dist/dap/transport.js +30 -0
  83. package/dist/dap/types.d.ts +50 -0
  84. package/dist/dap/types.js +2 -0
  85. package/dist/db/client.d.ts +22 -0
  86. package/dist/db/client.js +39 -0
  87. package/dist/db/index.d.ts +6 -0
  88. package/dist/db/index.js +4 -0
  89. package/dist/db/migrate.d.ts +9 -0
  90. package/dist/db/migrate.js +15 -0
  91. package/dist/db/schema.d.ts +965 -0
  92. package/dist/db/schema.js +112 -0
  93. package/dist/db/types.d.ts +11 -0
  94. package/dist/db/types.js +1 -0
  95. package/dist/llm/index.d.ts +13 -0
  96. package/dist/llm/index.js +8 -0
  97. package/dist/llm/protocols/openai-compat.d.ts +139 -0
  98. package/dist/llm/protocols/openai-compat.js +214 -0
  99. package/dist/llm/protocols/utils/index.d.ts +2 -0
  100. package/dist/llm/protocols/utils/index.js +2 -0
  101. package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
  102. package/dist/llm/protocols/utils/lifecycle.js +46 -0
  103. package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
  104. package/dist/llm/protocols/utils/tool-stream.js +94 -0
  105. package/dist/llm/provider-error.d.ts +5 -0
  106. package/dist/llm/provider-error.js +33 -0
  107. package/dist/llm/provider.d.ts +32 -0
  108. package/dist/llm/provider.js +191 -0
  109. package/dist/llm/registry.d.ts +44 -0
  110. package/dist/llm/registry.js +107 -0
  111. package/dist/llm/retry.d.ts +37 -0
  112. package/dist/llm/retry.js +82 -0
  113. package/dist/llm/routing.d.ts +36 -0
  114. package/dist/llm/routing.js +53 -0
  115. package/dist/llm/schema/errors.d.ts +88 -0
  116. package/dist/llm/schema/errors.js +33 -0
  117. package/dist/llm/schema/events.d.ts +135 -0
  118. package/dist/llm/schema/events.js +35 -0
  119. package/dist/llm/schema/ids.d.ts +22 -0
  120. package/dist/llm/schema/ids.js +3 -0
  121. package/dist/llm/schema/index.d.ts +5 -0
  122. package/dist/llm/schema/index.js +5 -0
  123. package/dist/llm/schema/messages.d.ts +97 -0
  124. package/dist/llm/schema/messages.js +18 -0
  125. package/dist/llm/schema/options.d.ts +54 -0
  126. package/dist/llm/schema/options.js +54 -0
  127. package/dist/llm/token.d.ts +2 -0
  128. package/dist/llm/token.js +13 -0
  129. package/dist/llm/transport.d.ts +31 -0
  130. package/dist/llm/transport.js +165 -0
  131. package/dist/mcp/index.d.ts +1 -0
  132. package/dist/mcp/index.js +3 -0
  133. package/dist/plugins/builtin.d.ts +6 -0
  134. package/dist/plugins/builtin.js +54 -0
  135. package/dist/plugins/hooks.d.ts +3 -0
  136. package/dist/plugins/hooks.js +79 -0
  137. package/dist/plugins/index.d.ts +9 -0
  138. package/dist/plugins/index.js +8 -0
  139. package/dist/plugins/init.d.ts +23 -0
  140. package/dist/plugins/init.js +30 -0
  141. package/dist/plugins/lifecycle.d.ts +6 -0
  142. package/dist/plugins/lifecycle.js +74 -0
  143. package/dist/plugins/loader.d.ts +16 -0
  144. package/dist/plugins/loader.js +68 -0
  145. package/dist/plugins/logger.d.ts +4 -0
  146. package/dist/plugins/logger.js +31 -0
  147. package/dist/plugins/registry.d.ts +7 -0
  148. package/dist/plugins/registry.js +24 -0
  149. package/dist/plugins/types.d.ts +113 -0
  150. package/dist/plugins/types.js +1 -0
  151. package/dist/project/detect.d.ts +4 -0
  152. package/dist/project/detect.js +131 -0
  153. package/dist/project/index.d.ts +4 -0
  154. package/dist/project/index.js +2 -0
  155. package/dist/project/project.d.ts +23 -0
  156. package/dist/project/project.js +87 -0
  157. package/dist/project/resolve.d.ts +8 -0
  158. package/dist/project/resolve.js +81 -0
  159. package/dist/server/agent-manager.d.ts +32 -0
  160. package/dist/server/agent-manager.js +54 -0
  161. package/dist/server/app.d.ts +5 -0
  162. package/dist/server/app.js +75 -0
  163. package/dist/server/context.d.ts +20 -0
  164. package/dist/server/context.js +47 -0
  165. package/dist/server/dev.d.ts +11 -0
  166. package/dist/server/dev.js +83 -0
  167. package/dist/server/index.d.ts +11 -0
  168. package/dist/server/index.js +7 -0
  169. package/dist/server/middleware/auth.d.ts +15 -0
  170. package/dist/server/middleware/auth.js +29 -0
  171. package/dist/server/middleware/cors.d.ts +16 -0
  172. package/dist/server/middleware/cors.js +44 -0
  173. package/dist/server/middleware/error.d.ts +15 -0
  174. package/dist/server/middleware/error.js +11 -0
  175. package/dist/server/permission/interactive.d.ts +24 -0
  176. package/dist/server/permission/interactive.js +56 -0
  177. package/dist/server/permission/store.d.ts +30 -0
  178. package/dist/server/permission/store.js +47 -0
  179. package/dist/server/routes/agent.d.ts +4 -0
  180. package/dist/server/routes/agent.js +19 -0
  181. package/dist/server/routes/catalog.d.ts +42 -0
  182. package/dist/server/routes/catalog.js +194 -0
  183. package/dist/server/routes/chat.d.ts +8 -0
  184. package/dist/server/routes/chat.js +282 -0
  185. package/dist/server/routes/commands.d.ts +5 -0
  186. package/dist/server/routes/commands.js +17 -0
  187. package/dist/server/routes/config.d.ts +4 -0
  188. package/dist/server/routes/config.js +27 -0
  189. package/dist/server/routes/files.d.ts +4 -0
  190. package/dist/server/routes/files.js +183 -0
  191. package/dist/server/routes/filesystem.d.ts +29 -0
  192. package/dist/server/routes/filesystem.js +108 -0
  193. package/dist/server/routes/health.d.ts +3 -0
  194. package/dist/server/routes/health.js +13 -0
  195. package/dist/server/routes/permissions.d.ts +5 -0
  196. package/dist/server/routes/permissions.js +23 -0
  197. package/dist/server/routes/project.d.ts +4 -0
  198. package/dist/server/routes/project.js +51 -0
  199. package/dist/server/routes/provider.d.ts +16 -0
  200. package/dist/server/routes/provider.js +66 -0
  201. package/dist/server/routes/session.d.ts +4 -0
  202. package/dist/server/routes/session.js +130 -0
  203. package/dist/server/routes/tool.d.ts +4 -0
  204. package/dist/server/routes/tool.js +28 -0
  205. package/dist/server/routes/update.d.ts +14 -0
  206. package/dist/server/routes/update.js +49 -0
  207. package/dist/server/server.d.ts +43 -0
  208. package/dist/server/server.js +197 -0
  209. package/dist/server/types.d.ts +73 -0
  210. package/dist/server/types.js +2 -0
  211. package/dist/server/util/safe-path.d.ts +7 -0
  212. package/dist/server/util/safe-path.js +7 -0
  213. package/dist/session/archive.d.ts +15 -0
  214. package/dist/session/archive.js +129 -0
  215. package/dist/session/branch.d.ts +9 -0
  216. package/dist/session/branch.js +63 -0
  217. package/dist/session/compaction.d.ts +27 -0
  218. package/dist/session/compaction.js +192 -0
  219. package/dist/session/context.d.ts +16 -0
  220. package/dist/session/context.js +173 -0
  221. package/dist/session/index.d.ts +10 -0
  222. package/dist/session/index.js +10 -0
  223. package/dist/session/message.d.ts +22 -0
  224. package/dist/session/message.js +145 -0
  225. package/dist/session/session.d.ts +37 -0
  226. package/dist/session/session.js +144 -0
  227. package/dist/session/snapshot.d.ts +11 -0
  228. package/dist/session/snapshot.js +60 -0
  229. package/dist/session/squash.d.ts +9 -0
  230. package/dist/session/squash.js +88 -0
  231. package/dist/session/token.d.ts +10 -0
  232. package/dist/session/token.js +33 -0
  233. package/dist/session/types.d.ts +122 -0
  234. package/dist/session/types.js +1 -0
  235. package/dist/shared/index.d.ts +2 -0
  236. package/dist/shared/index.js +1 -0
  237. package/dist/shared/types/agent.d.ts +202 -0
  238. package/dist/shared/types/agent.js +1 -0
  239. package/dist/shared/types/base.d.ts +32 -0
  240. package/dist/shared/types/base.js +1 -0
  241. package/dist/shared/types/config.d.ts +104 -0
  242. package/dist/shared/types/config.js +1 -0
  243. package/dist/shared/types/index.d.ts +6 -0
  244. package/dist/shared/types/index.js +1 -0
  245. package/dist/shared/types/llm.d.ts +122 -0
  246. package/dist/shared/types/llm.js +1 -0
  247. package/dist/shared/types/message.d.ts +71 -0
  248. package/dist/shared/types/message.js +1 -0
  249. package/dist/shared/types/tool.d.ts +134 -0
  250. package/dist/shared/types/tool.js +1 -0
  251. package/dist/shared/utils/path.d.ts +15 -0
  252. package/dist/shared/utils/path.js +23 -0
  253. package/dist/shared/utils.d.ts +5 -0
  254. package/dist/shared/utils.js +10 -0
  255. package/dist/tools/builtin/bash.d.ts +12 -0
  256. package/dist/tools/builtin/bash.js +121 -0
  257. package/dist/tools/builtin/dap.d.ts +23 -0
  258. package/dist/tools/builtin/dap.js +231 -0
  259. package/dist/tools/builtin/edit.d.ts +12 -0
  260. package/dist/tools/builtin/edit.js +151 -0
  261. package/dist/tools/builtin/glob.d.ts +11 -0
  262. package/dist/tools/builtin/glob.js +120 -0
  263. package/dist/tools/builtin/grep.d.ts +6 -0
  264. package/dist/tools/builtin/grep.js +141 -0
  265. package/dist/tools/builtin/read.d.ts +6 -0
  266. package/dist/tools/builtin/read.js +73 -0
  267. package/dist/tools/builtin/resolvers.d.ts +12 -0
  268. package/dist/tools/builtin/resolvers.js +79 -0
  269. package/dist/tools/builtin/task.d.ts +9 -0
  270. package/dist/tools/builtin/task.js +100 -0
  271. package/dist/tools/builtin/write.d.ts +6 -0
  272. package/dist/tools/builtin/write.js +36 -0
  273. package/dist/tools/builtin/yield.d.ts +9 -0
  274. package/dist/tools/builtin/yield.js +40 -0
  275. package/dist/tools/executor.d.ts +9 -0
  276. package/dist/tools/executor.js +54 -0
  277. package/dist/tools/hashline/index.d.ts +2 -0
  278. package/dist/tools/hashline/index.js +3 -0
  279. package/dist/tools/hashline/patch.d.ts +48 -0
  280. package/dist/tools/hashline/patch.js +201 -0
  281. package/dist/tools/index.d.ts +30 -0
  282. package/dist/tools/index.js +55 -0
  283. package/dist/tools/permission.d.ts +13 -0
  284. package/dist/tools/permission.js +39 -0
  285. package/dist/tools/registry.d.ts +15 -0
  286. package/dist/tools/registry.js +54 -0
  287. package/dist/tools/resolver.d.ts +15 -0
  288. package/dist/tools/resolver.js +47 -0
  289. package/dist/tools/truncate.d.ts +8 -0
  290. package/dist/tools/truncate.js +50 -0
  291. package/dist/tools/types.d.ts +101 -0
  292. package/dist/tools/types.js +1 -0
  293. package/dist/tools/validate.d.ts +8 -0
  294. package/dist/tools/validate.js +130 -0
  295. package/dist/tools/websearch/fetch.d.ts +11 -0
  296. package/dist/tools/websearch/fetch.js +21 -0
  297. package/dist/tools/websearch/index.d.ts +19 -0
  298. package/dist/tools/websearch/index.js +78 -0
  299. package/dist/tools/websearch/providers/brave.d.ts +5 -0
  300. package/dist/tools/websearch/providers/brave.js +53 -0
  301. package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
  302. package/dist/tools/websearch/providers/duckduckgo.js +75 -0
  303. package/dist/tools/websearch/providers/tavily.d.ts +11 -0
  304. package/dist/tools/websearch/providers/tavily.js +89 -0
  305. package/dist/tools/websearch/types.d.ts +51 -0
  306. package/dist/tools/websearch/types.js +18 -0
  307. package/dist/tools/websearch/websearch.d.ts +10 -0
  308. package/dist/tools/websearch/websearch.js +48 -0
  309. package/dist/update/index.d.ts +10 -0
  310. package/dist/update/index.js +6 -0
  311. package/dist/update/ipc.d.ts +15 -0
  312. package/dist/update/ipc.js +41 -0
  313. package/dist/update/scheduler.d.ts +34 -0
  314. package/dist/update/scheduler.js +57 -0
  315. package/dist/update/snapshot.d.ts +39 -0
  316. package/dist/update/snapshot.js +99 -0
  317. package/dist/update/updater.d.ts +48 -0
  318. package/dist/update/updater.js +83 -0
  319. package/dist/update/version.d.ts +18 -0
  320. package/dist/update/version.js +49 -0
  321. package/drizzle/0000_magical_nightshade.sql +55 -0
  322. package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
  323. package/drizzle/0002_amusing_micromacro.sql +12 -0
  324. package/drizzle/0003_regular_purifiers.sql +2 -0
  325. package/drizzle/meta/0000_snapshot.json +448 -0
  326. package/drizzle/meta/0001_snapshot.json +539 -0
  327. package/drizzle/meta/0002_snapshot.json +633 -0
  328. package/drizzle/meta/0003_snapshot.json +645 -0
  329. package/drizzle/meta/_journal.json +34 -0
  330. package/package.json +100 -8
@@ -0,0 +1,27 @@
1
+ import type { DB } from '../db/client.js';
2
+ import type { Message } from '../shared/types/message.js';
3
+ import type { CompactionConfig, CompactionResult, HotFile, Summarizer } from './types.js';
4
+ /**
5
+ * Find a safe cut point: the index of the most recent 'user' message
6
+ * at or before `preferredCut`. Cutting at a user-turn boundary ensures
7
+ * we never split an assistant reply from its tool results.
8
+ */
9
+ declare function findSafeCutPoint(messages: Message[], preferredCut: number): number;
10
+ /** Extract frequently-accessed files (read ≥ 2 times) from message history. */
11
+ declare function extractHotFiles(messages: Message[]): HotFile[];
12
+ /** Build the LLM summarization prompt for a set of messages. */
13
+ declare function buildCompactionPrompt(messages: Message[]): string;
14
+ /**
15
+ * Compact a session: summarize old messages, archive them, keep recent ones.
16
+ *
17
+ * The keep window is driven by a token budget (`keepRecentTokens`), not a
18
+ * fixed message count — so compaction actually fires on a few long messages
19
+ * instead of silently no-op'ing when message count is small. The `summarizer`
20
+ * function is injected (the session layer never imports the LLM package).
21
+ *
22
+ * All destructive writes (archive + snapshot + delete + insert) run inside a
23
+ * single transaction so a mid-way failure cannot lose history: the originals
24
+ * are only deleted once the summary is durably stored.
25
+ */
26
+ declare function compactSession(handle: DB, sessionId: string, summarizer: Summarizer, config?: Partial<CompactionConfig>): Promise<CompactionResult>;
27
+ export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint };
@@ -0,0 +1,192 @@
1
+ import { generateId } from '../shared/index.js';
2
+ import { archiveOriginalEntries } from './archive.js';
3
+ import { deleteEntriesByIds, getMessages, insertEntry } from './message.js';
4
+ import { upsertFileSnapshot } from './snapshot.js';
5
+ import { estimateMessageTokens, estimateTokens } from './token.js';
6
+ /**
7
+ * Find a safe cut point: the index of the most recent 'user' message
8
+ * at or before `preferredCut`. Cutting at a user-turn boundary ensures
9
+ * we never split an assistant reply from its tool results.
10
+ */
11
+ function findSafeCutPoint(messages, preferredCut) {
12
+ const upper = Math.min(preferredCut, messages.length);
13
+ for (let i = upper; i >= 0; i--) {
14
+ const msg = messages[i];
15
+ if (msg && msg.role === 'user')
16
+ return i;
17
+ }
18
+ return 0;
19
+ }
20
+ /** Extract frequently-accessed files (read ≥ 2 times) from message history. */
21
+ function extractHotFiles(messages) {
22
+ const accessCount = new Map();
23
+ const callPath = new Map();
24
+ const latestContent = new Map();
25
+ for (const msg of messages) {
26
+ for (const part of msg.content) {
27
+ if (part._tag === 'tool_call' && part.tool === 'read') {
28
+ const input = part.input;
29
+ if (input?.path) {
30
+ accessCount.set(input.path, (accessCount.get(input.path) ?? 0) + 1);
31
+ callPath.set(part.id, input.path);
32
+ }
33
+ }
34
+ if (part._tag === 'tool_result' && part.tool === 'read') {
35
+ if (part.output._tag === 'success') {
36
+ const path = callPath.get(part.id);
37
+ if (path)
38
+ latestContent.set(path, part.output.output);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ const hot = [];
44
+ for (const [path, count] of accessCount) {
45
+ if (count >= 2) {
46
+ const content = latestContent.get(path);
47
+ if (content) {
48
+ hot.push({ path, content, tokenCount: estimateTokens(content), accessCount: count });
49
+ }
50
+ }
51
+ }
52
+ return hot.sort((a, b) => b.accessCount - a.accessCount).slice(0, 10);
53
+ }
54
+ /** Build the LLM summarization prompt for a set of messages. */
55
+ function buildCompactionPrompt(messages) {
56
+ const history = messages
57
+ .map((m) => `[${m.role}] ${m.content.map((p) => (p._tag === 'text' ? p.text : JSON.stringify(p))).join(' ')}`)
58
+ .join('\n');
59
+ return `将以下对话历史压缩为结构化摘要。保留关键信息,丢弃冗余细节。
60
+
61
+ ## Goal
62
+ 用户的目标是什么
63
+
64
+ ## Progress
65
+ 已完成的工作
66
+
67
+ ## Decisions
68
+ 做出的关键决策
69
+
70
+ ## Next Steps
71
+ 接下来要做什么
72
+
73
+ ## Critical Context
74
+ 必须记住的上下文(文件路径、变量名、错误信息等)
75
+
76
+ ## Modified Files
77
+ 修改过的文件列表及变更摘要
78
+
79
+ ---
80
+ 对话历史:
81
+ ${history}`;
82
+ }
83
+ /**
84
+ * Token cost of a single message, honoring a stored tokenCount when present.
85
+ * Mirrors core/context.ts rawMessageTokens without crossing the core→session
86
+ * layer boundary (core already imports session, so session must not import core).
87
+ */
88
+ function messageTokens(m) {
89
+ if (m.tokenCount > 0)
90
+ return m.tokenCount;
91
+ return estimateMessageTokens(m.content);
92
+ }
93
+ /**
94
+ * Find the start index of the token-budgeted keep window by reverse-walking
95
+ * from the newest message. This is the session-layer analogue of
96
+ * core/context.ts fitToBudget's keep-window logic: accumulate tokens from the
97
+ * end until `keepRecentTokens` is exceeded, then everything older is eligible
98
+ * for compaction. At least the most recent message is always retained.
99
+ */
100
+ function findKeepRecentStart(messages, keepRecentTokens) {
101
+ if (messages.length === 0)
102
+ return 0;
103
+ let used = 0;
104
+ let start = messages.length;
105
+ for (let i = messages.length - 1; i >= 0; i--) {
106
+ const m = messages[i];
107
+ if (!m)
108
+ continue;
109
+ const tc = messageTokens(m);
110
+ if (used + tc > keepRecentTokens)
111
+ break;
112
+ used += tc;
113
+ start = i;
114
+ }
115
+ // Always retain at least the most recent message.
116
+ return Math.min(start, messages.length - 1);
117
+ }
118
+ /**
119
+ * Compact a session: summarize old messages, archive them, keep recent ones.
120
+ *
121
+ * The keep window is driven by a token budget (`keepRecentTokens`), not a
122
+ * fixed message count — so compaction actually fires on a few long messages
123
+ * instead of silently no-op'ing when message count is small. The `summarizer`
124
+ * function is injected (the session layer never imports the LLM package).
125
+ *
126
+ * All destructive writes (archive + snapshot + delete + insert) run inside a
127
+ * single transaction so a mid-way failure cannot lose history: the originals
128
+ * are only deleted once the summary is durably stored.
129
+ */
130
+ async function compactSession(handle, sessionId, summarizer, config) {
131
+ const messages = await getMessages(handle, sessionId);
132
+ const keepRecentTokens = config?.keepRecentTokens ?? 4000;
133
+ if (messages.length === 0) {
134
+ return { compacted: false, reason: 'too_few_messages' };
135
+ }
136
+ // Reverse-walk from the newest message to find the keep window boundary,
137
+ // driven by a token budget rather than a fixed message count.
138
+ const keepStart = findKeepRecentStart(messages, keepRecentTokens);
139
+ // Align the cut to a user-turn boundary so we never split an assistant
140
+ // reply from its tool results.
141
+ const cutPoint = findSafeCutPoint(messages, keepStart);
142
+ const compactMessages = messages.slice(0, cutPoint);
143
+ const keepMessages = messages.slice(cutPoint);
144
+ if (compactMessages.length === 0) {
145
+ return { compacted: false, reason: 'nothing_to_compact' };
146
+ }
147
+ const prompt = buildCompactionPrompt(compactMessages);
148
+ // Run the (slow, network) summarizer OUTSIDE the transaction so we don't
149
+ // hold a DB transaction open across an LLM call.
150
+ const summary = await summarizer(prompt);
151
+ const compactionEntryId = generateId();
152
+ // Atomic rewrite: archive originals → upsert snapshots → delete originals →
153
+ // insert summary. If any step throws, the whole transaction rolls back and
154
+ // the original message history is left intact.
155
+ const { archiveId, fileSnapshotIds } = await handle.db.transaction(async (tx) => {
156
+ const txHandle = { db: tx, close: handle.close };
157
+ const archiveId = await archiveOriginalEntries(txHandle, sessionId, compactMessages, 'compaction', summary, compactionEntryId);
158
+ const fileSnapshotIds = [];
159
+ if (config?.preserveSnapshots !== false) {
160
+ const hotFiles = extractHotFiles(compactMessages);
161
+ for (const file of hotFiles) {
162
+ const id = await upsertFileSnapshot(txHandle, sessionId, file.path, file.content);
163
+ fileSnapshotIds.push(id);
164
+ }
165
+ }
166
+ await deleteEntriesByIds(txHandle, compactMessages.map((m) => m.id));
167
+ await insertEntry(txHandle, {
168
+ id: compactionEntryId,
169
+ sessionId,
170
+ tag: 'compaction',
171
+ content: {
172
+ summary,
173
+ originalEntryIds: compactMessages.map((m) => m.id),
174
+ archiveId,
175
+ },
176
+ tokenCount: estimateTokens(summary),
177
+ // Position the summary at the first compacted message's timestamp so it
178
+ // sorts BEFORE the kept recent messages under createdAt-ascending order.
179
+ createdAt: compactMessages[0] ? new Date(compactMessages[0].createdAt) : new Date(),
180
+ });
181
+ return { archiveId, fileSnapshotIds };
182
+ });
183
+ return {
184
+ compacted: true,
185
+ summary,
186
+ archiveId,
187
+ fileSnapshots: fileSnapshotIds,
188
+ compactedCount: compactMessages.length,
189
+ keptCount: keepMessages.length,
190
+ };
191
+ }
192
+ export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint };
@@ -0,0 +1,16 @@
1
+ import type { DB } from '../db/client.js';
2
+ import type { ChatMessage } from '../shared/types/llm.js';
3
+ import type { Message } from '../shared/types/message.js';
4
+ import type { FileSnapshot, SessionEntry } from './types.js';
5
+ /** Convert a stored Message to a protocol-level ChatMessage for the LLM. */
6
+ declare function messageToChatMessage(msg: Message): ChatMessage;
7
+ /** Convert all session entries (messages + special) to ChatMessage[] for the LLM. */
8
+ declare function entriesToChatMessages(entries: SessionEntry[], snapshots: FileSnapshot[]): ChatMessage[];
9
+ /** Inject file snapshot block after the first message (preserves cache prefix). */
10
+ declare function injectSnapshots(messages: ChatMessage[], snapshots: FileSnapshot[]): ChatMessage[];
11
+ /** Get the full session context: all entries + file snapshots. */
12
+ declare function getSessionContext(handle: DB, sessionId: string): Promise<{
13
+ entries: SessionEntry[];
14
+ snapshots: FileSnapshot[];
15
+ }>;
16
+ export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage };
@@ -0,0 +1,173 @@
1
+ import { getEntries } from './message.js';
2
+ import { getFileSnapshots } from './snapshot.js';
3
+ /**
4
+ * Drop tool messages whose id cannot be paired with an assistant tool_call,
5
+ * and inject synthetic tool results for orphan tool_calls (assistant 发出了
6
+ * tool_call 但无对应 tool result——服务重启中断场景)。
7
+ *
8
+ * OpenAI Chat 协议要求每个 role=tool 消息必须有 tool_call_id,且必须匹配
9
+ * 前面某条 assistant 消息的 tool_calls[].id。部分输出不规范的 provider
10
+ * 会产生空 id 的 tool_call/tool_result 碎片;这些碎片发回 provider 会触发
11
+ * "invalid tool_call_id"。这里在重建上下文时丢弃:
12
+ * - assistant.toolCalls 里 id 为空的项(连带丢弃其后无法配对的 tool result)
13
+ * - role=tool 但 toolCallId 为空、或找不到对应 assistant tool_call 的消息
14
+ * 正常(完整配对)的数据不受影响。
15
+ *
16
+ * 新增:assistant 有有效 tool_call 但后续无匹配 tool result(服务重启中断)时,
17
+ * 注入合成 tool result 告知模型工具被中断,而非丢弃 tool_call 让模型
18
+ * 盲目重试可能已产生副作用的非幂等工具。
19
+ */
20
+ function sanitizeToolPairs(messages) {
21
+ // 收集所有有效的 assistant tool_call id(非空)
22
+ const validCallIds = new Set();
23
+ for (const m of messages) {
24
+ if (m.role === 'assistant' && m.toolCalls) {
25
+ for (const tc of m.toolCalls) {
26
+ if (tc.id)
27
+ validCallIds.add(tc.id);
28
+ }
29
+ }
30
+ }
31
+ // 收集已有 tool result 配对的 tool_call id(用于检测孤儿 tool_call)
32
+ const answeredCallIds = new Set();
33
+ for (const m of messages) {
34
+ if (m.role === 'tool' && m.toolCallId && validCallIds.has(m.toolCallId)) {
35
+ answeredCallIds.add(m.toolCallId);
36
+ }
37
+ }
38
+ const result = [];
39
+ for (const m of messages) {
40
+ if (m.role === 'assistant' && m.toolCalls) {
41
+ // 丢弃 id 为空的 tool_call 项;若删空了则整个 toolCalls 移除
42
+ const filtered = m.toolCalls.filter((tc) => tc.id);
43
+ if (filtered.length === 0) {
44
+ result.push({ role: 'assistant', content: m.content });
45
+ }
46
+ else {
47
+ result.push({ ...m, toolCalls: filtered });
48
+ // 为无 result 配对的 tool_call 注入合成 result(服务中断场景)
49
+ for (const tc of filtered) {
50
+ if (!answeredCallIds.has(tc.id)) {
51
+ result.push({
52
+ role: 'tool',
53
+ toolCallId: tc.id,
54
+ content: JSON.stringify({
55
+ error: 'Tool execution was interrupted by server restart',
56
+ }),
57
+ });
58
+ }
59
+ }
60
+ }
61
+ continue;
62
+ }
63
+ if (m.role === 'tool') {
64
+ // toolCallId 为空、或找不到对应 assistant tool_call:丢弃整条 tool 消息
65
+ if (!m.toolCallId || !validCallIds.has(m.toolCallId)) {
66
+ continue;
67
+ }
68
+ }
69
+ result.push(m);
70
+ }
71
+ return result;
72
+ }
73
+ /** Convert a stored Message to a protocol-level ChatMessage for the LLM. */
74
+ function messageToChatMessage(msg) {
75
+ const textParts = msg.content
76
+ .filter((p) => p._tag === 'text' || p._tag === 'thinking')
77
+ .map((p) => (p._tag === 'thinking' ? `<think>${p.text}</think>` : p.text))
78
+ .join('');
79
+ const toolCalls = msg.content
80
+ .filter((p) => p._tag === 'tool_call')
81
+ .map((p) => ({
82
+ id: p.id,
83
+ name: p.tool,
84
+ arguments: JSON.stringify(p.input),
85
+ }));
86
+ const toolResultPart = msg.content.find((p) => p._tag === 'tool_result');
87
+ const imageParts = msg.content.filter((p) => p._tag === 'image');
88
+ // 含图片:构建多模态 content 数组(text 在前,image 在后)
89
+ if (imageParts.length > 0) {
90
+ const parts = [];
91
+ if (textParts)
92
+ parts.push({ type: 'text', text: textParts });
93
+ for (const img of imageParts) {
94
+ if (img._tag !== 'image')
95
+ continue;
96
+ parts.push({ type: 'image', mediaType: img.mediaType, data: img.data });
97
+ }
98
+ const multimodal = { role: msg.role, content: parts };
99
+ if (toolCalls.length > 0)
100
+ multimodal.toolCalls = toolCalls;
101
+ return multimodal;
102
+ }
103
+ // 无图片:保持原纯字符串逻辑(零回归)
104
+ const chat = {
105
+ role: msg.role,
106
+ content: textParts || (toolResultPart ? JSON.stringify(toolResultPart.output) : ''),
107
+ };
108
+ if (toolCalls.length > 0) {
109
+ chat.toolCalls = toolCalls;
110
+ }
111
+ if (toolResultPart && toolResultPart._tag === 'tool_result') {
112
+ chat.toolCallId = toolResultPart.id;
113
+ chat.content = JSON.stringify(toolResultPart.output);
114
+ }
115
+ return chat;
116
+ }
117
+ /** Convert all session entries (messages + special) to ChatMessage[] for the LLM. */
118
+ function entriesToChatMessages(entries, snapshots) {
119
+ const messages = [];
120
+ for (const entry of entries) {
121
+ if (!('_tag' in entry)) {
122
+ messages.push(messageToChatMessage(entry));
123
+ continue;
124
+ }
125
+ switch (entry._tag) {
126
+ case 'compaction':
127
+ case 'squash':
128
+ messages.push({ role: 'system', content: `[Compacted History]\n${entry.summary}` });
129
+ break;
130
+ case 'branch_summary':
131
+ messages.push({ role: 'system', content: `[Branch Context]\n${entry.summary}` });
132
+ break;
133
+ case 'steering':
134
+ messages.push({ role: 'system', content: entry.content });
135
+ break;
136
+ }
137
+ }
138
+ return injectSnapshots(sanitizeToolPairs(messages), snapshots);
139
+ }
140
+ /** Inject file snapshot block after the first message (preserves cache prefix). */
141
+ function injectSnapshots(messages, snapshots) {
142
+ if (snapshots.length === 0)
143
+ return messages;
144
+ // Keep only the highest-version snapshot per filePath (avoid stale duplicates).
145
+ const latestByPath = new Map();
146
+ for (const s of snapshots) {
147
+ const prev = latestByPath.get(s.filePath);
148
+ if (!prev || s.version > prev.version)
149
+ latestByPath.set(s.filePath, s);
150
+ }
151
+ const block = [...latestByPath.values()]
152
+ .map((s) => `[Cached File: ${s.filePath}]\n\`\`\`\n${s.content}\n\`\`\``)
153
+ .join('\n\n');
154
+ const snapshotMessage = {
155
+ role: 'system',
156
+ content: `[Active File Snapshots — DO NOT re-read these files]\n${block}`,
157
+ };
158
+ if (messages.length === 0)
159
+ return [snapshotMessage];
160
+ const first = messages[0];
161
+ if (!first)
162
+ return [snapshotMessage];
163
+ return [first, snapshotMessage, ...messages.slice(1)];
164
+ }
165
+ /** Get the full session context: all entries + file snapshots. */
166
+ async function getSessionContext(handle, sessionId) {
167
+ const [entries, snapshots] = await Promise.all([
168
+ getEntries(handle, sessionId),
169
+ getFileSnapshots(handle, sessionId),
170
+ ]);
171
+ return { entries, snapshots };
172
+ }
173
+ export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage };
@@ -0,0 +1,10 @@
1
+ export { archiveOriginalEntries, getArchive, getArchiveOriginalEntries, parseArchiveReference, resolveArchiveReference, searchArchives, } from './archive.js';
2
+ export { forkSession, getBranches, getTree } from './branch.js';
3
+ export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint, } from './compaction.js';
4
+ export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage, } from './context.js';
5
+ export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, } from './message.js';
6
+ export { createSession, deleteSession, getLLMSegments, getSession, listSessions, migrateLegacyDetails, saveLLMSegments, segmentFingerprint, touchSession, updateSessionTitle, } from './session.js';
7
+ export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot, } from './snapshot.js';
8
+ export { squashRecent } from './squash.js';
9
+ export { estimateMessageTokens, estimateTokens } from './token.js';
10
+ export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SquashConfig, SquashEntry, SteeringEntry, Summarizer, } from './types.js';
@@ -0,0 +1,10 @@
1
+ // Session layer: conversation persistence, branching, compaction, and context reconstruction.
2
+ export { archiveOriginalEntries, getArchive, getArchiveOriginalEntries, parseArchiveReference, resolveArchiveReference, searchArchives, } from './archive.js';
3
+ export { forkSession, getBranches, getTree } from './branch.js';
4
+ export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint, } from './compaction.js';
5
+ export { entriesToChatMessages, getSessionContext, injectSnapshots, messageToChatMessage, } from './context.js';
6
+ export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, } from './message.js';
7
+ export { createSession, deleteSession, getLLMSegments, getSession, listSessions, migrateLegacyDetails, saveLLMSegments, segmentFingerprint, touchSession, updateSessionTitle, } from './session.js';
8
+ export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot, } from './snapshot.js';
9
+ export { squashRecent } from './squash.js';
10
+ export { estimateMessageTokens, estimateTokens } from './token.js';
@@ -0,0 +1,22 @@
1
+ import type { DB } from '../db/client.js';
2
+ import { sessionEntries } from '../db/schema.js';
3
+ import type { Message } from '../shared/types/message.js';
4
+ import type { MessageInput, SessionEntry } from './types.js';
5
+ /** Append a message to a session. Returns the stored Message with generated id/timestamp. */
6
+ declare function appendMessage(handle: DB, sessionId: string, input: MessageInput): Promise<Message>;
7
+ /** Get messages for a session (tag='message' only), ordered chronologically. */
8
+ declare function getMessages(handle: DB, sessionId: string, opts?: {
9
+ limit?: number;
10
+ offset?: number;
11
+ }): Promise<Message[]>;
12
+ /** Count messages in a session. */
13
+ declare function getMessageCount(handle: DB, sessionId: string): Promise<number>;
14
+ /** Delete all messages after the given 0-based index (keeps 0..index inclusive). */
15
+ declare function deleteMessagesAfter(handle: DB, sessionId: string, messageIndex: number): Promise<void>;
16
+ /** Low-level: get ALL entries (messages + special) in chronological order. */
17
+ declare function getEntries(handle: DB, sessionId: string): Promise<SessionEntry[]>;
18
+ /** Low-level: delete entries by id. */
19
+ declare function deleteEntriesByIds(handle: DB, ids: string[]): Promise<void>;
20
+ /** Low-level: insert a raw entry row (for compaction/squash/branch_summary/steering). */
21
+ declare function insertEntry(handle: DB, values: typeof sessionEntries.$inferInsert): Promise<typeof sessionEntries.$inferSelect>;
22
+ export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, };
@@ -0,0 +1,145 @@
1
+ import { and, asc, count, eq, inArray } from 'drizzle-orm';
2
+ import { sessionEntries } from '../db/schema.js';
3
+ import { generateId } from '../shared/index.js';
4
+ import { touchSession } from './session.js';
5
+ import { estimateMessageTokens } from './token.js';
6
+ /** Convert a Date-like DB value to epoch milliseconds. */
7
+ function toEpochMs(value) {
8
+ if (value instanceof Date)
9
+ return value.getTime();
10
+ if (typeof value === 'number')
11
+ return value;
12
+ return new Date(value).getTime();
13
+ }
14
+ /** Convert a message-tagged row to a Message. */
15
+ function rowToMessage(row) {
16
+ return {
17
+ id: row.id,
18
+ sessionId: row.sessionId,
19
+ role: row.role,
20
+ content: row.content,
21
+ tokenCount: row.tokenCount ?? 0,
22
+ createdAt: toEpochMs(row.createdAt),
23
+ };
24
+ }
25
+ /** Convert any row to a SessionEntry (dispatch on tag). */
26
+ function rowToEntry(row) {
27
+ if (row.tag === 'message') {
28
+ return rowToMessage(row);
29
+ }
30
+ const content = row.content;
31
+ const createdAt = toEpochMs(row.createdAt);
32
+ switch (row.tag) {
33
+ case 'compaction':
34
+ return {
35
+ _tag: 'compaction',
36
+ id: row.id,
37
+ sessionId: row.sessionId,
38
+ summary: content.summary,
39
+ originalEntryIds: content.originalEntryIds,
40
+ archiveId: content.archiveId,
41
+ tokenCount: row.tokenCount ?? 0,
42
+ createdAt,
43
+ };
44
+ case 'squash':
45
+ return {
46
+ _tag: 'squash',
47
+ id: row.id,
48
+ sessionId: row.sessionId,
49
+ summary: content.summary,
50
+ squashedEntryIds: content.squashedEntryIds,
51
+ archiveId: content.archiveId,
52
+ tokenCount: row.tokenCount ?? 0,
53
+ createdAt,
54
+ };
55
+ case 'branch_summary':
56
+ return {
57
+ _tag: 'branch_summary',
58
+ id: row.id,
59
+ sessionId: row.sessionId,
60
+ summary: content.summary,
61
+ sourceSessionId: content.sourceSessionId,
62
+ createdAt,
63
+ };
64
+ case 'steering':
65
+ return {
66
+ _tag: 'steering',
67
+ id: row.id,
68
+ sessionId: row.sessionId,
69
+ content: content.text,
70
+ createdAt,
71
+ };
72
+ default:
73
+ // Fallback: treat unknown tags as messages
74
+ return rowToMessage(row);
75
+ }
76
+ }
77
+ /** Append a message to a session. Returns the stored Message with generated id/timestamp. */
78
+ async function appendMessage(handle, sessionId, input) {
79
+ const tokenCount = input.tokenCount ?? estimateMessageTokens(input.content);
80
+ const [row] = await handle.db
81
+ .insert(sessionEntries)
82
+ .values({
83
+ id: generateId(),
84
+ sessionId,
85
+ tag: 'message',
86
+ role: input.role,
87
+ content: input.content,
88
+ tokenCount,
89
+ })
90
+ .returning();
91
+ await touchSession(handle, sessionId);
92
+ if (!row)
93
+ throw new Error('Failed to insert message');
94
+ return rowToMessage(row);
95
+ }
96
+ /** Get messages for a session (tag='message' only), ordered chronologically. */
97
+ async function getMessages(handle, sessionId, opts) {
98
+ const rows = await handle.db
99
+ .select()
100
+ .from(sessionEntries)
101
+ .where(and(eq(sessionEntries.sessionId, sessionId), eq(sessionEntries.tag, 'message')))
102
+ .orderBy(asc(sessionEntries.createdAt))
103
+ .limit(opts?.limit ?? 100_000)
104
+ .offset(opts?.offset ?? 0);
105
+ return rows.map(rowToMessage);
106
+ }
107
+ /** Count messages in a session. */
108
+ async function getMessageCount(handle, sessionId) {
109
+ const [result] = await handle.db
110
+ .select({ value: count() })
111
+ .from(sessionEntries)
112
+ .where(and(eq(sessionEntries.sessionId, sessionId), eq(sessionEntries.tag, 'message')));
113
+ return result?.value ?? 0;
114
+ }
115
+ /** Delete all messages after the given 0-based index (keeps 0..index inclusive). */
116
+ async function deleteMessagesAfter(handle, sessionId, messageIndex) {
117
+ const messages = await getMessages(handle, sessionId);
118
+ const toDelete = messages.slice(messageIndex + 1);
119
+ if (toDelete.length > 0) {
120
+ await handle.db.delete(sessionEntries).where(inArray(sessionEntries.id, toDelete.map((m) => m.id)));
121
+ }
122
+ }
123
+ /** Low-level: get ALL entries (messages + special) in chronological order. */
124
+ async function getEntries(handle, sessionId) {
125
+ const rows = await handle.db
126
+ .select()
127
+ .from(sessionEntries)
128
+ .where(eq(sessionEntries.sessionId, sessionId))
129
+ .orderBy(asc(sessionEntries.createdAt));
130
+ return rows.map(rowToEntry);
131
+ }
132
+ /** Low-level: delete entries by id. */
133
+ async function deleteEntriesByIds(handle, ids) {
134
+ if (ids.length === 0)
135
+ return;
136
+ await handle.db.delete(sessionEntries).where(inArray(sessionEntries.id, ids));
137
+ }
138
+ /** Low-level: insert a raw entry row (for compaction/squash/branch_summary/steering). */
139
+ async function insertEntry(handle, values) {
140
+ const [row] = await handle.db.insert(sessionEntries).values(values).returning();
141
+ if (!row)
142
+ throw new Error('Failed to insert entry');
143
+ return row;
144
+ }
145
+ export { appendMessage, deleteEntriesByIds, deleteMessagesAfter, getEntries, getMessageCount, getMessages, insertEntry, };
@@ -0,0 +1,37 @@
1
+ import type { DB } from '../db/client.js';
2
+ import { sessions } from '../db/schema.js';
3
+ import type { LLMSegment } from '../shared/types/agent.js';
4
+ import type { ChatTool } from '../shared/types/llm.js';
5
+ import type { LastRun, Session } from '../shared/types/message.js';
6
+ /** Convert a DB row (with Date timestamps) to the shared Session type (with number timestamps). */
7
+ export declare function rowToSession(row: typeof sessions.$inferSelect): Session;
8
+ /** Create a new root session. */
9
+ declare function createSession(handle: DB, title: string, projectId?: string, agentType?: string): Promise<Session>;
10
+ /** Get a session by id, or null if not found. */
11
+ declare function getSession(handle: DB, id: string): Promise<Session | null>;
12
+ /** List all sessions. */
13
+ declare function listSessions(handle: DB): Promise<Session[]>;
14
+ /** Delete a session (cascades to entries, archives, snapshots via FK). */
15
+ declare function deleteSession(handle: DB, id: string): Promise<void>;
16
+ /** Update a session's title. */
17
+ declare function updateSessionTitle(handle: DB, id: string, title: string): Promise<void>;
18
+ /** Bump updatedAt to now (used after appending messages). */
19
+ declare function touchSession(handle: DB, id: string): Promise<void>;
20
+ /** 规格化工具集并计算前缀指纹。tools 顺序不影响指纹(按 name 排序)。 */
21
+ export declare function segmentFingerprint(systemPrompt: string, tools: ChatTool[]): string;
22
+ /**
23
+ * 将旧 metadata.llmDetails 迁移为单个 legacy segment。
24
+ * - 无 llmDetails 或已有 segments → 原样返回。
25
+ * - 否则取首条的 systemPrompt/tools 作为段首快照,所有旧 detail 转为 calls,
26
+ * responseText 从 responseChunks 的 text 块拼接提取。
27
+ * 幂等:迁移后 llmDetails 字段被移除,不会重复迁移。
28
+ */
29
+ export declare function migrateLegacyDetails(meta: Record<string, unknown>): Record<string, unknown>;
30
+ /** 读取会话 metadata.segments(读取时顺带迁移旧 llmDetails)。 */
31
+ export declare function getLLMSegments(handle: DB, id: string): Promise<LLMSegment[]>;
32
+ /** 全量替换会话 metadata.segments(每轮 loop 结束写入;段数据轻量)。 */
33
+ export declare function saveLLMSegments(handle: DB, id: string, segments: LLMSegment[]): Promise<void>;
34
+ /** 更新会话 metadata.lastRun(agent run 开始/结束时写入;重启后检测中断用)。 */
35
+ declare function updateSessionLastRun(handle: DB, id: string, lastRun: LastRun): Promise<void>;
36
+ declare function listSessionsByProject(handle: DB, projectId: string): Promise<Session[]>;
37
+ export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchSession, updateSessionLastRun, updateSessionTitle, };