@aporthq/aport-agent-guardrails 1.0.22 → 1.0.23

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/bin/openclaw CHANGED
@@ -30,6 +30,48 @@ REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
30
30
  APORT_PLUGIN_PATH="$REPO_ROOT/extensions/openclaw-aport"
31
31
  DEFAULT_CONFIG="${OPENCLAW_HOME:-$HOME/.openclaw}"
32
32
 
33
+ read_local_plugin_version() {
34
+ if ! command -v node >/dev/null 2>&1; then
35
+ return 0
36
+ fi
37
+ local package_json="$APORT_PLUGIN_PATH/package.json"
38
+ if [ ! -f "$package_json" ]; then
39
+ return 0
40
+ fi
41
+ node -e '
42
+ const fs = require("node:fs");
43
+ try {
44
+ const pkg = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
45
+ if (pkg && typeof pkg.version === "string") process.stdout.write(pkg.version);
46
+ } catch {}
47
+ ' "$package_json"
48
+ }
49
+
50
+ read_installed_plugin_version() {
51
+ if ! command -v openclaw >/dev/null 2>&1 || ! command -v node >/dev/null 2>&1; then
52
+ return 0
53
+ fi
54
+ openclaw plugins list --json 2>/dev/null | node -e '
55
+ let raw = "";
56
+ process.stdin.on("data", (chunk) => {
57
+ raw += chunk;
58
+ });
59
+ process.stdin.on("end", () => {
60
+ const jsonStart = raw.indexOf("{");
61
+ if (jsonStart < 0) return;
62
+ try {
63
+ const data = JSON.parse(raw.slice(jsonStart));
64
+ const plugin = Array.isArray(data.plugins)
65
+ ? data.plugins.find((entry) => entry && entry.id === "openclaw-aport")
66
+ : null;
67
+ if (plugin && typeof plugin.version === "string") {
68
+ process.stdout.write(plugin.version);
69
+ }
70
+ } catch {}
71
+ });
72
+ '
73
+ }
74
+
33
75
  # Parse command line arguments
34
76
  HOSTED_AGENT_ID=""
35
77
  if [ -n "$1" ]; then
@@ -210,19 +252,29 @@ if true; then
210
252
  echo " Skipping plugin installation."
211
253
  fi
212
254
  else
255
+ LOCAL_PLUGIN_VERSION="$(read_local_plugin_version)"
256
+ INSTALLED_PLUGIN_VERSION="$(read_installed_plugin_version)"
213
257
  echo ""
214
- echo " Installing plugin from: $APORT_PLUGIN_PATH"
215
- echo " (Using -l to link local directory; OpenClaw accepts only registry or --link for install.)"
216
- if openclaw plugins install -l "$APORT_PLUGIN_PATH" 2>&1; then
217
- echo " ✅ Plugin installed successfully"
258
+ if [ -n "$LOCAL_PLUGIN_VERSION" ] && [ "$LOCAL_PLUGIN_VERSION" = "$INSTALLED_PLUGIN_VERSION" ]; then
259
+ echo " Plugin version $LOCAL_PLUGIN_VERSION already installed; skipping reinstall."
218
260
  PLUGIN_INSTALLED=true
219
261
  else
220
- echo " Plugin installation failed."
221
- echo " OpenClaw did not accept the plugin bundle, so setup is stopping here"
222
- echo " instead of writing plugin config that points at a broken install."
223
- echo " Retry after fixing the bundle or install it manually:"
224
- echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
225
- exit 1
262
+ if [ -n "$INSTALLED_PLUGIN_VERSION" ]; then
263
+ echo " Existing openclaw-aport version: $INSTALLED_PLUGIN_VERSION"
264
+ fi
265
+ echo " Installing plugin from: $APORT_PLUGIN_PATH"
266
+ echo " (Using -l to link local directory; OpenClaw accepts only registry or --link for install.)"
267
+ if openclaw plugins install -l "$APORT_PLUGIN_PATH" 2>&1; then
268
+ echo " ✅ Plugin installed successfully"
269
+ PLUGIN_INSTALLED=true
270
+ else
271
+ echo " ❌ Plugin installation failed."
272
+ echo " OpenClaw did not accept the plugin bundle, so setup is stopping here"
273
+ echo " instead of writing plugin config that points at a broken install."
274
+ echo " Retry after fixing the bundle or install it manually:"
275
+ echo " openclaw plugins install -l $APORT_PLUGIN_PATH"
276
+ exit 1
277
+ fi
226
278
  fi
