@deepwhale/coding-agent 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/bin/deepwhale.js +299 -0
  2. package/dist/agent/agent-compaction.d.ts +74 -0
  3. package/dist/agent/agent-compaction.d.ts.map +1 -0
  4. package/dist/agent/agent-compaction.js +145 -0
  5. package/dist/agent/agent-compaction.js.map +1 -0
  6. package/dist/agent/index.d.ts +16 -0
  7. package/dist/agent/index.d.ts.map +1 -0
  8. package/dist/agent/index.js +17 -0
  9. package/dist/agent/index.js.map +1 -0
  10. package/dist/agent/session-adapter.d.ts +177 -0
  11. package/dist/agent/session-adapter.d.ts.map +1 -0
  12. package/dist/agent/session-adapter.js +340 -0
  13. package/dist/agent/session-adapter.js.map +1 -0
  14. package/dist/agent/tool-loop.d.ts +123 -0
  15. package/dist/agent/tool-loop.d.ts.map +1 -0
  16. package/dist/agent/tool-loop.js +425 -0
  17. package/dist/agent/tool-loop.js.map +1 -0
  18. package/dist/env/load-project-env.d.ts +40 -0
  19. package/dist/env/load-project-env.d.ts.map +1 -0
  20. package/dist/env/load-project-env.js +80 -0
  21. package/dist/env/load-project-env.js.map +1 -0
  22. package/dist/index.d.ts +25 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +24 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/llm-factory.d.ts +47 -0
  27. package/dist/llm-factory.d.ts.map +1 -0
  28. package/dist/llm-factory.js +88 -0
  29. package/dist/llm-factory.js.map +1 -0
  30. package/dist/modes/index.d.ts +14 -0
  31. package/dist/modes/index.d.ts.map +1 -0
  32. package/dist/modes/index.js +14 -0
  33. package/dist/modes/index.js.map +1 -0
  34. package/dist/modes/print.d.ts +50 -0
  35. package/dist/modes/print.d.ts.map +1 -0
  36. package/dist/modes/print.js +236 -0
  37. package/dist/modes/print.js.map +1 -0
  38. package/dist/modes/rpc.d.ts +52 -0
  39. package/dist/modes/rpc.d.ts.map +1 -0
  40. package/dist/modes/rpc.js +316 -0
  41. package/dist/modes/rpc.js.map +1 -0
  42. package/dist/modes/tui.d.ts +54 -0
  43. package/dist/modes/tui.d.ts.map +1 -0
  44. package/dist/modes/tui.js +361 -0
  45. package/dist/modes/tui.js.map +1 -0
  46. package/dist/policy/args-digest.d.ts +13 -0
  47. package/dist/policy/args-digest.d.ts.map +1 -0
  48. package/dist/policy/args-digest.js +29 -0
  49. package/dist/policy/args-digest.js.map +1 -0
  50. package/dist/policy/chain.d.ts +19 -0
  51. package/dist/policy/chain.d.ts.map +1 -0
  52. package/dist/policy/chain.js +24 -0
  53. package/dist/policy/chain.js.map +1 -0
  54. package/dist/policy/sanitize-reason.d.ts +11 -0
  55. package/dist/policy/sanitize-reason.d.ts.map +1 -0
  56. package/dist/policy/sanitize-reason.js +24 -0
  57. package/dist/policy/sanitize-reason.js.map +1 -0
  58. package/dist/policy/static-rules.d.ts +32 -0
  59. package/dist/policy/static-rules.d.ts.map +1 -0
  60. package/dist/policy/static-rules.js +106 -0
  61. package/dist/policy/static-rules.js.map +1 -0
  62. package/dist/policy/types.d.ts +56 -0
  63. package/dist/policy/types.d.ts.map +1 -0
  64. package/dist/policy/types.js +13 -0
  65. package/dist/policy/types.js.map +1 -0
  66. package/dist/repl/repl-confirm.d.ts +49 -0
  67. package/dist/repl/repl-confirm.d.ts.map +1 -0
  68. package/dist/repl/repl-confirm.js +88 -0
  69. package/dist/repl/repl-confirm.js.map +1 -0
  70. package/dist/repl.d.ts +126 -0
  71. package/dist/repl.d.ts.map +1 -0
  72. package/dist/repl.js +734 -0
  73. package/dist/repl.js.map +1 -0
  74. package/dist/sandbox/docker-runner.d.ts +147 -0
  75. package/dist/sandbox/docker-runner.d.ts.map +1 -0
  76. package/dist/sandbox/docker-runner.js +426 -0
  77. package/dist/sandbox/docker-runner.js.map +1 -0
  78. package/dist/sandbox/env-gate.d.ts +28 -0
  79. package/dist/sandbox/env-gate.d.ts.map +1 -0
  80. package/dist/sandbox/env-gate.js +65 -0
  81. package/dist/sandbox/env-gate.js.map +1 -0
  82. package/dist/sandbox/local-runner.d.ts +29 -0
  83. package/dist/sandbox/local-runner.d.ts.map +1 -0
  84. package/dist/sandbox/local-runner.js +79 -0
  85. package/dist/sandbox/local-runner.js.map +1 -0
  86. package/dist/sandbox/types.d.ts +80 -0
  87. package/dist/sandbox/types.d.ts.map +1 -0
  88. package/dist/sandbox/types.js +25 -0
  89. package/dist/sandbox/types.js.map +1 -0
  90. package/dist/tools/bash.d.ts +35 -0
  91. package/dist/tools/bash.d.ts.map +1 -0
  92. package/dist/tools/bash.js +233 -0
  93. package/dist/tools/bash.js.map +1 -0
  94. package/dist/tools/edit-file.d.ts +22 -0
  95. package/dist/tools/edit-file.d.ts.map +1 -0
  96. package/dist/tools/edit-file.js +79 -0
  97. package/dist/tools/edit-file.js.map +1 -0
  98. package/dist/tools/find.d.ts +21 -0
  99. package/dist/tools/find.d.ts.map +1 -0
  100. package/dist/tools/find.js +168 -0
  101. package/dist/tools/find.js.map +1 -0
  102. package/dist/tools/grep.d.ts +19 -0
  103. package/dist/tools/grep.d.ts.map +1 -0
  104. package/dist/tools/grep.js +170 -0
  105. package/dist/tools/grep.js.map +1 -0
  106. package/dist/tools/index.d.ts +10 -0
  107. package/dist/tools/index.d.ts.map +1 -0
  108. package/dist/tools/index.js +10 -0
  109. package/dist/tools/index.js.map +1 -0
  110. package/dist/tools/read-file.d.ts +18 -0
  111. package/dist/tools/read-file.d.ts.map +1 -0
  112. package/dist/tools/read-file.js +52 -0
  113. package/dist/tools/read-file.js.map +1 -0
  114. package/dist/tools/registry.d.ts +39 -0
  115. package/dist/tools/registry.d.ts.map +1 -0
  116. package/dist/tools/registry.js +67 -0
  117. package/dist/tools/registry.js.map +1 -0
  118. package/dist/tools/write-file.d.ts +18 -0
  119. package/dist/tools/write-file.d.ts.map +1 -0
  120. package/dist/tools/write-file.js +47 -0
  121. package/dist/tools/write-file.js.map +1 -0
  122. package/dist/types.d.ts +89 -0
  123. package/dist/types.d.ts.map +1 -0
  124. package/dist/types.js +5 -0
  125. package/dist/types.js.map +1 -0
  126. package/dist/verify/format-report.d.ts +57 -0
  127. package/dist/verify/format-report.d.ts.map +1 -0
  128. package/dist/verify/format-report.js +128 -0
  129. package/dist/verify/format-report.js.map +1 -0
  130. package/dist/verify/index.d.ts +8 -0
  131. package/dist/verify/index.d.ts.map +1 -0
  132. package/dist/verify/index.js +8 -0
  133. package/dist/verify/index.js.map +1 -0
  134. package/dist/verify/verify-runner.d.ts +125 -0
  135. package/dist/verify/verify-runner.d.ts.map +1 -0
  136. package/dist/verify/verify-runner.js +524 -0
  137. package/dist/verify/verify-runner.js.map +1 -0
  138. package/package.json +30 -0
