openclaw-airlock 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +406 -0
- package/dist/cli/register.d.ts +22 -0
- package/dist/cli/register.d.ts.map +1 -0
- package/dist/cli/register.js +229 -0
- package/dist/cli/register.js.map +1 -0
- package/dist/client.d.ts +136 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +474 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/airlockStatus.d.ts +20 -0
- package/dist/commands/airlockStatus.d.ts.map +1 -0
- package/dist/commands/airlockStatus.js +48 -0
- package/dist/commands/airlockStatus.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +104 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +41 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +112 -0
- package/dist/crypto.js.map +1 -0
- package/dist/hooks/beforeTool.d.ts +46 -0
- package/dist/hooks/beforeTool.d.ts.map +1 -0
- package/dist/hooks/beforeTool.js +112 -0
- package/dist/hooks/beforeTool.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/state.d.ts +44 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +79 -0
- package/dist/state.js.map +1 -0
- package/dist/tools/checkStatus.d.ts +42 -0
- package/dist/tools/checkStatus.d.ts.map +1 -0
- package/dist/tools/checkStatus.js +67 -0
- package/dist/tools/checkStatus.js.map +1 -0
- package/dist/tools/requestApproval.d.ts +63 -0
- package/dist/tools/requestApproval.d.ts.map +1 -0
- package/dist/tools/requestApproval.js +85 -0
- package/dist/tools/requestApproval.js.map +1 -0
- package/openclaw.plugin.json +69 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Airlock Plugin for OpenClaw — Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Registers all Airlock capabilities with the OpenClaw plugin system:
|
|
5
|
+
* - Config validation
|
|
6
|
+
* - Pairing state restoration from disk
|
|
7
|
+
* - Presence heartbeat
|
|
8
|
+
* - requestApproval tool (AI-callable)
|
|
9
|
+
* - checkStatus tool (AI-callable)
|
|
10
|
+
* - beforeTool hook (automatic enforcement with DND check)
|
|
11
|
+
* - /airlock-status command (diagnostics)
|
|
12
|
+
* - airlock setup CLI command
|
|
13
|
+
* - airlock pair CLI command
|
|
14
|
+
*/
|
|
15
|
+
import { loadAndValidateConfig } from "./config.js";
|
|
16
|
+
import { createAirlockClient } from "./client.js";
|
|
17
|
+
import { registerRequestApprovalTool } from "./tools/requestApproval.js";
|
|
18
|
+
import { registerCheckStatusTool } from "./tools/checkStatus.js";
|
|
19
|
+
import { registerBeforeToolHook } from "./hooks/beforeTool.js";
|
|
20
|
+
import { registerAirlockStatusCommand } from "./commands/airlockStatus.js";
|
|
21
|
+
import { registerCliCommands } from "./cli/register.js";
|
|
22
|
+
/**
|
|
23
|
+
* Stub for OpenClaw's definePluginEntry.
|
|
24
|
+
* Replace with actual import when the SDK is available:
|
|
25
|
+
* import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
26
|
+
*/
|
|
27
|
+
function definePluginEntry(def) {
|
|
28
|
+
return def;
|
|
29
|
+
}
|
|
30
|
+
// ── Plugin Definition ───────────────────────────────────────────
|
|
31
|
+
export default definePluginEntry({
|
|
32
|
+
id: "openclaw-airlock",
|
|
33
|
+
name: "Airlock Security Gateway",
|
|
34
|
+
description: "Enforces human-in-the-loop approval for risky AI actions via Airlock Gateway. " +
|
|
35
|
+
"Supports tool-based and hook-based enforcement with polling-based decision handling.",
|
|
36
|
+
register(api) {
|
|
37
|
+
// AIRLOCK_COMPAT_SHIM v7
|
|
38
|
+
if (!api.getConfig) {
|
|
39
|
+
api.getConfig = () => api.pluginConfig ?? {};
|
|
40
|
+
}
|
|
41
|
+
const _oCmd = api.registerCommand;
|
|
42
|
+
if (_oCmd) {
|
|
43
|
+
api.registerCommand = function (a, b) {
|
|
44
|
+
if (typeof b === "function") {
|
|
45
|
+
return _oCmd.call(api, Object.assign({}, a, { handler: b }));
|
|
46
|
+
}
|
|
47
|
+
return _oCmd.call(api, a);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
// Hook shim removed — using correct OpenClaw API: registerHook(event, handler, { name, description })
|
|
51
|
+
// END AIRLOCK_COMPAT_SHIM
|
|
52
|
+
const readyApi = api;
|
|
53
|
+
// Phase 2: Config
|
|
54
|
+
let config;
|
|
55
|
+
try {
|
|
56
|
+
config = loadAndValidateConfig(readyApi.getConfig());
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
60
|
+
console.error(`[Airlock] Plugin disabled — config error: ${msg}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Phase 3: Client
|
|
64
|
+
const client = createAirlockClient(config);
|
|
65
|
+
// Restore pairing state from disk + start presence heartbeat
|
|
66
|
+
// (async — non-blocking; plugin is usable immediately if state exists in config)
|
|
67
|
+
client.initialize().catch((err) => {
|
|
68
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69
|
+
console.warn(`[Airlock] Initialization warning: ${msg}`);
|
|
70
|
+
});
|
|
71
|
+
// Phase 4: Tool — requestApproval
|
|
72
|
+
registerRequestApprovalTool(readyApi, client, config);
|
|
73
|
+
// Phase 5: Tool — checkStatus
|
|
74
|
+
registerCheckStatusTool(readyApi, client, config);
|
|
75
|
+
// Phase 6: Hook — beforeTool (with DND check)
|
|
76
|
+
registerBeforeToolHook(readyApi, client, config);
|
|
77
|
+
// Phase 7: Command — /airlock-status
|
|
78
|
+
registerAirlockStatusCommand(readyApi, client, config);
|
|
79
|
+
// Phase 8: CLI commands — single registrar for all airlock subcommands
|
|
80
|
+
registerCliCommands(readyApi, client, config);
|
|
81
|
+
console.info(`[Airlock] Plugin loaded — enforcer=${config.enforcerId}, ` +
|
|
82
|
+
`failMode=${config.failMode}, ` +
|
|
83
|
+
`protectedTools=${config.protectedTools.length > 0 ? config.protectedTools.join(",") : "(none)"}`);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
export { loadAndValidateConfig, ConfigError } from "./config.js";
|
|
87
|
+
export { createAirlockClient } from "./client.js";
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State persistence for pairing results.
|
|
3
|
+
*
|
|
4
|
+
* After a successful pairing claim, we need to persist:
|
|
5
|
+
* - routingToken (used in artifact metadata for routing to approver)
|
|
6
|
+
* - encryptionKey (AES-256-GCM key for artifact encryption)
|
|
7
|
+
* - pairedKeys (Ed25519 public keys for decision signature verification)
|
|
8
|
+
*
|
|
9
|
+
* State is stored as a JSON file in the working directory.
|
|
10
|
+
* This is similar to the Claude Code enforcer's config.storeRoutingTokenAsync() pattern.
|
|
11
|
+
*/
|
|
12
|
+
import type { PairedKeyEntry } from "./crypto.js";
|
|
13
|
+
export interface AirlockPairingState {
|
|
14
|
+
/** Routing token from completed pairing. */
|
|
15
|
+
routingToken: string;
|
|
16
|
+
/** AES-256-GCM encryption key (base64url) derived via X25519 ECDH. */
|
|
17
|
+
encryptionKey: string;
|
|
18
|
+
/** Paired approver public keys, keyed by signerKeyId. */
|
|
19
|
+
pairedKeys: Record<string, PairedKeyEntry>;
|
|
20
|
+
/** Timestamp of when pairing was completed. */
|
|
21
|
+
pairedAt: string;
|
|
22
|
+
/** The pairing nonce used. */
|
|
23
|
+
pairingNonce: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Load persisted pairing state from disk.
|
|
27
|
+
* Returns null if no state file exists or it's corrupted.
|
|
28
|
+
*/
|
|
29
|
+
export declare function loadPairingState(): Promise<AirlockPairingState | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Save pairing state to disk.
|
|
32
|
+
* Creates the .airlock directory if it doesn't exist.
|
|
33
|
+
*/
|
|
34
|
+
export declare function savePairingState(state: AirlockPairingState): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Clear persisted pairing state (e.g. on pairing revocation).
|
|
37
|
+
*/
|
|
38
|
+
export declare function clearPairingState(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Get paired keys from persisted state.
|
|
41
|
+
* Returns an empty record if no state is available.
|
|
42
|
+
*/
|
|
43
|
+
export declare function loadPairedKeys(): Promise<Record<string, PairedKeyEntry>>;
|
|
44
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIlD,MAAM,WAAW,mBAAmB;IAClC,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;IACrB,sEAAsE;IACtE,aAAa,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC3C,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB;AAsBD;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAc5E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAOvD;AAED;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAG9E"}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State persistence for pairing results.
|
|
3
|
+
*
|
|
4
|
+
* After a successful pairing claim, we need to persist:
|
|
5
|
+
* - routingToken (used in artifact metadata for routing to approver)
|
|
6
|
+
* - encryptionKey (AES-256-GCM key for artifact encryption)
|
|
7
|
+
* - pairedKeys (Ed25519 public keys for decision signature verification)
|
|
8
|
+
*
|
|
9
|
+
* State is stored as a JSON file in the working directory.
|
|
10
|
+
* This is similar to the Claude Code enforcer's config.storeRoutingTokenAsync() pattern.
|
|
11
|
+
*/
|
|
12
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
// ── Constants ───────────────────────────────────────────────────
|
|
16
|
+
const OPENCLAW_DIR = ".openclaw";
|
|
17
|
+
const STATE_DIR = ".airlock";
|
|
18
|
+
const STATE_FILE = "pairing-state.json";
|
|
19
|
+
// ── State Operations ────────────────────────────────────────────
|
|
20
|
+
/**
|
|
21
|
+
* Returns a stable state file path: ~/.openclaw/.airlock/pairing-state.json
|
|
22
|
+
*
|
|
23
|
+
* Uses homedir() instead of process.cwd() so the pairing state is found
|
|
24
|
+
* regardless of which user or working directory runs the CLI command.
|
|
25
|
+
* Falls back to OPENCLAW_HOME env var if set.
|
|
26
|
+
*/
|
|
27
|
+
function getStatePath() {
|
|
28
|
+
const base = process.env.OPENCLAW_HOME ?? join(homedir(), OPENCLAW_DIR);
|
|
29
|
+
return join(base, STATE_DIR, STATE_FILE);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Load persisted pairing state from disk.
|
|
33
|
+
* Returns null if no state file exists or it's corrupted.
|
|
34
|
+
*/
|
|
35
|
+
export async function loadPairingState() {
|
|
36
|
+
try {
|
|
37
|
+
const raw = await readFile(getStatePath(), "utf-8");
|
|
38
|
+
const state = JSON.parse(raw);
|
|
39
|
+
// Basic validation
|
|
40
|
+
if (!state.routingToken || !state.encryptionKey) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return state;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Save pairing state to disk.
|
|
51
|
+
* Creates the .airlock directory if it doesn't exist.
|
|
52
|
+
*/
|
|
53
|
+
export async function savePairingState(state) {
|
|
54
|
+
const statePath = getStatePath();
|
|
55
|
+
const dir = dirname(statePath);
|
|
56
|
+
await mkdir(dir, { recursive: true });
|
|
57
|
+
await writeFile(statePath, JSON.stringify(state, null, 2), "utf-8");
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clear persisted pairing state (e.g. on pairing revocation).
|
|
61
|
+
*/
|
|
62
|
+
export async function clearPairingState() {
|
|
63
|
+
try {
|
|
64
|
+
const { unlink } = await import("node:fs/promises");
|
|
65
|
+
await unlink(getStatePath());
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// File might not exist, that's fine
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get paired keys from persisted state.
|
|
73
|
+
* Returns an empty record if no state is available.
|
|
74
|
+
*/
|
|
75
|
+
export async function loadPairedKeys() {
|
|
76
|
+
const state = await loadPairingState();
|
|
77
|
+
return state?.pairedKeys ?? {};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAkBlC,mEAAmE;AAEnE,MAAM,YAAY,GAAG,WAAW,CAAC;AACjC,MAAM,SAAS,GAAG,UAAU,CAAC;AAC7B,MAAM,UAAU,GAAG,oBAAoB,CAAC;AAExC,mEAAmE;AAEnE;;;;;;GAMG;AACH,SAAS,YAAY;IACnB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;IACxE,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QAErD,mBAAmB;QACnB,IAAI,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAA0B;IAC/D,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE/B,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AACtE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;QACpD,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,KAAK,GAAG,MAAM,gBAAgB,EAAE,CAAC;IACvC,OAAO,KAAK,EAAE,UAAU,IAAI,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: checkStatus — AI-callable tool to check an existing exchange status.
|
|
3
|
+
*
|
|
4
|
+
* The agent can use this to follow up on a previously submitted approval
|
|
5
|
+
* request and check whether a decision has been made.
|
|
6
|
+
*/
|
|
7
|
+
import type { AirlockClient } from "../client.js";
|
|
8
|
+
import type { AirlockConfig } from "../config.js";
|
|
9
|
+
/** Input schema for the checkStatus tool. */
|
|
10
|
+
export interface CheckStatusInput {
|
|
11
|
+
/** The exchange request ID from a previous approval request. */
|
|
12
|
+
requestId: string;
|
|
13
|
+
}
|
|
14
|
+
/** Output schema for the checkStatus tool. */
|
|
15
|
+
export interface CheckStatusOutput {
|
|
16
|
+
/** The exchange state (e.g. "Pending", "Decided", "Expired", "Withdrawn"). */
|
|
17
|
+
state: string;
|
|
18
|
+
/** Human-readable message. */
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
/** Tool definition for OpenClaw registration. */
|
|
22
|
+
export declare const checkStatusToolDef: {
|
|
23
|
+
name: string;
|
|
24
|
+
description: string;
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object";
|
|
27
|
+
required: readonly ["requestId"];
|
|
28
|
+
properties: {
|
|
29
|
+
requestId: {
|
|
30
|
+
type: "string";
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Register the checkStatus tool with the OpenClaw plugin API.
|
|
38
|
+
*/
|
|
39
|
+
export declare function registerCheckStatusTool(api: {
|
|
40
|
+
registerTool: (def: unknown, handler: (input: unknown) => Promise<unknown>) => void;
|
|
41
|
+
}, client: AirlockClient, _config: AirlockConfig): void;
|
|
42
|
+
//# sourceMappingURL=checkStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkStatus.d.ts","sourceRoot":"","sources":["../../src/tools/checkStatus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC/B,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,8CAA8C;AAC9C,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iDAAiD;AACjD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;CAe9B,CAAC;AAEF;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE;IAAE,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAAE,EAC5F,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,aAAa,GACrB,IAAI,CAyCN"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: checkStatus — AI-callable tool to check an existing exchange status.
|
|
3
|
+
*
|
|
4
|
+
* The agent can use this to follow up on a previously submitted approval
|
|
5
|
+
* request and check whether a decision has been made.
|
|
6
|
+
*/
|
|
7
|
+
import { AirlockGatewayError } from "@airlockapp/gateway-sdk";
|
|
8
|
+
/** Tool definition for OpenClaw registration. */
|
|
9
|
+
export const checkStatusToolDef = {
|
|
10
|
+
name: "airlock_check_status",
|
|
11
|
+
description: "Check the status of a previously submitted Airlock approval request. " +
|
|
12
|
+
"Use the requestId returned from airlock_request_approval.",
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: "object",
|
|
15
|
+
required: ["requestId"],
|
|
16
|
+
properties: {
|
|
17
|
+
requestId: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "The exchange request ID from a previous approval request",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Register the checkStatus tool with the OpenClaw plugin API.
|
|
26
|
+
*/
|
|
27
|
+
export function registerCheckStatusTool(api, client, _config) {
|
|
28
|
+
api.registerTool(checkStatusToolDef, async (rawInput) => {
|
|
29
|
+
const input = rawInput;
|
|
30
|
+
if (!input.requestId?.trim()) {
|
|
31
|
+
return {
|
|
32
|
+
state: "error",
|
|
33
|
+
message: "requestId is required",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const status = await client.getExchangeStatus(input.requestId);
|
|
38
|
+
let message = `Exchange ${input.requestId}: ${status.state}`;
|
|
39
|
+
if (status.state === "Decided") {
|
|
40
|
+
message += " — a decision has been made.";
|
|
41
|
+
}
|
|
42
|
+
else if (status.state === "Pending") {
|
|
43
|
+
message += " — waiting for approver response.";
|
|
44
|
+
}
|
|
45
|
+
else if (status.state === "Expired") {
|
|
46
|
+
message += " — the request has expired.";
|
|
47
|
+
}
|
|
48
|
+
else if (status.state === "Withdrawn") {
|
|
49
|
+
message += " — the request was withdrawn.";
|
|
50
|
+
}
|
|
51
|
+
return { state: status.state, message };
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err instanceof AirlockGatewayError && err.statusCode === 404) {
|
|
55
|
+
return {
|
|
56
|
+
state: "not_found",
|
|
57
|
+
message: `Exchange ${input.requestId} not found. It may have expired or been withdrawn.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
state: "error",
|
|
62
|
+
message: `Failed to check status: ${err instanceof Error ? err.message : String(err)}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=checkStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkStatus.js","sourceRoot":"","sources":["../../src/tools/checkStatus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAkB9D,iDAAiD;AACjD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,IAAI,EAAE,sBAAsB;IAC5B,WAAW,EACT,uEAAuE;QACvE,2DAA2D;IAC7D,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,QAAQ,EAAE,CAAC,WAAW,CAAU;QAChC,UAAU,EAAE;YACV,SAAS,EAAE;gBACT,IAAI,EAAE,QAAiB;gBACvB,WAAW,EAAE,0DAA0D;aACxE;SACF;KACF;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,uBAAuB,CACrC,GAA4F,EAC5F,MAAqB,EACrB,OAAsB;IAEtB,GAAG,CAAC,YAAY,CAAC,kBAAkB,EAAE,KAAK,EAAE,QAAiB,EAAE,EAAE;QAC/D,MAAM,KAAK,GAAG,QAA4B,CAAC;QAE3C,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC;YAC7B,OAAO;gBACL,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,uBAAuB;aACL,CAAC;QAChC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAE/D,IAAI,OAAO,GAAG,YAAY,KAAK,CAAC,SAAS,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC;YAE7D,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO,IAAI,8BAA8B,CAAC;YAC5C,CAAC;iBAAM,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACtC,OAAO,IAAI,mCAAmC,CAAC;YACjD,CAAC;iBAAM,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;gBACtC,OAAO,IAAI,6BAA6B,CAAC;YAC3C,CAAC;iBAAM,IAAI,MAAM,CAAC,KAAK,KAAK,WAAW,EAAE,CAAC;gBACxC,OAAO,IAAI,+BAA+B,CAAC;YAC7C,CAAC;YAED,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAA8B,CAAC;QACtE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,mBAAmB,IAAI,GAAG,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;gBACjE,OAAO;oBACL,KAAK,EAAE,WAAW;oBAClB,OAAO,EAAE,YAAY,KAAK,CAAC,SAAS,oDAAoD;iBAC7D,CAAC;YAChC,CAAC;YAED,OAAO;gBACL,KAAK,EAAE,OAAO;gBACd,OAAO,EAAE,2BAA2B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;aAC3D,CAAC;QAChC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: requestApproval — AI-callable tool for explicit approval requests.
|
|
3
|
+
*
|
|
4
|
+
* The agent calls this tool when it wants to get approval for an action
|
|
5
|
+
* before executing it. Unlike the beforeTool hook (which intercepts
|
|
6
|
+
* automatically), this tool gives the agent explicit control.
|
|
7
|
+
*
|
|
8
|
+
* Respects DND (Do Not Disturb) policies — if DND is active, auto-approves.
|
|
9
|
+
*/
|
|
10
|
+
import type { AirlockClient } from "../client.js";
|
|
11
|
+
import type { AirlockConfig } from "../config.js";
|
|
12
|
+
/** Input schema for the requestApproval tool. */
|
|
13
|
+
export interface RequestApprovalInput {
|
|
14
|
+
/** The action requiring approval (e.g. "deploy to production"). */
|
|
15
|
+
action: string;
|
|
16
|
+
/** Why this action needs approval. */
|
|
17
|
+
reason: string;
|
|
18
|
+
/** Additional context about the action. */
|
|
19
|
+
context?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Output schema for the requestApproval tool. */
|
|
22
|
+
export interface RequestApprovalOutput {
|
|
23
|
+
/** The decision: "approved", "rejected", or "timeout". */
|
|
24
|
+
decision: "approved" | "rejected" | "timeout";
|
|
25
|
+
/** The exchange request ID (for follow-up status checks). */
|
|
26
|
+
requestId: string;
|
|
27
|
+
/** Optional message from the approver or system. */
|
|
28
|
+
message?: string;
|
|
29
|
+
}
|
|
30
|
+
/** Tool definition for OpenClaw registration. */
|
|
31
|
+
export declare const requestApprovalToolDef: {
|
|
32
|
+
name: string;
|
|
33
|
+
description: string;
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object";
|
|
36
|
+
required: readonly ["action", "reason"];
|
|
37
|
+
properties: {
|
|
38
|
+
action: {
|
|
39
|
+
type: "string";
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
reason: {
|
|
43
|
+
type: "string";
|
|
44
|
+
description: string;
|
|
45
|
+
};
|
|
46
|
+
context: {
|
|
47
|
+
type: "string";
|
|
48
|
+
description: string;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Register the requestApproval tool with the OpenClaw plugin API.
|
|
55
|
+
*
|
|
56
|
+
* @param api OpenClaw plugin API (api.registerTool)
|
|
57
|
+
* @param client AirlockClient instance
|
|
58
|
+
* @param config Validated AirlockConfig
|
|
59
|
+
*/
|
|
60
|
+
export declare function registerRequestApprovalTool(api: {
|
|
61
|
+
registerTool: (def: unknown, handler: (input: unknown) => Promise<unknown>) => void;
|
|
62
|
+
}, client: AirlockClient, config: AirlockConfig): void;
|
|
63
|
+
//# sourceMappingURL=requestApproval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestApproval.d.ts","sourceRoot":"","sources":["../../src/tools/requestApproval.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,cAAc,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,kDAAkD;AAClD,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,CAAC;IAC9C,6DAA6D;IAC7D,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,iDAAiD;AACjD,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;CAyBlC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,GAAG,EAAE;IAAE,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CAAE,EAC5F,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,aAAa,GACpB,IAAI,CA2CN"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool: requestApproval — AI-callable tool for explicit approval requests.
|
|
3
|
+
*
|
|
4
|
+
* The agent calls this tool when it wants to get approval for an action
|
|
5
|
+
* before executing it. Unlike the beforeTool hook (which intercepts
|
|
6
|
+
* automatically), this tool gives the agent explicit control.
|
|
7
|
+
*
|
|
8
|
+
* Respects DND (Do Not Disturb) policies — if DND is active, auto-approves.
|
|
9
|
+
*/
|
|
10
|
+
/** Tool definition for OpenClaw registration. */
|
|
11
|
+
export const requestApprovalToolDef = {
|
|
12
|
+
name: "airlock_request_approval",
|
|
13
|
+
description: "Request human approval for an action via Airlock. " +
|
|
14
|
+
"Use this before executing high-risk operations like deployments, " +
|
|
15
|
+
"data mutations, or system configuration changes. " +
|
|
16
|
+
"The request will be sent to the mobile approver app.",
|
|
17
|
+
inputSchema: {
|
|
18
|
+
type: "object",
|
|
19
|
+
required: ["action", "reason"],
|
|
20
|
+
properties: {
|
|
21
|
+
action: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "The action requiring approval (e.g. 'deploy to production', 'delete database')",
|
|
24
|
+
},
|
|
25
|
+
reason: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Why this action needs approval",
|
|
28
|
+
},
|
|
29
|
+
context: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Additional context about the action (optional)",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Register the requestApproval tool with the OpenClaw plugin API.
|
|
38
|
+
*
|
|
39
|
+
* @param api OpenClaw plugin API (api.registerTool)
|
|
40
|
+
* @param client AirlockClient instance
|
|
41
|
+
* @param config Validated AirlockConfig
|
|
42
|
+
*/
|
|
43
|
+
export function registerRequestApprovalTool(api, client, config) {
|
|
44
|
+
api.registerTool(requestApprovalToolDef, async (rawInput) => {
|
|
45
|
+
const input = rawInput;
|
|
46
|
+
// Check DND (Do Not Disturb) policies before sending approval request
|
|
47
|
+
const dndActive = await client.isDndActive();
|
|
48
|
+
if (dndActive) {
|
|
49
|
+
return {
|
|
50
|
+
decision: "approved",
|
|
51
|
+
requestId: "",
|
|
52
|
+
message: "Auto-approved — Do Not Disturb mode is active.",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const result = await client.requestApproval({
|
|
56
|
+
actionType: input.action,
|
|
57
|
+
commandText: input.reason,
|
|
58
|
+
description: `Approval requested: ${input.action}`,
|
|
59
|
+
context: input.context,
|
|
60
|
+
});
|
|
61
|
+
const output = {
|
|
62
|
+
decision: result.decision,
|
|
63
|
+
requestId: result.requestId,
|
|
64
|
+
};
|
|
65
|
+
// Build a meaningful message
|
|
66
|
+
if (result.decision === "approved") {
|
|
67
|
+
output.message = "Action approved by the human approver. Proceed.";
|
|
68
|
+
}
|
|
69
|
+
else if (result.decision === "rejected") {
|
|
70
|
+
output.message = result.reason ?? "Action rejected by the approver.";
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// timeout
|
|
74
|
+
if (config.failMode === "open") {
|
|
75
|
+
output.message = "Approval timed out. Proceeding (fail-open mode).";
|
|
76
|
+
output.decision = "approved"; // Override to approved in fail-open
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
output.message = "Approval timed out. Action blocked (fail-closed mode).";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return output;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=requestApproval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requestApproval.js","sourceRoot":"","sources":["../../src/tools/requestApproval.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyBH,iDAAiD;AACjD,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,IAAI,EAAE,0BAA0B;IAChC,WAAW,EACT,oDAAoD;QACpD,mEAAmE;QACnE,mDAAmD;QACnD,sDAAsD;IACxD,WAAW,EAAE;QACX,IAAI,EAAE,QAAiB;QACvB,QAAQ,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAU;QACvC,UAAU,EAAE;YACV,MAAM,EAAE;gBACN,IAAI,EAAE,QAAiB;gBACvB,WAAW,EAAE,gFAAgF;aAC9F;YACD,MAAM,EAAE;gBACN,IAAI,EAAE,QAAiB;gBACvB,WAAW,EAAE,gCAAgC;aAC9C;YACD,OAAO,EAAE;gBACP,IAAI,EAAE,QAAiB;gBACvB,WAAW,EAAE,gDAAgD;aAC9D;SACF;KACF;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CACzC,GAA4F,EAC5F,MAAqB,EACrB,MAAqB;IAErB,GAAG,CAAC,YAAY,CAAC,sBAAsB,EAAE,KAAK,EAAE,QAAiB,EAAE,EAAE;QACnE,MAAM,KAAK,GAAG,QAAgC,CAAC;QAE/C,sEAAsE;QACtE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;QAC7C,IAAI,SAAS,EAAE,CAAC;YACd,OAAO;gBACL,QAAQ,EAAE,UAAU;gBACpB,SAAS,EAAE,EAAE;gBACb,OAAO,EAAE,gDAAgD;aAC1B,CAAC;QACpC,CAAC;QAED,MAAM,MAAM,GAAa,MAAM,MAAM,CAAC,eAAe,CAAC;YACpD,UAAU,EAAE,KAAK,CAAC,MAAM;YACxB,WAAW,EAAE,KAAK,CAAC,MAAM;YACzB,WAAW,EAAE,uBAAuB,KAAK,CAAC,MAAM,EAAE;YAClD,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,CAAC,CAAC;QAEH,MAAM,MAAM,GAA0B;YACpC,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,SAAS,EAAE,MAAM,CAAC,SAAS;SAC5B,CAAC;QAEF,6BAA6B;QAC7B,IAAI,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YACnC,MAAM,CAAC,OAAO,GAAG,iDAAiD,CAAC;QACrE,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,UAAU,EAAE,CAAC;YAC1C,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,kCAAkC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,UAAU;YACV,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;gBAC/B,MAAM,CAAC,OAAO,GAAG,kDAAkD,CAAC;gBACpE,MAAM,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC,oCAAoC;YACpE,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,OAAO,GAAG,wDAAwD,CAAC;YAC5E,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "openclaw-airlock",
|
|
3
|
+
"name": "Airlock Security Gateway",
|
|
4
|
+
"version": "1.0.2",
|
|
5
|
+
"description": "Enforces human-in-the-loop approval for risky AI actions via Airlock Gateway",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"required": ["gatewayUrl", "enforcerId"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"gatewayUrl": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "Airlock Gateway base URL (e.g. https://gw.airlocks.io)"
|
|
13
|
+
},
|
|
14
|
+
"enforcerId": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Unique identifier for this enforcer instance"
|
|
17
|
+
},
|
|
18
|
+
"pat": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"description": "Personal Access Token for user authentication"
|
|
21
|
+
},
|
|
22
|
+
"clientId": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "Enforcer App Client ID"
|
|
25
|
+
},
|
|
26
|
+
"clientSecret": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Enforcer App Client Secret"
|
|
29
|
+
},
|
|
30
|
+
"pairingCode": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Pre-generated device pairing code"
|
|
33
|
+
},
|
|
34
|
+
"workspaceName": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Human-readable workspace name",
|
|
37
|
+
"default": "OpenClaw Workspace"
|
|
38
|
+
},
|
|
39
|
+
"timeoutMs": {
|
|
40
|
+
"type": "number",
|
|
41
|
+
"description": "Approval timeout in milliseconds",
|
|
42
|
+
"default": 300000
|
|
43
|
+
},
|
|
44
|
+
"pollIntervalMs": {
|
|
45
|
+
"type": "number",
|
|
46
|
+
"description": "Decision poll interval in milliseconds (minimum 1000)",
|
|
47
|
+
"default": 3000
|
|
48
|
+
},
|
|
49
|
+
"failMode": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": ["open", "closed"],
|
|
52
|
+
"description": "Behavior on timeout or error: open = allow, closed = block",
|
|
53
|
+
"default": "closed"
|
|
54
|
+
},
|
|
55
|
+
"protectedTools": {
|
|
56
|
+
"type": "array",
|
|
57
|
+
"items": { "type": "string" },
|
|
58
|
+
"description": "List of tool names requiring approval (empty = none protected)",
|
|
59
|
+
"default": []
|
|
60
|
+
},
|
|
61
|
+
"executionMode": {
|
|
62
|
+
"type": "string",
|
|
63
|
+
"enum": ["poll", "webhook"],
|
|
64
|
+
"description": "How to wait for decisions: poll (default) or webhook (future)",
|
|
65
|
+
"default": "poll"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclaw-airlock",
|
|
3
|
+
"version": "0.4.7",
|
|
4
|
+
"description": "Airlock security gateway plugin for OpenClaw — enforces human-in-the-loop approval for AI tool use",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "Airlock",
|
|
10
|
+
"homepage": "https://airlockapp.io",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/airlockapp/gateway-clients.git",
|
|
14
|
+
"directory": "src/openclaw-airlock"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/airlockapp/gateway-clients/issues"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"airlock",
|
|
21
|
+
"openclaw",
|
|
22
|
+
"security",
|
|
23
|
+
"human-in-the-loop",
|
|
24
|
+
"approval",
|
|
25
|
+
"enforcer",
|
|
26
|
+
"gateway",
|
|
27
|
+
"harp"
|
|
28
|
+
],
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"openclaw.plugin.json"
|
|
32
|
+
],
|
|
33
|
+
"openclaw": {
|
|
34
|
+
"extensions": [
|
|
35
|
+
"./dist/index.js"
|
|
36
|
+
],
|
|
37
|
+
"compat": {
|
|
38
|
+
"pluginApi": ">=2026.3.0",
|
|
39
|
+
"minGatewayVersion": "2026.3.0"
|
|
40
|
+
},
|
|
41
|
+
"build": {
|
|
42
|
+
"openclawVersion": "2026.3.12"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"dev": "tsc --watch",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"prepublishOnly": "npm run build"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@airlockapp/gateway-sdk": "^0.4.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^22.0.0",
|
|
56
|
+
"typescript": "^5.3.0"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=18.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|