@zqagent/core 0.2.16 → 0.3.6

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 (238) hide show
  1. package/README.md +1 -1
  2. package/README.oss.en.md +15 -2
  3. package/README.oss.md +18 -2
  4. package/README.oss.zh-TW.md +13 -2
  5. package/dist/McpClient-CEQBUPuC.d.cts +145 -0
  6. package/dist/McpClient-CEQBUPuC.d.ts +145 -0
  7. package/dist/{ToolRegistry-C3pTFiaJ.d.cts → ToolRegistry-Cy0fQxrb.d.ts} +2 -111
  8. package/dist/{ToolRegistry-C3pTFiaJ.d.ts → ToolRegistry-DouDvn9E.d.cts} +2 -111
  9. package/dist/{explore-RtZtb-V6.d.ts → TurnEngine-BPKhWuSI.d.cts} +108 -73
  10. package/dist/{explore-BgRhj6SP.d.cts → TurnEngine-DtGEuVLT.d.ts} +108 -73
  11. package/dist/conflictGuard-BBOOIxxK.d.cts +42 -0
  12. package/dist/conflictGuard-BBOOIxxK.d.ts +42 -0
  13. package/dist/index.cjs +5477 -1140
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +577 -195
  16. package/dist/index.d.ts +577 -195
  17. package/dist/index.js +5305 -1002
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/crypto/secrets.cjs +25 -0
  20. package/dist/internal/crypto/secrets.cjs.map +1 -0
  21. package/dist/internal/crypto/secrets.d.cts +2 -0
  22. package/dist/internal/crypto/secrets.d.ts +2 -0
  23. package/dist/internal/crypto/secrets.js +3 -0
  24. package/dist/internal/crypto/secrets.js.map +1 -0
  25. package/dist/internal/engine/FileLogHook.cjs +179 -0
  26. package/dist/internal/engine/FileLogHook.cjs.map +1 -0
  27. package/dist/internal/engine/FileLogHook.d.cts +67 -0
  28. package/dist/internal/engine/FileLogHook.d.ts +67 -0
  29. package/dist/internal/engine/FileLogHook.js +144 -0
  30. package/dist/internal/engine/FileLogHook.js.map +1 -0
  31. package/dist/internal/engine/approvalView.cjs +25 -0
  32. package/dist/internal/engine/approvalView.cjs.map +1 -0
  33. package/dist/internal/engine/approvalView.d.cts +1 -0
  34. package/dist/internal/engine/approvalView.d.ts +1 -0
  35. package/dist/internal/engine/approvalView.js +3 -0
  36. package/dist/internal/engine/approvalView.js.map +1 -0
  37. package/dist/internal/engine/limits.cjs +59 -0
  38. package/dist/internal/engine/limits.cjs.map +1 -0
  39. package/dist/internal/engine/limits.d.cts +32 -0
  40. package/dist/internal/engine/limits.d.ts +32 -0
  41. package/dist/internal/engine/limits.js +31 -0
  42. package/dist/internal/engine/limits.js.map +1 -0
  43. package/dist/internal/engine/sessionNaming.cjs +47 -0
  44. package/dist/internal/engine/sessionNaming.cjs.map +1 -0
  45. package/dist/internal/engine/sessionNaming.d.cts +25 -0
  46. package/dist/internal/engine/sessionNaming.d.ts +25 -0
  47. package/dist/internal/engine/sessionNaming.js +20 -0
  48. package/dist/internal/engine/sessionNaming.js.map +1 -0
  49. package/dist/internal/llmConfig.cjs +561 -0
  50. package/dist/internal/llmConfig.cjs.map +1 -0
  51. package/dist/internal/llmConfig.d.cts +75 -0
  52. package/dist/internal/llmConfig.d.ts +75 -0
  53. package/dist/internal/llmConfig.js +535 -0
  54. package/dist/internal/llmConfig.js.map +1 -0
  55. package/dist/internal/mcp/builtin/index.cjs +685 -0
  56. package/dist/internal/mcp/builtin/index.cjs.map +1 -0
  57. package/dist/internal/mcp/builtin/index.d.cts +21 -0
  58. package/dist/internal/mcp/builtin/index.d.ts +21 -0
  59. package/dist/internal/mcp/builtin/index.js +652 -0
  60. package/dist/internal/mcp/builtin/index.js.map +1 -0
  61. package/dist/internal/mcp/config.cjs +278 -0
  62. package/dist/internal/mcp/config.cjs.map +1 -0
  63. package/dist/internal/mcp/config.d.cts +61 -0
  64. package/dist/internal/mcp/config.d.ts +61 -0
  65. package/dist/internal/mcp/config.js +235 -0
  66. package/dist/internal/mcp/config.js.map +1 -0
  67. package/dist/internal/mcp/presets.cjs +184 -0
  68. package/dist/internal/mcp/presets.cjs.map +1 -0
  69. package/dist/internal/mcp/presets.d.cts +39 -0
  70. package/dist/internal/mcp/presets.d.ts +39 -0
  71. package/dist/internal/mcp/presets.js +158 -0
  72. package/dist/internal/mcp/presets.js.map +1 -0
  73. package/dist/internal/node-name.cjs +45 -0
  74. package/dist/internal/node-name.cjs.map +1 -0
  75. package/dist/internal/node-name.d.cts +3 -0
  76. package/dist/internal/node-name.d.ts +3 -0
  77. package/dist/internal/node-name.js +10 -0
  78. package/dist/internal/node-name.js.map +1 -0
  79. package/dist/internal/paths.cjs +63 -0
  80. package/dist/internal/paths.cjs.map +1 -0
  81. package/dist/internal/paths.d.cts +17 -0
  82. package/dist/internal/paths.d.ts +17 -0
  83. package/dist/internal/paths.js +25 -0
  84. package/dist/internal/paths.js.map +1 -0
  85. package/dist/internal/providers/diagnostics.cjs +288 -0
  86. package/dist/internal/providers/diagnostics.cjs.map +1 -0
  87. package/dist/internal/providers/diagnostics.d.cts +103 -0
  88. package/dist/internal/providers/diagnostics.d.ts +103 -0
  89. package/dist/internal/providers/diagnostics.js +250 -0
  90. package/dist/internal/providers/diagnostics.js.map +1 -0
  91. package/dist/internal/providers/models.cjs +699 -0
  92. package/dist/internal/providers/models.cjs.map +1 -0
  93. package/dist/internal/providers/models.d.cts +136 -0
  94. package/dist/internal/providers/models.d.ts +136 -0
  95. package/dist/internal/providers/models.js +654 -0
  96. package/dist/internal/providers/models.js.map +1 -0
  97. package/dist/internal/terminal.cjs +161 -0
  98. package/dist/internal/terminal.cjs.map +1 -0
  99. package/dist/internal/terminal.d.cts +58 -0
  100. package/dist/internal/terminal.d.ts +58 -0
  101. package/dist/internal/terminal.js +128 -0
  102. package/dist/internal/terminal.js.map +1 -0
  103. package/dist/internal/tools/execAsync.cjs +187 -0
  104. package/dist/internal/tools/execAsync.cjs.map +1 -0
  105. package/dist/internal/tools/execAsync.d.cts +30 -0
  106. package/dist/internal/tools/execAsync.d.ts +30 -0
  107. package/dist/internal/tools/execAsync.js +162 -0
  108. package/dist/internal/tools/execAsync.js.map +1 -0
  109. package/dist/internal/tools/git.cjs +935 -0
  110. package/dist/internal/tools/git.cjs.map +1 -0
  111. package/dist/internal/tools/git.d.cts +206 -0
  112. package/dist/internal/tools/git.d.ts +206 -0
  113. package/dist/internal/tools/git.js +883 -0
  114. package/dist/internal/tools/git.js.map +1 -0
  115. package/dist/internal/tools/pathSafety.cjs +99 -0
  116. package/dist/internal/tools/pathSafety.cjs.map +1 -0
  117. package/dist/internal/tools/pathSafety.d.cts +44 -0
  118. package/dist/internal/tools/pathSafety.d.ts +44 -0
  119. package/dist/internal/tools/pathSafety.js +61 -0
  120. package/dist/internal/tools/pathSafety.js.map +1 -0
  121. package/dist/internal/tools/plan.cjs +146 -0
  122. package/dist/internal/tools/plan.cjs.map +1 -0
  123. package/dist/internal/tools/plan.d.cts +48 -0
  124. package/dist/internal/tools/plan.d.ts +48 -0
  125. package/dist/internal/tools/plan.js +117 -0
  126. package/dist/internal/tools/plan.js.map +1 -0
  127. package/dist/internal/tools/ripgrep.cjs +118 -0
  128. package/dist/internal/tools/ripgrep.cjs.map +1 -0
  129. package/dist/internal/tools/ripgrep.d.cts +6 -0
  130. package/dist/internal/tools/ripgrep.d.ts +6 -0
  131. package/dist/internal/tools/ripgrep.js +89 -0
  132. package/dist/internal/tools/ripgrep.js.map +1 -0
  133. package/dist/internal/tools/sandbox.cjs +253 -0
  134. package/dist/internal/tools/sandbox.cjs.map +1 -0
  135. package/dist/internal/tools/sandbox.d.cts +67 -0
  136. package/dist/internal/tools/sandbox.d.ts +67 -0
  137. package/dist/internal/tools/sandbox.js +213 -0
  138. package/dist/internal/tools/sandbox.js.map +1 -0
  139. package/dist/internal/tools/shell.cjs +771 -0
  140. package/dist/internal/tools/shell.cjs.map +1 -0
  141. package/dist/internal/tools/shell.d.cts +47 -0
  142. package/dist/internal/tools/shell.d.ts +47 -0
  143. package/dist/internal/tools/shell.js +731 -0
  144. package/dist/internal/tools/shell.js.map +1 -0
  145. package/dist/internal/tools/shellRuntime.cjs +220 -0
  146. package/dist/internal/tools/shellRuntime.cjs.map +1 -0
  147. package/dist/internal/tools/shellRuntime.d.cts +67 -0
  148. package/dist/internal/tools/shellRuntime.d.ts +67 -0
  149. package/dist/internal/tools/shellRuntime.js +181 -0
  150. package/dist/internal/tools/shellRuntime.js.map +1 -0
  151. package/dist/internal/update/controller.cjs +1035 -0
  152. package/dist/internal/update/controller.cjs.map +1 -0
  153. package/dist/internal/update/controller.d.cts +150 -0
  154. package/dist/internal/update/controller.d.ts +150 -0
  155. package/dist/internal/update/controller.js +991 -0
  156. package/dist/internal/update/controller.js.map +1 -0
  157. package/dist/internal/update/platform.cjs +234 -0
  158. package/dist/internal/update/platform.cjs.map +1 -0
  159. package/dist/internal/update/platform.d.cts +77 -0
  160. package/dist/internal/update/platform.d.ts +77 -0
  161. package/dist/internal/update/platform.js +184 -0
  162. package/dist/internal/update/platform.js.map +1 -0
  163. package/dist/tools/ask.d.cts +3 -1
  164. package/dist/tools/ask.d.ts +3 -1
  165. package/dist/tools/diff.cjs +17 -6
  166. package/dist/tools/diff.cjs.map +1 -1
  167. package/dist/tools/diff.d.cts +5 -2
  168. package/dist/tools/diff.d.ts +5 -2
  169. package/dist/tools/diff.js +17 -6
  170. package/dist/tools/diff.js.map +1 -1
  171. package/dist/tools/directories.d.cts +3 -1
  172. package/dist/tools/directories.d.ts +3 -1
  173. package/dist/tools/explore.cjs +1778 -204
  174. package/dist/tools/explore.cjs.map +1 -1
  175. package/dist/tools/explore.d.cts +82 -2
  176. package/dist/tools/explore.d.ts +82 -2
  177. package/dist/tools/explore.js +1780 -204
  178. package/dist/tools/explore.js.map +1 -1
  179. package/dist/tools/files.cjs +56 -21
  180. package/dist/tools/files.cjs.map +1 -1
  181. package/dist/tools/files.d.cts +6 -3
  182. package/dist/tools/files.d.ts +6 -3
  183. package/dist/tools/files.js +56 -21
  184. package/dist/tools/files.js.map +1 -1
  185. package/dist/tools/git.cjs +34 -25
  186. package/dist/tools/git.cjs.map +1 -1
  187. package/dist/tools/git.d.cts +4 -204
  188. package/dist/tools/git.d.ts +4 -204
  189. package/dist/tools/git.js +34 -25
  190. package/dist/tools/git.js.map +1 -1
  191. package/dist/tools/media.cjs +280 -3
  192. package/dist/tools/media.cjs.map +1 -1
  193. package/dist/tools/media.d.cts +3 -1
  194. package/dist/tools/media.d.ts +3 -1
  195. package/dist/tools/media.js +278 -1
  196. package/dist/tools/media.js.map +1 -1
  197. package/dist/tools/plan.cjs +62 -0
  198. package/dist/tools/plan.cjs.map +1 -1
  199. package/dist/tools/plan.d.cts +4 -17
  200. package/dist/tools/plan.d.ts +4 -17
  201. package/dist/tools/plan.js +59 -0
  202. package/dist/tools/plan.js.map +1 -1
  203. package/dist/tools/search.cjs +3 -0
  204. package/dist/tools/search.cjs.map +1 -1
  205. package/dist/tools/search.d.cts +3 -1
  206. package/dist/tools/search.d.ts +3 -1
  207. package/dist/tools/search.js +3 -0
  208. package/dist/tools/search.js.map +1 -1
  209. package/dist/tools/shell.cjs +76 -11
  210. package/dist/tools/shell.cjs.map +1 -1
  211. package/dist/tools/shell.d.cts +5 -39
  212. package/dist/tools/shell.d.ts +5 -39
  213. package/dist/tools/shell.js +75 -11
  214. package/dist/tools/shell.js.map +1 -1
  215. package/dist/tools/todo.d.cts +3 -1
  216. package/dist/tools/todo.d.ts +3 -1
  217. package/dist/tools/web.cjs +279 -5
  218. package/dist/tools/web.cjs.map +1 -1
  219. package/dist/tools/web.d.cts +3 -1
  220. package/dist/tools/web.d.ts +3 -1
  221. package/dist/tools/web.js +275 -3
  222. package/dist/tools/web.js.map +1 -1
  223. package/dist/types-BYyXWWPW.d.cts +237 -0
  224. package/dist/types-BYyXWWPW.d.ts +237 -0
  225. package/package.json +26 -37
  226. package/bin/zqagent +0 -2
  227. package/dist/cli/index.cjs +0 -24507
  228. package/dist/cli/index.cjs.map +0 -1
  229. package/dist/cli/index.d.cts +0 -2
  230. package/dist/cli/index.d.ts +0 -2
  231. package/dist/cli/index.js +0 -24490
  232. package/dist/cli/index.js.map +0 -1
  233. package/dist/server/index.cjs +0 -19824
  234. package/dist/server/index.cjs.map +0 -1
  235. package/dist/server/index.d.cts +0 -2
  236. package/dist/server/index.d.ts +0 -2
  237. package/dist/server/index.js +0 -19807
  238. package/dist/server/index.js.map +0 -1