227
279
  echo ""
228
280
 
@@ -590,19 +642,18 @@ echo " OpenClaw (or your code) calls the guardrail with a tool name and contex
590
642
  echo " The guardrail maps that to a policy pack in external/aport-policies:"
591
643
  echo
592
644
  cat << 'TABLE'
593
- Tool name (examples) → Policy pack
594
- ------------------------------------------------
595
- git.create_pr, git.merge, git.* → code.repository.merge.v1
596
- exec.run, system.command.* → system.command.execute.v1
597
- message.send, messaging.* → messaging.message.send.v1
598
- mcp.tool.*, mcp.* mcp.tool.execute.v1
599
- agent.session.*, session.* agent.session.create.v1
600
- agent.tool.*, tool.register agent.tool.register.v1
601
- payment.refund, finance.* finance.payment.refund.v1
602
- payment.charge → finance.payment.charge.v1
603
- database.write, data.export → data.export.create.v1
645
+ Tool name (examples) → Policy pack
646
+ ------------------------------------------------
647
+ git.create_pr, git.merge, git.* → code.repository.merge.v1
648
+ exec.run, system.command.* → system.command.execute.v1
649
+ message + send/reply/broadcast → messaging.message.send.v1
650
+ message + sendAttachment/react messaging.message.send.v1
651
+ serverName__toolName, mcp__* mcp.tool.execute.v1
652
+ read, glob, grep data.file.read.v1
653
+ write, edit, multiedit data.file.write.v1
604
654
  TABLE
605
- echo " Full list: docs/TOOL_POLICY_MAPPING.md"
655
+ echo " Plugin mapping source: extensions/openclaw-aport/tool-mapping.js"
656
+ echo " Unmapped tools are allowed by default; set allowUnmappedTools: false for strict mode."
606
657
  echo
607
658
 
608
659
  # 5. Enforcement summary
package/docs/RELEASE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Release process and version policy
2
2
 
3
- **Current release:** 1.0.22 (see [CHANGELOG.md](../CHANGELOG.md)).
3
+ **Current release:** 1.0.23 (see [CHANGELOG.md](../CHANGELOG.md)).
4
4
 
5
5
  We keep **one version number** across all published packages (Node core, Python core, and every framework adapter). That avoids “core is 1.2 but CLI is 0.9” and keeps the story simple for users and support.
6
6
 
@@ -1,8 +1,8 @@
1
1
  # Tool → policy pack mapping
2
2
 
3
- OpenClaw (or any caller) invokes the guardrail with a **tool name** and **context JSON**. The guardrail maps the tool name to a **policy pack** in `external/aport-policies/` and evaluates the request against that policy and the passport.
3
+ The shell/API guardrail entrypoints invoke the guardrail with a **tool name** and **context JSON**. The guardrail maps the tool name to a **policy pack** in `external/aport-policies/` and evaluates the request against that policy and the passport.
4
4
 
5
- This mapping is implemented in `bin/aport-guardrail-api.sh` and `bin/aport-guardrail-bash.sh`. The table below is the single source of truth for documentation.
5
+ This mapping is implemented in `bin/aport-guardrail-api.sh` and `bin/aport-guardrail-bash.sh`. The OpenClaw plugin has its own host-specific mapping in `extensions/openclaw-aport/tool-mapping.js`.
6
6
 
7
7
  ## Mapping table
8
8
 
@@ -14,6 +14,20 @@ If you already have a hosted passport on aport.io, pass the `agent_id` and skip
14
14
  npx @aporthq/aport-agent-guardrails openclaw ap_your_agent_id
