@inquiryon/openclaw-amp-governance 1.1.2 → 1.1.3

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 (3) hide show
  1. package/hook/handler.ts +42 -10
  2. package/index.js +45 -10
  3. package/package.json +4 -2
package/hook/handler.ts CHANGED
@@ -6,6 +6,29 @@ console.log('--- [AMP Hook] Logic Loaded — Phase 5 ---');
6
6
  const configPath = path.join(process.env.HOME || '', '.openclaw/hooks/amp/amp_config.json');
7
7
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
8
8
 
9
+ function normalizeBackendUrl(raw: unknown): string {
10
+ let base = String(raw || '').trim();
11
+ if (!base) return '';
12
+ if (!/^https?:\/\//i.test(base)) base = `https://${base}`;
13
+ return base.replace(/\/+$/, '');
14
+ }
15
+
16
+ function buildAmpUrl(p: string): string {
17
+ const base = normalizeBackendUrl((config as any).AMP_BACKEND_URL);
18
+ if (!base) throw new Error('AMP_BACKEND_URL missing');
19
+ const suffix = p.startsWith('/') ? p : `/${p}`;
20
+ return `${base}${suffix}`;
21
+ }
22
+
23
+ function getAmpApiKey(): string {
24
+ let key = String((config as any).AMP_API_KEY || '').trim();
25
+ if ((key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))) {
26
+ key = key.slice(1, -1).trim();
27
+ }
28
+ if (/^bearer\s+/i.test(key)) key = key.replace(/^bearer\s+/i, '').trim();
29
+ return key;
30
+ }
31
+
9
32
  const SESSION_FILE = '/tmp/amp-session-state.json';
10
33
  const COMMAND_THRESHOLD = 30; // roll to new instance after this many tool calls
11
34
 
@@ -20,9 +43,10 @@ let _startupCleanupDone = false;
20
43
 