@@ -1,6 +1,8 @@
1
- import { a as TokenUsage, b as ToolMetadata, M as Message, c as ToolSchema, R as ReasoningEffort, S as StreamChunk, T as ToolRegistry } from './ToolRegistry-C3pTFiaJ.js';
1
+ import { T as TokenUsage, M as Message, a as ToolSchema, R as ReasoningEffort, S as StreamChunk, b as MessageAttachment, c as StoredAttachment } from './types-BYyXWWPW.cjs';
2
+ import { a as ToolMetadata, T as ToolRegistry } from './ToolRegistry-DouDvn9E.cjs';
2
3
  import { ChatCompletionMessageParam } from 'openai/resources/chat/completions.js';
3
4
  import { RequestOptions } from 'openai/core.mjs';
5
+ import { ModelCapability } from '@zqagent/shared/model-capabilities';
4
6
 
5
7
  /**
6
8
  * 事件模型
@@ -182,6 +184,7 @@ interface EngineHook {
182
184
  * - 运行模式(只读模式硬阻断写入与 shell)
183
185
  * - 工具风险分类(读 / 写 / 执行 / 外部)
184
186
  * - 写入路径是否在可写根目录内
187
+ * - shell 是否可免审(OS 沙箱生效 / 命令路径都在工作区内)
185
188
  * - 命令是否命中白名单(且不含 shell 运算符)
186
189
  * - 会话级白名单(用户选择「总是允许」后生效)
187
190
  */
