c0de-agent 1.13.1 → 1.14.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/dist/cli/commands/serve.js +5 -1
- package/dist/cli/deps.d.ts +2 -4
- package/dist/cli/deps.js +22 -36
- package/dist/cli/modes/print.js +27 -17
- package/dist/core/agent.js +59 -36
- package/dist/core/agents/parallel.js +4 -2
- package/dist/core/config-path.d.ts +3 -1
- package/dist/core/config-path.js +33 -3
- package/dist/core/config.js +81 -0
- package/dist/core/context.js +6 -3
- package/dist/core/loop/persist.js +4 -1
- package/dist/core/loop/subagent.d.ts +19 -0
- package/dist/core/loop/subagent.js +73 -7
- package/dist/core/loop.d.ts +4 -1
- package/dist/core/loop.js +22 -6
- package/dist/{server/registry-config.d.ts → core/provider-registry.d.ts} +10 -2
- package/dist/core/provider-registry.js +66 -0
- package/dist/core/redact.d.ts +2 -1
- package/dist/core/redact.js +4 -2
- package/dist/core/secret.d.ts +5 -0
- package/dist/core/secret.js +14 -0
- package/dist/core/skills.d.ts +2 -0
- package/dist/core/skills.js +61 -0
- package/dist/core/slash.js +31 -18
- package/dist/core/title.js +4 -2
- package/dist/core/todo-tags.d.ts +4 -3
- package/dist/core/todo-tags.js +20 -6
- package/dist/core/workflow.d.ts +4 -13
- package/dist/core/workflow.js +6 -6
- package/dist/core/workflows/context.js +31 -59
- package/dist/core/workflows/runtime.js +10 -8
- package/dist/dap/index.d.ts +1 -0
- package/dist/dap/index.js +1 -0
- package/dist/dap/protocol.d.ts +8 -3
- package/dist/dap/protocol.js +54 -18
- package/dist/dap/session.js +71 -12
- package/dist/dap/spawn.d.ts +11 -0
- package/dist/dap/spawn.js +48 -0
- package/dist/dap/transport.js +18 -0
- package/dist/kanban/index.d.ts +1 -1
- package/dist/kanban/index.js +1 -1
- package/dist/kanban/store.d.ts +8 -1
- package/dist/kanban/store.js +61 -13
- package/dist/llm/protocols/openai-compat.d.ts +7 -0
- package/dist/llm/protocols/openai-compat.js +19 -2
- package/dist/llm/protocols/utils/tool-stream.d.ts +12 -3
- package/dist/llm/protocols/utils/tool-stream.js +47 -23
- package/dist/llm/provider.js +18 -3
- package/dist/llm/retry.d.ts +1 -0
- package/dist/llm/retry.js +20 -7
- package/dist/llm/routing.js +14 -2
- package/dist/llm/schema/messages.d.ts +9 -2
- package/dist/llm/token.d.ts +9 -0
- package/dist/llm/token.js +10 -6
- package/dist/llm/transport.js +7 -2
- package/dist/mcp/client.d.ts +11 -0
- package/dist/mcp/client.js +70 -0
- package/dist/mcp/index.d.ts +46 -0
- package/dist/mcp/index.js +106 -0
- package/dist/mcp/tool-adapter.d.ts +14 -0
- package/dist/mcp/tool-adapter.js +65 -0
- package/dist/mcp/transport.d.ts +35 -0
- package/dist/mcp/transport.js +243 -0
- package/dist/mcp/types.d.ts +55 -0
- package/dist/mcp/types.js +3 -0
- package/dist/plugins/init.d.ts +2 -0
- package/dist/plugins/init.js +10 -1
- package/dist/project/project.js +50 -3
- package/dist/project/resolve.d.ts +7 -0
- package/dist/project/resolve.js +52 -5
- package/dist/server/permission/interactive.js +9 -4
- package/dist/server/permission/store.js +8 -2
- package/dist/server/routes/auth.js +28 -13
- package/dist/server/routes/chat.js +92 -26
- package/dist/server/routes/config.js +38 -10
- package/dist/server/routes/files.js +75 -16
- package/dist/server/routes/filesystem.js +9 -2
- package/dist/server/routes/kanban.js +146 -9
- package/dist/server/routes/permissions.js +32 -0
- package/dist/server/routes/project.js +15 -6
- package/dist/server/routes/provider.js +10 -5
- package/dist/server/routes/session.js +72 -26
- package/dist/server/routes/terminal.js +10 -2
- package/dist/server/routes/todo.js +18 -0
- package/dist/server/routes/tool.js +13 -2
- package/dist/server/routes/update.js +5 -1
- package/dist/server/routes/workflows.js +17 -6
- package/dist/server/server.d.ts +1 -1
- package/dist/server/server.js +31 -17
- package/dist/server/terminal/pty-manager.d.ts +3 -0
- package/dist/server/terminal/pty-manager.js +8 -3
- package/dist/server/types.d.ts +2 -0
- package/dist/server/util/json-body.d.ts +31 -0
- package/dist/server/util/json-body.js +38 -0
- package/dist/session/archive.js +4 -4
- package/dist/session/branch.d.ts +10 -1
- package/dist/session/branch.js +54 -9
- package/dist/session/compaction.d.ts +5 -0
- package/dist/session/compaction.js +15 -10
- package/dist/session/context.d.ts +7 -3
- package/dist/session/context.js +35 -9
- package/dist/session/import.js +53 -6
- package/dist/session/index.d.ts +2 -2
- package/dist/session/index.js +2 -2
- package/dist/session/message.d.ts +12 -3
- package/dist/session/message.js +44 -4
- package/dist/session/session.d.ts +11 -5
- package/dist/session/session.js +99 -88
- package/dist/session/shake.js +35 -25
- package/dist/session/squash.js +3 -4
- package/dist/session/token.d.ts +7 -8
- package/dist/session/token.js +19 -16
- package/dist/session/usage.js +4 -2
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/round.d.ts +12 -0
- package/dist/shared/round.js +18 -0
- package/dist/shared/timer.d.ts +23 -0
- package/dist/shared/timer.js +27 -0
- package/dist/shared/types/tool.d.ts +4 -0
- package/dist/shared/utils/markdown-code.d.ts +15 -0
- package/dist/shared/utils/markdown-code.js +44 -0
- package/dist/shared/utils/path.d.ts +4 -1
- package/dist/shared/utils/path.js +5 -2
- package/dist/shared/utils/string.d.ts +5 -0
- package/dist/shared/utils/string.js +52 -0
- package/dist/tools/builtin/bash.js +79 -10
- package/dist/tools/builtin/edit.js +67 -8
- package/dist/tools/builtin/glob.js +150 -14
- package/dist/tools/builtin/grep.js +53 -11
- package/dist/tools/builtin/kanban.d.ts +14 -0
- package/dist/tools/builtin/kanban.js +4 -1
- package/dist/tools/builtin/read.js +15 -2
- package/dist/tools/builtin/resolvers.d.ts +9 -2
- package/dist/tools/builtin/resolvers.js +20 -6
- package/dist/tools/builtin/task.js +107 -25
- package/dist/tools/builtin/todo.js +84 -9
- package/dist/tools/builtin/write.js +7 -1
- package/dist/tools/builtin/yield.js +9 -1
- package/dist/tools/executor.js +53 -1
- package/dist/tools/hashline/patch.js +114 -38
- package/dist/tools/truncate.js +45 -16
- package/dist/tools/validate.js +66 -20
- package/dist/tools/websearch/index.js +8 -4
- package/dist/update/index.d.ts +1 -1
- package/dist/update/index.js +1 -1
- package/dist/update/scheduler.js +9 -2
- package/dist/update/snapshot.d.ts +14 -4
- package/dist/update/snapshot.js +22 -22
- package/dist/update/updater.d.ts +16 -2
- package/dist/update/updater.js +39 -8
- package/dist/update/version.d.ts +3 -1
- package/dist/update/version.js +63 -9
- package/package.json +1 -1
- package/dist/server/registry-config.js +0 -44
package/dist/core/workflow.d.ts
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "workflowz" keyword support — Dynamic Workflow(spec: dynamic-workflow-design)。
|
|
3
|
-
*
|
|
4
|
-
* 用户在消息中包含独立单词 `workflowz` 时:
|
|
5
|
-
* 1. 后端检测到关键词(prose 感知,忽略代码块/路径/大小写变体)
|
|
6
|
-
* 2. 向当轮注入一条隐藏 system 通知(steering),引导模型用 `task` 工具批量 fan-out
|
|
7
|
-
* 做确定性的多子 agent 工作流分解
|
|
8
|
-
*
|
|
9
|
-
* 复刻自 oh-my-pi 的 modes/workflow.ts,适配 c0de-agent 的 task 工具 schema:
|
|
10
|
-
* - 批量模式:`{ subagent_type, context, tasks: [{ prompt, description? }] }`
|
|
11
|
-
* - 单任务模式:`{ subagent_type?, prompt, description? }`
|
|
12
|
-
*/
|
|
13
1
|
/**
|
|
14
2
|
* 判断 `text` 是否在 prose 中包含独立关键词 "workflowz"。
|
|
15
3
|
*
|
|
@@ -17,7 +5,10 @@
|
|
|
17
5
|
* (中文无词间空格);但仍拒绝 workflowzed / reworkflowz / workflowz.test.ts
|
|
18
6
|
* 等拉丁词续或路径内嵌形式。
|
|
19
7
|
*
|
|
20
|
-
*
|
|
8
|
+
* prose 检测:先剥离围栏代码块与行内代码再匹配(shared/utils/markdown-code.ts 的
|
|
9
|
+
* stripMarkdownCode)——未闭合围栏、~~~ 围栏、长反引号围栏、多反引号
|
|
10
|
+
* 行内代码都会漏过此前的手写剥离,代码内容上的字面量关键字误触发
|
|
11
|
+
* 工作流通告注入。
|
|
21
12
|
*/
|
|
22
13
|
export declare function containsWorkflow(text: string): boolean;
|
|
23
14
|
/**
|
package/dist/core/workflow.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - 批量模式:`{ subagent_type, context, tasks: [{ prompt, description? }] }`
|
|
11
11
|
* - 单任务模式:`{ subagent_type?, prompt, description? }`
|
|
12
12
|
*/
|
|
13
|
+
import { stripMarkdownCode } from '../shared/utils/markdown-code.js';
|
|
13
14
|
// 检测:小写关键词,两侧不得是 ASCII 词/路径字符(字母、数字、_、.、/、-)。
|
|
14
15
|
// 用 [\w./-] 而非 \S 做边界,使 CJK 字符和中文标点也能当边界——
|
|
15
16
|
// 中文无词间空格,用 \S 会拒绝「请workflowz」「workflowz。」等合法写法。
|
|
@@ -22,14 +23,13 @@ const WORKFLOW_WORD = /(?<![\w./-])workflowz(?![\w./-])/;
|
|
|
22
23
|
* (中文无词间空格);但仍拒绝 workflowzed / reworkflowz / workflowz.test.ts
|
|
23
24
|
* 等拉丁词续或路径内嵌形式。
|
|
24
25
|
*
|
|
25
|
-
*
|
|
26
|
+
* prose 检测:先剥离围栏代码块与行内代码再匹配(shared/utils/markdown-code.ts 的
|
|
27
|
+
* stripMarkdownCode)——未闭合围栏、~~~ 围栏、长反引号围栏、多反引号
|
|
28
|
+
* 行内代码都会漏过此前的手写剥离,代码内容上的字面量关键字误触发
|
|
29
|
+
* 工作流通告注入。
|
|
26
30
|
*/
|
|
27
31
|
export function containsWorkflow(text) {
|
|
28
|
-
|
|
29
|
-
const withoutBlocks = text.replace(/```[\s\S]*?```/g, '');
|
|
30
|
-
// 移除 `行内代码`
|
|
31
|
-
const withoutInline = withoutBlocks.replace(/`[^`]*`/g, '');
|
|
32
|
-
return WORKFLOW_WORD.test(withoutInline);
|
|
32
|
+
return WORKFLOW_WORD.test(stripMarkdownCode(text));
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* 工作流通知:注入为 steering system 消息,引导模型用 task 工具做批量 fan-out。
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
import { globToRegex } from '../../tools/builtin/glob.js';
|
|
3
4
|
/** SubAgentResult → WorkflowAgentResult 映射。 */
|
|
4
5
|
function mapResult(result) {
|
|
5
6
|
if (result._tag === 'success') {
|
|
@@ -36,29 +37,19 @@ function buildWorkflowContext(opts) {
|
|
|
36
37
|
return mapResult(result);
|
|
37
38
|
},
|
|
38
39
|
runSubagents: async (type, tasks, context) => {
|
|
39
|
-
const {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
catch (e) {
|
|
53
|
-
// 隔离单个任务的异常:不向上抛,避免 mapWithConcurrencyLimit 的
|
|
54
|
-
// fail-fast 终止所有尚未启动的兄弟任务。按合约返回 per-task { ok: false }。
|
|
55
|
-
return {
|
|
56
|
-
ok: false,
|
|
57
|
-
error: e instanceof Error ? e.message : String(e),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
return results.filter((r) => r !== undefined);
|
|
40
|
+
const { runSubAgents } = await import('../loop/subagent.js');
|
|
41
|
+
const requests = tasks.map((task) => ({
|
|
42
|
+
agentType: type,
|
|
43
|
+
prompt: task.assignment,
|
|
44
|
+
...(task.description ? { description: task.description } : {}),
|
|
45
|
+
...(task.role ? { role: task.role } : {}),
|
|
46
|
+
...(context ? { context } : {}),
|
|
47
|
+
}));
|
|
48
|
+
// 与 task 工具批量模式共用同一派发实现:并发上限取
|
|
49
|
+
// config.agents.subagentConcurrency(此前本模块硬编码 3,配置项形同虚设),
|
|
50
|
+
// 逐任务错误隔离与「结果顺序一致、无空洞」的契约同源。
|
|
51
|
+
const results = await runSubAgents(deps, parent, requests, doRunSubAgent);
|
|
52
|
+
return results.map(mapResult);
|
|
62
53
|
},
|
|
63
54
|
progress: onProgress,
|
|
64
55
|
utils: {
|
|
@@ -74,8 +65,17 @@ function buildWorkflowContext(opts) {
|
|
|
74
65
|
const content = await readFile(absPath, 'utf-8');
|
|
75
66
|
if (!range)
|
|
76
67
|
return content;
|
|
68
|
+
// 与内置 read 工具同口径:range 未经校验直接进 slice(start - 1, end) 时
|
|
69
|
+
// start=0 经 slice(-1, end) 静默返回文件最后一行(0 基/1 基错位的最坏
|
|
70
|
+
// 形态)、小数行号被 slice 静默截断错行、end < start 静默空串——非法
|
|
71
|
+
// range 显式抛错让工作流作者/模型自纠;end 超出行数收敛到末行(与
|
|
72
|
+
// slice 语义一致)。
|
|
73
|
+
const { start, end } = range;
|
|
74
|
+
if (!Number.isInteger(start) || start < 1 || !Number.isInteger(end) || end < start) {
|
|
75
|
+
throw new Error(`utils.read: invalid range { start: ${start}, end: ${end} } — start/end 必须是整数,start >= 1 且 end >= start`);
|
|
76
|
+
}
|
|
77
77
|
const lines = content.split('\n');
|
|
78
|
-
return lines.slice(
|
|
78
|
+
return lines.slice(start - 1, end).join('\n');
|
|
79
79
|
},
|
|
80
80
|
splitByDirectory: async (dir, opts) => {
|
|
81
81
|
return splitByDir(resolve(rootDir, dir), opts?.depth ?? 1, opts?.ignore ?? []);
|
|
@@ -84,46 +84,15 @@ function buildWorkflowContext(opts) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
// ── 工具函数 ──
|
|
87
|
-
/** 把 glob 模式编译为正则:双星号跨目录,单星号与问号不跨路径分隔符,其余字符转义。 */
|
|
88
|
-
function globToRegExp(pattern) {
|
|
89
|
-
let re = '';
|
|
90
|
-
for (let i = 0; i < pattern.length; i++) {
|
|
91
|
-
const ch = pattern[i] ?? '';
|
|
92
|
-
if (ch === '*') {
|
|
93
|
-
if (pattern[i + 1] === '*') {
|
|
94
|
-
// `**/` 须能匹配零层目录(globstar 语义:src/**/*.ts 命中 src/a.ts)
|
|
95
|
-
if (pattern[i + 2] === '/') {
|
|
96
|
-
re += '(?:.*/)?';
|
|
97
|
-
i += 2;
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
re += '.*';
|
|
101
|
-
i++;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
re += '[^/]*';
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
else if (ch === '?') {
|
|
109
|
-
re += '[^/]';
|
|
110
|
-
}
|
|
111
|
-
else if (/[.+^${}()|[\]\\]/.test(ch)) {
|
|
112
|
-
re += `\\${ch}`;
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
re += ch;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return new RegExp(`^${re}$`);
|
|
119
|
-
}
|
|
120
87
|
/**
|
|
121
88
|
* 递归 glob。模式含路径分隔符时按相对路径匹配(双星号跨目录,如「src 下任意层级的 .ts」);
|
|
122
89
|
* 不含分隔符时按文件名匹配(与旧行为兼容:星号 .ts 命中任意层级)。
|
|
90
|
+
* 模式翻译统一走 glob 工具的 globToRegex——此前本模块自带的翻译器不支持
|
|
91
|
+
* 字符类/花括号(静默零命中),两处语义发散已收敛为单一实现。
|
|
123
92
|
*/
|
|
124
93
|
async function globRecursive(rootDir, pattern) {
|
|
125
94
|
const results = [];
|
|
126
|
-
const regex =
|
|
95
|
+
const regex = globToRegex(pattern);
|
|
127
96
|
const matchRel = pattern.includes('/');
|
|
128
97
|
async function walk(dir) {
|
|
129
98
|
let entries;
|
|
@@ -180,8 +149,11 @@ async function grepRecursive(baseDir, pattern, rootDir) {
|
|
|
180
149
|
try {
|
|
181
150
|
const content = await readFile(fullPath, 'utf-8');
|
|
182
151
|
const lines = content.split('\n');
|
|
152
|
+
// CRLF 文件行尾 \r 使 ^...$ 锚定匹配恒失败(与内置 grep 工具同型)。
|
|
153
|
+
const crlf = content.includes('\r\n');
|
|
183
154
|
for (let i = 0; i < lines.length; i++) {
|
|
184
|
-
const
|
|
155
|
+
const raw = lines[i];
|
|
156
|
+
const line = crlf && raw?.endsWith('\r') ? raw.slice(0, -1) : raw;
|
|
185
157
|
if (line && regex.test(line)) {
|
|
186
158
|
results.push({
|
|
187
159
|
path: relative(rootDir, fullPath),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clampTimerDelay, MAX_TIMER_MS } from '../../shared/timer.js';
|
|
1
2
|
import { buildWorkflowContext } from './context.js';
|
|
2
3
|
/** 工作流超时错误(用于 Promise.race 中识别超时)。 */
|
|
3
4
|
class WorkflowTimeoutError extends Error {
|
|
@@ -47,15 +48,16 @@ async function executeWorkflow(opts) {
|
|
|
47
48
|
onProgress: onProgress ?? (() => { }),
|
|
48
49
|
});
|
|
49
50
|
const { timeout } = entry.meta;
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
// 声明的秒数转毫秒后须经 32 位上限钳制:超限(如 2^31 秒)会被 Node 钳到
|
|
52
|
+
// 1ms——工作流刚启动就被「超时」中止,且错误信息谎报原始时长;parent 的
|
|
53
|
+
// abort 级联还会被连带触发(同型:ToolDef.timeout 的钳制口径)。
|
|
54
|
+
const timeoutSpec = typeof timeout === 'number' && timeout > 0
|
|
55
|
+
? { seconds: timeout, ms: clampTimerDelay(timeout * 1000, MAX_TIMER_MS, 1) }
|
|
56
|
+
: null;
|
|
52
57
|
try {
|
|
53
58
|
const execPromise = entry.execute(ctx);
|
|
54
|
-
const result =
|
|
55
|
-
? await Promise.race([
|
|
56
|
-
execPromise,
|
|
57
|
-
createTimeoutPromise(timeoutSeconds * 1000, timeoutSeconds),
|
|
58
|
-
])
|
|
59
|
+
const result = timeoutSpec
|
|
60
|
+
? await Promise.race([execPromise, createTimeoutPromise(timeoutSpec.ms, timeoutSpec.seconds)])
|
|
59
61
|
: await execPromise;
|
|
60
62
|
return {
|
|
61
63
|
_tag: 'text',
|
|
@@ -75,7 +77,7 @@ async function executeWorkflow(opts) {
|
|
|
75
77
|
}
|
|
76
78
|
return {
|
|
77
79
|
_tag: 'error',
|
|
78
|
-
message: `Workflow "${name}" timed out after ${
|
|
80
|
+
message: `Workflow "${name}" timed out after ${timeoutSpec?.seconds}s(执行已中止)`,
|
|
79
81
|
};
|
|
80
82
|
}
|
|
81
83
|
return {
|
package/dist/dap/index.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ export type { DAPClient, DAPMessage, DAPTransport } from './protocol.js';
|
|
|
2
2
|
export { createDAPClient, createFramer, encodeMessage, } from './protocol.js';
|
|
3
3
|
export type { DebugSessionManager, DebugSpawn } from './session.js';
|
|
4
4
|
export { createDebugSessionManager, dapVariables } from './session.js';
|
|
5
|
+
export { ADAPTER_COMMANDS, createDebugSpawn, resolveAdapterCommand } from './spawn.js';
|
|
5
6
|
export { createProcessTransport } from './transport.js';
|
|
6
7
|
export type { Breakpoint, DAPConfig, DAPSession, DebugStartInput, StackFrame, Variable, } from './types.js';
|
package/dist/dap/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// DAP 包(spec §21):DAP 客户端、会话管理、进程 transport。
|
|
2
2
|
export { createDAPClient, createFramer, encodeMessage, } from './protocol.js';
|
|
3
3
|
export { createDebugSessionManager, dapVariables } from './session.js';
|
|
4
|
+
export { ADAPTER_COMMANDS, createDebugSpawn, resolveAdapterCommand } from './spawn.js';
|
|
4
5
|
export { createProcessTransport } from './transport.js';
|
package/dist/dap/protocol.d.ts
CHANGED
|
@@ -18,14 +18,19 @@ type DAPMessage = {
|
|
|
18
18
|
};
|
|
19
19
|
/** DAP 客户端:发 request 等 response,收 event。 */
|
|
20
20
|
type DAPClient = {
|
|
21
|
-
/** 发请求,等对应 seq 的 response;失败 response reject。
|
|
22
|
-
|
|
21
|
+
/** 发请求,等对应 seq 的 response;失败 response reject。
|
|
22
|
+
* timeoutMs ≤ 0 表示不限时;缺省取 DEFAULT_REQUEST_TIMEOUT_MS。 */
|
|
23
|
+
request: (command: string, args?: unknown, timeoutMs?: number) => Promise<unknown>;
|
|
23
24
|
/** 发事件通知(不等响应)。 */
|
|
24
25
|
notify: (command: string, args?: unknown) => void;
|
|
25
26
|
/** 订阅 event,返回取消订阅函数。 */
|
|
26
27
|
on: (event: string, handler: (body: unknown) => void) => () => void;
|
|
27
28
|
dispose: () => void;
|
|
28
29
|
};
|
|
30
|
+
/** 单请求默认超时:适配器进程存活但不响应(自身挂起、不支持的命令被静默忽略)时
|
|
31
|
+
* 拒绝而非永久挂起——debug_* 工具(ToolDef 无 timeout)与 start() 的 launch 都会
|
|
32
|
+
* 无限等待,整个 agent run 只能靠用户手动中止。与 MCP 客户端的单请求超时同型。 */
|
|
33
|
+
declare const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
|
|
29
34
|
/** 把一条 JSON 消息编码为 DAP 分帧字节(`Content-Length: N\r\n\r\n{json}`)。 */
|
|
30
35
|
declare function encodeMessage(json: string): string;
|
|
31
36
|
/** 输入流分帧器:喂字节,吐完整 JSON 消息。处理跨 chunk / 粘包。 */
|
|
@@ -37,4 +42,4 @@ declare function createFramer(): Framer;
|
|
|
37
42
|
/** 创建 DAP 客户端。transport 由调用方提供(真实为适配器进程 stdio)。 */
|
|
38
43
|
declare function createDAPClient(transport: DAPTransport): DAPClient;
|
|
39
44
|
export type { DAPClient, DAPMessage, DAPTransport };
|
|
40
|
-
export { createDAPClient, createFramer, encodeMessage };
|
|
45
|
+
export { createDAPClient, createFramer, DEFAULT_REQUEST_TIMEOUT_MS, encodeMessage };
|
package/dist/dap/protocol.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
// DAP 协议层(spec §21):JSON-RPC over Content-Length 分帧。
|
|
2
2
|
// 零外部依赖;transport 抽象分离 IO,协议层(编码/分帧/seq 配对)纯逻辑可测。
|
|
3
|
+
/** 单请求默认超时:适配器进程存活但不响应(自身挂起、不支持的命令被静默忽略)时
|
|
4
|
+
* 拒绝而非永久挂起——debug_* 工具(ToolDef 无 timeout)与 start() 的 launch 都会
|
|
5
|
+
* 无限等待,整个 agent run 只能靠用户手动中止。与 MCP 客户端的单请求超时同型。 */
|
|
6
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
3
7
|
/** 把一条 JSON 消息编码为 DAP 分帧字节(`Content-Length: N\r\n\r\n{json}`)。 */
|
|
4
8
|
function encodeMessage(json) {
|
|
5
9
|
const len = Buffer.byteLength(json, 'utf8');
|
|
6
10
|
return `Content-Length: ${len}\r\n\r\n${json}`;
|
|
7
11
|
}
|
|
12
|
+
/** 单帧 body 上限(字节):Content-Length 是未受信输入——恶意/损坏适配器
|
|
13
|
+
* 声明巨大 body 时 `buffer.length < bodyStart + len` 恒成立,缓冲无限增长
|
|
14
|
+
* 且吞掉后续所有合法帧。超限声明视为协议错误,丢弃头部重新同步。 */
|
|
15
|
+
const MAX_FRAME_BYTES = 64 * 1024 * 1024;
|
|
8
16
|
function createFramer() {
|
|
9
17
|
let buffer = Buffer.alloc(0);
|
|
10
18
|
const handlers = [];
|
|
11
19
|
function tryParse() {
|
|
20
|
+
// 缓冲硬上限:流中长时间无有效分帧头(垃圾字节流)时防无界增长。
|
|
21
|
+
// 保留尾部 4KB 作为重新同步窗口,其余丢弃。
|
|
22
|
+
if (buffer.length > MAX_FRAME_BYTES) {
|
|
23
|
+
buffer = buffer.subarray(buffer.length - 4096);
|
|
24
|
+
}
|
|
12
25
|
// 找 header 结束分隔符 \r\n\r\n
|
|
13
26
|
const sep = buffer.indexOf('\r\n\r\n');
|
|
14
27
|
if (sep === -1)
|
|
@@ -23,6 +36,13 @@ function createFramer() {
|
|
|
23
36
|
return;
|
|
24
37
|
}
|
|
25
38
|
const len = Number(m[1]);
|
|
39
|
+
// 超限/异常声明:丢弃该头重试,绝不为未受信长度无界缓冲
|
|
40
|
+
if (!Number.isSafeInteger(len) || len < 0 || len > MAX_FRAME_BYTES) {
|
|
41
|
+
buffer = buffer.subarray(sep + 4);
|
|
42
|
+
if (buffer.length > 0)
|
|
43
|
+
tryParse();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
26
46
|
const bodyStart = sep + 4;
|
|
27
47
|
if (buffer.length < bodyStart + len)
|
|
28
48
|
return; // body 未到齐
|
|
@@ -53,6 +73,16 @@ function createDAPClient(transport) {
|
|
|
53
73
|
const eventHandlers = new Map();
|
|
54
74
|
const framer = createFramer();
|
|
55
75
|
let disposed = false;
|
|
76
|
+
/** 终结一个 pending:清定时器 → 出表 → settle(幂等:超时/响应/关闭谁先到谁生效)。 */
|
|
77
|
+
const settle = (id, fn) => {
|
|
78
|
+
const p = pending.get(id);
|
|
79
|
+
if (!p)
|
|
80
|
+
return;
|
|
81
|
+
if (p.timer)
|
|
82
|
+
clearTimeout(p.timer);
|
|
83
|
+
pending.delete(id);
|
|
84
|
+
fn(p);
|
|
85
|
+
};
|
|
56
86
|
framer.onMessage((json) => {
|
|
57
87
|
let msg;
|
|
58
88
|
try {
|
|
@@ -62,16 +92,17 @@ function createDAPClient(transport) {
|
|
|
62
92
|
return;
|
|
63
93
|
}
|
|
64
94
|
if (msg.type === 'response') {
|
|
65
|
-
const
|
|
66
|
-
if (!
|
|
95
|
+
const requestSeq = msg.request_seq ?? -1;
|
|
96
|
+
if (!pending.has(requestSeq))
|
|
67
97
|
return;
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
settle(requestSeq, (p) => {
|
|
99
|
+
if (msg.success === false) {
|
|
100
|
+
p.reject(new Error(msg.message || `DAP request ${requestSeq} failed`));
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
p.resolve(msg.body);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
75
106
|
}
|
|
76
107
|
else if (msg.type === 'event') {
|
|
77
108
|
const handlers = eventHandlers.get(msg.event ?? '');
|
|
@@ -83,21 +114,26 @@ function createDAPClient(transport) {
|
|
|
83
114
|
transport.onData((chunk) => framer.feed(chunk));
|
|
84
115
|
transport.onClose(() => {
|
|
85
116
|
disposed = true;
|
|
86
|
-
for (const
|
|
87
|
-
p.reject(new Error('DAP transport closed'));
|
|
88
|
-
|
|
117
|
+
for (const id of Array.from(pending.keys())) {
|
|
118
|
+
settle(id, (p) => p.reject(new Error('DAP transport closed')));
|
|
119
|
+
}
|
|
89
120
|
});
|
|
90
121
|
function send(msg) {
|
|
91
122
|
transport.write(encodeMessage(JSON.stringify(msg)));
|
|
92
123
|
}
|
|
93
124
|
return {
|
|
94
|
-
request(command, args) {
|
|
125
|
+
request(command, args, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
95
126
|
if (disposed)
|
|
96
127
|
return Promise.reject(new Error('DAP client disposed'));
|
|
97
128
|
seq += 1;
|
|
98
129
|
const cur = seq;
|
|
99
130
|
return new Promise((resolve, reject) => {
|
|
100
|
-
|
|
131
|
+
const timer = timeoutMs > 0
|
|
132
|
+
? setTimeout(() => {
|
|
133
|
+
settle(cur, (p) => p.reject(new Error(`DAP request "${command}" timed out after ${timeoutMs}ms`)));
|
|
134
|
+
}, timeoutMs)
|
|
135
|
+
: undefined;
|
|
136
|
+
pending.set(cur, { resolve, reject, ...(timer ? { timer } : {}) });
|
|
101
137
|
send({ seq: cur, type: 'request', command, arguments: args });
|
|
102
138
|
});
|
|
103
139
|
},
|
|
@@ -123,10 +159,10 @@ function createDAPClient(transport) {
|
|
|
123
159
|
return;
|
|
124
160
|
disposed = true;
|
|
125
161
|
transport.close();
|
|
126
|
-
for (const
|
|
127
|
-
p.reject(new Error('DAP client disposed'));
|
|
128
|
-
|
|
162
|
+
for (const id of Array.from(pending.keys())) {
|
|
163
|
+
settle(id, (p) => p.reject(new Error('DAP client disposed')));
|
|
164
|
+
}
|
|
129
165
|
},
|
|
130
166
|
};
|
|
131
167
|
}
|
|
132
|
-
export { createDAPClient, createFramer, encodeMessage };
|
|
168
|
+
export { createDAPClient, createFramer, DEFAULT_REQUEST_TIMEOUT_MS, encodeMessage };
|
package/dist/dap/session.js
CHANGED
|
@@ -1,24 +1,64 @@
|
|
|
1
1
|
import { generateId } from '../shared/index.js';
|
|
2
2
|
import { createDAPClient } from './protocol.js';
|
|
3
3
|
// ── 基于已建立 client 的原子操作(薄封装,对齐 DAP command) ──
|
|
4
|
+
/** initialize/launch 超时:适配器首启(如 npx 下载 js-debug)可慢,给足余量;
|
|
5
|
+
* 超时拒绝并关闭 transport,避免 debug_start 永久挂住 agent run。 */
|
|
6
|
+
const DAP_INIT_TIMEOUT_MS = 120_000;
|
|
7
|
+
/** disconnect 超时:调试器可能已卡死,停止调试不应让工具再等满默认超时。 */
|
|
8
|
+
const DAP_DISCONNECT_TIMEOUT_MS = 10_000;
|
|
4
9
|
async function dapInitialize(client, adapterID) {
|
|
5
|
-
return client.request('initialize', {
|
|
10
|
+
return (await client.request('initialize', {
|
|
6
11
|
clientID: 'c0de-agent',
|
|
7
12
|
adapterID,
|
|
8
13
|
linesStartAt1: true,
|
|
9
14
|
columnsStartAt1: true,
|
|
10
15
|
pathFormat: 'path',
|
|
11
|
-
});
|
|
16
|
+
}, DAP_INIT_TIMEOUT_MS));
|
|
12
17
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
/**
|
|
19
|
+
* launch/attach 的参数。
|
|
20
|
+
*
|
|
21
|
+
* `type` 是适配器(尤其 js-debug)选择调试目标类型的字段:缺失时 js-debug 不创建
|
|
22
|
+
* 目标,launch 请求永不返回响应——start() 会一直等到 initialize 超时才报错,错误
|
|
23
|
+
* 文本还不指向根因。用 adapter id 作默认值(node→node、python→python…),
|
|
24
|
+
* launchArgs 可覆盖。
|
|
25
|
+
*/
|
|
26
|
+
function dapLaunchArguments(config) {
|
|
27
|
+
return {
|
|
28
|
+
type: config.adapter,
|
|
16
29
|
program: config.program,
|
|
17
30
|
args: config.args,
|
|
18
31
|
cwd: config.cwd,
|
|
19
32
|
stopOnEntry: false,
|
|
20
33
|
...config.launchArgs,
|
|
21
|
-
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 发送 launch/attach 请求并返回「响应 promise」(不 await)。
|
|
38
|
+
* 必须能分两步:支持 configurationDone 的适配器(js-debug)在收到
|
|
39
|
+
* configurationDone 之前不返回 launch 响应——先 await launch 再发
|
|
40
|
+
* configurationDone 会互锁到超时。超时与 initialize 同口径(120s):目标启动慢
|
|
41
|
+
* 时不应永久挂住 start()。
|
|
42
|
+
*/
|
|
43
|
+
function dapLaunchRequest(client, config) {
|
|
44
|
+
const cmd = config.request === 'attach' ? 'attach' : 'launch';
|
|
45
|
+
return client.request(cmd, dapLaunchArguments(config), DAP_INIT_TIMEOUT_MS);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 声明 supportsConfigurationDoneRequest 的适配器在启动目标前等待
|
|
49
|
+
* configurationDone(DAP 规范:客户端完成「启动前配置」后发送)。本客户端没有
|
|
50
|
+
* 独立的配置阶段(断点是启动后经 debug_breakpoint 增量设置),故紧接 launch 发送:
|
|
51
|
+
* 不发送时 js-debug 永远不启动目标——程序不运行、断点不命中、launch 响应不返回。
|
|
52
|
+
* 失败不致命(部分适配器实现宽松或不需要),但记录以便排查。
|
|
53
|
+
*/
|
|
54
|
+
const DAP_CONFIGURATION_DONE_TIMEOUT_MS = 10_000;
|
|
55
|
+
async function dapConfigurationDone(client) {
|
|
56
|
+
try {
|
|
57
|
+
await client.request('configurationDone', {}, DAP_CONFIGURATION_DONE_TIMEOUT_MS);
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
console.warn('[dap] configurationDone failed:', e instanceof Error ? e.message : String(e));
|
|
61
|
+
}
|
|
22
62
|
}
|
|
23
63
|
async function dapSetBreakpoints(client, file, bps) {
|
|
24
64
|
return client.request('setBreakpoints', {
|
|
@@ -84,13 +124,32 @@ function createDebugSessionManager() {
|
|
|
84
124
|
client.on('terminated', () => {
|
|
85
125
|
session.state = 'stopped';
|
|
86
126
|
});
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
127
|
+
try {
|
|
128
|
+
const initBody = await dapInitialize(client, config.adapter);
|
|
129
|
+
// launch 响应与 configurationDone 有先后依赖:支持 configurationDone 的
|
|
130
|
+
// 适配器要收到它才创建/启动目标并回响应。先发 launch,再补 configurationDone,
|
|
131
|
+
// 最后 await launch 响应(见 dapLaunchRequest / dapConfigurationDone 注释)。
|
|
132
|
+
const launchPromise = dapLaunchRequest(client, config);
|
|
133
|
+
if (initBody?.supportsConfigurationDoneRequest === true) {
|
|
134
|
+
await dapConfigurationDone(client);
|
|
135
|
+
}
|
|
136
|
+
await launchPromise;
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
// 握手/launch 失败:dispose 关闭 transport(杀适配器子进程),不留悬挂会话。
|
|
140
|
+
client.dispose();
|
|
141
|
+
throw e;
|
|
142
|
+
}
|
|
143
|
+
sessions.set(session.id, { session, client, config, breakpoints: new Map() });
|
|
90
144
|
return { sessionId: session.id, threadId: lastThread };
|
|
91
145
|
},
|
|
92
146
|
setBreakpoint(sessionId, bp) {
|
|
93
|
-
|
|
147
|
+
const managed = require(sessionId);
|
|
148
|
+
const existing = managed.breakpoints.get(bp.file) ?? [];
|
|
149
|
+
// 同一行重复设置(典型是补/改条件)替换旧条目,其余保留——断点集内不产生重复。
|
|
150
|
+
const next = [...existing.filter((b) => b.line !== bp.line), bp];
|
|
151
|
+
managed.breakpoints.set(bp.file, next);
|
|
152
|
+
return dapSetBreakpoints(managed.client, bp.file, next);
|
|
94
153
|
},
|
|
95
154
|
continue(sessionId, threadId) {
|
|
96
155
|
return dapContinue(require(sessionId).client, threadId);
|
|
@@ -112,9 +171,9 @@ function createDebugSessionManager() {
|
|
|
112
171
|
if (!s)
|
|
113
172
|
return;
|
|
114
173
|
s.session.state = 'stopped';
|
|
115
|
-
// disconnect
|
|
174
|
+
// disconnect 适配器可能已退出/卡死;吞错但记录,便于排查非正常退出。
|
|
116
175
|
try {
|
|
117
|
-
await s.client.request('disconnect', {});
|
|
176
|
+
await s.client.request('disconnect', {}, DAP_DISCONNECT_TIMEOUT_MS);
|
|
118
177
|
}
|
|
119
178
|
catch (e) {
|
|
120
179
|
console.warn('[dap] disconnect failed:', e instanceof Error ? e.message : String(e));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DebugSpawn } from './session.js';
|
|
2
|
+
/** 已知适配器 → [command, args]。 */
|
|
3
|
+
declare const ADAPTER_COMMANDS: Record<string, [string, string[]]>;
|
|
4
|
+
/** 解析适配器 id → spawn 命令(导出供测试)。 */
|
|
5
|
+
declare function resolveAdapterCommand(adapter: string): [string, string[]];
|
|
6
|
+
/**
|
|
7
|
+
* 创建生产 DebugSpawn:按适配器映射 spawn 调试器进程,返回其 stdio 包装的
|
|
8
|
+
* DAPTransport。`overrides` 允许测试/高级用户注入自定义映射。
|
|
9
|
+
*/
|
|
10
|
+
declare function createDebugSpawn(overrides?: Record<string, [string, string[]]>): DebugSpawn;
|
|
11
|
+
export { ADAPTER_COMMANDS, createDebugSpawn, resolveAdapterCommand };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// DAP 宿主接线(spec §21):把调试适配器 id 映射为可 spawn 的命令,包装成
|
|
2
|
+
// DebugSpawn(session.ts 的依赖反转注入点)。此前无任何宿主注入 debugSpawn,
|
|
3
|
+
// debug_start 恒报「no debug adapter spawn is wired」——本模块就是缺的那块。
|
|
4
|
+
//
|
|
5
|
+
// 已知适配器映射(stdlib 命名的保守默认):
|
|
6
|
+
// node → npx @bloopai/js-debug-adapter-stdio --stdio(官方 js-debug 的
|
|
7
|
+
// stdio 包装器:安装时从 microsoft/vscode-js-debug releases 下载
|
|
8
|
+
// 官方 DAP server,运行时把 stdio 桥到其 TCP 端口;js-debug 自己
|
|
9
|
+
// spawn node --inspect)
|
|
10
|
+
// python → python3 -m debugpy.adapter(需 pip install debugpy)
|
|
11
|
+
// go → dlv dap(默认 stdio 模式)
|
|
12
|
+
// lldb → lldb-dap(LLDB DAP 前端)
|
|
13
|
+
// 未知 adapter id → 直接把 id 当作可执行命令名(如 "gdb" 的 DAP 包装器名),
|
|
14
|
+
// 允许用户通过自定义适配器命令扩展。
|
|
15
|
+
//
|
|
16
|
+
// node 为何不用「官方包名」:js-debug 只以 VS Code 扩展 / GitHub release 分发,
|
|
17
|
+
// npm 上并不存在 @vscode/js-debug 之类的官方 DAP server 包(此前映射的
|
|
18
|
+
// `npx -y -p @vscode/js-debug js-debug --stdio` 恒 404,node 调试从第一步就
|
|
19
|
+
// 起不来且报错不指向根因)。上面这个包装器是 npm 上可用的 stdio DAP server。
|
|
20
|
+
import { createProcessTransport } from './transport.js';
|
|
21
|
+
/** 已知适配器 → [command, args]。 */
|
|
22
|
+
const ADAPTER_COMMANDS = {
|
|
23
|
+
// 包名即 bin 名,npx 直接解析;--stdio 是包装器自身的 stdio 模式开关。
|
|
24
|
+
node: ['npx', ['-y', '@bloopai/js-debug-adapter-stdio', '--stdio']],
|
|
25
|
+
python: ['python3', ['-m', 'debugpy.adapter']],
|
|
26
|
+
go: ['dlv', ['dap']],
|
|
27
|
+
lldb: ['lldb-dap', []],
|
|
28
|
+
};
|
|
29
|
+
/** 解析适配器 id → spawn 命令(导出供测试)。 */
|
|
30
|
+
function resolveAdapterCommand(adapter) {
|
|
31
|
+
const known = ADAPTER_COMMANDS[adapter];
|
|
32
|
+
if (known)
|
|
33
|
+
return known;
|
|
34
|
+
return [adapter, []];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 创建生产 DebugSpawn:按适配器映射 spawn 调试器进程,返回其 stdio 包装的
|
|
38
|
+
* DAPTransport。`overrides` 允许测试/高级用户注入自定义映射。
|
|
39
|
+
*/
|
|
40
|
+
function createDebugSpawn(overrides) {
|
|
41
|
+
return (config) => {
|
|
42
|
+
const map = overrides ?? ADAPTER_COMMANDS;
|
|
43
|
+
const [command, args] = map[config.adapter] ?? [config.adapter, []];
|
|
44
|
+
const { transport } = createProcessTransport(command, args);
|
|
45
|
+
return transport;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export { ADAPTER_COMMANDS, createDebugSpawn, resolveAdapterCommand };
|
package/dist/dap/transport.js
CHANGED
|
@@ -4,11 +4,29 @@ function createProcessTransport(command, args = []) {
|
|
|
4
4
|
const child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
5
5
|
const dataHandlers = [];
|
|
6
6
|
const closeHandlers = [];
|
|
7
|
+
// spawn 失败(ENOENT 等):无监听器时 unhandled 'error' 会击穿父进程。
|
|
8
|
+
// 仅记录——随后的 'close' 事件走既有关闭路径 reject pending。
|
|
9
|
+
child.on('error', (err) => {
|
|
10
|
+
console.warn(`[dap] 调试适配器进程错误:${err.message}`);
|
|
11
|
+
});
|
|
12
|
+
// 调试器进程崩溃/提前 close(0) 时 stdin 管道写入会异步触发 EPIPE 'error',
|
|
13
|
+
// 该事件发在 stdin socket 上而非 child 上——无监听器同样击穿宿主进程。
|
|
14
|
+
child.stdin.on('error', (err) => {
|
|
15
|
+
console.warn(`[dap] 调试适配器 stdin 写入失败(进程可能已退出):${err.message}`);
|
|
16
|
+
});
|
|
7
17
|
child.stdout.setEncoding('utf8');
|
|
8
18
|
child.stdout.on('data', (chunk) => {
|
|
9
19
|
for (const h of dataHandlers)
|
|
10
20
|
h(chunk);
|
|
11
21
|
});
|
|
22
|
+
// 不读取 stderr 会让管道写满卡死子进程:适配器(js-debug 等)向 stderr 输出
|
|
23
|
+
// 日志/崩溃栈,同步写满 64KB 管道缓冲后阻塞在 write(2) 上,stdout 的 DAP 帧
|
|
24
|
+
// 永远发不出来(握手/请求全部等不到响应)。消费并透传到宿主 stderr——与 MCP
|
|
25
|
+
// stdio 传输同口径,同时保住适配器的错误输出可供排查。
|
|
26
|
+
child.stderr.setEncoding('utf8');
|
|
27
|
+
child.stderr.on('data', (chunk) => {
|
|
28
|
+
process.stderr.write(`[dap] ${chunk}`);
|
|
29
|
+
});
|
|
12
30
|
child.on('close', () => {
|
|
13
31
|
for (const h of closeHandlers)
|
|
14
32
|
h();
|
package/dist/kanban/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { DeletedKanbanBoard, KanbanTrashPurgeResult, MergeKanbanBoardResult, RestoreKanbanBoardResult, } from './store.js';
|
|
2
|
-
export { createKanbanStore, getDeletedKanbanBoard, KanbanCardNotFoundError, KanbanColumnInUseError, KanbanColumnNotFoundError, KanbanInvalidPriorityError, listDeletedKanbanBoards, mergeKanbanBoard, permanentlyDeleteKanbanBoard, purgeDeletedKanbanBoards, restoreKanbanBoard, softDeleteKanbanBoard, } from './store.js';
|
|
2
|
+
export { createKanbanStore, getDeletedKanbanBoard, KanbanCardNotFoundError, KanbanColumnInUseError, KanbanColumnNotFoundError, KanbanInvalidPositionError, KanbanInvalidPriorityError, listDeletedKanbanBoards, mergeKanbanBoard, permanentlyDeleteKanbanBoard, purgeDeletedKanbanBoards, restoreKanbanBoard, softDeleteKanbanBoard, } from './store.js';
|
package/dist/kanban/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createKanbanStore, getDeletedKanbanBoard, KanbanCardNotFoundError, KanbanColumnInUseError, KanbanColumnNotFoundError, KanbanInvalidPriorityError, listDeletedKanbanBoards, mergeKanbanBoard, permanentlyDeleteKanbanBoard, purgeDeletedKanbanBoards, restoreKanbanBoard, softDeleteKanbanBoard, } from './store.js';
|
|
1
|
+
export { createKanbanStore, getDeletedKanbanBoard, KanbanCardNotFoundError, KanbanColumnInUseError, KanbanColumnNotFoundError, KanbanInvalidPositionError, KanbanInvalidPriorityError, listDeletedKanbanBoards, mergeKanbanBoard, permanentlyDeleteKanbanBoard, purgeDeletedKanbanBoards, restoreKanbanBoard, softDeleteKanbanBoard, } from './store.js';
|
package/dist/kanban/store.d.ts
CHANGED
|
@@ -19,6 +19,13 @@ declare class KanbanInvalidPriorityError extends Error {
|
|
|
19
19
|
readonly value: string;
|
|
20
20
|
constructor(value: string);
|
|
21
21
|
}
|
|
22
|
+
/** 非有限 position(如 JSON 1e999 → Infinity)。
|
|
23
|
+
* 写入 position 列后 max(position) 恒 Infinity,后续追加卡片全部同值并列、
|
|
24
|
+
* 排序静默失效且无法自愈——入口显式拒绝。 */
|
|
25
|
+
declare class KanbanInvalidPositionError extends Error {
|
|
26
|
+
readonly value: number;
|
|
27
|
+
constructor(value: number);
|
|
28
|
+
}
|
|
22
29
|
/** 卡片不存在(update/move 目标 id 无效)。 */
|
|
23
30
|
declare class KanbanCardNotFoundError extends Error {
|
|
24
31
|
readonly cardId: string;
|
|
@@ -29,7 +36,7 @@ declare class KanbanCardNotFoundError extends Error {
|
|
|
29
36
|
* The board is created on first access (lazy) with the default 5 columns.
|
|
30
37
|
*/
|
|
31
38
|
declare function createKanbanStore(handle: DB, projectId: string): KanbanStore;
|
|
32
|
-
export { createKanbanStore, KanbanCardNotFoundError, KanbanColumnInUseError, KanbanColumnNotFoundError, KanbanInvalidPriorityError, };
|
|
39
|
+
export { createKanbanStore, KanbanCardNotFoundError, KanbanColumnInUseError, KanbanColumnNotFoundError, KanbanInvalidPositionError, KanbanInvalidPriorityError, };
|
|
33
40
|
/** 回收站条目信息(列表展示用)。 */
|
|
34
41
|
export type DeletedKanbanBoard = {
|
|
35
42
|
id: string;
|