@coffeexdev/openclaw-sentinel 0.4.1 → 0.4.3

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 CHANGED
@@ -132,7 +132,7 @@ It **does not** execute user-authored code from watcher definitions.
132
132
  ## Features
133
133
 
134
134
  - Tool registration: `sentinel_control`
135
- - actions: `create`, `enable`, `disable`, `remove`, `status`, `list`
135
+ - actions: `create` (`add`), `enable`, `disable`, `remove` (`delete`), `status` (`get`), `list`
136
136
  - Strict schema validation (TypeBox, strict object checks) + code-like field/value rejection
137
137
  - Strategies:
138
138
  - `http-poll`
package/dist/tool.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { AnyAgentTool } from "openclaw/plugin-sdk";
2
2
  import type { Static } from "@sinclair/typebox";
3
3
  import { WatcherManager } from "./watcherManager.js";
4
- import { SentinelToolSchema } from "./toolSchema.js";
5
- export type SentinelToolParams = Static<typeof SentinelToolSchema>;
4
+ import { SentinelToolValidationSchema } from "./toolSchema.js";
5
+ export type SentinelToolParams = Static<typeof SentinelToolValidationSchema>;
6
6
  type SentinelToolContext = {
7
7
  messageChannel?: string;
8
8
  requesterSenderId?: string;
package/dist/tool.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { jsonResult } from "openclaw/plugin-sdk";
2
2
  import { Value } from "@sinclair/typebox/value";
3
- import { SentinelToolSchema } from "./toolSchema.js";
3
+ import { SentinelToolSchema, SentinelToolValidationSchema } from "./toolSchema.js";
4
+ import { TemplateValueSchema } from "./templateValueSchema.js";
4
5
  function validateParams(params) {
5
6
  const candidate = (params ?? {});
6
- if (!Value.Check(SentinelToolSchema, candidate)) {
7
- const first = [...Value.Errors(SentinelToolSchema, candidate)][0];
7
+ if (!Value.Check(SentinelToolValidationSchema, [TemplateValueSchema], candidate)) {
8
+ const first = [
9
+ ...Value.Errors(SentinelToolValidationSchema, [TemplateValueSchema], candidate),
10
+ ][0];
8
11
  const where = first?.path || "(root)";
9
12
  const why = first?.message || "Invalid parameters";
10
13
  throw new Error(`Invalid sentinel_control parameters at ${where}: ${why}`);
@@ -35,17 +38,20 @@ export function registerSentinelControl(registerTool, manager) {
35
38
  const payload = validateParams(params);
36
39
  switch (payload.action) {
37
40
  case "create":
41
+ case "add":
38
42
  return jsonResult(await manager.create(payload.watcher, {
39
43
  deliveryTargets: inferDefaultDeliveryTargets(ctx),
40
44
  }));
41
45
  case "enable":
42
- return jsonResult(await manager.enable(payload.id ?? ""));
46
+ return jsonResult(await manager.enable(payload.id));
43
47
  case "disable":
44
- return jsonResult(await manager.disable(payload.id ?? ""));
48
+ return jsonResult(await manager.disable(payload.id));
45
49
  case "remove":
46
- return jsonResult(await manager.remove(payload.id ?? ""));
50
+ case "delete":
51
+ return jsonResult(await manager.remove(payload.id));
47
52
  case "status":
48
- return jsonResult(manager.status(payload.id ?? ""));
53
+ case "get":
54
+ return jsonResult(manager.status(payload.id));
49
55
  case "list":
50
56
  return jsonResult(manager.list());
51
57
  }
@@ -1,6 +1,53 @@
1
+ export declare const SentinelToolValidationSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
2
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"add">]>;
3
+ watcher: import("@sinclair/typebox").TObject<{
4
+ id: import("@sinclair/typebox").TString;
5
+ skillId: import("@sinclair/typebox").TString;
6
+ enabled: import("@sinclair/typebox").TBoolean;
7
+ strategy: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"http-poll">, import("@sinclair/typebox").TLiteral<"websocket">, import("@sinclair/typebox").TLiteral<"sse">, import("@sinclair/typebox").TLiteral<"http-long-poll">]>;
8
+ endpoint: import("@sinclair/typebox").TString;
9
+ method: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"GET">, import("@sinclair/typebox").TLiteral<"POST">]>>;
10
+ headers: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
11
+ body: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
12
+ intervalMs: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
13
+ timeoutMs: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
14
+ match: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"all">, import("@sinclair/typebox").TLiteral<"any">]>;
15
+ conditions: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
16
+ path: import("@sinclair/typebox").TString;
17
+ op: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"eq">, import("@sinclair/typebox").TLiteral<"neq">, import("@sinclair/typebox").TLiteral<"gt">, import("@sinclair/typebox").TLiteral<"gte">, import("@sinclair/typebox").TLiteral<"lt">, import("@sinclair/typebox").TLiteral<"lte">, import("@sinclair/typebox").TLiteral<"exists">, import("@sinclair/typebox").TLiteral<"absent">, import("@sinclair/typebox").TLiteral<"contains">, import("@sinclair/typebox").TLiteral<"matches">, import("@sinclair/typebox").TLiteral<"changed">]>;
18
+ value: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnknown>;
19
+ }>>;
20
+ fire: import("@sinclair/typebox").TObject<{
21
+ webhookPath: import("@sinclair/typebox").TString;
22
+ eventName: import("@sinclair/typebox").TString;
23
+ payloadTemplate: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TRef<any>>;
24
+ intent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
25
+ contextTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TRef<any>>>;
26
+ priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"normal">, import("@sinclair/typebox").TLiteral<"high">, import("@sinclair/typebox").TLiteral<"critical">]>>;
27
+ deadlineTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
28
+ dedupeKeyTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
29
+ }>;
30
+ retry: import("@sinclair/typebox").TObject<{
31
+ maxRetries: import("@sinclair/typebox").TNumber;
32
+ baseMs: import("@sinclair/typebox").TNumber;
33
+ maxMs: import("@sinclair/typebox").TNumber;
34
+ }>;
35
+ fireOnce: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
36
+ deliveryTargets: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
37
+ channel: import("@sinclair/typebox").TString;
38
+ to: import("@sinclair/typebox").TString;
39
+ accountId: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
40
+ }>>>;
41
+ metadata: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
42
+ }>;
43
+ }>, import("@sinclair/typebox").TObject<{
44
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"enable">, import("@sinclair/typebox").TLiteral<"disable">, import("@sinclair/typebox").TLiteral<"remove">, import("@sinclair/typebox").TLiteral<"delete">, import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"get">]>;
45
+ id: import("@sinclair/typebox").TString;
46
+ }>, import("@sinclair/typebox").TObject<{
47
+ action: import("@sinclair/typebox").TLiteral<"list">;
48
+ }>]>;
1
49
  export declare const SentinelToolSchema: import("@sinclair/typebox").TObject<{
2
- action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"enable">, import("@sinclair/typebox").TLiteral<"disable">, import("@sinclair/typebox").TLiteral<"remove">, import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"list">]>;
3
- id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
50
+ action: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"create">, import("@sinclair/typebox").TLiteral<"add">]>, import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"enable">, import("@sinclair/typebox").TLiteral<"disable">, import("@sinclair/typebox").TLiteral<"remove">, import("@sinclair/typebox").TLiteral<"delete">, import("@sinclair/typebox").TLiteral<"status">, import("@sinclair/typebox").TLiteral<"get">]>, import("@sinclair/typebox").TLiteral<"list">]>;
4
51
  watcher: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TObject<{
5
52
  id: import("@sinclair/typebox").TString;
6
53
  skillId: import("@sinclair/typebox").TString;
@@ -21,9 +68,9 @@ export declare const SentinelToolSchema: import("@sinclair/typebox").TObject<{
21
68
  fire: import("@sinclair/typebox").TObject<{
22
69
  webhookPath: import("@sinclair/typebox").TString;
23
70
  eventName: import("@sinclair/typebox").TString;
24
- payloadTemplate: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, any>;
71
+ payloadTemplate: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TRef<any>>;
25
72
  intent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
26
- contextTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, any>>;
73
+ contextTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TRef<any>>>;
27
74
  priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"normal">, import("@sinclair/typebox").TLiteral<"high">, import("@sinclair/typebox").TLiteral<"critical">]>>;
28
75
  deadlineTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
29
76
  dedupeKeyTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
@@ -41,4 +88,5 @@ export declare const SentinelToolSchema: import("@sinclair/typebox").TObject<{
41
88
  }>>>;
42
89
  metadata: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TString>>;
43
90
  }>>;
91
+ id: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
44
92
  }>;
