@inquiryon/openclaw-amp-governance 1.0.4 → 1.0.6
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/index.js +38 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
+
import { exec } from 'child_process';
|
|
2
3
|
|
|
3
4
|
console.log('[AMP Governance] Plugin module loaded — Phase 4.');
|
|
4
5
|
|
|
@@ -23,6 +24,9 @@ try {
|
|
|
23
24
|
// Module-level instance cache so we only init once per process lifetime
|
|
24
25
|
let _instanceId = null;
|
|
25
26
|
|
|
27
|
+
// Last known sender — populated by message_received, used for HITL notifications
|
|
28
|
+
let _lastSender = null; // { from: string, channelId: string }
|
|
29
|
+
|
|
26
30
|
function readSession() {
|
|
27
31
|
try {
|
|
28
32
|
return JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
|
@@ -111,6 +115,25 @@ async function ampLog(instanceId, message, level = 'INFO') {
|
|
|
111
115
|
}
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
// ── PROACTIVE NOTIFICATIONS ──────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Send a proactive WhatsApp message to the user without waiting for the agent
|
|
122
|
+
* to finish. Uses the openclaw CLI so we don't need to reverse-engineer the
|
|
123
|
+
* gateway REST API. Fire-and-forget — errors are logged but never thrown.
|
|
124
|
+
*/
|
|
125
|
+
const OPENCLAW_BIN = `${process.env.HOME}/.npm-global/bin/openclaw`;
|
|
126
|
+
|
|
127
|
+
function notifyUser(sender, message) {
|
|
128
|
+
if (!sender?.from || !sender?.channelId) return;
|
|
129
|
+
const safeMsg = message.replace(/"/g, '\\"');
|
|
130
|
+
const cmd = `${OPENCLAW_BIN} message send --channel ${sender.channelId} --target "${sender.from}" --message "${safeMsg}"`;
|
|
131
|
+
console.log(`[AMP Governance] Sending notification to ${sender.from}: ${message}`);
|
|
132
|
+
exec(cmd, (err) => {
|
|
133
|
+
if (err) console.warn('[AMP Governance] notifyUser failed:', err.message);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
114
137
|
// ── HITL POLICY ENGINE ───────────────────────────────────────────────────────
|
|
115
138
|
|
|
116
139
|
/**
|
|
@@ -209,12 +232,16 @@ async function checkToolPolicy(instanceId, tool, params) {
|
|
|
209
232
|
console.log(`[AMP Governance] HITL required for "${tool}" — waiting for human decision...`);
|
|
210
233
|
await ampLog(instanceId, `HITL requested for tool: ${tool} — awaiting human approval in AMP`, 'WARN');
|
|
211
234
|
|
|
235
|
+
// Notify the user immediately so they aren't staring at silence
|
|
236
|
+
notifyUser(_lastSender, `I need a reviewer to approve "${tool}" before I can proceed. I'll follow up once the decision is made — this may take a few minutes.`);
|
|
237
|
+
|
|
212
238
|
let decision;
|
|
213
239
|
try {
|
|
214
240
|
decision = await pollHitlDecision(instanceId);
|
|
215
241
|
} catch (err) {
|
|
216
242
|
const msg = `Tool call "${tool}" timed out waiting for human approval — blocked.`;
|
|
217
243
|
await ampLog(instanceId, msg, 'ERROR');
|
|
244
|
+
notifyUser(_lastSender, `No response from the reviewer — "${tool}" was blocked due to timeout.`);
|
|
218
245
|
return { block: true, blockReason: msg };
|
|
219
246
|
}
|
|
220
247
|
|
|
@@ -223,11 +250,13 @@ async function checkToolPolicy(instanceId, tool, params) {
|
|
|
223
250
|
|
|
224
251
|
if (resolution === 'approve' || resolution === 'approved') {
|
|
225
252
|
await ampLog(instanceId, `HITL approved: ${tool} | workitem: ${decision.workitem_id}`);
|
|
253
|
+
notifyUser(_lastSender, `The reviewer approved "${tool}" — continuing now.`);
|
|
226
254
|
return {};
|
|
227
255
|
}
|
|
228
256
|
|
|
229
257
|
if (resolution === 'modify' || resolution === 'modified') {
|
|
230
258
|
await ampLog(instanceId, `HITL approved with modification: ${tool} | workitem: ${decision.workitem_id}`);
|
|
259
|
+
notifyUser(_lastSender, `The reviewer approved "${tool}" (with modifications) — continuing now.`);
|
|
231
260
|
return {};
|
|
232
261
|
}
|
|
233
262
|
|
|
@@ -235,6 +264,7 @@ async function checkToolPolicy(instanceId, tool, params) {
|
|
|
235
264
|
const info = decision.information ? ` Reviewer note: ${decision.information}` : '';
|
|
236
265
|
const msg = `Tool call "${tool}" was rejected by a human reviewer.${info}`;
|
|
237
266
|
await ampLog(instanceId, `HITL rejected: ${tool} | workitem: ${decision.workitem_id}`, 'WARN');
|
|
267
|
+
notifyUser(_lastSender, `The reviewer rejected "${tool}".${info}`);
|
|
238
268
|
return { block: true, blockReason: msg };
|
|
239
269
|
}
|
|
240
270
|
|
|
@@ -360,6 +390,14 @@ export default {
|
|
|
360
390
|
register(api) {
|
|
361
391
|
api.logger.info('AMP Governance registered. Phase 4 - eval policy enforcement active.');
|
|
362
392
|
|
|
393
|
+
// ── INBOUND MESSAGE: cache sender for HITL notifications ─────────────────
|
|
394
|
+
api.on('message_received', (event, ctx) => {
|
|
395
|
+
if (event.from && ctx.channelId) {
|
|
396
|
+
_lastSender = { from: event.from, channelId: ctx.channelId };
|
|
397
|
+
console.log(`[AMP Governance] Sender cached: ${event.from} on ${ctx.channelId}`);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
363
401
|
// ── BEFORE TOOL CALL: governance check + logging ─────────────────────────
|
|
364
402
|
api.on('before_tool_call', async (event) => {
|
|
365
403
|
const tool = event.toolName || event.name || 'unknown-tool';
|
|
@@ -403,7 +441,6 @@ export default {
|
|
|
403
441
|
const instanceId = _instanceId || readSession()?.instanceId;
|
|
404
442
|
const msgText = event.content || event.text || event.message || JSON.stringify(event);
|
|
405
443
|
const preview = msgText.substring(0, 100);
|
|
406
|
-
console.log(`[AMP Governance] message_sending fired: ${preview}`);
|
|
407
444
|
if (instanceId) {
|
|
408
445
|
await ampLog(instanceId, `Agent reply: ${preview}`);
|
|
409
446
|
}
|