@vinhnt-sdk/step-executor 0.1.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/LICENSE +21 -0
- package/README.md +29 -0
- package/dist/approval-handler.d.ts +18 -0
- package/dist/approval-handler.d.ts.map +1 -0
- package/dist/approval-handler.js +39 -0
- package/dist/approval-handler.js.map +1 -0
- package/dist/circuit-breaker.d.ts +48 -0
- package/dist/circuit-breaker.d.ts.map +1 -0
- package/dist/circuit-breaker.js +171 -0
- package/dist/circuit-breaker.js.map +1 -0
- package/dist/hooks.d.ts +15 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +2 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel-error.d.ts +10 -0
- package/dist/kernel-error.d.ts.map +1 -0
- package/dist/kernel-error.js +12 -0
- package/dist/kernel-error.js.map +1 -0
- package/dist/kernel-utils.d.ts +49 -0
- package/dist/kernel-utils.d.ts.map +1 -0
- package/dist/kernel-utils.js +117 -0
- package/dist/kernel-utils.js.map +1 -0
- package/dist/path-policy.d.ts +18 -0
- package/dist/path-policy.d.ts.map +1 -0
- package/dist/path-policy.js +38 -0
- package/dist/path-policy.js.map +1 -0
- package/dist/permission-gate.d.ts +93 -0
- package/dist/permission-gate.d.ts.map +1 -0
- package/dist/permission-gate.js +303 -0
- package/dist/permission-gate.js.map +1 -0
- package/dist/run-context.d.ts +26 -0
- package/dist/run-context.d.ts.map +1 -0
- package/dist/run-context.js +13 -0
- package/dist/run-context.js.map +1 -0
- package/dist/run-state.d.ts +27 -0
- package/dist/run-state.d.ts.map +1 -0
- package/dist/run-state.js +88 -0
- package/dist/run-state.js.map +1 -0
- package/dist/self-correction.d.ts +29 -0
- package/dist/self-correction.d.ts.map +1 -0
- package/dist/self-correction.js +89 -0
- package/dist/self-correction.js.map +1 -0
- package/dist/step-executor.d.ts +81 -0
- package/dist/step-executor.d.ts.map +1 -0
- package/dist/step-executor.js +314 -0
- package/dist/step-executor.js.map +1 -0
- package/dist/termination.d.ts +87 -0
- package/dist/termination.d.ts.map +1 -0
- package/dist/termination.js +89 -0
- package/dist/termination.js.map +1 -0
- package/dist/tool-context-builder.d.ts +22 -0
- package/dist/tool-context-builder.d.ts.map +1 -0
- package/dist/tool-context-builder.js +42 -0
- package/dist/tool-context-builder.js.map +1 -0
- package/dist/tool-error-router.d.ts +26 -0
- package/dist/tool-error-router.d.ts.map +1 -0
- package/dist/tool-error-router.js +85 -0
- package/dist/tool-error-router.js.map +1 -0
- package/dist/tool-result-processor.d.ts +24 -0
- package/dist/tool-result-processor.d.ts.map +1 -0
- package/dist/tool-result-processor.js +47 -0
- package/dist/tool-result-processor.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { RunAbortedError } from "@vinhnt-sdk/schema";
|
|
2
|
+
/** Race a promise against an abort signal — rejects with RunAbortedError if aborted. @internal */
|
|
3
|
+
export function raceWithAbort(promise, signal, runId) {
|
|
4
|
+
if (signal.aborted)
|
|
5
|
+
return Promise.reject(new RunAbortedError(runId));
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const onAbort = () => {
|
|
8
|
+
signal.removeEventListener("abort", onAbort);
|
|
9
|
+
reject(new RunAbortedError(runId));
|
|
10
|
+
};
|
|
11
|
+
signal.addEventListener("abort", onAbort);
|
|
12
|
+
promise.then((v) => { signal.removeEventListener("abort", onAbort); resolve(v); }, (e) => { signal.removeEventListener("abort", onAbort); reject(e); });
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run a tool execution with a cooperative timeout (RV-19). The caller's
|
|
17
|
+
* `run` receives a local `AbortSignal` that fires when EITHER the parent
|
|
18
|
+
* (run) signal aborts OR the tool exceeds `timeoutMs`. Well-behaved tools
|
|
19
|
+
* that watch their context signal stop their side effects at the deadline
|
|
20
|
+
* instead of continuing past it (a plain `Promise.race` abandons the tool
|
|
21
|
+
* promise and its effects keep running). The returned promise still
|
|
22
|
+
* hard-rejects at `timeoutMs`, so a non-cooperative tool can never hang the
|
|
23
|
+
* caller longer than the deadline. Parent aborts are NOT synthesised here —
|
|
24
|
+
* the caller's `raceWithAbort` rejects with `RunAbortedError`.
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function withToolTimeout(run, timeoutMs, parent, timeoutMessage) {
|
|
28
|
+
const ctrl = new AbortController();
|
|
29
|
+
const onParentAbort = () => ctrl.abort(parent.reason);
|
|
30
|
+
if (parent.aborted) {
|
|
31
|
+
ctrl.abort(parent.reason);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
parent.addEventListener("abort", onParentAbort, { once: true });
|
|
35
|
+
}
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
const timer = setTimeout(() => {
|
|
38
|
+
ctrl.abort(new Error(timeoutMessage));
|
|
39
|
+
reject(new Error(timeoutMessage));
|
|
40
|
+
}, timeoutMs);
|
|
41
|
+
run(ctrl.signal).then((v) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
parent.removeEventListener("abort", onParentAbort);
|
|
44
|
+
resolve(v);
|
|
45
|
+
}, (e) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
parent.removeEventListener("abort", onParentAbort);
|
|
48
|
+
reject(e);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** Default cap on steps per run. */
|
|
53
|
+
export const DEFAULT_MAX_STEPS = 25;
|
|
54
|
+
/** Default cap on tool calls per step. */
|
|
55
|
+
export const DEFAULT_MAX_TOOL_CALLS_PER_STEP = 20;
|
|
56
|
+
/** Consecutive identical calls that trigger doom-loop detection. */
|
|
57
|
+
export const DOOM_LOOP_THRESHOLD = 3;
|
|
58
|
+
/** System prompt guiding the model to correct a failed tool call. @internal */
|
|
59
|
+
export const SELF_CORRECT_PROMPT = `A tool call just failed. Analyze the error and try a corrected approach.
|
|
60
|
+
|
|
61
|
+
Guidelines:
|
|
62
|
+
- If the error is about arguments, fix the arguments and retry.
|
|
63
|
+
- If the tool is unavailable, suggest an alternative tool.
|
|
64
|
+
- If the problem is permanent (e.g., invalid request), explain to the user.
|
|
65
|
+
- Do NOT repeat the same failing call.`;
|
|
66
|
+
/**
|
|
67
|
+
* Stable, order-independent hash of an arbitrary tool-arguments value.
|
|
68
|
+
* Object key order is normalized so `{a:1,b:2}` and `{b:2,a:1}` hash the same,
|
|
69
|
+
* and the hash is computed once per call (vs. repeated JSON.stringify in a
|
|
70
|
+
* doom-loop scan). Collisions are possible but astronomically unlikely for the
|
|
71
|
+
* small arg payloads agents produce.
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export function hashArgs(value) {
|
|
75
|
+
return fnv1a(canonicalJson(value)).toString(36);
|
|
76
|
+
}
|
|
77
|
+
function canonicalJson(value) {
|
|
78
|
+
if (value === null)
|
|
79
|
+
return "null";
|
|
80
|
+
if (typeof value !== "object")
|
|
81
|
+
return JSON.stringify(value);
|
|
82
|
+
if (Array.isArray(value))
|
|
83
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
84
|
+
const obj = value;
|
|
85
|
+
const keys = Object.keys(obj).sort();
|
|
86
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(obj[k])}`).join(",")}}`;
|
|
87
|
+
}
|
|
88
|
+
function fnv1a(str) {
|
|
89
|
+
let h = 0x811c9dc5;
|
|
90
|
+
for (let i = 0; i < str.length; i++) {
|
|
91
|
+
h ^= str.charCodeAt(i);
|
|
92
|
+
h = Math.imul(h, 0x01000193);
|
|
93
|
+
}
|
|
94
|
+
return h >>> 0;
|
|
95
|
+
}
|
|
96
|
+
/** Detect whether the same tool+args repeated the last `threshold` calls. @internal */
|
|
97
|
+
export function detectDoomLoop(recentCalls, name, args, threshold = DOOM_LOOP_THRESHOLD) {
|
|
98
|
+
if (recentCalls.length < threshold)
|
|
99
|
+
return false;
|
|
100
|
+
const last = recentCalls.slice(-threshold);
|
|
101
|
+
const argsKey = hashArgs(args);
|
|
102
|
+
return last.every((c) => c.id === name && (c.argsKey ?? hashArgs(c.args)) === argsKey);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Derive the owning domain from a namespaced tool id:
|
|
106
|
+
* `mcp__<server>__<tool>` → "mcp:<server>", `coding.read_file` → "coding",
|
|
107
|
+
* bare ids → "core".
|
|
108
|
+
*/
|
|
109
|
+
export function toolDomain(toolName) {
|
|
110
|
+
if (toolName.startsWith("mcp__")) {
|
|
111
|
+
const parts = toolName.split("__");
|
|
112
|
+
return parts.length >= 2 ? `mcp:${parts[1]}` : "mcp";
|
|
113
|
+
}
|
|
114
|
+
const dot = toolName.indexOf(".");
|
|
115
|
+
return dot > 0 ? toolName.slice(0, dot) : "core";
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=kernel-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kernel-utils.js","sourceRoot":"","sources":["../src/kernel-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared external-path containment policy for path-aware tools.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for "which tools are path-aware" and "what counts as
|
|
5
|
+
* escaping the workspace root", so the step executor and the tool-error router
|
|
6
|
+
* can never drift apart (RV-45).
|
|
7
|
+
*
|
|
8
|
+
* @module step-executor/path-policy
|
|
9
|
+
*/
|
|
10
|
+
/** Tools whose path arguments are validated against the workspace root. */
|
|
11
|
+
export declare const PATH_AWARE_TOOLS: Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Check that every path argument of a path-aware tool stays inside the
|
|
14
|
+
* workspace root. Returns a human-readable violation reason, or `undefined`
|
|
15
|
+
* when the call is allowed (or not path-aware / no root configured).
|
|
16
|
+
*/
|
|
17
|
+
export declare function checkExternalPaths(toolName: string, args: unknown, workspaceRoot: string | undefined): string | undefined;
|
|
18
|
+
//# sourceMappingURL=path-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-policy.d.ts","sourceRoot":"","sources":["../src/path-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,aAG3B,CAAC;AAEH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,GAAG,SAAS,CAcpB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared external-path containment policy for path-aware tools.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for "which tools are path-aware" and "what counts as
|
|
5
|
+
* escaping the workspace root", so the step executor and the tool-error router
|
|
6
|
+
* can never drift apart (RV-45).
|
|
7
|
+
*
|
|
8
|
+
* @module step-executor/path-policy
|
|
9
|
+
*/
|
|
10
|
+
import { normalize } from "node:path";
|
|
11
|
+
/** Tools whose path arguments are validated against the workspace root. */
|
|
12
|
+
export const PATH_AWARE_TOOLS = new Set([
|
|
13
|
+
"read_file", "write_file", "edit_file", "apply_patch",
|
|
14
|
+
"list_directory", "glob_files", "grep_files", "shell", "read_image",
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Check that every path argument of a path-aware tool stays inside the
|
|
18
|
+
* workspace root. Returns a human-readable violation reason, or `undefined`
|
|
19
|
+
* when the call is allowed (or not path-aware / no root configured).
|
|
20
|
+
*/
|
|
21
|
+
export function checkExternalPaths(toolName, args, workspaceRoot) {
|
|
22
|
+
if (!workspaceRoot || !PATH_AWARE_TOOLS.has(toolName))
|
|
23
|
+
return undefined;
|
|
24
|
+
const input = args;
|
|
25
|
+
if (!input)
|
|
26
|
+
return undefined;
|
|
27
|
+
const normalizedRoot = workspaceRoot.replace(/\\/g, "/") + "/";
|
|
28
|
+
const pathCandidates = [input.filePath, input.path, input.dirPath].filter((p) => typeof p === "string");
|
|
29
|
+
for (const p of pathCandidates) {
|
|
30
|
+
const absRaw = p.startsWith("/") || /^[A-Za-z]:[/\\]/.test(p) ? p : workspaceRoot + "/" + p;
|
|
31
|
+
const absPath = normalize(absRaw).replace(/\\/g, "/");
|
|
32
|
+
if (!absPath.startsWith(normalizedRoot)) {
|
|
33
|
+
return `references path outside workspace: "${p}" (${absPath} not in ${normalizedRoot})`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=path-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-policy.js","sourceRoot":"","sources":["../src/path-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IACtC,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa;IACrD,gBAAgB,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY;CACpE,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,IAAa,EACb,aAAiC;IAEjC,IAAI,CAAC,aAAa,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IACxE,MAAM,KAAK,GAAG,IAA2C,CAAC;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;IAC/D,MAAM,cAAc,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IACrH,KAAK,MAAM,CAAC,IAAI,cAAc,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAW,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,GAAG,GAAG,GAAG,CAAC,CAAC;QACpG,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACtD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;YACxC,OAAO,uCAAuC,CAAC,MAAM,OAAO,WAAW,cAAc,GAAG,CAAC;QAC3F,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { RunId, AgentConfig } from "@vinhnt-sdk/schema";
|
|
2
|
+
import type { ToolRisk, PermissionReply } from "@vinhnt-sdk/tools";
|
|
3
|
+
import type { StepExecutorPluginHooks } from "./hooks.js";
|
|
4
|
+
import type { ApprovalStore } from "@vinhnt-sdk/permission";
|
|
5
|
+
import type { RunEventStore } from "@vinhnt-sdk/session";
|
|
6
|
+
import type { EventBus } from "@vinhnt-sdk/event";
|
|
7
|
+
/** Result of an approval check: allow, deny, or needs approval. */
|
|
8
|
+
export type ApprovalDecision = "allow" | "deny" | "approval_required";
|
|
9
|
+
/** A user-approved allow/deny rule for a tool call pattern. */
|
|
10
|
+
export interface DynamicRule {
|
|
11
|
+
toolName: string;
|
|
12
|
+
pattern: string;
|
|
13
|
+
decision: "allow" | "deny";
|
|
14
|
+
}
|
|
15
|
+
/** Outcome of {@link PermissionGate.checkTool}. */
|
|
16
|
+
export interface PermissionCheckResult {
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
reason?: string;
|
|
19
|
+
needsApproval?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Dependencies required by {@link PermissionGate}. */
|
|
22
|
+
export interface PermissionGateDeps {
|
|
23
|
+
readonly store: RunEventStore;
|
|
24
|
+
readonly eventBus?: EventBus;
|
|
25
|
+
readonly pluginManager: StepExecutorPluginHooks | undefined;
|
|
26
|
+
readonly approvalStore: ApprovalStore | undefined;
|
|
27
|
+
readonly autoApprovalEnabled?: boolean | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** Enforces 4-phase permission gating: global rules, agent perms, dynamic rules, risk defaults. */
|
|
30
|
+
export declare class PermissionGate {
|
|
31
|
+
private readonly deps;
|
|
32
|
+
private globalPermissionRules?;
|
|
33
|
+
private dynamicRules;
|
|
34
|
+
private riskOverrides?;
|
|
35
|
+
private topLevelRules;
|
|
36
|
+
private autoApproval;
|
|
37
|
+
constructor(deps: PermissionGateDeps);
|
|
38
|
+
/** Toggle auto-approval at runtime (mirrors config.autoApprovalEnabled). */
|
|
39
|
+
setAutoApprovalEnabled(enabled: boolean): void;
|
|
40
|
+
/** Parse and apply config-level permission rules (OpenCode-style nested `{ tool: "allow|deny|ask" }`). */
|
|
41
|
+
setGlobalRules(configRules: Record<string, string | Record<string, string>>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Apply top-level `allow`/`deny`/`ask` pattern lists (OpenCode-style
|
|
44
|
+
* `"ToolName(glob)"` strings). These take precedence over risk defaults and
|
|
45
|
+
* agent-level allowed/denied tool lists.
|
|
46
|
+
*/
|
|
47
|
+
setTopLevelRules(rules: Record<"allow" | "deny" | "ask", string[]>): void;
|
|
48
|
+
/** Register a user-approved dynamic rule (last-match-wins over risk defaults). */
|
|
49
|
+
addDynamicRule(rule: DynamicRule): void;
|
|
50
|
+
/** Override risk-level defaults (read→allow, write→approval, destructive→deny). */
|
|
51
|
+
setRiskOverrides(overrides?: Partial<Record<ToolRisk, ApprovalDecision>>): void;
|
|
52
|
+
/**
|
|
53
|
+
* Evaluate whether a tool call is allowed.
|
|
54
|
+
* 4-phase gate: global rules → agent permissions → dynamic rules → risk defaults.
|
|
55
|
+
* Returns { allowed, reason, needsApproval }.
|
|
56
|
+
*/
|
|
57
|
+
checkTool(name: string, risk: ToolRisk, args?: Record<string, unknown>, agent?: AgentConfig): PermissionCheckResult;
|
|
58
|
+
/** Check whether combined token count stays within agent's maxTokens limit. */
|
|
59
|
+
checkMaxTokens(inputTokens: number, outputTokens: number, agent?: AgentConfig): boolean;
|
|
60
|
+
/** Check whether step count is within agent's maxSteps limit. */
|
|
61
|
+
checkMaxSteps(step: number, agent?: AgentConfig): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Ask the user to approve a tool call.
|
|
64
|
+
* Routes through ApprovalStore and plugin hooks.
|
|
65
|
+
* When reply is "always", `savePatterns` are persisted as allow rules so
|
|
66
|
+
* future matching calls auto-approve (without opening the whole tool).
|
|
67
|
+
* Returns "once", "always", or "reject".
|
|
68
|
+
*/
|
|
69
|
+
askForTool(toolName: string, _toolId: string, runId: RunId, _sessionId: string, reason: string, _agentId: string, traceId: string, pluginManager?: StepExecutorPluginHooks, savePatterns?: readonly string[], signal?: AbortSignal): Promise<PermissionReply>;
|
|
70
|
+
private askViaApprovalStore;
|
|
71
|
+
/**
|
|
72
|
+
* Check whether a previously saved approval exists for this tool+args+agent.
|
|
73
|
+
* Scoped to the args context pattern (e.g. `tool.write_file(src/*)` covers
|
|
74
|
+
* `src/a.ts`), falling back to a whole-tool approval (`tool.write_file`).
|
|
75
|
+
* A matching saved rejection always wins over a saved approval.
|
|
76
|
+
*/
|
|
77
|
+
checkSavedApproval(toolName: string, argsOrAgentId?: Record<string, unknown> | string, agentId?: string): boolean;
|
|
78
|
+
/** Return all registered dynamic rules (saved allow/deny policies). */
|
|
79
|
+
getDynamicRules(): readonly DynamicRule[];
|
|
80
|
+
/**
|
|
81
|
+
* Persist a saved approval so future matching calls skip the ask dialog.
|
|
82
|
+
* Scoped to the args context pattern — approving `read_file("a.txt")` does
|
|
83
|
+
* NOT auto-approve `read_file("b.txt")` (whole-tool approval requires a call
|
|
84
|
+
* with no scoped context, which resolves to `tool.<name>`).
|
|
85
|
+
*/
|
|
86
|
+
saveApproval(toolName: string, argsOrAgentId?: Record<string, unknown> | string, agentId?: string): void;
|
|
87
|
+
/**
|
|
88
|
+
* Persist a saved rejection so future matching calls are auto-denied.
|
|
89
|
+
* Scoped to the args context pattern like {@link saveApproval}.
|
|
90
|
+
*/
|
|
91
|
+
saveRejection(toolName: string, argsOrAgentId?: Record<string, unknown> | string, agentId?: string): void;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=permission-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-gate.d.ts","sourceRoot":"","sources":["../src/permission-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { evaluatePermission, checkRiskAllowed } from "@vinhnt-sdk/permission";
|
|
2
|
+
import { wildcardMatch } from "@vinhnt-sdk/schema";
|
|
3
|
+
import { buildPermissionRules } from "@vinhnt-sdk/permission";
|
|
4
|
+
import { commandPattern } from "@vinhnt-sdk/tools";
|
|
5
|
+
import { PermissionRequested, PermissionReplied } from "@vinhnt-sdk/event";
|
|
6
|
+
const RISK_DECISIONS = {
|
|
7
|
+
read: "allow",
|
|
8
|
+
write: "approval_required",
|
|
9
|
+
destructive: "deny",
|
|
10
|
+
external: "approval_required",
|
|
11
|
+
};
|
|
12
|
+
function extractContextPattern(toolName, args) {
|
|
13
|
+
if (!args || typeof args !== "object")
|
|
14
|
+
return "*";
|
|
15
|
+
if (toolName === "execute_command" || toolName === "bash") {
|
|
16
|
+
const cmd = args.command;
|
|
17
|
+
if (typeof cmd === "string" && cmd.trim())
|
|
18
|
+
return commandPattern(cmd);
|
|
19
|
+
}
|
|
20
|
+
const path = args.filePath ?? args.path;
|
|
21
|
+
if (typeof path === "string" && path.trim())
|
|
22
|
+
return path;
|
|
23
|
+
return "*";
|
|
24
|
+
}
|
|
25
|
+
/** Split a `Tool(glob)` permission-pattern string into its parts. Returns null if malformed. */
|
|
26
|
+
function parseToolPattern(expr) {
|
|
27
|
+
const m = /^\s*([A-Za-z0-9_.-]+)\s*\(\s*(.*?)\s*\)\s*$/.exec(expr);
|
|
28
|
+
if (!m) {
|
|
29
|
+
const bare = expr.trim();
|
|
30
|
+
if (!bare)
|
|
31
|
+
return null;
|
|
32
|
+
return { toolName: bare, pattern: "*" };
|
|
33
|
+
}
|
|
34
|
+
return { toolName: m[1], pattern: m[2] || "*" };
|
|
35
|
+
}
|
|
36
|
+
/** Enforces 4-phase permission gating: global rules, agent perms, dynamic rules, risk defaults. */
|
|
37
|
+
export class PermissionGate {
|
|
38
|
+
deps;
|
|
39
|
+
globalPermissionRules;
|
|
40
|
+
dynamicRules = [];
|
|
41
|
+
riskOverrides;
|
|
42
|
+
topLevelRules = [];
|
|
43
|
+
autoApproval;
|
|
44
|
+
constructor(deps) {
|
|
45
|
+
this.deps = deps;
|
|
46
|
+
this.autoApproval = deps.autoApprovalEnabled ?? false;
|
|
47
|
+
}
|
|
48
|
+
/** Toggle auto-approval at runtime (mirrors config.autoApprovalEnabled). */
|
|
49
|
+
setAutoApprovalEnabled(enabled) {
|
|
50
|
+
this.autoApproval = enabled;
|
|
51
|
+
}
|
|
52
|
+
/** Parse and apply config-level permission rules (OpenCode-style nested `{ tool: "allow|deny|ask" }`). */
|
|
53
|
+
setGlobalRules(configRules) {
|
|
54
|
+
const raw = buildPermissionRules(configRules);
|
|
55
|
+
this.globalPermissionRules = raw.map((r) => ({
|
|
56
|
+
effect: r.effect,
|
|
57
|
+
target: r.action,
|
|
58
|
+
...(r.resource !== "*" ? { paramPattern: r.resource } : {}),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Apply top-level `allow`/`deny`/`ask` pattern lists (OpenCode-style
|
|
63
|
+
* `"ToolName(glob)"` strings). These take precedence over risk defaults and
|
|
64
|
+
* agent-level allowed/denied tool lists.
|
|
65
|
+
*/
|
|
66
|
+
setTopLevelRules(rules) {
|
|
67
|
+
const parsed = [];
|
|
68
|
+
for (const decision of ["allow", "deny", "ask"]) {
|
|
69
|
+
for (const expr of rules[decision] ?? []) {
|
|
70
|
+
if (typeof expr !== "string")
|
|
71
|
+
continue;
|
|
72
|
+
const parts = parseToolPattern(expr);
|
|
73
|
+
if (parts)
|
|
74
|
+
parsed.push({ ...parts, decision });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.topLevelRules = parsed;
|
|
78
|
+
}
|
|
79
|
+
/** Register a user-approved dynamic rule (last-match-wins over risk defaults). */
|
|
80
|
+
addDynamicRule(rule) {
|
|
81
|
+
this.dynamicRules.push(rule);
|
|
82
|
+
}
|
|
83
|
+
/** Override risk-level defaults (read→allow, write→approval, destructive→deny). */
|
|
84
|
+
setRiskOverrides(overrides) {
|
|
85
|
+
if (overrides !== undefined)
|
|
86
|
+
this.riskOverrides = overrides;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Evaluate whether a tool call is allowed.
|
|
90
|
+
* 4-phase gate: global rules → agent permissions → dynamic rules → risk defaults.
|
|
91
|
+
* Returns { allowed, reason, needsApproval }.
|
|
92
|
+
*/
|
|
93
|
+
checkTool(name, risk, args, agent) {
|
|
94
|
+
// Phase 1: Global config-level rules (OpenCode-style nested patterns)
|
|
95
|
+
if (this.globalPermissionRules && this.globalPermissionRules.length > 0) {
|
|
96
|
+
const globalResult = evaluatePermission({ rules: this.globalPermissionRules }, `tool.${name}`, args);
|
|
97
|
+
if (globalResult.decision === "deny")
|
|
98
|
+
return { allowed: false, reason: globalResult.reason };
|
|
99
|
+
if (globalResult.decision === "ask")
|
|
100
|
+
return { allowed: false, reason: globalResult.reason, needsApproval: true };
|
|
101
|
+
}
|
|
102
|
+
// Phase 1.5: Top-level allow/deny/ask pattern lists ("Tool(glob)")
|
|
103
|
+
if (this.topLevelRules.length > 0) {
|
|
104
|
+
const pattern = extractContextPattern(name, args);
|
|
105
|
+
let matched;
|
|
106
|
+
for (const rule of this.topLevelRules) {
|
|
107
|
+
if (rule.toolName === name && wildcardMatch(rule.pattern, pattern)) {
|
|
108
|
+
matched = rule; // last-match-wins
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (matched) {
|
|
112
|
+
if (matched.decision === "deny")
|
|
113
|
+
return { allowed: false, reason: `Tool "${name}" is denied` };
|
|
114
|
+
if (matched.decision === "ask")
|
|
115
|
+
return { allowed: false, reason: `Tool "${name}" requires approval`, needsApproval: true };
|
|
116
|
+
return { allowed: true, reason: "Allowed by configured rule" };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Phase 2: Agent-specific permissions
|
|
120
|
+
const perms = agent?.permissions;
|
|
121
|
+
if (perms) {
|
|
122
|
+
if (perms.ruleset) {
|
|
123
|
+
const result = evaluatePermission(perms.ruleset, `tool.${name}`, args);
|
|
124
|
+
if (result.decision === "deny")
|
|
125
|
+
return { allowed: false, reason: result.reason };
|
|
126
|
+
if (result.decision === "ask")
|
|
127
|
+
return { allowed: false, reason: result.reason, needsApproval: true };
|
|
128
|
+
if (perms.ruleset.allowedRisks && !checkRiskAllowed(perms.ruleset, risk)) {
|
|
129
|
+
return { allowed: false, reason: `Tool "${name}" has risk "${risk}" which exceeds agent's allowed risks` };
|
|
130
|
+
}
|
|
131
|
+
return { allowed: true };
|
|
132
|
+
}
|
|
133
|
+
if (perms.deniedTools?.some((p) => wildcardMatch(p, name))) {
|
|
134
|
+
return { allowed: false, reason: `Tool "${name}" is denied for this agent` };
|
|
135
|
+
}
|
|
136
|
+
if (perms.allowedTools && !perms.allowedTools.some((p) => wildcardMatch(p, name))) {
|
|
137
|
+
return { allowed: false, reason: `Tool "${name}" is not in the allowed tools list` };
|
|
138
|
+
}
|
|
139
|
+
if (perms.allowedRisks && !perms.allowedRisks.includes(risk)) {
|
|
140
|
+
return { allowed: false, reason: `Tool "${name}" has risk "${risk}" which exceeds agent's allowed risks` };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Phase 3: Dynamic rules (last-match-wins, same as ToolPolicy)
|
|
144
|
+
if (this.dynamicRules.length > 0) {
|
|
145
|
+
const pattern = extractContextPattern(name, args);
|
|
146
|
+
for (let i = this.dynamicRules.length - 1; i >= 0; i--) {
|
|
147
|
+
const rule = this.dynamicRules[i];
|
|
148
|
+
if (rule.toolName === name && wildcardMatch(rule.pattern, pattern)) {
|
|
149
|
+
if (rule.decision === "deny")
|
|
150
|
+
return { allowed: false, reason: `Tool "${name}" denied by dynamic rule` };
|
|
151
|
+
return { allowed: true };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Phase 4: Risk-based default fallback (merged from ToolPolicy)
|
|
156
|
+
const decision = this.riskOverrides?.[risk] ?? RISK_DECISIONS[risk];
|
|
157
|
+
if (decision === "deny")
|
|
158
|
+
return { allowed: false, reason: `Tool "${name}" (risk: ${risk}) is denied` };
|
|
159
|
+
if (decision === "approval_required")
|
|
160
|
+
return { allowed: false, needsApproval: true, reason: `Tool "${name}" (risk: ${risk}) requires approval` };
|
|
161
|
+
return { allowed: true };
|
|
162
|
+
}
|
|
163
|
+
/** Check whether combined token count stays within agent's maxTokens limit. */
|
|
164
|
+
checkMaxTokens(inputTokens, outputTokens, agent) {
|
|
165
|
+
const perms = agent?.permissions;
|
|
166
|
+
if (!perms?.maxTokens)
|
|
167
|
+
return true;
|
|
168
|
+
return (inputTokens + outputTokens) <= perms.maxTokens;
|
|
169
|
+
}
|
|
170
|
+
/** Check whether step count is within agent's maxSteps limit. */
|
|
171
|
+
checkMaxSteps(step, agent) {
|
|
172
|
+
const perms = agent?.permissions;
|
|
173
|
+
if (!perms?.maxSteps)
|
|
174
|
+
return true;
|
|
175
|
+
return step < perms.maxSteps;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Ask the user to approve a tool call.
|
|
179
|
+
* Routes through ApprovalStore and plugin hooks.
|
|
180
|
+
* When reply is "always", `savePatterns` are persisted as allow rules so
|
|
181
|
+
* future matching calls auto-approve (without opening the whole tool).
|
|
182
|
+
* Returns "once", "always", or "reject".
|
|
183
|
+
*/
|
|
184
|
+
async askForTool(toolName, _toolId, runId, _sessionId, reason, _agentId, traceId, pluginManager, savePatterns, signal) {
|
|
185
|
+
if (this.autoApproval)
|
|
186
|
+
return "once";
|
|
187
|
+
const reply = await this.askViaApprovalStore(toolName, runId, reason, traceId, pluginManager, signal);
|
|
188
|
+
if (reply === "always" && savePatterns && savePatterns.length > 0) {
|
|
189
|
+
for (const pattern of savePatterns) {
|
|
190
|
+
if (!pattern || pattern === "*")
|
|
191
|
+
continue;
|
|
192
|
+
this.addDynamicRule({ toolName, pattern, decision: "allow" });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return reply;
|
|
196
|
+
}
|
|
197
|
+
async askViaApprovalStore(toolName, runId, reason, traceId, pluginManager, signal) {
|
|
198
|
+
const requestId = crypto.randomUUID();
|
|
199
|
+
const req = {
|
|
200
|
+
id: requestId,
|
|
201
|
+
runId,
|
|
202
|
+
toolName,
|
|
203
|
+
resource: toolName,
|
|
204
|
+
reason,
|
|
205
|
+
prompt: reason,
|
|
206
|
+
occurredAt: new Date().toISOString(),
|
|
207
|
+
};
|
|
208
|
+
// Persist permission.requested to store (sync for tests) AND publish to bus
|
|
209
|
+
await this.deps.store.append({
|
|
210
|
+
id: crypto.randomUUID(), runId, type: "permission.requested",
|
|
211
|
+
occurredAt: new Date().toISOString(), traceId,
|
|
212
|
+
data: { requestId, toolName, resource: toolName, reason, prompt: reason },
|
|
213
|
+
});
|
|
214
|
+
if (this.deps.eventBus) {
|
|
215
|
+
this.deps.eventBus.publish(PermissionRequested, {
|
|
216
|
+
requestId, toolName, resource: toolName, reason, prompt: reason
|
|
217
|
+
}, { traceId, aggregateId: runId });
|
|
218
|
+
}
|
|
219
|
+
let reply = "reject";
|
|
220
|
+
const hookResult = await pluginManager?.fireHook("onPermissionAsk", {
|
|
221
|
+
permission: `tool.${toolName}`,
|
|
222
|
+
resource: toolName,
|
|
223
|
+
reason,
|
|
224
|
+
});
|
|
225
|
+
if (hookResult?.modified?.reply) {
|
|
226
|
+
reply = hookResult.modified.reply;
|
|
227
|
+
}
|
|
228
|
+
else if (this.deps.approvalStore) {
|
|
229
|
+
// Race the approval wait against the run's abort signal so a cancelled
|
|
230
|
+
// run does not hang forever waiting for a human reply.
|
|
231
|
+
reply = await this.deps.approvalStore.awaitReply(req, { signal });
|
|
232
|
+
}
|
|
233
|
+
// Persist permission.replied to store (sync for tests) AND publish to bus
|
|
234
|
+
await this.deps.store.append({
|
|
235
|
+
id: crypto.randomUUID(), runId, type: "permission.replied",
|
|
236
|
+
occurredAt: new Date().toISOString(), traceId,
|
|
237
|
+
data: { requestId, reply },
|
|
238
|
+
});
|
|
239
|
+
if (this.deps.eventBus) {
|
|
240
|
+
this.deps.eventBus.publish(PermissionReplied, {
|
|
241
|
+
requestId, reply
|
|
242
|
+
}, { traceId, aggregateId: runId });
|
|
243
|
+
}
|
|
244
|
+
return reply;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Check whether a previously saved approval exists for this tool+args+agent.
|
|
248
|
+
* Scoped to the args context pattern (e.g. `tool.write_file(src/*)` covers
|
|
249
|
+
* `src/a.ts`), falling back to a whole-tool approval (`tool.write_file`).
|
|
250
|
+
* A matching saved rejection always wins over a saved approval.
|
|
251
|
+
*/
|
|
252
|
+
checkSavedApproval(toolName, argsOrAgentId, agentId) {
|
|
253
|
+
const store = this.deps.approvalStore;
|
|
254
|
+
if (!store)
|
|
255
|
+
return false;
|
|
256
|
+
const args = typeof argsOrAgentId === "object" ? argsOrAgentId : undefined;
|
|
257
|
+
const resolvedAgentId = typeof argsOrAgentId === "string" ? argsOrAgentId : agentId;
|
|
258
|
+
const pattern = extractContextPattern(toolName, args);
|
|
259
|
+
const resource = pattern === "*" ? `tool.${toolName}` : `tool.${toolName}(${pattern})`;
|
|
260
|
+
// Deny always wins — any scoped or whole-tool rejection blocks the call.
|
|
261
|
+
if (store.checkRejection(`tool.${toolName}`, toolName, resolvedAgentId))
|
|
262
|
+
return false;
|
|
263
|
+
if (store.checkRejection(resource, toolName, resolvedAgentId))
|
|
264
|
+
return false;
|
|
265
|
+
// Pattern-scoped approval first, then whole-tool approval as fallback.
|
|
266
|
+
if (store.checkApproval(resource, toolName, resolvedAgentId))
|
|
267
|
+
return true;
|
|
268
|
+
return store.checkApproval(`tool.${toolName}`, toolName, resolvedAgentId);
|
|
269
|
+
}
|
|
270
|
+
/** Return all registered dynamic rules (saved allow/deny policies). */
|
|
271
|
+
getDynamicRules() {
|
|
272
|
+
return [...this.dynamicRules];
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Persist a saved approval so future matching calls skip the ask dialog.
|
|
276
|
+
* Scoped to the args context pattern — approving `read_file("a.txt")` does
|
|
277
|
+
* NOT auto-approve `read_file("b.txt")` (whole-tool approval requires a call
|
|
278
|
+
* with no scoped context, which resolves to `tool.<name>`).
|
|
279
|
+
*/
|
|
280
|
+
saveApproval(toolName, argsOrAgentId, agentId) {
|
|
281
|
+
const args = typeof argsOrAgentId === "object" ? argsOrAgentId : undefined;
|
|
282
|
+
const resolvedAgentId = typeof argsOrAgentId === "string" ? argsOrAgentId : agentId;
|
|
283
|
+
const pattern = extractContextPattern(toolName, args);
|
|
284
|
+
const resource = pattern === "*" ? `tool.${toolName}` : `tool.${toolName}(${pattern})`;
|
|
285
|
+
this.deps.approvalStore?.saveApproval({
|
|
286
|
+
resource,
|
|
287
|
+
action: toolName,
|
|
288
|
+
...(resolvedAgentId ? { agentId: resolvedAgentId } : {}),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Persist a saved rejection so future matching calls are auto-denied.
|
|
293
|
+
* Scoped to the args context pattern like {@link saveApproval}.
|
|
294
|
+
*/
|
|
295
|
+
saveRejection(toolName, argsOrAgentId, agentId) {
|
|
296
|
+
const args = typeof argsOrAgentId === "object" ? argsOrAgentId : undefined;
|
|
297
|
+
const resolvedAgentId = typeof argsOrAgentId === "string" ? argsOrAgentId : agentId;
|
|
298
|
+
const pattern = extractContextPattern(toolName, args);
|
|
299
|
+
const resource = pattern === "*" ? `tool.${toolName}` : `tool.${toolName}(${pattern})`;
|
|
300
|
+
this.deps.approvalStore?.saveRejection(resource, toolName, resolvedAgentId);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=permission-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-gate.js","sourceRoot":"","sources":["../src/permission-gate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentConfig, AgentId, RunId } from "@vinhnt-sdk/schema";
|
|
2
|
+
import type { ToolDefinition } from "@vinhnt-sdk/tools";
|
|
3
|
+
import type { ToolSaga } from "@vinhnt-sdk/tools";
|
|
4
|
+
/**
|
|
5
|
+
* Mutable state that belongs to a single run — kept out of the kernel
|
|
6
|
+
* instance so concurrent runs never share (and clobber) each other's
|
|
7
|
+
* active agent, sub-agent depth, agent chain, tool cache or saga.
|
|
8
|
+
*/
|
|
9
|
+
export interface RunContext {
|
|
10
|
+
/** The run this context belongs to. */
|
|
11
|
+
readonly runId: RunId;
|
|
12
|
+
/** Agent actively executing this run (may change during sub-agent traversal). */
|
|
13
|
+
agent: AgentConfig | undefined;
|
|
14
|
+
/** Current sub-agent nesting depth of this run. */
|
|
15
|
+
depth: number;
|
|
16
|
+
/** Agent ids already in this run's sub-agent call chain (cycle detection). */
|
|
17
|
+
agentChain: Set<AgentId>;
|
|
18
|
+
/** Per-run saga for tool compensation recording. */
|
|
19
|
+
saga: ToolSaga;
|
|
20
|
+
/** Per-run tool resolution cache, keyed by the agent it was built for. */
|
|
21
|
+
cachedTools: readonly ToolDefinition[] | null;
|
|
22
|
+
cachedToolsAgentId: AgentId | undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Create a fresh per-run context. */
|
|
25
|
+
export declare function createRunContext(runId: RunId, agent: AgentConfig | undefined, saga: ToolSaga): RunContext;
|
|
26
|
+
//# sourceMappingURL=run-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-context.d.ts","sourceRoot":"","sources":["../src/run-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,uCAAuC;IACvC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,iFAAiF;IACjF,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;IAC/B,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,oDAAoD;IACpD,IAAI,EAAE,QAAQ,CAAC;IACf,0EAA0E;IAC1E,WAAW,EAAE,SAAS,cAAc,EAAE,GAAG,IAAI,CAAC;IAC9C,kBAAkB,EAAE,OAAO,GAAG,SAAS,CAAC;CACzC;AAED,sCAAsC;AACtC,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,IAAI,EAAE,QAAQ,GACb,UAAU,CAUZ"}
|