@fishwithcat/dsh-user-approval 0.0.0-stage → 0.2.2-alpha.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/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/lib/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
3
|
+
import z from "@deepseek-ai/schemastery";
|
|
4
|
+
import { z as z$1 } from "zod";
|
|
5
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
6
|
+
import { scopeTarget } from "@deepseek-ai/dsh-scope";
|
|
7
|
+
import { SessionSeq } from "@deepseek-ai/dsh-session";
|
|
8
|
+
//#region lib/types/types.js
|
|
9
|
+
/**
|
|
10
|
+
* Wire-safe approval identifiers and outcome vocabulary, free of
|
|
11
|
+
* cordis/service imports so browser type chains can
|
|
12
|
+
* consume them without loading this package's Context augmentation.
|
|
13
|
+
* @module @deepseek-ai/dsh-user-approval/types
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Brand a string as an {@link ApprovalRequestId}.
|
|
17
|
+
* @param id - the raw id string to brand.
|
|
18
|
+
* @returns the same string carrying the brand.
|
|
19
|
+
*/
|
|
20
|
+
function ApprovalRequestId(id) {
|
|
21
|
+
return id;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region lib/types/index.js
|
|
25
|
+
/**
|
|
26
|
+
* Service Definition for the approval capability seam, covering requests,
|
|
27
|
+
* cancellation, audit, per-session policy, and remembered per-tool grants.
|
|
28
|
+
* Missing answerers fail closed.
|
|
29
|
+
* @module @deepseek-ai/dsh-user-approval
|
|
30
|
+
*/
|
|
31
|
+
/** Every {@link ApprovalOutcome}, for runtime normalization of answerer returns. */
|
|
32
|
+
const OUTCOMES = [
|
|
33
|
+
"allowed-once",
|
|
34
|
+
"rejected",
|
|
35
|
+
"cancelled",
|
|
36
|
+
"unavailable"
|
|
37
|
+
];
|
|
38
|
+
/** Every {@link ApprovalPolicy}, for option advertisement and runtime validation of untrusted policy strings. */
|
|
39
|
+
const APPROVAL_POLICIES = ["ask", "never"];
|
|
40
|
+
/** Model-facing statement for the deterministic `'never'` policy. */
|
|
41
|
+
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`).";
|
|
42
|
+
/** Model-facing statement for an interactive policy that may still fail closed. */
|
|
43
|
+
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.";
|
|
44
|
+
/** Model-facing policy names in switch notices; a bare `never` reads as "approval is never granted". */
|
|
45
|
+
const POLICY_NOTICE_NAMES = {
|
|
46
|
+
ask: "ask the user for approval",
|
|
47
|
+
never: "never ask the user for approval"
|
|
48
|
+
};
|
|
49
|
+
const approvalStateDefinition = {
|
|
50
|
+
key: "approvalState",
|
|
51
|
+
stateVersion: 1,
|
|
52
|
+
stateSchema: z$1.object({
|
|
53
|
+
policy: z$1.enum(["ask", "never"]).nullable(),
|
|
54
|
+
turnOpen: z$1.boolean()
|
|
55
|
+
}),
|
|
56
|
+
init: () => ({
|
|
57
|
+
policy: null,
|
|
58
|
+
turnOpen: false
|
|
59
|
+
}),
|
|
60
|
+
apply(state, event) {
|
|
61
|
+
switch (event.type) {
|
|
62
|
+
case "approval/policy": return state.policy === event.data.policy ? state : {
|
|
63
|
+
...state,
|
|
64
|
+
policy: event.data.policy
|
|
65
|
+
};
|
|
66
|
+
case "turn/start": return state.turnOpen ? state : {
|
|
67
|
+
...state,
|
|
68
|
+
turnOpen: true
|
|
69
|
+
};
|
|
70
|
+
case "turn/end": return state.turnOpen ? {
|
|
71
|
+
...state,
|
|
72
|
+
turnOpen: false
|
|
73
|
+
} : state;
|
|
74
|
+
default: return state;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Append the sole durable representation of a session policy override. Invalid
|
|
80
|
+
* values throw before the log changes; projected state follows the committed record.
|
|
81
|
+
* @param session - the session the override belongs to.
|
|
82
|
+
* @param policy - the policy in effect until the next switch.
|
|
83
|
+
*/
|
|
84
|
+
function setApprovalPolicy(session, policy) {
|
|
85
|
+
if (!APPROVAL_POLICIES.includes(policy)) throw new TypeError("approval policy must be one of \"ask\" or \"never\"");
|
|
86
|
+
session.append("approval/policy", { policy });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Approval service that applies session policy and remembered per-tool grants
|
|
90
|
+
* before answerers and logs every ask/outcome pair to the requesting session.
|
|
91
|
+
* It exposes deterministic policy changes to the model through the
|
|
92
|
+
* runtime-context snapshot and switch notices.
|
|
93
|
+
*/
|
|
94
|
+
var ApprovalService = class extends Service {
|
|
95
|
+
config;
|
|
96
|
+
static inject = ["sessionProjections"];
|
|
97
|
+
static Config = z.object({ policy: z.union(["ask", "never"]).default("ask") });
|
|
98
|
+
approvalProjectionHandle;
|
|
99
|
+
constructor(ctx, config) {
|
|
100
|
+
super(ctx, "approval");
|
|
101
|
+
this.config = config;
|
|
102
|
+
}
|
|
103
|
+
async [Service.init]() {
|
|
104
|
+
this.approvalProjectionHandle = await this.ctx.sessionProjections.register(approvalStateDefinition);
|
|
105
|
+
if (this.ctx.fiber.uid === null) return;
|
|
106
|
+
const effective = (agent) => this.effectivePolicy(agent.session);
|
|
107
|
+
await this.ctx.inject(["systemPrompt"], (scope) => {
|
|
108
|
+
scope.systemPrompt.context({
|
|
109
|
+
name: "approval:policy",
|
|
110
|
+
order: scope.systemPrompt.getContextOrder("APPROVAL_POLICY"),
|
|
111
|
+
text: (context) => {
|
|
112
|
+
const agent = context.agent;
|
|
113
|
+
if (agent === void 0) return "";
|
|
114
|
+
return effective(agent) === "never" ? NEVER_SENTENCE : ASK_SENTENCE;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Switch one live agent's policy and queue the transition for its next model
|
|
121
|
+
* step. Session initialization uses {@link setApprovalPolicy} directly
|
|
122
|
+
* because there is no previously visible policy to change.
|
|
123
|
+
* @param agent - the live agent whose policy is changing.
|
|
124
|
+
* @param policy - the new effective policy.
|
|
125
|
+
*/
|
|
126
|
+
setPolicy(agent, policy) {
|
|
127
|
+
const previous = this.effectivePolicy(agent.session);
|
|
128
|
+
if (previous === policy) return;
|
|
129
|
+
setApprovalPolicy(agent.session, policy);
|
|
130
|
+
agent.inject(createUserMessage({
|
|
131
|
+
content: [{
|
|
132
|
+
type: "text",
|
|
133
|
+
text: `The user changed the approval policy from "${POLICY_NOTICE_NAMES[previous]}" to "${POLICY_NOTICE_NAMES[policy]}".`
|
|
134
|
+
}],
|
|
135
|
+
source: { kind: "user-approval" }
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Ask the composed answerers to decide one readonly same-process request.
|
|
140
|
+
* The service borrows the request, agent, session, and live signal directly.
|
|
141
|
+
* A remembered grant for the tool resolves the ask `allowed-once` before any
|
|
142
|
+
* answerer sees it. The request requires an open turn because the audit pair
|
|
143
|
+
* must be enclosed by the durable log's commit/replay boundary; an idle ask
|
|
144
|
+
* rejects before appending anything. The answerer phase always produces an
|
|
145
|
+
* outcome: an aborted signal yields `'cancelled'`, a missing or throwing answerer yields
|
|
146
|
+
* `'unavailable'` (fail closed), and a rogue non-vocabulary return value is
|
|
147
|
+
* normalized to `'unavailable'`. A failure that prevents either audit append
|
|
148
|
+
* from committing still rejects because returning an unlogged decision would
|
|
149
|
+
* violate the pair. Session contains post-commit observer failures, so an
|
|
150
|
+
* authoritative append cannot reject the request or suppress its matching
|
|
151
|
+
* audit event.
|
|
152
|
+
* @param req - the pending decision (agent, tool identity, reason, signal).
|
|
153
|
+
* @returns the closed outcome; `'allowed-once'` is the only grant.
|
|
154
|
+
* @throws when no turn is open or either audit event fails before the session
|
|
155
|
+
* append commit point.
|
|
156
|
+
*/
|
|
157
|
+
async request(req) {
|
|
158
|
+
const session = req.agent.session;
|
|
159
|
+
if (!this.approvalStateOf(session).turnOpen) throw new Error("approval.request() outside an open turn: the approval/asked + approval/decided audit pair must be turn-enclosed (a bare event between turns is crash-tail garbage on reload). Ask from inside the turn that needs the decision.");
|
|
160
|
+
const id = ApprovalRequestId(randomUUID());
|
|
161
|
+
session.append("approval/asked", {
|
|
162
|
+
id,
|
|
163
|
+
toolName: req.toolName,
|
|
164
|
+
...req.callId !== void 0 ? { callId: req.callId } : {},
|
|
165
|
+
...req.reason !== void 0 ? { reason: req.reason } : {}
|
|
166
|
+
});
|
|
167
|
+
const outcome = await this.decide(req, session);
|
|
168
|
+
const remembered = outcome === "allowed-once" && this.isGranted(req.agent, req.toolName);
|
|
169
|
+
session.append("approval/decided", {
|
|
170
|
+
id,
|
|
171
|
+
outcome,
|
|
172
|
+
...remembered ? { remembered: true } : {}
|
|
173
|
+
});
|
|
174
|
+
return outcome;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Remember one tool's approval for a session and its live descendants. Every
|
|
178
|
+
* later ask for that tool resolves `allowed-once` without consulting an
|
|
179
|
+
* answerer while the effective policy is `ask`; `never` still rejects first.
|
|
180
|
+
* Recording nothing when the grant already exists keeps replay and repeated
|
|
181
|
+
* Always allow choices from growing the log.
|
|
182
|
+
* @param session - the session whose subtree the grant covers.
|
|
183
|
+
* @param toolName - the tool whose future asks the grant covers.
|
|
184
|
+
* @throws when the tool name is empty.
|
|
185
|
+
*/
|
|
186
|
+
grant(session, toolName) {
|
|
187
|
+
if (toolName.trim() === "") throw new TypeError("approval grant requires a non-empty tool name");
|
|
188
|
+
if (this.grantedIn(session, toolName)) return;
|
|
189
|
+
session.append("approval/grant", { toolName });
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Whether a remembered grant covers this Agent's ask for the tool. The walk
|
|
193
|
+
* reads the Agent's session, then each live ancestor through `parentSession`;
|
|
194
|
+
* a vanished ancestor ends it, so an orphaned child never inherits a grant it
|
|
195
|
+
* cannot prove.
|
|
196
|
+
* @param agent - the asking Agent.
|
|
197
|
+
* @param toolName - the tool being asked about.
|
|
198
|
+
* @returns true when the Agent or a live ancestor holds the grant.
|
|
199
|
+
*/
|
|
200
|
+
isGranted(agent, toolName) {
|
|
201
|
+
let current = agent;
|
|
202
|
+
for (;;) {
|
|
203
|
+
if (this.grantedIn(current.session, toolName)) return true;
|
|
204
|
+
const parentId = current.session.header.parentSession;
|
|
205
|
+
if (parentId === void 0) return false;
|
|
206
|
+
const parent = this.ctx.get("agents")?.get(parentId);
|
|
207
|
+
if (parent === void 0) return false;
|
|
208
|
+
current = parent;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Read one session's own grants without walking its lineage.
|
|
213
|
+
* @param session - session whose log supplies the grants.
|
|
214
|
+
* @param toolName - tool whose grant is sought.
|
|
215
|
+
* @returns whether this session recorded a grant for the tool.
|
|
216
|
+
*/
|
|
217
|
+
grantedIn(session, toolName) {
|
|
218
|
+
for (let seq = session.seq - 1; seq >= 0; seq -= 1) {
|
|
219
|
+
const event = session.eventAt(SessionSeq(seq));
|
|
220
|
+
if (event?.type === "approval/grant" && event.data.toolName === toolName) return true;
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* The session's effective policy: its projected override, else the
|
|
226
|
+
* configured default (the schema already defaulted an omitted policy to
|
|
227
|
+
* `'ask'`; the `??` only narrows the optional-input TYPE).
|
|
228
|
+
* @param session - the exact accepted session whose policy applies.
|
|
229
|
+
* @returns the policy every ask for this session resolves under right now.
|
|
230
|
+
*/
|
|
231
|
+
effectivePolicy(session) {
|
|
232
|
+
return this.overrideOf(session) ?? this.config.policy ?? "ask";
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Read the session override without applying the configured default.
|
|
236
|
+
* @param session - session whose log supplies the override.
|
|
237
|
+
* @returns the last logged policy, or `undefined` without one.
|
|
238
|
+
*/
|
|
239
|
+
overrideOf(session) {
|
|
240
|
+
return this.approvalStateOf(session).policy ?? void 0;
|
|
241
|
+
}
|
|
242
|
+
approvalStateOf(session) {
|
|
243
|
+
return this.approvalProjectionHandle.stateOf(session);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Dispatch the waterfall, contained and raced against the request signal.
|
|
247
|
+
* @param req - the borrowed public request.
|
|
248
|
+
* @param session - the request agent's session used for policy lookup.
|
|
249
|
+
* @returns the normalized closed outcome.
|
|
250
|
+
*/
|
|
251
|
+
async decide(req, session) {
|
|
252
|
+
const signal = req.signal;
|
|
253
|
+
if (signal?.aborted) return "cancelled";
|
|
254
|
+
if (this.effectivePolicy(session) === "never") return "rejected";
|
|
255
|
+
if (this.isGranted(req.agent, req.toolName)) return "allowed-once";
|
|
256
|
+
const answer = Promise.resolve().then(() => this.ctx.waterfall(scopeTarget(req.agent, req.agent), "approval/request", req, () => Promise.resolve("unavailable"))).then((outcome) => OUTCOMES.includes(outcome) ? outcome : "unavailable", () => "unavailable");
|
|
257
|
+
if (signal === void 0) return answer;
|
|
258
|
+
return await new Promise((resolve) => {
|
|
259
|
+
const onAbort = () => {
|
|
260
|
+
signal.removeEventListener("abort", onAbort);
|
|
261
|
+
resolve("cancelled");
|
|
262
|
+
};
|
|
263
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
264
|
+
answer.then((outcome) => {
|
|
265
|
+
signal.removeEventListener("abort", onAbort);
|
|
266
|
+
resolve(outcome);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
//#endregion
|
|
272
|
+
export { APPROVAL_POLICIES, ApprovalRequestId, ApprovalService, ApprovalService as default, setApprovalPolicy };
|
|
@@ -0,0 +1,194 @@
|
|
|
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 { Context, Service } from '@deepseek-ai/cordis';
|
|
8
|
+
import z from '@deepseek-ai/schemastery';
|
|
9
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
10
|
+
import { type ToolCallId } from '@deepseek-ai/dsh-llm';
|
|
11
|
+
import type { ContextFormed } from '@deepseek-ai/dsh-llm';
|
|
12
|
+
declare module '@deepseek-ai/dsh-llm' {
|
|
13
|
+
interface MessageSourceMap {
|
|
14
|
+
'user-approval': {
|
|
15
|
+
kind: 'user-approval';
|
|
16
|
+
} & ContextFormed;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
import { type Session } from '@deepseek-ai/dsh-session';
|
|
20
|
+
declare module '@deepseek-ai/cordis' {
|
|
21
|
+
interface Context {
|
|
22
|
+
approval: ApprovalService;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
declare module '@deepseek-ai/dsh-session/types' {
|
|
26
|
+
interface SessionEventMap {
|
|
27
|
+
/**
|
|
28
|
+
* The session's approval policy was switched — log-only, durable,
|
|
29
|
+
* replayable, never in the model transcript (the model learns the policy
|
|
30
|
+
* from the runtime-context snapshot and live switch notices). The LAST
|
|
31
|
+
* such event is the session's override.
|
|
32
|
+
* `source: 'delegation'` marks an override seeded into a child; an absent
|
|
33
|
+
* source is a runtime switch.
|
|
34
|
+
*/
|
|
35
|
+
'approval/policy': {
|
|
36
|
+
policy: ApprovalPolicy;
|
|
37
|
+
/** Marks an override seeded into a child at delegation. */
|
|
38
|
+
source?: 'delegation';
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
import type { ApprovalOutcome, ApprovalRequestEvent } from './types.ts';
|
|
43
|
+
export { ApprovalRequestId } from './types.ts';
|
|
44
|
+
export type { ApprovalOutcome } from './types.ts';
|
|
45
|
+
/**
|
|
46
|
+
* A session's approval policy — what happens to an {@link ApprovalService}
|
|
47
|
+
* ask BEFORE any interactive answerer sees it:
|
|
48
|
+
*
|
|
49
|
+
* - `'ask'` (the default) — delegate to the composed answerers; with none
|
|
50
|
+
* composed the chain falls through to the fail-closed `'unavailable'`.
|
|
51
|
+
* - `'never'` — never prompt anyone: every ask resolves `'rejected'`
|
|
52
|
+
* deterministically. The strict headless stance (CI, unattended runs) and
|
|
53
|
+
* the policy whose outcome is knowable without asking.
|
|
54
|
+
*/
|
|
55
|
+
export type ApprovalPolicy = 'ask' | 'never';
|
|
56
|
+
/** Every {@link ApprovalPolicy}, for option advertisement and runtime validation of untrusted policy strings. */
|
|
57
|
+
export declare const APPROVAL_POLICIES: readonly ApprovalPolicy[];
|
|
58
|
+
/**
|
|
59
|
+
* Append the sole durable representation of a session policy override. Invalid
|
|
60
|
+
* values throw before the log changes; projected state follows the committed record.
|
|
61
|
+
* @param session - the session the override belongs to.
|
|
62
|
+
* @param policy - the policy in effect until the next switch.
|
|
63
|
+
*/
|
|
64
|
+
export declare function setApprovalPolicy(session: Session, policy: ApprovalPolicy): void;
|
|
65
|
+
/**
|
|
66
|
+
* Readonly same-process permission question. `callId` links to an already
|
|
67
|
+
* presented tool call, so arguments are not duplicated here.
|
|
68
|
+
*/
|
|
69
|
+
export interface ApprovalRequest extends ApprovalRequestEvent {
|
|
70
|
+
/**
|
|
71
|
+
* The agent on whose behalf the question is asked. Routes the question (a
|
|
72
|
+
* UI answerer only answers for agents it owns) and receives the audit
|
|
73
|
+
* events on its session log.
|
|
74
|
+
*/
|
|
75
|
+
readonly agent: Agent;
|
|
76
|
+
/** The tool the question is about (presentation and audit). */
|
|
77
|
+
readonly toolName: string;
|
|
78
|
+
/**
|
|
79
|
+
* The exact tool call being decided, when the asker has one — lets a UI
|
|
80
|
+
* attach the prompt to the tool call it already streamed.
|
|
81
|
+
*/
|
|
82
|
+
readonly callId?: ToolCallId;
|
|
83
|
+
/** The asker's human-readable explanation of WHY it is asking. */
|
|
84
|
+
readonly reason?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Aborting withdraws the question: the request settles `'cancelled'`
|
|
87
|
+
* immediately and a late answer from a still-pending answerer is discarded.
|
|
88
|
+
*/
|
|
89
|
+
readonly signal?: AbortSignal;
|
|
90
|
+
}
|
|
91
|
+
/** Plugin config. All optional — `static Config` supplies the defaults. */
|
|
92
|
+
export interface Config {
|
|
93
|
+
/**
|
|
94
|
+
* The deployment's default {@link ApprovalPolicy} for sessions without an
|
|
95
|
+
* `approval/policy` override — `'ask'` delegates to the composed answerers
|
|
96
|
+
* (fail-closed with none); `'never'` auto-rejects every ask without
|
|
97
|
+
* prompting (the deterministic CI/unattended stance).
|
|
98
|
+
*/
|
|
99
|
+
readonly policy?: ApprovalPolicy;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Approval service that applies session policy and remembered per-tool grants
|
|
103
|
+
* before answerers and logs every ask/outcome pair to the requesting session.
|
|
104
|
+
* It exposes deterministic policy changes to the model through the
|
|
105
|
+
* runtime-context snapshot and switch notices.
|
|
106
|
+
*/
|
|
107
|
+
export declare class ApprovalService extends Service {
|
|
108
|
+
config: Config;
|
|
109
|
+
static inject: string[];
|
|
110
|
+
static Config: z<Config>;
|
|
111
|
+
private approvalProjectionHandle;
|
|
112
|
+
constructor(ctx: Context, config: Config);
|
|
113
|
+
protected [Service.init](): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Switch one live agent's policy and queue the transition for its next model
|
|
116
|
+
* step. Session initialization uses {@link setApprovalPolicy} directly
|
|
117
|
+
* because there is no previously visible policy to change.
|
|
118
|
+
* @param agent - the live agent whose policy is changing.
|
|
119
|
+
* @param policy - the new effective policy.
|
|
120
|
+
*/
|
|
121
|
+
setPolicy(agent: Agent, policy: ApprovalPolicy): void;
|
|
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
|
+
request(req: ApprovalRequest): Promise<ApprovalOutcome>;
|
|
142
|
+
/**
|
|
143
|
+
* Remember one tool's approval for a session and its live descendants. Every
|
|
144
|
+
* later ask for that tool resolves `allowed-once` without consulting an
|
|
145
|
+
* answerer while the effective policy is `ask`; `never` still rejects first.
|
|
146
|
+
* Recording nothing when the grant already exists keeps replay and repeated
|
|
147
|
+
* Always allow choices from growing the log.
|
|
148
|
+
* @param session - the session whose subtree the grant covers.
|
|
149
|
+
* @param toolName - the tool whose future asks the grant covers.
|
|
150
|
+
* @throws when the tool name is empty.
|
|
151
|
+
*/
|
|
152
|
+
grant(session: Session, toolName: string): void;
|
|
153
|
+
/**
|
|
154
|
+
* Whether a remembered grant covers this Agent's ask for the tool. The walk
|
|
155
|
+
* reads the Agent's session, then each live ancestor through `parentSession`;
|
|
156
|
+
* a vanished ancestor ends it, so an orphaned child never inherits a grant it
|
|
157
|
+
* cannot prove.
|
|
158
|
+
* @param agent - the asking Agent.
|
|
159
|
+
* @param toolName - the tool being asked about.
|
|
160
|
+
* @returns true when the Agent or a live ancestor holds the grant.
|
|
161
|
+
*/
|
|
162
|
+
isGranted(agent: Agent, toolName: string): boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Read one session's own grants without walking its lineage.
|
|
165
|
+
* @param session - session whose log supplies the grants.
|
|
166
|
+
* @param toolName - tool whose grant is sought.
|
|
167
|
+
* @returns whether this session recorded a grant for the tool.
|
|
168
|
+
*/
|
|
169
|
+
private grantedIn;
|
|
170
|
+
/**
|
|
171
|
+
* The session's effective policy: its projected override, else the
|
|
172
|
+
* configured default (the schema already defaulted an omitted policy to
|
|
173
|
+
* `'ask'`; the `??` only narrows the optional-input TYPE).
|
|
174
|
+
* @param session - the exact accepted session whose policy applies.
|
|
175
|
+
* @returns the policy every ask for this session resolves under right now.
|
|
176
|
+
*/
|
|
177
|
+
effectivePolicy(session: Session): ApprovalPolicy;
|
|
178
|
+
/**
|
|
179
|
+
* Read the session override without applying the configured default.
|
|
180
|
+
* @param session - session whose log supplies the override.
|
|
181
|
+
* @returns the last logged policy, or `undefined` without one.
|
|
182
|
+
*/
|
|
183
|
+
overrideOf(session: Session): ApprovalPolicy | undefined;
|
|
184
|
+
private approvalStateOf;
|
|
185
|
+
/**
|
|
186
|
+
* Dispatch the waterfall, contained and raced against the request signal.
|
|
187
|
+
* @param req - the borrowed public request.
|
|
188
|
+
* @param session - the request agent's session used for policy lookup.
|
|
189
|
+
* @returns the normalized closed outcome.
|
|
190
|
+
*/
|
|
191
|
+
private decide;
|
|
192
|
+
}
|
|
193
|
+
export default ApprovalService;
|
|
194
|
+
//# sourceMappingURL=index.d.ts.map
|