@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 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.2",
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
- return JSON.parse(response.body);
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
- debugLog(`Plugin loaded, API: ${config.apiUrl}, agent: ${config.agentName}`);
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();
@@ -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",
5
+ "version": "0.1.6",
6
6
  "homepage": "https://agentapprove.com",
7
7
  "configSchema": {
8
8
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentapprove/openclaw",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Agent Approve plugin for OpenClaw - approve or deny AI agent tool calls from your iPhone and Apple Watch",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {