@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.
Files changed (2) hide show
  1. package/index.js +38 -1
  2. 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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inquiryon/openclaw-amp-governance",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "AMP governance plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "files": [