@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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Definition for the approval capability seam, covering requests,
|
|
3
|
+
* cancellation, audit, per-session policy, and remembered per-tool grants.
|
|
4
|
+
* Missing answerers fail closed.
|
|
5
|
+
* @module @deepseek-ai/dsh-user-approval
|
|
6
|
+
*/
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
9
|
+
import z from '@deepseek-ai/schemastery';
|
|
10
|
+
import { z as zod } from 'zod';
|
|
11
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
import { scopeTarget } from '@deepseek-ai/dsh-scope';
|
|
13
|
+
import { SessionSeq } from '@deepseek-ai/dsh-session';
|
|
14
|
+
import { ApprovalRequestId } from "./types.js";
|
|
15
|
+
export { ApprovalRequestId } from "./types.js";
|
|
16
|
+
/** Every {@link ApprovalOutcome}, for runtime normalization of answerer returns. */
|
|
17
|
+
const OUTCOMES = ['allowed-once', 'rejected', 'cancelled', 'unavailable'];
|
|
18
|
+
/** Every {@link ApprovalPolicy}, for option advertisement and runtime validation of untrusted policy strings. */
|
|
19
|
+
export const APPROVAL_POLICIES = ['ask', 'never'];
|
|
20
|
+
/** Model-facing statement for the deterministic `'never'` policy. */
|
|
21
|
+
const NEVER_SENTENCE = 'Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).';
|
|
22
|
+
/** Model-facing statement for an interactive policy that may still fail closed. */
|
|
23
|
+
const ASK_SENTENCE = '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.';
|
|
24
|
+
/** Model-facing policy names in switch notices; a bare `never` reads as "approval is never granted". */
|
|
25
|
+
const POLICY_NOTICE_NAMES = {
|
|
26
|
+
ask: 'ask the user for approval',
|
|
27
|
+
never: 'never ask the user for approval',
|
|
28
|
+
};
|
|
29
|
+
const approvalStateDefinition = {
|
|
30
|
+
key: 'approvalState',
|
|
31
|
+
stateVersion: 1,
|
|
32
|
+
stateSchema: zod.object({
|
|
33
|
+
policy: zod.enum(['ask', 'never']).nullable(),
|
|
34
|
+
turnOpen: zod.boolean(),
|
|
35
|
+
}),
|
|
36
|
+
init: () => ({ policy: null, turnOpen: false }),
|
|
37
|
+
apply(state, event) {
|
|
38
|
+
switch (event.type) {
|
|
39
|
+
case 'approval/policy':
|
|
40
|
+
return state.policy === event.data.policy ? state : { ...state, policy: event.data.policy };
|
|
41
|
+
case 'turn/start':
|
|
42
|
+
return state.turnOpen ? state : { ...state, turnOpen: true };
|
|
43
|
+
case 'turn/end':
|
|
44
|
+
return state.turnOpen ? { ...state, turnOpen: false } : state;
|
|
45
|
+
default:
|
|
46
|
+
return state;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Append the sole durable representation of a session policy override. Invalid
|
|
52
|
+
* values throw before the log changes; projected state follows the committed record.
|
|
53
|
+
* @param session - the session the override belongs to.
|
|
54
|
+
* @param policy - the policy in effect until the next switch.
|
|
55
|
+
*/
|
|
56
|
+
export function setApprovalPolicy(session, policy) {
|
|
57
|
+
if (!APPROVAL_POLICIES.includes(policy)) {
|
|
58
|
+
throw new TypeError('approval policy must be one of "ask" or "never"');
|
|
59
|
+
}
|
|
60
|
+
session.append('approval/policy', { policy });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Approval service that applies session policy and remembered per-tool grants
|
|
64
|
+
* before answerers and logs every ask/outcome pair to the requesting session.
|
|
65
|
+
* It exposes deterministic policy changes to the model through the
|
|
66
|
+
* runtime-context snapshot and switch notices.
|
|
67
|
+
*/
|
|
68
|
+
export class ApprovalService extends Service {
|
|
69
|
+
config;
|
|
70
|
+
static inject = ['sessionProjections'];
|
|
71
|
+
static Config = z.object({
|
|
72
|
+
policy: z.union(['ask', 'never']).default('ask'),
|
|
73
|
+
});
|
|
74
|
+
approvalProjectionHandle;
|
|
75
|
+
constructor(ctx, config) {
|
|
76
|
+
super(ctx, 'approval');
|
|
77
|
+
this.config = config;
|
|
78
|
+
}
|
|
79
|
+
async [Service.init]() {
|
|
80
|
+
this.approvalProjectionHandle = await this.ctx.sessionProjections.register(approvalStateDefinition);
|
|
81
|
+
// Registration can finish after an unload has started. Do not add effects then.
|
|
82
|
+
if (this.ctx.fiber.uid === null)
|
|
83
|
+
return;
|
|
84
|
+
const effective = (agent) => this.effectivePolicy(agent.session);
|
|
85
|
+
// The complete current value travels after retained history, so switching
|
|
86
|
+
// policy does not rewrite the stable system-prompt cache prefix.
|
|
87
|
+
await this.ctx.inject(['systemPrompt'], (scope) => {
|
|
88
|
+
scope.systemPrompt.context({
|
|
89
|
+
name: 'approval:policy',
|
|
90
|
+
order: scope.systemPrompt.getContextOrder('APPROVAL_POLICY'),
|
|
91
|
+
text: (context) => {
|
|
92
|
+
const agent = context.agent;
|
|
93
|
+
// A bare assemble() (tests, diagnostics) has no session to state.
|
|
94
|
+
if (agent === undefined)
|
|
95
|
+
return '';
|
|
96
|
+
const policy = effective(agent);
|
|
97
|
+
return policy === 'never' ? NEVER_SENTENCE : ASK_SENTENCE;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Switch one live agent's policy and queue the transition for its next model
|
|
104
|
+
* step. Session initialization uses {@link setApprovalPolicy} directly
|
|
105
|
+
* because there is no previously visible policy to change.
|
|
106
|
+
* @param agent - the live agent whose policy is changing.
|
|
107
|
+
* @param policy - the new effective policy.
|
|
108
|
+
*/
|
|
109
|
+
setPolicy(agent, policy) {
|
|
110
|
+
const previous = this.effectivePolicy(agent.session);
|
|
111
|
+
if (previous === policy)
|
|
112
|
+
return;
|
|
113
|
+
setApprovalPolicy(agent.session, policy);
|
|
114
|
+
agent.inject(createUserMessage({
|
|
115
|
+
content: [{
|
|
116
|
+
type: 'text',
|
|
117
|
+
text: `The user changed the approval policy from "${POLICY_NOTICE_NAMES[previous]}" to "${POLICY_NOTICE_NAMES[policy]}".`,
|
|
118
|
+
}],
|
|
119
|
+
source: { kind: 'user-approval' },
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Ask the composed answerers to decide one readonly same-process request.
|
|
124
|
+
* The service borrows the request, agent, session, and live signal directly.
|
|
125
|
+
* A remembered grant for the tool resolves the ask `allowed-once` before any
|
|
126
|
+
* answerer sees it. The request requires an open turn because the audit pair
|
|
127
|
+
* must be enclosed by the durable log's commit/replay boundary; an idle ask
|
|
128
|
+
* rejects before appending anything. The answerer phase always produces an
|
|
129
|
+
* outcome: an aborted signal yields `'cancelled'`, a missing or throwing answerer yields
|
|
130
|
+
* `'unavailable'` (fail closed), and a rogue non-vocabulary return value is
|
|
131
|
+
* normalized to `'unavailable'`. A failure that prevents either audit append
|
|
132
|
+
* from committing still rejects because returning an unlogged decision would
|
|
133
|
+
* violate the pair. Session contains post-commit observer failures, so an
|
|
134
|
+
* authoritative append cannot reject the request or suppress its matching
|
|
135
|
+
* audit event.
|
|
136
|
+
* @param req - the pending decision (agent, tool identity, reason, signal).
|
|
137
|
+
* @returns the closed outcome; `'allowed-once'` is the only grant.
|
|
138
|
+
* @throws when no turn is open or either audit event fails before the session
|
|
139
|
+
* append commit point.
|
|
140
|
+
*/
|
|
141
|
+
async request(req) {
|
|
142
|
+
const session = req.agent.session;
|
|
143
|
+
if (!this.approvalStateOf(session).turnOpen) {
|
|
144
|
+
throw new Error('approval.request() outside an open turn: the approval/asked + approval/decided audit pair '
|
|
145
|
+
+ 'must be turn-enclosed (a bare event between turns is crash-tail garbage on reload). '
|
|
146
|
+
+ 'Ask from inside the turn that needs the decision.');
|
|
147
|
+
}
|
|
148
|
+
const id = ApprovalRequestId(randomUUID());
|
|
149
|
+
session.append('approval/asked', {
|
|
150
|
+
id,
|
|
151
|
+
toolName: req.toolName,
|
|
152
|
+
...req.callId !== undefined ? { callId: req.callId } : {},
|
|
153
|
+
...req.reason !== undefined ? { reason: req.reason } : {},
|
|
154
|
+
});
|
|
155
|
+
const outcome = await this.decide(req, session);
|
|
156
|
+
// A grant recorded while the answerer was deciding (the TUI's Always
|
|
157
|
+
// allow) covers this request too: mark the decision so the transcript and
|
|
158
|
+
// watch viewer can show that the tool will not prompt again.
|
|
159
|
+
const remembered = outcome === 'allowed-once' && this.isGranted(req.agent, req.toolName);
|
|
160
|
+
session.append('approval/decided', { id, outcome, ...remembered ? { remembered: true } : {} });
|
|
161
|
+
return outcome;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Remember one tool's approval for a session and its live descendants. Every
|
|
165
|
+
* later ask for that tool resolves `allowed-once` without consulting an
|
|
166
|
+
* answerer while the effective policy is `ask`; `never` still rejects first.
|
|
167
|
+
* Recording nothing when the grant already exists keeps replay and repeated
|
|
168
|
+
* Always allow choices from growing the log.
|
|
169
|
+
* @param session - the session whose subtree the grant covers.
|
|
170
|
+
* @param toolName - the tool whose future asks the grant covers.
|
|
171
|
+
* @throws when the tool name is empty.
|
|
172
|
+
*/
|
|
173
|
+
grant(session, toolName) {
|
|
174
|
+
if (toolName.trim() === '') {
|
|
175
|
+
throw new TypeError('approval grant requires a non-empty tool name');
|
|
176
|
+
}
|
|
177
|
+
if (this.grantedIn(session, toolName))
|
|
178
|
+
return;
|
|
179
|
+
session.append('approval/grant', { toolName });
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Whether a remembered grant covers this Agent's ask for the tool. The walk
|
|
183
|
+
* reads the Agent's session, then each live ancestor through `parentSession`;
|
|
184
|
+
* a vanished ancestor ends it, so an orphaned child never inherits a grant it
|
|
185
|
+
* cannot prove.
|
|
186
|
+
* @param agent - the asking Agent.
|
|
187
|
+
* @param toolName - the tool being asked about.
|
|
188
|
+
* @returns true when the Agent or a live ancestor holds the grant.
|
|
189
|
+
*/
|
|
190
|
+
isGranted(agent, toolName) {
|
|
191
|
+
let current = agent;
|
|
192
|
+
for (;;) {
|
|
193
|
+
if (this.grantedIn(current.session, toolName))
|
|
194
|
+
return true;
|
|
195
|
+
const parentId = current.session.header.parentSession;
|
|
196
|
+
if (parentId === undefined)
|
|
197
|
+
return false;
|
|
198
|
+
const parent = this.ctx.get('agents')?.get(parentId);
|
|
199
|
+
if (parent === undefined)
|
|
200
|
+
return false;
|
|
201
|
+
current = parent;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Read one session's own grants without walking its lineage.
|
|
206
|
+
* @param session - session whose log supplies the grants.
|
|
207
|
+
* @param toolName - tool whose grant is sought.
|
|
208
|
+
* @returns whether this session recorded a grant for the tool.
|
|
209
|
+
*/
|
|
210
|
+
grantedIn(session, toolName) {
|
|
211
|
+
for (let seq = session.seq - 1; seq >= 0; seq -= 1) {
|
|
212
|
+
// oxlint-disable-next-line typescript/no-deprecated -- Existing Session history read; migration deferred.
|
|
213
|
+
const event = session.eventAt(SessionSeq(seq));
|
|
214
|
+
if (event?.type === 'approval/grant' && event.data.toolName === toolName)
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The session's effective policy: its projected override, else the
|
|
221
|
+
* configured default (the schema already defaulted an omitted policy to
|
|
222
|
+
* `'ask'`; the `??` only narrows the optional-input TYPE).
|
|
223
|
+
* @param session - the exact accepted session whose policy applies.
|
|
224
|
+
* @returns the policy every ask for this session resolves under right now.
|
|
225
|
+
*/
|
|
226
|
+
effectivePolicy(session) {
|
|
227
|
+
return this.overrideOf(session) ?? this.config.policy ?? 'ask';
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Read the session override without applying the configured default.
|
|
231
|
+
* @param session - session whose log supplies the override.
|
|
232
|
+
* @returns the last logged policy, or `undefined` without one.
|
|
233
|
+
*/
|
|
234
|
+
overrideOf(session) {
|
|
235
|
+
return this.approvalStateOf(session).policy ?? undefined;
|
|
236
|
+
}
|
|
237
|
+
approvalStateOf(session) {
|
|
238
|
+
return this.approvalProjectionHandle.stateOf(session);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Dispatch the waterfall, contained and raced against the request signal.
|
|
242
|
+
* @param req - the borrowed public request.
|
|
243
|
+
* @param session - the request agent's session used for policy lookup.
|
|
244
|
+
* @returns the normalized closed outcome.
|
|
245
|
+
*/
|
|
246
|
+
async decide(req, session) {
|
|
247
|
+
const signal = req.signal;
|
|
248
|
+
if (signal?.aborted)
|
|
249
|
+
return 'cancelled';
|
|
250
|
+
// The 'never' policy is decided HERE, before any dispatch: a listener
|
|
251
|
+
// registered with `prepend: true` after this service mounts would sit
|
|
252
|
+
// ahead of any gate LISTENER, so a listener-shaped gate cannot keep the
|
|
253
|
+
// documented promise that 'never' rejects deterministically regardless
|
|
254
|
+
// of registration order — only the service's own request path can.
|
|
255
|
+
if (this.effectivePolicy(session) === 'never')
|
|
256
|
+
return 'rejected';
|
|
257
|
+
// A remembered grant is part of the session policy: it decides before any
|
|
258
|
+
// answerer so a UI that already answered Always allow never sees the ask.
|
|
259
|
+
if (this.isGranted(req.agent, req.toolName))
|
|
260
|
+
return 'allowed-once';
|
|
261
|
+
// Enter the promise chain BEFORE dispatching: a listener that throws
|
|
262
|
+
// SYNCHRONOUSLY (before its first await) must land in the same rejection
|
|
263
|
+
// path as an async one — `Promise.resolve(call())` would let it escape
|
|
264
|
+
// the containment into the caller.
|
|
265
|
+
const answer = Promise.resolve().then(() => this.ctx.waterfall(scopeTarget(req.agent, req.agent), 'approval/request', req, () => Promise.resolve('unavailable'))).then(
|
|
266
|
+
// Normalize a rogue (non-vocabulary) answerer return to the fail-closed
|
|
267
|
+
// outcome instead of leaking it into callers' closed-union switches.
|
|
268
|
+
outcome => OUTCOMES.includes(outcome) ? outcome : 'unavailable',
|
|
269
|
+
// A throwing answerer must fail the QUESTION closed, not the caller's
|
|
270
|
+
// tool call open — the seam contains its callbacks.
|
|
271
|
+
() => 'unavailable');
|
|
272
|
+
if (signal === undefined)
|
|
273
|
+
return answer;
|
|
274
|
+
return await new Promise((resolve) => {
|
|
275
|
+
const onAbort = () => {
|
|
276
|
+
signal.removeEventListener('abort', onAbort);
|
|
277
|
+
resolve('cancelled');
|
|
278
|
+
};
|
|
279
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
280
|
+
void answer.then((outcome) => {
|
|
281
|
+
signal.removeEventListener('abort', onAbort);
|
|
282
|
+
// After an abort won the race this resolve is a settled-promise no-op:
|
|
283
|
+
// the late answer is discarded by construction.
|
|
284
|
+
resolve(outcome);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export default ApprovalService;
|
|
290
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-safe approval identifiers and outcome vocabulary, free of
|
|
3
|
+
* cordis/service imports so browser type chains can
|
|
4
|
+
* consume them without loading this package's Context augmentation.
|
|
5
|
+
* @module @deepseek-ai/dsh-user-approval/types
|
|
6
|
+
*/
|
|
7
|
+
import type { Branded } from '@deepseek-ai/dsh-brand';
|
|
8
|
+
import type { Scoped } from '@deepseek-ai/dsh-scope';
|
|
9
|
+
import type { Agent } from '@deepseek-ai/dsh-agent/types';
|
|
10
|
+
import type { ToolCallId } from '@deepseek-ai/dsh-llm/brand';
|
|
11
|
+
/**
|
|
12
|
+
* Pairs one `approval/asked` audit event with its `approval/decided`.
|
|
13
|
+
* Service-issued (one fresh id per {@link ApprovalService.request} call).
|
|
14
|
+
*/
|
|
15
|
+
export type ApprovalRequestId = Branded<'ApprovalRequestId'>;
|
|
16
|
+
/**
|
|
17
|
+
* Brand a string as an {@link ApprovalRequestId}.
|
|
18
|
+
* @param id - the raw id string to brand.
|
|
19
|
+
* @returns the same string carrying the brand.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ApprovalRequestId(id: string): ApprovalRequestId;
|
|
22
|
+
/**
|
|
23
|
+
* Closed approval outcomes: a one-shot grant, explicit rejection, withdrawn
|
|
24
|
+
* request, or unavailable answerer. Callers fail closed on `unavailable`.
|
|
25
|
+
*/
|
|
26
|
+
export type ApprovalOutcome = 'allowed-once' | 'rejected' | 'cancelled' | 'unavailable';
|
|
27
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
28
|
+
interface SessionEventMap {
|
|
29
|
+
/**
|
|
30
|
+
* The session remembered one tool's approval — log-only state, not a
|
|
31
|
+
* surface event. Later asks for the same tool from this session or a live
|
|
32
|
+
* descendant resolve `allowed-once` without consulting an answerer while
|
|
33
|
+
* the effective policy is `ask`. Absence means no remembered grant.
|
|
34
|
+
*/
|
|
35
|
+
'approval/grant': {
|
|
36
|
+
toolName: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* An approval question was put to the answerer chain — log-only audit
|
|
40
|
+
* (like `hook/*`; NOT a surface event, carries no `surfaceOp`). `id` pairs
|
|
41
|
+
* it with the `approval/decided` that always follows; `toolName` is the
|
|
42
|
+
* tool the question is about, `callId` the exact tool call when the asker
|
|
43
|
+
* had one, `reason` the asker's human-readable explanation (e.g. a hook's
|
|
44
|
+
* permission-decision reason).
|
|
45
|
+
*/
|
|
46
|
+
'approval/asked': {
|
|
47
|
+
id: ApprovalRequestId;
|
|
48
|
+
toolName: string;
|
|
49
|
+
callId?: ToolCallId;
|
|
50
|
+
reason?: string;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The outcome of a prior `approval/asked` (same `id`) — log-only audit.
|
|
54
|
+
* Exactly one per ask, appended when the outcome is known: a decision, a
|
|
55
|
+
* cancellation, or the fail-closed `'unavailable'`.
|
|
56
|
+
*/
|
|
57
|
+
'approval/decided': {
|
|
58
|
+
id: ApprovalRequestId;
|
|
59
|
+
outcome: ApprovalOutcome;
|
|
60
|
+
/**
|
|
61
|
+
* The `allowed-once` outcome was covered by a remembered grant for the
|
|
62
|
+
* asked-about tool, so the same session would allow it again without a
|
|
63
|
+
* prompt. One-shot grants and non-grants omit this field.
|
|
64
|
+
*/
|
|
65
|
+
remembered?: true;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** Client-safe payload declared for the approval answerer waterfall. */
|
|
70
|
+
export interface ApprovalRequestEvent {
|
|
71
|
+
/** Agent identity projected to the corresponding Client Context in transit. */
|
|
72
|
+
readonly agent: Agent;
|
|
73
|
+
/** Tool whose operation requires a decision. */
|
|
74
|
+
readonly toolName: string;
|
|
75
|
+
/** Exact tool call being decided, when available. */
|
|
76
|
+
readonly callId?: ToolCallId;
|
|
77
|
+
/** Human-readable reason supplied by the asker. */
|
|
78
|
+
readonly reason?: string;
|
|
79
|
+
/** Localized presentation only; never persisted in approval audit events. */
|
|
80
|
+
readonly displayReason?: {
|
|
81
|
+
readonly en: string;
|
|
82
|
+
readonly [locale: string]: string;
|
|
83
|
+
};
|
|
84
|
+
/** Cancellation lifetime of the pending request. */
|
|
85
|
+
readonly signal?: AbortSignal;
|
|
86
|
+
}
|
|
87
|
+
declare module '@deepseek-ai/cordis' {
|
|
88
|
+
interface Events {
|
|
89
|
+
/**
|
|
90
|
+
* Ask composed answerers for one decision. Return an outcome to claim the
|
|
91
|
+
* request or call `next()` to delegate. Scope-filtered dispatch
|
|
92
|
+
* (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
|
|
93
|
+
* @param req - pending approval request.
|
|
94
|
+
* @mode waterfall
|
|
95
|
+
*/
|
|
96
|
+
'approval/request'(this: Scoped<Agent>, req: ApprovalRequestEvent, next: () => Promise<ApprovalOutcome>): Promise<ApprovalOutcome>;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire-safe approval identifiers and outcome vocabulary, free of
|
|
3
|
+
* cordis/service imports so browser type chains can
|
|
4
|
+
* consume them without loading this package's Context augmentation.
|
|
5
|
+
* @module @deepseek-ai/dsh-user-approval/types
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Brand a string as an {@link ApprovalRequestId}.
|
|
9
|
+
* @param id - the raw id string to brand.
|
|
10
|
+
* @returns the same string carrying the brand.
|
|
11
|
+
*/
|
|
12
|
+
export function ApprovalRequestId(id) {
|
|
13
|
+
return id;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=types.js.map
|
package/locale/de.json
ADDED
package/locale/en.json
ADDED
package/locale/es.json
ADDED
package/locale/fr.json
ADDED
package/locale/ja.json
ADDED
package/locale/pt.json
ADDED
package/locale/ru.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishwithcat/dsh-user-approval",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"description": "User-approval seam (ctx.approval) for the DeepSeek Harness: one-shot permission decisions dispatched to composed answerers over the approval/request waterfall, fail-closed by default",
|
|
4
|
+
"version": "0.2.2-alpha.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/FishwithCat/deepseek-harness-tui",
|
|
11
|
+
"directory": "packages/interaction/user-approval"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./types": {
|
|
22
|
+
"types": "./lib/types/types.d.ts",
|
|
23
|
+
"default": "./lib/types/types.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json",
|
|
27
|
+
"./locale/*.json": "./locale/*.json"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"locale/*.json",
|
|
31
|
+
"lib/index.js",
|
|
32
|
+
"lib/types/**/*.js",
|
|
33
|
+
"lib/types/**/*.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
|
|
38
|
+
"@deepseek-ai/dsh-brand": "0.2.2-alpha.3",
|
|
39
|
+
"@deepseek-ai/dsh-llm": "0.2.2-alpha.3",
|
|
40
|
+
"@deepseek-ai/dsh-scope": "0.2.2-alpha.3",
|
|
41
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.3",
|
|
42
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.3",
|
|
43
|
+
"@deepseek-ai/cordis": "~4.0.6-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-session-projection": "0.2.2-alpha.3"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@deepseek-ai/schemastery": "~3.18.6-alpha.2",
|
|
48
|
+
"zod": "4.4.3"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
|
|
52
|
+
"@deepseek-ai/dsh-brand": "0.2.2-alpha.3",
|
|
53
|
+
"@deepseek-ai/dsh-llm": "0.2.2-alpha.3",
|
|
54
|
+
"@deepseek-ai/dsh-scope": "0.2.2-alpha.3",
|
|
55
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.3",
|
|
56
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.3",
|
|
57
|
+
"@deepseek-ai/cordis": "~4.0.6-alpha.2",
|
|
58
|
+
"@deepseek-ai/dsh-session-projection": "0.2.2-alpha.3"
|
|
59
|
+
}
|
|
60
|
+
}
|