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,115 @@
|
|
|
1
|
+
import { chat as defaultChat } from '../llm/provider.js';
|
|
2
|
+
import { updateSessionTitle } from '../session/session.js';
|
|
3
|
+
/** 会话默认占位标题;仍是该值时才有资格自动生成。 */
|
|
4
|
+
const DEFAULT_SESSION_TITLE = 'New Session';
|
|
5
|
+
/** 标题最长字符数,超出截断并加省略号。 */
|
|
6
|
+
const TITLE_MAX_LENGTH = 100;
|
|
7
|
+
/**
|
|
8
|
+
* 标题生成系统提示。直接移植自 opencode 的 agent/prompt/title.txt(同语义):
|
|
9
|
+
* 要求与用户消息同语言、语法自然、聚焦主题、保留技术术语,且对极简输入也
|
|
10
|
+
* 给出有意义的标题。中文消息 → 中文标题。
|
|
11
|
+
*/
|
|
12
|
+
const TITLE_PROMPT = `You are a title generator. You output ONLY a thread title. Nothing else.
|
|
13
|
+
|
|
14
|
+
<task>
|
|
15
|
+
Generate a brief title that would help the user find this conversation later.
|
|
16
|
+
|
|
17
|
+
Follow all rules in <rules>
|
|
18
|
+
Use the <examples> so you know what a good title looks like.
|
|
19
|
+
Your output must be:
|
|
20
|
+
- A single line
|
|
21
|
+
- ≤50 characters
|
|
22
|
+
- No explanations
|
|
23
|
+
</task>
|
|
24
|
+
|
|
25
|
+
<rules>
|
|
26
|
+
- you MUST use the same language as the user message you are summarizing
|
|
27
|
+
- Title must be grammatically correct and read naturally - no word salad
|
|
28
|
+
- Never include tool names in the title (e.g. "read tool", "bash tool", "edit tool")
|
|
29
|
+
- Focus on the main topic or question the user needs to retrieve
|
|
30
|
+
- Vary your phrasing - avoid repetitive patterns like always starting with "Analyzing"
|
|
31
|
+
- When a file is mentioned, focus on WHAT the user wants to do WITH the file, not just that they shared it
|
|
32
|
+
- Keep exact: technical terms, numbers, filenames, HTTP codes
|
|
33
|
+
- Remove: the, this, my, a, an
|
|
34
|
+
- Never assume tech stack
|
|
35
|
+
- Never use tools
|
|
36
|
+
- NEVER respond to questions, just generate a title for the conversation
|
|
37
|
+
- The title should NEVER include "summarizing" or "generating" when generating a title
|
|
38
|
+
- DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
|
|
39
|
+
- Always output something meaningful, even if the input is minimal.
|
|
40
|
+
- If the user message is short or conversational (e.g. "hello", "lol", "what's up", "hey"):
|
|
41
|
+
→ create a title that reflects the user's tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
|
|
42
|
+
</rules>
|
|
43
|
+
|
|
44
|
+
<examples>
|
|
45
|
+
"debug 500 errors in production" → Debugging production 500 errors
|
|
46
|
+
"refactor user service" → Refactoring user service
|
|
47
|
+
"why is app.js failing" → app.js failure investigation
|
|
48
|
+
"implement rate limiting" → Rate limiting implementation
|
|
49
|
+
"how do I connect postgres to my API" → Postgres API connection
|
|
50
|
+
"best practices for React hooks" → React hooks best practices
|
|
51
|
+
"@src/auth.ts can you add refresh token support" → Auth refresh token support
|
|
52
|
+
"@utils/parser.ts this is broken" → Parser bug fix
|
|
53
|
+
"look at @config.json" → Config review
|
|
54
|
+
"@App.tsx add dark mode toggle" → Dark mode toggle in App
|
|
55
|
+
</examples>`;
|
|
56
|
+
/** 解析标题生成用的 (provider, model):smol 角色优先,否则用传入的实际 chat 模型。 */
|
|
57
|
+
function resolveTitleModel(config, chatProvider, chatModel) {
|
|
58
|
+
const smol = config.roleRouting?.smol;
|
|
59
|
+
if (smol)
|
|
60
|
+
return { provider: smol.provider, model: smol.model };
|
|
61
|
+
return { provider: chatProvider, model: chatModel };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 清理模型原始输出为标题:剥除 <think> 标签(推理模型可能输出),取首个非空
|
|
65
|
+
* trim 行,超长截断加省略号。参考 opencode 的标题清理逻辑。
|
|
66
|
+
*/
|
|
67
|
+
function cleanTitle(raw) {
|
|
68
|
+
const noThink = raw.replace(/<think>[\s\S]*?<\/think>\s*/g, '');
|
|
69
|
+
const firstLine = noThink
|
|
70
|
+
.split('\n')
|
|
71
|
+
.map((l) => l.trim())
|
|
72
|
+
.find((l) => l.length > 0) ?? '';
|
|
73
|
+
if (firstLine.length > TITLE_MAX_LENGTH) {
|
|
74
|
+
return `${firstLine.slice(0, TITLE_MAX_LENGTH - 3)}...`;
|
|
75
|
+
}
|
|
76
|
+
return firstLine;
|
|
77
|
+
}
|
|
78
|
+
/** 会话标题是否仍是默认占位值。 */
|
|
79
|
+
function isDefaultTitle(title) {
|
|
80
|
+
return title === DEFAULT_SESSION_TITLE;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 为会话生成标题并持久化。fire-and-forget 语义:任何错误都被吞掉,
|
|
84
|
+
* 绝不向调用方抛出,绝不覆盖一个已存在的非默认标题。
|
|
85
|
+
*
|
|
86
|
+
* @param deps 最小依赖(含可注入的 chatFn)
|
|
87
|
+
* @param sessionId 目标会话 id
|
|
88
|
+
* @param firstMessage 第一条用户消息原文
|
|
89
|
+
* @param chatProvider 会话实际使用的 LLM provider(AgentConfig.provider)
|
|
90
|
+
* @param chatModel 会话实际使用的 LLM model(AgentConfig.model)
|
|
91
|
+
*/
|
|
92
|
+
async function generateSessionTitle(deps, sessionId, firstMessage, chatProvider, chatModel) {
|
|
93
|
+
const { db, llmRegistry, config, chatFn = defaultChat } = deps;
|
|
94
|
+
try {
|
|
95
|
+
const { provider, model } = resolveTitleModel(config, chatProvider, chatModel);
|
|
96
|
+
const messages = [
|
|
97
|
+
{ role: 'user', content: `Generate a title for this conversation:\n${firstMessage}` },
|
|
98
|
+
];
|
|
99
|
+
const request = {
|
|
100
|
+
model,
|
|
101
|
+
messages,
|
|
102
|
+
stream: true,
|
|
103
|
+
system: TITLE_PROMPT,
|
|
104
|
+
};
|
|
105
|
+
const text = await chatFn({ registry: llmRegistry }, request, { provider, model });
|
|
106
|
+
const title = cleanTitle(text);
|
|
107
|
+
if (title.length === 0)
|
|
108
|
+
return;
|
|
109
|
+
await updateSessionTitle(db, sessionId, title);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// 标题生成是尽力而为的辅助功能:失败不阻塞主对话流。
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
export { DEFAULT_SESSION_TITLE, generateSessionTitle, isDefaultTitle };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { HookRunner } from '../plugins/types.js';
|
|
2
|
+
import type { ToolContext, ToolResult } from '../shared/types/tool.js';
|
|
3
|
+
import type { PermissionChecker, ToolRegistry } from '../tools/types.js';
|
|
4
|
+
type CollectedToolCall = {
|
|
5
|
+
id: string;
|
|
6
|
+
tool: string;
|
|
7
|
+
input: unknown;
|
|
8
|
+
};
|
|
9
|
+
type ToolCallResult = {
|
|
10
|
+
id: string;
|
|
11
|
+
result: ToolResult;
|
|
12
|
+
};
|
|
13
|
+
declare function executeToolCall(registry: ToolRegistry, permission: PermissionChecker, ctx: ToolContext, name: string, input: unknown, hookRunner?: HookRunner): Promise<ToolResult>;
|
|
14
|
+
declare function partitionByConflict(calls: CollectedToolCall[]): {
|
|
15
|
+
parallel: CollectedToolCall[];
|
|
16
|
+
serial: CollectedToolCall[];
|
|
17
|
+
};
|
|
18
|
+
declare function executeToolCalls(registry: ToolRegistry, permission: PermissionChecker, ctx: ToolContext, calls: CollectedToolCall[], hookRunner?: HookRunner): Promise<ToolCallResult[]>;
|
|
19
|
+
export type { CollectedToolCall, ToolCallResult };
|
|
20
|
+
export { executeToolCall, executeToolCalls, partitionByConflict };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { executeTool } from '../tools/executor.js';
|
|
2
|
+
const WRITE_TOOLS = new Set(['write', 'edit', 'bash']);
|
|
3
|
+
async function executeToolCall(registry, permission, ctx, name, input, hookRunner) {
|
|
4
|
+
let effectiveInput = input;
|
|
5
|
+
if (hookRunner) {
|
|
6
|
+
const hookResult = await hookRunner.runHooks('tool:before', { tool: name, input, ctx });
|
|
7
|
+
if (hookResult === false) {
|
|
8
|
+
return { _tag: 'error', error: `Tool "${name}" aborted by hook` };
|
|
9
|
+
}
|
|
10
|
+
effectiveInput = hookResult.input;
|
|
11
|
+
}
|
|
12
|
+
const result = await executeTool(registry, name, effectiveInput, ctx, permission);
|
|
13
|
+
if (hookRunner) {
|
|
14
|
+
await hookRunner.fireHooks('tool:after', { tool: name, input: effectiveInput, result, ctx });
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
function partitionByConflict(calls) {
|
|
19
|
+
const parallel = [];
|
|
20
|
+
const serial = [];
|
|
21
|
+
const writePaths = new Set();
|
|
22
|
+
for (const tc of calls) {
|
|
23
|
+
if (WRITE_TOOLS.has(tc.tool)) {
|
|
24
|
+
const input = tc.input;
|
|
25
|
+
const path = (input?.path ?? input?.file);
|
|
26
|
+
if (path && writePaths.has(path)) {
|
|
27
|
+
serial.push(tc);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
if (path)
|
|
31
|
+
writePaths.add(path);
|
|
32
|
+
parallel.push(tc);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
parallel.push(tc);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { parallel, serial };
|
|
40
|
+
}
|
|
41
|
+
async function executeToolCalls(registry, permission, ctx, calls, hookRunner) {
|
|
42
|
+
const { parallel, serial } = partitionByConflict(calls);
|
|
43
|
+
const results = [];
|
|
44
|
+
if (parallel.length > 0) {
|
|
45
|
+
const settled = await Promise.allSettled(parallel.map(async (tc) => ({
|
|
46
|
+
id: tc.id,
|
|
47
|
+
result: await executeToolCall(registry, permission, ctx, tc.tool, tc.input, hookRunner),
|
|
48
|
+
})));
|
|
49
|
+
for (const s of settled) {
|
|
50
|
+
if (s.status === 'fulfilled') {
|
|
51
|
+
results.push(s.value);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
results.push({ id: 'unknown', result: { _tag: 'error', error: String(s.reason) } });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const tc of serial) {
|
|
59
|
+
const result = await executeToolCall(registry, permission, ctx, tc.tool, tc.input, hookRunner);
|
|
60
|
+
results.push({ id: tc.id, result });
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
export { executeToolCall, executeToolCalls, partitionByConflict };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { DB } from '../db/client.js';
|
|
2
|
+
import type { chat as ChatFn } from '../llm/provider.js';
|
|
3
|
+
import type { Registry } from '../llm/registry.js';
|
|
4
|
+
import type { HookRunner } from '../plugins/types.js';
|
|
5
|
+
import type { AgentConfig, AgentError, AgentEvent, AgentState, AgentStatus, LLMSegment, PendingToolCall, TokenBudget } from '../shared/types/agent.js';
|
|
6
|
+
import type { ChatTool } from '../shared/types/llm.js';
|
|
7
|
+
import type { DebugTransport, ToolDef, URLRegistry } from '../shared/types/tool.js';
|
|
8
|
+
import type { PermissionChecker, ToolRegistry } from '../tools/types.js';
|
|
9
|
+
import type { AgentRegistry } from './agents/types.js';
|
|
10
|
+
import type { Config } from './config.js';
|
|
11
|
+
/** Runtime services injected into every core function (DI pattern). */
|
|
12
|
+
type AgentDependencies = {
|
|
13
|
+
db: DB;
|
|
14
|
+
llmRegistry: Registry;
|
|
15
|
+
toolRegistry: ToolRegistry;
|
|
16
|
+
permission: PermissionChecker;
|
|
17
|
+
config: Config;
|
|
18
|
+
cwd: string;
|
|
19
|
+
hookRunner?: HookRunner;
|
|
20
|
+
/** Optional URL resolver registry wired into tool contexts (spec §3.10). */
|
|
21
|
+
urlRegistry?: URLRegistry;
|
|
22
|
+
/** Optional prompt registry for dynamic system-prompt assembly (spec §17).
|
|
23
|
+
* When provided, the agent loop renders the prompt from it; otherwise the
|
|
24
|
+
* default built-in registry is used. Plugins register sections here. */
|
|
25
|
+
promptRegistry?: PromptRegistry;
|
|
26
|
+
/** 可注入的标题生成 chat 实现(测试用);省略走真实 llm/provider。 */
|
|
27
|
+
titleChatFn?: typeof ChatFn;
|
|
28
|
+
/** 可选调试适配器 spawn 钩子(spec §21):注入后 debug_* 工具可用。
|
|
29
|
+
* 接收 debug_start 的配置,返回适配器进程的 stdio transport。 */
|
|
30
|
+
debugSpawn?: (config: unknown) => DebugTransport;
|
|
31
|
+
/** Agent 类型注册表(spec: multi-agent-design)。注入后 task 工具可按类型派发。 */
|
|
32
|
+
agentRegistry?: AgentRegistry;
|
|
33
|
+
};
|
|
34
|
+
type ProjectInfo = {
|
|
35
|
+
name: string;
|
|
36
|
+
language: string;
|
|
37
|
+
framework?: string;
|
|
38
|
+
rootDir: string;
|
|
39
|
+
gitBranch?: string;
|
|
40
|
+
};
|
|
41
|
+
type PromptContext = {
|
|
42
|
+
tools: ToolDef[];
|
|
43
|
+
config: AgentConfig;
|
|
44
|
+
projectInfo: ProjectInfo;
|
|
45
|
+
skills?: string[];
|
|
46
|
+
cwd?: string;
|
|
47
|
+
};
|
|
48
|
+
/** Build context for dynamic prompt assembly (spec §17). */
|
|
49
|
+
type PromptBuildContext = {
|
|
50
|
+
tools: ToolDef[];
|
|
51
|
+
config: AgentConfig;
|
|
52
|
+
projectInfo: ProjectInfo;
|
|
53
|
+
skills?: string[];
|
|
54
|
+
agents?: string[];
|
|
55
|
+
cwd?: string;
|
|
56
|
+
};
|
|
57
|
+
/** A dynamically composable system-prompt section (spec §17). */
|
|
58
|
+
type PromptSection = {
|
|
59
|
+
id: string;
|
|
60
|
+
title?: string;
|
|
61
|
+
/** Static body. Overridden by `render` when present. */
|
|
62
|
+
content: string;
|
|
63
|
+
/** Lower priority sorts earlier. */
|
|
64
|
+
priority: number;
|
|
65
|
+
/** Omit the section when false. */
|
|
66
|
+
condition?: (ctx: PromptBuildContext) => boolean;
|
|
67
|
+
/** Dynamic body; takes precedence over `content`. */
|
|
68
|
+
render?: (ctx: PromptBuildContext) => string;
|
|
69
|
+
};
|
|
70
|
+
/** Mutable registry of prompt sections, keyed by id (later registration wins). */
|
|
71
|
+
type PromptRegistry = {
|
|
72
|
+
sections: Map<string, PromptSection>;
|
|
73
|
+
};
|
|
74
|
+
type CommandResult = {
|
|
75
|
+
_tag: 'success';
|
|
76
|
+
message: string;
|
|
77
|
+
} | {
|
|
78
|
+
_tag: 'error';
|
|
79
|
+
message: string;
|
|
80
|
+
} | {
|
|
81
|
+
_tag: 'text';
|
|
82
|
+
text: string;
|
|
83
|
+
};
|
|
84
|
+
type CommandContext = {
|
|
85
|
+
cwd: string;
|
|
86
|
+
config: Config;
|
|
87
|
+
deps: AgentDependencies;
|
|
88
|
+
};
|
|
89
|
+
type SlashCommand = {
|
|
90
|
+
name: string;
|
|
91
|
+
description: string;
|
|
92
|
+
argsHint?: string;
|
|
93
|
+
execute: (args: string, ctx: CommandContext) => Promise<CommandResult>;
|
|
94
|
+
};
|
|
95
|
+
export type { AgentConfig, AgentDependencies, AgentError, AgentEvent, AgentState, AgentStatus, ChatTool, CommandContext, CommandResult, HookRunner, LLMSegment, PendingToolCall, ProjectInfo, PromptBuildContext, PromptContext, PromptRegistry, PromptSection, SlashCommand, TokenBudget, };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** 单仓库 baseline 状态(简化版,不处理嵌套 repo)。 */
|
|
2
|
+
interface RepoBaseline {
|
|
3
|
+
repoRoot: string;
|
|
4
|
+
headCommit: string;
|
|
5
|
+
staged: string;
|
|
6
|
+
unstaged: string;
|
|
7
|
+
untracked: string[];
|
|
8
|
+
}
|
|
9
|
+
/** 捕获仓库当前 baseline(HEAD + staged + unstaged + untracked)。 */
|
|
10
|
+
declare function captureBaseline(repoRoot: string): Promise<RepoBaseline>;
|
|
11
|
+
/** 创建隔离 worktree(detached HEAD),返回其路径(子 agent 的 cwd)。 */
|
|
12
|
+
declare function createWorktree(repoRoot: string, branchName: string): Promise<string>;
|
|
13
|
+
/** 计算 worktree 相对 baseline 的完整 diff。
|
|
14
|
+
* 暂存所有改动(含新文件)后 diff --cached vs HEAD = 含增/删/改的完整 delta。 */
|
|
15
|
+
declare function captureDeltaPatch(worktreeDir: string, _baseline?: RepoBaseline): Promise<string>;
|
|
16
|
+
/** 把 patch 应用回父仓库并自动 commit。返回 commit SHA。 */
|
|
17
|
+
declare function applyPatchToParent(repoRoot: string, patch: string, commitMessage: string): Promise<{
|
|
18
|
+
commitSha: string;
|
|
19
|
+
warnings: string[];
|
|
20
|
+
}>;
|
|
21
|
+
/** 清理 worktree(git worktree remove)。 */
|
|
22
|
+
declare function removeWorktree(repoRoot: string, worktreeDir: string): void;
|
|
23
|
+
export type { RepoBaseline };
|
|
24
|
+
export { applyPatchToParent, captureBaseline, captureDeltaPatch, createWorktree, removeWorktree };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { mkdtemp } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/** 以数组参数执行 git(不经 shell,避免消息含特殊字符被解析)。返回 stdout。 */
|
|
6
|
+
function git(cwd, args) {
|
|
7
|
+
return execFileSync('git', args, {
|
|
8
|
+
cwd,
|
|
9
|
+
encoding: 'utf-8',
|
|
10
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
11
|
+
}).trim();
|
|
12
|
+
}
|
|
13
|
+
/** 执行 git 并通过 stdin 提供 input(用于 git apply)。 */
|
|
14
|
+
function gitWithInput(cwd, args, input) {
|
|
15
|
+
return execFileSync('git', args, {
|
|
16
|
+
cwd,
|
|
17
|
+
encoding: 'utf-8',
|
|
18
|
+
input,
|
|
19
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
20
|
+
}).trim();
|
|
21
|
+
}
|
|
22
|
+
/** 捕获仓库当前 baseline(HEAD + staged + unstaged + untracked)。 */
|
|
23
|
+
async function captureBaseline(repoRoot) {
|
|
24
|
+
const headCommit = git(repoRoot, ['rev-parse', 'HEAD']);
|
|
25
|
+
const staged = git(repoRoot, ['diff', '--cached', '--binary']);
|
|
26
|
+
const unstaged = git(repoRoot, ['diff', '--binary']);
|
|
27
|
+
const untrackedRaw = git(repoRoot, ['ls-files', '--others', '--exclude-standard']);
|
|
28
|
+
const untracked = untrackedRaw.split('\n').filter(Boolean);
|
|
29
|
+
return { repoRoot, headCommit, staged, unstaged, untracked };
|
|
30
|
+
}
|
|
31
|
+
/** 创建隔离 worktree(detached HEAD),返回其路径(子 agent 的 cwd)。 */
|
|
32
|
+
async function createWorktree(repoRoot, branchName) {
|
|
33
|
+
const wtDir = await mkdtemp(join(tmpdir(), `c0de-wt-${branchName}-`));
|
|
34
|
+
git(repoRoot, ['worktree', 'add', '--detach', wtDir, 'HEAD']);
|
|
35
|
+
return wtDir;
|
|
36
|
+
}
|
|
37
|
+
/** 计算 worktree 相对 baseline 的完整 diff。
|
|
38
|
+
* 暂存所有改动(含新文件)后 diff --cached vs HEAD = 含增/删/改的完整 delta。 */
|
|
39
|
+
async function captureDeltaPatch(worktreeDir, _baseline) {
|
|
40
|
+
git(worktreeDir, ['add', '-A']);
|
|
41
|
+
return git(worktreeDir, ['diff', '--cached', '--binary']);
|
|
42
|
+
}
|
|
43
|
+
/** 把 patch 应用回父仓库并自动 commit。返回 commit SHA。 */
|
|
44
|
+
async function applyPatchToParent(repoRoot, patch, commitMessage) {
|
|
45
|
+
if (!patch.trim()) {
|
|
46
|
+
return { commitSha: git(repoRoot, ['rev-parse', 'HEAD']), warnings: [] };
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
gitWithInput(repoRoot, ['apply'], patch);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// apply 失败:尝试 3-way 合并
|
|
53
|
+
gitWithInput(repoRoot, ['apply', '--3way'], patch);
|
|
54
|
+
}
|
|
55
|
+
git(repoRoot, ['add', '-A']);
|
|
56
|
+
git(repoRoot, ['commit', '-m', commitMessage]);
|
|
57
|
+
return { commitSha: git(repoRoot, ['rev-parse', 'HEAD']), warnings: [] };
|
|
58
|
+
}
|
|
59
|
+
/** 清理 worktree(git worktree remove)。 */
|
|
60
|
+
function removeWorktree(repoRoot, worktreeDir) {
|
|
61
|
+
try {
|
|
62
|
+
git(repoRoot, ['worktree', 'remove', '--force', worktreeDir]);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// 清理失败忽略(可能已删)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export { applyPatchToParent, captureBaseline, captureDeltaPatch, createWorktree, removeWorktree };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { DAPClient, DAPMessage, DAPTransport } from './protocol.js';
|
|
2
|
+
export { createDAPClient, createFramer, encodeMessage, } from './protocol.js';
|
|
3
|
+
export type { DebugSessionManager, DebugSpawn } from './session.js';
|
|
4
|
+
export { createDebugSessionManager, dapVariables } from './session.js';
|
|
5
|
+
export { createProcessTransport } from './transport.js';
|
|
6
|
+
export type { Breakpoint, DAPConfig, DAPSession, DebugStartInput, StackFrame, Variable, } from './types.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** DAP 传输抽象:包装适配器进程的 stdin/stdout。真实实现见 transport.ts。 */
|
|
2
|
+
type DAPTransport = {
|
|
3
|
+
write: (chunk: string | Uint8Array) => void;
|
|
4
|
+
onData: (handler: (chunk: Uint8Array | string) => void) => void;
|
|
5
|
+
onClose: (handler: () => void) => void;
|
|
6
|
+
close: () => void;
|
|
7
|
+
};
|
|
8
|
+
type DAPMessage = {
|
|
9
|
+
seq: number;
|
|
10
|
+
type: 'request' | 'response' | 'event';
|
|
11
|
+
command?: string;
|
|
12
|
+
event?: string;
|
|
13
|
+
arguments?: unknown;
|
|
14
|
+
success?: boolean;
|
|
15
|
+
message?: string;
|
|
16
|
+
body?: unknown;
|
|
17
|
+
request_seq?: number;
|
|
18
|
+
};
|
|
19
|
+
/** DAP 客户端:发 request 等 response,收 event。 */
|
|
20
|
+
type DAPClient = {
|
|
21
|
+
/** 发请求,等对应 seq 的 response;失败 response reject。 */
|
|
22
|
+
request: (command: string, args?: unknown) => Promise<unknown>;
|
|
23
|
+
/** 发事件通知(不等响应)。 */
|
|
24
|
+
notify: (command: string, args?: unknown) => void;
|
|
25
|
+
/** 订阅 event,返回取消订阅函数。 */
|
|
26
|
+
on: (event: string, handler: (body: unknown) => void) => () => void;
|
|
27
|
+
dispose: () => void;
|
|
28
|
+
};
|
|
29
|
+
/** 把一条 JSON 消息编码为 DAP 分帧字节(`Content-Length: N\r\n\r\n{json}`)。 */
|
|
30
|
+
declare function encodeMessage(json: string): string;
|
|
31
|
+
/** 输入流分帧器:喂字节,吐完整 JSON 消息。处理跨 chunk / 粘包。 */
|
|
32
|
+
type Framer = {
|
|
33
|
+
feed: (chunk: Uint8Array | string) => void;
|
|
34
|
+
onMessage: (handler: (json: string) => void) => void;
|
|
35
|
+
};
|
|
36
|
+
declare function createFramer(): Framer;
|
|
37
|
+
/** 创建 DAP 客户端。transport 由调用方提供(真实为适配器进程 stdio)。 */
|
|
38
|
+
declare function createDAPClient(transport: DAPTransport): DAPClient;
|
|
39
|
+
export type { DAPClient, DAPMessage, DAPTransport };
|
|
40
|
+
export { createDAPClient, createFramer, encodeMessage };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// DAP 协议层(spec §21):JSON-RPC over Content-Length 分帧。
|
|
2
|
+
// 零外部依赖;transport 抽象分离 IO,协议层(编码/分帧/seq 配对)纯逻辑可测。
|
|
3
|
+
/** 把一条 JSON 消息编码为 DAP 分帧字节(`Content-Length: N\r\n\r\n{json}`)。 */
|
|
4
|
+
function encodeMessage(json) {
|
|
5
|
+
const len = Buffer.byteLength(json, 'utf8');
|
|
6
|
+
return `Content-Length: ${len}\r\n\r\n${json}`;
|
|
7
|
+
}
|
|
8
|
+
function createFramer() {
|
|
9
|
+
let buffer = Buffer.alloc(0);
|
|
10
|
+
const handlers = [];
|
|
11
|
+
function tryParse() {
|
|
12
|
+
// 找 header 结束分隔符 \r\n\r\n
|
|
13
|
+
const sep = buffer.indexOf('\r\n\r\n');
|
|
14
|
+
if (sep === -1)
|
|
15
|
+
return;
|
|
16
|
+
const header = buffer.subarray(0, sep).toString('utf8');
|
|
17
|
+
const m = /Content-Length:\s*(\d+)/i.exec(header);
|
|
18
|
+
if (!m) {
|
|
19
|
+
// 协议错误:丢弃分隔符前内容重试
|
|
20
|
+
buffer = buffer.subarray(sep + 4);
|
|
21
|
+
if (buffer.length > 0)
|
|
22
|
+
tryParse();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const len = Number(m[1]);
|
|
26
|
+
const bodyStart = sep + 4;
|
|
27
|
+
if (buffer.length < bodyStart + len)
|
|
28
|
+
return; // body 未到齐
|
|
29
|
+
const body = buffer.subarray(bodyStart, bodyStart + len).toString('utf8');
|
|
30
|
+
buffer = buffer.subarray(bodyStart + len);
|
|
31
|
+
for (const h of handlers)
|
|
32
|
+
h(body);
|
|
33
|
+
if (buffer.length > 0)
|
|
34
|
+
tryParse(); // 粘包:继续解析
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
feed(chunk) {
|
|
38
|
+
buffer =
|
|
39
|
+
typeof chunk === 'string'
|
|
40
|
+
? Buffer.concat([buffer, Buffer.from(chunk, 'utf8')])
|
|
41
|
+
: Buffer.concat([buffer, chunk]);
|
|
42
|
+
tryParse();
|
|
43
|
+
},
|
|
44
|
+
onMessage(handler) {
|
|
45
|
+
handlers.push(handler);
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** 创建 DAP 客户端。transport 由调用方提供(真实为适配器进程 stdio)。 */
|
|
50
|
+
function createDAPClient(transport) {
|
|
51
|
+
let seq = 0;
|
|
52
|
+
const pending = new Map();
|
|
53
|
+
const eventHandlers = new Map();
|
|
54
|
+
const framer = createFramer();
|
|
55
|
+
let disposed = false;
|
|
56
|
+
framer.onMessage((json) => {
|
|
57
|
+
let msg;
|
|
58
|
+
try {
|
|
59
|
+
msg = JSON.parse(json);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (msg.type === 'response') {
|
|
65
|
+
const p = pending.get(msg.request_seq ?? -1);
|
|
66
|
+
if (!p)
|
|
67
|
+
return;
|
|
68
|
+
pending.delete(msg.request_seq ?? -1);
|
|
69
|
+
if (msg.success === false) {
|
|
70
|
+
p.reject(new Error(msg.message || `DAP request ${msg.request_seq} failed`));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
p.resolve(msg.body);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (msg.type === 'event') {
|
|
77
|
+
const handlers = eventHandlers.get(msg.event ?? '');
|
|
78
|
+
if (handlers)
|
|
79
|
+
for (const h of handlers)
|
|
80
|
+
h(msg.body);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
transport.onData((chunk) => framer.feed(chunk));
|
|
84
|
+
transport.onClose(() => {
|
|
85
|
+
disposed = true;
|
|
86
|
+
for (const p of pending.values())
|
|
87
|
+
p.reject(new Error('DAP transport closed'));
|
|
88
|
+
pending.clear();
|
|
89
|
+
});
|
|
90
|
+
function send(msg) {
|
|
91
|
+
transport.write(encodeMessage(JSON.stringify(msg)));
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
request(command, args) {
|
|
95
|
+
if (disposed)
|
|
96
|
+
return Promise.reject(new Error('DAP client disposed'));
|
|
97
|
+
seq += 1;
|
|
98
|
+
const cur = seq;
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
pending.set(cur, { resolve, reject });
|
|
101
|
+
send({ seq: cur, type: 'request', command, arguments: args });
|
|
102
|
+
});
|
|
103
|
+
},
|
|
104
|
+
notify(command, args) {
|
|
105
|
+
if (disposed)
|
|
106
|
+
return;
|
|
107
|
+
seq += 1;
|
|
108
|
+
send({ seq, type: 'request', command, arguments: args });
|
|
109
|
+
},
|
|
110
|
+
on(event, handler) {
|
|
111
|
+
let set = eventHandlers.get(event);
|
|
112
|
+
if (!set) {
|
|
113
|
+
set = new Set();
|
|
114
|
+
eventHandlers.set(event, set);
|
|
115
|
+
}
|
|
116
|
+
set.add(handler);
|
|
117
|
+
return () => {
|
|
118
|
+
set?.delete(handler);
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
dispose() {
|
|
122
|
+
if (disposed)
|
|
123
|
+
return;
|
|
124
|
+
disposed = true;
|
|
125
|
+
transport.close();
|
|
126
|
+
for (const p of pending.values())
|
|
127
|
+
p.reject(new Error('DAP client disposed'));
|
|
128
|
+
pending.clear();
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
export { createDAPClient, createFramer, encodeMessage };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type DAPClient, type DAPTransport } from './protocol.js';
|
|
2
|
+
import type { Breakpoint, DAPConfig, DAPSession, StackFrame, Variable } from './types.js';
|
|
3
|
+
/** spawn 一个调试适配器并返回其 stdio 包装的 transport(由 host 注入)。 */
|
|
4
|
+
type DebugSpawn = (config: DAPConfig) => DAPTransport;
|
|
5
|
+
/** 按帧取变量:先 scopes 得到 variablesReference,再 variables 汇总。 */
|
|
6
|
+
declare function dapVariables(client: DAPClient, frameId: number): Promise<Variable[]>;
|
|
7
|
+
type DebugSessionManager = {
|
|
8
|
+
start: (spawn: DebugSpawn, config: DAPConfig) => Promise<{
|
|
9
|
+
sessionId: string;
|
|
10
|
+
threadId: number | null;
|
|
11
|
+
}>;
|
|
12
|
+
setBreakpoint: (sessionId: string, bp: Breakpoint) => Promise<unknown>;
|
|
13
|
+
continue: (sessionId: string, threadId: number) => Promise<unknown>;
|
|
14
|
+
step: (sessionId: string, threadId: number, kind: 'over' | 'in' | 'out') => Promise<unknown>;
|
|
15
|
+
stack: (sessionId: string, threadId: number) => Promise<StackFrame[]>;
|
|
16
|
+
variables: (sessionId: string, frameId: number) => Promise<Variable[]>;
|
|
17
|
+
evaluate: (sessionId: string, frameId: number, expression: string) => Promise<string>;
|
|
18
|
+
stop: (sessionId: string) => Promise<void>;
|
|
19
|
+
getSession: (sessionId: string) => DAPSession | undefined;
|
|
20
|
+
};
|
|
21
|
+
declare function createDebugSessionManager(): DebugSessionManager;
|
|
22
|
+
export type { DebugSessionManager, DebugSpawn };
|
|
23
|
+
export { createDebugSessionManager, dapVariables };
|