dsh-perm-gate 5.1.0 → 5.1.2-compat.15
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 +96 -0
- package/dsh.plugin.json +1 -1
- package/lib/ask-context.d.ts +207 -0
- package/lib/ask-context.js +504 -0
- package/lib/bridge.d.ts +41 -0
- package/lib/bridge.js +128 -0
- package/lib/classifier.d.ts +8 -14
- package/lib/classifier.js +24 -2
- package/lib/cli.js +0 -0
- package/lib/client.js +257 -18
- package/lib/client.js.map +1 -1
- package/lib/command-normalize.d.ts +80 -0
- package/lib/command-normalize.js +246 -0
- package/lib/debug-generation.d.ts +21 -0
- package/lib/debug-generation.js +44 -0
- package/lib/engine.js +78 -58
- package/lib/evaluate.d.ts +5 -0
- package/lib/evaluate.js +17 -11
- package/lib/events.d.ts +26 -0
- package/lib/events.js +7 -0
- package/lib/grader-rules.d.ts +32 -0
- package/lib/grader-rules.js +48 -0
- package/lib/index.js +109 -7
- package/lib/irreversible.d.ts +54 -0
- package/lib/irreversible.js +376 -0
- package/lib/parsers/shell-cmds.d.ts +9 -0
- package/lib/parsers/shell-cmds.js +37 -3
- package/lib/redact.d.ts +55 -0
- package/lib/redact.js +183 -0
- package/lib/risk.d.ts +32 -5
- package/lib/risk.js +44 -40
- package/lib/runtime.d.ts +82 -0
- package/lib/runtime.js +216 -26
- package/lib/tool-names.d.ts +43 -0
- package/lib/tool-names.js +85 -0
- package/package.json +31 -20
- package/scripts/patch-permission-glyph.mjs +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,102 @@ undefined
|
|
|
9
9
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
10
10
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
11
11
|
|
|
12
|
+
## [5.1.2-compat.15] - 2026-10-12
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **提示文字跟随 UI 语言切换(通知条 + 审批记录)**:上一版把服务器生成的英文 `humanBrief` 原文存进事件,
|
|
17
|
+
回看时不管 UI 是什么语言都显示英文。现客户端改从事件的 `doubtKinds`(机器键,早已落盘)经 `t('doubt.xxx')`
|
|
18
|
+
渲染——`locales.ts` 新增 10 条 `doubt.*` × 9 语,通知条与审批记录列表两处渲染点本地化;
|
|
19
|
+
旧事件无 `doubtKinds` 时回退 `humanBrief ?? justification ?? reason`。弹窗那半(宿主 `.headline` 取 `displayReason.{zh,en}`)不变。
|
|
20
|
+
|
|
21
|
+
## [5.1.2-compat.14] - 2026-10-12
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **命令不再夹在文案里**。宿主审批面板的排版是固定的:`.headline`(放 `displayReason`,一个普通 div,**不保留换行**)
|
|
26
|
+
+ `.command`(等宽代码块,由 ui-chat 注册的 `conversation.approval.detail` 填充)。
|
|
27
|
+
上一版把命令塞进 `displayReason` 的“影响面”一句,结果换行被吞掉、命令埋在句子中间读不出来。
|
|
28
|
+
现 `humanBrief()` 只输出三段文案(疑点 / 影响面 / 代价),命令走它自己的通道:
|
|
29
|
+
弹窗交给宿主的代码块,审批记录则落在新的事件字段 `humanCommand`(脱敏、上限 400 字),
|
|
30
|
+
面板用等宽 + `white-space: pre-wrap` 单独成块渲染。
|
|
31
|
+
|
|
32
|
+
## [5.1.2-compat.13] - 2026-10-11
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **弹窗不再只给内部代号**:`humanBrief()` 把一次 ask 翻成三段人话——**疑点**(为什么没结论:无规则覆盖 / 载荷读不懂 / 规则判不可逆 / 模型只能升级不能放行)、**影响面**(目标类型 + 可逆性 + 看到哪些文件 + 脱敏命令)、**两个按钮各花什么钱**(按 R/C/I/U 分档,I 档写明“无法撤回”)。
|
|
37
|
+
进 `displayReason: { zh, en }`,宿主按 UI 语言取,其余七语回退 `en`;审计 `reason` 保持英文短标签不变。
|
|
38
|
+
- **审批记录可回溯**:同一份 brief 随人工终态事件落盘(`humanBrief` + `doubtKinds`),面板优先渲染它。之前弹窗里的解释只存在几秒钟,回看时只剩 `no rule matched; default action`。
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- **只读命令被标成 `target=write`**:只读判定是“读动词 + 全局无管道”,于是 `Get-ChildItem -Force | Select-Object Name`(PowerShell 会话里最常见的形状)在卡片上被说成会写。现逐段判定:首段是读动词、后面全是重排过滤器(`Select-Object`/`Where-Object`/`Out-String`/`Format-Table` …)、且无重定向,才算只读;管道进写入器/网络工具/`Stop-Process` 仍不算。
|
|
43
|
+
- **工具名白名单是个绕过面**:P0 与规则链都靠 `SHELL_TOOLS` 认“这是不是一条命令”,而模型已经会打出 `pash` 这种名字(实测日志可见)——不认识的 runner 带着 `diskpart /s x.txt` 就跳过了所有命令级检查。现按**形状**判定(参数里有 `command`/`cmd`/`script` 串),并保留已知非执行器(`read`/`write`/内部工具)的例外,否则一份引用了命令的文档会被当成执行了它。
|
|
44
|
+
名单本身(`READ_TOOLS`/`INTERNAL_TOOLS`/`WRITE_TOOLS`/`SHELL_TOOLS`)集中到 `src/tool-names.ts`,之前“这是一条被执行的命令吗”在代码里有两个答案。
|
|
45
|
+
|
|
46
|
+
### Known gap
|
|
47
|
+
|
|
48
|
+
- 面板端的 `doubtKinds` 目前直接显示英文 brief(审计语言);按 9 语渲染疑点文案需给 `locales.ts` 补 10 个键 × 9 语,已记为后续页。
|
|
49
|
+
|
|
50
|
+
## [5.1.2-compat.12] - 2026-10-11
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
|
|
54
|
+
- **`reg delete` 只匹配了 hive 根,没匹配真实在用的子键形**:`REGISTRY_DESTROY` 在 `system` 后留了个 `\b`,
|
|
55
|
+
而解析器已把 `HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy` 剥成 `HKLMSYSTEMCurrentControlSet…`——
|
|
56
|
+
于是**所有带子键的删除全部绕过 P0**(只落到 ask),而无人在跑的裸 hive 根反而被拦。
|
|
57
|
+
现去掉尾部边界;新预例:`reg delete HKLM\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy /f`、
|
|
58
|
+
`reg delete "HKLM\SYSTEM\MountedSoftware" /f` 均为 deny;
|
|
59
|
+
`reg delete HKCU\Software\…`、`reg delete HKLM\SOFTWARE\…`(非 hive)仍放行。
|
|
60
|
+
发现的途径:给真宿主组装可粘贴测试集时,把每一行先过一遍已装副本的 dry-run(`scripts/gate-dryrun-check.mjs`),29 行里就它一条 DIFF。
|
|
61
|
+
|
|
62
|
+
## [5.1.2-compat.11] - 2026-10-11
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **`src/redact.ts`**:有界且幂等的脱敏原语(`[redacted:<len>:<kind>]`、文档体→字节数+哈希)。此前 `classifier.ts` 声称“密钥已在上游剥离”,实际并未剥离 —— grader 载荷是整包 args 原文外送第三方端点的。
|
|
67
|
+
- **`src/ask-context.ts`**:一次构造、三个投影(grader 载荷 / 人侧卡片 / 事件归因)。档位三字段(preset/sandbox/approval)只进弹窗与事件流,**绝不进 grader prompt**(实测喂给模型会让它顺着规则层答而不是自己判,打扰 67.1%→87.8%)。
|
|
68
|
+
- **`src/irreversible.ts`**:不可逆性的单一真源,分三层 —— P0 deny / never-clear ask(模型不能洗白、确认不能沉淀)/ 可学习 ask。所有函数只返回 `string | undefined`,结构上无法产生 allow。
|
|
69
|
+
- **`src/command-normalize.ts`**:base64(UTF-16LE/UTF-8 双读)/hex/变量展开的旁路解码,四条护栏:不回写、限长限深限变体、超预算丢弃部分成果、自家 `--exec-b64` wrapper 认出但不豁免解码。
|
|
70
|
+
- **`src/grader-rules.ts`**:classifier 与 risk 共享的唯一判据真源(分开成文件以避开 ESM 循环引用);`classifyWithLLM` 标 `@deprecated`,其 deny 永不进决策路径。
|
|
71
|
+
- 弹窗 `displayReason`(宿主正式字段,不进审计):为何在问 / 脱敏命令 / 文件清单+真实总数 / `tier=target=payload=opaque never-learn` 标 / 两个按钮的代价。
|
|
72
|
+
- `PENDING_CAP` FIFO 淘汰可观测:`evictions` 计数 + 事件告警 + `pendingAskStats()`。
|
|
73
|
+
|
|
74
|
+
### Changed
|
|
75
|
+
|
|
76
|
+
- `unknown-target` 成为 risk 协议的**硬类目**(永不 auto-allow、永不学习),且刻意不进任何 deny 词表:“看不见”不是“确定坏”。
|
|
77
|
+
- 判定层按程序名位置匹配,不做自由文本词匹配;`unresolved` 用 `verdict` 标签区分(不新增 `kind`,因为 `presentation()` 的 default 分支会把未知 kind 渲染成绿色 ALLOW)。
|
|
78
|
+
- 提示词版本化:`v7-terse-budget`。延迟门槛按服务端 `timings.prompt_ms` 量(墙钟在共享端点上会被排队淹没):v5 +36.0ms → v6 +1.9ms → v7 −0.1ms 且 token 持平 714。
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- `decomposeShellCommand` 会剥掉裸参数里的反斜杠,导致所有要求 `\` 分隔符的 Windows 规则(`HKLM\SYSTEM`、`C:\Windows\System32`)在主判定路径上默默落空 —— 现同时匹配“去分隔符”形态(要求驱动号前缀,避免把仓内目录名当系统根)。
|
|
83
|
+
- 同一解析器不拆 `;` 链且剥引号,致使 `Select-String -Pattern "vssadmin|cipher|diskpart"` 被当成执行 `diskpart` 并不可申诉地拒绝 —— 现改用解析器给出的权威程序名、仅在 arg 边界取链、分段器改为引号感知;链式 wipe 覆盖不回退。
|
|
84
|
+
- `containsCredentialMaterial` 对参数里任意 `Bearer <token>` 与只读探测(`Test-Path ~/.ssh/id_rsa`)直接 P0 硬拒 —— 两条都是误拦(正当编码反例集 1/20 → 0/20),且带密钥的命令根本到不了弹窗;同一物料一旦移动(`scp`/`Copy-Item`/管道入 `curl`)仍拒。
|
|
85
|
+
- 流水拆段使“密件 + 外发通道”分属两段、per-command deny 看不到成对关系 —— 现由整调用视图的 `tier=I` 否决兑住(只否决模型,不产生 deny)。
|
|
86
|
+
- `dsh.plugin.json` 版本号与 `package.json` 差一个发布号(宿主读的是前者)。
|
|
87
|
+
- `client-settings-seat` 测试仍断言 compat.10 已退役的 `shell.overlay` 席位(基线红),现跟随退役决定修正。
|
|
88
|
+
|
|
89
|
+
## [5.1.2-compat.10] - 2026-10-08
|
|
90
|
+
|
|
91
|
+
### Removed
|
|
92
|
+
|
|
93
|
+
- 退役 `shell.overlay` 悬浮齿轮:设置面在所有宿主线上都由 `settings.section` 渲染(+ bridge describe/mutate),固定角落的齿轮全机冗余。
|
|
94
|
+
|
|
95
|
+
## [5.1.2-compat.9] - 2026-10-08
|
|
96
|
+
|
|
97
|
+
### Changed
|
|
98
|
+
|
|
99
|
+
- **回退家族 tab 收拢(用户定案:自动审查门不在 tab 化范围内)**:顶级
|
|
100
|
+
`settings.section`(侧栏「自动审查门」独立节,order 30)恢复为唯一设置导航
|
|
101
|
+
入口;同日上午加入的 `dsh-family.tab`(order 45)撤除。
|
|
102
|
+
- `settings.plugins.tab`(插件页 tab,0.1.0 实测与独立节双渲染)与
|
|
103
|
+
`settings.plugin.item`(≤0.1.5 派发卡)维持退役——与独立节重复;
|
|
104
|
+
`plugins.bundle.config` 详情卡与 `shell.overlay` 浮窗齿轮不变。
|
|
105
|
+
- 席位契约测试(test/client-settings-seat.spec.ts)改钉单节形态:
|
|
106
|
+
settings.section 恰一次(order 30 / PermissiveCard),三个退役席位断言缺席。
|
|
107
|
+
|
|
12
108
|
## [2.6.0] - 2026-09-17
|
|
13
109
|
|
|
14
110
|
### Added
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-perm-gate",
|
|
3
3
|
"name": "dsh-perm-gate",
|
|
4
|
-
"version": "5.1.
|
|
4
|
+
"version": "5.1.2-compat.15",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Deterministic-first, fail-closed permission gate for DeepSeek Harness 0.1.2+: a P0 hard-deny -> P1 precise session grant -> P2 static allow/deny rule chain -> P3 optional LLM classifier -> P4 ask pipeline over the tools/pre-execute waterfall, with a standalone CLI dry-run evaluator and an auditable decision log. It only vetoes with deny/ask and always delegates otherwise, and unknown or malformed rules fail loud instead of silently standing the gate down.",
|
|
7
7
|
"zh": "面向 DeepSeek Harness 0.1.2+ 的确定性优先、失败即拒的权限闸门:在 tools/pre-execute 瀑布上串起 P0 硬拒绝 → P1 精确会话授权 → P2 静态 allow/deny 规则链 → P3 可选 LLM 语义分类 → P4 ask,并附带独立 CLI 干跑评估器与可审计的决策日志。只以 deny/ask 否决、其余一律放行,规则未知或非法时直接报错,绝不静默失效。"
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/** Reversibility tier — the axis the human and the safety metric both care about. */
|
|
2
|
+
export type Reversibility =
|
|
3
|
+
/** irreversible damage (data destroyed, credential left the machine) */
|
|
4
|
+
'I'
|
|
5
|
+
/** unknown-target: the payload cannot be inspected, so harm cannot be ruled out */
|
|
6
|
+
| 'U'
|
|
7
|
+
/** has an external side effect but is recoverable (push, PR, global config) */
|
|
8
|
+
| 'C'
|
|
9
|
+
/** reversible or read-only (list, diff, build, local commit, workspace edit) */
|
|
10
|
+
| 'R';
|
|
11
|
+
/** What the call is aimed at. */
|
|
12
|
+
export type TargetKind = 'read' | 'write' | 'delete' | 'remote-write' | 'exec-external' | 'unknown';
|
|
13
|
+
/** The session's three tier knobs, folded out of the durable session log. */
|
|
14
|
+
export interface SessionKnobs {
|
|
15
|
+
readonly permissionPreset?: string;
|
|
16
|
+
readonly sandboxMode?: string;
|
|
17
|
+
readonly approvalPolicy?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Input to {@link buildAskContext}; every field optional except `tool`. */
|
|
20
|
+
export interface AskContextSource {
|
|
21
|
+
readonly tool: string;
|
|
22
|
+
readonly args?: Record<string, unknown>;
|
|
23
|
+
readonly commandText?: string;
|
|
24
|
+
readonly cwd?: string;
|
|
25
|
+
/** The workspace root the prompt's scope rules keep assuming but never had. */
|
|
26
|
+
readonly workspaceRoot?: string;
|
|
27
|
+
readonly knobs?: SessionKnobs;
|
|
28
|
+
readonly files?: readonly string[];
|
|
29
|
+
/** The deterministic layer's own conclusion, kept for the human, not the grader. */
|
|
30
|
+
readonly ruleReason?: string;
|
|
31
|
+
/** Correlation key, mixed into the event digest. */
|
|
32
|
+
readonly callId?: string;
|
|
33
|
+
/** Per-session salt so `inputDigest` cannot be dictionary-reversed or joined across sessions. */
|
|
34
|
+
readonly salt?: string;
|
|
35
|
+
/** Injected hash (sha256 hex on the node half) to keep crypto out of this module. */
|
|
36
|
+
readonly hash?: (text: string) => string;
|
|
37
|
+
}
|
|
38
|
+
/** The frozen context. */
|
|
39
|
+
export interface AskContext extends AskContextSource {
|
|
40
|
+
readonly targetKind: TargetKind;
|
|
41
|
+
readonly targetResolvable: boolean;
|
|
42
|
+
readonly reversibility: Reversibility;
|
|
43
|
+
/** Whether the act is unrecoverable, judged by the shared `irreversible.ts`. */
|
|
44
|
+
readonly irreversible: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Unrecoverable data loss that is nonetheless **not** a hard-deny: the human
|
|
47
|
+
* decides, but no grader may clear it and no confirmations may learn it away.
|
|
48
|
+
* `docker volume rm` is the archetype — routine maintenance, unrecoverable.
|
|
49
|
+
*/
|
|
50
|
+
readonly unlearnable: boolean;
|
|
51
|
+
/** Read-only commands that need no human attention at all. */
|
|
52
|
+
readonly readOnly: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Freeze one call into an {@link AskContext}. Pure and side-effect free: it reads
|
|
56
|
+
* what it is handed and never touches the filesystem or the network, so the three
|
|
57
|
+
* projections cannot disagree about what they were built from.
|
|
58
|
+
*/
|
|
59
|
+
export declare function buildAskContext(source: AskContextSource): AskContext;
|
|
60
|
+
/** What the risk grader is allowed to see. */
|
|
61
|
+
export interface GraderPayload {
|
|
62
|
+
readonly tool: string;
|
|
63
|
+
readonly args: unknown;
|
|
64
|
+
readonly reason: string;
|
|
65
|
+
readonly target_resolvable: boolean;
|
|
66
|
+
readonly workspace_root?: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Project for egress to the grader. Tier knobs and the rule layer's own verdict
|
|
70
|
+
* are stripped by design — see the module comment.
|
|
71
|
+
*/
|
|
72
|
+
export declare function toGraderPayload(ctx: AskContext): GraderPayload;
|
|
73
|
+
/** What the human approving this call should be shown. */
|
|
74
|
+
export interface HumanCard {
|
|
75
|
+
readonly tool: string;
|
|
76
|
+
readonly command: string;
|
|
77
|
+
readonly files: readonly string[];
|
|
78
|
+
/** Total file count, so the card can say "and N more" instead of lying by omission. */
|
|
79
|
+
readonly fileCount: number;
|
|
80
|
+
readonly reversibility: Reversibility;
|
|
81
|
+
readonly targetKind: TargetKind;
|
|
82
|
+
readonly permissionPreset?: string;
|
|
83
|
+
readonly sandboxMode?: string;
|
|
84
|
+
readonly approvalPolicy?: string;
|
|
85
|
+
readonly reason: string;
|
|
86
|
+
}
|
|
87
|
+
/** Project for the approval popup (already redacted; never raw args). */
|
|
88
|
+
export declare function toHumanCard(ctx: AskContext, maxFiles?: number): HumanCard;
|
|
89
|
+
/**
|
|
90
|
+
* A one-line summary for the human who must decide.
|
|
91
|
+
*
|
|
92
|
+
* The host renders an approval request from its `reason` string and nothing
|
|
93
|
+
* else, so until `approval.request` can carry structured fields this is the only
|
|
94
|
+
* channel that reaches a human. Built from language-neutral enum tokens
|
|
95
|
+
* (`tier=C`, `payload=opaque`) rather than prose: prose would need nine
|
|
96
|
+
* translations per batch, and a host renderer can localize the tokens later
|
|
97
|
+
* without a format change.
|
|
98
|
+
*/
|
|
99
|
+
export declare function humanCardLine(ctx: AskContext, maxFiles?: number): string;
|
|
100
|
+
/**
|
|
101
|
+
* Why this call has no answer yet, in words a person who does not know this
|
|
102
|
+
* codebase can read. The tokens (`tier=C target=write`) were never a decision aid:
|
|
103
|
+
* they name the gate's internals, not the user's exposure, and a mislabelled one
|
|
104
|
+
* (`target=write` on a directory listing) teaches people to stop reading the card.
|
|
105
|
+
*
|
|
106
|
+
* Keys are stable machine identifiers so the client panel can localize them later;
|
|
107
|
+
* the popup gets `zh`/`en` text because the host resolves `displayReason` by UI
|
|
108
|
+
* locale and falls back to `en` for the other seven languages.
|
|
109
|
+
*/
|
|
110
|
+
export declare const DOUBT_COPY: {
|
|
111
|
+
readonly 'no-rule': {
|
|
112
|
+
readonly zh: "没有规则覆盖这次调用,按默认策略交给人 — 这不表示它危险";
|
|
113
|
+
readonly en: "no rule covers this call, so the default is to ask — that does not mean it is dangerous";
|
|
114
|
+
};
|
|
115
|
+
readonly opaque: {
|
|
116
|
+
readonly zh: "命令载荷读不懂(编码过 / 工作区外的脚本 / 认不出程序名),规则层无法定位目标";
|
|
117
|
+
readonly en: "the payload cannot be read (encoded, a script outside the workspace, or no recognisable program), so no rule can locate its target";
|
|
118
|
+
};
|
|
119
|
+
readonly irreversible: {
|
|
120
|
+
readonly zh: "规则判定不可逆:跑完无法撤回";
|
|
121
|
+
readonly en: "a rule says this is irreversible: it cannot be undone once run";
|
|
122
|
+
};
|
|
123
|
+
readonly 'never-learn': {
|
|
124
|
+
readonly zh: "这类确认不会沉淀,下次同样的操作还会再问";
|
|
125
|
+
readonly en: "this answer will not be learned; the same call will ask again next time";
|
|
126
|
+
};
|
|
127
|
+
readonly delete: {
|
|
128
|
+
readonly zh: "会删除内容";
|
|
129
|
+
readonly en: "it deletes content";
|
|
130
|
+
};
|
|
131
|
+
readonly 'remote-write': {
|
|
132
|
+
readonly zh: "会产生对外可见的副作用(推送 / 发布 / 写请求),撤回需要额外手动操作";
|
|
133
|
+
readonly en: "it has an externally visible effect (push / publish / write request); undoing it takes extra manual steps";
|
|
134
|
+
};
|
|
135
|
+
readonly 'exec-external': {
|
|
136
|
+
readonly zh: "会执行工作区之外的代码,本次调用无法静态看清它做什么";
|
|
137
|
+
readonly en: "it runs code from outside the workspace, which this call cannot inspect";
|
|
138
|
+
};
|
|
139
|
+
readonly 'outside-paths': {
|
|
140
|
+
readonly zh: "涉及工作区之外的路径";
|
|
141
|
+
readonly en: "it touches paths outside the workspace";
|
|
142
|
+
};
|
|
143
|
+
readonly 'model-flagged': {
|
|
144
|
+
readonly zh: "风险模型认为可疑(它只能升级,不能放行)";
|
|
145
|
+
readonly en: "the risk model flagged it (it can only escalate, never clear)";
|
|
146
|
+
};
|
|
147
|
+
readonly write: {
|
|
148
|
+
readonly zh: "会写入或修改文件";
|
|
149
|
+
readonly en: "it writes or modifies files";
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
export type DoubtKind = keyof typeof DOUBT_COPY;
|
|
153
|
+
/**
|
|
154
|
+
* The machine keys behind "why am I being asked". Derived, never passed in: the
|
|
155
|
+
* caller cannot forget to add a doubt, and a doubt cannot contradict the facts.
|
|
156
|
+
*/
|
|
157
|
+
export declare function askDoubtKinds(ctx: AskContext, extra?: {
|
|
158
|
+
readonly modelFlagged?: boolean;
|
|
159
|
+
}): DoubtKind[];
|
|
160
|
+
/**
|
|
161
|
+
* The card a human decides from, as three prose clauses with **no command in them**.
|
|
162
|
+
*
|
|
163
|
+
* The command does not belong here. The host's approval panel renders the pending
|
|
164
|
+
* tool call's command in its own monospace block (ui-chat registers
|
|
165
|
+
* `conversation.approval.detail` → `ApprovalCommand`, styled `.command`), while the
|
|
166
|
+
* resolved `displayReason` goes into `.headline`, a plain div with no
|
|
167
|
+
* `white-space: pre-line` — so any `\n` in it collapses and a command embedded in
|
|
168
|
+
* prose ends up buried mid-sentence. Keeping the two in their own channels is what
|
|
169
|
+
* makes the popup readable.
|
|
170
|
+
*
|
|
171
|
+
* The event still carries the command separately (`humanCommand`) because the
|
|
172
|
+
* review list has no host-provided command block next to it.
|
|
173
|
+
*/
|
|
174
|
+
export declare function humanBrief(ctx: AskContext, lang: 'zh' | 'en', kinds?: ("write" | "delete" | "remote-write" | "exec-external" | "never-learn" | "no-rule" | "opaque" | "irreversible" | "outside-paths" | "model-flagged")[]): string;
|
|
175
|
+
/** The redacted command alone, for the channels that have nowhere else to show it. */
|
|
176
|
+
export declare function humanCommand(ctx: AskContext): string;
|
|
177
|
+
/** What goes on the durable event, for later attribution. */
|
|
178
|
+
export interface EventFields {
|
|
179
|
+
readonly inputDigest?: string;
|
|
180
|
+
readonly cwd?: string;
|
|
181
|
+
readonly permissionPreset?: string;
|
|
182
|
+
readonly sandboxMode?: string;
|
|
183
|
+
readonly approvalPolicy?: string;
|
|
184
|
+
/** The machine keys of "why it asked", so the panel can localize them. */
|
|
185
|
+
readonly doubtKinds?: readonly DoubtKind[];
|
|
186
|
+
/** The English form of the card the human decided from (audit language). */
|
|
187
|
+
readonly humanBrief?: string;
|
|
188
|
+
/** The redacted command on its own, so the review list can show it as code. */
|
|
189
|
+
readonly humanCommand?: string;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Project for the event log: a salted digest (raw args never land on disk) plus
|
|
193
|
+
* the knobs, so a reviewer can reconstruct *why* the gate saw what it saw.
|
|
194
|
+
* Emits no digest when no hash is injected rather than falling back to an
|
|
195
|
+
* unsalted one — that would be dictionary-reversible across sessions.
|
|
196
|
+
*/
|
|
197
|
+
export declare function toEventFields(ctx: AskContext): EventFields;
|
|
198
|
+
/**
|
|
199
|
+
* Fold the three durable tier knobs out of a session event log (last wins).
|
|
200
|
+
* `preset.ts` folds only `permission/preset`; the other two ride the same log.
|
|
201
|
+
* Tolerant of every host line: an unknown shape yields `undefined`, never a
|
|
202
|
+
* wrong value.
|
|
203
|
+
*/
|
|
204
|
+
export declare function foldSessionKnobs(events: readonly {
|
|
205
|
+
type?: string;
|
|
206
|
+
data?: Record<string, unknown>;
|
|
207
|
+
}[] | undefined): SessionKnobs;
|