@agentapprove/openclaw 0.1.5 → 0.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/dist/index.js +158 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -154,7 +154,7 @@ function deriveEpochKey(rootKeyHex, epoch) {
|
|
|
154
154
|
}
|
|
155
155
|
return current;
|
|
156
156
|
}
|
|
157
|
-
function migrateRootKey() {
|
|
157
|
+
function migrateRootKey(debug = false) {
|
|
158
158
|
if (!existsSync2(E2E_KEY_FILE) || existsSync2(E2E_ROOT_KEY_FILE)) return;
|
|
159
159
|
try {
|
|
160
160
|
copyFileSync(E2E_KEY_FILE, E2E_ROOT_KEY_FILE);
|
|
@@ -173,12 +173,12 @@ function migrateRootKey() {
|
|
|
173
173
|
};
|
|
174
174
|
writeFileSync2(E2E_ROTATION_FILE, JSON.stringify(config, null, 2), { mode: 384 });
|
|
175
175
|
}
|
|
176
|
-
debugLog("Migrated e2e-key to e2e-root-key");
|
|
176
|
+
if (debug) debugLog("Migrated e2e-key to e2e-root-key");
|
|
177
177
|
} catch {
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
function checkAndRotateKeys(currentKeyHex) {
|
|
181
|
-
migrateRootKey();
|
|
180
|
+
function checkAndRotateKeys(currentKeyHex, debug = false) {
|
|
181
|
+
migrateRootKey(debug);
|
|
182
182
|
if (!existsSync2(E2E_ROTATION_FILE) || !existsSync2(E2E_ROOT_KEY_FILE)) {
|
|
183
183
|
return currentKeyHex;
|
|
184
184
|
}
|
|
@@ -217,7 +217,7 @@ function checkAndRotateKeys(currentKeyHex) {
|
|
|
217
217
|
writeFileSync2(E2E_ROTATION_FILE, JSON.stringify(rotCfg, null, 2), { mode: 384 });
|
|
218
218
|
} catch {
|
|
219
219
|
}
|
|
220
|
-
debugLog(`E2E key rotated: epoch ${currentEpoch} -> ${expectedEpoch}`);
|
|
220
|
+
if (debug) debugLog(`E2E key rotated: epoch ${currentEpoch} -> ${expectedEpoch}`);
|
|
221
221
|
return newKey;
|
|
222
222
|
}
|
|
223
223
|
|
|
@@ -293,7 +293,7 @@ function loadConfig(openclawConfig, logger) {
|
|
|
293
293
|
const timeout = openclawConfig?.timeout || parseInt(process.env.AGENTAPPROVE_TIMEOUT || "", 10) || parseInt(parseConfigValue(fileContent, "AGENTAPPROVE_TIMEOUT") || "", 10) || 300;
|
|
294
294
|
const failBehavior = openclawConfig?.failBehavior || process.env.AGENTAPPROVE_FAIL_BEHAVIOR || parseConfigValue(fileContent, "AGENTAPPROVE_FAIL_BEHAVIOR") || "ask";
|
|
295
295
|
const privacyTier = openclawConfig?.privacyTier || process.env.AGENTAPPROVE_PRIVACY || parseConfigValue(fileContent, "AGENTAPPROVE_PRIVACY") || "full";
|
|
296
|
-
const debug = openclawConfig?.debug || process.env.AGENTAPPROVE_DEBUG === "true" || parseConfigValue(fileContent, "AGENTAPPROVE_DEBUG") === "true" || false;
|
|
296
|
+
const debug = openclawConfig?.debug || process.env.AGENTAPPROVE_DEBUG === "true" || process.env.AGENTAPPROVE_DEBUG_LOG === "true" || parseConfigValue(fileContent, "AGENTAPPROVE_DEBUG") === "true" || parseConfigValue(fileContent, "AGENTAPPROVE_DEBUG_LOG") === "true" || false;
|
|
297
297
|
const agentName = process.env.AGENTAPPROVE_AGENT_NAME || parseConfigValue(fileContent, "AGENTAPPROVE_OPENCLAW_NAME") || "OpenClaw";
|
|
298
298
|
const e2eEnabled = process.env.AGENTAPPROVE_E2E_ENABLED === "true" || parseConfigValue(fileContent, "AGENTAPPROVE_E2E_ENABLED") === "true";
|
|
299
299
|
const e2eUserKeyPath = join3(homedir3(), ".agentapprove", "e2e-key");
|
|
@@ -316,7 +316,7 @@ function loadConfig(openclawConfig, logger) {
|
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
if (e2eUserKey) {
|
|
319
|
-
e2eUserKey = checkAndRotateKeys(e2eUserKey) || e2eUserKey;
|
|
319
|
+
e2eUserKey = checkAndRotateKeys(e2eUserKey, debug) || e2eUserKey;
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
if (debug) {
|
|
@@ -331,7 +331,7 @@ function loadConfig(openclawConfig, logger) {
|
|
|
331
331
|
failBehavior,
|
|
332
332
|
privacyTier,
|
|
333
333
|
debug,
|
|
334
|
-
hookVersion: "1.1.
|
|
334
|
+
hookVersion: "1.1.3",
|
|
335
335
|
agentName,
|
|
336
336
|
e2eEnabled,
|
|
337
337
|
e2eUserKey,
|
|
@@ -453,12 +453,142 @@ function applyEventPrivacyFilter(event, privacyTier) {
|
|
|
453
453
|
return filtered;
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
// src/config-sync.ts
|
|
457
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, renameSync } from "fs";
|
|
458
|
+
import { join as join4 } from "path";
|
|
459
|
+
import { homedir as homedir4 } from "os";
|
|
460
|
+
function getAADir() {
|
|
461
|
+
return process.env.__AGENTAPPROVE_TEST_DIR || join4(homedir4(), ".agentapprove");
|
|
462
|
+
}
|
|
463
|
+
function getEnvPath() {
|
|
464
|
+
return join4(getAADir(), "env");
|
|
465
|
+
}
|
|
466
|
+
function getRotationFile() {
|
|
467
|
+
return join4(getAADir(), "e2e-rotation.json");
|
|
468
|
+
}
|
|
469
|
+
var VALID_PRIVACY = /* @__PURE__ */ new Set(["minimal", "summary", "full"]);
|
|
470
|
+
var VALID_FAIL = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
|
|
471
|
+
function updateEnvValues(updates) {
|
|
472
|
+
const envPath = getEnvPath();
|
|
473
|
+
if (!existsSync4(envPath)) return;
|
|
474
|
+
const validUpdates = Object.fromEntries(
|
|
475
|
+
Object.entries(updates).filter(([, v]) => !/[`$(){};<>|&!\\]/.test(v))
|
|
476
|
+
);
|
|
477
|
+
const updateKeys = new Set(Object.keys(validUpdates));
|
|
478
|
+
if (updateKeys.size === 0) return;
|
|
479
|
+
try {
|
|
480
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
481
|
+
const lines = content.split("\n");
|
|
482
|
+
const filtered = lines.filter((line) => {
|
|
483
|
+
const trimmed = line.replace(/^export\s+/, "").trim();
|
|
484
|
+
const eqIdx = trimmed.indexOf("=");
|
|
485
|
+
if (eqIdx <= 0) return true;
|
|
486
|
+
return !updateKeys.has(trimmed.slice(0, eqIdx));
|
|
487
|
+
});
|
|
488
|
+
for (const [key, value] of Object.entries(validUpdates)) {
|
|
489
|
+
filtered.push(`export ${key}=${value}`);
|
|
490
|
+
}
|
|
491
|
+
const tmpPath = `${envPath}.tmp.${process.pid}`;
|
|
492
|
+
writeFileSync3(tmpPath, filtered.join("\n"), { mode: 384 });
|
|
493
|
+
renameSync(tmpPath, envPath);
|
|
494
|
+
} catch {
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
function syncRotationConfig(serverPeriod, serverStartedAt, debug) {
|
|
498
|
+
const rotationFile = getRotationFile();
|
|
499
|
+
if (!existsSync4(rotationFile)) return;
|
|
500
|
+
try {
|
|
501
|
+
const raw = readFileSync5(rotationFile, "utf-8");
|
|
502
|
+
const config = JSON.parse(raw);
|
|
503
|
+
const currentPeriod = config.periodSeconds ?? 0;
|
|
504
|
+
const newPeriod = serverPeriod ?? 0;
|
|
505
|
+
let needsUpdate = false;
|
|
506
|
+
if (typeof newPeriod === "number" && newPeriod !== currentPeriod) {
|
|
507
|
+
config.periodSeconds = newPeriod;
|
|
508
|
+
needsUpdate = true;
|
|
509
|
+
}
|
|
510
|
+
if (serverStartedAt && (!config.startedAt || config.startedAt === "null")) {
|
|
511
|
+
config.startedAt = serverStartedAt;
|
|
512
|
+
needsUpdate = true;
|
|
513
|
+
}
|
|
514
|
+
if (needsUpdate) {
|
|
515
|
+
const tmpPath = `${rotationFile}.tmp.${process.pid}`;
|
|
516
|
+
writeFileSync3(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
517
|
+
renameSync(tmpPath, rotationFile);
|
|
518
|
+
if (debug) {
|
|
519
|
+
debugLog(`E2E rotation synced: periodSeconds=${newPeriod}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
} catch {
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
function processConfigSync(parsed, config) {
|
|
526
|
+
const sync = parsed.configSync;
|
|
527
|
+
if (!sync || !sync.configSetAt) return;
|
|
528
|
+
const localSetAt = getLocalConfigSetAt();
|
|
529
|
+
if (sync.configSetAt <= localSetAt) return;
|
|
530
|
+
if (config.debug) {
|
|
531
|
+
debugLog(`Config sync: server=${sync.configSetAt} > local=${localSetAt}, updating...`);
|
|
532
|
+
}
|
|
533
|
+
const envUpdates = {};
|
|
534
|
+
if (sync.privacyTier && VALID_PRIVACY.has(sync.privacyTier)) {
|
|
535
|
+
envUpdates["AGENTAPPROVE_PRIVACY"] = sync.privacyTier;
|
|
536
|
+
config.privacyTier = sync.privacyTier;
|
|
537
|
+
if (config.debug) debugLog(`Config synced: privacy=${sync.privacyTier}`);
|
|
538
|
+
}
|
|
539
|
+
if (sync.timeoutSeconds != null) {
|
|
540
|
+
const t = sync.timeoutSeconds;
|
|
541
|
+
if (t >= 30 && t <= 600) {
|
|
542
|
+
envUpdates["AGENTAPPROVE_TIMEOUT"] = String(t);
|
|
543
|
+
config.timeout = t;
|
|
544
|
+
if (config.debug) debugLog(`Config synced: timeout=${t}`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (sync.failBehavior && VALID_FAIL.has(sync.failBehavior)) {
|
|
548
|
+
envUpdates["AGENTAPPROVE_FAIL_BEHAVIOR"] = sync.failBehavior;
|
|
549
|
+
config.failBehavior = sync.failBehavior;
|
|
550
|
+
if (config.debug) debugLog(`Config synced: failBehavior=${sync.failBehavior}`);
|
|
551
|
+
}
|
|
552
|
+
if (sync.e2eEnabled === true || sync.e2eEnabled === false) {
|
|
553
|
+
envUpdates["AGENTAPPROVE_E2E_ENABLED"] = String(sync.e2eEnabled);
|
|
554
|
+
config.e2eEnabled = sync.e2eEnabled;
|
|
555
|
+
if (config.debug) debugLog(`Config synced: e2eEnabled=${sync.e2eEnabled}`);
|
|
556
|
+
}
|
|
557
|
+
syncRotationConfig(sync.e2eRotationPeriod, sync.e2eRotationStartedAt, config.debug);
|
|
558
|
+
if (config.e2eEnabled && config.e2eUserKey) {
|
|
559
|
+
const rotatedKey = checkAndRotateKeys(config.e2eUserKey, config.debug);
|
|
560
|
+
if (rotatedKey && rotatedKey !== config.e2eUserKey) {
|
|
561
|
+
config.e2eUserKey = rotatedKey;
|
|
562
|
+
if (config.debug) debugLog(`Key rotated after config sync`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
envUpdates["AGENTAPPROVE_CONFIG_SET_AT"] = String(sync.configSetAt);
|
|
566
|
+
updateEnvValues(envUpdates);
|
|
567
|
+
}
|
|
568
|
+
function getLocalConfigSetAt() {
|
|
569
|
+
const envPath = getEnvPath();
|
|
570
|
+
if (!existsSync4(envPath)) return 0;
|
|
571
|
+
try {
|
|
572
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
573
|
+
for (const raw of content.split("\n")) {
|
|
574
|
+
const line = raw.replace(/^export\s+/, "").trim();
|
|
575
|
+
if (line.startsWith("AGENTAPPROVE_CONFIG_SET_AT=")) {
|
|
576
|
+
const val = line.slice("AGENTAPPROVE_CONFIG_SET_AT=".length).replace(/^["']|["']$/g, "");
|
|
577
|
+
const num = parseInt(val, 10);
|
|
578
|
+
return isNaN(num) ? 0 : num;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} catch {
|
|
582
|
+
}
|
|
583
|
+
return 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
456
586
|
// src/api-client.ts
|
|
457
587
|
var cachedPluginHash;
|
|
458
|
-
function getPluginHash(pluginPath) {
|
|
588
|
+
function getPluginHash(pluginPath, debug = false) {
|
|
459
589
|
if (!cachedPluginHash) {
|
|
460
590
|
cachedPluginHash = computePluginHash(pluginPath);
|
|
461
|
-
if (cachedPluginHash) {
|
|
591
|
+
if (cachedPluginHash && debug) {
|
|
462
592
|
debugLog(`Plugin hash computed: ${cachedPluginHash.slice(0, 16)}...`);
|
|
463
593
|
}
|
|
464
594
|
}
|
|
@@ -515,7 +645,7 @@ async function sendApprovalRequest(request, config, pluginPath) {
|
|
|
515
645
|
payload = applyPrivacyFilter(request, config.privacyTier);
|
|
516
646
|
}
|
|
517
647
|
const bodyStr = JSON.stringify(payload);
|
|
518
|
-
const pluginHash = getPluginHash(pluginPath);
|
|
648
|
+
const pluginHash = getPluginHash(pluginPath, config.debug);
|
|
519
649
|
const hmacHeaders = buildHMACHeaders(bodyStr, config.token, config.hookVersion, pluginHash);
|
|
520
650
|
const headers = {
|
|
521
651
|
"Authorization": `Bearer ${config.token}`,
|
|
@@ -532,11 +662,14 @@ async function sendApprovalRequest(request, config, pluginPath) {
|
|
|
532
662
|
if (response.status !== 200) {
|
|
533
663
|
throw new Error(`API returned status ${response.status}: ${response.body.slice(0, 200)}`);
|
|
534
664
|
}
|
|
665
|
+
let parsed;
|
|
535
666
|
try {
|
|
536
|
-
|
|
667
|
+
parsed = JSON.parse(response.body);
|
|
537
668
|
} catch {
|
|
538
669
|
throw new Error(`Failed to parse API response: ${response.body.slice(0, 200)}`);
|
|
539
670
|
}
|
|
671
|
+
processConfigSync(parsed, config);
|
|
672
|
+
return parsed;
|
|
540
673
|
}
|
|
541
674
|
async function sendEvent(event, config, pluginPath) {
|
|
542
675
|
if (!config.token) return;
|
|
@@ -554,7 +687,7 @@ async function sendEvent(event, config, pluginPath) {
|
|
|
554
687
|
}
|
|
555
688
|
}
|
|
556
689
|
const bodyStr = JSON.stringify(payload);
|
|
557
|
-
const pluginHash = getPluginHash(pluginPath);
|
|
690
|
+
const pluginHash = getPluginHash(pluginPath, config.debug);
|
|
558
691
|
const hmacHeaders = buildHMACHeaders(bodyStr, config.token, config.hookVersion, pluginHash);
|
|
559
692
|
const headers = {
|
|
560
693
|
"Authorization": `Bearer ${config.token}`,
|
|
@@ -570,6 +703,13 @@ async function sendEvent(event, config, pluginPath) {
|
|
|
570
703
|
if (config.debug) {
|
|
571
704
|
debugLog(`send_event response: ${response.body.slice(0, 200)}`);
|
|
572
705
|
}
|
|
706
|
+
if (response.status === 200) {
|
|
707
|
+
try {
|
|
708
|
+
const parsed = JSON.parse(response.body);
|
|
709
|
+
processConfigSync(parsed, config);
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
573
713
|
} catch (err) {
|
|
574
714
|
if (config.debug) {
|
|
575
715
|
debugLog(`Failed to send ${eventType || "event"}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -712,7 +852,11 @@ function register(api) {
|
|
|
712
852
|
}
|
|
713
853
|
api.logger.info(`Agent Approve: Plugin loaded (privacy: ${config.privacyTier}, fail: ${config.failBehavior})`);
|
|
714
854
|
if (config.debug) {
|
|
715
|
-
|
|
855
|
+
const e2eStatus = !config.e2eEnabled ? "disabled" : !config.e2eUserKey ? "enabled (key missing)" : "enabled";
|
|
856
|
+
debugLog(
|
|
857
|
+
`Plugin loaded: v${config.hookVersion}, api=${config.apiUrl}, privacy=${config.privacyTier}, e2e=${e2eStatus}, debug=${config.debug}`
|
|
858
|
+
);
|
|
859
|
+
debugLog(`Full config: agent=${config.agentName}, timeout=${config.timeout}s, fail=${config.failBehavior}`);
|
|
716
860
|
}
|
|
717
861
|
api.on("before_tool_call", async (event, ctx) => {
|
|
718
862
|
const conversationId = resolveConversationId();
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "openclaw",
|
|
3
3
|
"name": "Agent Approve",
|
|
4
4
|
"description": "Mobile approval for AI agent tool execution. Approve or deny tool calls from your iPhone and Apple Watch.",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.6",
|
|
6
6
|
"homepage": "https://agentapprove.com",
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED