@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.
- package/hook/handler.ts +42 -10
- package/index.js +45 -10
- 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
|
-
|
|
46
|
+
const apiKey = getAmpApiKey();
|
|
47
|
+
await fetch(buildAmpUrl('/api/log'), {
|
|
24
48
|
method: 'POST',
|
|
25
|
-
headers: { 'X-API-Key':
|
|
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
|
-
|
|
67
|
+
const apiKey = getAmpApiKey();
|
|
68
|
+
await fetch(buildAmpUrl('/api/agent/setState'), {
|
|
44
69
|
method: 'POST',
|
|
45
|
-
headers: { 'X-API-Key':
|
|
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
|
|
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':
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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':
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
const apiKey = getAmpApiKey();
|
|
172
|
+
await fetch(buildAmpUrl('/api/log'), {
|
|
140
173
|
method: 'POST',
|
|
141
|
-
headers: { 'X-API-Key':
|
|
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
|
|
215
|
+
const apiKey = getAmpApiKey();
|
|
216
|
+
const res = await fetch(buildAmpUrl('/api/hitl/request'), {
|
|
183
217
|
method: 'POST',
|
|
184
|
-
headers: { 'X-API-Key':
|
|
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
|
-
`${
|
|
213
|
-
{ headers: { 'X-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.
|
|
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": [
|
|
17
|
+
"extensions": [
|
|
18
|
+
"./index.js"
|
|
19
|
+
]
|
|
18
20
|
}
|
|
19
21
|
}
|