@senpi/runtime 3.0.24 → 3.0.26

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.
Files changed (33) hide show
  1. package/dist/approvals/approval-store.d.ts +35 -0
  2. package/dist/approvals/approval-store.d.ts.map +1 -0
  3. package/dist/approvals/approval-store.js +87 -0
  4. package/dist/approvals/approval-store.js.map +1 -0
  5. package/dist/approvals/manifest.d.ts +47 -0
  6. package/dist/approvals/manifest.d.ts.map +1 -0
  7. package/dist/approvals/manifest.js +91 -0
  8. package/dist/approvals/manifest.js.map +1 -0
  9. package/dist/approvals/tool-approval-gate.d.ts +93 -0
  10. package/dist/approvals/tool-approval-gate.d.ts.map +1 -0
  11. package/dist/approvals/tool-approval-gate.js +205 -0
  12. package/dist/approvals/tool-approval-gate.js.map +1 -0
  13. package/dist/approvals/tool-approvals-manifest.json +28 -0
  14. package/dist/dsl/notifications/formatter.js +2 -2
  15. package/dist/dsl/notifications/formatter.js.map +1 -1
  16. package/dist/dsl/notifications/index.d.ts.map +1 -1
  17. package/dist/dsl/notifications/index.js +28 -2
  18. package/dist/dsl/notifications/index.js.map +1 -1
  19. package/dist/dsl/notifications/raise-correlator.d.ts +87 -0
  20. package/dist/dsl/notifications/raise-correlator.d.ts.map +1 -0
  21. package/dist/dsl/notifications/raise-correlator.js +136 -0
  22. package/dist/dsl/notifications/raise-correlator.js.map +1 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +37 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/runtime/auto-update/coordinator.d.ts +1 -1
  28. package/dist/runtime/auto-update/coordinator.js +1 -1
  29. package/dist/runtime/strategy-lifecycle.js +3 -3
  30. package/dist/runtime/strategy-lifecycle.js.map +1 -1
  31. package/dist/types/notification.d.ts +1 -1
  32. package/openclaw.plugin.json +10 -1
  33. package/package.json +3 -2
