@dsh-edge/dsh-edge-agent 0.0.0-stage → 0.1.2

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/CHANGELOG.md +158 -0
  2. package/LICENSE +21 -0
  3. package/README.md +181 -2
  4. package/config/edge.example.json +179 -0
  5. package/config/privacy-policy.json +202 -0
  6. package/cordis.desktop-safe.patch.yml +129 -0
  7. package/cordis.patch.yml +382 -0
  8. package/cordis.yml +374 -0
  9. package/lib/attestation.d.ts +2 -0
  10. package/lib/attestation.d.ts.map +1 -0
  11. package/lib/attestation.js +2 -0
  12. package/lib/audit-log.d.ts +2 -0
  13. package/lib/audit-log.d.ts.map +1 -0
  14. package/lib/audit-log.js +2 -0
  15. package/lib/client.js +87 -0
  16. package/lib/cloud-llm-adapter.d.ts +2 -0
  17. package/lib/cloud-llm-adapter.d.ts.map +1 -0
  18. package/lib/cloud-llm-adapter.js +2 -0
  19. package/lib/dsh-adapter/attestation.d.ts +42 -0
  20. package/lib/dsh-adapter/attestation.d.ts.map +1 -0
  21. package/lib/dsh-adapter/attestation.js +40 -0
  22. package/lib/dsh-adapter/audit-log.d.ts +139 -0
  23. package/lib/dsh-adapter/audit-log.d.ts.map +1 -0
  24. package/lib/dsh-adapter/audit-log.js +376 -0
  25. package/lib/dsh-adapter/cloud-egress.d.ts +181 -0
  26. package/lib/dsh-adapter/cloud-egress.d.ts.map +1 -0
  27. package/lib/dsh-adapter/cloud-egress.js +257 -0
  28. package/lib/dsh-adapter/cloud-llm-adapter.d.ts +416 -0
  29. package/lib/dsh-adapter/cloud-llm-adapter.d.ts.map +1 -0
  30. package/lib/dsh-adapter/cloud-llm-adapter.js +1472 -0
  31. package/lib/dsh-adapter/contract.d.ts +80 -0
  32. package/lib/dsh-adapter/contract.d.ts.map +1 -0
  33. package/lib/dsh-adapter/contract.js +194 -0
  34. package/lib/dsh-adapter/desktop-cookie-janitor.d.ts +214 -0
  35. package/lib/dsh-adapter/desktop-cookie-janitor.d.ts.map +1 -0
  36. package/lib/dsh-adapter/desktop-cookie-janitor.js +409 -0
  37. package/lib/dsh-adapter/dsh-edge-agent.d.ts +48 -0
  38. package/lib/dsh-adapter/dsh-edge-agent.d.ts.map +1 -0
  39. package/lib/dsh-adapter/dsh-edge-agent.js +64 -0
  40. package/lib/dsh-adapter/edge-config.d.ts +484 -0
  41. package/lib/dsh-adapter/edge-config.d.ts.map +1 -0
  42. package/lib/dsh-adapter/edge-config.js +898 -0
  43. package/lib/dsh-adapter/edge-context-view.d.ts +184 -0
  44. package/lib/dsh-adapter/edge-context-view.d.ts.map +1 -0
  45. package/lib/dsh-adapter/edge-context-view.js +483 -0
  46. package/lib/dsh-adapter/effective-model.d.ts +85 -0
  47. package/lib/dsh-adapter/effective-model.d.ts.map +1 -0
  48. package/lib/dsh-adapter/effective-model.js +171 -0
  49. package/lib/dsh-adapter/etscli-shell-guard.d.ts +244 -0
  50. package/lib/dsh-adapter/etscli-shell-guard.d.ts.map +1 -0
  51. package/lib/dsh-adapter/etscli-shell-guard.js +420 -0
  52. package/lib/dsh-adapter/etscli-tools.d.ts +112 -0
  53. package/lib/dsh-adapter/etscli-tools.d.ts.map +1 -0
  54. package/lib/dsh-adapter/etscli-tools.js +393 -0
  55. package/lib/dsh-adapter/file-logger.d.ts +294 -0
  56. package/lib/dsh-adapter/file-logger.d.ts.map +1 -0
  57. package/lib/dsh-adapter/file-logger.js +564 -0
  58. package/lib/dsh-adapter/function-call-external.d.ts +357 -0
  59. package/lib/dsh-adapter/function-call-external.d.ts.map +1 -0
  60. package/lib/dsh-adapter/function-call-external.js +1005 -0
  61. package/lib/dsh-adapter/host-egress-guard.d.ts +161 -0
  62. package/lib/dsh-adapter/host-egress-guard.d.ts.map +1 -0
  63. package/lib/dsh-adapter/host-egress-guard.js +213 -0
  64. package/lib/dsh-adapter/host-llm-trace.d.ts +198 -0
  65. package/lib/dsh-adapter/host-llm-trace.d.ts.map +1 -0
  66. package/lib/dsh-adapter/host-llm-trace.js +344 -0
  67. package/lib/dsh-adapter/http-api-routes.d.ts +61 -0
  68. package/lib/dsh-adapter/http-api-routes.d.ts.map +1 -0
  69. package/lib/dsh-adapter/http-api-routes.js +79 -0
  70. package/lib/dsh-adapter/http-api-sessions.d.ts +105 -0
  71. package/lib/dsh-adapter/http-api-sessions.d.ts.map +1 -0
  72. package/lib/dsh-adapter/http-api-sessions.js +173 -0
  73. package/lib/dsh-adapter/http-api.d.ts +221 -0
  74. package/lib/dsh-adapter/http-api.d.ts.map +1 -0
  75. package/lib/dsh-adapter/http-api.js +1811 -0
  76. package/lib/dsh-adapter/index.d.ts +62 -0
  77. package/lib/dsh-adapter/index.d.ts.map +1 -0
  78. package/lib/dsh-adapter/index.js +97 -0
  79. package/lib/dsh-adapter/input-guard-messages.d.ts +56 -0
  80. package/lib/dsh-adapter/input-guard-messages.d.ts.map +1 -0
  81. package/lib/dsh-adapter/input-guard-messages.js +136 -0
  82. package/lib/dsh-adapter/input-guard.d.ts +508 -0
  83. package/lib/dsh-adapter/input-guard.d.ts.map +1 -0
  84. package/lib/dsh-adapter/input-guard.js +2174 -0
  85. package/lib/dsh-adapter/intent-prompts.d.ts +77 -0
  86. package/lib/dsh-adapter/intent-prompts.d.ts.map +1 -0
  87. package/lib/dsh-adapter/intent-prompts.js +97 -0
  88. package/lib/dsh-adapter/intent-router.d.ts +710 -0
  89. package/lib/dsh-adapter/intent-router.d.ts.map +1 -0
  90. package/lib/dsh-adapter/intent-router.js +2802 -0
  91. package/lib/dsh-adapter/intent-signals.d.ts +208 -0
  92. package/lib/dsh-adapter/intent-signals.d.ts.map +1 -0
  93. package/lib/dsh-adapter/intent-signals.js +391 -0
  94. package/lib/dsh-adapter/invariant-install.d.ts +16 -0
  95. package/lib/dsh-adapter/invariant-install.d.ts.map +1 -0
  96. package/lib/dsh-adapter/invariant-install.js +15 -0
  97. package/lib/dsh-adapter/invariant.d.ts +16 -0
  98. package/lib/dsh-adapter/invariant.d.ts.map +1 -0
  99. package/lib/dsh-adapter/invariant.js +22 -0
  100. package/lib/dsh-adapter/llm-adapters.d.ts +19 -0
  101. package/lib/dsh-adapter/llm-adapters.d.ts.map +1 -0
  102. package/lib/dsh-adapter/llm-adapters.js +16 -0
  103. package/lib/dsh-adapter/llm-internal-caller.d.ts +92 -0
  104. package/lib/dsh-adapter/llm-internal-caller.d.ts.map +1 -0
  105. package/lib/dsh-adapter/llm-internal-caller.js +100 -0
  106. package/lib/dsh-adapter/llm-max-turns.d.ts +55 -0
  107. package/lib/dsh-adapter/llm-max-turns.d.ts.map +1 -0
  108. package/lib/dsh-adapter/llm-max-turns.js +127 -0
  109. package/lib/dsh-adapter/llm-trace-store.d.ts +131 -0
  110. package/lib/dsh-adapter/llm-trace-store.d.ts.map +1 -0
  111. package/lib/dsh-adapter/llm-trace-store.js +205 -0
  112. package/lib/dsh-adapter/llm-trace.d.ts +450 -0
  113. package/lib/dsh-adapter/llm-trace.d.ts.map +1 -0
  114. package/lib/dsh-adapter/llm-trace.js +463 -0
  115. package/lib/dsh-adapter/local-llm-adapter.d.ts +141 -0
  116. package/lib/dsh-adapter/local-llm-adapter.d.ts.map +1 -0
  117. package/lib/dsh-adapter/local-llm-adapter.js +787 -0
  118. package/lib/dsh-adapter/local-storage.d.ts +96 -0
  119. package/lib/dsh-adapter/local-storage.d.ts.map +1 -0
  120. package/lib/dsh-adapter/local-storage.js +198 -0
  121. package/lib/dsh-adapter/model-router.d.ts +79 -0
  122. package/lib/dsh-adapter/model-router.d.ts.map +1 -0
  123. package/lib/dsh-adapter/model-router.js +107 -0
  124. package/lib/dsh-adapter/ohttp-gateway.d.ts +49 -0
  125. package/lib/dsh-adapter/ohttp-gateway.d.ts.map +1 -0
  126. package/lib/dsh-adapter/ohttp-gateway.js +32 -0
  127. package/lib/dsh-adapter/openai-sse.d.ts +85 -0
  128. package/lib/dsh-adapter/openai-sse.d.ts.map +1 -0
  129. package/lib/dsh-adapter/openai-sse.js +462 -0
  130. package/lib/dsh-adapter/pii-utils.d.ts +96 -0
  131. package/lib/dsh-adapter/pii-utils.d.ts.map +1 -0
  132. package/lib/dsh-adapter/pii-utils.js +324 -0
  133. package/lib/dsh-adapter/plugin-host.d.ts +38 -0
  134. package/lib/dsh-adapter/plugin-host.d.ts.map +1 -0
  135. package/lib/dsh-adapter/plugin-host.js +38 -0
  136. package/lib/dsh-adapter/plugin.d.ts +46 -0
  137. package/lib/dsh-adapter/plugin.d.ts.map +1 -0
  138. package/lib/dsh-adapter/plugin.js +100 -0
  139. package/lib/dsh-adapter/privacy-policy.d.ts +237 -0
  140. package/lib/dsh-adapter/privacy-policy.d.ts.map +1 -0
  141. package/lib/dsh-adapter/privacy-policy.js +391 -0
  142. package/lib/dsh-adapter/prompt-guard.d.ts +152 -0
  143. package/lib/dsh-adapter/prompt-guard.d.ts.map +1 -0
  144. package/lib/dsh-adapter/prompt-guard.js +872 -0
  145. package/lib/dsh-adapter/realm-skill-bridge.d.ts +365 -0
  146. package/lib/dsh-adapter/realm-skill-bridge.d.ts.map +1 -0
  147. package/lib/dsh-adapter/realm-skill-bridge.js +1180 -0
  148. package/lib/dsh-adapter/sanitizer-pipeline.d.ts +140 -0
  149. package/lib/dsh-adapter/sanitizer-pipeline.d.ts.map +1 -0
  150. package/lib/dsh-adapter/sanitizer-pipeline.js +431 -0
  151. package/lib/dsh-adapter/secure-bridge.d.ts +47 -0
  152. package/lib/dsh-adapter/secure-bridge.d.ts.map +1 -0
  153. package/lib/dsh-adapter/secure-bridge.js +35 -0
  154. package/lib/dsh-adapter/session-attribution.d.ts +70 -0
  155. package/lib/dsh-adapter/session-attribution.d.ts.map +1 -0
  156. package/lib/dsh-adapter/session-attribution.js +76 -0
  157. package/lib/dsh-adapter/sessions.d.ts +86 -0
  158. package/lib/dsh-adapter/sessions.d.ts.map +1 -0
  159. package/lib/dsh-adapter/sessions.js +179 -0
  160. package/lib/dsh-adapter/tcm.d.ts +55 -0
  161. package/lib/dsh-adapter/tcm.d.ts.map +1 -0
  162. package/lib/dsh-adapter/tcm.js +94 -0
  163. package/lib/dsh-adapter/td-bus.d.ts +87 -0
  164. package/lib/dsh-adapter/td-bus.d.ts.map +1 -0
  165. package/lib/dsh-adapter/td-bus.js +196 -0
  166. package/lib/dsh-adapter/tool-runner.d.ts +185 -0
  167. package/lib/dsh-adapter/tool-runner.d.ts.map +1 -0
  168. package/lib/dsh-adapter/tool-runner.js +271 -0
  169. package/lib/dsh-adapter/tools.d.ts +118 -0
  170. package/lib/dsh-adapter/tools.d.ts.map +1 -0
  171. package/lib/dsh-adapter/tools.js +292 -0
  172. package/lib/dsh-adapter/types.d.ts +23 -0
  173. package/lib/dsh-adapter/types.d.ts.map +1 -0
  174. package/lib/dsh-adapter/types.js +16 -0
  175. package/lib/dsh-adapter/voice-input.d.ts +30 -0
  176. package/lib/dsh-adapter/voice-input.d.ts.map +1 -0
  177. package/lib/dsh-adapter/voice-input.js +34 -0
  178. package/lib/dsh-adapter/web-search-tavily-bocha.d.ts +152 -0
  179. package/lib/dsh-adapter/web-search-tavily-bocha.d.ts.map +1 -0
  180. package/lib/dsh-adapter/web-search-tavily-bocha.js +289 -0
  181. package/lib/http-api.d.ts +2 -0
  182. package/lib/http-api.d.ts.map +1 -0
  183. package/lib/http-api.js +2 -0
  184. package/lib/index.d.ts +48 -0
  185. package/lib/index.d.ts.map +1 -0
  186. package/lib/index.js +49 -0
  187. package/lib/intent-router.d.ts +2 -0
  188. package/lib/intent-router.d.ts.map +1 -0
  189. package/lib/intent-router.js +2 -0
  190. package/lib/llm-adapters.d.ts +2 -0
  191. package/lib/llm-adapters.d.ts.map +1 -0
  192. package/lib/llm-adapters.js +2 -0
  193. package/lib/local-llm-adapter.d.ts +2 -0
  194. package/lib/local-llm-adapter.d.ts.map +1 -0
  195. package/lib/local-llm-adapter.js +2 -0
  196. package/lib/local-storage.d.ts +2 -0
  197. package/lib/local-storage.d.ts.map +1 -0
  198. package/lib/local-storage.js +2 -0
  199. package/lib/model-router.d.ts +2 -0
  200. package/lib/model-router.d.ts.map +1 -0
  201. package/lib/model-router.js +2 -0
  202. package/lib/ohttp-gateway.d.ts +2 -0
  203. package/lib/ohttp-gateway.d.ts.map +1 -0
  204. package/lib/ohttp-gateway.js +2 -0
  205. package/lib/plugin-host.d.ts +2 -0
  206. package/lib/plugin-host.d.ts.map +1 -0
  207. package/lib/plugin-host.js +2 -0
  208. package/lib/privacy-policy.d.ts +2 -0
  209. package/lib/privacy-policy.d.ts.map +1 -0
  210. package/lib/privacy-policy.js +2 -0
  211. package/lib/prompt-guard.d.ts +2 -0
  212. package/lib/prompt-guard.d.ts.map +1 -0
  213. package/lib/prompt-guard.js +2 -0
  214. package/lib/sanitizer-pipeline.d.ts +2 -0
  215. package/lib/sanitizer-pipeline.d.ts.map +1 -0
  216. package/lib/sanitizer-pipeline.js +2 -0
  217. package/lib/secure-bridge.d.ts +2 -0
  218. package/lib/secure-bridge.d.ts.map +1 -0
  219. package/lib/secure-bridge.js +2 -0
  220. package/lib/sessions.d.ts +2 -0
  221. package/lib/sessions.d.ts.map +1 -0
  222. package/lib/sessions.js +2 -0
  223. package/lib/tcm.d.ts +2 -0
  224. package/lib/tcm.d.ts.map +1 -0
  225. package/lib/tcm.js +2 -0
  226. package/lib/td-bus.d.ts +2 -0
  227. package/lib/td-bus.d.ts.map +1 -0
  228. package/lib/td-bus.js +2 -0
  229. package/lib/tools.d.ts +2 -0
  230. package/lib/tools.d.ts.map +1 -0
  231. package/lib/tools.js +2 -0
  232. package/lib/voice-input.d.ts +2 -0
  233. package/lib/voice-input.d.ts.map +1 -0
  234. package/lib/voice-input.js +2 -0
  235. package/package.json +253 -3
  236. package/presets/agent-preset-edge.json +50 -0
  237. package/presets/edge.desktop-safe.patch.yml +503 -0
  238. package/presets/edge.patch.yml +557 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/invariant.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEzC,eAAO,MAAM,YAAY,6BAA6B,CAAA;AAEtD,eAAO,MAAM,IAAI,6BAA6B,CAAA;AAE9C,eAAO,MAAM,MAAM,yBAA0B,CAAA;AAE7C,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAQtE"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Package-owned invariant companion. The harness exposes a `ctx.invariants`
