@coffeexdev/openclaw-sentinel 0.4.4 → 0.5.0

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/types.js CHANGED
@@ -1 +1,5 @@
1
1
  export const DEFAULT_SENTINEL_WEBHOOK_PATH = "/hooks/sentinel";
2
+ export const SENTINEL_ORIGIN_SESSION_KEY_METADATA = "openclaw.sentinel.origin.sessionKey";
3
+ export const SENTINEL_ORIGIN_CHANNEL_METADATA = "openclaw.sentinel.origin.channel";
4
+ export const SENTINEL_ORIGIN_TARGET_METADATA = "openclaw.sentinel.origin.to";
5
+ export const SENTINEL_ORIGIN_ACCOUNT_METADATA = "openclaw.sentinel.origin.accountId";
@@ -25,6 +25,8 @@ export declare const WatcherSchema: import("@sinclair/typebox").TObject<{
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>;
28
+ notificationPayloadMode: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"inherit">, import("@sinclair/typebox").TLiteral<"none">, import("@sinclair/typebox").TLiteral<"concise">, import("@sinclair/typebox").TLiteral<"debug">]>>;
29
+ sessionGroup: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
28
30
  }>;
29
31
  retry: import("@sinclair/typebox").TObject<{
30
32
  maxRetries: import("@sinclair/typebox").TInteger;
package/dist/validator.js CHANGED
@@ -54,6 +54,13 @@ export const WatcherSchema = Type.Object({
54
54
  ])),
55
55
  deadlineTemplate: Type.Optional(Type.String({ minLength: 1 })),
56
56
  dedupeKeyTemplate: Type.Optional(Type.String({ minLength: 1 })),
57
+ notificationPayloadMode: Type.Optional(Type.Union([
58
+ Type.Literal("inherit"),
59
+ Type.Literal("none"),
60
+ Type.Literal("concise"),
61
+ Type.Literal("debug"),
62
+ ])),
63
+ sessionGroup: Type.Optional(Type.String({ minLength: 1 })),
57
64
  }, { additionalProperties: false }),
