@drakon-systems/shieldcortex-realtime 4.47.25 → 4.47.27

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.
@@ -0,0 +1,124 @@
1
+ /**
2
+ * ShieldCortex — the OpenClaw gateway operator-notify channel (#143).
3
+ *
4
+ * Design: docs/design/2026-07-31-ai-approval-broker.md, acceptance criterion 6:
5
+ * "On OpenClaw the transport should use the gateway's own message capability."
6
+ *
7
+ * Same shape and same reasoning as broker-invoker.ts's `createGatewayInvoker`:
8
+ * the gateway does not expose a message-sending seam to plugins by any fixed
9
+ * contract today, so this defines the narrowest one that could satisfy the
10
+ * design and fails closed when it is absent —
11
+ *
12
+ * no `context.notifyOperator` → no invoker → `createGatewayNotifyChannel`
13
+ * returns null → `requestOperatorApproval`'s resolution order (see
14
+ * operator-notify.ts) simply has one fewer candidate and falls through to
15
+ * whatever else is configured, ultimately to the unchanged
16
+ * hash-in-terminal fallback.
17
+ *
18
+ * Which is exactly today's behaviour on every gateway build shipping now:
19
+ * nothing regresses, and wiring this up costs nothing on a host that hasn't
20
+ * implemented the seam yet.
21
+ *
22
+ * What this file deliberately does NOT contain: any client for Telegram,
23
+ * WhatsApp, or any other chat platform. That lives entirely on the gateway
24
+ * side of `notifyOperator`, wherever the host chooses to implement it — this
25
+ * plugin only ever hands over structured data and a rendered text fallback,
26
+ * mirroring the "no Telegram client hard-coded into the guard core"
27
+ * requirement from the design doc.
28
+ *
29
+ * Types are declared/rendered LOCALLY rather than imported from
30
+ * `shieldcortex/defence/iron-dome/operator-notify.js` — same reason
31
+ * `ToolGuardVerdictLike` is structural in interceptor.ts (see that file's
32
+ * header): this module is built by tsconfig.openclaw-plugin.json, whose
33
+ * `rootDir` is `plugins/openclaw`, and a cross-boundary import fails that
34
+ * build (TS6059). `NotifyChannelLike`/`OperatorNotificationLike` mirror
35
+ * `operator-notify.ts`'s real shapes structurally, and the real
36
+ * `requestOperatorApproval` accepts anything satisfying `NotifyChannel`
37
+ * regardless of which module declared the type.
38
+ */
39
+ /**
40
+ * Local, minimal text rendering — deliberately duplicated rather than
41
+ * imported (see the module header). Kept in sync BY HAND with
42
+ * `formatOperatorNotification` in operator-notify.ts, whose own test suite
43
+ * pins the fields that must appear (exact command, tripped signals, tier,
44
+ * judge verdict, both affordances on the same hash); this local copy is
45
+ * pinned by this file's own tests below the same way.
46
+ */
47
+ function renderText(n) {
48
+ const lines = [
49
+ '🛡️ ShieldCortex — approval needed',
50
+ '',
51
+ `Tool: ${n.tool}`,
52
+ `Command: ${n.command}`,
53
+ `Tripped: ${n.signals.join(', ') || 'none'}`,
54
+ `Tier: ${n.severity}`,
55
+ `Reason: ${n.reason}`,
56
+ '',
57
+ ];
58
+ if (n.judge) {
59
+ lines.push(`AI judge: ${n.judge.assessment} (confidence ${n.judge.confidence})` +
60
+ `${n.judge.inContext ? '' : ', out of context'}` +
61
+ `${n.judge.injectionSuspected ? ', INJECTION SUSPECTED' : ''}`);
62
+ if (n.judge.rationale)
63
+ lines.push(` "${n.judge.rationale}"`);
64
+ }
65
+ else {
66
+ lines.push('AI judge: no judge ran — this verdict is rules-only');
67
+ }
68
+ lines.push('');
69
+ lines.push(`[Approve] shieldcortex approve ${n.shortHash}`);
70
+ lines.push(`[Deny] shieldcortex deny ${n.shortHash}`);
71
+ return lines.join('\n').slice(0, 4_000);
72
+ }
73
+ function buildMessage(n) {
74
+ return {
75
+ text: renderText(n),
76
+ hash: n.hash,
77
+ shortHash: n.shortHash,
78
+ tool: n.tool,
79
+ command: n.command,
80
+ signals: n.signals,
81
+ severity: n.severity,
82
+ approveCommand: `shieldcortex approve ${n.shortHash}`,
83
+ denyCommand: `shieldcortex deny ${n.shortHash}`,
84
+ };
85
+ }
86
+ /**
87
+ * Read the seam's ack as narrowly as `coerceCompletion` reads a completion in
88
+ * broker-invoker.ts: a resolved call with no explicit `{ delivered: false }`
89
+ * is treated as accepted (some hosts will not have a richer ack shape at
90
+ * all), but an EXPLICIT `delivered: false` is honoured, and nothing beyond
91
+ * the boolean is ever read — a hostile or careless host echoing back
92
+ * `approved: true` gets nothing from this for it.
93
+ */
94
+ function coerceAck(raw) {
95
+ if (raw && typeof raw === 'object' && raw.delivered === false) {
96
+ const reason = raw.reason;
97
+ return { delivered: false, reason: typeof reason === 'string' ? reason : 'gateway reported delivery failure' };
98
+ }
99
+ return { delivered: true };
100
+ }
101
+ /**
102
+ * Build a notify channel from the gateway's own message seam, or null when
103
+ * the gateway does not offer one. Null is not an error state — see the
104
+ * module doc.
105
+ */
106
+ export function createGatewayNotifyChannel(context) {
107
+ if (!context || typeof context !== 'object')
108
+ return null;
109
+ const notifyOperator = context.notifyOperator;
110
+ if (typeof notifyOperator !== 'function')
111
+ return null;
112
+ return {
113
+ name: 'gateway',
114
+ async send(notification) {
115
+ try {
116
+ const raw = await notifyOperator(buildMessage(notification));
117
+ return coerceAck(raw);
118
+ }
119
+ catch (err) {
120
+ return { delivered: false, reason: err instanceof Error ? err.message : String(err) };
121
+ }
122
+ },
123
+ };
124
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "shieldcortex-realtime",
3
- "version": "4.47.25",
3
+ "version": "4.47.27",
4
4
  "name": "ShieldCortex Real-time Scanner",
5
5
  "description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
6
6
  "kind": null,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "shieldcortex-realtime",
3
- "version": "4.47.25",
3
+ "version": "4.47.27",
4
4
  "name": "ShieldCortex Real-time Scanner",
5
5
  "description": "Real-time defence scanning on LLM input, memory extraction on LLM output, and active tool call interception with approval gating.",
6
6
  "kind": null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakon-systems/shieldcortex-realtime",
3
- "version": "4.47.25",
3
+ "version": "4.47.27",
4
4
  "description": "OpenClaw plugin for ShieldCortex real-time defence scanning and optional memory extraction.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",