@coffeexdev/openclaw-sentinel 0.1.2 → 0.1.4
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 +10 -4
- package/dist/configSchema.d.ts +2 -0
- package/dist/configSchema.js +115 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +18 -2
- package/dist/tool.d.ts +4 -1
- package/dist/tool.js +31 -28
- package/openclaw.plugin.json +91 -2
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -35,6 +35,10 @@ Formal JSON Schema for sentinel config/watchers is available at:
|
|
|
35
35
|
|
|
36
36
|
You can validate a watcher config document (for example `.sentinel.json`) against this schema in CI or local tooling.
|
|
37
37
|
|
|
38
|
+
## Documentation
|
|
39
|
+
|
|
40
|
+
- [Usage Guide](docs/USAGE.md)
|
|
41
|
+
|
|
38
42
|
## Install
|
|
39
43
|
|
|
40
44
|
```bash
|
|
@@ -43,6 +47,8 @@ npm i @coffeexdev/openclaw-sentinel
|
|
|
43
47
|
|
|
44
48
|
## Quick usage
|
|
45
49
|
|
|
50
|
+
No hosts are allowed by default — you must explicitly configure `allowedHosts` for watchers to connect to any endpoint.
|
|
51
|
+
|
|
46
52
|
```ts
|
|
47
53
|
import { createSentinelPlugin } from "@coffeexdev/openclaw-sentinel";
|
|
48
54
|
|
|
@@ -65,8 +71,8 @@ sentinel.register({
|
|
|
65
71
|
{
|
|
66
72
|
"action": "create",
|
|
67
73
|
"watcher": {
|
|
68
|
-
"id": "sentinel-
|
|
69
|
-
"skillId": "skills.
|
|
74
|
+
"id": "sentinel-alert",
|
|
75
|
+
"skillId": "skills.general-monitor"
|
|
70
76
|
"enabled": true,
|
|
71
77
|
"strategy": "http-poll",
|
|
72
78
|
"endpoint": "https://api.github.com/events",
|
|
@@ -75,7 +81,7 @@ sentinel.register({
|
|
|
75
81
|
"conditions": [{ "path": "type", "op": "eq", "value": "PushEvent" }],
|
|
76
82
|
"fire": {
|
|
77
83
|
"webhookPath": "/internal/sentinel/fire",
|
|
78
|
-
"eventName": "
|
|
84
|
+
"eventName": "sentinel_push",
|
|
79
85
|
"payloadTemplate": {
|
|
80
86
|
"watcher": "${watcher.id}",
|
|
81
87
|
"event": "${event.name}",
|
|
@@ -104,7 +110,7 @@ Set `"fireOnce": true` to automatically disable a watcher after its first matche
|
|
|
104
110
|
|
|
105
111
|
## Example scenarios
|
|
106
112
|
|
|
107
|
-
###
|
|
113
|
+
### Feed monitoring example
|
|
108
114
|
|
|
109
115
|
Watch API changes and fire internal webhook events for orchestration.
|
|
110
116
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const limitsSchema = z.object({
|
|
3
|
+
maxWatchersTotal: z.number().int().positive().default(200),
|
|
4
|
+
maxWatchersPerSkill: z.number().int().positive().default(20),
|
|
5
|
+
maxConditionsPerWatcher: z.number().int().positive().default(25),
|
|
6
|
+
maxIntervalMsFloor: z.number().int().positive().default(1000),
|
|
7
|
+
});
|
|
8
|
+
const configZodSchema = z.object({
|
|
9
|
+
allowedHosts: z.array(z.string()).default([]),
|
|
10
|
+
localDispatchBase: z.string().url().default("http://127.0.0.1:18789"),
|
|
11
|
+
dispatchAuthToken: z.string().optional(),
|
|
12
|
+
stateFilePath: z.string().optional(),
|
|
13
|
+
limits: limitsSchema.default({}),
|
|
14
|
+
});
|
|
15
|
+
export const sentinelConfigSchema = {
|
|
16
|
+
safeParse: (value) => {
|
|
17
|
+
if (value === undefined)
|
|
18
|
+
return { success: true, data: undefined };
|
|
19
|
+
return configZodSchema.safeParse(value);
|
|
20
|
+
},
|
|
21
|
+
jsonSchema: {
|
|
22
|
+
type: "object",
|
|
23
|
+
additionalProperties: false,
|
|
24
|
+
properties: {
|
|
25
|
+
allowedHosts: {
|
|
26
|
+
type: "array",
|
|
27
|
+
items: { type: "string" },
|
|
28
|
+
description: "Hostnames the watchers are permitted to connect to. Must be explicitly configured — no hosts are allowed by default.",
|
|
29
|
+
default: [],
|
|
30
|
+
},
|
|
31
|
+
localDispatchBase: {
|
|
32
|
+
type: "string",
|
|
33
|
+
format: "uri",
|
|
34
|
+
description: "Base URL for internal webhook dispatch",
|
|
35
|
+
default: "http://127.0.0.1:18789",
|
|
36
|
+
},
|
|
37
|
+
dispatchAuthToken: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Bearer token for authenticating webhook dispatch requests",
|
|
40
|
+
},
|
|
41
|
+
stateFilePath: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Custom path for the sentinel state persistence file",
|
|
44
|
+
},
|
|
45
|
+
limits: {
|
|
46
|
+
type: "object",
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
description: "Resource limits for watcher creation",
|
|
49
|
+
properties: {
|
|
50
|
+
maxWatchersTotal: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "Maximum total watchers across all skills",
|
|
53
|
+
default: 200,
|
|
54
|
+
},
|
|
55
|
+
maxWatchersPerSkill: {
|
|
56
|
+
type: "number",
|
|
57
|
+
description: "Maximum watchers per skill",
|
|
58
|
+
default: 20,
|
|
59
|
+
},
|
|
60
|
+
maxConditionsPerWatcher: {
|
|
61
|
+
type: "number",
|
|
62
|
+
description: "Maximum conditions per watcher definition",
|
|
63
|
+
default: 25,
|
|
64
|
+
},
|
|
65
|
+
maxIntervalMsFloor: {
|
|
66
|
+
type: "number",
|
|
67
|
+
description: "Minimum allowed polling interval in milliseconds",
|
|
68
|
+
default: 1000,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
uiHints: {
|
|
75
|
+
allowedHosts: {
|
|
76
|
+
label: "Allowed Hosts",
|
|
77
|
+
help: "Hostnames the watchers are permitted to connect to",
|
|
78
|
+
},
|
|
79
|
+
localDispatchBase: {
|
|
80
|
+
label: "Dispatch Base URL",
|
|
81
|
+
help: "Base URL for internal webhook dispatch (default: http://127.0.0.1:18789)",
|
|
82
|
+
},
|
|
83
|
+
dispatchAuthToken: {
|
|
84
|
+
label: "Dispatch Auth Token",
|
|
85
|
+
help: "Bearer token for webhook dispatch authentication (or use SENTINEL_DISPATCH_TOKEN env var)",
|
|
86
|
+
sensitive: true,
|
|
87
|
+
placeholder: "sk-...",
|
|
88
|
+
},
|
|
89
|
+
stateFilePath: {
|
|
90
|
+
label: "State File Path",
|
|
91
|
+
help: "Custom path for sentinel state persistence file",
|
|
92
|
+
advanced: true,
|
|
93
|
+
},
|
|
94
|
+
"limits.maxWatchersTotal": {
|
|
95
|
+
label: "Max Watchers",
|
|
96
|
+
help: "Maximum total watchers across all skills",
|
|
97
|
+
advanced: true,
|
|
98
|
+
},
|
|
99
|
+
"limits.maxWatchersPerSkill": {
|
|
100
|
+
label: "Max Per Skill",
|
|
101
|
+
help: "Maximum watchers a single skill can create",
|
|
102
|
+
advanced: true,
|
|
103
|
+
},
|
|
104
|
+
"limits.maxConditionsPerWatcher": {
|
|
105
|
+
label: "Max Conditions",
|
|
106
|
+
help: "Maximum conditions per watcher definition",
|
|
107
|
+
advanced: true,
|
|
108
|
+
},
|
|
109
|
+
"limits.maxIntervalMsFloor": {
|
|
110
|
+
label: "Min Poll Interval (ms)",
|
|
111
|
+
help: "Minimum allowed polling interval in milliseconds",
|
|
112
|
+
advanced: true,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
1
2
|
import { WatcherManager } from "./watcherManager.js";
|
|
2
3
|
import { SentinelConfig } from "./types.js";
|
|
3
4
|
export declare function createSentinelPlugin(overrides?: Partial<SentinelConfig>): {
|
|
4
5
|
manager: WatcherManager;
|
|
5
6
|
init(): Promise<void>;
|
|
6
|
-
register(api:
|
|
7
|
-
registerTool: (name: string, handler: (input: unknown) => Promise<unknown>) => void;
|
|
8
|
-
}): void;
|
|
7
|
+
register(api: OpenClawPluginApi): void;
|
|
9
8
|
};
|
|
9
|
+
declare const sentinelPlugin: {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
configSchema: import("openclaw/plugin-sdk").OpenClawPluginConfigSchema;
|
|
14
|
+
register(api: OpenClawPluginApi): void;
|
|
15
|
+
};
|
|
16
|
+
export declare const register: (api: OpenClawPluginApi) => void;
|
|
17
|
+
export declare const activate: (api: OpenClawPluginApi) => void;
|
|
18
|
+
export default sentinelPlugin;
|
|
10
19
|
export * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { registerSentinelControl } from "./tool.js";
|
|
2
2
|
import { WatcherManager } from "./watcherManager.js";
|
|
3
|
+
import { sentinelConfigSchema } from "./configSchema.js";
|
|
3
4
|
export function createSentinelPlugin(overrides) {
|
|
4
5
|
const config = {
|
|
5
|
-
allowedHosts: [
|
|
6
|
+
allowedHosts: [],
|
|
6
7
|
localDispatchBase: "http://127.0.0.1:18789",
|
|
7
8
|
dispatchAuthToken: process.env.SENTINEL_DISPATCH_TOKEN,
|
|
8
9
|
limits: {
|
|
@@ -31,8 +32,23 @@ export function createSentinelPlugin(overrides) {
|
|
|
31
32
|
await manager.init();
|
|
32
33
|
},
|
|
33
34
|
register(api) {
|
|
34
|
-
registerSentinelControl(api.registerTool, manager);
|
|
35
|
+
registerSentinelControl(api.registerTool.bind(api), manager);
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
}
|
|
39
|
+
// OpenClaw plugin entrypoint (default plugin object with register)
|
|
40
|
+
const sentinelPlugin = {
|
|
41
|
+
id: "openclaw-sentinel",
|
|
42
|
+
name: "OpenClaw Sentinel",
|
|
43
|
+
description: "Secure declarative gateway-native watcher plugin for OpenClaw",
|
|
44
|
+
configSchema: sentinelConfigSchema,
|
|
45
|
+
register(api) {
|
|
46
|
+
const plugin = createSentinelPlugin(api.pluginConfig);
|
|
47
|
+
void plugin.init();
|
|
48
|
+
plugin.register(api);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
export const register = sentinelPlugin.register.bind(sentinelPlugin);
|
|
52
|
+
export const activate = sentinelPlugin.register.bind(sentinelPlugin);
|
|
53
|
+
export default sentinelPlugin;
|
|
38
54
|
export * from "./types.js";
|
package/dist/tool.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
+
import type { AnyAgentTool } from "openclaw/plugin-sdk";
|
|
1
2
|
import { WatcherManager } from "./watcherManager.js";
|
|
2
|
-
|
|
3
|
+
type RegisterToolFn = (tool: AnyAgentTool) => void;
|
|
4
|
+
export declare function registerSentinelControl(registerTool: RegisterToolFn, manager: WatcherManager): void;
|
|
5
|
+
export {};
|
package/dist/tool.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { jsonResult } from "openclaw/plugin-sdk";
|
|
1
2
|
import { z } from "zod";
|
|
2
|
-
const
|
|
3
|
+
const ParamsSchema = z
|
|
3
4
|
.object({
|
|
4
5
|
action: z.enum(["create", "enable", "disable", "remove", "status", "list"]),
|
|
5
6
|
id: z.string().optional(),
|
|
@@ -7,32 +8,34 @@ const inputSchema = z
|
|
|
7
8
|
})
|
|
8
9
|
.strict();
|
|
9
10
|
export function registerSentinelControl(registerTool, manager) {
|
|
10
|
-
registerTool(
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
11
|
+
registerTool({
|
|
12
|
+
name: "sentinel_control",
|
|
13
|
+
label: "sentinel_control",
|
|
14
|
+
description: "Create/manage sentinel watchers",
|
|
15
|
+
parameters: {
|
|
16
|
+
action: {
|
|
17
|
+
type: "string",
|
|
18
|
+
enum: ["create", "enable", "disable", "remove", "status", "list"],
|
|
19
|
+
},
|
|
20
|
+
id: { type: "string" },
|
|
21
|
+
watcher: { type: "object" },
|
|
22
|
+
},
|
|
23
|
+
async execute(_toolCallId, params) {
|
|
24
|
+
const payload = ParamsSchema.parse((params ?? {}));
|
|
25
|
+
switch (payload.action) {
|
|
26
|
+
case "create":
|
|
27
|
+
return jsonResult(await manager.create(payload.watcher));
|
|
28
|
+
case "enable":
|
|
29
|
+
return jsonResult(await manager.enable(payload.id ?? ""));
|
|
30
|
+
case "disable":
|
|
31
|
+
return jsonResult(await manager.disable(payload.id ?? ""));
|
|
32
|
+
case "remove":
|
|
33
|
+
return jsonResult(await manager.remove(payload.id ?? ""));
|
|
34
|
+
case "status":
|
|
35
|
+
return jsonResult(manager.status(payload.id ?? ""));
|
|
36
|
+
case "list":
|
|
37
|
+
return jsonResult(manager.list());
|
|
38
|
+
}
|
|
39
|
+
},
|
|
37
40
|
});
|
|
38
41
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,8 +2,97 @@
|
|
|
2
2
|
"id": "openclaw-sentinel",
|
|
3
3
|
"configSchema": {
|
|
4
4
|
"type": "object",
|
|
5
|
-
"additionalProperties":
|
|
6
|
-
"properties": {
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"properties": {
|
|
7
|
+
"allowedHosts": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": { "type": "string" },
|
|
10
|
+
"description": "Hostnames the watchers are permitted to connect to. Must be explicitly configured — no hosts are allowed by default.",
|
|
11
|
+
"default": []
|
|
12
|
+
},
|
|
13
|
+
"localDispatchBase": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"format": "uri",
|
|
16
|
+
"description": "Base URL for internal webhook dispatch",
|
|
17
|
+
"default": "http://127.0.0.1:18789"
|
|
18
|
+
},
|
|
19
|
+
"dispatchAuthToken": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Bearer token for authenticating webhook dispatch requests"
|
|
22
|
+
},
|
|
23
|
+
"stateFilePath": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Custom path for the sentinel state persistence file"
|
|
26
|
+
},
|
|
27
|
+
"limits": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": false,
|
|
30
|
+
"description": "Resource limits for watcher creation",
|
|
31
|
+
"properties": {
|
|
32
|
+
"maxWatchersTotal": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Maximum total watchers across all skills",
|
|
35
|
+
"default": 200
|
|
36
|
+
},
|
|
37
|
+
"maxWatchersPerSkill": {
|
|
38
|
+
"type": "number",
|
|
39
|
+
"description": "Maximum watchers per skill",
|
|
40
|
+
"default": 20
|
|
41
|
+
},
|
|
42
|
+
"maxConditionsPerWatcher": {
|
|
43
|
+
"type": "number",
|
|
44
|
+
"description": "Maximum conditions per watcher definition",
|
|
45
|
+
"default": 25
|
|
46
|
+
},
|
|
47
|
+
"maxIntervalMsFloor": {
|
|
48
|
+
"type": "number",
|
|
49
|
+
"description": "Minimum allowed polling interval in milliseconds",
|
|
50
|
+
"default": 1000
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"uiHints": {
|
|
57
|
+
"allowedHosts": {
|
|
58
|
+
"label": "Allowed Hosts",
|
|
59
|
+
"help": "Hostnames the watchers are permitted to connect to"
|
|
60
|
+
},
|
|
61
|
+
"localDispatchBase": {
|
|
62
|
+
"label": "Dispatch Base URL",
|
|
63
|
+
"help": "Base URL for internal webhook dispatch"
|
|
64
|
+
},
|
|
65
|
+
"dispatchAuthToken": {
|
|
66
|
+
"label": "Dispatch Auth Token",
|
|
67
|
+
"help": "Bearer token for webhook dispatch authentication",
|
|
68
|
+
"sensitive": true,
|
|
69
|
+
"placeholder": "sk-..."
|
|
70
|
+
},
|
|
71
|
+
"stateFilePath": {
|
|
72
|
+
"label": "State File Path",
|
|
73
|
+
"help": "Custom path for sentinel state persistence file",
|
|
74
|
+
"advanced": true
|
|
75
|
+
},
|
|
76
|
+
"limits.maxWatchersTotal": {
|
|
77
|
+
"label": "Max Watchers",
|
|
78
|
+
"help": "Maximum total watchers across all skills",
|
|
79
|
+
"advanced": true
|
|
80
|
+
},
|
|
81
|
+
"limits.maxWatchersPerSkill": {
|
|
82
|
+
"label": "Max Per Skill",
|
|
83
|
+
"help": "Maximum watchers a single skill can create",
|
|
84
|
+
"advanced": true
|
|
85
|
+
},
|
|
86
|
+
"limits.maxConditionsPerWatcher": {
|
|
87
|
+
"label": "Max Conditions",
|
|
88
|
+
"help": "Maximum conditions per watcher definition",
|
|
89
|
+
"advanced": true
|
|
90
|
+
},
|
|
91
|
+
"limits.maxIntervalMsFloor": {
|
|
92
|
+
"label": "Min Poll Interval (ms)",
|
|
93
|
+
"help": "Minimum allowed polling interval in milliseconds",
|
|
94
|
+
"advanced": true
|
|
95
|
+
}
|
|
7
96
|
},
|
|
8
97
|
"install": {
|
|
9
98
|
"npmSpec": "@coffeexdev/openclaw-sentinel"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coffeexdev/openclaw-sentinel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Secure declarative gateway-native watcher plugin for OpenClaw",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"changeset": "changeset",
|
|
39
39
|
"version-packages": "changeset version",
|
|
40
40
|
"release": "changeset publish",
|
|
41
|
-
"prepack": "npm run build"
|
|
41
|
+
"prepack": "npm run build",
|
|
42
|
+
"prepare": "husky"
|
|
42
43
|
},
|
|
43
44
|
"dependencies": {
|
|
44
45
|
"re2-wasm": "^1.0.2",
|
|
@@ -49,15 +50,24 @@
|
|
|
49
50
|
"@changesets/cli": "^2.29.7",
|
|
50
51
|
"@types/node": "^24.0.0",
|
|
51
52
|
"@types/ws": "^8.5.13",
|
|
53
|
+
"husky": "^9.1.7",
|
|
54
|
+
"lint-staged": "^16.3.2",
|
|
55
|
+
"openclaw": "latest",
|
|
52
56
|
"oxfmt": "^0.36.0",
|
|
53
57
|
"typescript": "^5.8.2",
|
|
54
58
|
"vitest": "^3.0.8"
|
|
55
59
|
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"openclaw": ">=2026.3.2"
|
|
62
|
+
},
|
|
56
63
|
"optionalDependencies": {
|
|
57
64
|
"re2": "^1.23.3"
|
|
58
65
|
},
|
|
66
|
+
"lint-staged": {
|
|
67
|
+
"*.{ts,js,jsx,tsx,mjs,cjs,json,yml,yaml,md,css,html}": "oxfmt --write"
|
|
68
|
+
},
|
|
59
69
|
"engines": {
|
|
60
|
-
"node": ">=
|
|
70
|
+
"node": ">=22"
|
|
61
71
|
},
|
|
62
72
|
"openclaw": {
|
|
63
73
|
"extensions": [
|