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.
- 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 +19 -0
- package/dist/cli/deps.js +57 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +149 -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 +95 -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 +84 -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 +25 -0
- package/dist/core/loop.js +651 -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 +114 -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 +115 -0
- package/dist/core/tool-exec.d.ts +20 -0
- package/dist/core/tool-exec.js +64 -0
- package/dist/core/types.d.ts +95 -0
- package/dist/core/types.js +1 -0
- package/dist/core/worktree.d.ts +24 -0
- package/dist/core/worktree.js +68 -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 +191 -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 +68 -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 +32 -0
- package/dist/server/agent-manager.js +54 -0
- package/dist/server/app.d.ts +5 -0
- package/dist/server/app.js +75 -0
- package/dist/server/context.d.ts +20 -0
- package/dist/server/context.js +47 -0
- package/dist/server/dev.d.ts +11 -0
- package/dist/server/dev.js +83 -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 +30 -0
- package/dist/server/permission/store.js +47 -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 +282 -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 +130 -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 +43 -0
- package/dist/server/server.js +197 -0
- package/dist/server/types.d.ts +73 -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 +27 -0
- package/dist/session/compaction.js +192 -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/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 +202 -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 +104 -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 +122 -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 +134 -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 +100 -8
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm';
|
|
2
|
+
import { sessions } from '../db/schema.js';
|
|
3
|
+
import { generateId } from '../shared/index.js';
|
|
4
|
+
/** Convert a DB row (with Date timestamps) to the shared Session type (with number timestamps). */
|
|
5
|
+
export function rowToSession(row) {
|
|
6
|
+
const created = row.createdAt instanceof Date ? row.createdAt.getTime() : new Date(row.createdAt).getTime();
|
|
7
|
+
const updated = row.updatedAt instanceof Date ? row.updatedAt.getTime() : new Date(row.updatedAt).getTime();
|
|
8
|
+
return {
|
|
9
|
+
id: row.id,
|
|
10
|
+
title: row.title,
|
|
11
|
+
parentId: row.parentId,
|
|
12
|
+
projectId: row.projectId,
|
|
13
|
+
branchPoint: row.branchPoint,
|
|
14
|
+
metadata: (row.metadata ?? {}),
|
|
15
|
+
agentType: row.agentType ?? null,
|
|
16
|
+
worktreePath: row.worktreePath ?? null,
|
|
17
|
+
createdAt: created,
|
|
18
|
+
updatedAt: updated,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Create a new root session. */
|
|
22
|
+
async function createSession(handle, title, projectId, agentType) {
|
|
23
|
+
const [row] = await handle.db
|
|
24
|
+
.insert(sessions)
|
|
25
|
+
.values({ title, projectId: projectId ?? null, agentType: agentType ?? null })
|
|
26
|
+
.returning();
|
|
27
|
+
if (!row)
|
|
28
|
+
throw new Error('Failed to insert session');
|
|
29
|
+
return rowToSession(row);
|
|
30
|
+
}
|
|
31
|
+
/** Get a session by id, or null if not found. */
|
|
32
|
+
async function getSession(handle, id) {
|
|
33
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
34
|
+
return row ? rowToSession(row) : null;
|
|
35
|
+
}
|
|
36
|
+
/** List all sessions. */
|
|
37
|
+
async function listSessions(handle) {
|
|
38
|
+
const rows = await handle.db.select().from(sessions);
|
|
39
|
+
return rows.map(rowToSession);
|
|
40
|
+
}
|
|
41
|
+
/** Delete a session (cascades to entries, archives, snapshots via FK). */
|
|
42
|
+
async function deleteSession(handle, id) {
|
|
43
|
+
await handle.db.delete(sessions).where(eq(sessions.id, id));
|
|
44
|
+
}
|
|
45
|
+
/** Update a session's title. */
|
|
46
|
+
async function updateSessionTitle(handle, id, title) {
|
|
47
|
+
await handle.db.update(sessions).set({ title, updatedAt: new Date() }).where(eq(sessions.id, id));
|
|
48
|
+
}
|
|
49
|
+
/** Bump updatedAt to now (used after appending messages). */
|
|
50
|
+
async function touchSession(handle, id) {
|
|
51
|
+
await handle.db.update(sessions).set({ updatedAt: new Date() }).where(eq(sessions.id, id));
|
|
52
|
+
}
|
|
53
|
+
/** 规格化工具集并计算前缀指纹。tools 顺序不影响指纹(按 name 排序)。 */
|
|
54
|
+
export function segmentFingerprint(systemPrompt, tools) {
|
|
55
|
+
const norm = JSON.stringify({
|
|
56
|
+
systemPrompt,
|
|
57
|
+
tools: [...tools]
|
|
58
|
+
.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters }))
|
|
59
|
+
.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0)),
|
|
60
|
+
});
|
|
61
|
+
let h = 5381;
|
|
62
|
+
for (let i = 0; i < norm.length; i++)
|
|
63
|
+
h = ((h << 5) + h + norm.charCodeAt(i)) | 0;
|
|
64
|
+
return (h >>> 0).toString(16);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 将旧 metadata.llmDetails 迁移为单个 legacy segment。
|
|
68
|
+
* - 无 llmDetails 或已有 segments → 原样返回。
|
|
69
|
+
* - 否则取首条的 systemPrompt/tools 作为段首快照,所有旧 detail 转为 calls,
|
|
70
|
+
* responseText 从 responseChunks 的 text 块拼接提取。
|
|
71
|
+
* 幂等:迁移后 llmDetails 字段被移除,不会重复迁移。
|
|
72
|
+
*/
|
|
73
|
+
export function migrateLegacyDetails(meta) {
|
|
74
|
+
if (meta.segments !== undefined)
|
|
75
|
+
return meta;
|
|
76
|
+
const legacy = meta.llmDetails;
|
|
77
|
+
if (!Array.isArray(legacy) || legacy.length === 0)
|
|
78
|
+
return meta;
|
|
79
|
+
const first = legacy[0];
|
|
80
|
+
const segment = {
|
|
81
|
+
id: generateId(),
|
|
82
|
+
fingerprint: segmentFingerprint(first.systemPrompt, first.tools ?? []),
|
|
83
|
+
provider: first.provider,
|
|
84
|
+
model: first.model,
|
|
85
|
+
systemPrompt: first.systemPrompt,
|
|
86
|
+
tools: first.tools ?? [],
|
|
87
|
+
startedAt: first.timestamp,
|
|
88
|
+
trigger: 'initial',
|
|
89
|
+
...(first.contextWindow !== undefined ? { contextWindow: first.contextWindow } : {}),
|
|
90
|
+
calls: legacy.map((d) => {
|
|
91
|
+
const detail = d;
|
|
92
|
+
return {
|
|
93
|
+
id: detail.id,
|
|
94
|
+
timestamp: detail.timestamp,
|
|
95
|
+
usage: detail.usage,
|
|
96
|
+
latency: detail.latency,
|
|
97
|
+
cost: detail.cost,
|
|
98
|
+
...(detail.thinking ? { thinking: detail.thinking } : {}),
|
|
99
|
+
responseText: (detail.responseChunks ?? [])
|
|
100
|
+
.map((c) => (c._tag === 'text' && typeof c.text === 'string' ? c.text : ''))
|
|
101
|
+
.join(''),
|
|
102
|
+
};
|
|
103
|
+
}),
|
|
104
|
+
};
|
|
105
|
+
const { llmDetails: _omit, ...rest } = meta;
|
|
106
|
+
return { ...rest, segments: [segment] };
|
|
107
|
+
}
|
|
108
|
+
/** 读取会话 metadata.segments(读取时顺带迁移旧 llmDetails)。 */
|
|
109
|
+
export async function getLLMSegments(handle, id) {
|
|
110
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
111
|
+
if (!row)
|
|
112
|
+
return [];
|
|
113
|
+
const meta = migrateLegacyDetails((row.metadata ?? {}));
|
|
114
|
+
return meta.segments ?? [];
|
|
115
|
+
}
|
|
116
|
+
/** 全量替换会话 metadata.segments(每轮 loop 结束写入;段数据轻量)。 */
|
|
117
|
+
export async function saveLLMSegments(handle, id, segments) {
|
|
118
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
119
|
+
if (!row)
|
|
120
|
+
return;
|
|
121
|
+
const meta = migrateLegacyDetails((row.metadata ?? {}));
|
|
122
|
+
const { segments: _omit, ...rest } = meta;
|
|
123
|
+
const next = { ...rest, segments };
|
|
124
|
+
await handle.db
|
|
125
|
+
.update(sessions)
|
|
126
|
+
.set({ metadata: next, updatedAt: new Date() })
|
|
127
|
+
.where(eq(sessions.id, id));
|
|
128
|
+
}
|
|
129
|
+
/** 更新会话 metadata.lastRun(agent run 开始/结束时写入;重启后检测中断用)。 */
|
|
130
|
+
async function updateSessionLastRun(handle, id, lastRun) {
|
|
131
|
+
const [row] = await handle.db.select().from(sessions).where(eq(sessions.id, id));
|
|
132
|
+
if (!row)
|
|
133
|
+
return;
|
|
134
|
+
const meta = (row.metadata ?? {});
|
|
135
|
+
await handle.db
|
|
136
|
+
.update(sessions)
|
|
137
|
+
.set({ metadata: { ...meta, lastRun }, updatedAt: new Date() })
|
|
138
|
+
.where(eq(sessions.id, id));
|
|
139
|
+
}
|
|
140
|
+
async function listSessionsByProject(handle, projectId) {
|
|
141
|
+
const rows = await handle.db.select().from(sessions).where(eq(sessions.projectId, projectId));
|
|
142
|
+
return rows.map(rowToSession);
|
|
143
|
+
}
|
|
144
|
+
export { createSession, deleteSession, getSession, listSessions, listSessionsByProject, touchSession, updateSessionLastRun, updateSessionTitle, };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { FileSnapshot } from './types.js';
|
|
3
|
+
/** Create or update a file snapshot. Always creates a new version. Returns the snapshot id. */
|
|
4
|
+
declare function upsertFileSnapshot(handle: DB, sessionId: string, filePath: string, content: string): Promise<string>;
|
|
5
|
+
/** Get all snapshots for a session. */
|
|
6
|
+
declare function getFileSnapshots(handle: DB, sessionId: string): Promise<FileSnapshot[]>;
|
|
7
|
+
/** Get the latest snapshot for a specific file, or null. */
|
|
8
|
+
declare function getLatestFileSnapshot(handle: DB, sessionId: string, filePath: string): Promise<FileSnapshot | null>;
|
|
9
|
+
/** Quick check: return cached file content if a snapshot exists, else null. */
|
|
10
|
+
declare function checkFileSnapshot(handle: DB, sessionId: string, filePath: string): Promise<string | null>;
|
|
11
|
+
export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { and, desc, eq } from 'drizzle-orm';
|
|
3
|
+
import { fileSnapshots } from '../db/schema.js';
|
|
4
|
+
import { estimateTokens } from './token.js';
|
|
5
|
+
function rowToSnapshot(row) {
|
|
6
|
+
const createdAt = row.createdAt instanceof Date ? row.createdAt.getTime() : new Date(row.createdAt).getTime();
|
|
7
|
+
return {
|
|
8
|
+
id: row.id,
|
|
9
|
+
sessionId: row.sessionId,
|
|
10
|
+
filePath: row.filePath,
|
|
11
|
+
content: row.content,
|
|
12
|
+
contentHash: row.contentHash,
|
|
13
|
+
tokenCount: row.tokenCount ?? 0,
|
|
14
|
+
version: row.version ?? 1,
|
|
15
|
+
createdAt,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/** Create or update a file snapshot. Always creates a new version. Returns the snapshot id. */
|
|
19
|
+
async function upsertFileSnapshot(handle, sessionId, filePath, content) {
|
|
20
|
+
const contentHash = createHash('sha256').update(content).digest('hex');
|
|
21
|
+
const tokenCount = estimateTokens(content);
|
|
22
|
+
const [existing] = await handle.db
|
|
23
|
+
.select()
|
|
24
|
+
.from(fileSnapshots)
|
|
25
|
+
.where(and(eq(fileSnapshots.sessionId, sessionId), eq(fileSnapshots.filePath, filePath)))
|
|
26
|
+
.orderBy(desc(fileSnapshots.version))
|
|
27
|
+
.limit(1);
|
|
28
|
+
const version = (existing?.version ?? 0) + 1;
|
|
29
|
+
const [row] = await handle.db
|
|
30
|
+
.insert(fileSnapshots)
|
|
31
|
+
.values({ sessionId, filePath, content, contentHash, tokenCount, version })
|
|
32
|
+
.returning();
|
|
33
|
+
if (!row)
|
|
34
|
+
throw new Error('Failed to insert file snapshot');
|
|
35
|
+
return row.id;
|
|
36
|
+
}
|
|
37
|
+
/** Get all snapshots for a session. */
|
|
38
|
+
async function getFileSnapshots(handle, sessionId) {
|
|
39
|
+
const rows = await handle.db
|
|
40
|
+
.select()
|
|
41
|
+
.from(fileSnapshots)
|
|
42
|
+
.where(eq(fileSnapshots.sessionId, sessionId));
|
|
43
|
+
return rows.map(rowToSnapshot);
|
|
44
|
+
}
|
|
45
|
+
/** Get the latest snapshot for a specific file, or null. */
|
|
46
|
+
async function getLatestFileSnapshot(handle, sessionId, filePath) {
|
|
47
|
+
const [row] = await handle.db
|
|
48
|
+
.select()
|
|
49
|
+
.from(fileSnapshots)
|
|
50
|
+
.where(and(eq(fileSnapshots.sessionId, sessionId), eq(fileSnapshots.filePath, filePath)))
|
|
51
|
+
.orderBy(desc(fileSnapshots.version))
|
|
52
|
+
.limit(1);
|
|
53
|
+
return row ? rowToSnapshot(row) : null;
|
|
54
|
+
}
|
|
55
|
+
/** Quick check: return cached file content if a snapshot exists, else null. */
|
|
56
|
+
async function checkFileSnapshot(handle, sessionId, filePath) {
|
|
57
|
+
const snapshot = await getLatestFileSnapshot(handle, sessionId, filePath);
|
|
58
|
+
return snapshot?.content ?? null;
|
|
59
|
+
}
|
|
60
|
+
export { checkFileSnapshot, getFileSnapshots, getLatestFileSnapshot, upsertFileSnapshot };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { CompactionResult, SquashConfig, Summarizer } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Squash the most recent `count` messages into a summary, keeping `keepRecent` verbatim.
|
|
5
|
+
* Similar to `git rebase -i + squash`: compresses recent interactions while preserving
|
|
6
|
+
* the conversation prefix and a small tail for cache stability.
|
|
7
|
+
*/
|
|
8
|
+
declare function squashRecent(handle: DB, sessionId: string, count: number, summarizer: Summarizer, config?: Partial<SquashConfig>): Promise<CompactionResult>;
|
|
9
|
+
export { squashRecent };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { generateId } from '../shared/index.js';
|
|
2
|
+
import { archiveOriginalEntries } from './archive.js';
|
|
3
|
+
import { extractHotFiles } from './compaction.js';
|
|
4
|
+
import { deleteEntriesByIds, getMessages, insertEntry } from './message.js';
|
|
5
|
+
import { upsertFileSnapshot } from './snapshot.js';
|
|
6
|
+
import { estimateTokens } from './token.js';
|
|
7
|
+
const DEFAULT_SQUASH_CONFIG = {
|
|
8
|
+
keepRecent: 2,
|
|
9
|
+
preserveFileSnapshots: true,
|
|
10
|
+
archiveOriginal: true,
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Squash the most recent `count` messages into a summary, keeping `keepRecent` verbatim.
|
|
14
|
+
* Similar to `git rebase -i + squash`: compresses recent interactions while preserving
|
|
15
|
+
* the conversation prefix and a small tail for cache stability.
|
|
16
|
+
*/
|
|
17
|
+
async function squashRecent(handle, sessionId, count, summarizer, config) {
|
|
18
|
+
const cfg = { ...DEFAULT_SQUASH_CONFIG, ...config };
|
|
19
|
+
const messages = await getMessages(handle, sessionId);
|
|
20
|
+
if (messages.length < count + cfg.keepRecent) {
|
|
21
|
+
return { compacted: false, reason: 'too_few_messages' };
|
|
22
|
+
}
|
|
23
|
+
const tailStart = messages.length - cfg.keepRecent;
|
|
24
|
+
const squashStart = messages.length - count;
|
|
25
|
+
const toSquash = messages.slice(squashStart, tailStart);
|
|
26
|
+
const keepTail = messages.slice(tailStart);
|
|
27
|
+
if (toSquash.length === 0) {
|
|
28
|
+
return { compacted: false, reason: 'nothing_to_compact' };
|
|
29
|
+
}
|
|
30
|
+
const history = toSquash
|
|
31
|
+
.map((m) => `[${m.role}] ${m.content.map((p) => (p._tag === 'text' ? p.text : JSON.stringify(p))).join(' ')}`)
|
|
32
|
+
.join('\n');
|
|
33
|
+
const prompt = `将以下最近的交互压缩为简洁摘要,保留关键决策和上下文,丢弃冗余细节。
|
|
34
|
+
重点保留:修改了哪些文件、做了什么决策、当前进度、下一步计划。
|
|
35
|
+
|
|
36
|
+
## 最近操作
|
|
37
|
+
[简要描述做了什么]
|
|
38
|
+
|
|
39
|
+
## 修改的文件
|
|
40
|
+
- path: 变更描述
|
|
41
|
+
|
|
42
|
+
## 关键决策
|
|
43
|
+
[做出的重要决策]
|
|
44
|
+
|
|
45
|
+
## 当前状态
|
|
46
|
+
[进度和下一步]
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
交互历史:
|
|
50
|
+
${history}`;
|
|
51
|
+
const summary = await summarizer(prompt);
|
|
52
|
+
const squashEntryId = generateId();
|
|
53
|
+
const archiveId = cfg.archiveOriginal
|
|
54
|
+
? await archiveOriginalEntries(handle, sessionId, toSquash, 'squash', summary, squashEntryId)
|
|
55
|
+
: generateId();
|
|
56
|
+
const fileSnapshotIds = [];
|
|
57
|
+
if (cfg.preserveFileSnapshots) {
|
|
58
|
+
const hotFiles = extractHotFiles(toSquash);
|
|
59
|
+
for (const file of hotFiles) {
|
|
60
|
+
const id = await upsertFileSnapshot(handle, sessionId, file.path, file.content);
|
|
61
|
+
fileSnapshotIds.push(id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
await deleteEntriesByIds(handle, toSquash.map((m) => m.id));
|
|
65
|
+
await insertEntry(handle, {
|
|
66
|
+
id: squashEntryId,
|
|
67
|
+
sessionId,
|
|
68
|
+
tag: 'squash',
|
|
69
|
+
content: {
|
|
70
|
+
summary,
|
|
71
|
+
squashedEntryIds: toSquash.map((m) => m.id),
|
|
72
|
+
archiveId,
|
|
73
|
+
},
|
|
74
|
+
tokenCount: estimateTokens(summary),
|
|
75
|
+
// Position at the first squashed message's timestamp so it sorts between
|
|
76
|
+
// the prefix and the kept tail under createdAt-ascending order.
|
|
77
|
+
createdAt: toSquash[0] ? new Date(toSquash[0].createdAt) : new Date(),
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
compacted: true,
|
|
81
|
+
summary,
|
|
82
|
+
archiveId,
|
|
83
|
+
fileSnapshots: fileSnapshotIds,
|
|
84
|
+
compactedCount: toSquash.length,
|
|
85
|
+
keptCount: keepTail.length,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export { squashRecent };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MessageContent } from '../shared/types/message.js';
|
|
2
|
+
/**
|
|
3
|
+
* CJK-aware token estimate.
|
|
4
|
+
* Chinese/CJK characters ≈ 2 tokens each (denser encoding).
|
|
5
|
+
* Other characters ≈ 4 chars/token (standard heuristic).
|
|
6
|
+
*/
|
|
7
|
+
declare const estimateTokens: (text: string) => number;
|
|
8
|
+
/** Sum token estimates across all parts of a message's content array. */
|
|
9
|
+
declare const estimateMessageTokens: (content: MessageContent[]) => number;
|
|
10
|
+
export { estimateMessageTokens, estimateTokens };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CJK-aware token estimate.
|
|
3
|
+
* Chinese/CJK characters ≈ 2 tokens each (denser encoding).
|
|
4
|
+
* Other characters ≈ 4 chars/token (standard heuristic).
|
|
5
|
+
*/
|
|
6
|
+
const estimateTokens = (text) => {
|
|
7
|
+
if (text.length === 0)
|
|
8
|
+
return 0;
|
|
9
|
+
const cjkCount = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g)?.length ?? 0;
|
|
10
|
+
const otherCount = text.length - cjkCount;
|
|
11
|
+
return Math.ceil(cjkCount * 2 + otherCount / 4);
|
|
12
|
+
};
|
|
13
|
+
/** Sum token estimates across all parts of a message's content array. */
|
|
14
|
+
const estimateMessageTokens = (content) => {
|
|
15
|
+
let total = 0;
|
|
16
|
+
for (const part of content) {
|
|
17
|
+
switch (part._tag) {
|
|
18
|
+
case 'text':
|
|
19
|
+
case 'thinking':
|
|
20
|
+
case 'steering':
|
|
21
|
+
total += estimateTokens(part.text);
|
|
22
|
+
break;
|
|
23
|
+
case 'tool_call':
|
|
24
|
+
total += estimateTokens(JSON.stringify(part.input));
|
|
25
|
+
break;
|
|
26
|
+
case 'tool_result':
|
|
27
|
+
total += estimateTokens(JSON.stringify(part.output));
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return total;
|
|
32
|
+
};
|
|
33
|
+
export { estimateMessageTokens, estimateTokens };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { MessageRole } from '../shared/types/base.js';
|
|
2
|
+
import type { ChatMessage } from '../shared/types/llm.js';
|
|
3
|
+
import type { Message, MessageContent, Session, SessionMetadata } from '../shared/types/message.js';
|
|
4
|
+
export type { MessageRole } from '../shared/types/base.js';
|
|
5
|
+
export type { ChatMessage, Message, MessageContent, Session, SessionMetadata };
|
|
6
|
+
/** A compaction summary entry — replaces compacted messages with a summary. */
|
|
7
|
+
type CompactionEntry = {
|
|
8
|
+
_tag: 'compaction';
|
|
9
|
+
id: string;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
summary: string;
|
|
12
|
+
originalEntryIds: string[];
|
|
13
|
+
archiveId: string;
|
|
14
|
+
tokenCount: number;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
};
|
|
17
|
+
/** A squash entry — compresses recent interactions into a summary. */
|
|
18
|
+
type SquashEntry = {
|
|
19
|
+
_tag: 'squash';
|
|
20
|
+
id: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
squashedEntryIds: string[];
|
|
24
|
+
archiveId: string;
|
|
25
|
+
tokenCount: number;
|
|
26
|
+
createdAt: number;
|
|
27
|
+
};
|
|
28
|
+
/** Records that a session was forked from another. */
|
|
29
|
+
type BranchSummaryEntry = {
|
|
30
|
+
_tag: 'branch_summary';
|
|
31
|
+
id: string;
|
|
32
|
+
sessionId: string;
|
|
33
|
+
summary: string;
|
|
34
|
+
sourceSessionId: string;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
};
|
|
37
|
+
/** A steering instruction injected mid-conversation. */
|
|
38
|
+
type SteeringEntry = {
|
|
39
|
+
_tag: 'steering';
|
|
40
|
+
id: string;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
content: string;
|
|
43
|
+
createdAt: number;
|
|
44
|
+
};
|
|
45
|
+
/** All session entries, ordered chronologically. Narrow via `'_tag' in entry`. */
|
|
46
|
+
type SessionEntry = Message | CompactionEntry | SquashEntry | BranchSummaryEntry | SteeringEntry;
|
|
47
|
+
/** Input for `appendMessage` — role + content; id/timestamps auto-generated. */
|
|
48
|
+
type MessageInput = {
|
|
49
|
+
role: MessageRole;
|
|
50
|
+
content: MessageContent[];
|
|
51
|
+
tokenCount?: number;
|
|
52
|
+
};
|
|
53
|
+
/** A hot file detected from tool-call history. */
|
|
54
|
+
type HotFile = {
|
|
55
|
+
path: string;
|
|
56
|
+
content: string;
|
|
57
|
+
tokenCount: number;
|
|
58
|
+
accessCount: number;
|
|
59
|
+
};
|
|
60
|
+
/** Configuration for compaction. */
|
|
61
|
+
type CompactionConfig = {
|
|
62
|
+
/** Token budget for the recent-message keep window. Messages are retained
|
|
63
|
+
* from the newest backward until their cumulative token count exceeds this
|
|
64
|
+
* value; everything older is summarized. Drives compaction by tokens, not
|
|
65
|
+
* by a fixed message count. */
|
|
66
|
+
keepRecentTokens: number;
|
|
67
|
+
preserveSnapshots: boolean;
|
|
68
|
+
};
|
|
69
|
+
/** Result of a compaction or squash operation. */
|
|
70
|
+
type CompactionResult = {
|
|
71
|
+
compacted: true;
|
|
72
|
+
summary: string;
|
|
73
|
+
archiveId: string;
|
|
74
|
+
fileSnapshots: string[];
|
|
75
|
+
compactedCount: number;
|
|
76
|
+
keptCount: number;
|
|
77
|
+
} | {
|
|
78
|
+
compacted: false;
|
|
79
|
+
reason: 'too_few_messages' | 'nothing_to_compact';
|
|
80
|
+
};
|
|
81
|
+
/** Configuration for squash. */
|
|
82
|
+
type SquashConfig = {
|
|
83
|
+
keepRecent: number;
|
|
84
|
+
preserveFileSnapshots: boolean;
|
|
85
|
+
archiveOriginal: boolean;
|
|
86
|
+
};
|
|
87
|
+
/** Injected summarizer — the session layer does NOT import the LLM package. */
|
|
88
|
+
type Summarizer = (prompt: string) => Promise<string>;
|
|
89
|
+
/** Parsed `@[archive:<id>]` / `@[squash:<n>]` reference. */
|
|
90
|
+
type ArchiveRef = {
|
|
91
|
+
type: 'archive' | 'squash';
|
|
92
|
+
id: string;
|
|
93
|
+
};
|
|
94
|
+
/** A node in the session tree. */
|
|
95
|
+
type SessionTreeNode = {
|
|
96
|
+
session: Session;
|
|
97
|
+
children: SessionTreeNode[];
|
|
98
|
+
};
|
|
99
|
+
/** A decoded compaction archive row. */
|
|
100
|
+
type CompactionArchive = {
|
|
101
|
+
id: string;
|
|
102
|
+
sessionId: string;
|
|
103
|
+
compactionId: string;
|
|
104
|
+
archiveType: 'compaction' | 'squash';
|
|
105
|
+
originalEntries: SessionEntry[];
|
|
106
|
+
summary: string;
|
|
107
|
+
tokenCount: number;
|
|
108
|
+
searchableText: string;
|
|
109
|
+
createdAt: number;
|
|
110
|
+
};
|
|
111
|
+
/** A decoded file snapshot row. */
|
|
112
|
+
type FileSnapshot = {
|
|
113
|
+
id: string;
|
|
114
|
+
sessionId: string;
|
|
115
|
+
filePath: string;
|
|
116
|
+
content: string;
|
|
117
|
+
contentHash: string;
|
|
118
|
+
tokenCount: number;
|
|
119
|
+
version: number;
|
|
120
|
+
createdAt: number;
|
|
121
|
+
};
|
|
122
|
+
export type { ArchiveRef, BranchSummaryEntry, CompactionArchive, CompactionConfig, CompactionEntry, CompactionResult, FileSnapshot, HotFile, MessageInput, SessionEntry, SessionTreeNode, SquashConfig, SquashEntry, SteeringEntry, Summarizer, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generateId, now } from './utils.js';
|