@@ -0,0 +1,299 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * deepwhale CLI 入口 — Sprint 1a
4
+ *
5
+ * 3 种运行模式:
6
+ * deepwhale # interactive REPL(默认)
7
+ * deepwhale -p "..." # print 模式:一次性 chat + tool loop
8
+ * deepwhale --rpc # RPC 模式:NDJSON over stdio(Sprint 1a stub)
9
+ *
10
+ * Sprint 0.3 极简:只启动 REPL。Sprint 1a 加 3 模式路由。
11
+ *
12
+ * 通用参数:
13
+ * --session <path> JSONL 持久化路径
14
+ * --provider <name> 显式 provider (deepseek | anthropic); 走 createDefaultClient
15
+ * 的 env 推断会被覆盖. 拍板 2026-06-04 review P2: 之前
16
+ * 解析没接, --provider 走 'unknown flag' 分支当 print prompt.
17
+ * --model <id> 显式 model id. 跟 createDefaultClient 的 model 字段对接.
18
+ * --no-tool-loop 退化到 Sprint 0.3 单轮 chat(不调工具)
19
+ * --max-steps <n> 工具循环上限(默认 5)
20
+ * --yes Sprint 1c-revive-3-D-13 (2026-06-05): bypass require_confirmation
21
+ * (write_file / edit_file / 危险 bash), 不 bypass deny.
22
+ * --version | -v 输出版本
23
+ * --help | -h 输出帮助
24
+ */
25
+
26
+ import { resolve as pathResolve } from 'node:path';
27
+ import process from 'node:process';
28
+ // Sprint 1c-revive-2-D-7 (review, 2026-06-04): 启动时加载项目根 .env (补缺不覆盖,
29
+ // CI / shell export 优先级最高). 必须在 import dist 之前调, 让 createDefaultClient
30
+ // factory 看到 process.env['DEEPSEEK_API_KEY'] / ['ANTHROPIC_AUTH_TOKEN'] 已就位.
31
+ import { loadProjectEnv } from '../dist/env/load-project-env.js';
32
+ loadProjectEnv();
33
+ import { startRepl } from '../dist/index.js';
34
+ import { runPrintMode } from '../dist/modes/print.js';
35
+ import { runRpcMode } from '../dist/modes/rpc.js';
36
+ import { runTuiMode } from '../dist/modes/tui.js';
37
+ import { runVerify } from '../dist/verify/index.js';
38
+ import { buildSummaryAndNext, formatReport } from '../dist/verify/index.js';
39
+ // Sprint 1c-revive-4-D-20.1 (2026-06-05): 友好错误处理.
40
+ // - APIKeyMissingError: REPL/print/rpc/tui 缺 key 时 → 印 setup hint + exit 2.
41
+ // - "invalid DEEPWHALE_SANDBOX=..." / "invalid DEEPWHALE_DOCKER_NETWORK=...":
42
+ // env-gate throw, CLI 入口接住转 stderr + exit 2.
43
+ import { APIKeyMissingError } from '@deepwhale/llm';
44
+
45
+ /**
46
+ * @typedef {Object} CliArgs
47
+ * @property {'interactive'|'print'|'rpc'|'verify'} mode
48
+ * @property {string|undefined} prompt
49
+ * @property {string|undefined} sessionPath
50
+ * @property {string|undefined} provider
51
+ * @property {string|undefined} model
52
+ * @property {boolean} enableToolLoop
53
+ * @property {number} maxSteps
54
+ * @property {boolean} [yes] Sprint 1c-revive-3-D-13: --yes flag, 透传 3 mode.
55
+ */
56
+
57
+ /**
58
+ * @param {ReadonlyArray<string>} argv
59
+ * @returns {CliArgs}
60
+ */
61
+ function parseArgs(argv) {
62
+ const args = {
63
+ mode: 'interactive',
64
+ prompt: undefined,
65
+ sessionPath: undefined,
66
+ provider: undefined,
67
+ model: undefined,
68
+ enableToolLoop: true,
69
+ maxSteps: 5,
70
+ yes: undefined,
71
+ };
72
+
73
+ let i = 0;
74
+ while (i < argv.length) {
75
+ const a = argv[i];
76
+ if (a === '--version' || a === '-v') {
77
+ process.stdout.write('deepwhale 0.1.0 (Sprint 1a)\n');
78
+ process.exit(0);
79
+ }
80
+ if (a === '--help' || a === '-h') {
81
+ process.stdout.write(HELP_TEXT);
82
+ process.exit(0);
83
+ }
84
+ if (a === '--rpc') {
85
+ args.mode = 'rpc';
86
+ i += 1;
87
+ continue;
88
+ }
89
+ // Sprint 1c-revive-4-D-20.3 (2026-06-05): TUI mode (D-20.3 P0-B)
90
+ // `deepwhale tui` 启动 minimal ANSI TUI. 必须出现在 -p/--prompt 检查之前,
91
+ // 否则 'tui' 会被当 print mode 的 prompt (D-15 拍板: 非 '-' 开头的 argv 走 print).
92
+ if (a === 'tui') {
93
+ args.mode = 'tui';
94
+ i += 1;
95
+ continue;
96
+ }
97
+ if (a === '--verify') {
98
+ // Sprint 1c-revive-2-D-11-4 (2026-06-04): CLI 接入 verify 模式.
99
+ // 走 runVerify() → formatReport() 印到 stdout, 不走 LLM / tool loop / session.
100
+ // 退出码: 0 = passed, 1 = failed (跟 runVerify overallStatus 对应),
101
+ // 2 = 参数错 (跟现有 mode 退出码一致)
102
+ args.mode = 'verify';
103
+ i += 1;
104
+ continue;
105
+ }
106
+ if (a === '-p' || a === '--prompt') {
107
+ args.mode = 'print';
108
+ args.prompt = argv[i + 1] ?? '';
109
+ i += 2;
110
+ continue;
111
+ }
112
+ if (a === '--session') {
113
+ args.sessionPath = pathResolve(argv[i + 1] ?? '');
114
+ i += 2;
115
+ continue;
116
+ }
117
+ if (a === '--provider') {
118
+ // 拍板 2026-06-04 review P2: 之前未接, --provider X 被当 prompt 进入 print 模式.
119
+ // 校验: deepseek | anthropic (跟 llm-factory.ts 的 Provider 拍板一致).
120
+ const v = argv[i + 1] ?? '';
121
+ if (v !== 'deepseek' && v !== 'anthropic') {
122
+ process.stderr.write(`Error: --provider must be 'deepseek' or 'anthropic', got '${v}'\n`);
123
+ process.exit(2);
124
+ }
125
+ args.provider = v;
126
+ i += 2;
127
+ continue;
128
+ }
129
+ if (a === '--yes') {
130
+ // Sprint 1c-revive-3-D-13 (2026-06-05): --yes 透传 3 mode.
131
+ // 拍板: bypass require_confirmation (write_file/edit_file/bash 危险模式),
132
+ // 不 bypass deny. 跟 R-3 拍板一致.
133
+ args.yes = true;
134
+ i += 1;
135
+ continue;
136
+ }
137
+ if (a === '--model') {
138
+ args.model = argv[i + 1] ?? '';
139
+ i += 2;
140
+ continue;
141
+ }
142
+ if (a === '--no-tool-loop') {
143
+ args.enableToolLoop = false;
144
+ i += 1;
145
+ continue;
146
+ }
147
+ if (a === '--max-steps') {
148
+ const n = Number.parseInt(argv[i + 1] ?? '5', 10);
149
+ args.maxSteps = Number.isFinite(n) && n > 0 ? n : 5;
150
+ i += 2;
151
+ continue;
152
+ }
153
+ // 未知参数 → 当作 print 模式的 prompt(Sprint 0.3 兼容)
154
+ if (!a.startsWith('-')) {
155
+ args.mode = 'print';
156
+ args.prompt = a;
157
+ }
158
+ i += 1;
159
+ }
160
+ return args;
161
+ }
162
+
163
+ const HELP_TEXT = `deepwhale — Coding Agent CLI
164
+
165
+ Usage:
166
+ deepwhale Start interactive REPL (default)
167
+ deepwhale -p "<prompt>" Print mode: single-shot chat + tool loop
168
+ deepwhale --verify Verify mode: run build/lint/typecheck/test, no LLM
169
+ deepwhale --rpc RPC mode: NDJSON over stdio (Sprint 1a stub)
170
+ deepwhale tui TUI mode: minimal ANSI TUI (D-20.3, ships in v1.0)
171
+
172
+ Options:
173
+ --session <path> Persist session to JSONL file
174
+ --provider <name> LLM provider: deepseek | anthropic (overrides env detection)
175
+ --model <id> LLM model id (e.g. deepseek-v4-flash, claude-sonnet-4-5)
176
+ --no-tool-loop Disable tool calling (single-turn chat only)
177
+ --max-steps <n> Max tool-loop steps (default 5)
178
+ --yes Bypass require_confirmation (write/edit/dangerous bash).
179
+ Does NOT bypass deny. Sprint 1c-revive-3-D-13.
180
+ --version, -v Print version and exit
181
+ --help, -h Print this help
182
+
183
+ Environment:
184
+ DEEPSEEK_API_KEY Required for deepseek (or --provider).
185
+ ANTHROPIC_AUTH_TOKEN Required for anthropic (or --provider). May also be
186
+ DEEPWHALE_SESSION_KEY for session-at-rest encryption.
187
+ DEEPWHALE_LANG Optional. 'en' (default) or 'zh-CN'.
188
+ DEEPWHALE_SANDBOX Optional. 'local' (default) or 'docker'.
189
+ DEEPWHALE_DOCKER_IMAGE Optional. default 'node:22-alpine'.
190
+ DEEPWHALE_DOCKER_NETWORK Optional. unset|'none' (default) or 'bridge'.
191
+ D-20.1: invalid value → fail-closed (exit 2).
192
+
193
+ Built-in REPL commands:
194
+ /help, /verify, /exit, exit, quit
195
+
196
+ Setup hint:
197
+ If you see "API key not set" on first run:
198
+ 1. Set DEEPSEEK_API_KEY=<your-key> (or ANTHROPIC_AUTH_TOKEN=...)
199
+ 2. Or place it in ./.env (project root, see .env.example)
200
+ 3. --verify does not require any key (build/lint/typecheck/test only)
201
+ `;
202
+
203
+ /**
204
+ * 路由到 3 种模式之一。
205
+ * Sprint 1a:interactive/print 接 tool loop + session;rpc 是 NDJSON 框架 stub。
206
+ * Sprint 1c-revive-2-D-5+ (review P2, 2026-06-04): --provider/--model 透传
207
+ * 3 mode, 跟 createDefaultClient factory 对接.
208
+ *
209
+ * @returns {Promise<number>}
210
+ */
211
+ async function main() {
212
+ const args = parseArgs(process.argv.slice(2));
213
+ switch (args.mode) {
214
+ case 'interactive':
215
+ return startRepl({
216
+ ...(args.sessionPath !== undefined ? { sessionPath: args.sessionPath } : {}),
217
+ ...(args.provider !== undefined ? { provider: args.provider } : {}),
218
+ ...(args.model !== undefined ? { model: args.model } : {}),
219
+ ...(args.yes ? { yes: true } : {}),
220
+ enableToolLoop: args.enableToolLoop,
221
+ });
222
+ case 'print':
223
+ if (!args.prompt) {
224
+ process.stderr.write('Error: print mode requires -p "<prompt>" argument\n');
225
+ return 2;
226
+ }
227
+ return runPrintMode({
228
+ prompt: args.prompt,
229
+ ...(args.sessionPath !== undefined ? { sessionPath: args.sessionPath } : {}),
230
+ ...(args.provider !== undefined ? { provider: args.provider } : {}),
231
+ ...(args.model !== undefined ? { model: args.model } : {}),
232
+ ...(args.yes ? { yes: true } : {}),
233
+ enableToolLoop: args.enableToolLoop,
234
+ maxSteps: args.maxSteps,
235
+ });
236
+ case 'rpc':
237
+ return runRpcMode({
238
+ ...(args.sessionPath !== undefined ? { sessionPath: args.sessionPath } : {}),
239
+ ...(args.provider !== undefined ? { provider: args.provider } : {}),
240
+ ...(args.model !== undefined ? { model: args.model } : {}),
241
+ ...(args.yes ? { yes: true } : {}),
242
+ maxSteps: args.maxSteps,
243
+ });
244
+ case 'tui':
245
+ return runTuiMode({
246
+ ...(args.sessionPath !== undefined ? { sessionPath: args.sessionPath } : {}),
247
+ ...(args.provider !== undefined ? { provider: args.provider } : {}),
248
+ ...(args.model !== undefined ? { model: args.model } : {}),
249
+ ...(args.yes ? { yes: true } : {}),
250
+ enableToolLoop: args.enableToolLoop,
251
+ maxSteps: args.maxSteps,
252
+ });
253
+ case 'verify': // Sprint 1c-revive-2-D-11-4: 走 runVerify (4 步 default), formatReport 印到 stdout.
254
+ // 退出码: passed=0, failed=1 (跟 runVerify overallStatus 对应). 跟 Unix
255
+ // 惯例一致 (CI 脚本 \`if deepwhale --verify; then ...\`).
256
+ // 注: 不写 session event (verify 不是 chat 行为, session JSONL 是 chat 持久化,
257
+ // verify 跑完不污染 session). 后续 sprint 如要 audit log, 留 --verify-log 选项.
258
+ {
259
+ const report = await runVerify();
260
+ const filled = buildSummaryAndNext(report);
261
+ const text = formatReport({
262
+ ...report,
263
+ summary: filled.summary,
264
+ nextSuggestedAction: filled.nextSuggestedAction,
265
+ });
266
+ process.stdout.write(text + '\n');
267
+ return report.overallStatus === 'passed' ? 0 : 1;
268
+ }
269
+ }
270
+ }
271
+
272
+ main().then(
273
+ (code) => process.exit(code),
274
+ (err) => {
275
+ // Sprint 1c-revive-4-D-20.1 (2026-06-05) review-fix: 友好错误处理, 退出码 2 (跟参数错一致).
276
+ // - APIKeyMissingError: REPL/print/rpc/tui 缺 key → 印 setup hint
277
+ // - env-gate 抛 'invalid DEEPWHALE_SANDBOX=...' / 'invalid DEEPWHALE_DOCKER_NETWORK=...'
278
+ // → 直接 stderr 打印 (message 拼齐了 hint), exit 2
279
+ if (err instanceof APIKeyMissingError) {
280
+ process.stderr.write(
281
+ 'Error: API key not set. deepwhale needs DEEPSEEK_API_KEY (or ANTHROPIC_AUTH_TOKEN),\n' +
282
+ ' or pass --provider. See --help for full setup.\n' +
283
+ ' Hint: --verify runs build/lint/typecheck/test and does NOT need a key.\n' +
284
+ ` Underlying: ${err.message}\n`,
285
+ );
286
+ process.exit(2);
287
+ }
288
+ if (err instanceof Error && /^invalid DEEPWHALE_SANDBOX=/.test(err.message)) {
289
+ process.stderr.write(`Error: ${err.message}\n`);
290
+ process.exit(2);
291
+ }
292
+ if (err instanceof Error && /^invalid DEEPWHALE_DOCKER_NETWORK=/.test(err.message)) {
293
+ process.stderr.write(`Error: ${err.message}\n`);
294
+ process.exit(2);
295
+ }
296
+ process.stderr.write(`FATAL: ${err instanceof Error ? err.message : String(err)}\n`);
297
+ process.exit(1);
298
+ },
299
+ );
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Tool Loop + Compaction 集成 (Sprint 1c-revive-2-D-5 + D-5-2)
3
+ *
4
+ * 拍板 (跟 P21 / P27 跨协议 6 cell 一致):
5
+ * - runToolLoop 每次 LLM call 前, 测 messages token 数
6
+ * - 触发 (>= window * compactRatio) → 调 summaryFn (LLM 走同 client 拍板)
7
+ * - 成功 → 替换 working messages, 写 1 条 'compaction' event 到 SessionWriter
8
+ * - 失败 → CompactionState 累计; latch → 写 1 条 'compaction_paused' event
9
+ * - paused → 跳过 compaction (不浪费 LLM token 调 summaryFn, 防 death loop)
10
+ *
11
+ * 协议集成 (P38 拍板 + D-5-2 拍板):
12
+ * - protocol: 'openai' | 'anthropic' (用于 summaryFn, 不同协议 system prompt 不同)
13
+ * - 集成在 runToolLoop 入口, 跟 tool-loop.ts P21 6 cell 拍板一致:
14
+ * DeepSeek + Anthropic 协议都走同一 hook
15
+ *
16
+ * 不变量:
17
+ * - compaction 永不 throw 给 runToolLoop (防 LLM 续聊死锁)
18
+ * - 失败 event 总能写盘 (即使 writer.append 抛错, 也只 warn 不 throw)
19
+ * - paused 后仍跑 runToolLoop 正常 (不阻塞用户任务, 仅不再 compact)
20
+ *
21
+ * @module @deepwhale/coding-agent/agent-compaction
22
+ */
23
+ import { type ChatMessage, type LLMClient } from '@deepwhale/llm';
24
+ import { type CompactionConfig, type SummarizeFn, CompactionState } from '@deepwhale/core';
25
+ import type { SessionWriter } from '@deepwhale/core';
26
+ import type { ToolLoopOptions, ToolLoopResult } from './tool-loop.js';
27
+ /**
28
+ * Compaction 集成配置 (runToolLoop 包装层用).
29
+ *
30
+ * 跟 core CompactionConfig 区别:
31
+ * - client 来自 coding-agent 包 (P21 拍板: 协议从 LLMClient 推断)
32
+ * - state 由集成层管 (caller 用 new CompactionState(...) 注入)
33
+ * - protocol 拍板: 'openai' (DeepSeek) | 'anthropic' — summaryFn 的 system prompt 不同
34
+ */
35
+ export interface AgentCompactionConfig extends CompactionConfig {
36
+ /** 协议拍板 — 跟 P21 6 cell 一致. summaryFn 内部用此拍 system prompt 模板. */
37
+ readonly protocol: 'openai' | 'anthropic';
38
+ /** SessionWriter — 写 'compaction' / 'compaction_paused' event 用 */
39
+ readonly writer: SessionWriter;
40
+ /** CompactionState — caller 管, 跨 runToolLoop 调用持久化 */
41
+ readonly state: CompactionState;
42
+ }
43
+ /**
44
+ * 跑 runToolLoop 集成 compaction 触发.
45
+ *
46
+ * 拍板行为 (跟 P21 6 cell 一致):
47
+ * 1. 入口测 token → 触发则 compact → 替换 working
48
+ * 2. runToolLoop 内部每个 LLM call 前不重复触发 (避免 per-step 开销)
49
+ * 3. 失败 latch → 写 paused event, runToolLoop 继续跑 (不阻塞)
50
+ *
51
+ * Sprint 1c-revive-2-D-6 (review P1 修复, 2026-06-04): caller-side system prefix
52
+ * handling. 拍板: REPL/caller 可能把系统提示拼到 messages 最前面 (e.g. integration
53
+ * test fixture, 或未来 system prompt 注入). 传 compact() 之前先 **剥掉** 连续
54
+ * system 前缀, 让 compact 看到的 index 跟 session-adapter 累积 messages
55
+ * (user/assistant/tool + compaction summary) 同 index 空间, replaced_range
56
+ * 切 reload replay 同步. compact 后把 system 前缀 **原样 prepend 回** 替换后
57
+ * 的 messages, LLM 看到的 context 跟 caller 拼的一致.
58
+ *
59
+ * @param client LLM client
60
+ * @param messages 入口 messages (不变, 内部 copy)
61
+ * @param options runToolLoop 原生 options
62
+ * @param compaction 集成配置
63
+ * @param summaryFn 生成 summary text 的 callback (caller 决定 LLM 协议)
64
+ * @returns ToolLoopResult
65
+ */
66
+ export declare function runToolLoopWithCompaction(client: LLMClient, messages: ReadonlyArray<ChatMessage>, options: ToolLoopOptions | undefined, compaction: AgentCompactionConfig, summaryFn: SummarizeFn): Promise<ToolLoopResult>;
67
+ /**
68
+ * 便捷工具: 估算当前 messages 的 token 数 (供 REPL / footer 拍板显示用).
69
+ *
70
+ * 跟 compaction.ts 的 estimateTokens 拍板一致 (char/4 粗估).
71
+ * 集成在 coding-agent 包, 供 REPL 端零成本 import core.
72
+ */
73
+ export declare function estimateContextTokens(messages: ReadonlyArray<ChatMessage>): number;
74
+ //# sourceMappingURL=agent-compaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-compaction.d.ts","sourceRoot":"","sources":["../../src/agent/agent-compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAEhB,eAAe,EAGhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGtE;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC1C,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,aAAa,CAAC,WAAW,CAAC,EACpC,OAAO,EAAE,eAAe,YAAK,EAC7B,UAAU,EAAE,qBAAqB,EACjC,SAAS,EAAE,WAAW,GACrB,OAAO,CAAC,cAAc,CAAC,CAUzB;AAyFD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,MAAM,CAElF"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Tool Loop + Compaction 集成 (Sprint 1c-revive-2-D-5 + D-5-2)
3
+ *
4
+ * 拍板 (跟 P21 / P27 跨协议 6 cell 一致):
5
+ * - runToolLoop 每次 LLM call 前, 测 messages token 数
6
+ * - 触发 (>= window * compactRatio) → 调 summaryFn (LLM 走同 client 拍板)
7
+ * - 成功 → 替换 working messages, 写 1 条 'compaction' event 到 SessionWriter
8
+ * - 失败 → CompactionState 累计; latch → 写 1 条 'compaction_paused' event
9
+ * - paused → 跳过 compaction (不浪费 LLM token 调 summaryFn, 防 death loop)
10
+ *
11
+ * 协议集成 (P38 拍板 + D-5-2 拍板):
12
+ * - protocol: 'openai' | 'anthropic' (用于 summaryFn, 不同协议 system prompt 不同)
13
+ * - 集成在 runToolLoop 入口, 跟 tool-loop.ts P21 6 cell 拍板一致:
14
+ * DeepSeek + Anthropic 协议都走同一 hook
15
+ *
16
+ * 不变量:
17
+ * - compaction 永不 throw 给 runToolLoop (防 LLM 续聊死锁)
18
+ * - 失败 event 总能写盘 (即使 writer.append 抛错, 也只 warn 不 throw)
19
+ * - paused 后仍跑 runToolLoop 正常 (不阻塞用户任务, 仅不再 compact)
20
+ *
21
+ * @module @deepwhale/coding-agent/agent-compaction
22
+ */
23
+ import { runCompactionWithLatch, estimateTokens, } from '@deepwhale/core';
24
+ import { runToolLoop } from './tool-loop.js';
25
+ /**
26
+ * 跑 runToolLoop 集成 compaction 触发.
27
+ *
28
+ * 拍板行为 (跟 P21 6 cell 一致):
29
+ * 1. 入口测 token → 触发则 compact → 替换 working
30
+ * 2. runToolLoop 内部每个 LLM call 前不重复触发 (避免 per-step 开销)
31
+ * 3. 失败 latch → 写 paused event, runToolLoop 继续跑 (不阻塞)
32
+ *
33
+ * Sprint 1c-revive-2-D-6 (review P1 修复, 2026-06-04): caller-side system prefix
34
+ * handling. 拍板: REPL/caller 可能把系统提示拼到 messages 最前面 (e.g. integration
35
+ * test fixture, 或未来 system prompt 注入). 传 compact() 之前先 **剥掉** 连续
36
+ * system 前缀, 让 compact 看到的 index 跟 session-adapter 累积 messages
37
+ * (user/assistant/tool + compaction summary) 同 index 空间, replaced_range
38
+ * 切 reload replay 同步. compact 后把 system 前缀 **原样 prepend 回** 替换后
39
+ * 的 messages, LLM 看到的 context 跟 caller 拼的一致.
40
+ *
41
+ * @param client LLM client
42
+ * @param messages 入口 messages (不变, 内部 copy)
43
+ * @param options runToolLoop 原生 options
44
+ * @param compaction 集成配置
45
+ * @param summaryFn 生成 summary text 的 callback (caller 决定 LLM 协议)
46
+ * @returns ToolLoopResult
47
+ */
48
+ export async function runToolLoopWithCompaction(client, messages, options = {}, compaction, summaryFn) {
49
+ // 1) 剥掉 caller 拼的连续 system prefix, 保持 replaced_range 跟 JSONL 累积同空间
50
+ const headSystem = splitSystemPrefix(messages);
51
+ // 2) 入口测 token, 触发则 compact 替换 messages (在剥掉 system 的子集上跑)
52
+ const compactedTail = await maybeCompactBeforeLoop(headSystem.tail, compaction, summaryFn);
53
+ // 3) 拼回 system prefix → 跑原生 runToolLoop
54
+ const compactedMessages = [...headSystem.prefix, ...compactedTail];
55
+ return runToolLoop(client, compactedMessages, options);
56
+ }
57
+ /**
58
+ * 把 messages 切分为 "caller 自带的连续 system prefix" + "其余".
59
+ *
60
+ * 拍板 (D-6 review P1 修复, 2026-06-04):
61
+ * - prefix = messages[0..n), n = 头连续 system message 数量
62
+ * - tail = messages[n..)
63
+ * - 没 system prefix → prefix=[], tail=messages
64
+ * - 全 system (只有 1 条 system 也算) → tail=[] (compaction 看到空, 不触发)
65
+ *
66
+ * 用途: caller 把 system prompt 拼前面时, 传给 compact() 前先剥掉, 让
67
+ * replaced_range 的 index 空间 跟 session-adapter replay JSONL 时累积的
68
+ * messages (user/assistant/tool + 之前 reload 注入的 summary) 同空间,
69
+ * reload replay 不会 off-by-one.
70
+ *
71
+ * **不动** 中间或末尾的 system message (罕见, 但保留 — 不归 compaction 管).
72
+ * **不动** tool_call_id 关联, 因为 prefix 是 system role, 不会拖 tool pair.
73
+ */
74
+ function splitSystemPrefix(messages) {
75
+ let n = 0;
76
+ while (n < messages.length && messages[n].role === 'system') {
77
+ n++;
78
+ }
79
+ return {
80
+ prefix: messages.slice(0, n),
81
+ tail: messages.slice(n),
82
+ };
83
+ }
84
+ /**
85
+ * 入口 compaction 触发 (one-shot, 不重入 runToolLoop 内部).
86
+ *
87
+ * 行为契约:
88
+ * - paused → 返 messages 原样, 不调 summaryFn, 不写 event (latched 拍板)
89
+ * - 不该 compact → 返 messages 原样, 不调 summaryFn, 不写 event
90
+ * - compact 成功 → 替换 messages, 写 'compaction' event
91
+ * - compact 失败 (未 latch) → 返 messages 原样, 不写 event (caller 该 retry)
92
+ * - compact latched → 写 'compaction_paused' event, 返 messages 原样
93
+ */
94
+ async function maybeCompactBeforeLoop(messages, compaction, summaryFn) {
95
+ const { state, writer, protocol, contextWindow } = compaction;
96
+ // protocol 显式使用一次, 拍板留位 (跟 P21 6 cell summaryFn 拍 system prompt 模板用)
97
+ void protocol;
98
+ if (contextWindow <= 0)
99
+ return messages;
100
+ if (messages.length === 0)
101
+ return messages;
102
+ if (!state.shouldAttempt())
103
+ return messages;
104
+ let result;
105
+ try {
106
+ result = await runCompactionWithLatch(messages, compaction, summaryFn, state);
107
+ }
108
+ catch {
109
+ // 未触发 latch 的失败: caller 决定怎么处理 (e.g. 用户改配置), 这里不抛
110
+ // 拍板不阻塞 runToolLoop
111
+ return messages;
112
+ }
113
+ if (result === null) {
114
+ // 不该 compact 或 paused
115
+ return messages;
116
+ }
117
+ if (result.kind === 'ok') {
118
+ // 写 'compaction' event — writer.append 抛错只 warn, 不 throw
119
+ try {
120
+ await writer.append(result.event);
121
+ }
122
+ catch {
123
+ // 写盘失败不阻塞 loop
124
+ }
125
+ return result.result.messages;
126
+ }
127
+ // kind === 'latched'
128
+ try {
129
+ await writer.append(result.pausedEvent);
130
+ }
131
+ catch {
132
+ // 写盘失败不阻塞 loop
133
+ }
134
+ return messages;
135
+ }
136
+ /**
137
+ * 便捷工具: 估算当前 messages 的 token 数 (供 REPL / footer 拍板显示用).
138
+ *
139
+ * 跟 compaction.ts 的 estimateTokens 拍板一致 (char/4 粗估).
140
+ * 集成在 coding-agent 包, 供 REPL 端零成本 import core.
141
+ */
142
+ export function estimateContextTokens(messages) {
143
+ return estimateTokens(messages);
144
+ }
145
+ //# sourceMappingURL=agent-compaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-compaction.js","sourceRoot":"","sources":["../../src/agent/agent-compaction.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @deepwhale/coding-agent/agent — Tool Loop & 调度
3
+ *
4
+ * Sprint 1a 落地:
5
+ * - runToolLoop: 最小 agent loop(LLM ↔ tool_calls ↔ LLM)
6
+ * - ToolLoopLimitError: maxSteps 触顶时抛
7
+ * - session-adapter: tool loop step ↔ JSONL SessionEvent 转换
8
+ *
9
+ * Sprint 1b 再加:plan mode、schema 校验、budget cap、onStep 实时回调。
10
+ * Sprint 2+ 再加:并行 tool_call、recovery 3-way、断点续传。
11
+ */
12
+ export { runToolLoop, isToolLoopError, TOOL_LOOP_DEFAULT_MAX_STEPS, type ToolLoopOptions, type ToolLoopResult, type ToolLoopStep, ToolLoopLimitError, } from './tool-loop.js';
13
+ export { toolLoopStepToSessionEvent, sessionEventsToMessages, appendUserEvent, appendCompactionEvent, appendCompactionPausedEvent, appendVerificationEvent, persistToolLoopSteps, loadSession, } from './session-adapter.js';
14
+ export { runToolLoopWithCompaction, estimateContextTokens, type AgentCompactionConfig, } from './agent-compaction.js';
15
+ export { CompactionState, type CompactionConfig } from '@deepwhale/core';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,2BAA2B,EAC3B,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,eAAe,EACf,qBAAqB,EACrB,2BAA2B,EAC3B,uBAAuB,EACvB,oBAAoB,EACpB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,yBAAyB,EACzB,qBAAqB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @deepwhale/coding-agent/agent — Tool Loop & 调度
3
+ *
4
+ * Sprint 1a 落地:
5
+ * - runToolLoop: 最小 agent loop(LLM ↔ tool_calls ↔ LLM)
6
+ * - ToolLoopLimitError: maxSteps 触顶时抛
7
+ * - session-adapter: tool loop step ↔ JSONL SessionEvent 转换
8
+ *
9
+ * Sprint 1b 再加:plan mode、schema 校验、budget cap、onStep 实时回调。
10
+ * Sprint 2+ 再加:并行 tool_call、recovery 3-way、断点续传。
11
+ */
12
+ export { runToolLoop, isToolLoopError, TOOL_LOOP_DEFAULT_MAX_STEPS, ToolLoopLimitError, } from './tool-loop.js';
13
+ export { toolLoopStepToSessionEvent, sessionEventsToMessages, appendUserEvent, appendCompactionEvent, appendCompactionPausedEvent, appendVerificationEvent, persistToolLoopSteps, loadSession, } from './session-adapter.js';
14
+ export { runToolLoopWithCompaction, estimateContextTokens, } from './agent-compaction.js';
15
+ // Re-export core compaction types for caller convenience
16
+ export { CompactionState } from '@deepwhale/core';
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,WAAW,EACX,eAAe,EACf,2BAA2B,EAI3B,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,uBAAuB,EACvB,eAAe,EACf,qBAAqB,EACrB,2BAA2B,EAC3B,uBAAuB,EACvB,oBAAoB,EACpB,WAAW,GACZ,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,yBAAyB,EACzB,qBAAqB,GAEtB,MAAM,uBAAuB,CAAC;AAC/B,yDAAyD;AACzD,OAAO,EAAE,eAAe,EAAyB,MAAM,iBAAiB,CAAC"}