@fishwithcat/dsh-user-approval 0.0.0-stage → 0.2.2-alpha.3
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/README.i18n.yaml +94 -0
- package/README.md +183 -2
- package/README.zh.md +184 -0
- package/lib/index.js +272 -0
- package/lib/types/index.d.ts +194 -0
- package/lib/types/index.js +290 -0
- package/lib/types/types.d.ts +99 -0
- package/lib/types/types.js +15 -0
- package/locale/de.json +6 -0
- package/locale/en.json +5 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/ja.json +6 -0
- package/locale/pt.json +6 -0
- package/locale/ru.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +58 -4
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/interaction/user-approval/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 515e991a5671711d
|
|
7
|
+
zh: 194d48a8da9b51b0
|
|
8
|
+
/deepseek-ai-dsh-user-approval:
|
|
9
|
+
en: 548c7fcc396775de
|
|
10
|
+
zh: ad82c4872acbf83a
|
|
11
|
+
/deepseek-ai-dsh-user-approval/summary:
|
|
12
|
+
en: 19dc57046af83b02
|
|
13
|
+
zh: 031d6e21c6a753bc
|
|
14
|
+
/deepseek-ai-dsh-user-approval/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/deepseek-ai-dsh-user-approval/use-this-package:
|
|
18
|
+
en: 7e3d08b4dbea2a91
|
|
19
|
+
zh: 8a6aecaef1afe61c
|
|
20
|
+
/deepseek-ai-dsh-user-approval/use-this-package/composing-answerers:
|
|
21
|
+
en: 3618ddc3386e65f0
|
|
22
|
+
zh: d6c0ac91aae122bb
|
|
23
|
+
/deepseek-ai-dsh-user-approval/use-this-package/setting-the-policy:
|
|
24
|
+
en: 63fb9a4f615b88fe
|
|
25
|
+
zh: 134429410e1a6573
|
|
26
|
+
/deepseek-ai-dsh-user-approval/use-this-package/remembering-a-tool:
|
|
27
|
+
en: a6e89af700f2d9d7
|
|
28
|
+
zh: 6656ecddf02ea6bb
|
|
29
|
+
/deepseek-ai-dsh-user-approval/use-this-package/requesting-a-decision:
|
|
30
|
+
en: dc465de82799bab0
|
|
31
|
+
zh: 51144cc77bb15689
|
|
32
|
+
/deepseek-ai-dsh-user-approval/use-this-package/what-the-model-and-user-see:
|
|
33
|
+
en: 1969e658964c8d31
|
|
34
|
+
zh: 9d374260b90199d2
|
|
35
|
+
/deepseek-ai-dsh-user-approval/understand-the-implementation:
|
|
36
|
+
en: d07be7ba08baf010
|
|
37
|
+
zh: 665e68e9188e5d60
|
|
38
|
+
/deepseek-ai-dsh-user-approval/understand-the-implementation/source-map:
|
|
39
|
+
en: 13e36326803e0d1e
|
|
40
|
+
zh: ce9b175b3b318e21
|
|
41
|
+
/deepseek-ai-dsh-user-approval/understand-the-implementation/dispatch:
|
|
42
|
+
en: 47f97100ae37720e
|
|
43
|
+
zh: 61a6c01408183076
|
|
44
|
+
/deepseek-ai-dsh-user-approval/understand-the-implementation/policy-and-the-runtime-context-snapshot:
|
|
45
|
+
en: a1aa2178ce3a4b85
|
|
46
|
+
zh: db06e4d2e8e259f1
|
|
47
|
+
/deepseek-ai-dsh-user-approval/understand-the-implementation/audit:
|
|
48
|
+
en: 0a583521364521cc
|
|
49
|
+
zh: 916bedea94f5c0e1
|
|
50
|
+
/deepseek-ai-dsh-user-approval/further-exploration:
|
|
51
|
+
en: 584b09e5be83317c
|
|
52
|
+
zh: 3c27cee4d33cb5af
|
|
53
|
+
/deepseek-ai-dsh-user-approval/model-experience:
|
|
54
|
+
en: 215e7ba838619b7b
|
|
55
|
+
zh: a311da3843709f90
|
|
56
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context:
|
|
57
|
+
en: d23980f003b42fbb
|
|
58
|
+
zh: 532f0174de5d87b5
|
|
59
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees:
|
|
60
|
+
en: eb41f1a4972c07f5
|
|
61
|
+
zh: 4264f408eb951951
|
|
62
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees/ask-policy-contribution:
|
|
63
|
+
en: b9f1262c97ec7568
|
|
64
|
+
zh: 8a6613695f896f70
|
|
65
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees/never-policy-contribution:
|
|
66
|
+
en: f13f09fc2466513b
|
|
67
|
+
zh: 4a755a682551c481
|
|
68
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/what-the-model-sees/switch-notice:
|
|
69
|
+
en: e87e1e28ae7d7243
|
|
70
|
+
zh: c6daddb5a131565a
|
|
71
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/token-effect:
|
|
72
|
+
en: e96807422bdce44d
|
|
73
|
+
zh: 9fdcfafcc96bd004
|
|
74
|
+
/deepseek-ai-dsh-user-approval/model-experience/current-approval-policy-context/kv-cache-effect:
|
|
75
|
+
en: b4bac06588fdd1c3
|
|
76
|
+
zh: 60271562fdd52f72
|
|
77
|
+
/deepseek-ai-dsh-user-approval/model-experience/tool-outcome:
|
|
78
|
+
en: 54c140b4a1b4a5bc
|
|
79
|
+
zh: 8573891441963be4
|
|
80
|
+
/deepseek-ai-dsh-user-approval/model-experience/tool-outcome/what-the-model-sees:
|
|
81
|
+
en: fd0c40c0a6b4c967
|
|
82
|
+
zh: c7e18063e11080e9
|
|
83
|
+
/deepseek-ai-dsh-user-approval/model-experience/tool-outcome/token-effect:
|
|
84
|
+
en: 972a63ca590fecde
|
|
85
|
+
zh: 217b63d98365dbdb
|
|
86
|
+
/deepseek-ai-dsh-user-approval/model-experience/tool-outcome/kv-cache-effect:
|
|
87
|
+
en: f5d5f97a153f7c70
|
|
88
|
+
zh: 4db81881ebdcfcc4
|
|
89
|
+
/deepseek-ai-dsh-user-approval/known-limitations-and-deferred-work:
|
|
90
|
+
en: bd3d35b5a8051db9
|
|
91
|
+
zh: fbd385cbba92298b
|
|
92
|
+
/deepseek-ai-dsh-user-approval/known-limitations-and-deferred-work/dev-note:
|
|
93
|
+
en: e67f633dc6fe7773
|
|
94
|
+
zh: 3bb0c20c93f16f98
|
package/README.md
CHANGED
|
@@ -1,3 +1,184 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Channel-neutral one-shot approval seam for users and maintainers composing answerers, setting policy, or debugging fail-closed permission decisions."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @deepseek-ai/dsh-user-approval
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Use this package to require a decision before a sensitive tool action proceeds. The `ask` policy sends each request to the deployment's human or machine answerers; `never` rejects it without prompting. Missing or failed answerers return `unavailable`, so the action fails closed. A one-shot approval covers only its request; a remembered grant covers the same tool for the session and its live descendants. Every request and outcome is recorded in the requesting session's audit log. The model sees the resulting tool outcome and current policy, but not the human permission UI or audit events.
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Use this package](#use-this-package)
|
|
17
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
18
|
+
- [Further Exploration](#further-exploration)
|
|
19
|
+
- [Model Experience](#model-experience)
|
|
20
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
21
|
+
- [Dev Note](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## Use this package
|
|
27
|
+
|
|
28
|
+
Compose this service when sensitive tool actions should pause for a human or machine decision instead of running unconditionally. It requires `sessionProjections`; shipped profiles already provide that service. The tools pipeline and the sandboxed bash tool route their `ask` decisions through this seam and fail closed when it is absent, so interactive deployments mount it with at least one answerer.
|
|
29
|
+
|
|
30
|
+
### Composing answerers
|
|
31
|
+
|
|
32
|
+
Answerers are `approval/request` waterfall listeners: return an outcome to answer for an owned agent, or call `next()` to delegate. Agent-scoped listeners receive only that agent's requests, and a deployment composes one terminal answerer — sibling listener order is not a policy-priority mechanism. Without a terminal answerer, requests resolve `unavailable` and fail closed; the service itself never prompts a human.
|
|
33
|
+
|
|
34
|
+
### Setting the policy
|
|
35
|
+
|
|
36
|
+
The effective policy is the one set for the session, falling back to the configured default. `ask` (the default) delegates to the composed answerers; `never` rejects every request deterministically before interactive dispatch — the strict headless stance for CI and unattended runs.
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@deepseek-ai/dsh-session-projection'
|
|
40
|
+
- name: '@deepseek-ai/dsh-user-approval'
|
|
41
|
+
config:
|
|
42
|
+
policy: ask
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Field | Default | Meaning |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `policy` | `ask` | Default for sessions without an `approval/policy` override |
|
|
48
|
+
|
|
49
|
+
The generated [configuration catalog](../../../docs/config-catalog.md#deepseek-aidsh-user-approval) is the exhaustive source for every accepted field and its JSDoc. `effectivePolicy(session)` reads the policy currently in force, including the configured default; delegation uses it to decide whether a child inherits an interactive policy. `setPolicy(agent, policy)` switches a live agent and queues the switch notice described under Model Experience for its next model step; `setApprovalPolicy(session, policy)` is the direct durable write path used by session initialization.
|
|
50
|
+
|
|
51
|
+
### Remembering a tool
|
|
52
|
+
|
|
53
|
+
`grant(session, toolName)` appends one durable `approval/grant` for a tool, and `isGranted(agent, toolName)` checks the Agent's own session and each live ancestor through `parentSession`. Under `ask`, a granted ask resolves `allowed-once` without consulting an answerer and its `approval/decided` carries `remembered: true`; `never` still rejects first. The TUI records the grant on its own root Agent, so one Always allow choice covers the app and every live subagent, including one that asked.
|
|
54
|
+
|
|
55
|
+
### Requesting a decision
|
|
56
|
+
|
|
57
|
+
`request(req)` names the agent, tool, optional call id and reason, and an abort signal. Optional `displayReason` supplies localized presentation text without changing the logged reason. It requires an open turn: an idle or between-turn caller throws before auditing anything. Aborting withdraws the question — the request settles `cancelled` and a late answer is discarded. A failure that prevents either audit append from committing rejects instead of returning an unlogged decision.
|
|
58
|
+
|
|
59
|
+
### What the model and user see
|
|
60
|
+
|
|
61
|
+
The model sees only the asking consumer's eventual tool outcome — allowed, rejected, cancelled, or unavailable — plus the current policy in the runtime-context snapshot; the audit events, remembered grants, and the human permission UI are not model context. Each policy switch is announced to the model by a sourced user message, and both policies contribute their complete current meaning to the snapshot.
|
|
62
|
+
|
|
63
|
+
-----
|
|
64
|
+
|
|
65
|
+
<a id="understand-the-implementation"></a>
|
|
66
|
+
## Understand the implementation
|
|
67
|
+
|
|
68
|
+
<details>
|
|
69
|
+
<summary>Implementation internals — click to expand</summary>
|
|
70
|
+
|
|
71
|
+
The observable behavior is covered in [Use this package](#use-this-package); this section explains dispatch, policy enforcement, and the audit path.
|
|
72
|
+
|
|
73
|
+
### Source map
|
|
74
|
+
|
|
75
|
+
| File | Role |
|
|
76
|
+
|---|---|
|
|
77
|
+
| [`src/index.ts`](src/index.ts) | `ApprovalService`: request dispatch, private approval state, policy write path, remembered grants, runtime-context contribution |
|
|
78
|
+
| [`src/types.ts`](src/types.ts) | `ApprovalRequestId` brand and outcome types |
|
|
79
|
+
|
|
80
|
+
### Dispatch
|
|
81
|
+
|
|
82
|
+
`decide()` races the answerer waterfall against the request signal and contains every answerer failure: a throwing listener fails the question closed to `unavailable`, and a rogue non-vocabulary return is normalized to `unavailable`. The `never` policy is enforced inside the service before waterfall dispatch, so a listener registered later with `prepend` cannot bypass the deterministic rejection. A remembered grant is checked after `never` and before dispatch, so a covered ask never reaches an answerer. The request must be turn-enclosed because the audit pair is the durable log's commit/replay boundary — a bare event between turns is indistinguishable from a crash tail.
|
|
83
|
+
|
|
84
|
+
### Policy and the runtime-context snapshot
|
|
85
|
+
|
|
86
|
+
The private host-only `approvalState` projection stores the policy override and whether a turn is open. Its size does not grow with the number of events. The registry includes it in persisted checkpoints; the event log remains authoritative. Its pure updates never dispatch questions or grant permission.
|
|
87
|
+
|
|
88
|
+
Service activation waits for projection registration and fails if registration fails. The service keeps the returned reader private. The host and registry own Session recovery; approval methods do not load history or call hydration from a synchronous read. See the [registration and recovery API](../../session/session-projection/README.md). Pending questions, answerers, and grants remain in the runtime.
|
|
89
|
+
|
|
90
|
+
The system-prompt contribution `approval:policy` states the complete current meaning of the effective policy — `ask` with its fail-closed consequence, or `never` with its non-escalation consequence — after retained history, so switching policy appends a new full snapshot instead of rewriting the stable request header. `setPolicy()` also injects a sourced user message announcing the change for the next step.
|
|
91
|
+
|
|
92
|
+
### Audit
|
|
93
|
+
|
|
94
|
+
`request()` appends `approval/asked` with the request identity and tool, then `approval/decided` with the closed outcome; a decision covered by a remembered grant also carries `remembered: true`. `grant()` appends `approval/grant` once per tool. The exact appended fields live in [`src/index.ts`](src/index.ts). All are log-only.
|
|
95
|
+
|
|
96
|
+
</details>
|
|
97
|
+
|
|
98
|
+
-----
|
|
99
|
+
|
|
100
|
+
<a id="further-exploration"></a>
|
|
101
|
+
## Further Exploration
|
|
102
|
+
|
|
103
|
+
Read these pages when the package-level contract is not enough. They move from the approval vocabulary to the consumers and the design rationale.
|
|
104
|
+
|
|
105
|
+
- [Approval subsystem reference](../../../docs/subsystems/approval.md) — the shared request/outcome vocabulary and the `ctx.approval` Cordis surface.
|
|
106
|
+
- [Approval seam Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-approval-seam.md) — design rationale for the seam.
|
|
107
|
+
- [Remembered approval grants Agent Note](../../../.agents/notes/implemented/architecture/2026-10-22-remembered-approval-grants.md) — why grants are tool-scoped session state and how the TUI shares them with subagents.
|
|
108
|
+
- [Sandbox reference](../../shell/tool-bash/README.md) — how the sandboxed bash tool consumes approvals for escalated retries.
|
|
109
|
+
- [Interaction group map](../README.md) — adjacent permission preset and question packages.
|
|
110
|
+
|
|
111
|
+
-----
|
|
112
|
+
|
|
113
|
+
<a id="model-experience"></a>
|
|
114
|
+
## Model Experience
|
|
115
|
+
|
|
116
|
+
### Current approval policy context
|
|
117
|
+
|
|
118
|
+
#### What the model sees
|
|
119
|
+
|
|
120
|
+
The first request and each effective policy change append a full runtime-context snapshot after retained history. Under `ask`, the approval contribution states that configured answerers may be consulted and absence fails closed. Under `never`, it states the deterministic rejection and non-escalation consequence. Unchanged requests retain the earlier snapshot without adding another message. When the user switches a live Session's policy, the next model step also receives a switch notice.
|
|
121
|
+
|
|
122
|
+
##### Ask-policy contribution
|
|
123
|
+
|
|
124
|
+
```markdown
|
|
125
|
+
Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
##### Never-policy contribution
|
|
129
|
+
|
|
130
|
+
```markdown
|
|
131
|
+
Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
##### Switch notice
|
|
135
|
+
|
|
136
|
+
```markdown
|
|
137
|
+
The user changed the approval policy from "ask the user for approval" to "never ask the user for approval".
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Token effect
|
|
141
|
+
|
|
142
|
+
One concise context message on the first request and on an effective change; unchanged requests add no duplicate policy tokens.
|
|
143
|
+
|
|
144
|
+
#### KV Cache effect
|
|
145
|
+
|
|
146
|
+
Append-only after retained history. An `ask`/`never` switch preserves the stable system and conversation prefix instead of rewriting the first wire message.
|
|
147
|
+
|
|
148
|
+
### Tool outcome
|
|
149
|
+
|
|
150
|
+
#### What the model sees
|
|
151
|
+
|
|
152
|
+
`approval/asked` and `approval/decided` are log-only. The model sees only the asking consumer's eventual allowed, rejected, cancelled, or unavailable tool outcome; the human permission UI is not context.
|
|
153
|
+
|
|
154
|
+
#### Token effect
|
|
155
|
+
|
|
156
|
+
Zero duplicate audit tokens. A rejection may replace a normal tool result with a small retained error, while an allowance leaves the consumer's ordinary result.
|
|
157
|
+
|
|
158
|
+
#### KV Cache effect
|
|
159
|
+
|
|
160
|
+
Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
|
|
161
|
+
|
|
162
|
+
## Known Limitations and Deferred Work
|
|
163
|
+
|
|
164
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
These limits define when the seam is a poor fit or needs special composition care. They are current package constraints, not a general permission comparison.
|
|
168
|
+
|
|
169
|
+
- **Requests are valid only inside an open turn** — an idle or between-turn caller throws before auditing; a durable out-of-turn approval workflow is deferred. Grants are standalone log entries and may be recorded outside a turn.
|
|
170
|
+
- **Grants are per tool name** — the request carries no arguments, so a grant covers every operation the tool gates; there is no per-command, per-path, or per-call grant.
|
|
171
|
+
- **Grants have no revocation control** — a grant lasts for the session and its live descendants; switching the policy to `never` suppresses every ask, but no API removes one grant.
|
|
172
|
+
- **The request carries no tool arguments** — an answerer sees the tool name, reason, and optional call id; the ACP machine channel requires a call id and delegates requests without one.
|
|
173
|
+
- **No built-in answerer** — headless or incompletely composed deployments resolve `unavailable` and fail closed; the service itself never prompts a human.
|
|
174
|
+
- **Pending approvals are process-local** — checkpoints restore policy and turn state, not pending answerer promises or one-shot grants. Recovery does not resubmit questions or repeat runtime effects.
|
|
175
|
+
|
|
176
|
+
<a id="dev-note"></a>
|
|
177
|
+
### Dev Note
|
|
178
|
+
|
|
179
|
+
<details>
|
|
180
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
181
|
+
|
|
182
|
+
None.
|
|
183
|
+
|
|
184
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "与通道无关的一次性审批 seam;供组合应答者、设置策略或排查以拒绝方式关闭的权限决定的用户与维护者阅读。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @deepseek-ai/dsh-user-approval
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
使用本包可要求敏感工具操作在继续前取得决定。`ask` 策略将每个请求发送给部署中的人类或机器应答者;`never` 则直接拒绝,不发出提示。应答者缺失或失败时返回 `unavailable`,使操作以拒绝方式关闭。一次性批准只适用于对应请求;已记住的授权则覆盖同一工具的该会话及其存活后代。每个请求与结果都会记录在发起请求的会话审计日志中。模型会看到最终工具结果与当前策略,但不会看到人类权限 UI 或审计事件。
|
|
13
|
+
|
|
14
|
+
## 目录
|
|
15
|
+
|
|
16
|
+
- [使用本包](#use-this-package)
|
|
17
|
+
- [理解实现](#understand-the-implementation)
|
|
18
|
+
- [进一步探索](#further-exploration)
|
|
19
|
+
- [模型体验](#model-experience)
|
|
20
|
+
- [已知限制与延期工作](#known-limitations-and-deferred-work)
|
|
21
|
+
- [开发备注](#dev-note)
|
|
22
|
+
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
<a id="use-this-package"></a>
|
|
26
|
+
## 使用本包
|
|
27
|
+
|
|
28
|
+
当敏感工具操作应当暂停等待人或机器的决定、而非无条件执行时,组合此服务。它要求 `sessionProjections`;发布的配置已提供该服务。工具流水线与沙箱 bash 工具会通过此 seam 路由 `ask` 决定,并在该 seam 缺失时以拒绝方式关闭,因此交互式部署至少应组合一个应答者。
|
|
29
|
+
|
|
30
|
+
### 组合应答者
|
|
31
|
+
|
|
32
|
+
应答者是 `approval/request` waterfall(瀑布式事件)监听器:返回一个结果即为所负责的 agent(智能体)作答,否则调用 `next()` 委托。限定到 agent 的监听器只接收该 agent 的请求,且每项部署应组合一个最终应答者——同级监听器的顺序不是策略优先级机制。没有最终应答者时,请求解析为 `unavailable` 并以拒绝方式关闭;服务自身绝不会提示人类。
|
|
33
|
+
|
|
34
|
+
### 设置策略
|
|
35
|
+
|
|
36
|
+
有效策略取会话中已设置的策略,并回退到配置的默认值。`ask`(默认)委托给已组合的应答者;`never` 在交互式分发之前确定性地拒绝每个请求——这是 CI 与无人值守运行采用的严格无头模式。
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
- name: '@deepseek-ai/dsh-session-projection'
|
|
40
|
+
- name: '@deepseek-ai/dsh-user-approval'
|
|
41
|
+
config:
|
|
42
|
+
policy: ask
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| 字段 | 默认值 | 含义 |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `policy` | `ask` | 没有 `approval/policy` 覆盖的会话的默认策略 |
|
|
48
|
+
|
|
49
|
+
生成的[配置目录](../../../docs/config-catalog.zh.md#deepseek-aidsh-user-approval)是每个受支持字段及其 JSDoc 的穷尽式真源。`effectivePolicy(session)` 读取当前生效的策略(含配置默认值);委派用它判断子级是否继承交互式策略。`setPolicy(agent, policy)` 切换运行中的 agent 的策略,并为它的下一个模型步骤排队一条模型体验一节所述的切换通知;`setApprovalPolicy(session, policy)` 是会话初始化使用的直接持久写入路径。
|
|
50
|
+
|
|
51
|
+
### 记住工具
|
|
52
|
+
|
|
53
|
+
`grant(session, toolName)` 为工具追加一条持久的 `approval/grant`,`isGranted(agent, toolName)` 检查 Agent 自身会话以及通过 `parentSession` 可达的每个存活祖先。在 `ask` 下,已授权的询问不再咨询应答者,直接解析为 `allowed-once`,其 `approval/decided` 带有 `remembered: true`;`never` 仍优先拒绝。TUI 在其自己的根 Agent 上记录授权,因此一次「始终允许」选择覆盖应用及所有存活子代理,包括发起询问的那个。
|
|
54
|
+
|
|
55
|
+
### 请求决定
|
|
56
|
+
|
|
57
|
+
`request(req)` 指名 agent、工具、原因、可选的调用 id,以及一个中止信号。可选的 `displayReason` 提供本地化展示文案,不改写日志中的原因。它要求当前处于尚未结束的轮次中:空闲或在轮次之间调用会在审计前抛出异常。中止会撤回问题——请求以 `cancelled` 结算,迟到的回答被丢弃。若任一审计事件在提交前失败,请求会被拒绝,而不会返回一项未记录的决定。
|
|
58
|
+
|
|
59
|
+
### 模型与用户看到什么
|
|
60
|
+
|
|
61
|
+
模型只会看到发起请求的消费方最终给出的工具结果——允许、拒绝、取消或不可用——以及运行时上下文快照中的当前策略;审计事件、已记住的授权与面向人类的权限 UI 不属于模型上下文。每次策略切换都会以一条带来源的用户消息告知模型,两种策略都会把各自的完整当前含义贡献给快照。
|
|
62
|
+
|
|
63
|
+
-----
|
|
64
|
+
|
|
65
|
+
<a id="understand-the-implementation"></a>
|
|
66
|
+
## 理解实现
|
|
67
|
+
|
|
68
|
+
<details>
|
|
69
|
+
<summary>实现细节——点击展开</summary>
|
|
70
|
+
|
|
71
|
+
可观察行为已在[使用本包](#use-this-package)中说明;本节解释分发、策略执行与审计路径。
|
|
72
|
+
|
|
73
|
+
### 源码地图
|
|
74
|
+
|
|
75
|
+
| 文件 | 职责 |
|
|
76
|
+
|---|---|
|
|
77
|
+
| [`src/index.ts`](src/index.ts) | `ApprovalService`:请求分发、私有审批状态、策略写入路径、已记住的授权、运行时上下文贡献 |
|
|
78
|
+
| [`src/types.ts`](src/types.ts) | `ApprovalRequestId` brand 与结果类型 |
|
|
79
|
+
|
|
80
|
+
### 分发
|
|
81
|
+
|
|
82
|
+
`decide()` 让应答者 waterfall 与请求信号赛跑,并隔离所有应答者故障:抛出异常的监听器会使问题以 `unavailable` 关闭,不属于结果词汇的异常返回值也会规范化为 `unavailable`。`never` 策略在服务内部、waterfall 分发之前执行,因此之后以 `prepend` 注册的监听器也无法绕过确定性的拒绝。已记住的授权在 `never` 之后、分发之前检查,因此被覆盖的询问绝不会到达应答者。请求必须处于未结束的轮次内,因为审计事件对是持久日志的提交/回放边界——轮次之间的裸事件与崩溃尾部无法区分。
|
|
83
|
+
|
|
84
|
+
### 策略与运行时上下文快照
|
|
85
|
+
|
|
86
|
+
私有的 Host 端 `approvalState` Projection 保存策略覆盖值,以及当前轮次是否仍未结束。其大小不随事件数增长。注册表会将它保存到持久化检查点;事件日志仍是真源。其纯状态更新不会发起问题或授予权限。
|
|
87
|
+
|
|
88
|
+
服务激活会等待 Projection 注册完成;注册失败会使激活失败。服务私有持有返回的读取句柄。宿主和注册表负责会话恢复;审批方法不会在同步读取时加载历史或调用 hydration。参见[注册与恢复 API](../../session/session-projection/README.zh.md)。待处理问题、应答者和授权仍由运行时负责。
|
|
89
|
+
|
|
90
|
+
系统提示词贡献 `approval:policy` 在保留历史之后陈述有效策略的完整当前含义——`ask` 及其以拒绝方式关闭的后果,或 `never` 及其非升权后果——因此切换策略会追加一份新的完整快照,而不会改写稳定的请求头。`setPolicy()` 还会注入一条带来源的用户消息,为下一步宣布变更。
|
|
91
|
+
|
|
92
|
+
### 审计
|
|
93
|
+
|
|
94
|
+
`request()` 先追加携带请求身份与工具的 `approval/asked`,再追加携带最终结果的 `approval/decided`;被已记住授权覆盖的决定还会携带 `remembered: true`。`grant()` 为每个工具追加一次 `approval/grant`。确切追加字段见 [`src/index.ts`](src/index.ts)。这些事件都只写入日志。
|
|
95
|
+
|
|
96
|
+
</details>
|
|
97
|
+
|
|
98
|
+
-----
|
|
99
|
+
|
|
100
|
+
<a id="further-exploration"></a>
|
|
101
|
+
## 进一步探索
|
|
102
|
+
|
|
103
|
+
当包级约定不够用时阅读以下页面。它们从审批词汇逐步进入消费方与设计依据。
|
|
104
|
+
|
|
105
|
+
- [审批子系统参考](../../../docs/subsystems/approval.zh.md)——共享的请求/结果词汇与 `ctx.approval` 的 Cordis 接口面。
|
|
106
|
+
- [审批 seam Agent Note](../../../.agents/notes/implemented/feature/2026-07-06-approval-seam.zh.md)——该 seam 的设计依据。
|
|
107
|
+
- [记住审批授权 Agent Note](../../../.agents/notes/implemented/architecture/2026-10-22-remembered-approval-grants.zh.md)——授权为何是按工具的会话状态,以及 TUI 如何与子代理共享。
|
|
108
|
+
- [沙箱 参考](../../shell/tool-bash/README.zh.md)——沙箱 bash 工具如何为升权重试消费审批。
|
|
109
|
+
- [交互组映射](../README.zh.md)——相邻的权限预设与问答包。
|
|
110
|
+
|
|
111
|
+
-----
|
|
112
|
+
|
|
113
|
+
<a id="model-experience"></a>
|
|
114
|
+
## 模型体验
|
|
115
|
+
|
|
116
|
+
### 当前审批策略上下文
|
|
117
|
+
|
|
118
|
+
#### 模型看到的内容
|
|
119
|
+
|
|
120
|
+
首次请求与有效策略每次变化时,都会在保留的历史后追加一份完整运行时上下文快照。在 `ask` 下,审批上下文内容会说明系统可以咨询已配置的应答者,缺少可用应答者时则以拒绝方式关闭。在 `never` 下,它会说明确定性的拒绝与非升权后果。未变化的请求会保留先前快照,不增加另一条消息。用户切换运行中 Session 的策略时,下一次模型调用还会收到一条切换通知。
|
|
121
|
+
|
|
122
|
+
##### Ask 策略贡献
|
|
123
|
+
|
|
124
|
+
```markdown
|
|
125
|
+
Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
##### Never 策略贡献
|
|
129
|
+
|
|
130
|
+
```markdown
|
|
131
|
+
Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
##### 切换通知
|
|
135
|
+
|
|
136
|
+
```markdown
|
|
137
|
+
The user changed the approval policy from "ask the user for approval" to "never ask the user for approval".
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### Token 影响
|
|
141
|
+
|
|
142
|
+
首次请求和策略实际变化时增加一条简洁的上下文消息;未变化的请求不增加重复的策略 token。
|
|
143
|
+
|
|
144
|
+
#### KV Cache 影响
|
|
145
|
+
|
|
146
|
+
在保留的历史之后仅追加。`ask`/`never` 切换会保留稳定的系统与对话前缀,而不会改写第一条 wire 消息。
|
|
147
|
+
|
|
148
|
+
### 工具结果
|
|
149
|
+
|
|
150
|
+
#### 模型看到的内容
|
|
151
|
+
|
|
152
|
+
`approval/asked` 和 `approval/decided` 只写入日志。模型只会看到发起请求的消费方最终给出的允许、拒绝、取消或不可用工具结果;面向人类的权限 UI 不属于上下文。
|
|
153
|
+
|
|
154
|
+
#### Token 影响
|
|
155
|
+
|
|
156
|
+
不会产生重复的审计 token。拒绝可能以一条简短且会保留的错误信息替换正常工具结果,而允许会保留消费方的普通结果。
|
|
157
|
+
|
|
158
|
+
#### KV Cache 影响
|
|
159
|
+
|
|
160
|
+
仅追加;新出现的可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
|
|
161
|
+
|
|
162
|
+
## 已知限制与延期工作
|
|
163
|
+
|
|
164
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
这些限制说明该 seam 不适用的场景,以及组合时需要特别注意的场景。它们是当前包约束,不是通用权限对比。
|
|
168
|
+
|
|
169
|
+
- **请求只在尚未结束的轮次内有效**:在空闲时或轮次之间发起调用,会在审计前抛出异常;持久化的轮次外审批工作流仍属延期工作。授权是独立的日志条目,可以在轮次外记录。
|
|
170
|
+
- **授权按工具名称生效**:请求不携带参数,因此一项授权覆盖该工具门控的所有操作;不存在按命令、按路径或按调用的授权。
|
|
171
|
+
- **授权没有撤销控制**:授权持续到会话及其存活后代结束;把策略切换为 `never` 会抑制所有询问,但没有 API 删除单项授权。
|
|
172
|
+
- **请求不携带工具参数**:应答者会看到工具名称、原因和可选调用 id;ACP(Agent Client Protocol)机器通道要求调用 id,并会委托不含 id 的请求。
|
|
173
|
+
- **没有内置应答者**:无头或组合不完整的部署会返回 `unavailable` 并以拒绝方式关闭;服务自身绝不会提示人类。
|
|
174
|
+
- **待处理审批仅在进程内有效**:检查点恢复策略和轮次状态,不恢复待处理应答的 Promise 或一次性授权。恢复不会重新提交问题或重复运行时副作用。
|
|
175
|
+
|
|
176
|
+
<a id="dev-note"></a>
|
|
177
|
+
### 开发备注
|
|
178
|
+
|
|
179
|
+
<details>
|
|
180
|
+
<summary>维护者的工作上下文——点击展开</summary>
|
|
181
|
+
|
|
182
|
+
无。
|
|
183
|
+
|
|
184
|
+
</details>
|