3
+ * registry on the root context; each plugin can attach its own runtime
4
+ * assertions that the harness asserts during session lifecycle hooks.
5
+ *
6
+ * For dsh-edge-agent, the invariants we install ensure:
7
+ * - All SessionEvents marked as model-visible are present in the log
8
+ * - The privacy policy is loaded before any plugin reads it
9
+ * - The threat level is consulted before every LLM request
10
+ */
11
+ export const PACKAGE_NAME = '@dsh-edge/dsh-edge-agent';
12
+ export const name = 'dsh-edge-agent-invariant';
13
+ export const inject = ['invariants'];
14
+ export async function apply(ctx) {
15
+ // Lazy-load the invariant registry to avoid pulling the runtime into
16
+ // pure type-graph evaluation paths.
17
+ const { installedRef } = await import('./invariant-install.js');
18
+ const dispose = ctx.invariants.register(PACKAGE_NAME, installedRef);
19
+ return async () => {
20
+ dispose();
21
+ };
22
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * llm-adapters — bundled export of both local and cloud LLM adapters.
3
+ * This is a convenience surface: cordis.yml can mount a single row
4
+ * pointing here instead of two separate rows.
5
+ *
6
+ * Note: this is a re-export shim and does not register a Cordis plugin
7
+ * itself. The individual adapter files (local-llm-adapter.ts and
8
+ * cloud-llm-adapter.ts) own the `apply` exports and the canonical
9
+ * `name`/`Config` plugins; we re-export only the service classes and
10
+ * interfaces here to avoid collisions on the shared plugin-metadata
11
+ * names (`name`, `inject`, `Config`, `apply`).
12
+ *
13
+ * @module dsh-edge-agent/llm-adapters
14
+ */
15
+ export type { LocalLlmAdapter } from './local-llm-adapter.js';
16
+ export { LocalLlmAdapterImpl } from './local-llm-adapter.js';
17
+ export type { CloudLlmAdapter } from './cloud-llm-adapter.js';
18
+ export { CloudLlmAdapterImpl } from './cloud-llm-adapter.js';
19
+ //# sourceMappingURL=llm-adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-adapters.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/llm-adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * llm-adapters — bundled export of both local and cloud LLM adapters.
3
+ * This is a convenience surface: cordis.yml can mount a single row
4
+ * pointing here instead of two separate rows.
5
+ *
6
+ * Note: this is a re-export shim and does not register a Cordis plugin
7
+ * itself. The individual adapter files (local-llm-adapter.ts and
8
+ * cloud-llm-adapter.ts) own the `apply` exports and the canonical
9
+ * `name`/`Config` plugins; we re-export only the service classes and
10
+ * interfaces here to avoid collisions on the shared plugin-metadata
11
+ * names (`name`, `inject`, `Config`, `apply`).
12
+ *
13
+ * @module dsh-edge-agent/llm-adapters
14
+ */
15
+ export { LocalLlmAdapterImpl } from './local-llm-adapter.js';
16
+ export { CloudLlmAdapterImpl } from './cloud-llm-adapter.js';
@@ -0,0 +1,92 @@
1
+ /**
2
+ * llm-internal-caller —— 区分「主对话出网」与「插件内部的 LLM 调用」。
3
+ *
4
+ * ## 为什么要这个
5
+ *
6
+ * 插件自己也会调 LLM:
7
+ *
8
+ * - `prompt-guard` —— 入口安检的**语义检测**(模型档)
9
+ * - `intent-router` —— **意图分类器**(jev / LLM 分类)
10
+ *
11
+ * 它们都走同一个 `ctx.llm.stream(...)`,所以在 adapter 眼里与主对话**完全
12
+ * 一致** —— 都落 `llm.local.start` / `llm.local.end` 审计行。
13
+ *
14
+ * §7.132 的现场:主人看到面板上「意图判定:等待 / 路由决策:等待」,
15
+ * 下面却有「答案出口:作答中… · 路由 local-llm/qwen3.8:27b · messageCount=1 ·
16
+ * promptLen=125」。查审计才发现 —— 那条 `llm.local.start` 是
17
+ * **prompt-guard 的安检调用**(单条消息、125 字符),不是主对话的出网
18
+ * (同一时刻还有一条 `messageCount=5 · promptLen=10171` 才是主对话)。
19
+ *
20
+ * 面板的答案出口节点取「最后一条 `llm.local.start`」,于是抓到了那条短的,
21
+ * 把主对话的真实出网状态盖掉了 —— **面板在显示一件没发生过的事**。
22
+ *
23
+ * ## 做法
24
+ *
25
+ * 内部调用在 options 里带一个额外字段 `edgeInternalCaller`,adapter 把它
26
+ * 落到出口审计行上(`internal` 字段),面板据此把内部调用排除在
27
+ * 「答案出口」之外。
28
+ *
29
+ * 字段名带 `edge` 前缀是为了不与宿主 dsh-llm 的 `GenerateOptions` 撞名 ——
30
+ * 宿主不认这个字段,会原样透传给 adapter。
31
+ */
32
+ /** 内部调用方标识。 */
33
+ export type InternalCaller = 'prompt-guard' | 'intent-classifier';
34
+ /** 宿主 options 上的额外字段名。 */
35
+ export declare const EDGE_INTERNAL_CALLER_FIELD = "edgeInternalCaller";
36
+ /** 带内部调用标记的 options。 */
37
+ export type WithInternalCaller<T> = T & {
38
+ [EDGE_INTERNAL_CALLER_FIELD]: InternalCaller;
39
+ };
40
+ /**
41
+ * 给 options 挂上内部调用标记。
42
+ *
43
+ * 返回的是**变量**而不是对象字面量 —— TS 的 excess property check 只对
44
+ * 直接传参的字面量生效,所以 `llm.stream(withInternalCaller({...}, 'x'))`
45
+ * 不会因为多了这个字段而报错。
46
+ */
47
+ export declare function withInternalCaller<T extends object>(options: T, caller: InternalCaller): WithInternalCaller<T>;
48
+ /** 从 options 里读出内部调用标记(没有就是 undefined = 主对话出网)。 */
49
+ export declare function internalCallerOf(options: unknown): InternalCaller | undefined;
50
+ /**
51
+ * 出网审计行要带的判别字段。
52
+ *
53
+ * - `internal` —— 内部调用标记(prompt-guard / intent-classifier)。没有这个
54
+ * 字段的行**可能**是宿主的内部插件调用(比如会话标题生成),但我们控制
55
+ * 不了它,只能靠下面的 `tools` 判别。
56
+ * - `tools` —— 这次请求带了几件工具。**主对话恒带 30+ 件**(agent loop 一定
57
+ * 带工具表),而一切内部调用(安检 / 分类器 / 标题生成)**一件都不带**。
58
+ * 实测(18:24-18:25 那轮)主对话 `tools=32`,同一时刻的三条内部调用
59
+ * `tools=<none>` —— sessionId 全都相同,靠 sessionId 分不开,靠 tools 可以。
60
+ *
61
+ * ## 为什么 `tools` 恒落(哪怕宿主没传)
62
+ *
63
+ * 第一版是「宿主传了数组才落长度,否则**不落**该字段」—— 想保住旧数据。
64
+ * 但那只保住了**旧数据**,没保住**宿主的内部调用**:宿主的会话标题生成走
65
+ * 同一个 `ctx.llm.stream`,**不传 tools**,于是它落下来的行既没有 `internal`
66
+ * 也没有 `tools` —— 面板按「两者皆无 → 保留」的旧数据兼容分支,会把它当成
67
+ * 主对话的出网,**继续冒充答案出口**(18:34:59 那轮实测:
68
+ * `messageCount=1 · promptLen=96`)。
69
+ *
70
+ * 所以改成**恒落**:宿主传了数组就落长度,没传就落 `0`。
71
+ *
72
+ * 这样「字段不存在」与「字段为 0」就被区分开了:
73
+ *
74
+ * | 行的来源 | `tools` 字段 | 面板判据 | 结果 |
75
+ * |---|---|---|---|
76
+ * | 主对话 | `32` | `> 0` | **保留** ✓ |
77
+ * | 我们的内部调用 | `0` + `internal` | 有 `internal` | **排除** ✓ |
78
+ * | 宿主内部调用(标题生成) | `0` | `=== 0` | **排除** ✓ |
79
+ * | §7.132 之前的旧数据 | *字段不存在* | 既无 `internal` 也无 `tools` | **保留** ✓ |
80
+ *
81
+ * 旧数据不会被误判(字段缺失 ≠ 0),新数据里的内部调用一条不漏。
82
+ *
83
+ * 残留风险:若某个宿主版本**对主对话也不传 tools**,主对话会落 `tools=0`
84
+ * 而被排除,答案出口会漏显示。那是「少显示」而不是「显示假信息」——
85
+ * 按面板诚实性原则,宁可漏显示也不能让面板显示一件没发生过的事。
86
+ * 实测(18:24、18:34 两轮)宿主对主对话恒传 32 件工具。
87
+ */
88
+ export declare function egressAuditFields(options: unknown): {
89
+ tools: number;
90
+ internal?: string;
91
+ };
92
+ //# sourceMappingURL=llm-internal-caller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-internal-caller.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/llm-internal-caller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,eAAe;AACf,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,mBAAmB,CAAA;AAEjE,0BAA0B;AAC1B,eAAO,MAAM,0BAA0B,uBAAuB,CAAA;AAE9D,wBAAwB;AACxB,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,CAAC,0BAA0B,CAAC,EAAE,cAAc,CAAA;CAAE,CAAA;AAExF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EACjD,OAAO,EAAE,CAAC,EACV,MAAM,EAAE,cAAc,GACrB,kBAAkB,CAAC,CAAC,CAAC,CAEvB;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAI7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CASxF"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * llm-internal-caller —— 区分「主对话出网」与「插件内部的 LLM 调用」。
3
+ *
4
+ * ## 为什么要这个
5
+ *
6
+ * 插件自己也会调 LLM:
7
+ *
8
+ * - `prompt-guard` —— 入口安检的**语义检测**(模型档)
9
+ * - `intent-router` —— **意图分类器**(jev / LLM 分类)
10
+ *
11
+ * 它们都走同一个 `ctx.llm.stream(...)`,所以在 adapter 眼里与主对话**完全
12
+ * 一致** —— 都落 `llm.local.start` / `llm.local.end` 审计行。
13
+ *
14
+ * §7.132 的现场:主人看到面板上「意图判定:等待 / 路由决策:等待」,
15
+ * 下面却有「答案出口:作答中… · 路由 local-llm/qwen3.8:27b · messageCount=1 ·
16
+ * promptLen=125」。查审计才发现 —— 那条 `llm.local.start` 是
17
+ * **prompt-guard 的安检调用**(单条消息、125 字符),不是主对话的出网
18
+ * (同一时刻还有一条 `messageCount=5 · promptLen=10171` 才是主对话)。
19
+ *
20
+ * 面板的答案出口节点取「最后一条 `llm.local.start`」,于是抓到了那条短的,
21
+ * 把主对话的真实出网状态盖掉了 —— **面板在显示一件没发生过的事**。
22
+ *
23
+ * ## 做法
24
+ *
25
+ * 内部调用在 options 里带一个额外字段 `edgeInternalCaller`,adapter 把它
26
+ * 落到出口审计行上(`internal` 字段),面板据此把内部调用排除在
27
+ * 「答案出口」之外。
28
+ *
29
+ * 字段名带 `edge` 前缀是为了不与宿主 dsh-llm 的 `GenerateOptions` 撞名 ——
30
+ * 宿主不认这个字段,会原样透传给 adapter。
31
+ */
32
+ /** 宿主 options 上的额外字段名。 */
33
+ export const EDGE_INTERNAL_CALLER_FIELD = 'edgeInternalCaller';
34
+ /**
35
+ * 给 options 挂上内部调用标记。
36
+ *
37
+ * 返回的是**变量**而不是对象字面量 —— TS 的 excess property check 只对
38
+ * 直接传参的字面量生效,所以 `llm.stream(withInternalCaller({...}, 'x'))`
39
+ * 不会因为多了这个字段而报错。
40
+ */
41
+ export function withInternalCaller(options, caller) {
42
+ return { ...options, [EDGE_INTERNAL_CALLER_FIELD]: caller };
43
+ }
44
+ /** 从 options 里读出内部调用标记(没有就是 undefined = 主对话出网)。 */
45
+ export function internalCallerOf(options) {
46
+ if (options === null || typeof options !== 'object')
47
+ return undefined;
48
+ const raw = options[EDGE_INTERNAL_CALLER_FIELD];
49
+ return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
50
+ }
51
+ /**
52
+ * 出网审计行要带的判别字段。
53
+ *
54
+ * - `internal` —— 内部调用标记(prompt-guard / intent-classifier)。没有这个
55
+ * 字段的行**可能**是宿主的内部插件调用(比如会话标题生成),但我们控制
56
+ * 不了它,只能靠下面的 `tools` 判别。
57
+ * - `tools` —— 这次请求带了几件工具。**主对话恒带 30+ 件**(agent loop 一定
58
+ * 带工具表),而一切内部调用(安检 / 分类器 / 标题生成)**一件都不带**。
59
+ * 实测(18:24-18:25 那轮)主对话 `tools=32`,同一时刻的三条内部调用
60
+ * `tools=<none>` —— sessionId 全都相同,靠 sessionId 分不开,靠 tools 可以。
61
+ *
62
+ * ## 为什么 `tools` 恒落(哪怕宿主没传)
63
+ *
64
+ * 第一版是「宿主传了数组才落长度,否则**不落**该字段」—— 想保住旧数据。
65
+ * 但那只保住了**旧数据**,没保住**宿主的内部调用**:宿主的会话标题生成走
66
+ * 同一个 `ctx.llm.stream`,**不传 tools**,于是它落下来的行既没有 `internal`
67
+ * 也没有 `tools` —— 面板按「两者皆无 → 保留」的旧数据兼容分支,会把它当成
68
+ * 主对话的出网,**继续冒充答案出口**(18:34:59 那轮实测:
69
+ * `messageCount=1 · promptLen=96`)。
70
+ *
71
+ * 所以改成**恒落**:宿主传了数组就落长度,没传就落 `0`。
72
+ *
73
+ * 这样「字段不存在」与「字段为 0」就被区分开了:
74
+ *
75
+ * | 行的来源 | `tools` 字段 | 面板判据 | 结果 |
76
+ * |---|---|---|---|
77
+ * | 主对话 | `32` | `> 0` | **保留** ✓ |
78
+ * | 我们的内部调用 | `0` + `internal` | 有 `internal` | **排除** ✓ |
79
+ * | 宿主内部调用(标题生成) | `0` | `=== 0` | **排除** ✓ |
80
+ * | §7.132 之前的旧数据 | *字段不存在* | 既无 `internal` 也无 `tools` | **保留** ✓ |
81
+ *
82
+ * 旧数据不会被误判(字段缺失 ≠ 0),新数据里的内部调用一条不漏。
83
+ *
84
+ * 残留风险:若某个宿主版本**对主对话也不传 tools**,主对话会落 `tools=0`
85
+ * 而被排除,答案出口会漏显示。那是「少显示」而不是「显示假信息」——
86
+ * 按面板诚实性原则,宁可漏显示也不能让面板显示一件没发生过的事。
87
+ * 实测(18:24、18:34 两轮)宿主对主对话恒传 32 件工具。
88
+ */
89
+ export function egressAuditFields(options) {
90
+ const out = { tools: 0 };
91
+ if (options !== null && typeof options === 'object') {
92
+ const tools = options.tools;
93
+ if (Array.isArray(tools))
94
+ out.tools = tools.length;
95
+ }
96
+ const caller = internalCallerOf(options);
97
+ if (caller !== undefined)
98
+ out.internal = caller;
99
+ return out;
100
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * §7.47.5 (2026-09-13) shared module: per-session consecutive tool-call
3
+ * runaway guard for LLM adapters.
4
+ *
5
+ * Originally introduced in §7.42 (2026-09-05, local-llm-adapter only) as
6
+ * MAX_CONSECUTIVE_TOOL_CALLS guard. §7.47.5 discovered that cloud-llm-adapter
7
+ * never adopted the guard, so cloud models (DeepSeek) could loop tool-calls
8
+ * indefinitely — news用例 hits the bug deterministically: "查询最新的AI新闻"
9
+ * triggers web_search → web_fetch cycles that never terminate with text
10
+ * (DeepSeek picks "call more tools" over "synthesize answer"), eventually
11
+ * dsh-agent-loop upper layer aborts with unknown reason → resultLen=0 →
12
+ * user sees empty reply.
13
+ *
14
+ * User feedback 2026-09-13: "每次修BUG通过日志来修理,确保日志里有所有错误的信息"
15
+ * — ops log showed 5+ tool-calls + chat finished reason=unknown + 0 errors,
16
+ * completely hiding the runaway. Adding the guard makes the failure mode
17
+ * visible (audit event + throw with deterministic MAX_TURNS_EXCEEDED code).
18
+ *
19
+ * The module-level Map + prune logic is shared between both adapters so
20
+ * we get unified session-level accounting: a session running across both
21
+ * adapters (e.g. classifier fallback LOCAL → CLOUD) maintains a single count.
22
+ */
23
+ export declare const MAX_CONSECUTIVE_TOOL_CALLS = 20;
24
+ /**
25
+ * Increment the consecutive-tool-call counter for this session and check
26
+ * whether it has exceeded the runaway threshold. Side effects:
27
+ * - bumps the counter on each call (caller should only invoke when the
28
+ * response was finishKind='tool-calls' with no text progress)
29
+ * - periodically prunes stale entries (every 100 calls, drops entries
30
+ * unseen for >5 min)
31
+ * - resets the counter on natural-termination finish (stop / length)
32
+ *
33
+ * Returns the new count after increment. Caller decides whether to throw
34
+ * based on `> MAX_CONSECUTIVE_TOOL_CALLS`. We separate the increment + the
35
+ * throw so the audit + logger emit can fire BEFORE the throw (so ops log
36
+ * records the runaway even when the throw aborts the stream).
37
+ */
38
+ export declare function bumpSessionToolCallCount(sessionId: unknown): number;
39
+ /** Reset the counter for this session (called when the agent loop terminates
40
+ * naturally — finishKind=stop / length). */
41
+ export declare function resetSessionToolCallCount(sessionId: unknown): void;
42
+ /** Read-only accessor for tests / ops log debugging. */
43
+ export declare function peekSessionToolCallCount(sessionId: unknown): number;
44
+ export declare const MAX_SSE_RETRIES_PER_SESSION = 1;
45
+ /** Returns true when the session still has retry budget left and increments
46
+ * the counter atomically; returns false when budget is exhausted.
47
+ * Prune-on-counter-increment: piggyback on the same 100-call cadence as
48
+ * the tool-call guard via a separate prune (cheap, one Map<10 entries). */
49
+ export declare function tryConsumeSseRetryBudget(sessionId: unknown): boolean;
50
+ /** Reset counter when the LLM call completes successfully (terminalChunk
51
+ * reached). Mirrors resetSessionToolCallCount. */
52
+ export declare function resetSessionSseRetryCount(sessionId: unknown): void;
53
+ /** Read-only accessor for tests / ops log debugging. */
54
+ export declare function peekSessionSseRetryCount(sessionId: unknown): number;
55
+ //# sourceMappingURL=llm-max-turns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-max-turns.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/llm-max-turns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,eAAO,MAAM,0BAA0B,KAAK,CAAA;AAe5C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAenE;AAED;6CAC6C;AAC7C,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAGlE;AAED,wDAAwD;AACxD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAGnE;AAyBD,eAAO,MAAM,2BAA2B,IAAI,CAAA;AAS5C;;;4EAG4E;AAC5E,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAgBpE;AAED;mDACmD;AACnD,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAGlE;AAED,wDAAwD;AACxD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,OAAO,GAAG,MAAM,CAGnE"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * §7.47.5 (2026-09-13) shared module: per-session consecutive tool-call
3
+ * runaway guard for LLM adapters.
4
+ *
5
+ * Originally introduced in §7.42 (2026-09-05, local-llm-adapter only) as
6
+ * MAX_CONSECUTIVE_TOOL_CALLS guard. §7.47.5 discovered that cloud-llm-adapter
7
+ * never adopted the guard, so cloud models (DeepSeek) could loop tool-calls
8
+ * indefinitely — news用例 hits the bug deterministically: "查询最新的AI新闻"
9
+ * triggers web_search → web_fetch cycles that never terminate with text
10
+ * (DeepSeek picks "call more tools" over "synthesize answer"), eventually
11
+ * dsh-agent-loop upper layer aborts with unknown reason → resultLen=0 →
12
+ * user sees empty reply.
13
+ *
14
+ * User feedback 2026-09-13: "每次修BUG通过日志来修理,确保日志里有所有错误的信息"
15
+ * — ops log showed 5+ tool-calls + chat finished reason=unknown + 0 errors,
16
+ * completely hiding the runaway. Adding the guard makes the failure mode
17
+ * visible (audit event + throw with deterministic MAX_TURNS_EXCEEDED code).
18
+ *
19
+ * The module-level Map + prune logic is shared between both adapters so
20
+ * we get unified session-level accounting: a session running across both
21
+ * adapters (e.g. classifier fallback LOCAL → CLOUD) maintains a single count.
22
+ */
23
+ export const MAX_CONSECUTIVE_TOOL_CALLS = 20;
24
+ const _sessionToolCallRuns = new Map();
25
+ let _pruneCounter = 0;
26
+ /** Resolve session key from GenerateOptions. Empty/missing falls back to 'default'. */
27
+ function resolveSessionKey(sessionId) {
28
+ return typeof sessionId === 'string' && sessionId.length > 0 ? sessionId : 'default';
29
+ }
30
+ /**
31
+ * Increment the consecutive-tool-call counter for this session and check
32
+ * whether it has exceeded the runaway threshold. Side effects:
33
+ * - bumps the counter on each call (caller should only invoke when the
34
+ * response was finishKind='tool-calls' with no text progress)
35
+ * - periodically prunes stale entries (every 100 calls, drops entries
36
+ * unseen for >5 min)
37
+ * - resets the counter on natural-termination finish (stop / length)
38
+ *
39
+ * Returns the new count after increment. Caller decides whether to throw
40
+ * based on `> MAX_CONSECUTIVE_TOOL_CALLS`. We separate the increment + the
41
+ * throw so the audit + logger emit can fire BEFORE the throw (so ops log
42
+ * records the runaway even when the throw aborts the stream).
43
+ */
44
+ export function bumpSessionToolCallCount(sessionId) {
45
+ const key = resolveSessionKey(sessionId);
46
+ const now = Date.now();
47
+ const entry = _sessionToolCallRuns.get(key) ?? { count: 0, lastSeen: now };
48
+ entry.count += 1;
49
+ entry.lastSeen = now;
50
+ _sessionToolCallRuns.set(key, entry);
51
+ if (++_pruneCounter % 100 === 0) {
52
+ const cutoff = now - 5 * 60 * 1000;
53
+ for (const [k, v] of _sessionToolCallRuns) {
54
+ if (v.lastSeen < cutoff)
55
+ _sessionToolCallRuns.delete(k);
56
+ }
57
+ }
58
+ return entry.count;
59
+ }
60
+ /** Reset the counter for this session (called when the agent loop terminates
61
+ * naturally — finishKind=stop / length). */
62
+ export function resetSessionToolCallCount(sessionId) {
63
+ const key = resolveSessionKey(sessionId);
64
+ _sessionToolCallRuns.delete(key);
65
+ }
66
+ /** Read-only accessor for tests / ops log debugging. */
67
+ export function peekSessionToolCallCount(sessionId) {
68
+ const key = resolveSessionKey(sessionId);
69
+ return _sessionToolCallRuns.get(key)?.count ?? 0;
70
+ }
71
+ // ---------------------------------------------------------------------------
72
+ // §7.50 (2026-09-13): per-session SSE-retry counter for cloud-llm-adapter.
73
+ //
74
+ // Different bug, same per-session accounting shape. Shared module so the
75
+ // counter is co-located with the tool-call guard and follows the same
76
+ // pruning rules.
77
+ //
78
+ // Background: §7.48 system-prompt augmentation moved news verification
79
+ // success from 2/5 → 3/5. The remaining 2/5 failures changed nature from
80
+ // "DeepSeek doesn't synthesize" to "DeepSeek SSE stream 137-494ms disconnect"
81
+ // — short-circuit aborts where the upstream TCP/SSE connection dropped
82
+ // before any chunk arrived. DeepSeek upstream is known to occasionally
83
+ // reset connections early (provider-side hiccup); without a retry the user
84
+ // sees an empty reply even though the request was valid.
85
+ //
86
+ // §7.49 explored retrying at the agent-loop layer and was reverted:
87
+ // dsh-agent-loop's turn model can't rescue kind=error, and new turns are
88
+ // fire-and-forget (don't flow back to http-api response). So retry must
89
+ // happen at the LLM adapter layer — wrap fetch + SSE parse in a retry
90
+ // loop. This counter enforces ≤1 retry per session to bound worst-case
91
+ // latency and avoid amplifying provider-side pressure.
92
+ // ---------------------------------------------------------------------------
93
+ export const MAX_SSE_RETRIES_PER_SESSION = 1;
94
+ const _sessionSseRetries = new Map();
95
+ /** Returns true when the session still has retry budget left and increments
96
+ * the counter atomically; returns false when budget is exhausted.
97
+ * Prune-on-counter-increment: piggyback on the same 100-call cadence as
98
+ * the tool-call guard via a separate prune (cheap, one Map<10 entries). */
99
+ export function tryConsumeSseRetryBudget(sessionId) {
100
+ const key = resolveSessionKey(sessionId);
101
+ const now = Date.now();
102
+ const entry = _sessionSseRetries.get(key) ?? { count: 0, lastSeen: now };
103
+ if (entry.count >= MAX_SSE_RETRIES_PER_SESSION)
104
+ return false;
105
+ entry.count += 1;
106
+ entry.lastSeen = now;
107
+ _sessionSseRetries.set(key, entry);
108
+ if (++_pruneCounter % 100 === 0) {
109
+ const cutoff = now - 5 * 60 * 1000;
110
+ for (const [k, v] of _sessionSseRetries) {
111
+ if (v.lastSeen < cutoff)
112
+ _sessionSseRetries.delete(k);
113
+ }
114
+ }
115
+ return true;
116
+ }
117
+ /** Reset counter when the LLM call completes successfully (terminalChunk
118
+ * reached). Mirrors resetSessionToolCallCount. */
119
+ export function resetSessionSseRetryCount(sessionId) {
120
+ const key = resolveSessionKey(sessionId);
121
+ _sessionSseRetries.delete(key);
122
+ }
123
+ /** Read-only accessor for tests / ops log debugging. */
124
+ export function peekSessionSseRetryCount(sessionId) {
125
+ const key = resolveSessionKey(sessionId);
126
+ return _sessionSseRetries.get(key)?.count ?? 0;
127
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * llm-trace-store — §7.133 LLM request/response trace 的**正文存储**
3
+ *
4
+ * ## 为什么正文不进 audit 行
5
+ *
6
+ * §7.118 的 trace 走 `ctx.logger.info` → ops log 文件。而 Edge 面板的数据
7
+ * 源是 **audit-log**(edge-context-view 的 fold)—— 两条通路不相通,所以
8
+ * 面板看不到任何 request/response 正文。
9
+ *
10
+ * 修法有两条路:
11
+ *
12
+ * A. 正文直接进 audit 行 —— 面板立刻能看到,但单轮主对话的 request
13
+ * 正文实测 **22.5 万字符**(promptLength=225630)。几十轮之后,
14
+ * 面板的常驻内存和渲染压力线性膨胀。
15
+ * B. 面板只放**索引**(每次调用一行摘要),正文按需拉取 —— 索引行是
16
+ * 几十字节的标量,正文存在独立的 store 里,点开才取。
17
+ *
18
+ * 选 B。这与本仓一贯的纪律一致(§7.107「面板不得断言与审计数据相矛盾
19
+ * 的事」、§7.129「宁可漏显示也不能显示一件没发生过的事」):面板常驻
20
+ * 的永远是**便宜的索引**,正文是**要才取**。
21
+ *
22
+ * ## store 的形状
23
+ *
24
+ * `traceId` → { kind: 'REQ' | 'RESP', caller?, trace }。ring buffer:
25
+ * 条数与总字节双上限 —— 单条 trace 正文上限 2 万字符(trace 模块自己
26
+ * 已按 maxChars 截断),所以 64 条 ≈ 1.3 MB 上界。
27
+ *
28
+ * ## 谁来写
29
+ *
30
+ * adapter(主对话出网)+ prompt-guard(安检)+ intent-router(分类器)。
31
+ * 三处都经 `emitTrace()` —— 它同时落 **audit 索引行**(进面板)与
32
+ * **正文**(进本 store)。
33
+ *
34
+ * 检测调用的标记沿用 §7.132 的 `edgeInternalCaller`:安检 = prompt-guard,
35
+ * 分类器 = intent-classifier,主对话没有这个字段。面板据此把「检测调用」
36
+ * 与「主对话出网」分开显示。
37
+ *
38
+ * @module dsh-edge-agent/llm-trace-store
39
+ */
40
+ /** trace 的种类:请求 / 响应。 */
41
+ export type TraceKind = 'REQ' | 'RESP';
42
+ /**
43
+ * 一次调用的归属。§7.132 的内部调用标记(安检 / 分类器)。
44
+ *
45
+ * 与 `llm-internal-caller.ts` 的 `InternalCaller` 是**同一个联合类型**
46
+ * (两个字符串成员相同),TS 结构化类型允许互换 —— 这样 trace 模块不必
47
+ * 依赖 adapter 模块,两边也不会各写一份而漂移。
48
+ */
49
+ export type TraceCaller = 'prompt-guard' | 'intent-classifier';
50
+ export interface StoredTrace {
51
+ kind: TraceKind;
52
+ /** §7.132:内部调用标记;undefined = 主对话出网。 */
53
+ caller?: TraceCaller;
54
+ /** 完整 trace 对象(已脱敏 + 截断)。 */
55
+ trace: unknown;
56
+ }
57
+ export interface TraceStoreOptions {
58
+ /** 最多保留多少条 trace(超出丢最旧的)。 */
59
+ maxEntries: number;
60
+ /** 全部 trace 正文的总字节上限(超出丢最旧的)。 */
61
+ maxBytes: number;
62
+ }
63
+ /** 默认值:64 条 / 2 MB。 */
64
+ export declare const DEFAULT_TRACE_STORE_OPTIONS: TraceStoreOptions;
65
+ /**
66
+ * trace 正文存储。
67
+ *
68
+ * 纯内存 —— 进程退出即消失,这是对的:正文是**当轮**的观测数据,不是
69
+ * 审计记录(审计记录在 audit.plain.jsonl 里,且只存标量)。
70
+ */
71
+ export declare class LlmTraceStore {
72
+ private readonly entries;
73
+ private readonly order;
74
+ private bytes;
75
+ private readonly opts;
76
+ constructor(options?: TraceStoreOptions);
77
+ /** 存一条 trace,返回它的 id(调用方拿去写索引行)。 */
78
+ put(id: string, entry: StoredTrace): void;
79
+ /** 取一条 trace;不存在返回 undefined。 */
80
+ get(id: string): StoredTrace | undefined;
81
+ /** 当前条数(面板显示「已记录 N 次调用」用)。 */
82
+ size(): number;
83
+ /** 丢掉最旧的直到双双回到上限内。 */
84
+ private evict;
85
+ }
86
+ /**
87
+ * §7.133: 生成 trace id。
88
+ *
89
+ * 单调计数 + 时间戳 —— 同一进程内唯一,且排序有意义(面板按时间排)。
90
+ */
91
+ export declare function formatTraceId(seq: number, ts?: number): string;
92
+ /** §7.133: 取下一个 trace id(进程内单调)。 */
93
+ export declare function nextTraceId(): string;
94
+ /** §7.134: 取一个新的 call id(进程内单调)。 */
95
+ export declare function nextCallId(): string;
96
+ /**
97
+ * §7.134: 取这次调用的 call id(第一次调用时创建,之后复用)。
98
+ *
99
+ * 传入的不是对象时(理论上不会发生)退化成「每次一个新 call id」——
100
+ * 宁可配不上,也不把两个不相干的调用错配成一对。
101
+ */
102
+ export declare function callIdFor(options: unknown): string;
103
+ /**
104
+ * trace store 的 service 形态。
105
+ *
106
+ * 为什么是**独立 service** 而不是 edge-context-view 的一个字段:
107
+ * edge-context-view 是 GUI 宿主才有的延迟注入插件(`ctx.inject(['connection'])`),
108
+ * 而写 trace 的是 adapter / prompt-guard / intent-router —— 它们在**任何**
109
+ * profile 里都要能写(headless 的 CLI profile 也要留痕)。
110
+ *
111
+ * 进程内只有一个实例:adapter 与面板读写的必须是**同一个** store,否则
112
+ * 面板点开索引行会永远 404。模块单例是这里最短也最可靠的路 —— ESM 的
113
+ * 模块缓存保证同一个 bundle 里的所有引用指向同一个实例。
114
+ */
115
+ export interface LlmTraceStoreService {
116
+ put(id: string, entry: StoredTrace): void;
117
+ get(id: string): StoredTrace | undefined;
118
+ size(): number;
119
+ }
120
+ /** §7.133: 取进程内的 trace store(不存在则创建)。 */
121
+ export declare function getTraceStore(): LlmTraceStoreService;
122
+ /**
123
+ * 把 store 注册成 cordis service(名字固定为 `llmTraceStore`)。
124
+ *
125
+ * 独立于 plugin 的 apply() —— 由**首个使用者**惰性创建并注册,这样
126
+ * 不需要在 cordis.patch.yml 里加一行(那个 patch row 的形状门禁很严)。
127
+ * 注册是 best-effort:ctx 不是 plugin-level proxy 时静默跳过,写入方照常
128
+ * 用模块单例。
129
+ */
130
+ export declare function provideTraceStore(ctx: unknown): LlmTraceStoreService;
131
+ //# sourceMappingURL=llm-trace-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llm-trace-store.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/llm-trace-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,yBAAyB;AACzB,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,MAAM,CAAA;AAEtC;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,mBAAmB,CAAA;AAE9D,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,SAAS,CAAA;IACf,uCAAuC;IACvC,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,6BAA6B;IAC7B,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,6BAA6B;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,uBAAuB;AACvB,eAAO,MAAM,2BAA2B,EAAE,iBAGzC,CAAA;AAWD;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,KAAK,CAAI;IACjB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;gBAE5B,OAAO,GAAE,iBAA+C;IAIpE,oCAAoC;IACpC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI;IAczC,iCAAiC;IACjC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIxC,8BAA8B;IAC9B,IAAI,IAAI,MAAM;IAId,sBAAsB;IACtB,OAAO,CAAC,KAAK;CAad;AAOD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,GAAE,MAAmB,GAAG,MAAM,CAE1E;AAED,oCAAoC;AACpC,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAmCD,oCAAoC;AACpC,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CASlD;AAID;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAAA;IACxC,IAAI,IAAI,MAAM,CAAA;CACf;AAKD,yCAAyC;AACzC,wBAAgB,aAAa,IAAI,oBAAoB,CAGpD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,oBAAoB,CAkBpE"}