15
15
  ```
16
16
 
17
+ If you run `openclaw plugins install @aporthq/openclaw-aport` directly, that installs only the plugin bundle. It does not create a local passport or write the plugin config. Use the setup command above for the full APort + OpenClaw flow, or configure the plugin manually.
18
+
19
+ After a direct plugin install, the recommended next step is:
20
+
21
+ ```bash
22
+ npx @aporthq/aport-agent-guardrails openclaw
23
+ ```
24
+
25
+ Hosted passport:
26
+
27
+ ```bash
28
+ npx @aporthq/aport-agent-guardrails openclaw ap_your_agent_id
29
+ ```
30
+
17
31
  The setup command:
18
32
 
19
33
  1. Chooses your OpenClaw config directory
@@ -137,9 +151,10 @@ Common mappings include:
137
151
 
138
152
  - `exec`, `exec.run` -> `system.command.execute.v1`
139
153
  - `git.create_pr`, `git.merge`, `git.push` -> `code.repository.merge.v1`
140
- - `message.send` -> `messaging.message.send.v1`
154
+ - `message` with send-family actions like `send`, `reply`, `broadcast`, `sendAttachment`, `upload-file`, or `react` -> `messaging.message.send.v1`
141
155
  - `read`, `view`, `glob` -> `data.file.read.v1`
142
156
  - `write`, `edit`, `multiedit` -> `data.file.write.v1`
157
+ - bundle MCP tools exposed as `serverName__toolName` -> `mcp.tool.execute.v1`
143
158
 
144
159
  ## Kill switch
145
160
 
@@ -1,10 +1,17 @@
1
1
  # Changelog - APort OpenClaw Plugin
2
2
 
3
+ ## 1.0.23
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix the OpenClaw plugin and setup flow for current public OpenClaw releases. This update makes the plugin installer and setup flow more reliable, tightens tool-to-policy mapping for current OpenClaw tools like `message` and MCP bundle tools, normalizes hosted API context for file and message tools, and skips redundant plugin reinstall when the same version is already installed.
8
+
3
9
  All notable changes to the APort OpenClaw plugin will be documented in this file.
4
10
 
5
11
  ## [1.0.22] - 2026-04-13
6
12
 
7
13
  ### Fixed
14
+
8
15
  - Replaced the old shell-spawning runtime with a scanner-safe JavaScript plugin runtime for local and API evaluation.
9
16
  - Added current OpenClaw compatibility metadata in `package.json` and aligned the hook return shape with the documented `before_tool_call` contract.
10
17
  - Kept `alwaysVerifyEachToolCall` as a deprecated manifest field so existing installs continue to load during upgrade.
@@ -33,6 +33,28 @@ After setup, start OpenClaw with the generated config:
33
33
  openclaw gateway start --config ~/.openclaw/config.yaml
34
34
  ```
35
35
 
36
+ ## If you installed with `openclaw plugins install`
37
+
38
+ If you installed the plugin directly with:
39
+
40
+ ```bash
41
+ openclaw plugins install @aporthq/openclaw-aport
42
+ ```
43
+
44
+ that installs only the plugin bundle. It does not create a passport, choose API vs local mode, or write plugin config.
45
+
46
+ Run the full APort setup immediately after install:
47
+
48
+ ```bash
49
+ npx @aporthq/aport-agent-guardrails openclaw
50
+ ```
51
+
52
+ If you already have a hosted passport, use:
53
+
54
+ ```bash
55
+ npx @aporthq/aport-agent-guardrails openclaw ap_your_agent_id
56
+ ```
57
+
36
58
  ## What OpenClaw already gives you
37
59
 
38
60
  OpenClaw already ships sandboxing, tool policy, elevated exec controls, and install-time scanning. Those are real security controls, not marketing copy.
@@ -57,6 +79,12 @@ If you are working from a local checkout, install the plugin directly from the e
57
79
  openclaw plugins install -l /path/to/aport-agent-guardrails/extensions/openclaw-aport
58
80
  ```
59
81
 
82
+ That command installs only the OpenClaw plugin bundle. It does not create a passport, choose API vs local mode, or write plugin config. For a full working setup, use:
83
+
84
+ ```bash
85
+ npx @aporthq/aport-agent-guardrails openclaw
86
+ ```
87
+
60
88
  Then configure it in your OpenClaw config:
61
89
 
62
90
  ```yaml
@@ -95,10 +123,10 @@ The plugin keeps the existing OpenClaw-specific tool mappings. Common examples:
95
123
 
96
124
  - `exec`, `exec.run` -> `system.command.execute.v1`
97
125
  - `git.create_pr`, `git.merge`, `git.push` -> `code.repository.merge.v1`
