@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,208 @@
1
+ /**
2
+ * intent-signals — the STRATEGY DATA for the intent router.
3
+ *
4
+ * §7.123 (2026-10-27): these tables used to live inside `intent-router.ts`
5
+ * (2923 lines), mixed with the decision LOGIC. Separating "what counts as a
6
+ * public-web signal" (data) from "how the verdict is reached" (logic) means:
7
+ *
8
+ * - a reviewer can audit the routing vocabulary without reading the
9
+ * classifier machinery;
10
+ * - the tables can be unit-tested directly (they used to be reachable
11
+ * only through a 2200-line class);
12
+ * - adding a domain (旅游攻略, 行情, …) touches ONE file instead of a
13
+ * 130 KB module.
14
+ *
15
+ * These are BUILT-IN defaults. The operator-extensible surfaces live in
16
+ * `config/privacy-policy.json` (`stages.intent.cloudKeywords` /
17
+ * `localEntities` / `fallbackKeywords`) and in `config/edge.json`; the
18
+ * built-ins are the last-resort / fast-path layer.
19
+ *
20
+ * @module dsh-edge-agent/intent-signals
21
+ */
22
+ import type { IntentCategory } from './privacy-policy.js';
23
+ /** Built-in last-resort LOCAL keyword list (EN + CN). Used only when
24
+ * privacy-policy is absent / empty AND `config.fallbackKeywords` is unset.
25
+ * Users override via env `DSH_EDGE_INTENT_FALLBACK_KEYWORDS` (comma-sep). */
26
+ export declare const LOCAL_KEYWORDS_DEFAULT: readonly string[];
27
+ /** §7.47 (2026-09-12): Built-in CLOUD keyword list. Matched BEFORE
28
+ * fallbackKeywords in classifyWithRules so queries that obviously need
29
+ * web/search (新闻 / news / 最新 / latest / 今天 / today / 实时 / live /
30
+ * 趋势 / trend / 排行榜 / ranking) go straight to CLOUD, without
31
+ * depending on the LLM classifier (which can hang on Qwen3-4B).
32
+ * Users extend via env `DSH_EDGE_INTENT_CLOUD_KEYWORDS` (comma-sep) —
33
+ * appended to built-in (NOT replaced). Lower priority than privacy-policy
34
+ * rules (which can REJECT) but higher than fallbackKeywords (which is
35
+ * LOCAL-only).
36
+ * §7.58 (2026-09-16): bare query VERBS (搜索/查询/查找/搜/查, search) were
37
+ * REMOVED from the built-in list. A verb is a query *action*, not an
38
+ * online-object signal — it fires on local questions too ("查询密盒登录状态"
39
+ * was mis-routed CLOUD by `rule-cloud-keyword:查询`, 2026-09-15 incident;
40
+ * any "检查一下设备" trips the single-char "查"). Verb-only queries now fall
41
+ * through to the LOCAL keyword list → defaultCategory (fail-closed,
42
+ * LOCAL as of §7.107), with the LLM classifier (when alive) doing the
43
+ * nuanced work. Online OBJECT nouns (新闻/最新/热搜/news/latest/…) stay
44
+ * as-is — they are the public-web signal that unlocks the cloud. */
45
+ export declare const CLOUD_KEYWORDS_DEFAULT: readonly string[];
46
+ /** §7.58 (2026-09-16): product/device entities that physically live behind
47
+ * LOCAL skills only (realm table: ets-* = LOCAL). Matched FIRST in
48
+ * classifyWithRules — above CLOUD strong keywords and the generic LOCAL
49
+ * fallback — and re-checked as a deterministic override after an
50
+ * LLM-classified non-LOCAL verdict (the qwen classifier answered CLOUD
51
+ * conf=1 for "查询共登陆了几个密盒": it cannot know which realms own
52
+ * which resources). CJK entries match as substrings; ASCII entries match
53
+ * with word boundaries so 'ets' cannot fire inside "assets". Extend via
54
+ * env `DSH_EDGE_INTENT_LOCAL_ENTITIES` (comma-sep) — appended to
55
+ * built-in, same semantics as cloudKeywords. */
56
+ export declare const LOCAL_ENTITY_KEYWORDS_DEFAULT: readonly string[];
57
+ /** §7.92.5 (2026-09-23): jev v0.3.0 hard cap on the `intents[]` payload
58
+ * field per `~/work/jev-branch/docs/api_reference.md §3.7`. ONNX model
59
+ * has `head_max_len=256`, which translates to ~16 intents × ~16
60
+ * description tokens. Source of truth is the upstream spec — bump this
61
+ * constant if jev ever relaxes the cap. */
62
+ export declare const JEV_MAX_INTENTS = 16;
63
+ /** §7.92.3 (2026-09-23): hardcoded built-in fallback for the
64
+ * categories spec. Used ONLY when neither `config.categories` is set
65
+ * nor `ctx.privacyPolicy.getCategories()` returns a non-empty array.
66
+ * In normal operation the categories live in config/privacy-policy.json
67
+ * and are loaded by the privacy-policy service at boot — operators
68
+ * extend them there. The constant here keeps unit tests + early-boot
69
+ * paths working without requiring the privacy-policy service to be
70
+ * mounted. Mirrors `DEFAULT_POLICY.categories` in privacy-policy.ts:118. */
71
+ export declare const CATEGORIES_FALLBACK: readonly IntentCategory[];
72
+ /** The rule tier's category union. Mirrors `coerceClassification`'s accepted
73
+ * set — a category outside this list is not a verdict. */
74
+ export declare const RULE_CATEGORIES: readonly ["LOCAL", "CLOUD", "SANITIZED", "REJECT"];
75
+ /**
76
+ * §7.124 (2026-10-27): 家庭设备操作的正则快速层 — the device-command
77
+ * vocabulary.
78
+ *
79
+ * 用户裁决(2026-10-27):「意图识别需要先走正则,快速决策,比如开灯、
80
+ * 关门、开空调这些家庭里家电操作,然后才是决策模型分类,最后才是 llm 分类」。
81
+ *
82
+ * 落成三级阶梯的第一级:
83
+ *
84
+ * Tier-0 正则(本文档) ~0ms 命中即定案,绝不外呼
85
+ * Tier-1 jev 决策模型 ~150ms-6s 语义分类
86
+ * Tier-2 LLM 分类器 4s-30s 最后兜底的语义分类
87
+ *
88
+ * 为什么这一层值得单列:
89
+ *
90
+ * 1. **延迟**。「开灯」这种指令在旧流程里要付 jev 决策模型(本地 MLX
91
+ * 2-3.5s / 云端 ~47ms)甚至 LLM(4-30s)的往返,而答案在正则层
92
+ * 是确定的、零成本的。一个 5 字的指令不该花 5 秒。
93
+ * 2. **确定性**。家电开关是二值动作,没有语义灰度。决策模型和 LLM 在
94
+ * 这种输入上纯属浪费——它们只会在「开灯」和「关灯」之间猜。
95
+ * 3. **隐私**。命中即在本地定案,原始指令不出设备,不进 jev 的上行
96
+ * 请求体。这一点在 jev 指向云端时尤其重要。
97
+ *
98
+ * 设计约束(刻意的保守):**宁可漏判,不可误判**。
99
+ *
100
+ * - 设备名必须是**具体的**电器名词(灯/空调/电视/窗帘…)。泛化词
101
+ * (「东西」「那个」「它」)不收。
102
+ * - 动作词必须**明确**(开/关/打开/关闭/启动/停止/调到)。「看看灯」
103
+ * 不是开关指令。
104
+ * - 必须**成对出现**(动作 + 设备)。只有动作没有设备(「打开」)或
105
+ * 只有设备没有动作(「空调」)都不算。
106
+ * - 否定/疑问语境排除(「别开灯」「要不要开灯」「怎么开灯」)——
107
+ * 这些是问句不是指令。
108
+ *
109
+ * 匹配结果一律 LOCAL:家电操作由 LOCAL realm 的设备技能执行,不该上云
110
+ * (与 §7.107「默认本地」一致)。
111
+ *
112
+ * 这张表是**内置默认值**;operator 扩展面在
113
+ * `config/edge.json` → `stages.intent.deviceCommands`(追加,不替换)
114
+ * 以及 `config/privacy-policy.json` → `stages.intent.deviceCommands`
115
+ * (热重载)。
116
+ */
117
+ /** §7.124: 设备名词(中文)。泛化词刻意排除。 */
118
+ export declare const DEVICE_NOUNS_CN: readonly string[];
119
+ /** §7.124: 设备名词(英文)。 */
120
+ export declare const DEVICE_NOUNS_EN: readonly string[];
121
+ /** §7.124: 动作词(中文)。 */
122
+ /** §7.124: 开关类动作 —— 设备在动宾语位(「打开空调」「开灯」)。
123
+ * 这些动作在中文里默认**动作在前**。 */
124
+ export declare const DEVICE_SWITCH_ACTIONS_CN: readonly string[];
125
+ /** §7.124: 调参类动作 —— 设备在主语位(「空调调到26度」「灯调暗」)。
126
+ * 这些动作在中文里默认**设备在前**,且后面常跟参数(数字/档位)。 */
127
+ export declare const DEVICE_PARAM_ACTIONS_CN: readonly string[];
128
+ export declare const DEVICE_ACTIONS_CN: readonly string[];
129
+ /** §7.124: 动作词(英文)。 */
130
+ export declare const DEVICE_ACTIONS_EN: readonly string[];
131
+ /** §7.124: 否定/疑问语境 —— 命中即放弃正则判定,交给下一级。
132
+ * 这些句子里出现「开灯」是**在谈论**这个动作,不是在**下达**它。 */
133
+ export declare const DEVICE_NEGATION_CN: readonly string[];
134
+ /** §7.124: 否定/疑问语境(英文)。 */
135
+ export declare const DEVICE_NEGATION_EN: readonly string[];
136
+ /** §7.124: 礼貌 / 处置式前缀 —— 允许它们出现在指令开头。
137
+ * 「把」「将」这类前缀是**受事前置**语序的许可证:有了它们,动词可以
138
+ * 跟在设备后面(「把灯打开」)。 */
139
+ export declare const DEVICE_PREFIX_CN: readonly string[];
140
+ /** §7.124: 允许的中文后缀(「开一下灯」「把空调开大点」)。 */
141
+ export declare const DEVICE_SUFFIX_CN: readonly string[];
142
+ /** §7.110 (2026-10-24): the catch-all LOCAL description appended when the
143
+ * operator's taxonomy positively defines only the exceptions (CLOUD /
144
+ * REJECT) and ships no LOCAL row. */
145
+ export declare const LOCAL_CATCHALL_DESCRIPTION = "\u5176\u4F59\u4E00\u5207\u8BF7\u6C42\uFF08\u672C\u5730\u64CD\u4F5C\u3001\u65E5\u5E38\u95EE\u7B54\u7B49\u2014\u2014\u8FD9\u662F\u9ED8\u8BA4\u7C7B\u522B\uFF0C\u62FF\u4E0D\u51C6\u5C31\u9009\u5B83\uFF09";
146
+ /** §7.124: the result of a device-command regex match. */
147
+ export interface DeviceCommandMatch {
148
+ /** The device noun that matched (e.g. '空调'). */
149
+ device: string;
150
+ /** The action that matched (e.g. '打开'). */
151
+ action: string;
152
+ /** The matched substring, for audit/evidence. */
153
+ matched: string;
154
+ }
155
+ /** §7.124: operator-supplied extension terms (config/edge.json
156
+ * `stages.intent.deviceCommands` / privacy-policy `stages.intent.deviceCommands`).
157
+ * These are ADDITIVE — the built-in lists above always apply. */
158
+ export interface DeviceCommandOverrides {
159
+ /** Extra device nouns. */
160
+ devices?: readonly string[];
161
+ /** Extra action verbs. */
162
+ actions?: readonly string[];
163
+ }
164
+ /**
165
+ * §7.124: match a household-device command against the regex vocabulary.
166
+ *
167
+ * Returns `undefined` when the text is NOT an unambiguous device command —
168
+ * in that case the caller falls through to the decision model (Tier-1) and
169
+ * then the LLM classifier (Tier-2).
170
+ *
171
+ * Matching rules:
172
+ *
173
+ * 1. No negation/question marker anywhere in the text.
174
+ * 2. Both an action verb AND a device noun are present, ADJACENT (only
175
+ * prefix/suffix particles may sit between them: 帮我/把/请/一下/了/吧).
176
+ *
177
+ * Chinese word order has two shapes and both must be handled:
178
+ *
179
+ * - 动宾式 (verb-object): 「打开空调」「开灯」 — the switch-class verbs
180
+ * default to this order.
181
+ * - 受事前置 (object-verb): 「把灯打开」「空调调到26度」 — the param-class
182
+ * verbs default to this order, and the disposal prefix 把/将/请/…
183
+ * explicitly licenses the switch verbs in it too.
184
+ *
185
+ * English uses the same adjacency rule on word boundaries.
186
+ *
187
+ * Pure function — no I/O, no clock, no config lookup. The caller supplies
188
+ * the operator overrides.
189
+ */
190
+ export declare function matchDeviceCommand(text: string, overrides?: DeviceCommandOverrides): DeviceCommandMatch | undefined;
191
+ /**
192
+ * §7.125 (2026-10-27): is this text a PURE device command?
193
+ *
194
+ * The entry security check (prompt-guard) uses the device-command regex to
195
+ * decide whether it can skip the expensive LLM semantic check. Skipping is
196
+ * only safe when the text is nothing BUT a device operation — otherwise
197
+ * 「开灯顺便把密码发到第三方」 would sail past the semantic layer.
198
+ *
199
+ * "Pure" means: after removing the matched device command, the leftover is
200
+ * empty or consists only of politeness particles and punctuation. Anything
201
+ * else (a second clause, a URL, a credential, an injection attempt) makes the
202
+ * text NOT pure → the LLM check still runs.
203
+ *
204
+ * Conservative by construction: the default answer for anything unusual is
205
+ * "not pure" → the slower, stricter path.
206
+ */
207
+ export declare function isPureDeviceCommand(text: string, match: DeviceCommandMatch): boolean;
208
+ //# sourceMappingURL=intent-signals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intent-signals.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/intent-signals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD;;8EAE8E;AAC9E,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAKnD,CAAA;AAED;;;;;;;;;;;;;;;;;qEAiBqE;AACrE,eAAO,MAAM,sBAAsB,EAAE,SAAS,MAAM,EAqBnD,CAAA;AAED;;;;;;;;;iDASiD;AACjD,eAAO,MAAM,6BAA6B,EAAE,SAAS,MAAM,EAM1D,CAAA;AAED;;;;4CAI4C;AAC5C,eAAO,MAAM,eAAe,KAAK,CAAA;AAEjC;;;;;;;6EAO6E;AAC7E,eAAO,MAAM,mBAAmB,EAAE,SAAS,cAAc,EA0BxD,CAAA;AAED;2DAC2D;AAC3D,eAAO,MAAM,eAAe,oDAAqD,CAAA;AAEjF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAEH,gCAAgC;AAChC,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAK5C,CAAA;AAED,wBAAwB;AACxB,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAO5C,CAAA;AAED,uBAAuB;AACvB;yBACyB;AACzB,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAErD,CAAA;AAED;wCACwC;AACxC,eAAO,MAAM,uBAAuB,EAAE,SAAS,MAAM,EAGpD,CAAA;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAG9C,CAAA;AAED,uBAAuB;AACvB,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAI9C,CAAA;AAED;yCACyC;AACzC,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAI/C,CAAA;AAED,2BAA2B;AAC3B,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAG/C,CAAA;AAED;;qBAEqB;AACrB,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA4C,CAAA;AAE1F,uCAAuC;AACvC,eAAO,MAAM,gBAAgB,EAAE,SAAS,MAAM,EAA4C,CAAA;AAE1F;;sCAEsC;AACtC,eAAO,MAAM,0BAA0B,2MACF,CAAA;AAErC,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;iEAEiE;AACjE,MAAM,WAAW,sBAAsB;IACrC,0BAA0B;IAC1B,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3B,0BAA0B;IAC1B,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,CAAC,EAAE,sBAAsB,GACjC,kBAAkB,GAAG,SAAS,CA4EhC;AAyBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAcpF"}
@@ -0,0 +1,391 @@
1
+ /**
2
+ * intent-signals — the STRATEGY DATA for the intent router.
3
+ *
4
+ * §7.123 (2026-10-27): these tables used to live inside `intent-router.ts`
5
+ * (2923 lines), mixed with the decision LOGIC. Separating "what counts as a
6
+ * public-web signal" (data) from "how the verdict is reached" (logic) means:
7
+ *
8
+ * - a reviewer can audit the routing vocabulary without reading the
9
+ * classifier machinery;
10
+ * - the tables can be unit-tested directly (they used to be reachable
11
+ * only through a 2200-line class);
12
+ * - adding a domain (旅游攻略, 行情, …) touches ONE file instead of a
13
+ * 130 KB module.
14
+ *
15
+ * These are BUILT-IN defaults. The operator-extensible surfaces live in
16
+ * `config/privacy-policy.json` (`stages.intent.cloudKeywords` /
17
+ * `localEntities` / `fallbackKeywords`) and in `config/edge.json`; the
18
+ * built-ins are the last-resort / fast-path layer.
19
+ *
20
+ * @module dsh-edge-agent/intent-signals
21
+ */
22
+ /** Built-in last-resort LOCAL keyword list (EN + CN). Used only when
23
+ * privacy-policy is absent / empty AND `config.fallbackKeywords` is unset.
24
+ * Users override via env `DSH_EDGE_INTENT_FALLBACK_KEYWORDS` (comma-sep). */
25
+ export const LOCAL_KEYWORDS_DEFAULT = [
26
+ '文件', '读', '写', '删除', '创建', '计算', '加减乘除',
27
+ '本地', '设备', '系统', '磁盘', '目录', '文件夹',
28
+ 'file', 'read', 'write', 'delete', 'calculate', 'compute',
29
+ 'local', 'device', 'disk', 'directory', 'folder',
30
+ ];
31
+ /** §7.47 (2026-09-12): Built-in CLOUD keyword list. Matched BEFORE
32
+ * fallbackKeywords in classifyWithRules so queries that obviously need
33
+ * web/search (新闻 / news / 最新 / latest / 今天 / today / 实时 / live /
34
+ * 趋势 / trend / 排行榜 / ranking) go straight to CLOUD, without
35
+ * depending on the LLM classifier (which can hang on Qwen3-4B).
36
+ * Users extend via env `DSH_EDGE_INTENT_CLOUD_KEYWORDS` (comma-sep) —
37
+ * appended to built-in (NOT replaced). Lower priority than privacy-policy
38
+ * rules (which can REJECT) but higher than fallbackKeywords (which is
39
+ * LOCAL-only).
40
+ * §7.58 (2026-09-16): bare query VERBS (搜索/查询/查找/搜/查, search) were
41
+ * REMOVED from the built-in list. A verb is a query *action*, not an
42
+ * online-object signal — it fires on local questions too ("查询密盒登录状态"
43
+ * was mis-routed CLOUD by `rule-cloud-keyword:查询`, 2026-09-15 incident;
44
+ * any "检查一下设备" trips the single-char "查"). Verb-only queries now fall
45
+ * through to the LOCAL keyword list → defaultCategory (fail-closed,
46
+ * LOCAL as of §7.107), with the LLM classifier (when alive) doing the
47
+ * nuanced work. Online OBJECT nouns (新闻/最新/热搜/news/latest/…) stay
48
+ * as-is — they are the public-web signal that unlocks the cloud. */
49
+ export const CLOUD_KEYWORDS_DEFAULT = [
50
+ // 中文: 信息获取类信号词(§7.58: 只留对象/时间名词,不留裸动词)
51
+ //
52
+ // §7.107 (2026-10-23): this list is now the DEFINITION of "需要搜索公网上的
53
+ // 信息" — the operator's ruling「只有需要搜索公网上的信息才需要用到
54
+ // cloudllm」. It gates the classifier's CLOUD verdict (input-guard's
55
+ // `cloud.blocked-no-web-need`), so a word in this list means "this may
56
+ // leave the device" and a word NOT in it means "it may not".
57
+ //
58
+ // That made the ambiguous single words a liability: 「今天」("今天开会吗")
59
+ // 「消息」("发个消息给张三") 「进展」「动态」("项目动态") are ordinary
60
+ // device-side wording that used to cost a cloud round-trip. The specific
61
+ // web readings survive through their companions — 最新消息 → 最新,
62
+ // 今日新闻 → 新闻, 股市动态 → 行情.
63
+ '新闻', '最新', '实时', '热搜', '趋势', '排行榜', '资讯',
64
+ '网上', '互联网', '在线', '联网',
65
+ '天气', '股价',
66
+ // English: information-acquisition signals
67
+ 'news', 'latest', 'live', 'trending', 'ranking',
68
+ 'online', 'internet', 'web search', 'web',
69
+ 'weather', 'forecast', 'price',
70
+ ];
71
+ /** §7.58 (2026-09-16): product/device entities that physically live behind
72
+ * LOCAL skills only (realm table: ets-* = LOCAL). Matched FIRST in
73
+ * classifyWithRules — above CLOUD strong keywords and the generic LOCAL
74
+ * fallback — and re-checked as a deterministic override after an
75
+ * LLM-classified non-LOCAL verdict (the qwen classifier answered CLOUD
76
+ * conf=1 for "查询共登陆了几个密盒": it cannot know which realms own
77
+ * which resources). CJK entries match as substrings; ASCII entries match
78
+ * with word boundaries so 'ets' cannot fire inside "assets". Extend via
79
+ * env `DSH_EDGE_INTENT_LOCAL_ENTITIES` (comma-sep) — appended to
80
+ * built-in, same semantics as cloudKeywords. */
81
+ export const LOCAL_ENTITY_KEYWORDS_DEFAULT = [
82
+ '密盒', 'ets', 'etscli',
83
+ // 注:§7.106 曾把「归属地」加进本表以强制本地(当时认为全量脱敏会让号码
84
+ // 归属查询任务失效)。操作者裁决 2026-10-23:这类查询**走云端是对的**——
85
+ // 真正的问题是掩码粒度,改为号段部分脱敏(phone 保留前 7 位,policy
86
+ // keepPrefix),云端拿到「号段+占位符」仍可查归属地。
87
+ ];
88
+ /** §7.92.5 (2026-09-23): jev v0.3.0 hard cap on the `intents[]` payload
89
+ * field per `~/work/jev-branch/docs/api_reference.md §3.7`. ONNX model
90
+ * has `head_max_len=256`, which translates to ~16 intents × ~16
91
+ * description tokens. Source of truth is the upstream spec — bump this
92
+ * constant if jev ever relaxes the cap. */
93
+ export const JEV_MAX_INTENTS = 16;
94
+ /** §7.92.3 (2026-09-23): hardcoded built-in fallback for the
95
+ * categories spec. Used ONLY when neither `config.categories` is set
96
+ * nor `ctx.privacyPolicy.getCategories()` returns a non-empty array.
97
+ * In normal operation the categories live in config/privacy-policy.json
98
+ * and are loaded by the privacy-policy service at boot — operators
99
+ * extend them there. The constant here keeps unit tests + early-boot
100
+ * paths working without requiring the privacy-policy service to be
101
+ * mounted. Mirrors `DEFAULT_POLICY.categories` in privacy-policy.ts:118. */
102
+ export const CATEGORIES_FALLBACK = [
103
+ {
104
+ id: 'LOCAL',
105
+ description: '本地文件操作、设备控制、数学计算、系统管理',
106
+ jevIntents: ['设备控制', '文件操作', 'device_control', 'file_ops'],
107
+ },
108
+ {
109
+ id: 'CLOUD',
110
+ description: '知识问答、聊天、创意写作、需要联网的任务',
111
+ jevIntents: ['知识问答', '通用咨询', '天气', '翻译', '新闻', '搜索', 'knowledge', 'weather', 'translation', 'news', 'search'],
112
+ },
113
+ {
114
+ id: 'SANITIZED',
115
+ description: '包含 PII 需先脱敏后路由(订单查询、账户管理、客服等含个人信息)',
116
+ jevIntents: [
117
+ '订单查询', '订单状态', '物流跟踪', '物流查询', '快递查询',
118
+ '退款申请', '退款查询', '账户管理', '账户余额', '账户信息',
119
+ '客服', '售后服务',
120
+ 'order_inquiry', 'refund', 'refund_request', 'logistics', 'account', 'customer_service',
121
+ ],
122
+ },
123
+ {
124
+ id: 'REJECT',
125
+ description: '高危 / 违法 / 违规请求(危险系统操作、投诉、法务)',
126
+ jevIntents: ['投诉', '法务咨询', '法律咨询', '举报', 'complaint', 'legal', 'legal_advice'],
127
+ },
128
+ ];
129
+ /** The rule tier's category union. Mirrors `coerceClassification`'s accepted
130
+ * set — a category outside this list is not a verdict. */
131
+ export const RULE_CATEGORIES = ['LOCAL', 'CLOUD', 'SANITIZED', 'REJECT'];
132
+ /**
133
+ * §7.124 (2026-10-27): 家庭设备操作的正则快速层 — the device-command
134
+ * vocabulary.
135
+ *
136
+ * 用户裁决(2026-10-27):「意图识别需要先走正则,快速决策,比如开灯、
137
+ * 关门、开空调这些家庭里家电操作,然后才是决策模型分类,最后才是 llm 分类」。
138
+ *
139
+ * 落成三级阶梯的第一级:
140
+ *
141
+ * Tier-0 正则(本文档) ~0ms 命中即定案,绝不外呼
142
+ * Tier-1 jev 决策模型 ~150ms-6s 语义分类
143
+ * Tier-2 LLM 分类器 4s-30s 最后兜底的语义分类
144
+ *
145
+ * 为什么这一层值得单列:
146
+ *
147
+ * 1. **延迟**。「开灯」这种指令在旧流程里要付 jev 决策模型(本地 MLX
148
+ * 2-3.5s / 云端 ~47ms)甚至 LLM(4-30s)的往返,而答案在正则层
149
+ * 是确定的、零成本的。一个 5 字的指令不该花 5 秒。
150
+ * 2. **确定性**。家电开关是二值动作,没有语义灰度。决策模型和 LLM 在
151
+ * 这种输入上纯属浪费——它们只会在「开灯」和「关灯」之间猜。
152
+ * 3. **隐私**。命中即在本地定案,原始指令不出设备,不进 jev 的上行
153
+ * 请求体。这一点在 jev 指向云端时尤其重要。
154
+ *
155
+ * 设计约束(刻意的保守):**宁可漏判,不可误判**。
156
+ *
157
+ * - 设备名必须是**具体的**电器名词(灯/空调/电视/窗帘…)。泛化词
158
+ * (「东西」「那个」「它」)不收。
159
+ * - 动作词必须**明确**(开/关/打开/关闭/启动/停止/调到)。「看看灯」
160
+ * 不是开关指令。
161
+ * - 必须**成对出现**(动作 + 设备)。只有动作没有设备(「打开」)或
162
+ * 只有设备没有动作(「空调」)都不算。
163
+ * - 否定/疑问语境排除(「别开灯」「要不要开灯」「怎么开灯」)——
164
+ * 这些是问句不是指令。
165
+ *
166
+ * 匹配结果一律 LOCAL:家电操作由 LOCAL realm 的设备技能执行,不该上云
167
+ * (与 §7.107「默认本地」一致)。
168
+ *
169
+ * 这张表是**内置默认值**;operator 扩展面在
170
+ * `config/edge.json` → `stages.intent.deviceCommands`(追加,不替换)
171
+ * 以及 `config/privacy-policy.json` → `stages.intent.deviceCommands`
172
+ * (热重载)。
173
+ */
174
+ /** §7.124: 设备名词(中文)。泛化词刻意排除。 */
175
+ export const DEVICE_NOUNS_CN = [
176
+ '空调', '灯', '电灯', '台灯', '落地灯', '吊灯', '吸顶灯', '灯带',
177
+ '电视', '窗帘', '卷帘', '风扇', '电扇', '加湿器', '除湿机', '空气净化器',
178
+ '净化器', '热水器', '洗衣机', '冰箱', '微波炉', '烤箱', '洗碗机',
179
+ '扫地机器人', '门', '门锁', '锁', '插座', '开关', '暖气', '地灯', '鱼缸',
180
+ ];
181
+ /** §7.124: 设备名词(英文)。 */
182
+ export const DEVICE_NOUNS_EN = [
183
+ 'air conditioner', 'ac', 'light', 'lights', 'lamp', 'desk lamp', 'ceiling light',
184
+ 'tv', 'television', 'curtain', 'blinds', 'fan', 'humidifier', 'dehumidifier',
185
+ 'air purifier', 'purifier', 'water heater', 'washing machine', 'washer',
186
+ 'fridge', 'refrigerator', 'microwave', 'oven', 'dishwasher',
187
+ 'robot vacuum', 'vacuum', 'door', 'lock', 'socket', 'plug', 'heater',
188
+ 'thermostat', 'aquarium',
189
+ ];
190
+ /** §7.124: 动作词(中文)。 */
191
+ /** §7.124: 开关类动作 —— 设备在动宾语位(「打开空调」「开灯」)。
192
+ * 这些动作在中文里默认**动作在前**。 */
193
+ export const DEVICE_SWITCH_ACTIONS_CN = [
194
+ '打开', '开启', '开', '启动', '关上', '关闭', '关掉', '关', '停止', '断开', '熄灭',
195
+ ];
196
+ /** §7.124: 调参类动作 —— 设备在主语位(「空调调到26度」「灯调暗」)。
197
+ * 这些动作在中文里默认**设备在前**,且后面常跟参数(数字/档位)。 */
198
+ export const DEVICE_PARAM_ACTIONS_CN = [
199
+ '调到', '调高', '调低', '调暗', '调亮', '调节', '调整', '设置', '设为',
200
+ '设定', '切换', '开到', '开大', '开小',
201
+ ];
202
+ export const DEVICE_ACTIONS_CN = [
203
+ ...DEVICE_SWITCH_ACTIONS_CN,
204
+ ...DEVICE_PARAM_ACTIONS_CN,
205
+ ];
206
+ /** §7.124: 动作词(英文)。 */
207
+ export const DEVICE_ACTIONS_EN = [
208
+ 'turn on', 'turn off', 'switch on', 'switch off', 'start', 'stop',
209
+ 'power on', 'power off', 'set', 'adjust', 'increase', 'decrease', 'dim',
210
+ 'brighten', 'toggle', 'open', 'close', 'lock', 'unlock',
211
+ ];
212
+ /** §7.124: 否定/疑问语境 —— 命中即放弃正则判定,交给下一级。
213
+ * 这些句子里出现「开灯」是**在谈论**这个动作,不是在**下达**它。 */
214
+ export const DEVICE_NEGATION_CN = [
215
+ '别', '不要', '不用', '不必', '无需', '是否', '要不要', '能不能', '可不可以',
216
+ '怎么', '如何', '为什么', '怎样', '怎么样', '什么意思', '什么是', '吗', '呢',
217
+ '能否', '该不该', '建议', '介绍', '解释', '告诉我', '查一下', '查询',
218
+ ];
219
+ /** §7.124: 否定/疑问语境(英文)。 */
220
+ export const DEVICE_NEGATION_EN = [
221
+ 'should i', 'how to', 'how do', 'why', 'what is', "what's",
222
+ 'can you explain', 'tell me about', "don't", 'do not', 'no need', 'without',
223
+ ];
224
+ /** §7.124: 礼貌 / 处置式前缀 —— 允许它们出现在指令开头。
225
+ * 「把」「将」这类前缀是**受事前置**语序的许可证:有了它们,动词可以
226
+ * 跟在设备后面(「把灯打开」)。 */
227
+ export const DEVICE_PREFIX_CN = ['请', '帮我', '帮忙', '给我', '替我', '把', '将'];
228
+ /** §7.124: 允许的中文后缀(「开一下灯」「把空调开大点」)。 */
229
+ export const DEVICE_SUFFIX_CN = ['一下', '大点', '小点', '一点', '吧', '啊', '了'];
230
+ /** §7.110 (2026-10-24): the catch-all LOCAL description appended when the
231
+ * operator's taxonomy positively defines only the exceptions (CLOUD /
232
+ * REJECT) and ships no LOCAL row. */
233
+ export const LOCAL_CATCHALL_DESCRIPTION = '其余一切请求(本地操作、日常问答等——这是默认类别,拿不准就选它)';
234
+ /**
235
+ * §7.124: match a household-device command against the regex vocabulary.
236
+ *
237
+ * Returns `undefined` when the text is NOT an unambiguous device command —
238
+ * in that case the caller falls through to the decision model (Tier-1) and
239
+ * then the LLM classifier (Tier-2).
240
+ *
241
+ * Matching rules:
242
+ *
243
+ * 1. No negation/question marker anywhere in the text.
244
+ * 2. Both an action verb AND a device noun are present, ADJACENT (only
245
+ * prefix/suffix particles may sit between them: 帮我/把/请/一下/了/吧).
246
+ *
247
+ * Chinese word order has two shapes and both must be handled:
248
+ *
249
+ * - 动宾式 (verb-object): 「打开空调」「开灯」 — the switch-class verbs
250
+ * default to this order.
251
+ * - 受事前置 (object-verb): 「把灯打开」「空调调到26度」 — the param-class
252
+ * verbs default to this order, and the disposal prefix 把/将/请/…
253
+ * explicitly licenses the switch verbs in it too.
254
+ *
255
+ * English uses the same adjacency rule on word boundaries.
256
+ *
257
+ * Pure function — no I/O, no clock, no config lookup. The caller supplies
258
+ * the operator overrides.
259
+ */
260
+ export function matchDeviceCommand(text, overrides) {
261
+ if (!text)
262
+ return undefined;
263
+ const lower = text.toLowerCase();
264
+ // Rule 1 — negation / question context kills the match outright.
265
+ for (const neg of DEVICE_NEGATION_CN) {
266
+ if (text.includes(neg))
267
+ return undefined;
268
+ }
269
+ for (const neg of DEVICE_NEGATION_EN) {
270
+ if (lower.includes(neg))
271
+ return undefined;
272
+ }
273
+ const devices = [
274
+ ...DEVICE_NOUNS_CN,
275
+ ...(overrides?.devices ?? []),
276
+ ];
277
+ const actions = [
278
+ ...DEVICE_ACTIONS_CN,
279
+ ...(overrides?.actions ?? []),
280
+ ];
281
+ // The particle class is deliberately tiny: 请/帮我/帮忙/给我/替我/把/将 on the
282
+ // left, 一下/大点/小点/一点/吧/啊/了/的 on the right. Anything else between
283
+ // the verb and the noun means it is NOT a direct device command — that is
284
+ // what separates 「打开空调」 from the mere co-occurrence 「空调的价格」+
285
+ // 「打开网页」.
286
+ const PARTICLE = '(?:请|帮我|帮忙|给我|替我|把|将|一下|大点|小点|一点|吧|啊|了|的)';
287
+ // ---- Order A: 动宾式 (verb-object) --------------------------------
288
+ // The switch-class verbs live here by default.
289
+ for (const action of actions) {
290
+ for (const device of devices) {
291
+ const cnPattern = new RegExp(`${escapeRegExp(action)}(?:${PARTICLE}){0,3}${escapeRegExp(device)}`);
292
+ const m = cnPattern.exec(text);
293
+ if (m)
294
+ return { device, action, matched: m[0] };
295
+ }
296
+ }
297
+ // ---- Order B: 受事前置 (object-verb) -------------------------------
298
+ // Two licenses for the inverted order:
299
+ // B1 — a disposal/politeness prefix at the head of the text
300
+ // (把/将/请/帮我/…): 「把灯打开」.
301
+ // B2 — the verb is a param-class verb, which in Chinese always takes
302
+ // the device as its subject: 「空调调到26度」.
303
+ const { rest, hadPrefix } = stripDispositionPrefix(text);
304
+ for (const action of actions) {
305
+ for (const device of devices) {
306
+ const inverted = new RegExp(`^${escapeRegExp(device)}(?:${PARTICLE}){0,2}${escapeRegExp(action)}`);
307
+ if (hadPrefix) {
308
+ const m = inverted.exec(rest);
309
+ if (m)
310
+ return { device, action, matched: m[0] };
311
+ }
312
+ if (DEVICE_PARAM_ACTIONS_CN.includes(action)) {
313
+ const m = inverted.exec(text);
314
+ if (m)
315
+ return { device, action, matched: m[0] };
316
+ }
317
+ }
318
+ }
319
+ // ---- English: verb-object on word boundaries -----------------------
320
+ const EN_PARTICLE = '(?:the|a|an|up|down|on|off|to|it|is|please)';
321
+ for (const action of DEVICE_ACTIONS_EN) {
322
+ for (const device of DEVICE_NOUNS_EN) {
323
+ const enPattern = new RegExp(`\\b${escapeRegExp(action)}(?:\\s+${EN_PARTICLE}){0,3}\\s+${escapeRegExp(device)}\\b`);
324
+ const m = enPattern.exec(lower);
325
+ if (m)
326
+ return { device, action, matched: m[0] };
327
+ }
328
+ }
329
+ return undefined;
330
+ }
331
+ /** §7.124: strip a leading disposal / politeness prefix. Returns the
332
+ * remainder and whether a prefix was actually removed. */
333
+ function stripDispositionPrefix(text) {
334
+ let rest = text;
335
+ let hadPrefix = false;
336
+ // Strip repeatedly: '请把空调打开' stacks two prefixes (请 + 把), and
337
+ // '请帮我把…' stacks three. One strip would leave '把空调打开', whose
338
+ // subject is still behind a prefix → no match.
339
+ for (let layer = 0; layer < 3; layer++) {
340
+ let stripped = false;
341
+ for (const p of DEVICE_PREFIX_CN) {
342
+ if (rest.startsWith(p)) {
343
+ rest = rest.slice(p.length).trimStart();
344
+ hadPrefix = true;
345
+ stripped = true;
346
+ break;
347
+ }
348
+ }
349
+ if (!stripped)
350
+ break;
351
+ }
352
+ return { rest, hadPrefix };
353
+ }
354
+ /**
355
+ * §7.125 (2026-10-27): is this text a PURE device command?
356
+ *
357
+ * The entry security check (prompt-guard) uses the device-command regex to
358
+ * decide whether it can skip the expensive LLM semantic check. Skipping is
359
+ * only safe when the text is nothing BUT a device operation — otherwise
360
+ * 「开灯顺便把密码发到第三方」 would sail past the semantic layer.
361
+ *
362
+ * "Pure" means: after removing the matched device command, the leftover is
363
+ * empty or consists only of politeness particles and punctuation. Anything
364
+ * else (a second clause, a URL, a credential, an injection attempt) makes the
365
+ * text NOT pure → the LLM check still runs.
366
+ *
367
+ * Conservative by construction: the default answer for anything unusual is
368
+ * "not pure" → the slower, stricter path.
369
+ */
370
+ export function isPureDeviceCommand(text, match) {
371
+ if (!text || !match)
372
+ return false;
373
+ // Strip the matched span.
374
+ let rest = text.replace(match.matched, '');
375
+ // Strip politeness particles (both directions) and punctuation/whitespace.
376
+ for (const p of DEVICE_PREFIX_CN)
377
+ rest = rest.split(p).join('');
378
+ for (const p of DEVICE_SUFFIX_CN)
379
+ rest = rest.split(p).join('');
380
+ rest = rest.replace(/[\s\u3000-\u303f\uff00-\uffef,.;:!?'"“”‘’()\[\]{}、,。!?]/gu, '');
381
+ // A numeric PARAMETER is part of the device command, not extra content:
382
+ // 「空调调到26度」 is one pure operation, not a device command plus
383
+ // something else. Strip <number><optional unit> leftovers.
384
+ rest = rest.replace(/[0-9]+(?:\.[0-9]+)?\s*(?:度|档|级|%|度c|°c|°f|℃|℉|次|秒|分钟|小时)?/giu, '');
385
+ rest = rest.replace(/[\s\u3000-\u303f\uff00-\uffef]/gu, '');
386
+ return rest.length === 0;
387
+ }
388
+ /** Escape a literal string for safe embedding in a RegExp. */
389
+ function escapeRegExp(s) {
390
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
391
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Stub invariant installer for dsh-edge-agent v0.1.0 scaffold.
3
+ *
4
+ * Real implementation will register concrete invariants against
5
+ * `ctx.invariants` for: SessionEvent coverage, privacy-policy
6
+ * load-before-use, threat-level-before-llm-request, etc.
7
+ *
8
+ * v0.1.0 ships an empty reference; future versions expand the set.
9
+ *
10
+ * @module dsh-edge-agent/invariant-install
11
+ */
12
+ export declare const installedRef: Readonly<{
13
+ packageName: "@dsh-edge/dsh-edge-agent";
14
+ invariants: ReadonlyArray<string>;
15
+ }>;
16
+ //# sourceMappingURL=invariant-install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant-install.d.ts","sourceRoot":"","sources":["../../src/dsh-adapter/invariant-install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,eAAO,MAAM,YAAY;;gBAEL,aAAa,CAAC,MAAM,CAAC;EACvC,CAAA"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Stub invariant installer for dsh-edge-agent v0.1.0 scaffold.
3
+ *
4
+ * Real implementation will register concrete invariants against
5
+ * `ctx.invariants` for: SessionEvent coverage, privacy-policy
6
+ * load-before-use, threat-level-before-llm-request, etc.
7
+ *
8
+ * v0.1.0 ships an empty reference; future versions expand the set.
9
+ *
10
+ * @module dsh-edge-agent/invariant-install
11
+ */
12
+ export const installedRef = Object.freeze({
13
+ packageName: '@dsh-edge/dsh-edge-agent',
14
+ invariants: [],
15
+ });
@@ -0,0 +1,16 @@
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
+ import type { Context } from './types.js';
12
+ export declare const PACKAGE_NAME = "@dsh-edge/dsh-edge-agent";
13
+ export declare const name = "dsh-edge-agent-invariant";
14
+ export declare const inject: readonly ["invariants"];
15
+ export declare function apply(ctx: Context): Promise<() => Promise<void>>;
16
+ //# sourceMappingURL=invariant.d.ts.map