@inquiryon/amp-governance 1.1.5 → 1.1.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 CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as fs from 'fs';
2
+ import { normalizeTool } from './toolNormalization.js';
2
3
 
3
4
  console.log('[AMP Governance] Plugin module loaded — Phase 4.');
4
5
 
@@ -55,13 +56,10 @@ let _lastSender = null; // { from: string, channelId: string }
55
56
  // Set by register() so notifyUser can use the runtime API directly
56
57
  let _runtime = null;
57
58
 
58
- // AMP reachability state — flips to false on first connectivity failure,
59
- // back to true when a request succeeds again.
59
+ // AMP reachability state — flips to false when a real AMP call fails,
60
+ // back to true the next time a real AMP call succeeds.
60
61
  let _ampReachable = true;
61
62
  let _ampDownNotified = false; // prevent notification spam while AMP is down
62
- let _recoveryPoller = null; // setInterval handle for recovery polling
63
-
64
- const RECOVERY_POLL_INTERVAL_MS = 15000; // 15 seconds
65
63
 
66
64
  // ── HOOK AUTO-DEPLOY ─────────────────────────────────────────────────────────
67
65
 
@@ -223,39 +221,13 @@ async function notifyUser(sender, message) {
223
221
  }
224
222
 
225
223
  /**
226
- * Lightweight AMP reachability check does a quick HTTP GET to a known
227
- * endpoint. Any HTTP response (even 4xx) means AMP is up; a network-level
228
- * error (ECONNREFUSED, ETIMEDOUT, etc.) means it is down.
229
- * Returns true if reachable, false if not.
230
- */
231
- async function pingAmp() {
232
- try {
233
- const apiKey = getAmpApiKey();
234
- const controller = new AbortController();
235
- const timer = setTimeout(() => controller.abort(), 4000); // 4 s timeout
236
- try {
237
- await fetch(buildAmpUrl('/api/amp/sse-status'), {
238
- method: 'GET',
239
- signal: controller.signal,
240
- });
241
- } finally {
242
- clearTimeout(timer);
243
- }
244
- return true; // any HTTP response = server is up
245
- } catch (_) {
246
- return false; // connection refused / timeout / DNS failure
247
- }
248
- }
249
-
250
- /**
251
- * Called whenever AMP is found to be unreachable.
224
+ * Called whenever a real AMP call fails (init or hitl/request).
252
225
  * Sends a clear channel notification on the first occurrence, then stays quiet
253
226
  * until AMP recovers (to avoid flooding the user with repeated messages).
254
227
  */
