@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.
- package/CHANGELOG.md +158 -0
- package/LICENSE +21 -0
- package/README.md +181 -2
- package/config/edge.example.json +179 -0
- package/config/privacy-policy.json +202 -0
- package/cordis.desktop-safe.patch.yml +129 -0
- package/cordis.patch.yml +382 -0
- package/cordis.yml +374 -0
- package/lib/attestation.d.ts +2 -0
- package/lib/attestation.d.ts.map +1 -0
- package/lib/attestation.js +2 -0
- package/lib/audit-log.d.ts +2 -0
- package/lib/audit-log.d.ts.map +1 -0
- package/lib/audit-log.js +2 -0
- package/lib/client.js +87 -0
- package/lib/cloud-llm-adapter.d.ts +2 -0
- package/lib/cloud-llm-adapter.d.ts.map +1 -0
- package/lib/cloud-llm-adapter.js +2 -0
- package/lib/dsh-adapter/attestation.d.ts +42 -0
- package/lib/dsh-adapter/attestation.d.ts.map +1 -0
- package/lib/dsh-adapter/attestation.js +40 -0
- package/lib/dsh-adapter/audit-log.d.ts +139 -0
- package/lib/dsh-adapter/audit-log.d.ts.map +1 -0
- package/lib/dsh-adapter/audit-log.js +376 -0
- package/lib/dsh-adapter/cloud-egress.d.ts +181 -0
- package/lib/dsh-adapter/cloud-egress.d.ts.map +1 -0
- package/lib/dsh-adapter/cloud-egress.js +257 -0
- package/lib/dsh-adapter/cloud-llm-adapter.d.ts +416 -0
- package/lib/dsh-adapter/cloud-llm-adapter.d.ts.map +1 -0
- package/lib/dsh-adapter/cloud-llm-adapter.js +1472 -0
- package/lib/dsh-adapter/contract.d.ts +80 -0
- package/lib/dsh-adapter/contract.d.ts.map +1 -0
- package/lib/dsh-adapter/contract.js +194 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.d.ts +214 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.d.ts.map +1 -0
- package/lib/dsh-adapter/desktop-cookie-janitor.js +409 -0
- package/lib/dsh-adapter/dsh-edge-agent.d.ts +48 -0
- package/lib/dsh-adapter/dsh-edge-agent.d.ts.map +1 -0
- package/lib/dsh-adapter/dsh-edge-agent.js +64 -0
- package/lib/dsh-adapter/edge-config.d.ts +484 -0
- package/lib/dsh-adapter/edge-config.d.ts.map +1 -0
- package/lib/dsh-adapter/edge-config.js +898 -0
- package/lib/dsh-adapter/edge-context-view.d.ts +184 -0
- package/lib/dsh-adapter/edge-context-view.d.ts.map +1 -0
- package/lib/dsh-adapter/edge-context-view.js +483 -0
- package/lib/dsh-adapter/effective-model.d.ts +85 -0
- package/lib/dsh-adapter/effective-model.d.ts.map +1 -0
- package/lib/dsh-adapter/effective-model.js +171 -0
- package/lib/dsh-adapter/etscli-shell-guard.d.ts +244 -0
- package/lib/dsh-adapter/etscli-shell-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/etscli-shell-guard.js +420 -0
- package/lib/dsh-adapter/etscli-tools.d.ts +112 -0
- package/lib/dsh-adapter/etscli-tools.d.ts.map +1 -0
- package/lib/dsh-adapter/etscli-tools.js +393 -0
- package/lib/dsh-adapter/file-logger.d.ts +294 -0
- package/lib/dsh-adapter/file-logger.d.ts.map +1 -0
- package/lib/dsh-adapter/file-logger.js +564 -0
- package/lib/dsh-adapter/function-call-external.d.ts +357 -0
- package/lib/dsh-adapter/function-call-external.d.ts.map +1 -0
- package/lib/dsh-adapter/function-call-external.js +1005 -0
- package/lib/dsh-adapter/host-egress-guard.d.ts +161 -0
- package/lib/dsh-adapter/host-egress-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/host-egress-guard.js +213 -0
- package/lib/dsh-adapter/host-llm-trace.d.ts +198 -0
- package/lib/dsh-adapter/host-llm-trace.d.ts.map +1 -0
- package/lib/dsh-adapter/host-llm-trace.js +344 -0
- package/lib/dsh-adapter/http-api-routes.d.ts +61 -0
- package/lib/dsh-adapter/http-api-routes.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api-routes.js +79 -0
- package/lib/dsh-adapter/http-api-sessions.d.ts +105 -0
- package/lib/dsh-adapter/http-api-sessions.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api-sessions.js +173 -0
- package/lib/dsh-adapter/http-api.d.ts +221 -0
- package/lib/dsh-adapter/http-api.d.ts.map +1 -0
- package/lib/dsh-adapter/http-api.js +1811 -0
- package/lib/dsh-adapter/index.d.ts +62 -0
- package/lib/dsh-adapter/index.d.ts.map +1 -0
- package/lib/dsh-adapter/index.js +97 -0
- package/lib/dsh-adapter/input-guard-messages.d.ts +56 -0
- package/lib/dsh-adapter/input-guard-messages.d.ts.map +1 -0
- package/lib/dsh-adapter/input-guard-messages.js +136 -0
- package/lib/dsh-adapter/input-guard.d.ts +508 -0
- package/lib/dsh-adapter/input-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/input-guard.js +2174 -0
- package/lib/dsh-adapter/intent-prompts.d.ts +77 -0
- package/lib/dsh-adapter/intent-prompts.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-prompts.js +97 -0
- package/lib/dsh-adapter/intent-router.d.ts +710 -0
- package/lib/dsh-adapter/intent-router.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-router.js +2802 -0
- package/lib/dsh-adapter/intent-signals.d.ts +208 -0
- package/lib/dsh-adapter/intent-signals.d.ts.map +1 -0
- package/lib/dsh-adapter/intent-signals.js +391 -0
- package/lib/dsh-adapter/invariant-install.d.ts +16 -0
- package/lib/dsh-adapter/invariant-install.d.ts.map +1 -0
- package/lib/dsh-adapter/invariant-install.js +15 -0
- package/lib/dsh-adapter/invariant.d.ts +16 -0
- package/lib/dsh-adapter/invariant.d.ts.map +1 -0
- package/lib/dsh-adapter/invariant.js +22 -0
- package/lib/dsh-adapter/llm-adapters.d.ts +19 -0
- package/lib/dsh-adapter/llm-adapters.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-adapters.js +16 -0
- package/lib/dsh-adapter/llm-internal-caller.d.ts +92 -0
- package/lib/dsh-adapter/llm-internal-caller.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-internal-caller.js +100 -0
- package/lib/dsh-adapter/llm-max-turns.d.ts +55 -0
- package/lib/dsh-adapter/llm-max-turns.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-max-turns.js +127 -0
- package/lib/dsh-adapter/llm-trace-store.d.ts +131 -0
- package/lib/dsh-adapter/llm-trace-store.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-trace-store.js +205 -0
- package/lib/dsh-adapter/llm-trace.d.ts +450 -0
- package/lib/dsh-adapter/llm-trace.d.ts.map +1 -0
- package/lib/dsh-adapter/llm-trace.js +463 -0
- package/lib/dsh-adapter/local-llm-adapter.d.ts +141 -0
- package/lib/dsh-adapter/local-llm-adapter.d.ts.map +1 -0
- package/lib/dsh-adapter/local-llm-adapter.js +787 -0
- package/lib/dsh-adapter/local-storage.d.ts +96 -0
- package/lib/dsh-adapter/local-storage.d.ts.map +1 -0
- package/lib/dsh-adapter/local-storage.js +198 -0
- package/lib/dsh-adapter/model-router.d.ts +79 -0
- package/lib/dsh-adapter/model-router.d.ts.map +1 -0
- package/lib/dsh-adapter/model-router.js +107 -0
- package/lib/dsh-adapter/ohttp-gateway.d.ts +49 -0
- package/lib/dsh-adapter/ohttp-gateway.d.ts.map +1 -0
- package/lib/dsh-adapter/ohttp-gateway.js +32 -0
- package/lib/dsh-adapter/openai-sse.d.ts +85 -0
- package/lib/dsh-adapter/openai-sse.d.ts.map +1 -0
- package/lib/dsh-adapter/openai-sse.js +462 -0
- package/lib/dsh-adapter/pii-utils.d.ts +96 -0
- package/lib/dsh-adapter/pii-utils.d.ts.map +1 -0
- package/lib/dsh-adapter/pii-utils.js +324 -0
- package/lib/dsh-adapter/plugin-host.d.ts +38 -0
- package/lib/dsh-adapter/plugin-host.d.ts.map +1 -0
- package/lib/dsh-adapter/plugin-host.js +38 -0
- package/lib/dsh-adapter/plugin.d.ts +46 -0
- package/lib/dsh-adapter/plugin.d.ts.map +1 -0
- package/lib/dsh-adapter/plugin.js +100 -0
- package/lib/dsh-adapter/privacy-policy.d.ts +237 -0
- package/lib/dsh-adapter/privacy-policy.d.ts.map +1 -0
- package/lib/dsh-adapter/privacy-policy.js +391 -0
- package/lib/dsh-adapter/prompt-guard.d.ts +152 -0
- package/lib/dsh-adapter/prompt-guard.d.ts.map +1 -0
- package/lib/dsh-adapter/prompt-guard.js +872 -0
- package/lib/dsh-adapter/realm-skill-bridge.d.ts +365 -0
- package/lib/dsh-adapter/realm-skill-bridge.d.ts.map +1 -0
- package/lib/dsh-adapter/realm-skill-bridge.js +1180 -0
- package/lib/dsh-adapter/sanitizer-pipeline.d.ts +140 -0
- package/lib/dsh-adapter/sanitizer-pipeline.d.ts.map +1 -0
- package/lib/dsh-adapter/sanitizer-pipeline.js +431 -0
- package/lib/dsh-adapter/secure-bridge.d.ts +47 -0
- package/lib/dsh-adapter/secure-bridge.d.ts.map +1 -0
- package/lib/dsh-adapter/secure-bridge.js +35 -0
- package/lib/dsh-adapter/session-attribution.d.ts +70 -0
- package/lib/dsh-adapter/session-attribution.d.ts.map +1 -0
- package/lib/dsh-adapter/session-attribution.js +76 -0
- package/lib/dsh-adapter/sessions.d.ts +86 -0
- package/lib/dsh-adapter/sessions.d.ts.map +1 -0
- package/lib/dsh-adapter/sessions.js +179 -0
- package/lib/dsh-adapter/tcm.d.ts +55 -0
- package/lib/dsh-adapter/tcm.d.ts.map +1 -0
- package/lib/dsh-adapter/tcm.js +94 -0
- package/lib/dsh-adapter/td-bus.d.ts +87 -0
- package/lib/dsh-adapter/td-bus.d.ts.map +1 -0
- package/lib/dsh-adapter/td-bus.js +196 -0
- package/lib/dsh-adapter/tool-runner.d.ts +185 -0
- package/lib/dsh-adapter/tool-runner.d.ts.map +1 -0
- package/lib/dsh-adapter/tool-runner.js +271 -0
- package/lib/dsh-adapter/tools.d.ts +118 -0
- package/lib/dsh-adapter/tools.d.ts.map +1 -0
- package/lib/dsh-adapter/tools.js +292 -0
- package/lib/dsh-adapter/types.d.ts +23 -0
- package/lib/dsh-adapter/types.d.ts.map +1 -0
- package/lib/dsh-adapter/types.js +16 -0
- package/lib/dsh-adapter/voice-input.d.ts +30 -0
- package/lib/dsh-adapter/voice-input.d.ts.map +1 -0
- package/lib/dsh-adapter/voice-input.js +34 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.d.ts +152 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.d.ts.map +1 -0
- package/lib/dsh-adapter/web-search-tavily-bocha.js +289 -0
- package/lib/http-api.d.ts +2 -0
- package/lib/http-api.d.ts.map +1 -0
- package/lib/http-api.js +2 -0
- package/lib/index.d.ts +48 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +49 -0
- package/lib/intent-router.d.ts +2 -0
- package/lib/intent-router.d.ts.map +1 -0
- package/lib/intent-router.js +2 -0
- package/lib/llm-adapters.d.ts +2 -0
- package/lib/llm-adapters.d.ts.map +1 -0
- package/lib/llm-adapters.js +2 -0
- package/lib/local-llm-adapter.d.ts +2 -0
- package/lib/local-llm-adapter.d.ts.map +1 -0
- package/lib/local-llm-adapter.js +2 -0
- package/lib/local-storage.d.ts +2 -0
- package/lib/local-storage.d.ts.map +1 -0
- package/lib/local-storage.js +2 -0
- package/lib/model-router.d.ts +2 -0
- package/lib/model-router.d.ts.map +1 -0
- package/lib/model-router.js +2 -0
- package/lib/ohttp-gateway.d.ts +2 -0
- package/lib/ohttp-gateway.d.ts.map +1 -0
- package/lib/ohttp-gateway.js +2 -0
- package/lib/plugin-host.d.ts +2 -0
- package/lib/plugin-host.d.ts.map +1 -0
- package/lib/plugin-host.js +2 -0
- package/lib/privacy-policy.d.ts +2 -0
- package/lib/privacy-policy.d.ts.map +1 -0
- package/lib/privacy-policy.js +2 -0
- package/lib/prompt-guard.d.ts +2 -0
- package/lib/prompt-guard.d.ts.map +1 -0
- package/lib/prompt-guard.js +2 -0
- package/lib/sanitizer-pipeline.d.ts +2 -0
- package/lib/sanitizer-pipeline.d.ts.map +1 -0
- package/lib/sanitizer-pipeline.js +2 -0
- package/lib/secure-bridge.d.ts +2 -0
- package/lib/secure-bridge.d.ts.map +1 -0
- package/lib/secure-bridge.js +2 -0
- package/lib/sessions.d.ts +2 -0
- package/lib/sessions.d.ts.map +1 -0
- package/lib/sessions.js +2 -0
- package/lib/tcm.d.ts +2 -0
- package/lib/tcm.d.ts.map +1 -0
- package/lib/tcm.js +2 -0
- package/lib/td-bus.d.ts +2 -0
- package/lib/td-bus.d.ts.map +1 -0
- package/lib/td-bus.js +2 -0
- package/lib/tools.d.ts +2 -0
- package/lib/tools.d.ts.map +1 -0
- package/lib/tools.js +2 -0
- package/lib/voice-input.d.ts +2 -0
- package/lib/voice-input.d.ts.map +1 -0
- package/lib/voice-input.js +2 -0
- package/package.json +253 -3
- package/presets/agent-preset-edge.json +50 -0
- package/presets/edge.desktop-safe.patch.yml +503 -0
- 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
|