@agentapprove/openclaw 0.1.5 → 0.1.7

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
@@ -35,22 +35,77 @@ function ensureLogFile() {
35
35
  if (stat.size > MAX_SIZE) {
36
36
  const content = readFileSync(DEBUG_LOG_PATH, "utf-8");
37
37
  writeFileSync(DEBUG_LOG_PATH, content.slice(-KEEP_SIZE), { mode: 384 });
38
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
39
- appendFileSync(DEBUG_LOG_PATH, `[${ts}] [openclaw-plugin] Log rotated (exceeded 5MB)
38
+ appendFileSync(DEBUG_LOG_PATH, `[${localTimestamp()}] [debug] Log rotated (exceeded 5MB, kept last 2MB)
40
39
  `);
41
40
  }
42
41
  } catch {
43
42
  }
44
43
  }
44
+ function localTimestamp() {
45
+ const d = /* @__PURE__ */ new Date();
46
+ const pad = (n) => String(n).padStart(2, "0");
47
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
48
+ }
45
49
  function debugLog(message, hookName = "openclaw-plugin") {
46
50
  try {
47
51
  ensureLogFile();
48
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
49
- appendFileSync(DEBUG_LOG_PATH, `[${ts}] [${hookName}] ${message}
52
+ appendFileSync(DEBUG_LOG_PATH, `[${localTimestamp()}] [${hookName}] ${message}
50
53
  `);
51
54
  } catch {
52
55
  }
53
56
  }
57
+ function debugLogRawInline(data) {
58
+ try {
59
+ ensureLogFile();
60
+ appendFileSync(DEBUG_LOG_PATH, `${data}
61
+ `);
62
+ } catch {
63
+ }
64
+ }
65
+ function safeStringify(value) {
66
+ try {
67
+ const seen = /* @__PURE__ */ new WeakSet();
68
+ return JSON.stringify(value, (_key, current) => {
69
+ if (typeof current === "bigint") return `BigInt(${current.toString()})`;
70
+ if (typeof current === "function") {
71
+ const fn = current;
72
+ return `[Function ${fn.name || "anonymous"}]`;
73
+ }
74
+ if (typeof current === "symbol") return current.toString();
75
+ if (current instanceof Error) {
76
+ return {
77
+ name: current.name,
78
+ message: current.message,
79
+ stack: current.stack
80
+ };
81
+ }
82
+ if (current && typeof current === "object") {
83
+ const obj = current;
84
+ if (seen.has(obj)) return "[Circular]";
85
+ seen.add(obj);
86
+ }
87
+ return current;
88
+ }) ?? "null";
89
+ } catch (error) {
90
+ const msg = error instanceof Error ? error.message : String(error);
91
+ return `"[Unserializable: ${msg}]"`;
92
+ }
93
+ }
94
+ function debugLogRaw(data, hookName = "openclaw-plugin") {
95
+ try {
96
+ ensureLogFile();
97
+ const ts = localTimestamp();
98
+ const rawData = typeof data === "string" ? data : safeStringify(data);
99
+ appendFileSync(
100
+ DEBUG_LOG_PATH,
101
+ `[${ts}] [${hookName}] === RAW INPUT ===
102
+ ${rawData}
103
+ [${ts}] [${hookName}] === END RAW ===
104
+ `
105
+ );
106
+ } catch {
107
+ }
108
+ }
54
109
 
55
110
  // src/e2e-crypto.ts
56
111
  function keyId(keyHex) {
@@ -154,7 +209,7 @@ function deriveEpochKey(rootKeyHex, epoch) {
154
209
  }
155
210
  return current;
156
211
  }
157
- function migrateRootKey() {
212
+ function migrateRootKey(debug = false) {
158
213
  if (!existsSync2(E2E_KEY_FILE) || existsSync2(E2E_ROOT_KEY_FILE)) return;
159
214
  try {
160
215
  copyFileSync(E2E_KEY_FILE, E2E_ROOT_KEY_FILE);
@@ -173,12 +228,12 @@ function migrateRootKey() {
173
228
  };
174
229
  writeFileSync2(E2E_ROTATION_FILE, JSON.stringify(config, null, 2), { mode: 384 });
175
230
  }
176
- debugLog("Migrated e2e-key to e2e-root-key");
231
+ if (debug) debugLog("Migrated e2e-key to e2e-root-key");
177
232
  } catch {
178
233
  }
179
234
  }
180
- function checkAndRotateKeys(currentKeyHex) {
181
- migrateRootKey();
235
+ function checkAndRotateKeys(currentKeyHex, debug = false) {
236
+ migrateRootKey(debug);
182
237
  if (!existsSync2(E2E_ROTATION_FILE) || !existsSync2(E2E_ROOT_KEY_FILE)) {
183
238
  return currentKeyHex;
184
239
  }
@@ -217,7 +272,7 @@ function checkAndRotateKeys(currentKeyHex) {
217
272
  writeFileSync2(E2E_ROTATION_FILE, JSON.stringify(rotCfg, null, 2), { mode: 384 });
218
273
  } catch {
219
274
  }
220
- debugLog(`E2E key rotated: epoch ${currentEpoch} -> ${expectedEpoch}`);
275
+ if (debug) debugLog(`E2E key rotated: epoch ${currentEpoch} -> ${expectedEpoch}`);
221
276
  return newKey;
222
277
  }
223
278
 
@@ -293,7 +348,7 @@ function loadConfig(openclawConfig, logger) {
293
348
  const timeout = openclawConfig?.timeout || parseInt(process.env.AGENTAPPROVE_TIMEOUT || "", 10) || parseInt(parseConfigValue(fileContent, "AGENTAPPROVE_TIMEOUT") || "", 10) || 300;
294
349
  const failBehavior = openclawConfig?.failBehavior || process.env.AGENTAPPROVE_FAIL_BEHAVIOR || parseConfigValue(fileContent, "AGENTAPPROVE_FAIL_BEHAVIOR") || "ask";
295
350
  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;
351
+ 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
352
  const agentName = process.env.AGENTAPPROVE_AGENT_NAME || parseConfigValue(fileContent, "AGENTAPPROVE_OPENCLAW_NAME") || "OpenClaw";
298
353
  const e2eEnabled = process.env.AGENTAPPROVE_E2E_ENABLED === "true" || parseConfigValue(fileContent, "AGENTAPPROVE_E2E_ENABLED") === "true";
299
354
  const e2eUserKeyPath = join3(homedir3(), ".agentapprove", "e2e-key");
@@ -316,7 +371,7 @@ function loadConfig(openclawConfig, logger) {
316
371
  }
317
372
  }
318
373
  if (e2eUserKey) {
319
- e2eUserKey = checkAndRotateKeys(e2eUserKey) || e2eUserKey;
374
+ e2eUserKey = checkAndRotateKeys(e2eUserKey, debug) || e2eUserKey;
320
375
  }
321
376
  }
322
377
  if (debug) {
@@ -331,7 +386,7 @@ function loadConfig(openclawConfig, logger) {
331
386
  failBehavior,
332
387
  privacyTier,
333
388
  debug,
334
- hookVersion: "1.1.2",
389
+ hookVersion: "1.1.4",
335
390
  agentName,
336
391
  e2eEnabled,
337
392
  e2eUserKey,
@@ -453,13 +508,143 @@ function applyEventPrivacyFilter(event, privacyTier) {
453
508
  return filtered;
454
509
  }
455
510
 
511
+ // src/config-sync.ts
512
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, renameSync } from "fs";
513
+ import { join as join4 } from "path";
514
+ import { homedir as homedir4 } from "os";
515
+ function getAADir() {
516
+ return process.env.__AGENTAPPROVE_TEST_DIR || join4(homedir4(), ".agentapprove");
517
+ }
518
+ function getEnvPath() {
519
+ return join4(getAADir(), "env");
520
+ }
521
+ function getRotationFile() {
522
+ return join4(getAADir(), "e2e-rotation.json");
523
+ }
524
+ var VALID_PRIVACY = /* @__PURE__ */ new Set(["minimal", "summary", "full"]);
525
+ var VALID_FAIL = /* @__PURE__ */ new Set(["allow", "deny", "ask"]);
526
+ function updateEnvValues(updates) {
527
+ const envPath = getEnvPath();
528
+ if (!existsSync4(envPath)) return;
529
+ const validUpdates = Object.fromEntries(
530
+ Object.entries(updates).filter(([, v]) => !/[`$(){};<>|&!\\]/.test(v))
531
+ );
532
+ const updateKeys = new Set(Object.keys(validUpdates));
533
+ if (updateKeys.size === 0) return;
534
+ try {
535
+ const content = readFileSync5(envPath, "utf-8");
536
+ const lines = content.split("\n");
537
+ const filtered = lines.filter((line) => {
538
+ const trimmed = line.replace(/^export\s+/, "").trim();
539
+ const eqIdx = trimmed.indexOf("=");
540
+ if (eqIdx <= 0) return true;
541
+ return !updateKeys.has(trimmed.slice(0, eqIdx));
542
+ });
543
+ for (const [key, value] of Object.entries(validUpdates)) {
544
+ filtered.push(`export ${key}=${value}`);
545
+ }
546
+ const tmpPath = `${envPath}.tmp.${process.pid}`;
547
+ writeFileSync3(tmpPath, filtered.join("\n"), { mode: 384 });
548
+ renameSync(tmpPath, envPath);
549
+ } catch {
550
+ }
551
+ }
552
+ function syncRotationConfig(serverPeriod, serverStartedAt, debug) {
553
+ const rotationFile = getRotationFile();
554
+ if (!existsSync4(rotationFile)) return;
555
+ try {
556
+ const raw = readFileSync5(rotationFile, "utf-8");
557
+ const config = JSON.parse(raw);
558
+ const currentPeriod = config.periodSeconds ?? 0;
559
+ const newPeriod = serverPeriod ?? 0;
560
+ let needsUpdate = false;
561
+ if (typeof newPeriod === "number" && newPeriod !== currentPeriod) {
562
+ config.periodSeconds = newPeriod;
563
+ needsUpdate = true;
564
+ }
565
+ if (serverStartedAt && (!config.startedAt || config.startedAt === "null")) {
566
+ config.startedAt = serverStartedAt;
567
+ needsUpdate = true;
568
+ }
569
+ if (needsUpdate) {
570
+ const tmpPath = `${rotationFile}.tmp.${process.pid}`;
571
+ writeFileSync3(tmpPath, JSON.stringify(config, null, 2), { mode: 384 });
572
+ renameSync(tmpPath, rotationFile);
573
+ if (debug) {
574
+ debugLog(`E2E rotation synced: periodSeconds=${newPeriod}`);
575
+ }
576
+ }
577
+ } catch {
578
+ }
579
+ }
580
+ function processConfigSync(parsed, config) {
581
+ const sync = parsed.configSync;
582
+ if (!sync || !sync.configSetAt) return;
583
+ const localSetAt = getLocalConfigSetAt();
584
+ if (sync.configSetAt <= localSetAt) return;
585
+ if (config.debug) {
586
+ debugLog(`Config sync: server=${sync.configSetAt} > local=${localSetAt}, updating...`);
587
+ }
588
+ const envUpdates = {};
589
+ if (sync.privacyTier && VALID_PRIVACY.has(sync.privacyTier)) {
590
+ envUpdates["AGENTAPPROVE_PRIVACY"] = sync.privacyTier;
591
+ config.privacyTier = sync.privacyTier;
592
+ if (config.debug) debugLog(`Config synced: privacy=${sync.privacyTier}`);
593
+ }
594
+ if (sync.timeoutSeconds != null) {
595
+ const t = sync.timeoutSeconds;
596
+ if (t >= 30 && t <= 600) {
597
+ envUpdates["AGENTAPPROVE_TIMEOUT"] = String(t);
598
+ config.timeout = t;
599
+ if (config.debug) debugLog(`Config synced: timeout=${t}`);
600
+ }
601
+ }
602
+ if (sync.failBehavior && VALID_FAIL.has(sync.failBehavior)) {
603
+ envUpdates["AGENTAPPROVE_FAIL_BEHAVIOR"] = sync.failBehavior;
604
+ config.failBehavior = sync.failBehavior;
605
+ if (config.debug) debugLog(`Config synced: failBehavior=${sync.failBehavior}`);
606
+ }
607
+ if (sync.e2eEnabled === true || sync.e2eEnabled === false) {
608
+ envUpdates["AGENTAPPROVE_E2E_ENABLED"] = String(sync.e2eEnabled);
609
+ config.e2eEnabled = sync.e2eEnabled;
610
+ if (config.debug) debugLog(`Config synced: e2eEnabled=${sync.e2eEnabled}`);
611
+ }
612
+ syncRotationConfig(sync.e2eRotationPeriod, sync.e2eRotationStartedAt, config.debug);
613
+ if (config.e2eEnabled && config.e2eUserKey) {
614
+ const rotatedKey = checkAndRotateKeys(config.e2eUserKey, config.debug);
615
+ if (rotatedKey && rotatedKey !== config.e2eUserKey) {
616
+ config.e2eUserKey = rotatedKey;
617
+ if (config.debug) debugLog(`Key rotated after config sync`);
618
+ }
619
+ }
620
+ envUpdates["AGENTAPPROVE_CONFIG_SET_AT"] = String(sync.configSetAt);
621
+ updateEnvValues(envUpdates);
622
+ }
623
+ function getLocalConfigSetAt() {
624
+ const envPath = getEnvPath();
625
+ if (!existsSync4(envPath)) return 0;
626
+ try {
627
+ const content = readFileSync5(envPath, "utf-8");
628
+ for (const raw of content.split("\n")) {
629
+ const line = raw.replace(/^export\s+/, "").trim();
630
+ if (line.startsWith("AGENTAPPROVE_CONFIG_SET_AT=")) {
631
+ const val = line.slice("AGENTAPPROVE_CONFIG_SET_AT=".length).replace(/^["']|["']$/g, "");
632
+ const num = parseInt(val, 10);
633
+ return isNaN(num) ? 0 : num;
634
+ }
635
+ }
636
+ } catch {
637
+ }
638
+ return 0;
639
+ }
640
+
456
641
  // src/api-client.ts
457
642
  var cachedPluginHash;
458
- function getPluginHash(pluginPath) {
643
+ function getPluginHash(pluginPath, debug = false, hookName = "openclaw-plugin") {
459
644
  if (!cachedPluginHash) {
460
645
  cachedPluginHash = computePluginHash(pluginPath);
461
- if (cachedPluginHash) {
462
- debugLog(`Plugin hash computed: ${cachedPluginHash.slice(0, 16)}...`);
646
+ if (cachedPluginHash && debug) {
647
+ debugLog(`Plugin hash computed: ${cachedPluginHash.slice(0, 16)}...`, hookName);
463
648
  }
464
649
  }
465
650
  return cachedPluginHash || "";
@@ -501,7 +686,7 @@ function httpPost(url, body, headers, timeoutMs) {
501
686
  req.end();
502
687
  });
503
688
  }
504
- async function sendApprovalRequest(request, config, pluginPath) {
689
+ async function sendApprovalRequest(request, config, pluginPath, hookName = "openclaw-plugin") {
505
690
  if (!config.token) {
506
691
  throw new Error("No Agent Approve token configured");
507
692
  }
@@ -509,13 +694,13 @@ async function sendApprovalRequest(request, config, pluginPath) {
509
694
  if (config.e2eEnabled && config.e2eUserKey) {
510
695
  payload = applyApprovalE2E(request, config.e2eUserKey, config.e2eServerKey);
511
696
  if (config.debug) {
512
- debugLog("E2E encryption applied to approval request");
697
+ debugLog("E2E encryption applied to approval request", hookName);
513
698
  }
514
699
  } else {
515
700
  payload = applyPrivacyFilter(request, config.privacyTier);
516
701
  }
517
702
  const bodyStr = JSON.stringify(payload);
518
- const pluginHash = getPluginHash(pluginPath);
703
+ const pluginHash = getPluginHash(pluginPath, config.debug, hookName);
519
704
  const hmacHeaders = buildHMACHeaders(bodyStr, config.token, config.hookVersion, pluginHash);
520
705
  const headers = {
521
706
  "Authorization": `Bearer ${config.token}`,
@@ -523,38 +708,44 @@ async function sendApprovalRequest(request, config, pluginPath) {
523
708
  };
524
709
  const url = `${config.apiUrl}/${config.apiVersion}/approve`;
525
710
  if (config.debug) {
526
- debugLog(`Sending approval request to ${url} for tool: ${request.toolName}`);
711
+ debugLog(`Requesting approval from ${url}`, hookName);
712
+ debugLog(`=== SENT TO ${url} ===`, hookName);
713
+ debugLogRawInline(bodyStr);
714
+ debugLog("=== END SENT ===", hookName);
527
715
  }
528
716
  const response = await httpPost(url, bodyStr, headers, config.timeout * 1e3);
529
717
  if (config.debug) {
530
- debugLog(`Response status: ${response.status}, body: ${response.body.slice(0, 200)}`);
718
+ debugLog(`Response: ${response.body || "<empty>"}`, hookName);
531
719
  }
532
720
  if (response.status !== 200) {
533
721
  throw new Error(`API returned status ${response.status}: ${response.body.slice(0, 200)}`);
534
722
  }
723
+ let parsed;
535
724
  try {
536
- return JSON.parse(response.body);
725
+ parsed = JSON.parse(response.body);
537
726
  } catch {
538
727
  throw new Error(`Failed to parse API response: ${response.body.slice(0, 200)}`);
539
728
  }
729
+ processConfigSync(parsed, config);
730
+ return parsed;
540
731
  }
541
- async function sendEvent(event, config, pluginPath) {
732
+ async function sendEvent(event, config, pluginPath, hookName = "openclaw-plugin") {
542
733
  if (!config.token) return;
543
734
  const eventType = event.eventType;
544
735
  const toolName = event.toolName;
545
736
  if (config.debug) {
546
- debugLog(`Sending ${eventType || "event"}${toolName ? ` (${toolName})` : ""} (privacy: ${config.privacyTier})`);
737
+ debugLog(`Sending ${eventType || "event"}${toolName ? ` (${toolName})` : ""} (privacy: ${config.privacyTier})`, hookName);
547
738
  }
548
739
  try {
549
740
  let payload = applyEventPrivacyFilter(event, config.privacyTier);
550
741
  if (config.e2eEnabled && config.e2eUserKey) {
551
742
  payload = applyEventE2E(payload, config.e2eUserKey);
552
743
  if (config.debug) {
553
- debugLog(`E2E applied to event (type=${eventType})`);
744
+ debugLog(`E2E applied to event (type=${eventType})`, hookName);
554
745
  }
555
746
  }
556
747
  const bodyStr = JSON.stringify(payload);
557
- const pluginHash = getPluginHash(pluginPath);
748
+ const pluginHash = getPluginHash(pluginPath, config.debug, hookName);
558
749
  const hmacHeaders = buildHMACHeaders(bodyStr, config.token, config.hookVersion, pluginHash);
559
750
  const headers = {
560
751
  "Authorization": `Bearer ${config.token}`,
@@ -562,17 +753,24 @@ async function sendEvent(event, config, pluginPath) {
562
753
  };
563
754
  const url = `${config.apiUrl}/${config.apiVersion}/events`;
564
755
  if (config.debug) {
565
- debugLog(`=== SENT TO ${url} ===`);
566
- debugLog(bodyStr.slice(0, 500));
567
- debugLog("=== END SENT ===");
756
+ debugLog(`=== SENT TO ${url} ===`, hookName);
757
+ debugLogRawInline(bodyStr);
758
+ debugLog("=== END SENT ===", hookName);
568
759
  }
569
760
  const response = await httpPost(url, bodyStr, headers, 5e3);
570
761
  if (config.debug) {
571
- debugLog(`send_event response: ${response.body.slice(0, 200)}`);
762
+ debugLog(`send_event response: ${response.body || "<empty>"}`, hookName);
763
+ }
764
+ if (response.status === 200) {
765
+ try {
766
+ const parsed = JSON.parse(response.body);
767
+ processConfigSync(parsed, config);
768
+ } catch {
769
+ }
572
770
  }
573
771
  } catch (err) {
574
772
  if (config.debug) {
575
- debugLog(`Failed to send ${eventType || "event"}: ${err instanceof Error ? err.message : String(err)}`);
773
+ debugLog(`Failed to send ${eventType || "event"}: ${err instanceof Error ? err.message : String(err)}`, hookName);
576
774
  }
577
775
  }
578
776
  }
@@ -588,6 +786,19 @@ var gatewaySessionId = randomBytes2(12).toString("hex");
588
786
  var DEDUP_WINDOW_MS = 1200;
589
787
  var DEDUP_MAX_SIZE = 300;
590
788
  var recentCompletions = /* @__PURE__ */ new Map();
789
+ var HOOK_PLUGIN = "openclaw-plugin";
790
+ var HOOK_BEFORE_TOOL = "openclaw-before-tool";
791
+ var HOOK_AFTER_TOOL = "openclaw-after-tool";
792
+ var HOOK_SESSION_START = "openclaw-session-start";
793
+ var HOOK_SESSION_END = "openclaw-session-end";
794
+ var HOOK_LLM_INPUT = "openclaw-llm-input";
795
+ var HOOK_LLM_OUTPUT = "openclaw-llm-output";
796
+ var HOOK_AGENT_END = "openclaw-agent-end";
797
+ var HOOK_BEFORE_COMPACTION = "openclaw-before-compaction";
798
+ var HOOK_SUBAGENT_SPAWNED = "openclaw-subagent-spawned";
799
+ var HOOK_SUBAGENT_ENDED = "openclaw-subagent-ended";
800
+ var HOOK_COMMAND = "openclaw-command";
801
+ var HOOK_MESSAGE = "openclaw-message";
591
802
  function resolveConversationId() {
592
803
  return gatewaySessionId;
593
804
  }
@@ -686,10 +897,10 @@ function extractResultPreview(toolName, params, result, maxLen = 300) {
686
897
  if (str.length <= maxLen) return str;
687
898
  return str.slice(0, maxLen) + "...";
688
899
  }
689
- function handleFailBehavior(config, error, toolName, logger) {
900
+ function handleFailBehavior(config, error, toolName, logger, hookName = HOOK_PLUGIN) {
690
901
  logger.warn(`Agent Approve API error for tool "${toolName}": ${error.message}`);
691
902
  if (config.debug) {
692
- debugLog(`API error: ${error.message}, failBehavior: ${config.failBehavior}`);
903
+ debugLog(`API error: ${error.message}, failBehavior: ${config.failBehavior}`, hookName);
693
904
  }
694
905
  switch (config.failBehavior) {
695
906
  case "deny":
@@ -712,12 +923,24 @@ function register(api) {
712
923
  }
713
924
  api.logger.info(`Agent Approve: Plugin loaded (privacy: ${config.privacyTier}, fail: ${config.failBehavior})`);
714
925
  if (config.debug) {
715
- debugLog(`Plugin loaded, API: ${config.apiUrl}, agent: ${config.agentName}`);
926
+ const e2eStatus = !config.e2eEnabled ? "disabled" : !config.e2eUserKey ? "enabled (key missing)" : "enabled";
927
+ debugLog(
928
+ `Plugin loaded: v${config.hookVersion}, api=${config.apiUrl}, privacy=${config.privacyTier}, e2e=${e2eStatus}, debug=${config.debug}`,
929
+ HOOK_PLUGIN
930
+ );
931
+ debugLog(`Full config: agent=${config.agentName}, timeout=${config.timeout}s, fail=${config.failBehavior}`, HOOK_PLUGIN);
716
932
  }
717
933
  api.on("before_tool_call", async (event, ctx) => {
934
+ if (config.debug) {
935
+ debugLogRaw({ event, ctx }, HOOK_BEFORE_TOOL);
936
+ debugLog("Started before_tool_call hook", HOOK_BEFORE_TOOL);
937
+ }
718
938
  const conversationId = resolveConversationId();
719
939
  const { toolType, displayName } = classifyTool(event.toolName, event.params);
720
940
  const command = extractCommand(event.toolName, event.params);
941
+ if (config.debug) {
942
+ debugLog(`Tool: ${displayName} (${toolType}) [agent: ${config.agentName}]`, HOOK_BEFORE_TOOL);
943
+ }
721
944
  const request = {
722
945
  toolName: displayName,
723
946
  toolType,
@@ -731,15 +954,18 @@ function register(api) {
731
954
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
732
955
  };
733
956
  try {
734
- const response = await sendApprovalRequest(request, config, pluginFilePath);
957
+ const response = await sendApprovalRequest(request, config, pluginFilePath, HOOK_BEFORE_TOOL);
958
+ if (config.debug) {
959
+ debugLog(`Decision: ${response.decision}, Reason: ${response.reason || ""}`, HOOK_BEFORE_TOOL);
960
+ }
735
961
  if (response.decision === "approve" || response.decision === "allow") {
736
962
  if (config.debug) {
737
- debugLog(`Tool "${event.toolName}" approved${response.reason ? ": " + response.reason : ""}`);
963
+ debugLog("Tool approved", HOOK_BEFORE_TOOL);
738
964
  }
739
965
  return void 0;
740
966
  }
741
967
  if (config.debug) {
742
- debugLog(`Tool "${event.toolName}" denied${response.reason ? ": " + response.reason : ""}`);
968
+ debugLog("Tool denied", HOOK_BEFORE_TOOL);
743
969
  }
744
970
  return {
745
971
  block: true,
@@ -750,15 +976,19 @@ function register(api) {
750
976
  config,
751
977
  error instanceof Error ? error : new Error(String(error)),
752
978
  event.toolName,
753
- api.logger
979
+ api.logger,
980
+ HOOK_BEFORE_TOOL
754
981
  );
755
982
  }
756
983
  });
757
984
  api.on("after_tool_call", async (event, ctx) => {
985
+ if (config.debug) {
986
+ debugLogRaw({ event, ctx }, HOOK_AFTER_TOOL);
987
+ }
758
988
  const conversationId = resolveConversationId();
759
989
  if (isDuplicateCompletion(event.toolName, event.params)) {
760
990
  if (config.debug) {
761
- debugLog(`Skipping duplicate tool_complete for "${event.toolName}"`);
991
+ debugLog(`Skipping duplicate tool_complete for "${event.toolName}"`, HOOK_AFTER_TOOL);
762
992
  }
763
993
  return;
764
994
  }
@@ -777,13 +1007,14 @@ function register(api) {
777
1007
  response: event.error || resultPreview || void 0,
778
1008
  durationMs: event.durationMs,
779
1009
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
780
- }, config, pluginFilePath);
1010
+ }, config, pluginFilePath, HOOK_AFTER_TOOL);
781
1011
  });
782
1012
  api.on("session_start", async (event, ctx) => {
783
- const conversationId = resolveConversationId();
784
1013
  if (config.debug) {
785
- debugLog(`Session started: ${event.sessionId}${event.resumedFrom ? ` (resumed from ${event.resumedFrom})` : ""}`);
1014
+ debugLogRaw({ event, ctx }, HOOK_SESSION_START);
1015
+ debugLog(`Session started: ${event.sessionId}${event.resumedFrom ? ` (resumed from ${event.resumedFrom})` : ""}`, HOOK_SESSION_START);
786
1016
  }
1017
+ const conversationId = resolveConversationId();
787
1018
  void sendEvent({
788
1019
  eventType: "session_start",
789
1020
  agent: config.agentName,
@@ -791,13 +1022,14 @@ function register(api) {
791
1022
  sessionId: conversationId,
792
1023
  conversationId,
793
1024
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
794
- }, config, pluginFilePath);
1025
+ }, config, pluginFilePath, HOOK_SESSION_START);
795
1026
  });
796
1027
  api.on("session_end", async (event, ctx) => {
797
- const conversationId = resolveConversationId();
798
1028
  if (config.debug) {
799
- debugLog(`Session ended: ${event.sessionId} (${event.messageCount} messages, ${event.durationMs ?? "?"}ms)`);
1029
+ debugLogRaw({ event, ctx }, HOOK_SESSION_END);
1030
+ debugLog(`Session ended: ${event.sessionId} (${event.messageCount} messages, ${event.durationMs ?? "?"}ms)`, HOOK_SESSION_END);
800
1031
  }
1032
+ const conversationId = resolveConversationId();
801
1033
  void sendEvent({
802
1034
  eventType: "session_end",
803
1035
  agent: config.agentName,
@@ -807,13 +1039,14 @@ function register(api) {
807
1039
  durationMs: event.durationMs,
808
1040
  sessionStats: { messageCount: event.messageCount },
809
1041
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
810
- }, config, pluginFilePath);
1042
+ }, config, pluginFilePath, HOOK_SESSION_END);
811
1043
  });
812
1044
  api.on("llm_input", async (event, ctx) => {
813
- const conversationId = resolveConversationId();
814
1045
  if (config.debug) {
815
- debugLog(`LLM input: model=${event.model}, prompt length=${event.prompt?.length ?? 0}`);
1046
+ debugLogRaw({ event, ctx }, HOOK_LLM_INPUT);
1047
+ debugLog(`LLM input: model=${event.model}, prompt length=${event.prompt?.length ?? 0}`, HOOK_LLM_INPUT);
816
1048
  }
1049
+ const conversationId = resolveConversationId();
817
1050
  void sendEvent({
818
1051
  eventType: "user_prompt",
819
1052
  agent: config.agentName,
@@ -824,14 +1057,15 @@ function register(api) {
824
1057
  prompt: event.prompt,
825
1058
  textLength: event.prompt?.length ?? 0,
826
1059
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
827
- }, config, pluginFilePath);
1060
+ }, config, pluginFilePath, HOOK_LLM_INPUT);
828
1061
  });
829
1062
  api.on("llm_output", async (event, ctx) => {
830
1063
  const conversationId = resolveConversationId();
831
1064
  const responseText = event.assistantTexts?.join("\n") || "";
832
1065
  const textLength = responseText.length;
833
1066
  if (config.debug) {
834
- debugLog(`LLM output: model=${event.model}, length=${textLength}${event.usage?.total ? `, tokens=${event.usage.total}` : ""}`);
1067
+ debugLogRaw({ event, ctx }, HOOK_LLM_OUTPUT);
1068
+ debugLog(`LLM output: model=${event.model}, length=${textLength}${event.usage?.total ? `, tokens=${event.usage.total}` : ""}`, HOOK_LLM_OUTPUT);
835
1069
  }
836
1070
  void sendEvent({
837
1071
  eventType: "response",
@@ -844,13 +1078,14 @@ function register(api) {
844
1078
  textPreview: responseText.slice(0, 200),
845
1079
  textLength,
846
1080
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
847
- }, config, pluginFilePath);
1081
+ }, config, pluginFilePath, HOOK_LLM_OUTPUT);
848
1082
  });
849
1083
  api.on("agent_end", async (event, ctx) => {
850
- const conversationId = resolveConversationId();
851
1084
  if (config.debug) {
852
- debugLog(`Agent ended: success=${event.success}${event.durationMs ? `, duration=${event.durationMs}ms` : ""}${event.error ? `, error=${event.error}` : ""}`);
1085
+ debugLogRaw({ event, ctx }, HOOK_AGENT_END);
1086
+ debugLog(`Agent ended: success=${event.success}${event.durationMs ? `, duration=${event.durationMs}ms` : ""}${event.error ? `, error=${event.error}` : ""}`, HOOK_AGENT_END);
853
1087
  }
1088
+ const conversationId = resolveConversationId();
854
1089
  void sendEvent({
855
1090
  eventType: "stop",
856
1091
  agent: config.agentName,
@@ -861,13 +1096,14 @@ function register(api) {
861
1096
  durationMs: event.durationMs,
862
1097
  response: event.error || void 0,
863
1098
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
864
- }, config, pluginFilePath);
1099
+ }, config, pluginFilePath, HOOK_AGENT_END);
865
1100
  });
866
1101
  api.on("before_compaction", async (event, ctx) => {
867
- const conversationId = resolveConversationId();
868
1102
  if (config.debug) {
869
- debugLog(`Context compaction: ${event.messageCount} messages${event.tokenCount ? `, ${event.tokenCount} tokens` : ""}`);
1103
+ debugLogRaw({ event, ctx }, HOOK_BEFORE_COMPACTION);
1104
+ debugLog(`Context compaction: ${event.messageCount} messages${event.tokenCount ? `, ${event.tokenCount} tokens` : ""}`, HOOK_BEFORE_COMPACTION);
870
1105
  }
1106
+ const conversationId = resolveConversationId();
871
1107
  void sendEvent({
872
1108
  eventType: "context_compact",
873
1109
  agent: config.agentName,
@@ -876,13 +1112,14 @@ function register(api) {
876
1112
  conversationId,
877
1113
  trigger: `${event.messageCount} messages${event.tokenCount ? `, ${event.tokenCount} tokens` : ""}`,
878
1114
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
879
- }, config, pluginFilePath);
1115
+ }, config, pluginFilePath, HOOK_BEFORE_COMPACTION);
880
1116
  });
881
1117
  api.on("subagent_spawned", async (event, ctx) => {
882
- const conversationId = resolveConversationId();
883
1118
  if (config.debug) {
884
- debugLog(`Subagent spawned: ${event.agentId} (${event.mode}${event.label ? `, label=${event.label}` : ""})`);
1119
+ debugLogRaw({ event, ctx }, HOOK_SUBAGENT_SPAWNED);
1120
+ debugLog(`Subagent spawned: ${event.agentId} (${event.mode}${event.label ? `, label=${event.label}` : ""})`, HOOK_SUBAGENT_SPAWNED);
885
1121
  }
1122
+ const conversationId = resolveConversationId();
886
1123
  void sendEvent({
887
1124
  eventType: "subagent_start",
888
1125
  agent: config.agentName,
@@ -893,13 +1130,14 @@ function register(api) {
893
1130
  subagentMode: event.mode,
894
1131
  subagentLabel: event.label,
895
1132
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
896
- }, config, pluginFilePath);
1133
+ }, config, pluginFilePath, HOOK_SUBAGENT_SPAWNED);
897
1134
  });
898
1135
  api.on("subagent_ended", async (event, ctx) => {
899
- const conversationId = resolveConversationId();
900
1136
  if (config.debug) {
901
- debugLog(`Subagent ended: ${event.targetKind} reason=${event.reason}${event.outcome ? `, outcome=${event.outcome}` : ""}`);
1137
+ debugLogRaw({ event, ctx }, HOOK_SUBAGENT_ENDED);
1138
+ debugLog(`Subagent ended: ${event.targetKind} reason=${event.reason}${event.outcome ? `, outcome=${event.outcome}` : ""}`, HOOK_SUBAGENT_ENDED);
902
1139
  }
1140
+ const conversationId = resolveConversationId();
903
1141
  void sendEvent({
904
1142
  eventType: "subagent_stop",
905
1143
  agent: config.agentName,
@@ -910,7 +1148,7 @@ function register(api) {
910
1148
  subagentType: event.targetKind,
911
1149
  response: event.error || void 0,
912
1150
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
913
- }, config, pluginFilePath);
1151
+ }, config, pluginFilePath, HOOK_SUBAGENT_ENDED);
914
1152
  });
915
1153
  api.registerHook(
916
1154
  ["command:new", "command:stop", "command:reset"],
@@ -921,7 +1159,8 @@ function register(api) {
921
1159
  reset: "session_start"
922
1160
  };
923
1161
  if (config.debug) {
924
- debugLog(`Command event: ${event.action}`);
1162
+ debugLogRaw(event, HOOK_COMMAND);
1163
+ debugLog(`Command event: ${event.action}`, HOOK_COMMAND);
925
1164
  }
926
1165
  void sendEvent({
927
1166
  toolName: `command:${event.action}`,
@@ -932,7 +1171,7 @@ function register(api) {
932
1171
  sessionId: resolveConversationId(),
933
1172
  conversationId: resolveConversationId(),
934
1173
  timestamp: event.timestamp.toISOString()
935
- }, config, pluginFilePath);
1174
+ }, config, pluginFilePath, HOOK_COMMAND);
936
1175
  },
937
1176
  { name: "agentapprove-command-monitor", description: "Log command events to Agent Approve" }
938
1177
  );
@@ -944,7 +1183,8 @@ function register(api) {
944
1183
  const peer = isInbound ? event.context.from : event.context.to;
945
1184
  const channelLabel = [provider, peer].filter(Boolean).join(":") || void 0;
946
1185
  if (config.debug) {
947
- debugLog(`Message event: ${event.action} ${channelLabel || "(no channel)"}`);
1186
+ debugLogRaw(event, HOOK_MESSAGE);
1187
+ debugLog(`Message event: ${event.action} ${channelLabel || "(no channel)"}`, HOOK_MESSAGE);
948
1188
  }
949
1189
  const payload = {
950
1190
  toolName: `message:${event.action}`,
@@ -966,7 +1206,7 @@ function register(api) {
966
1206
  payload.prompt = `${peer}: ${content}`;
967
1207
  }
968
1208
  }
969
- void sendEvent(payload, config, pluginFilePath);
1209
+ void sendEvent(payload, config, pluginFilePath, HOOK_MESSAGE);
970
1210
  },
971
1211
  { name: "agentapprove-session-monitor", description: "Log message events to Agent Approve" }
972
1212
  );
@@ -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.7",
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.7",
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": {