21
44
  async function ampLog(instanceId: string, message: string, level: string = 'INFO'): Promise<void> {
22
45
  try {
23
- await fetch(`${config.AMP_BACKEND_URL}/api/log`, {
46
+ const apiKey = getAmpApiKey();
47
+ await fetch(buildAmpUrl('/api/log'), {
24
48
  method: 'POST',
25
- headers: { 'X-API-Key': config.AMP_API_KEY, 'Content-Type': 'application/json' },
49
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
26
50
  body: JSON.stringify({
27
51
  instance_id: instanceId,
28
52
  service: config.AGENT_NAME,
@@ -40,9 +64,10 @@ async function ampLog(instanceId: string, message: string, level: string = 'INFO
40
64
 
41
65
  async function setInstanceFinished(instanceId: string): Promise<void> {
42
66
  try {
43
- await fetch(`${config.AMP_BACKEND_URL}/api/agent/setState`, {
67
+ const apiKey = getAmpApiKey();
68
+ await fetch(buildAmpUrl('/api/agent/setState'), {
44
69
  method: 'POST',
45
- headers: { 'X-API-Key': config.AMP_API_KEY, 'Content-Type': 'application/json' },
70
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
46
71
  body: JSON.stringify({ agent_name: config.AGENT_NAME, instance_id: instanceId, state: 'finished' })
47
72
  });
48
73
  console.log(`[AMP Hook] Instance ${instanceId} marked finished.`);
@@ -123,9 +148,11 @@ async function finalizeInstance(convKey: string, reason: string): Promise<void>
123
148
 
124
149
  async function initInstance(convKey: string, prompt: string, metadata: object): Promise<string | null> {
125
150
  try {
126
- const response = await fetch(`${config.AMP_BACKEND_URL}/api/agent/init`, {
151
+ const initUrl = buildAmpUrl('/api/agent/init');
152
+ const apiKey = getAmpApiKey();
153
+ const response = await fetch(initUrl, {
127
154
  method: 'POST',
128
- headers: { 'X-API-Key': config.AMP_API_KEY, 'Content-Type': 'application/json' },
155
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
129
156
  body: JSON.stringify({
130
157
  agent_name: config.AGENT_NAME,
131
158
  prompt,
@@ -134,16 +161,21 @@ async function initInstance(convKey: string, prompt: string, metadata: object):
134
161
  metadata: { source: 'openclaw-whatsapp', org_id: config.AMP_ORG_ID, ...metadata }
135
162
  })
136
163
  });
137
- const data = await response.json();
138
- console.log(`[AMP Hook] Init response: ${response.status} | instance: ${data.instance_id || 'NONE'}`);
164
+ const data = await response.json().catch(() => ({} as any));
165
+ const errText = (data && (data.error || data.detail || data.message)) ? String(data.error || data.detail || data.message) : '';
166
+ console.log(`[AMP Hook] Init response: ${response.status} | instance: ${data.instance_id || 'NONE'}${errText ? ` | error: ${errText}` : ''}`);
139
167
  if (data.instance_id) {
140
168
  sessionMap.set(convKey, data.instance_id);
141
169
  commandCounts.set(convKey, 0);
142
170
  writeSessionFile(data.instance_id, convKey);
143
171
  return data.instance_id;
144
172
  }
145
- } catch (err) {
146
- console.error('[AMP Hook] initInstance failed:', err);
173
+ } catch (err: any) {
174
+ const em = err?.message || String(err);
175
+ const ec = err?.cause ? (err.cause.code || err.cause.message || String(err.cause)) : '';
176
+ let target = '';
177
+ try { target = buildAmpUrl('/api/agent/init'); } catch {}
178
+ console.error(`[AMP Hook] initInstance failed: ${em}${ec ? ` | cause=${ec}` : ''}${target ? ` | url=${target}` : ''}`);
147
179
  }
148
180
  return null;
149
181
  }
package/index.js CHANGED
@@ -18,6 +18,31 @@ try {
18
18
  console.error('[AMP Governance] Failed to load config:', err.message);
19
19
  }
20
20
 
21
+ function normalizeBackendUrl(raw) {
22
+ let base = String(raw || '').trim();
23
+ if (!base) return '';
24
+ // If scheme is missing, default to HTTPS for server deployments.
25
+ if (!/^https?:\/\//i.test(base)) base = `https://${base}`;
26
+ return base.replace(/\/+$/, '');
27
+ }
28
+
29
+ function buildAmpUrl(path) {
30
+ const base = normalizeBackendUrl(config?.AMP_BACKEND_URL);
31
+ if (!base) throw new Error('AMP_BACKEND_URL missing');
32
+ const suffix = String(path || '').startsWith('/') ? path : `/${path}`;
33
+ return `${base}${suffix}`;
34
+ }
35
+
36
+ function getAmpApiKey() {
37
+ let key = String(config?.AMP_API_KEY || '').trim();
38
+ // tolerate accidental wrapping or "Bearer ..." pastes
39
+ if ((key.startsWith('"') && key.endsWith('"')) || (key.startsWith("'") && key.endsWith("'"))) {
40
+ key = key.slice(1, -1).trim();
41
+ }
42
+ if (/^bearer\s+/i.test(key)) key = key.replace(/^bearer\s+/i, '').trim();
43
+ return key;
44
+ }
45
+
21
46
  // HITL timeout — configurable via HITL_TIMEOUT_MINUTES in amp_config.json (default: 10)
22
47
  const HITL_TIMEOUT_MS = (config?.HITL_TIMEOUT_MINUTES ?? 10) * 60 * 1000;
23
48
 
@@ -100,9 +125,11 @@ async function ensureInstance() {
100
125
  }
101
126
 
102
127
  try {
103
- const res = await fetch(`${config.AMP_BACKEND_URL}/api/agent/init`, {
128
+ const initUrl = buildAmpUrl('/api/agent/init');
129
+ const apiKey = getAmpApiKey();
130
+ const res = await fetch(initUrl, {
104
131
  method: 'POST',
105
- headers: { 'X-API-Key': config.AMP_API_KEY, 'Content-Type': 'application/json' },
132
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
106
133
  body: JSON.stringify({
107
134
  agent_name: config.AGENT_NAME,
108
135
  org_id: config.AMP_ORG_ID,
@@ -114,7 +141,8 @@ async function ensureInstance() {
114
141
  }),
115
142
  });
116
143
  if (!res.ok) {
117
- console.error(`[AMP Governance] /api/agent/init returned ${res.status}`);
144
+ const bodyText = await res.text().catch(() => '');
145
+ console.error(`[AMP Governance] /api/agent/init returned ${res.status}${bodyText ? ` | body=${bodyText}` : ''}`);
118
146
  return null;
119
147
  }
120
148
  const data = await res.json();
@@ -126,7 +154,11 @@ async function ensureInstance() {
126
154
  }
127
155
  return _instanceId || null;
128
156
  } catch (err) {
129
- console.error('[AMP Governance] ensureInstance failed:', err.message);
157
+ const em = err && err.message ? err.message : String(err);
158
+ const ec = err && err.cause ? (err.cause.code || err.cause.message || String(err.cause)) : '';
159
+ let target = '';
160
+ try { target = buildAmpUrl('/api/agent/init'); } catch (_) {}
161
+ console.error(`[AMP Governance] ensureInstance failed: ${em}${ec ? ` | cause=${ec}` : ''}${target ? ` | url=${target}` : ''}`);
130
162
  return null;
131
163
  }
132
164
  }
@@ -136,9 +168,10 @@ async function ensureInstance() {
136
168
  async function ampLog(instanceId, message, level = 'INFO') {
137
169
  if (!config) return;
138
170
  try {
139
- await fetch(`${config.AMP_BACKEND_URL}/api/log`, {
171
+ const apiKey = getAmpApiKey();
172
+ await fetch(buildAmpUrl('/api/log'), {
140
173
  method: 'POST',
141
- headers: { 'X-API-Key': config.AMP_API_KEY, 'Content-Type': 'application/json' },
174
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
142
175
  body: JSON.stringify({
143
176
  instance_id: instanceId,
144
177
  service: config.AGENT_NAME,
@@ -179,9 +212,10 @@ async function notifyUser(sender, message) {
179
212
  * The AMP backend eval engine decides allow vs HITL.
180
213
  */
181
214
  async function requestHitlEval(instanceId, tool, params) {
182
- const res = await fetch(`${config.AMP_BACKEND_URL}/api/hitl/request`, {
215
+ const apiKey = getAmpApiKey();
216
+ const res = await fetch(buildAmpUrl('/api/hitl/request'), {
183
217
  method: 'POST',
184
- headers: { 'X-API-Key': config.AMP_API_KEY, 'Content-Type': 'application/json' },
218
+ headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
185
219
  body: JSON.stringify({
186
220
  caller_id: instanceId,
187
221
  instance_id: instanceId,
@@ -205,12 +239,13 @@ async function requestHitlEval(instanceId, tool, params) {
205
239
  * Poll /api/hitl/get-decision until we get a 'complete' status or time out.
206
240
  */
207
241
  async function pollHitlDecision(callerId) {
242
+ const apiKey = getAmpApiKey();
208
243
  const deadline = Date.now() + HITL_TIMEOUT_MS;
209
244
  while (Date.now() < deadline) {
210
245
  await new Promise(r => setTimeout(r, HITL_POLL_INTERVAL_MS));
211
246
  const res = await fetch(
212
- `${config.AMP_BACKEND_URL}/api/hitl/get-decision?caller_id=${encodeURIComponent(callerId)}`,
213
- { headers: { 'X-API-Key': config.AMP_API_KEY } }
247
+ `${buildAmpUrl('/api/hitl/get-decision')}?caller_id=${encodeURIComponent(callerId)}`,
248
+ { headers: { 'X-API-Key': apiKey } }
214
249
  );
215
250
  if (!res.ok) {
216
251
  console.warn(`[AMP Governance] get-decision returned ${res.status}, retrying...`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inquiryon/openclaw-amp-governance",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "AMP governance plugin for OpenClaw",
5
5
  "type": "module",
6
6
  "files": [
@@ -14,6 +14,8 @@
14
14
  "access": "public"
15
15
  },
16
16
  "openclaw": {
17
- "extensions": ["./index.js"]
17
+ "extensions": [
18
+ "./index.js"
19
+ ]
18
20
  }
19
21
  }