@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/README.md
CHANGED
|
@@ -18,19 +18,53 @@ Add/update `~/.openclaw/openclaw.json`:
|
|
|
18
18
|
|
|
19
19
|
```json5
|
|
20
20
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
plugins: {
|
|
22
|
+
entries: {
|
|
23
|
+
"openclaw-sentinel": {
|
|
24
|
+
enabled: true,
|
|
25
|
+
config: {
|
|
26
|
+
// Required: watchers can only call endpoints on these hosts.
|
|
27
|
+
allowedHosts: ["api.github.com", "api.coingecko.com"],
|
|
28
|
+
|
|
29
|
+
// Default dispatch base for internal webhook callbacks.
|
|
30
|
+
localDispatchBase: "http://127.0.0.1:18789",
|
|
31
|
+
|
|
32
|
+
// Optional: base prefix for isolated /hooks/sentinel callback sessions.
|
|
33
|
+
// Sentinel appends :watcher:<id> by default (or :group:<key> when grouped).
|
|
34
|
+
hookSessionPrefix: "agent:main:hooks:sentinel",
|
|
35
|
+
|
|
36
|
+
// Optional: default group key for callbacks without explicit hookSessionGroup.
|
|
37
|
+
// hookSessionGroup: "ops-alerts",
|
|
38
|
+
|
|
39
|
+
// Optional: suppress duplicate relays by dedupe key within this time window.
|
|
40
|
+
hookRelayDedupeWindowMs: 120000,
|
|
41
|
+
|
|
42
|
+
// Optional: guarantee hook-response delivery contract for /hooks/sentinel callbacks.
|
|
43
|
+
// Wait this long for assistant-authored output before fallback behavior applies.
|
|
44
|
+
hookResponseTimeoutMs: 30000,
|
|
45
|
+
|
|
46
|
+
// Optional: timeout fallback relay mode for /hooks/sentinel response contracts.
|
|
47
|
+
// "none" = no fallback message, "concise" = send a fail-safe relay line.
|
|
48
|
+
hookResponseFallbackMode: "concise",
|
|
49
|
+
|
|
50
|
+
// Optional: dedupe repeated callback response contracts by dedupe key.
|
|
51
|
+
hookResponseDedupeWindowMs: 120000,
|
|
52
|
+
|
|
53
|
+
// Optional: payload style for non-/hooks/sentinel deliveryTargets notifications.
|
|
54
|
+
// "none" suppresses delivery-target message fan-out (callback still fires).
|
|
55
|
+
// "concise" (default) sends human-friendly relay text only.
|
|
56
|
+
// "debug" appends a structured sentinel envelope block for diagnostics.
|
|
57
|
+
// notificationPayloadMode: "concise",
|
|
58
|
+
|
|
59
|
+
// Optional legacy alias for hookSessionPrefix (still supported).
|
|
60
|
+
// hookSessionKey: "agent:main:hooks:sentinel",
|
|
61
|
+
|
|
62
|
+
// Optional: bearer token used for dispatch calls back to gateway.
|
|
63
|
+
// Set this to your gateway auth token when gateway auth is enabled.
|
|
64
|
+
// dispatchAuthToken: "<gateway-token>"
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
34
68
|
},
|
|
35
69
|
}
|
|
36
70
|
```
|
|
@@ -41,6 +75,20 @@ Add/update `~/.openclaw/openclaw.json`:
|
|
|
41
75
|
openclaw gateway restart
|
|
42
76
|
```
|
|
43
77
|
|
|
78
|
+
### Troubleshooting: `Unrecognized key: "sentinel"`
|
|
79
|
+
|
|
80
|
+
If gateway startup/validation reports:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
Unrecognized key: "sentinel"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
your config is using the old root-level shape. Move Sentinel config under:
|
|
87
|
+
|
|
88
|
+
- `plugins.entries.openclaw-sentinel.config`
|
|
89
|
+
|
|
90
|
+
Sentinel also logs a runtime warning when that legacy root key is still observable, but it never writes a root-level `sentinel` key.
|
|
91
|
+
|
|
44
92
|
### 4) Create your first watcher (`sentinel_control`)
|
|
45
93
|
|
|
46
94
|
```json
|
|
@@ -65,6 +113,7 @@ openclaw gateway restart
|
|
|
65
113
|
"workflow": "alerts"
|
|
66
114
|
},
|
|
67
115
|
"priority": "high",
|
|
116
|
+
"sessionGroup": "portfolio-risk",
|
|
68
117
|
"deadlineTemplate": "${timestamp}",
|
|
69
118
|
"payloadTemplate": {
|
|
70
119
|
"event": "${event.name}",
|
|
@@ -96,12 +145,13 @@ Use `sentinel_control`:
|
|
|
96
145
|
|
|
97
146
|
1. Sentinel evaluates conditions.
|
|
98
147
|
2. On match, it dispatches a generic callback envelope (`type: "sentinel.callback"`) to `localDispatchBase + webhookPath`.
|
|
99
|
-
3. The envelope includes stable keys (`intent`, `context`, `watcher`, `trigger`, bounded `payload`, `deliveryTargets`, `source`) so downstream agent behavior is workflow-agnostic.
|
|
100
|
-
4.
|
|
101
|
-
5.
|
|
102
|
-
6. OpenClaw
|
|
148
|
+
3. The envelope includes stable keys (`intent`, `context`, `watcher`, `trigger`, bounded `payload`, `deliveryTargets`, `deliveryContext`, `source`) so downstream agent behavior is workflow-agnostic.
|
|
149
|
+
4. For `/hooks/sentinel`, Sentinel enqueues an instruction-prefixed system event plus structured JSON envelope and requests heartbeat wake.
|
|
150
|
+
5. The hook route creates a **response-delivery contract** keyed by callback dedupe key, preserving original chat/session context (`deliveryContext`) and intended relay targets.
|
|
151
|
+
6. OpenClaw processes each callback in an isolated hook session: per-watcher by default, or grouped when `hookSessionGroup` / `fire.sessionGroup` is set. Shared global hook-session mode is intentionally not supported.
|
|
152
|
+
7. When hook-session LLM output arrives, Sentinel relays assistant-authored text to the original chat context. If no assistant output arrives before `hookResponseTimeoutMs`, optional fallback relay behavior is applied (`hookResponseFallbackMode`).
|
|
103
153
|
|
|
104
|
-
The `/hooks/sentinel` route is auto-registered on plugin startup (idempotent).
|
|
154
|
+
The `/hooks/sentinel` route is auto-registered on plugin startup (idempotent). Response contracts are dedupe-aware by callback dedupe key (`hookResponseDedupeWindowMs`).
|
|
105
155
|
|
|
106
156
|
Sample emitted envelope:
|
|
107
157
|
|
|
@@ -120,6 +170,12 @@ Sample emitted envelope:
|
|
|
120
170
|
"context": { "asset": "ETH", "priceUsd": 5001, "workflow": "alerts" },
|
|
121
171
|
"payload": { "ethereum": { "usd": 5001 } },
|
|
122
172
|
"deliveryTargets": [{ "channel": "telegram", "to": "5613673222" }],
|
|
173
|
+
"deliveryContext": {
|
|
174
|
+
"sessionKey": "agent:main:telegram:direct:5613673222",
|
|
175
|
+
"messageChannel": "telegram",
|
|
176
|
+
"requesterSenderId": "5613673222",
|
|
177
|
+
"currentChat": { "channel": "telegram", "to": "5613673222" }
|
|
178
|
+
},
|
|
123
179
|
"source": { "plugin": "openclaw-sentinel", "route": "/hooks/sentinel" }
|
|
124
180
|
}
|
|
125
181
|
```
|
|
@@ -185,6 +241,124 @@ It **does not** execute user-authored code from watcher definitions.
|
|
|
185
241
|
|
|
186
242
|
`deliveryTargets` is optional. If omitted on `create`, Sentinel infers a default target from the current tool/session context (channel + current peer).
|
|
187
243
|
|
|
244
|
+
## Notification payload delivery modes
|
|
245
|
+
|
|
246
|
+
Sentinel always dispatches the callback envelope to `localDispatchBase + webhookPath` on match.
|
|
247
|
+
`notificationPayloadMode` only controls **additional fan-out messages** to `deliveryTargets` for watcher dispatches (for example `/hooks/agent`).
|
|
248
|
+
It does **not** control `/hooks/sentinel` hook-response contracts or assistant-output relay behavior.
|
|
249
|
+
|
|
250
|
+
Global mode options:
|
|
251
|
+
|
|
252
|
+
- `none`: suppress delivery-target notification messages (callback dispatch still occurs)
|
|
253
|
+
- `concise` (default): send short relay text only
|
|
254
|
+
- `debug`: send relay text plus `SENTINEL_DEBUG_ENVELOPE_JSON` block
|
|
255
|
+
|
|
256
|
+
### 1) Global notifications disabled (`none`)
|
|
257
|
+
|
|
258
|
+
```json5
|
|
259
|
+
{
|
|
260
|
+
sentinel: {
|
|
261
|
+
allowedHosts: ["api.github.com"],
|
|
262
|
+
notificationPayloadMode: "none",
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 2) Global concise relay (default)
|
|
268
|
+
|
|
269
|
+
```json5
|
|
270
|
+
{
|
|
271
|
+
sentinel: {
|
|
272
|
+
allowedHosts: ["api.github.com"],
|
|
273
|
+
notificationPayloadMode: "concise",
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 3) Global debug diagnostics
|
|
279
|
+
|
|
280
|
+
```json5
|
|
281
|
+
{
|
|
282
|
+
sentinel: {
|
|
283
|
+
allowedHosts: ["api.github.com"],
|
|
284
|
+
notificationPayloadMode: "debug",
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
In debug mode, delivery notifications include the same concise relay line plus a `SENTINEL_DEBUG_ENVELOPE_JSON` block for diagnostics.
|
|
290
|
+
|
|
291
|
+
### 4) Per-watcher override (`watcher.fire.notificationPayloadMode`)
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"action": "create",
|
|
296
|
+
"watcher": {
|
|
297
|
+
"id": "status-watch",
|
|
298
|
+
"skillId": "skills.ops",
|
|
299
|
+
"enabled": true,
|
|
300
|
+
"strategy": "http-poll",
|
|
301
|
+
"endpoint": "https://status.example.com/api/health",
|
|
302
|
+
"intervalMs": 10000,
|
|
303
|
+
"match": "all",
|
|
304
|
+
"conditions": [{ "path": "status", "op": "eq", "value": "degraded" }],
|
|
305
|
+
"fire": {
|
|
306
|
+
"webhookPath": "/hooks/agent",
|
|
307
|
+
"eventName": "service_degraded",
|
|
308
|
+
"notificationPayloadMode": "none",
|
|
309
|
+
"payloadTemplate": { "event": "${event.name}", "status": "${payload.status}" }
|
|
310
|
+
},
|
|
311
|
+
"retry": { "maxRetries": 5, "baseMs": 250, "maxMs": 5000 }
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Allowed values:
|
|
317
|
+
|
|
318
|
+
- `inherit` (or omitted): follow global `notificationPayloadMode`
|
|
319
|
+
- `none`: suppress delivery-target notification messages for this watcher
|
|
320
|
+
- `concise`: force concise notification text for this watcher
|
|
321
|
+
- `debug`: force debug envelope output for this watcher
|
|
322
|
+
|
|
323
|
+
Precedence: **watcher override > global setting**.
|
|
324
|
+
|
|
325
|
+
### Migration notes
|
|
326
|
+
|
|
327
|
+
- Existing installs keep default behavior (`concise`) unless you set `notificationPayloadMode` explicitly.
|
|
328
|
+
- If you want callback-only operation (wake LLM loop via `/hooks/sentinel` but no delivery-target chat message), set global or per-watcher mode to `none`.
|
|
329
|
+
|
|
330
|
+
## Hook-response delivery contract (`/hooks/sentinel`)
|
|
331
|
+
|
|
332
|
+
`/hooks/sentinel` now enforces a dedicated trigger → LLM → user-visible relay contract:
|
|
333
|
+
|
|
334
|
+
1. Callback is enqueued to isolated hook session.
|
|
335
|
+
2. Contract captures original delivery context (`deliveryContext` + resolved `deliveryTargets`).
|
|
336
|
+
3. First assistant-authored `llm_output` for that pending callback is relayed to target chat.
|
|
337
|
+
4. If no assistant output arrives in time (`hookResponseTimeoutMs`), fallback is configurable:
|
|
338
|
+
- `hookResponseFallbackMode: "concise"` (default) sends a short fail-safe relay.
|
|
339
|
+
- `hookResponseFallbackMode: "none"` suppresses fallback.
|
|
340
|
+
5. Repeated callbacks with same dedupe key are idempotent within `hookResponseDedupeWindowMs`.
|
|
341
|
+
|
|
342
|
+
Example config:
|
|
343
|
+
|
|
344
|
+
```json5
|
|
345
|
+
{
|
|
346
|
+
plugins: {
|
|
347
|
+
entries: {
|
|
348
|
+
"openclaw-sentinel": {
|
|
349
|
+
enabled: true,
|
|
350
|
+
config: {
|
|
351
|
+
allowedHosts: ["api.github.com"],
|
|
352
|
+
hookResponseTimeoutMs: 30000,
|
|
353
|
+
hookResponseFallbackMode: "concise",
|
|
354
|
+
hookResponseDedupeWindowMs: 120000,
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
188
362
|
## Runtime controls
|
|
189
363
|
|
|
190
364
|
```json
|
package/dist/callbackEnvelope.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { SENTINEL_ORIGIN_ACCOUNT_METADATA, SENTINEL_ORIGIN_CHANNEL_METADATA, SENTINEL_ORIGIN_SESSION_KEY_METADATA, SENTINEL_ORIGIN_TARGET_METADATA, } from "./types.js";
|
|
2
3
|
import { renderTemplate } from "./template.js";
|
|
3
4
|
const MAX_PAYLOAD_JSON_CHARS = 4000;
|
|
4
5
|
function toIntent(eventName) {
|
|
@@ -30,6 +31,32 @@ function truncatePayload(payload) {
|
|
|
30
31
|
preview: serialized.slice(0, MAX_PAYLOAD_JSON_CHARS),
|
|
31
32
|
};
|
|
32
33
|
}
|
|
34
|
+
function buildDeliveryContextFromMetadata(watcher) {
|
|
35
|
+
const metadata = watcher.metadata;
|
|
36
|
+
if (!metadata)
|
|
37
|
+
return undefined;
|
|
38
|
+
const sessionKey = metadata[SENTINEL_ORIGIN_SESSION_KEY_METADATA]?.trim();
|
|
39
|
+
const channel = metadata[SENTINEL_ORIGIN_CHANNEL_METADATA]?.trim();
|
|
40
|
+
const to = metadata[SENTINEL_ORIGIN_TARGET_METADATA]?.trim();
|
|
41
|
+
const accountId = metadata[SENTINEL_ORIGIN_ACCOUNT_METADATA]?.trim();
|
|
42
|
+
const context = {};
|
|
43
|
+
if (sessionKey)
|
|
44
|
+
context.sessionKey = sessionKey;
|
|
45
|
+
if (channel)
|
|
46
|
+
context.messageChannel = channel;
|
|
47
|
+
if (to)
|
|
48
|
+
context.requesterSenderId = to;
|
|
49
|
+
if (accountId)
|
|
50
|
+
context.agentAccountId = accountId;
|
|
51
|
+
if (channel && to) {
|
|
52
|
+
context.currentChat = {
|
|
53
|
+
channel,
|
|
54
|
+
to,
|
|
55
|
+
...(accountId ? { accountId } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return Object.keys(context).length > 0 ? context : undefined;
|
|
59
|
+
}
|
|
33
60
|
function getPath(obj, path) {
|
|
34
61
|
return path.split(".").reduce((acc, part) => acc?.[part], obj);
|
|
35
62
|
}
|
|
@@ -73,6 +100,7 @@ export function createCallbackEnvelope(args) {
|
|
|
73
100
|
const dedupeSeed = getTemplateString(watcher.fire.dedupeKeyTemplate, context) ??
|
|
74
101
|
`${watcher.id}|${watcher.fire.eventName}|${matchedAt}`;
|
|
75
102
|
const dedupeKey = createHash("sha256").update(dedupeSeed).digest("hex");
|
|
103
|
+
const deliveryContext = buildDeliveryContextFromMetadata(watcher);
|
|
76
104
|
return {
|
|
77
105
|
type: "sentinel.callback",
|
|
78
106
|
version: "1",
|
|
@@ -89,6 +117,8 @@ export function createCallbackEnvelope(args) {
|
|
|
89
117
|
priority,
|
|
90
118
|
...(deadline ? { deadline } : {}),
|
|
91
119
|
},
|
|
120
|
+
...(watcher.fire.sessionGroup ? { hookSessionGroup: watcher.fire.sessionGroup } : {}),
|
|
121
|
+
...(deliveryContext ? { deliveryContext } : {}),
|
|
92
122
|
context: renderedContext ?? summarizePayload(payload),
|
|
93
123
|
payload: truncatePayload(payload),
|
|
94
124
|
deliveryTargets: watcher.deliveryTargets ?? [],
|
package/dist/configSchema.js
CHANGED
|
@@ -6,12 +6,25 @@ const LimitsSchema = Type.Object({
|
|
|
6
6
|
maxConditionsPerWatcher: Type.Integer({ minimum: 1 }),
|
|
7
7
|
maxIntervalMsFloor: Type.Integer({ minimum: 1 }),
|
|
8
8
|
}, { additionalProperties: false });
|
|
9
|
+
const NotificationPayloadModeSchema = Type.Union([
|
|
10
|
+
Type.Literal("none"),
|
|
11
|
+
Type.Literal("concise"),
|
|
12
|
+
Type.Literal("debug"),
|
|
13
|
+
]);
|
|
14
|
+
const HookResponseFallbackModeSchema = Type.Union([Type.Literal("none"), Type.Literal("concise")]);
|
|
9
15
|
const ConfigSchema = Type.Object({
|
|
10
16
|
allowedHosts: Type.Array(Type.String()),
|
|
11
17
|
localDispatchBase: Type.String({ minLength: 1 }),
|
|
12
18
|
dispatchAuthToken: Type.Optional(Type.String()),
|
|
13
19
|
hookSessionKey: Type.Optional(Type.String({ minLength: 1 })),
|
|
20
|
+
hookSessionPrefix: Type.Optional(Type.String({ minLength: 1 })),
|
|
21
|
+
hookSessionGroup: Type.Optional(Type.String({ minLength: 1 })),
|
|
22
|
+
hookRelayDedupeWindowMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
23
|
+
hookResponseTimeoutMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
24
|
+
hookResponseFallbackMode: Type.Optional(HookResponseFallbackModeSchema),
|
|
25
|
+
hookResponseDedupeWindowMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
14
26
|
stateFilePath: Type.Optional(Type.String()),
|
|
27
|
+
notificationPayloadMode: Type.Optional(NotificationPayloadModeSchema),
|
|
15
28
|
limits: Type.Optional(LimitsSchema),
|
|
16
29
|
}, { additionalProperties: false });
|
|
17
30
|
function withDefaults(value) {
|
|
@@ -22,8 +35,23 @@ function withDefaults(value) {
|
|
|
22
35
|
? value.localDispatchBase
|
|
23
36
|
: "http://127.0.0.1:18789",
|
|
24
37
|
dispatchAuthToken: typeof value.dispatchAuthToken === "string" ? value.dispatchAuthToken : undefined,
|
|
25
|
-
hookSessionKey: typeof value.hookSessionKey === "string" ? value.hookSessionKey :
|
|
38
|
+
hookSessionKey: typeof value.hookSessionKey === "string" ? value.hookSessionKey : undefined,
|
|
39
|
+
hookSessionPrefix: typeof value.hookSessionPrefix === "string"
|
|
40
|
+
? value.hookSessionPrefix
|
|
41
|
+
: "agent:main:hooks:sentinel",
|
|
42
|
+
hookSessionGroup: typeof value.hookSessionGroup === "string" ? value.hookSessionGroup : undefined,
|
|
43
|
+
hookRelayDedupeWindowMs: typeof value.hookRelayDedupeWindowMs === "number" ? value.hookRelayDedupeWindowMs : 120000,
|
|
44
|
+
hookResponseTimeoutMs: typeof value.hookResponseTimeoutMs === "number" ? value.hookResponseTimeoutMs : 30000,
|
|
45
|
+
hookResponseFallbackMode: value.hookResponseFallbackMode === "none" ? "none" : "concise",
|
|
46
|
+
hookResponseDedupeWindowMs: typeof value.hookResponseDedupeWindowMs === "number"
|
|
47
|
+
? value.hookResponseDedupeWindowMs
|
|
48
|
+
: 120000,
|
|
26
49
|
stateFilePath: typeof value.stateFilePath === "string" ? value.stateFilePath : undefined,
|
|
50
|
+
notificationPayloadMode: value.notificationPayloadMode === "none"
|
|
51
|
+
? "none"
|
|
52
|
+
: value.notificationPayloadMode === "debug"
|
|
53
|
+
? "debug"
|
|
54
|
+
: "concise",
|
|
27
55
|
limits: {
|
|
28
56
|
maxWatchersTotal: typeof limitsIn.maxWatchersTotal === "number" ? limitsIn.maxWatchersTotal : 200,
|
|
29
57
|
maxWatchersPerSkill: typeof limitsIn.maxWatchersPerSkill === "number" ? limitsIn.maxWatchersPerSkill : 20,
|
|
@@ -96,13 +124,51 @@ export const sentinelConfigSchema = {
|
|
|
96
124
|
},
|
|
97
125
|
hookSessionKey: {
|
|
98
126
|
type: "string",
|
|
99
|
-
description: "
|
|
100
|
-
|
|
127
|
+
description: "Deprecated alias for hookSessionPrefix. Sentinel always appends watcher/group segments to prevent a shared global callback session.",
|
|
128
|
+
},
|
|
129
|
+
hookSessionPrefix: {
|
|
130
|
+
type: "string",
|
|
131
|
+
description: "Base session key prefix used for isolated /hooks/sentinel callback sessions (default: agent:main:hooks:sentinel)",
|
|
132
|
+
default: "agent:main:hooks:sentinel",
|
|
133
|
+
},
|
|
134
|
+
hookSessionGroup: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Optional default session group key. When set, callbacks without explicit hookSessionGroup are routed to this group session.",
|
|
137
|
+
},
|
|
138
|
+
hookRelayDedupeWindowMs: {
|
|
139
|
+
type: "number",
|
|
140
|
+
minimum: 0,
|
|
141
|
+
description: "Suppress duplicate relay messages for the same dedupe key within this window (milliseconds)",
|
|
142
|
+
default: 120000,
|
|
143
|
+
},
|
|
144
|
+
hookResponseTimeoutMs: {
|
|
145
|
+
type: "number",
|
|
146
|
+
minimum: 0,
|
|
147
|
+
description: "Milliseconds to wait for an assistant-authored hook response before optional fallback relay",
|
|
148
|
+
default: 30000,
|
|
149
|
+
},
|
|
150
|
+
hookResponseFallbackMode: {
|
|
151
|
+
type: "string",
|
|
152
|
+
enum: ["none", "concise"],
|
|
153
|
+
description: "Fallback behavior when no assistant response arrives before hookResponseTimeoutMs: none (silent timeout) or concise fail-safe relay",
|
|
154
|
+
default: "concise",
|
|
155
|
+
},
|
|
156
|
+
hookResponseDedupeWindowMs: {
|
|
157
|
+
type: "number",
|
|
158
|
+
minimum: 0,
|
|
159
|
+
description: "Deduplicate hook response-delivery contracts by dedupe key within this window (milliseconds)",
|
|
160
|
+
default: 120000,
|
|
101
161
|
},
|
|
102
162
|
stateFilePath: {
|
|
103
163
|
type: "string",
|
|
104
164
|
description: "Custom path for the sentinel state persistence file",
|
|
105
165
|
},
|
|
166
|
+
notificationPayloadMode: {
|
|
167
|
+
type: "string",
|
|
168
|
+
enum: ["none", "concise", "debug"],
|
|
169
|
+
description: "Controls delivery-target notifications: none (suppress message fan-out), concise relay text (default), or relay text with debug envelope payload",
|
|
170
|
+
default: "concise",
|
|
171
|
+
},
|
|
106
172
|
limits: {
|
|
107
173
|
type: "object",
|
|
108
174
|
additionalProperties: false,
|
|
@@ -148,8 +214,38 @@ export const sentinelConfigSchema = {
|
|
|
148
214
|
placeholder: "sk-...",
|
|
149
215
|
},
|
|
150
216
|
hookSessionKey: {
|
|
151
|
-
label: "
|
|
152
|
-
help: "
|
|
217
|
+
label: "Hook Session Key (Deprecated)",
|
|
218
|
+
help: "Deprecated alias for hookSessionPrefix. Sentinel appends watcher/group segments automatically.",
|
|
219
|
+
advanced: true,
|
|
220
|
+
},
|
|
221
|
+
hookSessionPrefix: {
|
|
222
|
+
label: "Hook Session Prefix",
|
|
223
|
+
help: "Base prefix for isolated callback sessions (default: agent:main:hooks:sentinel)",
|
|
224
|
+
advanced: true,
|
|
225
|
+
},
|
|
226
|
+
hookSessionGroup: {
|
|
227
|
+
label: "Default Hook Session Group",
|
|
228
|
+
help: "Optional default group key for callback sessions. Watchers with the same group share one isolated session.",
|
|
229
|
+
advanced: true,
|
|
230
|
+
},
|
|
231
|
+
hookRelayDedupeWindowMs: {
|
|
232
|
+
label: "Hook Relay Dedupe Window (ms)",
|
|
233
|
+
help: "Suppress duplicate relay messages with the same dedupe key for this many milliseconds",
|
|
234
|
+
advanced: true,
|
|
235
|
+
},
|
|
236
|
+
hookResponseTimeoutMs: {
|
|
237
|
+
label: "Hook Response Timeout (ms)",
|
|
238
|
+
help: "How long to wait for assistant-authored hook output before optional fallback relay",
|
|
239
|
+
advanced: true,
|
|
240
|
+
},
|
|
241
|
+
hookResponseFallbackMode: {
|
|
242
|
+
label: "Hook Response Fallback Mode",
|
|
243
|
+
help: "If timeout occurs, choose none (silent) or concise fail-safe relay",
|
|
244
|
+
advanced: true,
|
|
245
|
+
},
|
|
246
|
+
hookResponseDedupeWindowMs: {
|
|
247
|
+
label: "Hook Response Dedupe Window (ms)",
|
|
248
|
+
help: "Deduplicate hook-response delivery contracts by dedupe key within this window",
|
|
153
249
|
advanced: true,
|
|
154
250
|
},
|
|
155
251
|
stateFilePath: {
|
|
@@ -157,6 +253,11 @@ export const sentinelConfigSchema = {
|
|
|
157
253
|
help: "Custom path for sentinel state persistence file",
|
|
158
254
|
advanced: true,
|
|
159
255
|
},
|
|
256
|
+
notificationPayloadMode: {
|
|
257
|
+
label: "Notification Payload Mode",
|
|
258
|
+
help: "Choose none (suppress delivery-target messages), concise relay text (default), or include debug envelope payload",
|
|
259
|
+
advanced: true,
|
|
260
|
+
},
|
|
160
261
|
"limits.maxWatchersTotal": {
|
|
161
262
|
label: "Max Watchers",
|
|
162
263
|
help: "Maximum total watchers across all skills",
|