58
65
  retry: Type.Object({
59
66
  maxRetries: Type.Integer({ minimum: 0, maximum: 20 }),
@@ -10,6 +10,32 @@ import { sseStrategy } from "./strategies/sse.js";
10
10
  import { websocketStrategy } from "./strategies/websocket.js";
11
11
  import { DEFAULT_SENTINEL_WEBHOOK_PATH, } from "./types.js";
12
12
  export const RESET_BACKOFF_AFTER_MS = 60_000;
13
+ const MAX_DEBUG_NOTIFICATION_CHARS = 7000;
14
+ function trimForChat(text) {
15
+ if (text.length <= MAX_DEBUG_NOTIFICATION_CHARS)
16
+ return text;
17
+ return `${text.slice(0, MAX_DEBUG_NOTIFICATION_CHARS)}…`;
18
+ }
19
+ function resolveNotificationPayloadMode(config, watcher) {
20
+ const override = watcher.fire.notificationPayloadMode;
21
+ if (override === "none" || override === "concise" || override === "debug")
22
+ return override;
23
+ if (config.notificationPayloadMode === "none")
24
+ return "none";
25
+ return config.notificationPayloadMode === "debug" ? "debug" : "concise";
26
+ }
27
+ function buildDeliveryNotificationMessage(watcher, body, mode) {
28
+ const matchedAt = typeof body.trigger === "object" &&
29
+ body.trigger !== null &&
30
+ typeof body.trigger.matchedAt === "string"
31
+ ? body.trigger.matchedAt
32
+ : new Date().toISOString();
33
+ const concise = `Sentinel watcher "${watcher.id}" fired event "${watcher.fire.eventName}" at ${matchedAt}.`;
34
+ if (mode !== "debug")
35
+ return concise;
36
+ const envelopeJson = JSON.stringify(body, null, 2) ?? "{}";
37
+ return trimForChat(`${concise}\n\nSENTINEL_DEBUG_ENVELOPE_JSON:\n${envelopeJson}`);
38
+ }
13
39
  export const backoff = (base, max, failures) => {
14
40
  const raw = Math.min(max, base * 2 ** failures);
15
41
  const jitter = Math.floor(raw * 0.25 * (Math.random() * 2 - 1));
@@ -188,9 +214,14 @@ export class WatcherManager {
188
214
  webhookPath,
189
215
  });
190
216
  await this.dispatcher.dispatch(webhookPath, body);
191
- if (watcher.deliveryTargets?.length && this.notifier) {
217
+ const deliveryMode = resolveNotificationPayloadMode(this.config, watcher);
218
+ const isSentinelWebhook = webhookPath === DEFAULT_SENTINEL_WEBHOOK_PATH;
219
+ if (deliveryMode !== "none" &&
220
+ watcher.deliveryTargets?.length &&
221
+ this.notifier &&
222
+ !isSentinelWebhook) {
192
223
  const attemptedAt = new Date().toISOString();
193
- const message = JSON.stringify(body);
224
+ const message = buildDeliveryNotificationMessage(watcher, body, deliveryMode);
194
225
  const failures = [];
195
226
  let successCount = 0;
196
227
  await Promise.all(watcher.deliveryTargets.map(async (target) => {
@@ -6,7 +6,9 @@
6
6
  "properties": {
7
7
  "allowedHosts": {
8
8
  "type": "array",
9
- "items": { "type": "string" },
9
+ "items": {
10
+ "type": "string"
11
+ },
10
12
  "description": "Hostnames the watchers are permitted to connect to. Must be explicitly configured — no hosts are allowed by default.",
11
13
  "default": []
12
14
  },
@@ -21,13 +23,33 @@
21
23
  },
22
24
  "hookSessionKey": {
23
25
  "type": "string",
24
- "description": "Session key used when /hooks/sentinel enqueues system events into the LLM loop",
25
- "default": "agent:main:main"
26
+ "description": "Deprecated alias for hookSessionPrefix. Sentinel always appends watcher/group segments to prevent a shared global callback session."
27
+ },
28
+ "hookSessionPrefix": {
29
+ "type": "string",
30
+ "description": "Base session key prefix used for isolated /hooks/sentinel callback sessions (default: agent:main:hooks:sentinel)",
31
+ "default": "agent:main:hooks:sentinel"
32
+ },
33
+ "hookSessionGroup": {
34
+ "type": "string",
35
+ "description": "Optional default session group key. When set, callbacks without explicit hookSessionGroup are routed to this group session."
36
+ },
37
+ "hookRelayDedupeWindowMs": {
38
+ "type": "number",
39
+ "minimum": 0,
40
+ "description": "Suppress duplicate relay messages for the same dedupe key within this window (milliseconds)",
41
+ "default": 120000
26
42
  },
27
43
  "stateFilePath": {
28
44
  "type": "string",
29
45
  "description": "Custom path for the sentinel state persistence file"
30
46
  },
47
+ "notificationPayloadMode": {
48
+ "type": "string",
49
+ "enum": ["none", "concise", "debug"],
50
+ "description": "Controls delivery-target notifications: none (suppress message fan-out), concise relay text (default), or relay text with debug envelope payload",
51
+ "default": "concise"
52
+ },
31
53
  "limits": {
32
54
  "type": "object",
33
55
  "additionalProperties": false,
@@ -54,6 +76,24 @@
54
76
  "default": 1000
55
77
  }
56
78
  }
79
+ },
80
+ "hookResponseTimeoutMs": {
81
+ "type": "number",
82
+ "minimum": 0,
83
+ "description": "Milliseconds to wait for an assistant-authored hook response before optional fallback relay",
84
+ "default": 30000
85
+ },
86
+ "hookResponseFallbackMode": {
87
+ "type": "string",
88
+ "enum": ["none", "concise"],
89
+ "description": "Fallback behavior when no assistant response arrives before hookResponseTimeoutMs: none (silent timeout) or concise fail-safe relay",
90
+ "default": "concise"
91
+ },
92
+ "hookResponseDedupeWindowMs": {
93
+ "type": "number",
94
+ "minimum": 0,
95
+ "description": "Deduplicate hook response-delivery contracts by dedupe key within this window (milliseconds)",
96
+ "default": 120000
57
97
  }
58
98
  }
59
99
  },
@@ -73,8 +113,23 @@
73
113
  "placeholder": "sk-..."
74
114
  },
75
115
  "hookSessionKey": {
76
- "label": "Sentinel Hook Session Key",
77
- "help": "Session key that receives /hooks/sentinel callback events (default: agent:main:main)",
116
+ "label": "Hook Session Key (Deprecated)",
117
+ "help": "Deprecated alias for hookSessionPrefix. Sentinel appends watcher/group segments automatically.",
118
+ "advanced": true
119
+ },
120
+ "hookSessionPrefix": {
121
+ "label": "Hook Session Prefix",
122
+ "help": "Base prefix for isolated callback sessions (default: agent:main:hooks:sentinel)",
123
+ "advanced": true
124
+ },
125
+ "hookSessionGroup": {
126
+ "label": "Default Hook Session Group",
127
+ "help": "Optional default group key for callback sessions. Watchers with the same group share one isolated session.",
128
+ "advanced": true
129
+ },
130
+ "hookRelayDedupeWindowMs": {
131
+ "label": "Hook Relay Dedupe Window (ms)",
132
+ "help": "Suppress duplicate relay messages with the same dedupe key for this many milliseconds",
78
133
  "advanced": true
79
134
  },
80
135
  "stateFilePath": {
@@ -82,6 +137,11 @@
82
137
  "help": "Custom path for sentinel state persistence file",
83
138
  "advanced": true
84
139
  },
140
+ "notificationPayloadMode": {
141
+ "label": "Notification Payload Mode",
142
+ "help": "Choose none (suppress delivery-target messages), concise relay text (default), or include debug envelope payload",
143
+ "advanced": true
144
+ },
85
145
  "limits.maxWatchersTotal": {
86
146
  "label": "Max Watchers",
87
147
  "help": "Maximum total watchers across all skills",
@@ -101,6 +161,21 @@
101
161
  "label": "Min Poll Interval (ms)",
102
162
  "help": "Minimum allowed polling interval in milliseconds",
103
163
  "advanced": true
164
+ },
165
+ "hookResponseTimeoutMs": {
166
+ "label": "Hook Response Timeout (ms)",
167
+ "help": "How long to wait for assistant-authored hook output before optional fallback relay",
168
+ "advanced": true
169
+ },
170
+ "hookResponseFallbackMode": {
171
+ "label": "Hook Response Fallback Mode",
172
+ "help": "If timeout occurs, choose none (silent) or concise fail-safe relay",
173
+ "advanced": true
174
+ },
175
+ "hookResponseDedupeWindowMs": {
176
+ "label": "Hook Response Dedupe Window (ms)",
177
+ "help": "Deduplicate hook-response delivery contracts by dedupe key within this window",
178
+ "advanced": true
104
179
  }
105
180
  },
106
181
  "install": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffeexdev/openclaw-sentinel",
3
- "version": "0.4.4",
3
+ "version": "0.5.0",
4
4
  "description": "Secure declarative gateway-native watcher plugin for OpenClaw",
5
5
  "keywords": [
6
6
  "openclaw",