c0de-agent 0.0.0 → 1.1.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.
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/dist/cli/commands/acp.d.ts +19 -0
- package/dist/cli/commands/acp.js +50 -0
- package/dist/cli/commands/chat.d.ts +13 -0
- package/dist/cli/commands/chat.js +26 -0
- package/dist/cli/commands/config.d.ts +17 -0
- package/dist/cli/commands/config.js +77 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.js +14 -0
- package/dist/cli/commands/plugin.d.ts +11 -0
- package/dist/cli/commands/plugin.js +31 -0
- package/dist/cli/commands/serve.d.ts +18 -0
- package/dist/cli/commands/serve.js +35 -0
- package/dist/cli/commands/update.d.ts +24 -0
- package/dist/cli/commands/update.js +40 -0
- package/dist/cli/deps.d.ts +30 -0
- package/dist/cli/deps.js +70 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +155 -0
- package/dist/cli/modes/acp.d.ts +18 -0
- package/dist/cli/modes/acp.js +57 -0
- package/dist/cli/modes/print.d.ts +16 -0
- package/dist/cli/modes/print.js +30 -0
- package/dist/cli/parser.d.ts +19 -0
- package/dist/cli/parser.js +28 -0
- package/dist/cli/utils/output.d.ts +11 -0
- package/dist/cli/utils/output.js +23 -0
- package/dist/cli/utils/prompt.d.ts +12 -0
- package/dist/cli/utils/prompt.js +18 -0
- package/dist/core/agent.d.ts +12 -0
- package/dist/core/agent.js +100 -0
- package/dist/core/agents/builtin.d.ts +6 -0
- package/dist/core/agents/builtin.js +76 -0
- package/dist/core/agents/discovery.d.ts +6 -0
- package/dist/core/agents/discovery.js +96 -0
- package/dist/core/agents/index.d.ts +6 -0
- package/dist/core/agents/index.js +4 -0
- package/dist/core/agents/parallel.d.ts +13 -0
- package/dist/core/agents/parallel.js +37 -0
- package/dist/core/agents/registry.d.ts +4 -0
- package/dist/core/agents/registry.js +23 -0
- package/dist/core/agents/types.d.ts +37 -0
- package/dist/core/agents/types.js +1 -0
- package/dist/core/compact.d.ts +11 -0
- package/dist/core/compact.js +31 -0
- package/dist/core/config.d.ts +7 -0
- package/dist/core/config.js +85 -0
- package/dist/core/context.d.ts +23 -0
- package/dist/core/context.js +93 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +11 -0
- package/dist/core/loop.d.ts +35 -0
- package/dist/core/loop.js +915 -0
- package/dist/core/metrics.d.ts +27 -0
- package/dist/core/metrics.js +94 -0
- package/dist/core/prompt-registry.d.ts +21 -0
- package/dist/core/prompt-registry.js +200 -0
- package/dist/core/prompt.d.ts +11 -0
- package/dist/core/prompt.js +13 -0
- package/dist/core/secret.d.ts +12 -0
- package/dist/core/secret.js +51 -0
- package/dist/core/slash.d.ts +15 -0
- package/dist/core/slash.js +113 -0
- package/dist/core/steering.d.ts +8 -0
- package/dist/core/steering.js +15 -0
- package/dist/core/title.d.ts +42 -0
- package/dist/core/title.js +116 -0
- package/dist/core/tool-exec.d.ts +20 -0
- package/dist/core/tool-exec.js +64 -0
- package/dist/core/types.d.ts +97 -0
- package/dist/core/types.js +1 -0
- package/dist/core/worktree.d.ts +24 -0
- package/dist/core/worktree.js +70 -0
- package/dist/dap/index.d.ts +6 -0
- package/dist/dap/index.js +4 -0
- package/dist/dap/protocol.d.ts +40 -0
- package/dist/dap/protocol.js +132 -0
- package/dist/dap/session.d.ts +23 -0
- package/dist/dap/session.js +130 -0
- package/dist/dap/transport.d.ts +8 -0
- package/dist/dap/transport.js +30 -0
- package/dist/dap/types.d.ts +50 -0
- package/dist/dap/types.js +2 -0
- package/dist/db/client.d.ts +22 -0
- package/dist/db/client.js +39 -0
- package/dist/db/index.d.ts +6 -0
- package/dist/db/index.js +4 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +15 -0
- package/dist/db/schema.d.ts +965 -0
- package/dist/db/schema.js +112 -0
- package/dist/db/types.d.ts +11 -0
- package/dist/db/types.js +1 -0
- package/dist/llm/index.d.ts +13 -0
- package/dist/llm/index.js +8 -0
- package/dist/llm/protocols/openai-compat.d.ts +139 -0
- package/dist/llm/protocols/openai-compat.js +214 -0
- package/dist/llm/protocols/utils/index.d.ts +2 -0
- package/dist/llm/protocols/utils/index.js +2 -0
- package/dist/llm/protocols/utils/lifecycle.d.ts +23 -0
- package/dist/llm/protocols/utils/lifecycle.js +46 -0
- package/dist/llm/protocols/utils/tool-stream.d.ts +46 -0
- package/dist/llm/protocols/utils/tool-stream.js +94 -0
- package/dist/llm/provider-error.d.ts +5 -0
- package/dist/llm/provider-error.js +33 -0
- package/dist/llm/provider.d.ts +32 -0
- package/dist/llm/provider.js +195 -0
- package/dist/llm/registry.d.ts +44 -0
- package/dist/llm/registry.js +107 -0
- package/dist/llm/retry.d.ts +37 -0
- package/dist/llm/retry.js +82 -0
- package/dist/llm/routing.d.ts +36 -0
- package/dist/llm/routing.js +53 -0
- package/dist/llm/schema/errors.d.ts +88 -0
- package/dist/llm/schema/errors.js +33 -0
- package/dist/llm/schema/events.d.ts +135 -0
- package/dist/llm/schema/events.js +35 -0
- package/dist/llm/schema/ids.d.ts +22 -0
- package/dist/llm/schema/ids.js +3 -0
- package/dist/llm/schema/index.d.ts +5 -0
- package/dist/llm/schema/index.js +5 -0
- package/dist/llm/schema/messages.d.ts +97 -0
- package/dist/llm/schema/messages.js +18 -0
- package/dist/llm/schema/options.d.ts +54 -0
- package/dist/llm/schema/options.js +54 -0
- package/dist/llm/token.d.ts +2 -0
- package/dist/llm/token.js +13 -0
- package/dist/llm/transport.d.ts +31 -0
- package/dist/llm/transport.js +165 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +3 -0
- package/dist/plugins/builtin.d.ts +6 -0
- package/dist/plugins/builtin.js +54 -0
- package/dist/plugins/hooks.d.ts +3 -0
- package/dist/plugins/hooks.js +79 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +8 -0
- package/dist/plugins/init.d.ts +23 -0
- package/dist/plugins/init.js +30 -0
- package/dist/plugins/lifecycle.d.ts +6 -0
- package/dist/plugins/lifecycle.js +74 -0
- package/dist/plugins/loader.d.ts +16 -0
- package/dist/plugins/loader.js +69 -0
- package/dist/plugins/logger.d.ts +4 -0
- package/dist/plugins/logger.js +31 -0
- package/dist/plugins/registry.d.ts +7 -0
- package/dist/plugins/registry.js +24 -0
- package/dist/plugins/types.d.ts +113 -0
- package/dist/plugins/types.js +1 -0
- package/dist/project/detect.d.ts +4 -0
- package/dist/project/detect.js +131 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +2 -0
- package/dist/project/project.d.ts +23 -0
- package/dist/project/project.js +87 -0
- package/dist/project/resolve.d.ts +8 -0
- package/dist/project/resolve.js +81 -0
- package/dist/server/agent-manager.d.ts +34 -0
- package/dist/server/agent-manager.js +62 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.js +78 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +49 -0
- package/dist/server/dev.d.ts +14 -0
- package/dist/server/dev.js +148 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +7 -0
- package/dist/server/middleware/auth.d.ts +15 -0
- package/dist/server/middleware/auth.js +29 -0
- package/dist/server/middleware/cors.d.ts +16 -0
- package/dist/server/middleware/cors.js +44 -0
- package/dist/server/middleware/error.d.ts +15 -0
- package/dist/server/middleware/error.js +11 -0
- package/dist/server/permission/interactive.d.ts +24 -0
- package/dist/server/permission/interactive.js +56 -0
- package/dist/server/permission/store.d.ts +32 -0
- package/dist/server/permission/store.js +56 -0
- package/dist/server/routes/agent.d.ts +4 -0
- package/dist/server/routes/agent.js +19 -0
- package/dist/server/routes/catalog.d.ts +42 -0
- package/dist/server/routes/catalog.js +194 -0
- package/dist/server/routes/chat.d.ts +8 -0
- package/dist/server/routes/chat.js +318 -0
- package/dist/server/routes/commands.d.ts +5 -0
- package/dist/server/routes/commands.js +17 -0
- package/dist/server/routes/config.d.ts +4 -0
- package/dist/server/routes/config.js +27 -0
- package/dist/server/routes/files.d.ts +4 -0
- package/dist/server/routes/files.js +183 -0
- package/dist/server/routes/filesystem.d.ts +29 -0
- package/dist/server/routes/filesystem.js +108 -0
- package/dist/server/routes/health.d.ts +3 -0
- package/dist/server/routes/health.js +13 -0
- package/dist/server/routes/permissions.d.ts +5 -0
- package/dist/server/routes/permissions.js +23 -0
- package/dist/server/routes/project.d.ts +4 -0
- package/dist/server/routes/project.js +51 -0
- package/dist/server/routes/provider.d.ts +16 -0
- package/dist/server/routes/provider.js +66 -0
- package/dist/server/routes/session.d.ts +4 -0
- package/dist/server/routes/session.js +204 -0
- package/dist/server/routes/terminal.d.ts +5 -0
- package/dist/server/routes/terminal.js +66 -0
- package/dist/server/routes/tool.d.ts +4 -0
- package/dist/server/routes/tool.js +28 -0
- package/dist/server/routes/update.d.ts +14 -0
- package/dist/server/routes/update.js +49 -0
- package/dist/server/server.d.ts +56 -0
- package/dist/server/server.js +269 -0
- package/dist/server/terminal/pty-manager.d.ts +53 -0
- package/dist/server/terminal/pty-manager.js +160 -0
- package/dist/server/types.d.ts +76 -0
- package/dist/server/types.js +2 -0
- package/dist/server/util/safe-path.d.ts +7 -0
- package/dist/server/util/safe-path.js +7 -0
- package/dist/session/archive.d.ts +15 -0
- package/dist/session/archive.js +129 -0
- package/dist/session/branch.d.ts +9 -0
- package/dist/session/branch.js +63 -0
- package/dist/session/compaction.d.ts +33 -0
- package/dist/session/compaction.js +261 -0
- package/dist/session/context.d.ts +16 -0
- package/dist/session/context.js +173 -0
- package/dist/session/index.d.ts +10 -0
- package/dist/session/index.js +10 -0
- package/dist/session/message.d.ts +22 -0
- package/dist/session/message.js +145 -0
- package/dist/session/session.d.ts +37 -0
- package/dist/session/session.js +144 -0
- package/dist/session/shake.d.ts +66 -0
- package/dist/session/shake.js +304 -0
- package/dist/session/snapshot.d.ts +11 -0
- package/dist/session/snapshot.js +60 -0
- package/dist/session/squash.d.ts +9 -0
- package/dist/session/squash.js +88 -0
- package/dist/session/token.d.ts +10 -0
- package/dist/session/token.js +33 -0
- package/dist/session/types.d.ts +122 -0
- package/dist/session/types.js +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/types/agent.d.ts +229 -0
- package/dist/shared/types/agent.js +1 -0
- package/dist/shared/types/base.d.ts +32 -0
- package/dist/shared/types/base.js +1 -0
- package/dist/shared/types/config.d.ts +114 -0
- package/dist/shared/types/config.js +1 -0
- package/dist/shared/types/index.d.ts +6 -0
- package/dist/shared/types/index.js +1 -0
- package/dist/shared/types/llm.d.ts +123 -0
- package/dist/shared/types/llm.js +1 -0
- package/dist/shared/types/message.d.ts +71 -0
- package/dist/shared/types/message.js +1 -0
- package/dist/shared/types/tool.d.ts +137 -0
- package/dist/shared/types/tool.js +1 -0
- package/dist/shared/utils/path.d.ts +15 -0
- package/dist/shared/utils/path.js +23 -0
- package/dist/shared/utils.d.ts +5 -0
- package/dist/shared/utils.js +10 -0
- package/dist/tools/builtin/bash.d.ts +12 -0
- package/dist/tools/builtin/bash.js +121 -0
- package/dist/tools/builtin/dap.d.ts +23 -0
- package/dist/tools/builtin/dap.js +231 -0
- package/dist/tools/builtin/edit.d.ts +12 -0
- package/dist/tools/builtin/edit.js +151 -0
- package/dist/tools/builtin/glob.d.ts +11 -0
- package/dist/tools/builtin/glob.js +120 -0
- package/dist/tools/builtin/grep.d.ts +6 -0
- package/dist/tools/builtin/grep.js +141 -0
- package/dist/tools/builtin/read.d.ts +6 -0
- package/dist/tools/builtin/read.js +73 -0
- package/dist/tools/builtin/resolvers.d.ts +12 -0
- package/dist/tools/builtin/resolvers.js +79 -0
- package/dist/tools/builtin/task.d.ts +9 -0
- package/dist/tools/builtin/task.js +100 -0
- package/dist/tools/builtin/write.d.ts +6 -0
- package/dist/tools/builtin/write.js +36 -0
- package/dist/tools/builtin/yield.d.ts +9 -0
- package/dist/tools/builtin/yield.js +40 -0
- package/dist/tools/executor.d.ts +9 -0
- package/dist/tools/executor.js +54 -0
- package/dist/tools/hashline/index.d.ts +2 -0
- package/dist/tools/hashline/index.js +3 -0
- package/dist/tools/hashline/patch.d.ts +48 -0
- package/dist/tools/hashline/patch.js +201 -0
- package/dist/tools/index.d.ts +30 -0
- package/dist/tools/index.js +55 -0
- package/dist/tools/permission.d.ts +13 -0
- package/dist/tools/permission.js +39 -0
- package/dist/tools/registry.d.ts +15 -0
- package/dist/tools/registry.js +54 -0
- package/dist/tools/resolver.d.ts +15 -0
- package/dist/tools/resolver.js +47 -0
- package/dist/tools/truncate.d.ts +8 -0
- package/dist/tools/truncate.js +50 -0
- package/dist/tools/types.d.ts +101 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/validate.d.ts +8 -0
- package/dist/tools/validate.js +130 -0
- package/dist/tools/websearch/fetch.d.ts +11 -0
- package/dist/tools/websearch/fetch.js +21 -0
- package/dist/tools/websearch/index.d.ts +19 -0
- package/dist/tools/websearch/index.js +78 -0
- package/dist/tools/websearch/providers/brave.d.ts +5 -0
- package/dist/tools/websearch/providers/brave.js +53 -0
- package/dist/tools/websearch/providers/duckduckgo.d.ts +3 -0
- package/dist/tools/websearch/providers/duckduckgo.js +75 -0
- package/dist/tools/websearch/providers/tavily.d.ts +11 -0
- package/dist/tools/websearch/providers/tavily.js +89 -0
- package/dist/tools/websearch/types.d.ts +51 -0
- package/dist/tools/websearch/types.js +18 -0
- package/dist/tools/websearch/websearch.d.ts +10 -0
- package/dist/tools/websearch/websearch.js +48 -0
- package/dist/update/index.d.ts +10 -0
- package/dist/update/index.js +6 -0
- package/dist/update/ipc.d.ts +15 -0
- package/dist/update/ipc.js +41 -0
- package/dist/update/scheduler.d.ts +34 -0
- package/dist/update/scheduler.js +57 -0
- package/dist/update/snapshot.d.ts +39 -0
- package/dist/update/snapshot.js +99 -0
- package/dist/update/updater.d.ts +48 -0
- package/dist/update/updater.js +83 -0
- package/dist/update/version.d.ts +18 -0
- package/dist/update/version.js +49 -0
- package/drizzle/0000_magical_nightshade.sql +55 -0
- package/drizzle/0001_chemical_doctor_faustus.sql +13 -0
- package/drizzle/0002_amusing_micromacro.sql +12 -0
- package/drizzle/0003_regular_purifiers.sql +2 -0
- package/drizzle/meta/0000_snapshot.json +448 -0
- package/drizzle/meta/0001_snapshot.json +539 -0
- package/drizzle/meta/0002_snapshot.json +633 -0
- package/drizzle/meta/0003_snapshot.json +645 -0
- package/drizzle/meta/_journal.json +34 -0
- package/package.json +108 -8
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { and, desc, eq, ilike } from 'drizzle-orm';
|
|
2
|
+
import { compactionArchives } from '../db/schema.js';
|
|
3
|
+
import { generateId } from '../shared/index.js';
|
|
4
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
5
|
+
function toEpochMs(value) {
|
|
6
|
+
if (value instanceof Date)
|
|
7
|
+
return value.getTime();
|
|
8
|
+
if (typeof value === 'number')
|
|
9
|
+
return value;
|
|
10
|
+
return new Date(value).getTime();
|
|
11
|
+
}
|
|
12
|
+
function rowToArchive(row) {
|
|
13
|
+
return {
|
|
14
|
+
id: row.id,
|
|
15
|
+
sessionId: row.sessionId,
|
|
16
|
+
compactionId: row.compactionId,
|
|
17
|
+
archiveType: row.archiveType,
|
|
18
|
+
originalEntries: row.originalEntries,
|
|
19
|
+
summary: row.summary,
|
|
20
|
+
tokenCount: row.tokenCount ?? 0,
|
|
21
|
+
searchableText: row.searchableText ?? '',
|
|
22
|
+
createdAt: toEpochMs(row.createdAt),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Convert an entry to searchable plain text. */
|
|
26
|
+
function entryToSearchableText(entry) {
|
|
27
|
+
if ('_tag' in entry) {
|
|
28
|
+
switch (entry._tag) {
|
|
29
|
+
case 'compaction':
|
|
30
|
+
case 'squash':
|
|
31
|
+
case 'branch_summary':
|
|
32
|
+
return entry.summary;
|
|
33
|
+
case 'steering':
|
|
34
|
+
return entry.content;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return entry.content
|
|
38
|
+
.map((part) => {
|
|
39
|
+
switch (part._tag) {
|
|
40
|
+
case 'text':
|
|
41
|
+
case 'thinking':
|
|
42
|
+
case 'steering':
|
|
43
|
+
return part.text;
|
|
44
|
+
case 'tool_call':
|
|
45
|
+
return `${part.tool}: ${JSON.stringify(part.input)}`;
|
|
46
|
+
case 'tool_result':
|
|
47
|
+
return JSON.stringify(part.output);
|
|
48
|
+
default:
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.join(' ');
|
|
53
|
+
}
|
|
54
|
+
/** Archive original entries before compaction/squash. Returns the archive id. */
|
|
55
|
+
async function archiveOriginalEntries(handle, sessionId, entries, archiveType, summary, compactionId) {
|
|
56
|
+
const id = generateId();
|
|
57
|
+
const searchableText = entries.map(entryToSearchableText).join('\n');
|
|
58
|
+
const tokenCount = entries.reduce((sum, e) => sum + ('tokenCount' in e ? e.tokenCount : 0), 0);
|
|
59
|
+
await handle.db.insert(compactionArchives).values({
|
|
60
|
+
id,
|
|
61
|
+
sessionId,
|
|
62
|
+
compactionId,
|
|
63
|
+
archiveType,
|
|
64
|
+
originalEntries: entries,
|
|
65
|
+
summary,
|
|
66
|
+
tokenCount,
|
|
67
|
+
searchableText,
|
|
68
|
+
});
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
/** Get an archive by id. Returns null for invalid or non-existent ids. */
|
|
72
|
+
async function getArchive(handle, id) {
|
|
73
|
+
// PGLite rejects non-UUID strings at query time; validate first.
|
|
74
|
+
if (!UUID_RE.test(id))
|
|
75
|
+
return null;
|
|
76
|
+
const [row] = await handle.db
|
|
77
|
+
.select()
|
|
78
|
+
.from(compactionArchives)
|
|
79
|
+
.where(eq(compactionArchives.id, id));
|
|
80
|
+
return row ? rowToArchive(row) : null;
|
|
81
|
+
}
|
|
82
|
+
/** Get the original entries stored in an archive. */
|
|
83
|
+
async function getArchiveOriginalEntries(handle, archiveId) {
|
|
84
|
+
const archive = await getArchive(handle, archiveId);
|
|
85
|
+
return archive?.originalEntries ?? [];
|
|
86
|
+
}
|
|
87
|
+
/** Escape LIKE/ILIKE metacharacters so the query is treated as a literal substring. */
|
|
88
|
+
function escapeLikePattern(text) {
|
|
89
|
+
return text.replace(/[%_\\]/g, '\\$&');
|
|
90
|
+
}
|
|
91
|
+
/** Search archives by keyword (case-insensitive substring on searchable text). */
|
|
92
|
+
async function searchArchives(handle, sessionId, query) {
|
|
93
|
+
const pattern = `%${escapeLikePattern(query)}%`;
|
|
94
|
+
const rows = await handle.db
|
|
95
|
+
.select()
|
|
96
|
+
.from(compactionArchives)
|
|
97
|
+
.where(and(eq(compactionArchives.sessionId, sessionId), ilike(compactionArchives.searchableText, pattern)))
|
|
98
|
+
.orderBy(desc(compactionArchives.createdAt));
|
|
99
|
+
return rows.map(rowToArchive);
|
|
100
|
+
}
|
|
101
|
+
/** Parse a `@[archive:<id>]` or `@[squash:<n>]` reference from text. Returns null if none. */
|
|
102
|
+
function parseArchiveReference(text) {
|
|
103
|
+
const archiveId = text.match(/@\[archive:([^\]]+)\]/)?.[1];
|
|
104
|
+
if (archiveId)
|
|
105
|
+
return { type: 'archive', id: archiveId };
|
|
106
|
+
const squashId = text.match(/@\[squash:(\d+|last)\]/)?.[1];
|
|
107
|
+
if (squashId)
|
|
108
|
+
return { type: 'squash', id: squashId };
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
/** Resolve an archive reference to displayable text. */
|
|
112
|
+
async function resolveArchiveReference(handle, sessionId, ref) {
|
|
113
|
+
if (ref.type === 'archive') {
|
|
114
|
+
const archive = await getArchive(handle, ref.id);
|
|
115
|
+
return archive ? `[Referenced Archive]\n${archive.summary}` : null;
|
|
116
|
+
}
|
|
117
|
+
// squash reference: nth most recent squash archive
|
|
118
|
+
const archives = await handle.db
|
|
119
|
+
.select()
|
|
120
|
+
.from(compactionArchives)
|
|
121
|
+
.where(and(eq(compactionArchives.sessionId, sessionId), eq(compactionArchives.archiveType, 'squash')))
|
|
122
|
+
.orderBy(desc(compactionArchives.createdAt));
|
|
123
|
+
const index = ref.id === 'last' ? 0 : Number.parseInt(ref.id, 10) - 1;
|
|
124
|
+
const row = archives[index];
|
|
125
|
+
if (!row)
|
|
126
|
+
return null;
|
|
127
|
+
return `[Referenced Squash #${index + 1}]\n${row.summary}`;
|
|
128
|
+
}
|
|
129
|
+
export { archiveOriginalEntries, getArchive, getArchiveOriginalEntries, parseArchiveReference, resolveArchiveReference, searchArchives, };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { Session, SessionTreeNode } from './types.js';
|
|
3
|
+
/** Fork a session at a message index — copies messages 0..index into a new child session. */
|
|
4
|
+
declare function forkSession(handle: DB, sessionId: string, messageIndex: number): Promise<Session>;
|
|
5
|
+
/** Get direct child sessions (branches) of a session. */
|
|
6
|
+
declare function getBranches(handle: DB, sessionId: string): Promise<Session[]>;
|
|
7
|
+
/** Build a full session tree from root sessions down. */
|
|
8
|
+
declare function getTree(handle: DB): Promise<SessionTreeNode[]>;
|
|
9
|
+
export { forkSession, getBranches, getTree };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { sessions } from '../db/schema.js';
|
|
3
|
+
import { generateId } from '../shared/index.js';
|
|
4
|
+
import { getMessages, insertEntry } from './message.js';
|
|
5
|
+
import { createSession, getSession, rowToSession } from './session.js';
|
|
6
|
+
/** Fork a session at a message index — copies messages 0..index into a new child session. */
|
|
7
|
+
async function forkSession(handle, sessionId, messageIndex) {
|
|
8
|
+
const source = await getSession(handle, sessionId);
|
|
9
|
+
if (!source)
|
|
10
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
11
|
+
const messages = await getMessages(handle, sessionId);
|
|
12
|
+
const toCopy = messages.slice(0, messageIndex + 1);
|
|
13
|
+
const forked = await createSession(handle, `Branch of ${source.title}`, source.projectId ?? undefined);
|
|
14
|
+
await handle.db
|
|
15
|
+
.update(sessions)
|
|
16
|
+
.set({ parentId: sessionId, branchPoint: messageIndex })
|
|
17
|
+
.where(eq(sessions.id, forked.id));
|
|
18
|
+
for (const msg of toCopy) {
|
|
19
|
+
await insertEntry(handle, {
|
|
20
|
+
id: generateId(),
|
|
21
|
+
sessionId: forked.id,
|
|
22
|
+
tag: 'message',
|
|
23
|
+
role: msg.role,
|
|
24
|
+
content: msg.content,
|
|
25
|
+
tokenCount: msg.tokenCount,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
await insertEntry(handle, {
|
|
29
|
+
id: generateId(),
|
|
30
|
+
sessionId: forked.id,
|
|
31
|
+
tag: 'branch_summary',
|
|
32
|
+
content: {
|
|
33
|
+
summary: `Branched from session ${sessionId} at message ${messageIndex}`,
|
|
34
|
+
sourceSessionId: sessionId,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const updated = await getSession(handle, forked.id);
|
|
38
|
+
if (!updated)
|
|
39
|
+
throw new Error('Forked session not found after creation');
|
|
40
|
+
return updated;
|
|
41
|
+
}
|
|
42
|
+
/** Get direct child sessions (branches) of a session. */
|
|
43
|
+
async function getBranches(handle, sessionId) {
|
|
44
|
+
const rows = await handle.db.select().from(sessions).where(eq(sessions.parentId, sessionId));
|
|
45
|
+
return rows.map(rowToSession);
|
|
46
|
+
}
|
|
47
|
+
/** Build a full session tree from root sessions down. */
|
|
48
|
+
async function getTree(handle) {
|
|
49
|
+
const rows = await handle.db.select().from(sessions);
|
|
50
|
+
const byParent = new Map();
|
|
51
|
+
for (const row of rows) {
|
|
52
|
+
const session = rowToSession(row);
|
|
53
|
+
const list = byParent.get(session.parentId) ?? [];
|
|
54
|
+
list.push(session);
|
|
55
|
+
byParent.set(session.parentId, list);
|
|
56
|
+
}
|
|
57
|
+
const build = (parentId) => (byParent.get(parentId) ?? []).map((session) => ({
|
|
58
|
+
session,
|
|
59
|
+
children: build(session.id),
|
|
60
|
+
}));
|
|
61
|
+
return build(null);
|
|
62
|
+
}
|
|
63
|
+
export { forkSession, getBranches, getTree };
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
/**
|
|
13
|
+
* Build the LLM summarization prompt for a set of messages.
|
|
14
|
+
*
|
|
15
|
+
* 当存在 previousSummary(上一次压缩生成的摘要)时,prompt 头部改为增量更新指令,
|
|
16
|
+
* 引导模型在已有摘要上叠加新事实、剔除过时信息,从而避免连续多次压缩导致的
|
|
17
|
+
* 信息逐次丢失。无 previousSummary 时退回原始的从零压缩指令。
|
|
18
|
+
*/
|
|
19
|
+
declare function buildCompactionPrompt(messages: Message[], previousSummary?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Compact a session: summarize old messages, archive them, keep recent ones.
|
|
22
|
+
*
|
|
23
|
+
* The keep window is driven by a token budget (`keepRecentTokens`), not a
|
|
24
|
+
* fixed message count — so compaction actually fires on a few long messages
|
|
25
|
+
* instead of silently no-op'ing when message count is small. The `summarizer`
|
|
26
|
+
* function is injected (the session layer never imports the LLM package).
|
|
27
|
+
*
|
|
28
|
+
* All destructive writes (archive + snapshot + delete + insert) run inside a
|
|
29
|
+
* single transaction so a mid-way failure cannot lose history: the originals
|
|
30
|
+
* are only deleted once the summary is durably stored.
|
|
31
|
+
*/
|
|
32
|
+
declare function compactSession(handle: DB, sessionId: string, summarizer: Summarizer, config?: Partial<CompactionConfig>): Promise<CompactionResult>;
|
|
33
|
+
export { buildCompactionPrompt, compactSession, extractHotFiles, findSafeCutPoint };
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { generateId } from '../shared/index.js';
|
|
2
|
+
import { archiveOriginalEntries } from './archive.js';
|
|
3
|
+
import { deleteEntriesByIds, getEntries, 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
|
+
/** 工具输出在压缩 prompt 中保留的最大字符数(参考 opencode)。 */
|
|
55
|
+
const TOOL_OUTPUT_MAX_CHARS = 2000;
|
|
56
|
+
/** 截断超长字符串:head 60% + "[truncated]" + tail 40%,而非硬切。 */
|
|
57
|
+
function truncateToolOutput(s) {
|
|
58
|
+
if (s.length <= TOOL_OUTPUT_MAX_CHARS)
|
|
59
|
+
return s;
|
|
60
|
+
const head = Math.floor(TOOL_OUTPUT_MAX_CHARS * 0.6);
|
|
61
|
+
const tail = TOOL_OUTPUT_MAX_CHARS - head;
|
|
62
|
+
return `${s.slice(0, head)}[truncated]${s.slice(-tail)}`;
|
|
63
|
+
}
|
|
64
|
+
/** 序列化单个 content part:text/thinking 保持原样,tool 输出超长时截断。 */
|
|
65
|
+
function serializePart(p) {
|
|
66
|
+
if (p._tag === 'text' || p._tag === 'thinking')
|
|
67
|
+
return p.text;
|
|
68
|
+
if (p._tag === 'tool_call') {
|
|
69
|
+
const input = truncateToolOutput(JSON.stringify(p.input));
|
|
70
|
+
return JSON.stringify({ _tag: p._tag, id: p.id, tool: p.tool, input });
|
|
71
|
+
}
|
|
72
|
+
if (p._tag === 'tool_result') {
|
|
73
|
+
const output = truncateToolOutput(JSON.stringify(p.output));
|
|
74
|
+
return JSON.stringify({ _tag: p._tag, id: p.id, tool: p.tool, output });
|
|
75
|
+
}
|
|
76
|
+
return JSON.stringify(p);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Build the LLM summarization prompt for a set of messages.
|
|
80
|
+
*
|
|
81
|
+
* 当存在 previousSummary(上一次压缩生成的摘要)时,prompt 头部改为增量更新指令,
|
|
82
|
+
* 引导模型在已有摘要上叠加新事实、剔除过时信息,从而避免连续多次压缩导致的
|
|
83
|
+
* 信息逐次丢失。无 previousSummary 时退回原始的从零压缩指令。
|
|
84
|
+
*/
|
|
85
|
+
function buildCompactionPrompt(messages, previousSummary) {
|
|
86
|
+
const history = messages
|
|
87
|
+
.map((m) => `[${m.role}] ${m.content.map((p) => serializePart(p)).join(' ')}`)
|
|
88
|
+
.join('\n');
|
|
89
|
+
const sections = `## Agenda
|
|
90
|
+
逐条列出对话中出现的议题/任务,按处理顺序排列。每条格式:
|
|
91
|
+
- **[议题标题]** — ✅已解决 / ⏳进行中 / 🔒阻塞 / 📋待办
|
|
92
|
+
- ✅/🔒 → 一行:最终结论或卡点
|
|
93
|
+
- ⏳/📋 → 完整保留:目标、约束、已尝试方向、相关文件路径、关键决策、待确认问题
|
|
94
|
+
|
|
95
|
+
## Goal
|
|
96
|
+
用户此次会话的总体目标(若 Agenda 已涵盖,写"见 Agenda")
|
|
97
|
+
|
|
98
|
+
## Constraints & Preferences
|
|
99
|
+
用户约束、偏好、规范要求(或"(none)")
|
|
100
|
+
|
|
101
|
+
## Key Decisions
|
|
102
|
+
做出的关键决策及原因
|
|
103
|
+
|
|
104
|
+
## Critical Context
|
|
105
|
+
必须记住的技术事实(文件路径、变量名、命令、错误信息、未解决问题)
|
|
106
|
+
|
|
107
|
+
## Modified Files
|
|
108
|
+
修改过的文件路径及变更摘要
|
|
109
|
+
|
|
110
|
+
## Relevant Files
|
|
111
|
+
对任务重要的文件/目录路径及原因`;
|
|
112
|
+
const header = previousSummary
|
|
113
|
+
? `更新以下已有【议题驱动】摘要。重点:
|
|
114
|
+
- 已解决的议题:状态更新为✅并压缩为一行结论;
|
|
115
|
+
- 新增议题:补入 Agenda 并完整保留其描述与约束;
|
|
116
|
+
- 尚未解决的议题:保持其原有描述与约束不变,只叠加本轮新进展。
|
|
117
|
+
|
|
118
|
+
<previous-summary>
|
|
119
|
+
${previousSummary}
|
|
120
|
+
</previous-summary>`
|
|
121
|
+
: `将以下对话历史压缩为一份【议题驱动】的结构化摘要。
|
|
122
|
+
核心原则——非对称保留:已解决的议题只留一行结论;尚未解决/待办的议题
|
|
123
|
+
必须完整保留其描述、约束、已尝试方向、相关文件与待确认问题,它们是后续
|
|
124
|
+
工作的蓝图,绝不可被稀释。`;
|
|
125
|
+
return `${header}
|
|
126
|
+
|
|
127
|
+
${sections}
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
对话历史:
|
|
131
|
+
${history}`;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Token cost of a single message, honoring a stored tokenCount when present.
|
|
135
|
+
* Mirrors core/context.ts rawMessageTokens without crossing the core→session
|
|
136
|
+
* layer boundary (core already imports session, so session must not import core).
|
|
137
|
+
*/
|
|
138
|
+
function messageTokens(m) {
|
|
139
|
+
if (m.tokenCount > 0)
|
|
140
|
+
return m.tokenCount;
|
|
141
|
+
return estimateMessageTokens(m.content);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Find the start index of the token-budgeted keep window by reverse-walking
|
|
145
|
+
* from the newest message. This is the session-layer analogue of
|
|
146
|
+
* core/context.ts fitToBudget's keep-window logic: accumulate tokens from the
|
|
147
|
+
* end until `keepRecentTokens` is exceeded, then everything older is eligible
|
|
148
|
+
* for compaction. At least the most recent message is always retained.
|
|
149
|
+
*/
|
|
150
|
+
function findKeepRecentStart(messages, keepRecentTokens) {
|
|
151
|
+
if (messages.length === 0)
|
|
152
|
+
return 0;
|
|
153
|
+
let used = 0;
|
|
154
|
+
let start = messages.length;
|
|
155
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
156
|
+
const m = messages[i];
|
|
157
|
+
if (!m)
|
|
158
|
+
continue;
|
|
159
|
+
const tc = messageTokens(m);
|
|
160
|
+
if (used + tc > keepRecentTokens)
|
|
161
|
+
break;
|
|
162
|
+
used += tc;
|
|
163
|
+
start = i;
|
|
164
|
+
}
|
|
165
|
+
// Always retain at least the most recent message.
|
|
166
|
+
return Math.min(start, messages.length - 1);
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 查找 session 最新的 compaction 摘要,用于增量摘要(P0-2)。
|
|
170
|
+
*
|
|
171
|
+
* 从全部 entries 中筛选 tag='compaction' 的记录,取最后一条(时间序最晚)的
|
|
172
|
+
* summary。无历史摘要时返回 undefined,buildCompactionPrompt 据此退回从零压缩模式。
|
|
173
|
+
*/
|
|
174
|
+
async function findPreviousSummary(handle, sessionId) {
|
|
175
|
+
const entries = await getEntries(handle, sessionId);
|
|
176
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
177
|
+
const entry = entries[i];
|
|
178
|
+
if (entry && '_tag' in entry && entry._tag === 'compaction') {
|
|
179
|
+
return entry.summary;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Compact a session: summarize old messages, archive them, keep recent ones.
|
|
186
|
+
*
|
|
187
|
+
* The keep window is driven by a token budget (`keepRecentTokens`), not a
|
|
188
|
+
* fixed message count — so compaction actually fires on a few long messages
|
|
189
|
+
* instead of silently no-op'ing when message count is small. The `summarizer`
|
|
190
|
+
* function is injected (the session layer never imports the LLM package).
|
|
191
|
+
*
|
|
192
|
+
* All destructive writes (archive + snapshot + delete + insert) run inside a
|
|
193
|
+
* single transaction so a mid-way failure cannot lose history: the originals
|
|
194
|
+
* are only deleted once the summary is durably stored.
|
|
195
|
+
*/
|
|
196
|
+
async function compactSession(handle, sessionId, summarizer, config) {
|
|
197
|
+
const messages = await getMessages(handle, sessionId);
|
|
198
|
+
const keepRecentTokens = config?.keepRecentTokens ?? 4000;
|
|
199
|
+
if (messages.length === 0) {
|
|
200
|
+
return { compacted: false, reason: 'too_few_messages' };
|
|
201
|
+
}
|
|
202
|
+
// Reverse-walk from the newest message to find the keep window boundary,
|
|
203
|
+
// driven by a token budget rather than a fixed message count.
|
|
204
|
+
const keepStart = findKeepRecentStart(messages, keepRecentTokens);
|
|
205
|
+
// Align the cut to a user-turn boundary so we never split an assistant
|
|
206
|
+
// reply from its tool results.
|
|
207
|
+
const cutPoint = findSafeCutPoint(messages, keepStart);
|
|
208
|
+
const compactMessages = messages.slice(0, cutPoint);
|
|
209
|
+
const keepMessages = messages.slice(cutPoint);
|
|
210
|
+
if (compactMessages.length === 0) {
|
|
211
|
+
return { compacted: false, reason: 'nothing_to_compact' };
|
|
212
|
+
}
|
|
213
|
+
// 查询当前 session 最新的 compaction 摘要,作为增量更新的基线(P0-2)。
|
|
214
|
+
// 这样连续多次压缩时,新摘要会基于已有摘要叠加而非从零重建,避免早期信息逐次丢失。
|
|
215
|
+
const previousSummary = await findPreviousSummary(handle, sessionId);
|
|
216
|
+
const prompt = buildCompactionPrompt(compactMessages, previousSummary);
|
|
217
|
+
// Run the (slow, network) summarizer OUTSIDE the transaction so we don't
|
|
218
|
+
// hold a DB transaction open across an LLM call.
|
|
219
|
+
const summary = await summarizer(prompt);
|
|
220
|
+
const compactionEntryId = generateId();
|
|
221
|
+
// Atomic rewrite: archive originals → upsert snapshots → delete originals →
|
|
222
|
+
// insert summary. If any step throws, the whole transaction rolls back and
|
|
223
|
+
// the original message history is left intact.
|
|
224
|
+
const { archiveId, fileSnapshotIds } = await handle.db.transaction(async (tx) => {
|
|
225
|
+
const txHandle = { db: tx, close: handle.close };
|
|
226
|
+
const archiveId = await archiveOriginalEntries(txHandle, sessionId, compactMessages, 'compaction', summary, compactionEntryId);
|
|
227
|
+
const fileSnapshotIds = [];
|
|
228
|
+
if (config?.preserveSnapshots !== false) {
|
|
229
|
+
const hotFiles = extractHotFiles(compactMessages);
|
|
230
|
+
for (const file of hotFiles) {
|
|
231
|
+
const id = await upsertFileSnapshot(txHandle, sessionId, file.path, file.content);
|
|
232
|
+
fileSnapshotIds.push(id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
await deleteEntriesByIds(txHandle, compactMessages.map((m) => m.id));
|
|
236
|
+
await insertEntry(txHandle, {
|
|
237
|
+
id: compactionEntryId,
|
|
238
|
+
sessionId,
|
|
239
|
+
tag: 'compaction',
|
|
240
|
+
content: {
|
|
241
|
+
summary,
|
|
242
|
+
originalEntryIds: compactMessages.map((m) => m.id),
|
|
243
|
+
archiveId,
|
|
244
|
+
},
|
|
245
|
+
tokenCount: estimateTokens(summary),
|
|
246
|
+
// Position the summary at the first compacted message's timestamp so it
|
|
247
|
+
// sorts BEFORE the kept recent messages under createdAt-ascending order.
|
|
248
|
+
createdAt: compactMessages[0] ? new Date(compactMessages[0].createdAt) : new Date(),
|
|
249
|
+
});
|
|
250
|
+
return { archiveId, fileSnapshotIds };
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
compacted: true,
|
|
254
|
+
summary,
|
|
255
|
+
archiveId,
|
|
256
|
+
fileSnapshots: fileSnapshotIds,
|
|
257
|
+
compactedCount: compactMessages.length,
|
|
258
|
+
keptCount: keepMessages.length,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
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 };
|