@@ -0,0 +1,35 @@
1
+ /** Minimal logger contract so the store does not depend on a concrete logger. */
2
+ interface LoggerLike {
3
+ warn(...args: unknown[]): void;
4
+ }
5
+ /** File name inside the plugin state dir (volume-backed in prod). */
6
+ export declare const TOOL_APPROVALS_STATE_FILE = "tool-approvals.json";
7
+ /**
8
+ * Persistent per-tool "always allow" decisions for the MCP tool approval gate.
9
+ *
10
+ * OpenClaw does not persist `allow-always` for plugin approvals (unlike the exec
11
+ * tool's allowlist file) — the decision arrives once via the `onResolution`
12
+ * callback and is otherwise one-shot. This store is that missing persistence:
13
+ * a JSON file in the plugin state dir, so decisions survive gateway restarts,
14
+ * redeploys, and plugin auto-updates.
15
+ *
16
+ * To make a tool prompt again, delete its entry from `tool-approvals.json`
17
+ * (or the whole file) in the state dir.
18
+ *
19
+ * Load is synchronous (once, at plugin register()); writes are synchronous
20
+ * atomic temp → rename, and only happen on an explicit user decision.
21
+ */
22
+ export declare class ToolApprovalStore {
23
+ private readonly logger?;
24
+ private readonly filePath;
25
+ private readonly allowAlways;
26
+ constructor(stateDir: string, logger?: LoggerLike | undefined);
27
+ private load;
28
+ isAllowedAlways(tool: string): boolean;
29
+ get size(): number;
30
+ /** Record a user's allow-always decision and persist it immediately. */
31
+ rememberAllowAlways(tool: string): void;
32
+ private persist;
33
+ }
34
+ export {};
35
+ //# sourceMappingURL=approval-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-store.d.ts","sourceRoot":"","sources":["../../src/approvals/approval-store.ts"],"names":[],"mappings":"AAGA,iFAAiF;AACjF,UAAU,UAAU;IAClB,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAChC;AAED,qEAAqE;AACrE,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAU/D;;;;;;;;;;;;;;GAcG;AACH,qBAAa,iBAAiB;IAM1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuC;gBAGjE,QAAQ,EAAE,MAAM,EACC,MAAM,CAAC,EAAE,UAAU,YAAA;IAMtC,OAAO,CAAC,IAAI;IAiCZ,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAItC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,wEAAwE;IACxE,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAQvC,OAAO,CAAC,OAAO;CAehB"}
@@ -0,0 +1,87 @@
1
+ import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ /** File name inside the plugin state dir (volume-backed in prod). */
4
+ export const TOOL_APPROVALS_STATE_FILE = "tool-approvals.json";
5
+ /**
6
+ * Persistent per-tool "always allow" decisions for the MCP tool approval gate.
7
+ *
8
+ * OpenClaw does not persist `allow-always` for plugin approvals (unlike the exec
9
+ * tool's allowlist file) — the decision arrives once via the `onResolution`
10
+ * callback and is otherwise one-shot. This store is that missing persistence:
11
+ * a JSON file in the plugin state dir, so decisions survive gateway restarts,
12
+ * redeploys, and plugin auto-updates.
13
+ *
14
+ * To make a tool prompt again, delete its entry from `tool-approvals.json`
15
+ * (or the whole file) in the state dir.
16
+ *
17
+ * Load is synchronous (once, at plugin register()); writes are synchronous
18
+ * atomic temp → rename, and only happen on an explicit user decision.
19
+ */
20
+ export class ToolApprovalStore {
21
+ logger;
22
+ filePath;
23
+ allowAlways = new Map();
24
+ constructor(stateDir, logger) {
25
+ this.logger = logger;
26
+ this.filePath = join(stateDir, TOOL_APPROVALS_STATE_FILE);
27
+ this.load();
28
+ }
29
+ load() {
30
+ let raw;
31
+ try {
32
+ raw = readFileSync(this.filePath, "utf8");
33
+ }
34
+ catch (err) {
35
+ const isNotFound = err instanceof Error && "code" in err && err.code === "ENOENT";
36
+ if (!isNotFound) {
37
+ this.logger?.warn(`[senpi-tool-approvals] failed to read state file, starting empty | path=${this.filePath} err=${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ return;
40
+ }
41
+ try {
42
+ const parsed = JSON.parse(raw);
43
+ const entries = parsed?.allowAlways;
44
+ if (entries && typeof entries === "object" && !Array.isArray(entries)) {
45
+ for (const [tool, entry] of Object.entries(entries)) {
46
+ if (typeof tool === "string" && tool.length > 0) {
47
+ this.allowAlways.set(tool, {
48
+ decidedAt: typeof entry?.decidedAt === "string" ? entry.decidedAt : "",
49
+ });
50
+ }
51
+ }
52
+ }
53
+ }
54
+ catch (err) {
55
+ this.logger?.warn(`[senpi-tool-approvals] corrupt state file, starting empty (all gated tools will prompt again) | path=${this.filePath} err=${err instanceof Error ? err.message : String(err)}`);
56
+ }
57
+ }
58
+ isAllowedAlways(tool) {
59
+ return this.allowAlways.has(tool);
60
+ }
61
+ get size() {
62
+ return this.allowAlways.size;
63
+ }
64
+ /** Record a user's allow-always decision and persist it immediately. */
65
+ rememberAllowAlways(tool) {
66
+ if (!tool || this.allowAlways.has(tool)) {
67
+ return;
68
+ }
69
+ this.allowAlways.set(tool, { decidedAt: new Date().toISOString() });
70
+ this.persist();
71
+ }
72
+ persist() {
73
+ try {
74
+ mkdirSync(dirname(this.filePath), { recursive: true });
75
+ const shape = {
76
+ allowAlways: Object.fromEntries(this.allowAlways),
77
+ };
78
+ const tmpPath = `${this.filePath}.tmp`;
79
+ writeFileSync(tmpPath, JSON.stringify(shape, null, 2), "utf8");
80
+ renameSync(tmpPath, this.filePath);
81
+ }
82
+ catch (err) {
83
+ this.logger?.warn(`[senpi-tool-approvals] failed to persist state (decision kept in memory only) | path=${this.filePath} err=${err instanceof Error ? err.message : String(err)}`);
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=approval-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-store.js","sourceRoot":"","sources":["../../src/approvals/approval-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Tool-approvals manifest — the single, codebase-controlled source of truth
3
+ * for which senpi MCP tools the approval gate treats as trades (gated in the
4
+ * default mode) and for the gate's defaults (mode, prompt timeout).
5
+ *
6
+ * The manifest is a checked-in JSON file (`tool-approvals-manifest.json`,
7
+ * copied into `dist/` at build time) shipped *with the runtime*, so changing
8
+ * the gated tool set or the fleet default is one reviewable diff — no env
9
+ * vars, no scattered constants. Mirrors the skills-manager manifest pattern
10
+ * (`src/skills-manager/manifest.ts`).
11
+ *
12
+ * Runtime overrides still win over the manifest default for the mode:
13
+ * env `SENPI_TOOL_APPROVALS` > plugin config `toolApprovals` > manifest
14
+ * `defaults.mode` (see `resolveToolApprovalsMode` in tool-approval-gate.ts).
15
+ */
16
+ export type ToolApprovalsMode = "off" | "trades" | "all";
17
+ /** Parsed + validated manifest shape. */
18
+ export interface ToolApprovalsManifest {
19
+ schemaVersion: number;
20
+ defaults: {
21
+ mode: ToolApprovalsMode;
22
+ /** Prompt timeout; gateway clamps plugin approvals to max 600_000 ms. */
23
+ timeoutMs: number;
24
+ };
25
+ /** Bare senpi MCP tool names (no `senpi__` prefix) gated in `trades` mode. */
26
+ tradeTools: string[];
27
+ }
28
+ /**
29
+ * Hardcoded fallback used only when the checked-in JSON cannot be read or
30
+ * parsed (corrupt file, missing from dist). Keeps the plugin from crashing at
31
+ * import time; logged loudly by {@link loadToolApprovalsManifest}. Kept in
32
+ * sync with `tool-approvals-manifest.json` (a test asserts this).
33
+ */
34
+ export declare const FALLBACK_TOOL_APPROVALS_MANIFEST: ToolApprovalsManifest;
35
+ /**
36
+ * Load + validate the manifest from disk, cached after first read. Resolves
37
+ * the JSON path relative to this module so it works both from `src/` (vitest)
38
+ * and `dist/` (the build copies the JSON next to the compiled module).
39
+ *
40
+ * On any read/parse/validation failure, logs a warning and returns
41
+ * {@link FALLBACK_TOOL_APPROVALS_MANIFEST} — the gate must not fail to start
42
+ * because of a packaging mistake.
43
+ */
44
+ export declare function loadToolApprovalsManifest(): ToolApprovalsManifest;
45
+ /** Reset the manifest cache. Test-only. */
46
+ export declare function resetToolApprovalsManifestCacheForTests(): void;
47
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/approvals/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEzD,yCAAyC;AACzC,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE;QACR,IAAI,EAAE,iBAAiB,CAAC;QACxB,yEAAyE;QACzE,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,8EAA8E;IAC9E,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAWD;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,EAAE,qBA2B9C,CAAC;AAIF;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,IAAI,qBAAqB,CAcjE;AAED,2CAA2C;AAC3C,wBAAgB,uCAAuC,IAAI,IAAI,CAE9D"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Tool-approvals manifest — the single, codebase-controlled source of truth
3
+ * for which senpi MCP tools the approval gate treats as trades (gated in the
4
+ * default mode) and for the gate's defaults (mode, prompt timeout).
5
+ *
6
+ * The manifest is a checked-in JSON file (`tool-approvals-manifest.json`,
7
+ * copied into `dist/` at build time) shipped *with the runtime*, so changing
8
+ * the gated tool set or the fleet default is one reviewable diff — no env
9
+ * vars, no scattered constants. Mirrors the skills-manager manifest pattern
10
+ * (`src/skills-manager/manifest.ts`).
11
+ *
12
+ * Runtime overrides still win over the manifest default for the mode:
13
+ * env `SENPI_TOOL_APPROVALS` > plugin config `toolApprovals` > manifest
14
+ * `defaults.mode` (see `resolveToolApprovalsMode` in tool-approval-gate.ts).
15
+ */
16
+ import { readFileSync } from "node:fs";
17
+ import { fileURLToPath } from "node:url";
18
+ import { z } from "zod";
19
+ import { createLogger } from "../utils/logger.js";
20
+ const manifestSchema = z.object({
21
+ schemaVersion: z.number(),
22
+ defaults: z.object({
23
+ mode: z.union([z.literal("off"), z.literal("trades"), z.literal("all")]),
24
+ timeoutMs: z.number().int().positive().max(600_000),
25
+ }),
26
+ tradeTools: z.array(z.string().min(1)).min(1),
27
+ });
28
+ /**
29
+ * Hardcoded fallback used only when the checked-in JSON cannot be read or
30
+ * parsed (corrupt file, missing from dist). Keeps the plugin from crashing at
31
+ * import time; logged loudly by {@link loadToolApprovalsManifest}. Kept in
32
+ * sync with `tool-approvals-manifest.json` (a test asserts this).
33
+ */
34
+ export const FALLBACK_TOOL_APPROVALS_MANIFEST = {
35
+ schemaVersion: 1,
36
+ defaults: { mode: "trades", timeoutMs: 180_000 },
37
+ tradeTools: [
38
+ "create_position",
39
+ "close_position",
40
+ "edit_position",
41
+ "cancel_order",
42
+ "strategy_create",
43
+ "strategy_create_custom_strategy",
44
+ "strategy_close",
45
+ "strategy_close_positions",
46
+ "strategy_update",
47
+ "strategy_pause",
48
+ "strategy_top_up",
49
+ "strategy_withdraw_funds",
50
+ "strategy_bridge_funds_from_hyperliquid_to_evm",
51
+ "transfer_spot_to_perps",
52
+ // Not currently exposed by the senpi MCP server, but senpi-web's permission
53
+ // registry lists it — gating a tool that does not exist is a no-op, and it
54
+ // must not slip through unapproved if it ever ships.
55
+ "send_usdc",
56
+ "ratchet_stop_add",
57
+ "ratchet_stop_edit",
58
+ "ratchet_stop_delete",
59
+ "user_claim_referral_rewards",
60
+ ],
61
+ };
62
+ let cached = null;
63
+ /**
64
+ * Load + validate the manifest from disk, cached after first read. Resolves
65
+ * the JSON path relative to this module so it works both from `src/` (vitest)
66
+ * and `dist/` (the build copies the JSON next to the compiled module).
67
+ *
68
+ * On any read/parse/validation failure, logs a warning and returns
69
+ * {@link FALLBACK_TOOL_APPROVALS_MANIFEST} — the gate must not fail to start
70
+ * because of a packaging mistake.
71
+ */
72
+ export function loadToolApprovalsManifest() {
73
+ if (cached)
74
+ return cached;
75
+ try {
76
+ const jsonPath = fileURLToPath(new URL("./tool-approvals-manifest.json", import.meta.url));
77
+ const parsed = JSON.parse(readFileSync(jsonPath, "utf8"));
78
+ cached = manifestSchema.parse(parsed);
79
+ }
80
+ catch (err) {
81
+ const message = err instanceof Error ? err.message : String(err);
82
+ createLogger("[senpi-tool-approvals][manifest]").warn(`failed to load tool-approvals-manifest.json, using built-in fallback: ${message}`);
83
+ cached = FALLBACK_TOOL_APPROVALS_MANIFEST;
84
+ }
85
+ return cached;
86
+ }
87
+ /** Reset the manifest cache. Test-only. */
88
+ export function resetToolApprovalsManifestCacheForTests() {
89
+ cached = null;
90
+ }
91
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/approvals/manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAgBlD,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9B,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACjB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;KACpD,CAAC;IACF,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAC9C,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gCAAgC,GAA0B;IACrE,aAAa,EAAE,CAAC;IAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE;IAChD,UAAU,EAAE;QACV,iBAAiB;QACjB,gBAAgB;QAChB,eAAe;QACf,cAAc;QACd,iBAAiB;QACjB,iCAAiC;QACjC,gBAAgB;QAChB,0BAA0B;QAC1B,iBAAiB;QACjB,gBAAgB;QAChB,iBAAiB;QACjB,yBAAyB;QACzB,+CAA+C;QAC/C,wBAAwB;QACxB,4EAA4E;QAC5E,2EAA2E;QAC3E,qDAAqD;QACrD,WAAW;QACX,kBAAkB;QAClB,mBAAmB;QACnB,qBAAqB;QACrB,6BAA6B;KAC9B;CACF,CAAC;AAEF,IAAI,MAAM,GAAiC,IAAI,CAAC;AAEhD;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB;IACvC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,gCAAgC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QACnE,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,YAAY,CAAC,kCAAkC,CAAC,CAAC,IAAI,CACnD,yEAAyE,OAAO,EAAE,CACnF,CAAC;QACF,MAAM,GAAG,gCAAgC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,uCAAuC;IACrD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC"}
@@ -0,0 +1,93 @@
1
+ import { ToolApprovalStore } from "./approval-store.js";
2
+ import { type ToolApprovalsManifest, type ToolApprovalsMode } from "./manifest.js";
3
+ /**
4
+ * Tool approval gate: requires an interactive user approval before
5
+ * state-changing senpi MCP tools execute.
6
+ *
7
+ * OpenClaw v2026.5.x has no declarative "ask" policy for MCP tools (only the
8
+ * exec tool has `tools.exec.ask`); the supported mechanism is a plugin
9
+ * `before_tool_call` hook returning `requireApproval`, which the gateway
10
+ * drives via `plugin.approval.request` / `plugin.approval.resolve`
11
+ * (decisions: allow-once / allow-always / deny; timeout → deny). Approval
12
+ * prompts are broadcast to gateway clients holding the `operator.approvals`
13
+ * scope — the senpi agent-bridge connection — and surface in senpi-web.
14
+ *
15
+ * The gated tool set and the defaults (mode, prompt timeout) come from the
16
+ * checked-in manifest (`tool-approvals-manifest.json`, see manifest.ts) — the
17
+ * reviewed, fleet-wide control surface. Mode precedence: env
18
+ * {@link TOOL_APPROVALS_ENV_VAR} > plugin config `toolApprovals` > manifest
19
+ * `defaults.mode`.
20
+ *
21
+ * `allow-always` decisions are persisted per tool via {@link ToolApprovalStore}
22
+ * (OpenClaw treats them as one-shot for plugin approvals).
23
+ *
24
+ * Design doc: docs/mcp-tool-approvals-design.md
25
+ */
26
+ interface LoggerLike {
27
+ info(...args: unknown[]): void;
28
+ warn(...args: unknown[]): void;
29
+ }
30
+ /** Minimal shape of the OpenClaw plugin API surface the gate needs. */
31
+ export interface ToolApprovalGatePluginApi {
32
+ on(hookName: string, handler: (...args: unknown[]) => unknown, opts?: {
33
+ priority?: number;
34
+ }): void;
35
+ }
36
+ export type { ToolApprovalsMode };
37
+ /** Env var that overrides the `toolApprovals` plugin config and the manifest default. */
38
+ export declare const TOOL_APPROVALS_ENV_VAR = "SENPI_TOOL_APPROVALS";
39
+ /**
40
+ * Match a tool-registry name against the senpi MCP server and return the bare
41
+ * tool name. Covers the embedded-MCP shape (`senpi__create_position`), the
42
+ * CLI-runner backend shape (`mcp__senpi__create_position`), and openclaw's
43
+ * server-name dedupe suffix (`senpi-2__…`).
44
+ */
45
+ export declare function matchSenpiTool(toolName: unknown): string | null;
46
+ export declare function isToolApprovalsMode(value: unknown): value is ToolApprovalsMode;
47
+ /**
48
+ * Resolve the gate mode: env {@link TOOL_APPROVALS_ENV_VAR} > plugin config
49
+ * `toolApprovals` > manifest `defaults.mode`. Unrecognized values warn and
50
+ * fall through to the manifest default rather than silently disabling the gate.
51
+ */
52
+ export declare function resolveToolApprovalsMode(params: {
53
+ pluginConfig?: Record<string, unknown>;
54
+ env?: NodeJS.ProcessEnv;
55
+ logger?: LoggerLike;
56
+ manifest?: ToolApprovalsManifest;
57
+ }): ToolApprovalsMode;
58
+ /**
59
+ * Human sentence for a senpi trade tool call, mirroring senpi-web's own
60
+ * per-tool phrasing (its `getDescription` registry).
61
+ *
62
+ * Why the plugin builds this rather than leaving it to the UI: senpi-web keys
63
+ * that registry by the **bare** tool name and feeds it the call `args`, but for
64
+ * plugin approvals openclaw sends the prefixed registry name
65
+ * (`senpi__create_position`) and has **no `args` field at all` in
66
+ * `PluginApprovalRequestParamsSchema` (additionalProperties: false). So the UI
67
+ * takes its fallback branch and renders `description` verbatim, with no args
68
+ * block — meaning this string is the *only* place the user sees what they are
69
+ * approving. It must therefore carry the numbers, not just the intent.
70
+ */
71
+ export declare function describeSenpiToolCall(tool: string, params: unknown): string | undefined;
72
+ /**
73
+ * Prompt body: the human sentence when we know the tool, plus the compact raw
74
+ * args so the exact numbers are always visible (senpi-web renders no args block
75
+ * for plugin approvals — see {@link describeSenpiToolCall}). Capped to the
76
+ * gateway's description limit, which rejects (not truncates) anything longer.
77
+ */
78
+ export declare function buildApprovalDescription(tool: string, params: unknown): string;
79
+ export interface RegisterToolApprovalGateOptions {
80
+ api: ToolApprovalGatePluginApi;
81
+ mode: ToolApprovalsMode;
82
+ /** Base plugin state dir; the store file lives directly under it. */
83
+ stateDir: string;
84
+ logger?: LoggerLike;
85
+ /** Injectable for tests; defaults to the checked-in manifest. */
86
+ manifest?: ToolApprovalsManifest;
87
+ }
88
+ /**
89
+ * Register the `before_tool_call` approval gate. No-op in `off` mode.
90
+ * Returns the store (for tests/introspection) when registered.
91
+ */
92
+ export declare function registerToolApprovalGate(options: RegisterToolApprovalGateOptions): ToolApprovalStore | undefined;
93
+ //# sourceMappingURL=tool-approval-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-approval-gate.d.ts","sourceRoot":"","sources":["../../src/approvals/tool-approval-gate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,UAAU,UAAU;IAClB,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAChC;AAED,uEAAuE;AACvE,MAAM,WAAW,yBAAyB;IACxC,EAAE,CACA,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,EACxC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3B,IAAI,CAAC;CACT;AAED,YAAY,EAAE,iBAAiB,EAAE,CAAC;AAElC,yFAAyF;AACzF,eAAO,MAAM,sBAAsB,yBAAyB,CAAC;AAO7D;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,iBAAiB,CAE9E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC,GAAG,iBAAiB,CAqBpB;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CA2DvF;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAY9E;AAED,MAAM,WAAW,+BAA+B;IAC9C,GAAG,EAAE,yBAAyB,CAAC;IAC/B,IAAI,EAAE,iBAAiB,CAAC;IACxB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,iBAAiB,GAAG,SAAS,CAqD/B"}
@@ -0,0 +1,205 @@
1
+ import { ToolApprovalStore } from "./approval-store.js";
2
+ import { loadToolApprovalsManifest, } from "./manifest.js";
3
+ /** Env var that overrides the `toolApprovals` plugin config and the manifest default. */
4
+ export const TOOL_APPROVALS_ENV_VAR = "SENPI_TOOL_APPROVALS";
5
+ // Gateway schema hard limits (plugin.approval.request rejects, not truncates,
6
+ // beyond these — openclaw PLUGIN_APPROVAL_TITLE_MAX_LENGTH / _DESCRIPTION_).
7
+ const TITLE_MAX_LENGTH = 80;
8
+ const DESCRIPTION_MAX_LENGTH = 256;
9
+ /**
10
+ * Match a tool-registry name against the senpi MCP server and return the bare
11
+ * tool name. Covers the embedded-MCP shape (`senpi__create_position`), the
12
+ * CLI-runner backend shape (`mcp__senpi__create_position`), and openclaw's
13
+ * server-name dedupe suffix (`senpi-2__…`).
14
+ */
15
+ export function matchSenpiTool(toolName) {
16
+ if (typeof toolName !== "string") {
17
+ return null;
18
+ }
19
+ const match = /^(?:mcp__)?senpi(?:-\d+)?__(.+)$/.exec(toolName);
20
+ return match ? match[1] : null;
21
+ }
22
+ export function isToolApprovalsMode(value) {
23
+ return value === "off" || value === "trades" || value === "all";
24
+ }
25
+ /**
26
+ * Resolve the gate mode: env {@link TOOL_APPROVALS_ENV_VAR} > plugin config
27
+ * `toolApprovals` > manifest `defaults.mode`. Unrecognized values warn and
28
+ * fall through to the manifest default rather than silently disabling the gate.
29
+ */
30
+ export function resolveToolApprovalsMode(params) {
31
+ const manifestDefault = (params.manifest ?? loadToolApprovalsManifest()).defaults.mode;
32
+ const candidates = [
33
+ { source: `env ${TOOL_APPROVALS_ENV_VAR}`, value: params.env?.[TOOL_APPROVALS_ENV_VAR] },
34
+ { source: 'plugin config "toolApprovals"', value: params.pluginConfig?.toolApprovals },
35
+ ];
36
+ for (const candidate of candidates) {
37
+ if (candidate.value === undefined || candidate.value === null || candidate.value === "") {
38
+ continue;
39
+ }
40
+ const normalized = typeof candidate.value === "string" ? candidate.value.trim().toLowerCase() : candidate.value;
41
+ if (isToolApprovalsMode(normalized)) {
42
+ return normalized;
43
+ }
44
+ params.logger?.warn(`[senpi-tool-approvals] unrecognized ${candidate.source} value ${JSON.stringify(candidate.value)}; using manifest default "${manifestDefault}" (expected off|trades|all)`);
45
+ return manifestDefault;
46
+ }
47
+ return manifestDefault;
48
+ }
49
+ function truncate(value, maxLength) {
50
+ return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}…`;
51
+ }
52
+ function str(value) {
53
+ if (typeof value === "string" && value.trim())
54
+ return value.trim();
55
+ if (typeof value === "number" && Number.isFinite(value))
56
+ return String(value);
57
+ return undefined;
58
+ }
59
+ /**
60
+ * Human sentence for a senpi trade tool call, mirroring senpi-web's own
61
+ * per-tool phrasing (its `getDescription` registry).
62
+ *
63
+ * Why the plugin builds this rather than leaving it to the UI: senpi-web keys
64
+ * that registry by the **bare** tool name and feeds it the call `args`, but for
65
+ * plugin approvals openclaw sends the prefixed registry name
66
+ * (`senpi__create_position`) and has **no `args` field at all` in
67
+ * `PluginApprovalRequestParamsSchema` (additionalProperties: false). So the UI
68
+ * takes its fallback branch and renders `description` verbatim, with no args
69
+ * block — meaning this string is the *only* place the user sees what they are
70
+ * approving. It must therefore carry the numbers, not just the intent.
71
+ */
72
+ export function describeSenpiToolCall(tool, params) {
73
+ const p = (params ?? {});
74
+ const asset = str(p.asset) ?? str(p.coin) ?? str(p.symbol);
75
+ const strategyId = str(p.strategyId) ?? str(p.strategy_id);
76
+ const amount = str(p.amount) ?? str(p.usdAmount) ?? str(p.margin);
77
+ const leverage = str(p.leverage);
78
+ const direction = str(p.direction) ?? str(p.side);
79
+ const parts = [];
80
+ switch (tool) {
81
+ case "create_position": {
82
+ parts.push(`Open a ${direction ?? ""} position on ${asset ?? "?"}`.replace(/\s+/g, " "));
83
+ if (amount)
84
+ parts.push(`margin $${amount}`);
85
+ if (leverage)
86
+ parts.push(`${leverage}x`);
87
+ break;
88
+ }
89
+ case "close_position":
90
+ parts.push(`Close position ${str(p.positionId) ?? asset ?? ""}`.trim());
91
+ break;
92
+ case "edit_position":
93
+ parts.push(`Edit position ${str(p.positionId) ?? asset ?? ""}`.trim());
94
+ break;
95
+ case "cancel_order":
96
+ parts.push(`Cancel order ${str(p.orderId) ?? ""}`.trim());
97
+ break;
98
+ case "strategy_create":
99
+ case "strategy_create_custom_strategy":
100
+ parts.push(`Create strategy ${str(p.name) ?? asset ?? ""}`.trim());
101
+ break;
102
+ case "strategy_close":
103
+ case "strategy_close_positions":
104
+ parts.push(`Close strategy ${strategyId ?? ""}`.trim());
105
+ break;
106
+ case "strategy_update":
107
+ parts.push(`Update strategy ${strategyId ?? ""}`.trim());
108
+ break;
109
+ case "strategy_pause":
110
+ parts.push(`Pause strategy ${strategyId ?? ""}`.trim());
111
+ break;
112
+ case "strategy_top_up":
113
+ parts.push(`Add ${amount ? `$${amount} ` : ""}funds to strategy ${strategyId ?? ""}`.trim());
114
+ break;
115
+ case "strategy_withdraw_funds":
116
+ parts.push(`Withdraw ${amount ? `$${amount} ` : ""}from strategy ${strategyId ?? ""}`.trim());
117
+ break;
118
+ case "strategy_bridge_funds_from_hyperliquid_to_evm":
119
+ parts.push(`Bridge ${amount ? `$${amount} ` : ""}from Hyperliquid to EVM`.trim());
120
+ break;
121
+ case "transfer_spot_to_perps":
122
+ parts.push(`Transfer ${amount ? `$${amount} ` : ""}from spot to perps`.trim());
123
+ break;
124
+ case "send_usdc":
125
+ parts.push(`Send ${amount ? `$${amount} ` : ""}USDC to ${str(p.to) ?? "?"}`.trim());
126
+ break;
127
+ default:
128
+ return undefined;
129
+ }
130
+ return parts.filter(Boolean).join(" · ");
131
+ }
132
+ /**
133
+ * Prompt body: the human sentence when we know the tool, plus the compact raw
134
+ * args so the exact numbers are always visible (senpi-web renders no args block
135
+ * for plugin approvals — see {@link describeSenpiToolCall}). Capped to the
136
+ * gateway's description limit, which rejects (not truncates) anything longer.
137
+ */
138
+ export function buildApprovalDescription(tool, params) {
139
+ let args = "";
140
+ try {
141
+ args = params === undefined ? "" : JSON.stringify(params);
142
+ }
143
+ catch {
144
+ args = "[unserializable params]";
145
+ }
146
+ const hasArgs = Boolean(args) && args !== "{}";
147
+ const sentence = describeSenpiToolCall(tool, params);
148
+ const head = sentence ?? tool;
149
+ const base = hasArgs ? `${head} — ${args}` : head;
150
+ return truncate(base, DESCRIPTION_MAX_LENGTH);
151
+ }
152
+ /**
153
+ * Register the `before_tool_call` approval gate. No-op in `off` mode.
154
+ * Returns the store (for tests/introspection) when registered.
155
+ */
156
+ export function registerToolApprovalGate(options) {
157
+ const { api, mode, stateDir, logger } = options;
158
+ if (mode === "off") {
159
+ logger?.info("[senpi-tool-approvals] disabled (mode=off)");
160
+ return undefined;
161
+ }
162
+ const manifest = options.manifest ?? loadToolApprovalsManifest();
163
+ const tradeTools = new Set(manifest.tradeTools);
164
+ const timeoutMs = manifest.defaults.timeoutMs;
165
+ const store = new ToolApprovalStore(stateDir, logger);
166
+ logger?.info(`[senpi-tool-approvals] enabled | mode=${mode} tradeTools=${tradeTools.size} allowAlways=${store.size} timeoutMs=${timeoutMs}`);
167
+ api.on("before_tool_call", (event) => {
168
+ try {
169
+ const ev = event;
170
+ const tool = matchSenpiTool(ev?.toolName);
171
+ if (!tool) {
172
+ return undefined;
173
+ }
174
+ const isTrade = tradeTools.has(tool);
175
+ if (mode === "trades" && !isTrade) {
176
+ return undefined;
177
+ }
178
+ if (store.isAllowedAlways(tool)) {
179
+ return undefined;
180
+ }
181
+ return {
182
+ requireApproval: {
183
+ title: truncate(`Senpi trade approval: ${tool}`, TITLE_MAX_LENGTH),
184
+ description: buildApprovalDescription(tool, ev?.params),
185
+ severity: isTrade ? "critical" : "warning",
186
+ timeoutMs,
187
+ timeoutBehavior: "deny",
188
+ onResolution: (decision) => {
189
+ if (decision === "allow-always") {
190
+ store.rememberAllowAlways(tool);
191
+ logger?.info(`[senpi-tool-approvals] allow-always persisted | tool=${tool}`);
192
+ }
193
+ },
194
+ },
195
+ };
196
+ }
197
+ catch (err) {
198
+ // Never break the tool pipeline on a gate bug — fail open and log.
199
+ logger?.warn(`[senpi-tool-approvals] gate handler failed; tool proceeds ungated | err=${err instanceof Error ? err.message : String(err)}`);
200
+ return undefined;
201
+ }
202
+ });
203
+ return store;
204
+ }
205
+ //# sourceMappingURL=tool-approval-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-approval-gate.js","sourceRoot":"","sources":["../../src/approvals/tool-approval-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "defaults": {
4
+ "mode": "trades",
5
+ "timeoutMs": 180000
6
+ },
7
+ "tradeTools": [
8
+ "create_position",
9
+ "close_position",
10
+ "edit_position",
11
+ "cancel_order",
12
+ "strategy_create",
13
+ "strategy_create_custom_strategy",
14
+ "strategy_close",
15
+ "strategy_close_positions",
16
+ "strategy_update",
17
+ "strategy_pause",
18
+ "strategy_top_up",
19
+ "strategy_withdraw_funds",
20
+ "strategy_bridge_funds_from_hyperliquid_to_evm",
21
+ "transfer_spot_to_perps",
22
+ "send_usdc",
23
+ "ratchet_stop_add",
24
+ "ratchet_stop_edit",
25
+ "ratchet_stop_delete",
26
+ "user_claim_referral_rewards"
27
+ ]
28
+ }
@@ -21,7 +21,7 @@ export function formatDslTierAdvanced(e) {
21
21
  return {
22
22
  pushTitle: "🔒 Profit locked",
23
23
  pushBody: `DSL locked ${fmtUsdPnl(lockedUsd)} on ${asset}`,
24
- inApp: `🔒 ${asset} ${direction} — DSL locked in ${fmtUsdPnl(lockedUsd)} (${fmtRoe(lockedPct)})<br>` +
24
+ inApp: `🔒 ${asset} ${direction} — DSL locked in ${fmtUsdPnl(lockedUsd)} (${fmtRoe(lockedPct)}) ` +
25
25
  `Stop raised to ${fmtUsdPrice(e.newFloorPrice, asset)} · now ${fmtUsdPnl(currentPnl)} (${fmtRoe(curRoe)}) · peak ${fmtRoe(hw)} · still running, trailing for more`,
26
26
  };
27
27
  }
@@ -34,7 +34,7 @@ export function formatDslSlUpdated(e) {
34
34
  return {
35
35
  pushTitle: "📈 Stop raised",
36
36
  pushBody: `${asset} stop → ${fmtUsdPrice(e.newSLPrice, asset)}`,
37
- inApp: `${asset} — DSL stop raised to ${fmtUsdPrice(e.newSLPrice, asset)}<br>Locking in more of your gain.`,
37
+ inApp: `${asset} — DSL stop raised to ${fmtUsdPrice(e.newSLPrice, asset)} Locking in more of your gain.`,
38
38
  };
39
39
  }
40
40
  export function formatDslDeleted(e) {