@@ -206,6 +209,24 @@ interface Decision {
206
209
  /** 是否需要用户审批 */
207
210
  needsUser: boolean;
208
211
  }
212
+ /**
213
+ * shell 免审策略
214
+ *
215
+ * - `approve`:除白名单外一律审批(默认,保持既有语义)
216
+ * - `sandbox`:仅当 OS 级沙箱生效时免审,否则审批
217
+ * - `workspace`:沙箱生效 → 免审;未生效 → 用工作区路径判定兜底
218
+ * - `all`:shell 全部免审(等同只对 shell 生效的 auto 模式)
219
+ */
220
+ type ShellPolicy = "approve" | "sandbox" | "workspace" | "all";
221
+ /** 权限引擎可见的沙箱状态(由 tools/sandbox.ts 的 SandboxPlan 投影而来) */
222
+ interface ShellSandboxStatus {
223
+ /** OS 级文件系统约束是否真正生效 */
224
+ enforced: boolean;
225
+ /** 后端标识(sandbox-exec / bwrap / none) */
226
+ backend: string;
227
+ /** 状态说明(用于审批理由 / 日志) */
228
+ reason?: string;
229
+ }
209
230
  /** 权限引擎 (对齐 Python 版 PermissionEngine) */
210
231
  declare class PermissionEngine {
211
232
  /** 工作区根目录(写入路径必须位于其下) */
@@ -218,10 +239,19 @@ declare class PermissionEngine {
218
239
  sessionAllowTools: Set<string>;
219
240
  /** 会话级「总是允许此命令」集合 */
220
241
  sessionAllowCommands: Set<string>;
242
+ /** shell 免审策略 */
243
+ shellPolicy: ShellPolicy;
244
+ /** 沙箱状态(未传时视为无沙箱) */
245
+ sandbox: ShellSandboxStatus | null;
246
+ /** 取当前 shell cwd(用于工作区判定的相对路径解析) */
247
+ shellCwd?: () => string | null;
221
248
  constructor(opts: {
222
249
  workspaceRoot: string;
223
250
  mode: Mode;
224
251
  allowedCommands?: string[];
252
+ shellPolicy?: ShellPolicy;
253
+ sandbox?: ShellSandboxStatus | null;
254
+ shellCwd?: () => string | null;
225
255
  });
226
256
  /**
227
257
  * 评估工具调用是否允许
@@ -230,6 +260,13 @@ declare class PermissionEngine {
230
260
  * 命令白名单 → 会话白名单 → 最后兜底为「需要审批」。
231
261
  */
232
262
  evaluate(toolName: string, args: Record<string, unknown>, metadata?: ToolMetadata): Decision;
263
+ /**
264
+ * 按 shellPolicy 判断是否可免审。
265
+ *
266
+ * 返回 decision 时直接采用;否则 hint 说明「为什么仍需审批」,供审批卡片
267
+ * 展示(例如 `路径越界: /etc/hosts`),让用户一眼看出风险点。
268
+ */
269
+ private _autoAllowShell;
233
270
  /** 将工具加入会话白名单 */
234
271
  allowToolForSession(toolName: string): void;
235
272
  /** 将命令加入会话白名单(按可执行程序名记录,同类命令后续直接放行) */
@@ -369,8 +406,18 @@ declare class OpenAIProvider implements ProviderClient {
369
406
  private _responsesStream;
370
407
  /** Chat Completions 流式路径(默认或 Responses 降级后) */
371
408
  private _chatStream;
372
- /** 将内部 Message 转换为 OpenAI Chat Completions 格式 */
409
+ /**
410
+ * 内部 Message → Chat Completions 消息。
411
+ *
412
+ * 带附件时 `content` 变为数组(DeepSeek 与 OpenAI 的通用格式):
413
+ * - 文本 → `{type:"text"}`
414
+ * - 图片 → `{type:"image_url", image_url:{url:"data:..."}}`(≤32MiB)
415
+ * - 音频 → `{type:"input_audio"}`(仅 wav/mp3,DeepSeek 目前不支持)
416
+ * - 视频 → `{type:"video_url"}`(供应商私有扩展,DeepSeek 目前不支持)
417
+ */
373
418
  private _toOpenAIMessages;
419
+ /** 文本 + 附件 → Chat Completions 的 content part 数组 */
420
+ private _toOpenAIContent;
374
421
  /**
375
422
  * 将内部 Message 转换为 OpenAI Responses API 的 input items。
376
423
  *
@@ -518,8 +565,30 @@ interface TurnEngineOpts {
518
565
  instructions: string;
519
566
  /** 生成会话名称的提示词 */
520
567
  genSessionNamePrompt?: string;
521
- /** 单轮最大迭代次数(默认 300) */
568
+ /**
569
+ * 界面语言(zh / en / zh-TW / ja / ko / es)。
570
+ *
571
+ * 影响会话自动命名的语言,以及上下文压缩摘要与续接指令的语言。
572
+ * 系统提示词本身由调用方按语言构建后通过 `instructions` 传入。
573
+ */
574
+ lang?: string;
575
+ /** 单轮最大迭代次数(缺省用 DEFAULT_MAX_ITERATIONS) */
522
576
  maxIterations?: number;
577
+ /**
578
+ * 达到迭代上限后,是否再用一轮「不带工具」的调用让模型收尾总结。
579
+ *
580
+ * 只给子代理(explore)用:它的产出就是一份报告,半途而废等于白跑。
581
+ * 主代理撞上限通常意味着任务确实没做完,强行总结会给出似是而非的结论,
582
+ * 因此缺省关闭。
583
+ */
584
+ finalizeOnMaxIterations?: boolean;
585
+ /**
586
+ * 当前模型的输入能力(来自 `modelMeta.capabilities`)。
587
+ *
588
+ * 决定附件走「内联给模型」还是「落进工作目录交给助手读」。**不传表示调用方
589
+ * 没有能力信息**(直接集成 core / CLI),此时沿用旧行为:模态可识别就内联。
590
+ */
591
+ modelCapabilities?: ModelCapability[];
523
592
  /**
524
593
  * 推理强度(可选)。透传给 Provider 的 LLM 请求参数
525
594
  * (OpenAI reasoning_effort;DeepSeek 自动跳过)。
@@ -555,6 +624,7 @@ declare class TurnEngine {
555
624
  model: string;
556
625
  instructions: string;
557
626
  genSessionNamePrompt: string;
627
+ lang: string;
558
628
  maxIterations: number;
559
629
  /** 对话历史(含 notice 消息,发送前过滤) */
560
630
  messages: Message[];
@@ -566,6 +636,11 @@ declare class TurnEngine {
566
636
  private _llmAbort;
567
637
  private _toolAbort;
568
638
  private _steering;
639
+ /**
640
+ * 当前模型的输入能力。`undefined` = 调用方没提供(直接集成 core / CLI),
641
+ * 此时沿用旧行为:模态可识别的附件都内联给模型。
642
+ */
643
+ private readonly _modelCaps;
569
644
  compactionState: CompactionState | null;
570
645
  private _lastContextTokens;
571
646
  /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
@@ -592,6 +667,10 @@ declare class TurnEngine {
592
667
  private taskId?;
593
668
  /** 推理强度(透传给 Provider;undefined = 使用模型默认) */
594
669
  private _reasoningEffort;
670
+ /** 撞上限后是否再跑一轮收尾(见 TurnEngineOpts.finalizeOnMaxIterations) */
671
+ private _finalizeOnMaxIterations;
672
+ /** 收尾轮只跑一次(防止 yield* 被重入时反复总结) */
673
+ private _finalized;
595
674
  constructor(opts: TurnEngineOpts);
596
675
  /**
597
676
  * 外部中断:置位取消标记 + 掐断在飞的 LLM 请求 + 中止正在执行的工具,
@@ -607,8 +686,18 @@ declare class TurnEngine {
607
686
  * 若历史首条不是 system(如恢复的会话),则在最前插入新的 system 消息。
608
687
  */
609
688
  setSystemPrompt(prompt: string): void;
689
+ /**
690
+ * 把「模型读不了的附件」落到工作目录,并把路径拼进用户输入。
691
+ *
692
+ * 模型不知道自己漏收了什么,所以必须显式写进消息:否则用户说「见附件」,
693
+ * 模型回「我看不到附件」,任务直接卡死。落盘失败的文件不提及(避免给出
694
+ * 一个不存在的路径)。
695
+ *
696
+ * @returns 内联给模型的附件(已剔除落盘的那些)+ 可能加了说明的文本
697
+ */
698
+ private _routeAttachments;
610
699
  /** 排队 steering 消息(下一迭代间隙注入为 user 消息) */
611
- queueSteering(text: string): void;
700
+ queueSteering(text: string, attachments?: MessageAttachment[], attachmentRefs?: StoredAttachment[]): void;
612
701
  /**
613
702
  * 撤销一条尚未注入的 steering。
614
703
  *
@@ -625,7 +714,9 @@ declare class TurnEngine {
625
714
  * 追加用户输入到历史,产出 TURN_START 事件,然后转发 _loop()
626
715
  * 的全部事件。收到中断后仍会消费完剩余事件(保证 TURN_END 产出)。
627
716
  */
628
- run(userInput: string, taskId?: string): AsyncGenerator<WsEvent>;
717
+ run(userInput: string, taskId?: string, attachments?: MessageAttachment[],
718
+ /** 附件落盘引用(由会话层先存盘再传入):消息与事件只带它,不带 base64 */
719
+ attachmentRefs?: StoredAttachment[]): AsyncGenerator<WsEvent>;
629
720
  /** 重试 (仅在 Provider 错误后可用) */
630
721
  retry(): AsyncGenerator<WsEvent>;
631
722
  /** 从持久化的挂起点恢复 Turn (对齐 Python 版 resume) */
@@ -648,6 +739,17 @@ declare class TurnEngine {
648
739
  * 则执行(_handleToolCalls)→ ITERATION_END → 检查中断与 steering。
649
740
  */
650
741
  private _loop;
742
+ /**
743
+ * 撞上限后的收尾轮(仅 finalizeOnMaxIterations)。
744
+ *
745
+ * 与正常迭代的区别只有两点:追加一条「停止探索、输出报告」的指令,
746
+ * 以及**不提供任何工具**(schemas 传空)。这样模型没有可调用的工具,
747
+ * 只能把已经读到的内容写成正文;正文会像正常轮次一样产出
748
+ * ASSISTANT_MESSAGE,explore 据此取到报告。
749
+ *
750
+ * 收尾轮失败不改变结束状态:已流出的正文照样交给调用方,比直接丢弃好。
751
+ */
752
+ private _finalizeAtLimit;
651
753
  /**
652
754
  * 处理一轮迭代中的全部工具调用
653
755
  *
@@ -723,71 +825,4 @@ declare class TurnEngine {
723
825
  private _scanRecentFiles;
724
826
  }
725
827
 
726
- /**
727
- * Explore 子代理工具
728
- *
729
- * 提供 explore 工具:把广泛的只读研究任务委托给一个独立的
730
- * TurnEngine 子代理。子代理在自己的上下文中搜索和阅读代码,
731
- * 只把最终报告返回给调用方——中间的搜索过程不消耗调用方的上下文。
732
- * 子代理只注册只读工具(read_file / list_files / grep /
733
- * git_log / git_status / git_diff),并以 Plan 模式运行,
734
- * 从机制上保证不会写文件或执行命令。
735
- */
736
-
737
- /**
738
- * 构建 Explorer 子引擎 (对齐 Python 版 build_explorer_engine)
739
- *
740
- * 只注册只读工具,使用 Plan 模式权限引擎(硬阻断所有写入),
741
- * 迭代上限默认 30 次(环境变量 EXPLORE_MAX_ITERATIONS 可覆盖)。
742
- *
743
- * 可选 readonlyRegistrars:外部可注入"只读工具注册器"(例如沙箱远程工具),
744
- * 用于把 explore 子代理的执行目标从宿主机工作区切换到远程沙箱。
745
- * 缺省时使用本地文件系统的只读工具(行为与旧版一致)。
746
- */
747
- declare function buildExplorerEngine(opts: {
748
- workspace: string;
749
- provider: ProviderClient;
750
- model: string;
751
- readonlyRegistrars?: (registry: ToolRegistry) => void;
752
- /**
753
- * 子引擎生命周期钩子(可选):注入后子代理的执行过程(工具活动、终态)
754
- * 会回调给调用方渲染(如 CLI 的 SubagentHook);不传则与旧版一致(静默)。
755
- */
756
- hook?: EngineHook;
757
- /** 超预算工具结果的落盘目录(与主引擎共用,保持租户隔离) */
758
- toolResultDir?: string;
759
- }): TurnEngine;
760
- /**
761
- * 创建 explore 工具函数
762
- *
763
- * 每次调用都会新建一个独立的子引擎,运行研究任务并收集
764
- * 子代理的最终回复作为报告返回。
765
- */
766
- declare function makeExploreTool(opts: {
767
- workspace: string;
768
- provider: ProviderClient;
769
- model: string;
770
- readonlyRegistrars?: (registry: ToolRegistry) => void;
771
- /**
772
- * 子引擎钩子工厂(可选):每次 explore 调用生成一个新实例。
773
- *
774
- * 用工厂而非共享实例,是因为低风险工具会被并发执行(同一条回复里的多个
775
- * explore 会同时运行),共享同一个渲染器会互相串状态、输出交错难辨。
776
- */
777
- hookFactory?: () => EngineHook;
778
- /** 超预算工具结果的落盘目录(透传给子引擎) */
779
- toolResultDir?: string;
780
- }): (args: Record<string, unknown>) => Promise<Record<string, unknown>>;
781
- /** 注册 explore 工具(低风险,无需审批) */
782
- declare function registerExploreTool(registry: ToolRegistry, opts: {
783
- workspace: string;
784
- provider: ProviderClient;
785
- model: string;
786
- readonlyRegistrars?: (registry: ToolRegistry) => void;
787
- /** 子引擎钩子工厂(可选):见 makeExploreTool */
788
- hookFactory?: () => EngineHook;
789
- /** 超预算工具结果的落盘目录(透传给子引擎) */
790
- toolResultDir?: string;
791
- }): void;
792
-
793
- export { ApprovalOutcome as A, type CompactionStartInfo as C, type Decision as D, type EngineHook as E, Mode as M, OpenAIProvider as O, type ProviderClient as P, TurnEngine as T, type WsEvent as W, type CompactionDoneInfo as a, EventType as b, type EngineErrorInfo as c, type Approver as d, type CompactionTrigger as e, PermissionEngine as f, type PermissionRequest as g, type TurnEngineOpts as h, buildExplorerEngine as i, makeExploreTool as m, registerExploreTool as r };
828
+ export { ApprovalOutcome as A, type CompactionStartInfo as C, type Decision as D, type EngineHook as E, Mode as M, OpenAIProvider as O, type ProviderClient as P, type ShellPolicy as S, TurnEngine as T, type WsEvent as W, type CompactionDoneInfo as a, EventType as b, type EngineErrorInfo as c, type Approver as d, type CompactionTrigger as e, PermissionEngine as f, type PermissionRequest as g, type ShellSandboxStatus as h, type TurnEngineOpts as i };
@@ -1,6 +1,8 @@
1
- import { a as TokenUsage, b as ToolMetadata, M as Message, c as ToolSchema, R as ReasoningEffort, S as StreamChunk, T as ToolRegistry } from './ToolRegistry-C3pTFiaJ.cjs';
1
+ import { T as TokenUsage, M as Message, a as ToolSchema, R as ReasoningEffort, S as StreamChunk, b as MessageAttachment, c as StoredAttachment } from './types-BYyXWWPW.js';
2
+ import { a as ToolMetadata, T as ToolRegistry } from './ToolRegistry-Cy0fQxrb.js';
2
3
  import { ChatCompletionMessageParam } from 'openai/resources/chat/completions.js';
3
4
  import { RequestOptions } from 'openai/core.mjs';
5
+ import { ModelCapability } from '@zqagent/shared/model-capabilities';
4
6
 
5
7
  /**
6
8
  * 事件模型
@@ -182,6 +184,7 @@ interface EngineHook {
182
184
  * - 运行模式(只读模式硬阻断写入与 shell)
183
185
  * - 工具风险分类(读 / 写 / 执行 / 外部)
184
186
  * - 写入路径是否在可写根目录内
187
+ * - shell 是否可免审(OS 沙箱生效 / 命令路径都在工作区内)
185
188
  * - 命令是否命中白名单(且不含 shell 运算符)
186
189
  * - 会话级白名单(用户选择「总是允许」后生效)
187
190
  */
@@ -206,6 +209,24 @@ interface Decision {
206
209
  /** 是否需要用户审批 */
207
210
  needsUser: boolean;
208
211
  }
212
+ /**
213
+ * shell 免审策略
214
+ *
215
+ * - `approve`:除白名单外一律审批(默认,保持既有语义)
216
+ * - `sandbox`:仅当 OS 级沙箱生效时免审,否则审批
217
+ * - `workspace`:沙箱生效 → 免审;未生效 → 用工作区路径判定兜底
218
+ * - `all`:shell 全部免审(等同只对 shell 生效的 auto 模式)
219
+ */
220
+ type ShellPolicy = "approve" | "sandbox" | "workspace" | "all";
221
+ /** 权限引擎可见的沙箱状态(由 tools/sandbox.ts 的 SandboxPlan 投影而来) */
222
+ interface ShellSandboxStatus {
223
+ /** OS 级文件系统约束是否真正生效 */
224
+ enforced: boolean;
225
+ /** 后端标识(sandbox-exec / bwrap / none) */
226
+ backend: string;
227
+ /** 状态说明(用于审批理由 / 日志) */
228
+ reason?: string;
229
+ }
209
230
  /** 权限引擎 (对齐 Python 版 PermissionEngine) */
210
231
  declare class PermissionEngine {
211
232
  /** 工作区根目录(写入路径必须位于其下) */
@@ -218,10 +239,19 @@ declare class PermissionEngine {
218
239
  sessionAllowTools: Set<string>;
219
240
  /** 会话级「总是允许此命令」集合 */
220
241
  sessionAllowCommands: Set<string>;
242
+ /** shell 免审策略 */
243
+ shellPolicy: ShellPolicy;
244
+ /** 沙箱状态(未传时视为无沙箱) */
245
+ sandbox: ShellSandboxStatus | null;
246
+ /** 取当前 shell cwd(用于工作区判定的相对路径解析) */
247
+ shellCwd?: () => string | null;
221
248
  constructor(opts: {
222
249
  workspaceRoot: string;
223
250
  mode: Mode;
224
251
  allowedCommands?: string[];
252
+ shellPolicy?: ShellPolicy;
253
+ sandbox?: ShellSandboxStatus | null;
254
+ shellCwd?: () => string | null;
225
255
  });
226
256
  /**
227
257
  * 评估工具调用是否允许
@@ -230,6 +260,13 @@ declare class PermissionEngine {
230
260
  * 命令白名单 → 会话白名单 → 最后兜底为「需要审批」。
231
261
  */
232
262
  evaluate(toolName: string, args: Record<string, unknown>, metadata?: ToolMetadata): Decision;
263
+ /**
264
+ * 按 shellPolicy 判断是否可免审。
265
+ *
266
+ * 返回 decision 时直接采用;否则 hint 说明「为什么仍需审批」,供审批卡片
267
+ * 展示(例如 `路径越界: /etc/hosts`),让用户一眼看出风险点。
268
+ */
269
+ private _autoAllowShell;
233
270
  /** 将工具加入会话白名单 */
234
271
  allowToolForSession(toolName: string): void;
235
272
  /** 将命令加入会话白名单(按可执行程序名记录,同类命令后续直接放行) */
@@ -369,8 +406,18 @@ declare class OpenAIProvider implements ProviderClient {
369
406
  private _responsesStream;
370
407
  /** Chat Completions 流式路径(默认或 Responses 降级后) */
371
408
  private _chatStream;
372
- /** 将内部 Message 转换为 OpenAI Chat Completions 格式 */
409
+ /**
410
+ * 内部 Message → Chat Completions 消息。
411
+ *
412
+ * 带附件时 `content` 变为数组(DeepSeek 与 OpenAI 的通用格式):
413
+ * - 文本 → `{type:"text"}`
414
+ * - 图片 → `{type:"image_url", image_url:{url:"data:..."}}`(≤32MiB)
415
+ * - 音频 → `{type:"input_audio"}`(仅 wav/mp3,DeepSeek 目前不支持)
416
+ * - 视频 → `{type:"video_url"}`(供应商私有扩展,DeepSeek 目前不支持)
417
+ */
373
418
  private _toOpenAIMessages;
419
+ /** 文本 + 附件 → Chat Completions 的 content part 数组 */
420
+ private _toOpenAIContent;
374
421
  /**
375
422
  * 将内部 Message 转换为 OpenAI Responses API 的 input items。
376
423
  *
@@ -518,8 +565,30 @@ interface TurnEngineOpts {
518
565
  instructions: string;
519
566
  /** 生成会话名称的提示词 */
520
567
  genSessionNamePrompt?: string;
521
- /** 单轮最大迭代次数(默认 300) */
568
+ /**
569
+ * 界面语言(zh / en / zh-TW / ja / ko / es)。
570
+ *
571
+ * 影响会话自动命名的语言,以及上下文压缩摘要与续接指令的语言。
572
+ * 系统提示词本身由调用方按语言构建后通过 `instructions` 传入。
573
+ */
574
+ lang?: string;
575
+ /** 单轮最大迭代次数(缺省用 DEFAULT_MAX_ITERATIONS) */
522
576
  maxIterations?: number;
577
+ /**
578
+ * 达到迭代上限后,是否再用一轮「不带工具」的调用让模型收尾总结。
579
+ *
580
+ * 只给子代理(explore)用:它的产出就是一份报告,半途而废等于白跑。
581
+ * 主代理撞上限通常意味着任务确实没做完,强行总结会给出似是而非的结论,
582
+ * 因此缺省关闭。
583
+ */
584
+ finalizeOnMaxIterations?: boolean;
585
+ /**
586
+ * 当前模型的输入能力(来自 `modelMeta.capabilities`)。
587
+ *
588
+ * 决定附件走「内联给模型」还是「落进工作目录交给助手读」。**不传表示调用方
589
+ * 没有能力信息**(直接集成 core / CLI),此时沿用旧行为:模态可识别就内联。
590
+ */
591
+ modelCapabilities?: ModelCapability[];
523
592
  /**
524
593
  * 推理强度(可选)。透传给 Provider 的 LLM 请求参数
525
594
  * (OpenAI reasoning_effort;DeepSeek 自动跳过)。
@@ -555,6 +624,7 @@ declare class TurnEngine {
555
624
  model: string;
556
625
  instructions: string;
557
626
  genSessionNamePrompt: string;
627
+ lang: string;
558
628
  maxIterations: number;
559
629
  /** 对话历史(含 notice 消息,发送前过滤) */
560
630
  messages: Message[];
@@ -566,6 +636,11 @@ declare class TurnEngine {
566
636
  private _llmAbort;
567
637
  private _toolAbort;
568
638
  private _steering;
639
+ /**
640
+ * 当前模型的输入能力。`undefined` = 调用方没提供(直接集成 core / CLI),
641
+ * 此时沿用旧行为:模态可识别的附件都内联给模型。
642
+ */
643
+ private readonly _modelCaps;
569
644
  compactionState: CompactionState | null;
570
645
  private _lastContextTokens;
571
646
  /** 压缩无进展时的消息条数快照;-1 表示无冷却 */
@@ -592,6 +667,10 @@ declare class TurnEngine {
592
667
  private taskId?;
593
668
  /** 推理强度(透传给 Provider;undefined = 使用模型默认) */
594
669
  private _reasoningEffort;
670
+ /** 撞上限后是否再跑一轮收尾(见 TurnEngineOpts.finalizeOnMaxIterations) */
671
+ private _finalizeOnMaxIterations;
672
+ /** 收尾轮只跑一次(防止 yield* 被重入时反复总结) */
673
+ private _finalized;
595
674
  constructor(opts: TurnEngineOpts);
596
675
  /**
597
676
  * 外部中断:置位取消标记 + 掐断在飞的 LLM 请求 + 中止正在执行的工具,
@@ -607,8 +686,18 @@ declare class TurnEngine {
607
686
  * 若历史首条不是 system(如恢复的会话),则在最前插入新的 system 消息。
608
687
  */
609
688
  setSystemPrompt(prompt: string): void;
689
+ /**
690
+ * 把「模型读不了的附件」落到工作目录,并把路径拼进用户输入。
691
+ *
692
+ * 模型不知道自己漏收了什么,所以必须显式写进消息:否则用户说「见附件」,
693
+ * 模型回「我看不到附件」,任务直接卡死。落盘失败的文件不提及(避免给出
694
+ * 一个不存在的路径)。
695
+ *
696
+ * @returns 内联给模型的附件(已剔除落盘的那些)+ 可能加了说明的文本
697
+ */
698
+ private _routeAttachments;
610
699
  /** 排队 steering 消息(下一迭代间隙注入为 user 消息) */
611
- queueSteering(text: string): void;
700
+ queueSteering(text: string, attachments?: MessageAttachment[], attachmentRefs?: StoredAttachment[]): void;
612
701
  /**
613
702
  * 撤销一条尚未注入的 steering。
614
703
  *
@@ -625,7 +714,9 @@ declare class TurnEngine {
625
714
  * 追加用户输入到历史,产出 TURN_START 事件,然后转发 _loop()
626
715
  * 的全部事件。收到中断后仍会消费完剩余事件(保证 TURN_END 产出)。
627
716
  */
628
- run(userInput: string, taskId?: string): AsyncGenerator<WsEvent>;
717
+ run(userInput: string, taskId?: string, attachments?: MessageAttachment[],
718
+ /** 附件落盘引用(由会话层先存盘再传入):消息与事件只带它,不带 base64 */
719
+ attachmentRefs?: StoredAttachment[]): AsyncGenerator<WsEvent>;
629
720
  /** 重试 (仅在 Provider 错误后可用) */
630
721
  retry(): AsyncGenerator<WsEvent>;
631
722
  /** 从持久化的挂起点恢复 Turn (对齐 Python 版 resume) */
@@ -648,6 +739,17 @@ declare class TurnEngine {
648
739
  * 则执行(_handleToolCalls)→ ITERATION_END → 检查中断与 steering。
649
740
  */
650
741
  private _loop;
742
+ /**
743
+ * 撞上限后的收尾轮(仅 finalizeOnMaxIterations)。
744
+ *
745
+ * 与正常迭代的区别只有两点:追加一条「停止探索、输出报告」的指令,
746
+ * 以及**不提供任何工具**(schemas 传空)。这样模型没有可调用的工具,
747
+ * 只能把已经读到的内容写成正文;正文会像正常轮次一样产出
748
+ * ASSISTANT_MESSAGE,explore 据此取到报告。
749
+ *
750
+ * 收尾轮失败不改变结束状态:已流出的正文照样交给调用方,比直接丢弃好。
751
+ */
752
+ private _finalizeAtLimit;
651
753
  /**
652
754
  * 处理一轮迭代中的全部工具调用
653
755
  *
@@ -723,71 +825,4 @@ declare class TurnEngine {
723
825
  private _scanRecentFiles;
724
826
  }
725
827
 
726
- /**
727
- * Explore 子代理工具
728
- *
729
- * 提供 explore 工具:把广泛的只读研究任务委托给一个独立的
730
- * TurnEngine 子代理。子代理在自己的上下文中搜索和阅读代码,
731
- * 只把最终报告返回给调用方——中间的搜索过程不消耗调用方的上下文。
732
- * 子代理只注册只读工具(read_file / list_files / grep /
733
- * git_log / git_status / git_diff),并以 Plan 模式运行,
734
- * 从机制上保证不会写文件或执行命令。
735
- */
736
-
737
- /**
738
- * 构建 Explorer 子引擎 (对齐 Python 版 build_explorer_engine)
739
- *
740
- * 只注册只读工具,使用 Plan 模式权限引擎(硬阻断所有写入),
741
- * 迭代上限默认 30 次(环境变量 EXPLORE_MAX_ITERATIONS 可覆盖)。
742
- *
743
- * 可选 readonlyRegistrars:外部可注入"只读工具注册器"(例如沙箱远程工具),
744
- * 用于把 explore 子代理的执行目标从宿主机工作区切换到远程沙箱。
745
- * 缺省时使用本地文件系统的只读工具(行为与旧版一致)。
746
- */
747
- declare function buildExplorerEngine(opts: {
748
- workspace: string;
749
- provider: ProviderClient;
750
- model: string;
751
- readonlyRegistrars?: (registry: ToolRegistry) => void;
752
- /**
753
- * 子引擎生命周期钩子(可选):注入后子代理的执行过程(工具活动、终态)
754
- * 会回调给调用方渲染(如 CLI 的 SubagentHook);不传则与旧版一致(静默)。
755
- */
756
- hook?: EngineHook;
757
- /** 超预算工具结果的落盘目录(与主引擎共用,保持租户隔离) */
758
- toolResultDir?: string;
759
- }): TurnEngine;
760
- /**
761
- * 创建 explore 工具函数
762
- *
763
- * 每次调用都会新建一个独立的子引擎,运行研究任务并收集
764
- * 子代理的最终回复作为报告返回。
765
- */
766
- declare function makeExploreTool(opts: {
767
- workspace: string;
768
- provider: ProviderClient;
769
- model: string;
770
- readonlyRegistrars?: (registry: ToolRegistry) => void;
771
- /**
772
- * 子引擎钩子工厂(可选):每次 explore 调用生成一个新实例。
773
- *
774
- * 用工厂而非共享实例,是因为低风险工具会被并发执行(同一条回复里的多个
775
- * explore 会同时运行),共享同一个渲染器会互相串状态、输出交错难辨。
776
- */
777
- hookFactory?: () => EngineHook;
778
- /** 超预算工具结果的落盘目录(透传给子引擎) */
779
- toolResultDir?: string;
780
- }): (args: Record<string, unknown>) => Promise<Record<string, unknown>>;
781
- /** 注册 explore 工具(低风险,无需审批) */
782
- declare function registerExploreTool(registry: ToolRegistry, opts: {
783
- workspace: string;
784
- provider: ProviderClient;
785
- model: string;
786
- readonlyRegistrars?: (registry: ToolRegistry) => void;
787
- /** 子引擎钩子工厂(可选):见 makeExploreTool */
788
- hookFactory?: () => EngineHook;
789
- /** 超预算工具结果的落盘目录(透传给子引擎) */
790
- toolResultDir?: string;
791
- }): void;
792
-
793
- export { ApprovalOutcome as A, type CompactionStartInfo as C, type Decision as D, type EngineHook as E, Mode as M, OpenAIProvider as O, type ProviderClient as P, TurnEngine as T, type WsEvent as W, type CompactionDoneInfo as a, EventType as b, type EngineErrorInfo as c, type Approver as d, type CompactionTrigger as e, PermissionEngine as f, type PermissionRequest as g, type TurnEngineOpts as h, buildExplorerEngine as i, makeExploreTool as m, registerExploreTool as r };
828
+ export { ApprovalOutcome as A, type CompactionStartInfo as C, type Decision as D, type EngineHook as E, Mode as M, OpenAIProvider as O, type ProviderClient as P, type ShellPolicy as S, TurnEngine as T, type WsEvent as W, type CompactionDoneInfo as a, EventType as b, type EngineErrorInfo as c, type Approver as d, type CompactionTrigger as e, PermissionEngine as f, type PermissionRequest as g, type ShellSandboxStatus as h, type TurnEngineOpts as i };
@@ -0,0 +1,42 @@
1
+ /** 内容指纹(sha256 前 16 位,足够防碰撞且便于展示) */
2
+ declare function contentDigest(content: string | null | undefined): string;
3
+ interface WriteConflict {
4
+ /** 绝对路径 */
5
+ path: string;
6
+ /** 本会话读到的版本指纹 */
7
+ expected: string;
8
+ /** 磁盘上的当前指纹;文件已被删除时为 null */
9
+ actual: string | null;
10
+ /** 给模型看的错误消息(可直接作为工具返回值) */
11
+ message: string;
12
+ }
13
+ /**
14
+ * 会话级写冲突检测器。
15
+ *
16
+ * 一个会话一个实例;跨会话不共享状态,这正是「检测」而非「阻止」的前提:
17
+ * 每个会话只知道**自己**读到过什么,因此在别人改动后写入会被发现。
18
+ */
19
+ declare class ConflictGuard {
20
+ /** 绝对路径 → 本会话最近一次读到(或自己写入)的内容指纹 */
21
+ private readonly digests;
22
+ /** 记录一次读取:后续写入将以这个版本为基准 */
23
+ noteRead(absPath: string, content: string): void;
24
+ /** 记录一次自己的写入:更新基准,避免同会话连续写触发误报 */
25
+ noteWrite(absPath: string, content: string): void;
26
+ /** 是否读过该文件(用于判断「有没有保护」) */
27
+ hasRead(absPath: string): boolean;
28
+ /**
29
+ * 写入前检查。
30
+ *
31
+ * 返回 null 表示可以写:
32
+ * - 本会话没读过该文件(不保护,保持既有行为);
33
+ * - 或者磁盘内容与读到的版本一致。
34
+ *
35
+ * 返回冲突时调用方应放弃写入并把 message 交给模型,由它重新读取后重试。
36
+ */
37
+ checkWrite(absPath: string, displayPath: string): WriteConflict | null;
38
+ /** 清空记录(会话结束 / 测试用) */
39
+ reset(): void;
40
+ }
41
+
42
+ export { ConflictGuard as C, type WriteConflict as W, contentDigest as c };
@@ -0,0 +1,42 @@
1
+ /** 内容指纹(sha256 前 16 位,足够防碰撞且便于展示) */
2
+ declare function contentDigest(content: string | null | undefined): string;
3
+ interface WriteConflict {
4
+ /** 绝对路径 */
5
+ path: string;
6
+ /** 本会话读到的版本指纹 */
7
+ expected: string;
8
+ /** 磁盘上的当前指纹;文件已被删除时为 null */
9
+ actual: string | null;
10
+ /** 给模型看的错误消息(可直接作为工具返回值) */
11
+ message: string;
12
+ }
13
+ /**
14
+ * 会话级写冲突检测器。
15
+ *
16
+ * 一个会话一个实例;跨会话不共享状态,这正是「检测」而非「阻止」的前提:
17
+ * 每个会话只知道**自己**读到过什么,因此在别人改动后写入会被发现。
18
+ */
19
+ declare class ConflictGuard {
20
+ /** 绝对路径 → 本会话最近一次读到(或自己写入)的内容指纹 */
21
+ private readonly digests;
22
+ /** 记录一次读取:后续写入将以这个版本为基准 */
23
+ noteRead(absPath: string, content: string): void;
24
+ /** 记录一次自己的写入:更新基准,避免同会话连续写触发误报 */
25
+ noteWrite(absPath: string, content: string): void;
26
+ /** 是否读过该文件(用于判断「有没有保护」) */
27
+ hasRead(absPath: string): boolean;
28
+ /**
29
+ * 写入前检查。
30
+ *
31
+ * 返回 null 表示可以写:
32
+ * - 本会话没读过该文件(不保护,保持既有行为);
33
+ * - 或者磁盘内容与读到的版本一致。
34
+ *
35
+ * 返回冲突时调用方应放弃写入并把 message 交给模型,由它重新读取后重试。
36
+ */
37
+ checkWrite(absPath: string, displayPath: string): WriteConflict | null;
38
+ /** 清空记录(会话结束 / 测试用) */
39
+ reset(): void;
40
+ }
41
+
42
+ export { ConflictGuard as C, type WriteConflict as W, contentDigest as c };