98
- - `message.send` -> `messaging.message.send.v1`
126
+ - `message` with send-family actions like `send`, `reply`, `broadcast`, `sendAttachment`, `upload-file`, or `react` -> `messaging.message.send.v1`
99
127
  - `read`, `view`, `glob` -> `data.file.read.v1`
100
128
  - `write`, `edit`, `multiedit` -> `data.file.write.v1`
101
- - `mcp__*` -> `mcp.tool.execute.v1`
129
+ - bundle MCP tools exposed as `serverName__toolName` -> `mcp.tool.execute.v1`
102
130
 
103
131
  `allowUnmappedTools: true` keeps the previous OpenClaw compatibility behavior for custom skills and unmapped tools.
104
132
 
@@ -14,7 +14,15 @@ export async function verifyViaApi({ apiUrl, apiKey, policyName, context, passpo
14
14
  });
15
15
 
16
16
  if (!response.ok) {
17
- throw new Error(`API request failed: ${response.status} ${response.statusText}`);
17
+ let details = "";
18
+ try {
19
+ const text = await response.text();
20
+ if (text) details = text;
21
+ } catch {
22
+ details = "";
23
+ }
24
+ const suffix = details ? ` - ${details}` : "";
25
+ throw new Error(`API request failed: ${response.status} ${response.statusText}${suffix}`);
18
26
  }
19
27
 
20
28
  const data = await response.json();
@@ -13,7 +13,7 @@ import { homedir } from "node:os";
13
13
  import { logAuditEntry } from "./audit.js";
14
14
  import { canonicalize, formatReasons, verifyDecisionIntegrity } from "./decision.js";
15
15
  import { evaluateLocalDecision } from "./local-evaluator.js";
16
- import { mapToolToPolicy, normalizeExecContext } from "./tool-mapping.js";
16
+ import { mapToolToPolicy, normalizePolicyContext } from "./tool-mapping.js";
17
17
  import { verifyViaApi } from "./api-client.js";
18
18
 
19
19
  export { canonicalize, mapToolToPolicy, verifyDecisionIntegrity };