255
228
  async function notifyAmpDown(tool, errDetail) {
256
229
  const ampUrl = (() => { try { return buildAmpUrl('/'); } catch (_) { return config?.AMP_BACKEND_URL || 'unknown'; } })();
257
230
 
258
- startRecoveryPoller();
259
231
  if (!_ampDownNotified) {
260
232
  _ampDownNotified = true;
261
233
  const msg =
@@ -275,14 +247,13 @@ async function notifyAmpDown(tool, errDetail) {
275
247
  }
276
248
 
277
249
  /**
278
- * Called when AMP responds successfully after a period of being down.
279
- * Clears the down state and stops the recovery poller.
250
+ * Called when a real AMP call succeeds after a period of being down.
251
+ * Clears the down state so the next failure notifies again.
280
252
  */
281
253
  async function notifyAmpRecovered() {
282
254
  if (!_ampReachable) {
283
255
  _ampReachable = true;
284
256
  _ampDownNotified = false;
285
- stopRecoveryPoller();
286
257
  await notifyUser(_lastSender,
287
258
  `✅ *AMP Governance Service Restored*\n\nThe AMP Governance service is back online. OpenClaw is resuming normal operation — your next request will be processed.`
288
259
  );
@@ -290,27 +261,6 @@ async function notifyAmpRecovered() {
290
261
  }
291
262
  }
292
263
 
293
- function stopRecoveryPoller() {
294
- if (_recoveryPoller !== null) {
295
- clearInterval(_recoveryPoller);
296
- _recoveryPoller = null;
297
- console.log('[AMP Governance] Recovery poller stopped.');
298
- }
299
- }
300
-
301
- function startRecoveryPoller() {
302
- if (_recoveryPoller !== null) return; // already running
303
- console.log(`[AMP Governance] Starting recovery poller (every ${RECOVERY_POLL_INTERVAL_MS / 1000}s)...`);
304
- _recoveryPoller = setInterval(async () => {
305
- const reachable = await pingAmp();
306
- if (reachable) {
307
- await notifyAmpRecovered();
308
- } else {
309
- console.log('[AMP Governance] Recovery poll: AMP still unreachable.');
310
- }
311
- }, RECOVERY_POLL_INTERVAL_MS);
312
- }
313
-
314
264
  // ── HITL POLICY ENGINE ───────────────────────────────────────────────────────
315
265
 
316
266
  /**
@@ -319,6 +269,7 @@ function startRecoveryPoller() {
319
269
  */
320
270
  async function requestHitlEval(instanceId, tool, params) {
321
271
  const apiKey = getAmpApiKey();
272
+ const { tool: normTool, action: normAction } = normalizeTool(tool, params);
322
273
  const res = await fetch(buildAmpUrl('/api/hitl/request'), {
323
274
  method: 'POST',
324
275
  headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
@@ -327,8 +278,8 @@ async function requestHitlEval(instanceId, tool, params) {
327
278
  instance_id: instanceId,
328
279
  org_id: config.AMP_ORG_ID,
329
280
  agent_name: config.AGENT_NAME,
330
- tool,
331
- action: params?.action || '*',
281
+ tool: normTool,
282
+ action: normAction,
332
283
  context: params || {},
333
284
  hitl: {
334
285
  enable: true,
@@ -584,23 +535,12 @@ export default {
584
535
  _instanceId = null;
585
536
  });
586
537
 
587
- // ── INBOUND MESSAGE: cache sender + probe AMP health ─────────────────────
538
+ // ── INBOUND MESSAGE: cache sender for HITL/outage notifications ──────────
588
539
  api.on('message_received', async (event, ctx) => {
589
540
  if (event.from && ctx.channelId) {
590
541
  _lastSender = { from: event.from, channelId: ctx.channelId };
591
542
  console.log(`[AMP Governance] Sender cached: ${event.from} on ${ctx.channelId}`);
592
543
  }
593
- // Probe AMP on every inbound message so we catch outages even when the
594
- // agent responds without using any tools (e.g. simple greetings).
595
- // pingAmp() does a real HTTP check — bypasses any cached instance state.
596
- const reachable = await pingAmp();
597
- if (!reachable) {
598
- _ampReachable = false;
599
- await notifyAmpDown('(inbound message)',
600
- 'Could not reach AMP Governance service — backend may be down or unreachable.');
601
- } else if (!_ampReachable) {
602
- await notifyAmpRecovered();
603
- }
604
544
  });
605
545
 
606
546
  // ── BEFORE TOOL CALL: governance check + logging ─────────────────────────
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@inquiryon/amp-governance",
3
- "version": "1.1.5",
3
+ "version": "1.1.6",
4
4
  "description": "AMP governance plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "index.js",
8
+ "toolNormalization.js",
8
9
  "openclaw.plugin.json",
9
10
  "hook/handler.ts",
10
11
  "hook/HOOK.md",
@@ -0,0 +1,26 @@
1
+ // Normalizes OpenClaw's raw tool names into the same tool/action vocabulary
2
+ // used by AMP's Hermes plugin (AHP's policy.py), so a single AMP policy can
3
+ // govern both agent types identically instead of matching on each agent's
4
+ // own raw tool names.
5
+
6
+ const TOOL_MAP = {
7
+ web_search: { tool: 'exec', action: 'web_search' },
8
+ read: { tool: 'read', action: 'read' },
9
+ write: { tool: 'write', action: 'write' },
10
+ edit: { tool: 'write', action: 'edit' },
11
+ bash: { tool: 'exec', action: 'exec' },
12
+ shell: { tool: 'exec', action: 'exec' },
13
+ };
14
+
15
+ /**
16
+ * Returns { tool, action } for a raw OpenClaw tool name.
17
+ * Unlike Hermes's normalizer (which returns null for anything outside its
18
+ * known set), unmapped tools fall back to the raw name with action '*' —
19
+ * so a not-yet-mapped OpenClaw tool still reaches AMP instead of being
20
+ * silently dropped.
21
+ */
22
+ export function normalizeTool(rawTool, params) {
23
+ const mapped = TOOL_MAP[rawTool];
24
+ if (mapped) return { tool: mapped.tool, action: mapped.action };
25
+ return { tool: rawTool, action: params?.action || '*' };
26
+ }