@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/README.md +192 -18
- package/dist/callbackEnvelope.js +30 -0
- package/dist/configSchema.js +106 -5
- package/dist/index.js +433 -13
- package/dist/tool.js +29 -2
- package/dist/toolSchema.d.ts +4 -0
- package/dist/toolSchema.js +11 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +4 -0
- package/dist/validator.d.ts +2 -0
- package/dist/validator.js +7 -0
- package/dist/watcherManager.js +33 -2
- package/openclaw.plugin.json +80 -5
- package/package.json +1 -1
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";
|
package/dist/validator.d.ts
CHANGED
|
@@ -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 }),
|
package/dist/watcherManager.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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) => {
|
package/openclaw.plugin.json
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
"properties": {
|
|
7
7
|
"allowedHosts": {
|
|
8
8
|
"type": "array",
|
|
9
|
-
"items": {
|
|
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": "
|
|
25
|
-
|
|
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": "
|
|
77
|
-
"help": "
|
|
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": {
|