@@ -48,7 +48,7 @@ export default definePluginEntry({
48
48
 
49
49
  try {
50
50
  const policyName =
51
- toolName === "exec" && !mapExecToPolicy ? null : mapToolToPolicy(toolName);
51
+ toolName === "exec" && !mapExecToPolicy ? null : mapToolToPolicy(toolName, params);
52
52
 
53
53
  if (!policyName) {
54
54
  if (allowUnmappedTools) {
@@ -64,23 +64,22 @@ export default definePluginEntry({
64
64
 
65
65
  let effectivePolicyName = policyName;
66
66
  let effectiveToolName = toolName;
67
- let context =
68
- policyName === "system.command.execute.v1"
69
- ? normalizeExecContext(params, event)
70
- : (params || {});
67
+ let context = normalizePolicyContext(policyName, toolName, params, event);
71
68
 
72
69
  const delegated = parseGuardrailInvocation(
73
70
  effectivePolicyName === "system.command.execute.v1" ? context.command : null,
74
71
  );
75
72
  if (delegated) {
76
- const innerPolicy = mapToolToPolicy(delegated.innerToolName);
73
+ const innerPolicy = mapToolToPolicy(delegated.innerToolName, delegated.innerContext);
77
74
  if (innerPolicy) {
78
75
  effectivePolicyName = innerPolicy;
79
76
  effectiveToolName = delegated.innerToolName;
80
- context =
81
- innerPolicy === "system.command.execute.v1"
82
- ? normalizeExecContext(delegated.innerContext, { params: delegated.innerContext })
83
- : delegated.innerContext;
77
+ context = normalizePolicyContext(
78
+ innerPolicy,
79
+ delegated.innerToolName,
80
+ delegated.innerContext,
81
+ { params: delegated.innerContext },
82
+ );
84
83
  }
85
84
  }
86
85
 
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-aport",
3
3
  "name": "APort Guardrails",
4
4
  "description": "Deterministic pre-action authorization via APort policy enforcement. Registers before_tool_call to block disallowed tools.",
5
- "version": "1.0.22",
5
+ "version": "1.0.23",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@aporthq/openclaw-aport",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@aporthq/openclaw-aport",
9
- "version": "1.0.22",
9
+ "version": "1.0.23",
10
10
  "license": "Apache-2.0",
11
11
  "devDependencies": {
12
12
  "@types/node": "^18.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aporthq/openclaw-aport",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "OpenClaw plugin for deterministic pre-action authorization via APort guardrails",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -1,5 +1,60 @@
1
- export function mapToolToPolicy(toolName) {
2
- const tool = String(toolName ?? "").toLowerCase();
1
+ const MESSAGE_SEND_ACTIONS = new Set([
2
+ "send",
3
+ "broadcast",
4
+ "reply",
5
+ "thread-reply",
6
+ "sendwitheffect",
7
+ "sendattachment",
8
+ "upload-file",
9
+ "react",
10
+ ]);
11
+
12
+ function firstNonEmpty(...values) {
13
+ for (const value of values) {
14
+ if (typeof value === "string" && value.trim()) return value.trim();
15
+ }
16
+ return "";
17
+ }
18
+
19
+ function readAction(params) {
20
+ const src = params && typeof params === "object" ? params : {};
21
+ return firstNonEmpty(
22
+ src.action,
23
+ src.action_name,
24
+ src.actionName,
25
+ src.arguments && typeof src.arguments === "object" ? src.arguments.action : "",
26
+ src.input && typeof src.input === "object" ? src.input.action : "",
27
+ ).toLowerCase();
28
+ }
29
+
30
+ export function parseMcpToolName(toolName) {
31
+ const raw = String(toolName ?? "").trim();
32
+ if (!raw) return null;
33
+
34
+ if (raw.startsWith("mcp__")) {
35
+ const parts = raw.split("__");
36
+ if (parts.length >= 3 && parts[1] && parts.slice(2).join("__")) {
37
+ return {
38
+ serverName: parts[1],
39
+ toolName: parts.slice(2).join("__"),
40
+ };
41
+ }
42
+ }
43
+
44
+ const separatorIndex = raw.indexOf("__");
45
+ if (separatorIndex <= 0 || separatorIndex >= raw.length - 2) {
46
+ return null;
47
+ }
48
+
49
+ return {
50
+ serverName: raw.slice(0, separatorIndex),
51
+ toolName: raw.slice(separatorIndex + 2),
52
+ };
53
+ }
54
+
55
+ export function mapToolToPolicy(toolName, params) {
56
+ const rawTool = String(toolName ?? "").trim();
57
+ const tool = rawTool.toLowerCase();
3
58
 
4
59
  if (tool.match(/git\.(create_pr|merge|push|commit)/)) return "code.repository.merge.v1";
5
60
  if (tool.startsWith("git.")) return "code.repository.merge.v1";
@@ -10,9 +65,21 @@ export function mapToolToPolicy(toolName) {
10
65
  if (tool.startsWith("system.command.")) return "system.command.execute.v1";
11
66
  if (tool === "bash" || tool === "shell" || tool === "command") return "system.command.execute.v1";
12
67
 
13
- if (tool.startsWith("message.")) return "messaging.message.send.v1";
14
- if (tool.startsWith("messaging.")) return "messaging.message.send.v1";
15
- if (tool.match(/sms|whatsapp|slack|email/)) return "messaging.message.send.v1";
68
+ if (tool === "message") {
69
+ return MESSAGE_SEND_ACTIONS.has(readAction(params)) ? "messaging.message.send.v1" : null;
70
+ }
71
+ if (
72
+ tool === "message.send" ||
73
+ tool === "message.reply" ||
74
+ tool === "message.broadcast" ||
75
+ tool === "message.react" ||
76
+ tool === "messaging.message.send"
77
+ ) {
78
+ return "messaging.message.send.v1";
79
+ }
80
+ if (tool.match(/(^|[._-])(whatsapp|telegram|slack|email)([._-](send|reply|broadcast|message|react))?$/)) {
81
+ return "messaging.message.send.v1";
82
+ }
16
83
 
17
84
  if (tool === "read") return "data.file.read.v1";
18
85
  if (tool.startsWith("file.read")) return "data.file.read.v1";
@@ -24,13 +91,8 @@ export function mapToolToPolicy(toolName) {
24
91
  }
25
92
  if (tool === "todoread") return "data.file.read.v1";
26
93
  if (tool === "todowrite") return "data.file.write.v1";
27
- if (tool === "task" || tool === "taskcreate" || tool === "taskupdate" || tool === "taskstop") {
28
- return "agent.session.create.v1";
29
- }
30
94
  if (tool === "taskget" || tool === "tasklist" || tool === "taskoutput") return "data.file.read.v1";
31
- if (tool === "agent" || tool === "skill" || tool === "enterworktree") return "agent.session.create.v1";
32
95
  if (tool === "askuserquestion" || tool === "enterplanmode" || tool === "exitplanmode") return null;
33
- if (tool === "croncreate" || tool === "crondelete") return "agent.session.create.v1";
34
96
  if (tool === "cronlist") return "data.file.read.v1";
35
97
  if (tool.startsWith("file.write")) return "data.file.write.v1";
36
98
  if (tool.startsWith("file.edit")) return "data.file.write.v1";
@@ -45,25 +107,11 @@ export function mapToolToPolicy(toolName) {
45
107
  if (tool.startsWith("browser.")) return "web.browser.v1";
46
108
 
47
109
  if (tool.startsWith("mcp.")) return "mcp.tool.execute.v1";
48
- if (tool.startsWith("mcp__")) return "mcp.tool.execute.v1";
49
-
50
- if (tool.match(/agent\.session|session\.create/)) return "agent.session.create.v1";
51
- if (tool === "sessions_spawn" || tool === "sessions_send") return "agent.session.create.v1";
52
- if (tool.startsWith("session.") || tool.startsWith("sessions.")) return "agent.session.create.v1";
53
- if (tool === "cron" || tool.startsWith("cron.")) return "agent.session.create.v1";
110
+ if (parseMcpToolName(rawTool)) return "mcp.tool.execute.v1";
54
111
 
55
112
  if (tool === "gateway" || tool.startsWith("gateway.")) return "system.command.execute.v1";
56
113
  if (tool === "process" || tool.startsWith("process.")) return "system.command.execute.v1";
57
114
 
58
- if (tool.match(/agent\.tool|tool\.register/)) return "agent.tool.register.v1";
59
-
60
- if (tool.match(/payment\.refund|refund/)) return "finance.payment.refund.v1";
61
- if (tool.match(/payment\.charge|charge/)) return "finance.payment.charge.v1";
62
- if (tool.startsWith("finance.")) return "finance.payment.refund.v1";
63
-
64
- if (tool.match(/database\.(write|insert|update|delete)/)) return "data.export.create.v1";
65
- if (tool.match(/data\.export|export/)) return "data.export.create.v1";
66
-
67
115
  return null;
68
116
  }
69
117
 
@@ -87,3 +135,142 @@ export function normalizeExecContext(params, event) {
87
135
  if (params && params.workdir !== undefined && out.cwd === undefined) out.cwd = params.workdir;
88
136
  return out;
89
137
  }
138
+
139
+ export function normalizeFileContext(params) {
140
+ const src = params && typeof params === "object" ? params : {};
141
+ const filePath =
142
+ src.file_path ??
143
+ src.path ??
144
+ src.filePath ??
145
+ (src.arguments && typeof src.arguments === "object" ? src.arguments.file_path ?? src.arguments.path : null) ??
146
+ (src.input && typeof src.input === "object" ? src.input.file_path ?? src.input.path : null) ??
147
+ (src.args && typeof src.args === "object" ? src.args.file_path ?? src.args.path : null);
148
+
149
+ if (filePath == null || String(filePath).trim() === "") {
150
+ return { ...src };
151
+ }
152
+
153
+ return {
154
+ ...src,
155
+ file_path: String(filePath),
156
+ };
157
+ }
158
+
159
+ export function normalizeMessageContext(params) {
160
+ const src = params && typeof params === "object" ? params : {};
161
+ const action = readAction(src);
162
+ const firstTarget = Array.isArray(src.targets)
163
+ ? src.targets.find((value) => typeof value === "string" && value.trim())
164
+ : "";
165
+ const channelId = firstNonEmpty(
166
+ src.channel_id,
167
+ src.channelId,
168
+ src.target,
169
+ firstTarget,
170
+ src.channel,
171
+ src.to,
172
+ src.accountId,
173
+ );
174
+ const isAttachmentAction =
175
+ action === "sendattachment" ||
176
+ action === "upload-file" ||
177
+ Boolean(src.media || src.buffer || src.path || src.filePath || src.filename);
178
+ const messageType = action === "react" ? "reaction" : isAttachmentAction ? "file" : "text";
179
+ const message = firstNonEmpty(
180
+ src.message,
181
+ src.text,
182
+ src.content,
183
+ src.caption,
184
+ src.quoteText,
185
+ src.emoji,
186
+ isAttachmentAction ? src.filename : "",
187
+ );
188
+ const threadId = firstNonEmpty(src.thread_id, src.threadId);
189
+ const replyTo = firstNonEmpty(src.reply_to, src.replyTo, src.messageId, src.message_id);
190
+ const out = {
191
+ ...src,
192
+ message_type: src.message_type ?? messageType,
193
+ };
194
+
195
+ if (channelId) out.channel_id = channelId;
196
+ if (message) out.message = message;
197
+ else if (messageType === "reaction") out.message = "reaction";
198
+ else if (messageType === "file") out.message = "[attachment]";
199
+
200
+ if (threadId) out.thread_id = threadId;
201
+ if (replyTo) out.reply_to = replyTo;
202
+
203
+ if (!Array.isArray(out.attachments) && isAttachmentAction) {
204
+ out.attachments = [
205
+ {
206
+ ...(typeof src.media === "string" && src.media ? { url: src.media } : {}),
207
+ ...(typeof src.filename === "string" && src.filename ? { filename: src.filename } : {}),
208
+ },
209
+ ];
210
+ }
211
+
212
+ return out;
213
+ }
214
+
215
+ function buildMcpParameters(src) {
216
+ if (src.parameters && typeof src.parameters === "object") return src.parameters;
217
+ if (src.arguments && typeof src.arguments === "object") return src.arguments;
218
+ if (src.input && typeof src.input === "object") return src.input;
219
+ if (src.payload && typeof src.payload === "object") return src.payload;
220
+
221
+ const {
222
+ server,
223
+ server_url,
224
+ serverUrl,
225
+ server_name,
226
+ serverName,
227
+ tool,
228
+ tool_name,
229
+ toolName,
230
+ parameters,
231
+ ...rest
232
+ } = src;
233
+ return rest;
234
+ }
235
+
236
+ export function normalizeMcpContext(toolName, params) {
237
+ const src = params && typeof params === "object" ? params : {};
238
+ const parsed = parseMcpToolName(toolName);
239
+ const serverName = firstNonEmpty(
240
+ src.server,
241
+ src.server_url,
242
+ src.serverUrl,
243
+ src.server_name,
244
+ src.serverName,
245
+ src.mcp_server,
246
+ parsed?.serverName,
247
+ );
248
+ const normalizedServer =
249
+ serverName && serverName.includes("://") ? serverName : serverName ? `mcp://${serverName}` : "";
250
+ const tool = firstNonEmpty(src.tool, src.tool_name, src.toolName, src.mcp_tool, parsed?.toolName);
251
+ const out = {
252
+ ...src,
253
+ parameters: buildMcpParameters(src),
254
+ };
255
+
256
+ if (normalizedServer) out.server = normalizedServer;
257
+ if (tool) out.tool = tool;
258
+
259
+ return out;
260
+ }
261
+
262
+ export function normalizePolicyContext(policyName, toolName, params, event) {
263
+ if (policyName === "system.command.execute.v1") {
264
+ return normalizeExecContext(params, event);
265
+ }
266
+ if (policyName === "data.file.read.v1" || policyName === "data.file.write.v1") {
267
+ return normalizeFileContext(params);
268
+ }
269
+ if (policyName === "messaging.message.send.v1") {
270
+ return normalizeMessageContext(params);
271
+ }
272
+ if (policyName === "mcp.tool.execute.v1") {
273
+ return normalizeMcpContext(toolName, params);
274
+ }
275
+ return params || {};
276
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aporthq/aport-agent-guardrails",
3
- "version": "1.0.22",
3
+ "version": "1.0.23",
4
4
  "description": "Policy enforcement guardrails for OpenClaw-compatible agent frameworks",
5
5
  "workspaces": [
6
6
  "packages/*",