@@ -1,5 +1,6 @@
1
1
  import { Type } from "@sinclair/typebox";
2
2
  import { TemplateValueSchema } from "./templateValueSchema.js";
3
+ const TemplateValueRefSchema = Type.Ref(TemplateValueSchema);
3
4
  const ConditionSchema = Type.Object({
4
5
  path: Type.String({ description: "JSONPath expression to evaluate against the response" }),
5
6
  op: Type.Union([
@@ -24,11 +25,11 @@ const FireConfigSchema = Type.Object({
24
25
  description: "Path appended to localDispatchBase for webhook delivery",
25
26
  }),
26
27
  eventName: Type.String({ description: "Event name included in the dispatched payload" }),
27
- payloadTemplate: Type.Record(Type.String(), TemplateValueSchema, {
28
+ payloadTemplate: Type.Record(Type.String(), TemplateValueRefSchema, {
28
29
  description: "Key-value template for the webhook payload. Supports ${...} interpolation from matched response data.",
29
30
  }),
30
31
  intent: Type.Optional(Type.String({ description: "Generic callback intent for downstream agent routing" })),
31
- contextTemplate: Type.Optional(Type.Record(Type.String(), TemplateValueSchema, {
32
+ contextTemplate: Type.Optional(Type.Record(Type.String(), TemplateValueRefSchema, {
32
33
  description: "Structured callback context template. Supports ${...} interpolation from matched response data.",
33
34
  })),
34
35
  priority: Type.Optional(Type.Union([Type.Literal("low"), Type.Literal("normal"), Type.Literal("high"), Type.Literal("critical")], { description: "Callback urgency hint" })),
@@ -79,15 +80,46 @@ const WatcherSchema = Type.Object({
79
80
  })),
80
81
  metadata: Type.Optional(Type.Record(Type.String(), Type.String(), { description: "Arbitrary key-value metadata" })),
81
82
  }, { description: "Full watcher definition" });
83
+ const CreateActionNameSchema = Type.Union([Type.Literal("create"), Type.Literal("add")], {
84
+ description: "Create action (alias: add)",
85
+ });
86
+ const IdActionNameSchema = Type.Union([
87
+ Type.Literal("enable"),
88
+ Type.Literal("disable"),
89
+ Type.Literal("remove"),
90
+ Type.Literal("delete"),
91
+ Type.Literal("status"),
92
+ Type.Literal("get"),
93
+ ], { description: "ID-targeting action aliases: delete/remove and get/status" });
94
+ const ListActionNameSchema = Type.Literal("list", { description: "List all watchers" });
95
+ const AnyActionNameSchema = Type.Union([
96
+ CreateActionNameSchema,
97
+ IdActionNameSchema,
98
+ ListActionNameSchema,
99
+ ]);
100
+ const CreateActionSchema = Type.Object({
101
+ action: CreateActionNameSchema,
102
+ watcher: WatcherSchema,
103
+ }, { additionalProperties: false });
104
+ const IdActionSchema = Type.Object({
105
+ action: IdActionNameSchema,
106
+ id: Type.String({ description: "Watcher ID for action target" }),
107
+ }, { additionalProperties: false });
108
+ const ListActionSchema = Type.Object({
109
+ action: ListActionNameSchema,
110
+ }, { additionalProperties: false });
111
+ export const SentinelToolValidationSchema = Type.Union([CreateActionSchema, IdActionSchema, ListActionSchema], {
112
+ $defs: {
113
+ templateValue: TemplateValueSchema,
114
+ },
115
+ });
82
116
  export const SentinelToolSchema = Type.Object({
83
- action: Type.Union([
84
- Type.Literal("create"),
85
- Type.Literal("enable"),
86
- Type.Literal("disable"),
87
- Type.Literal("remove"),
88
- Type.Literal("status"),
89
- Type.Literal("list"),
90
- ], { description: "The action to perform" }),
91
- id: Type.Optional(Type.String({ description: "Watcher ID (required for enable/disable/remove/status)" })),
117
+ action: AnyActionNameSchema,
92
118
  watcher: Type.Optional(WatcherSchema),
93
- }, { additionalProperties: false });
119
+ id: Type.Optional(Type.String({ description: "Watcher ID for action target" })),
120
+ }, {
121
+ additionalProperties: false,
122
+ $defs: {
123
+ templateValue: TemplateValueSchema,
124
+ },
125
+ });
@@ -19,9 +19,9 @@ export declare const WatcherSchema: import("@sinclair/typebox").TObject<{
19
19
  fire: import("@sinclair/typebox").TObject<{
20
20
  webhookPath: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
21
21
  eventName: import("@sinclair/typebox").TString;
22
- payloadTemplate: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, any>;
22
+ payloadTemplate: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TRef<any>>;
23
23
  intent: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
24
- contextTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, any>>;
24
+ contextTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TRef<any>>>;
25
25
  priority: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"low">, import("@sinclair/typebox").TLiteral<"normal">, import("@sinclair/typebox").TLiteral<"high">, import("@sinclair/typebox").TLiteral<"critical">]>>;
26
26
  deadlineTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
27
27
  dedupeKeyTemplate: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
package/dist/validator.js CHANGED
@@ -2,6 +2,7 @@ import { Type } from "@sinclair/typebox";
2
2
  import { Value } from "@sinclair/typebox/value";
3
3
  import { TemplateValueSchema } from "./templateValueSchema.js";
4
4
  import { DEFAULT_SENTINEL_WEBHOOK_PATH } from "./types.js";
5
+ const TemplateValueRefSchema = Type.Ref(TemplateValueSchema);
5
6
  const codeyKeyPattern = /(script|code|eval|handler|function|import|require)/i;
6
7
  const codeyValuePattern = /(=>|\bfunction\b|\bimport\s+|\brequire\s*\(|\beval\s*\()/i;
7
8
  const ConditionSchema = Type.Object({
@@ -42,9 +43,9 @@ export const WatcherSchema = Type.Object({
42
43
  fire: Type.Object({
43
44
  webhookPath: Type.Optional(Type.String({ pattern: "^/" })),
44
45
  eventName: Type.String({ minLength: 1 }),
45
- payloadTemplate: Type.Record(Type.String(), TemplateValueSchema),
46
+ payloadTemplate: Type.Record(Type.String(), TemplateValueRefSchema),
46
47
  intent: Type.Optional(Type.String({ minLength: 1 })),
47
- contextTemplate: Type.Optional(Type.Record(Type.String(), TemplateValueSchema)),
48
+ contextTemplate: Type.Optional(Type.Record(Type.String(), TemplateValueRefSchema)),
48
49
  priority: Type.Optional(Type.Union([
49
50
  Type.Literal("low"),
50
51
  Type.Literal("normal"),
@@ -66,7 +67,12 @@ export const WatcherSchema = Type.Object({
66
67
  accountId: Type.Optional(Type.String({ minLength: 1 })),
67
68
  }, { additionalProperties: false }), { minItems: 1 })),
68
69
  metadata: Type.Optional(Type.Record(Type.String(), Type.String())),
69
- }, { additionalProperties: false });
70
+ }, {
71
+ additionalProperties: false,
72
+ $defs: {
73
+ templateValue: TemplateValueSchema,
74
+ },
75
+ });
70
76
  function scanNoCodeLike(input, parentKey = "") {
71
77
  if (input === null || input === undefined)
72
78
  return;
@@ -91,8 +97,8 @@ function scanNoCodeLike(input, parentKey = "") {
91
97
  }
92
98
  export function validateWatcherDefinition(input) {
93
99
  scanNoCodeLike(input);
94
- if (!Value.Check(WatcherSchema, input)) {
95
- const first = [...Value.Errors(WatcherSchema, input)][0];
100
+ if (!Value.Check(WatcherSchema, [TemplateValueSchema], input)) {
101
+ const first = [...Value.Errors(WatcherSchema, [TemplateValueSchema], input)][0];
96
102
  const where = first?.path || "(root)";
97
103
  const why = first?.message || "Invalid watcher definition";
98
104
  throw new Error(`Invalid watcher definition at ${where}: ${why}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffeexdev/openclaw-sentinel",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Secure declarative gateway-native watcher plugin for OpenClaw",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -38,6 +38,7 @@
38
38
  "@changesets/cli": "^2.29.7",
39
39
  "@types/node": "^24.0.0",
40
40
  "@types/ws": "^8.5.13",
41
+ "ajv": "^8.17.1",
41
42
  "husky": "^9.1.7",
42
43
  "lint-staged": "^16.3.2",
43
44
  "openclaw": "latest",