@coffeexdev/openclaw-sentinel 0.1.5 → 0.1.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/dist/tool.d.ts +3 -0
- package/dist/tool.js +12 -9
- package/dist/toolSchema.js +2 -2
- package/dist/validator.js +69 -54
- package/package.json +4 -4
package/dist/tool.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { AnyAgentTool } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { Static } from "@sinclair/typebox";
|
|
2
3
|
import { WatcherManager } from "./watcherManager.js";
|
|
4
|
+
import { SentinelToolSchema } from "./toolSchema.js";
|
|
5
|
+
export type SentinelToolParams = Static<typeof SentinelToolSchema>;
|
|
3
6
|
type RegisterToolFn = (tool: AnyAgentTool) => void;
|
|
4
7
|
export declare function registerSentinelControl(registerTool: RegisterToolFn, manager: WatcherManager): void;
|
|
5
8
|
export {};
|
package/dist/tool.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { jsonResult } from "openclaw/plugin-sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { Value } from "@sinclair/typebox/value";
|
|
3
3
|
import { SentinelToolSchema } from "./toolSchema.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
function validateParams(params) {
|
|
5
|
+
const candidate = (params ?? {});
|
|
6
|
+
if (!Value.Check(SentinelToolSchema, candidate)) {
|
|
7
|
+
const first = [...Value.Errors(SentinelToolSchema, candidate)][0];
|
|
8
|
+
const where = first?.path || "(root)";
|
|
9
|
+
const why = first?.message || "Invalid parameters";
|
|
10
|
+
throw new Error(`Invalid sentinel_control parameters at ${where}: ${why}`);
|
|
11
|
+
}
|
|
12
|
+
return candidate;
|
|
13
|
+
}
|
|
11
14
|
export function registerSentinelControl(registerTool, manager) {
|
|
12
15
|
registerTool({
|
|
13
16
|
name: "sentinel_control",
|
|
@@ -15,7 +18,7 @@ export function registerSentinelControl(registerTool, manager) {
|
|
|
15
18
|
description: "Create/manage sentinel watchers",
|
|
16
19
|
parameters: SentinelToolSchema,
|
|
17
20
|
async execute(_toolCallId, params) {
|
|
18
|
-
const payload =
|
|
21
|
+
const payload = validateParams(params);
|
|
19
22
|
switch (payload.action) {
|
|
20
23
|
case "create":
|
|
21
24
|
return jsonResult(await manager.create(payload.watcher));
|
package/dist/toolSchema.js
CHANGED
|
@@ -24,7 +24,7 @@ const FireConfigSchema = Type.Object({
|
|
|
24
24
|
}),
|
|
25
25
|
eventName: Type.String({ description: "Event name included in the dispatched payload" }),
|
|
26
26
|
payloadTemplate: Type.Record(Type.String(), Type.Union([Type.String(), Type.Number(), Type.Boolean(), Type.Null()]), {
|
|
27
|
-
description: "Key-value template for the webhook payload. Supports {
|
|
27
|
+
description: "Key-value template for the webhook payload. Supports ${...} interpolation from matched response data.",
|
|
28
28
|
}),
|
|
29
29
|
});
|
|
30
30
|
const RetryPolicySchema = Type.Object({
|
|
@@ -74,4 +74,4 @@ export const SentinelToolSchema = Type.Object({
|
|
|
74
74
|
], { description: "The action to perform" }),
|
|
75
75
|
id: Type.Optional(Type.String({ description: "Watcher ID (required for enable/disable/remove/status)" })),
|
|
76
76
|
watcher: Type.Optional(WatcherSchema),
|
|
77
|
-
});
|
|
77
|
+
}, { additionalProperties: false });
|
package/dist/validator.js
CHANGED
|
@@ -1,63 +1,62 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { Value } from "@sinclair/typebox/value";
|
|
2
3
|
const codeyKeyPattern = /(script|code|eval|handler|function|import|require)/i;
|
|
3
4
|
const codeyValuePattern = /(=>|\bfunction\b|\bimport\s+|\brequire\s*\(|\beval\s*\()/i;
|
|
4
|
-
const
|
|
5
|
-
.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"changed",
|
|
5
|
+
const ConditionSchema = Type.Object({
|
|
6
|
+
path: Type.String({ minLength: 1 }),
|
|
7
|
+
op: Type.Union([
|
|
8
|
+
Type.Literal("eq"),
|
|
9
|
+
Type.Literal("neq"),
|
|
10
|
+
Type.Literal("gt"),
|
|
11
|
+
Type.Literal("gte"),
|
|
12
|
+
Type.Literal("lt"),
|
|
13
|
+
Type.Literal("lte"),
|
|
14
|
+
Type.Literal("exists"),
|
|
15
|
+
Type.Literal("absent"),
|
|
16
|
+
Type.Literal("contains"),
|
|
17
|
+
Type.Literal("matches"),
|
|
18
|
+
Type.Literal("changed"),
|
|
19
19
|
]),
|
|
20
|
-
value:
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
maxRetries:
|
|
47
|
-
baseMs:
|
|
48
|
-
maxMs:
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
})
|
|
54
|
-
.strict();
|
|
20
|
+
value: Type.Optional(Type.Unknown()),
|
|
21
|
+
}, { additionalProperties: false });
|
|
22
|
+
const WatcherSchema = Type.Object({
|
|
23
|
+
id: Type.String({ minLength: 1 }),
|
|
24
|
+
skillId: Type.String({ minLength: 1 }),
|
|
25
|
+
enabled: Type.Boolean(),
|
|
26
|
+
strategy: Type.Union([
|
|
27
|
+
Type.Literal("http-poll"),
|
|
28
|
+
Type.Literal("websocket"),
|
|
29
|
+
Type.Literal("sse"),
|
|
30
|
+
Type.Literal("http-long-poll"),
|
|
31
|
+
]),
|
|
32
|
+
endpoint: Type.String({ minLength: 1 }),
|
|
33
|
+
method: Type.Optional(Type.Union([Type.Literal("GET"), Type.Literal("POST")])),
|
|
34
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
35
|
+
body: Type.Optional(Type.String()),
|
|
36
|
+
intervalMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
37
|
+
timeoutMs: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
38
|
+
match: Type.Union([Type.Literal("all"), Type.Literal("any")]),
|
|
39
|
+
conditions: Type.Array(ConditionSchema, { minItems: 1 }),
|
|
40
|
+
fire: Type.Object({
|
|
41
|
+
webhookPath: Type.String({ pattern: "^/" }),
|
|
42
|
+
eventName: Type.String({ minLength: 1 }),
|
|
43
|
+
payloadTemplate: Type.Record(Type.String(), Type.Union([Type.String(), Type.Number(), Type.Boolean(), Type.Null()])),
|
|
44
|
+
}, { additionalProperties: false }),
|
|
45
|
+
retry: Type.Object({
|
|
46
|
+
maxRetries: Type.Integer({ minimum: 0, maximum: 20 }),
|
|
47
|
+
baseMs: Type.Integer({ minimum: 50, maximum: 60000 }),
|
|
48
|
+
maxMs: Type.Integer({ minimum: 100, maximum: 300000 }),
|
|
49
|
+
}, { additionalProperties: false }),
|
|
50
|
+
fireOnce: Type.Optional(Type.Boolean()),
|
|
51
|
+
metadata: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
52
|
+
}, { additionalProperties: false });
|
|
55
53
|
function scanNoCodeLike(input, parentKey = "") {
|
|
56
54
|
if (input === null || input === undefined)
|
|
57
55
|
return;
|
|
58
56
|
if (typeof input === "string") {
|
|
59
|
-
if (codeyValuePattern.test(input))
|
|
57
|
+
if (codeyValuePattern.test(input)) {
|
|
60
58
|
throw new Error(`Code-like value rejected at ${parentKey || "<root>"}`);
|
|
59
|
+
}
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
if (Array.isArray(input)) {
|
|
@@ -66,13 +65,29 @@ function scanNoCodeLike(input, parentKey = "") {
|
|
|
66
65
|
}
|
|
67
66
|
if (typeof input === "object") {
|
|
68
67
|
for (const [key, value] of Object.entries(input)) {
|
|
69
|
-
if (codeyKeyPattern.test(key))
|
|
68
|
+
if (codeyKeyPattern.test(key)) {
|
|
70
69
|
throw new Error(`Code-like field rejected: ${parentKey ? `${parentKey}.` : ""}${key}`);
|
|
70
|
+
}
|
|
71
71
|
scanNoCodeLike(value, parentKey ? `${parentKey}.${key}` : key);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
export function validateWatcherDefinition(input) {
|
|
76
76
|
scanNoCodeLike(input);
|
|
77
|
-
|
|
77
|
+
if (!Value.Check(WatcherSchema, input)) {
|
|
78
|
+
const first = [...Value.Errors(WatcherSchema, input)][0];
|
|
79
|
+
const where = first?.path || "(root)";
|
|
80
|
+
const why = first?.message || "Invalid watcher definition";
|
|
81
|
+
throw new Error(`Invalid watcher definition at ${where}: ${why}`);
|
|
82
|
+
}
|
|
83
|
+
const endpoint = input.endpoint;
|
|
84
|
+
try {
|
|
85
|
+
if (typeof endpoint !== "string")
|
|
86
|
+
throw new Error("endpoint must be a string");
|
|
87
|
+
new URL(endpoint);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
throw new Error("Invalid watcher definition at /endpoint: Invalid URL");
|
|
91
|
+
}
|
|
92
|
+
return input;
|
|
78
93
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffeexdev/openclaw-sentinel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Secure declarative gateway-native watcher plugin for OpenClaw",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
@@ -44,8 +44,7 @@
|
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@sinclair/typebox": "^0.34.48",
|
|
46
46
|
"re2-wasm": "^1.0.2",
|
|
47
|
-
"ws": "^8.18.3"
|
|
48
|
-
"zod": "^3.24.1"
|
|
47
|
+
"ws": "^8.18.3"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
50
|
"@changesets/cli": "^2.29.7",
|
|
@@ -65,7 +64,8 @@
|
|
|
65
64
|
"re2": "^1.23.3"
|
|
66
65
|
},
|
|
67
66
|
"lint-staged": {
|
|
68
|
-
"
|
|
67
|
+
"!(*package-lock).json": "oxfmt --write",
|
|
68
|
+
"*.{ts,js,jsx,tsx,mjs,cjs,yml,yaml,md,css,html}": "oxfmt --write"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": ">=22"
|