@agentapprove/openclaw 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -10,6 +10,13 @@
10
10
 
11
11
  You decide the rules. Agent Approve enforces them.
12
12
 
13
+ ## Important
14
+
15
+ - A valid Agent Approve subscription is required to use this plugin.
16
+ - You must download the iOS app, run `npx agentapprove`, and pair your device.
17
+ - This plugin is currently pre-launch software. Public App Store availability is not open yet.
18
+ - If a valid token is not configured, tool calls are blocked with setup guidance.
19
+
13
20
  ## How it works
14
21
 
15
22
  This plugin hooks into OpenClaw's agent loop to enforce your approval policy:
@@ -67,6 +74,11 @@ Plugin settings in `~/.openclaw/openclaw.json`:
67
74
  | `privacyTier` | `full` | What tool data is stored in event logs: `minimal`, `summary`, or `full` |
68
75
  | `debug` | `false` | Write debug logs to `~/.agentapprove/hook-debug.log` |
69
76
 
77
+ ## Requirements
78
+
79
+ - OpenClaw runtime
80
+ - Agent Approve iOS app and active subscription ($9.99/month or $99/year, 7-day free trial)
81
+
70
82
  ## Supported agents
71
83
 
72
84
  Agent Approve works with OpenClaw, Claude Code, Cursor, Gemini CLI, VS Code Agent, Copilot CLI, OpenAI Codex (coming soon), and more. Run `npx agentapprove` to configure multiple agents at once.
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}
53
+ `);
54
+ } catch {
55
+ }
56
+ }
57
+ function debugLogRawInline(data) {
58
+ try {
59
+ ensureLogFile();
60
+ appendFileSync(DEBUG_LOG_PATH, `${data}
50
61
  `);
51
62
  } catch {
52
63
  }
53
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) {
@@ -284,7 +339,7 @@ function loadConfig(openclawConfig, logger) {
284
339
  }
285
340
  let token = process.env.AGENTAPPROVE_TOKEN || getKeychainToken() || parseConfigValue(fileContent, "AGENTAPPROVE_TOKEN") || "";
286
341
  if (!token) {
287
- logger?.warn("No Agent Approve token found. Run the Agent Approve installer to set up.");
342
+ logger?.warn("Missing token. Download the iOS app, run npx agentapprove, and pair your device. Subscription required.");
288
343
  }
289
344
  const rawApiUrl = openclawConfig?.apiUrl || process.env.AGENTAPPROVE_API || parseConfigValue(fileContent, "AGENTAPPROVE_API") || "https://api.agentapprove.com";
290
345
  const rawApiVersion = process.env.AGENTAPPROVE_API_VERSION || parseConfigValue(fileContent, "AGENTAPPROVE_API_VERSION") || "v001";
@@ -331,7 +386,8 @@ function loadConfig(openclawConfig, logger) {
331
386
  failBehavior,
332
387
  privacyTier,
333
388
  debug,
334
- hookVersion: "1.1.3",
389
+ hookVersion: "1.1.4",
390
+ agentType: "openclaw",
335
391
  agentName,
336
392
  e2eEnabled,
337
393
  e2eUserKey,
@@ -418,6 +474,7 @@ var CONTENT_FIELDS = [
418
474
  "command",
419
475
  "toolInput",
420
476
  "response",
477
+ "responsePreview",
421
478
  "text",
422
479
  "textPreview",
423
480
  "prompt",
@@ -585,11 +642,11 @@ function getLocalConfigSetAt() {
585
642
 
586
643
  // src/api-client.ts
587
644
  var cachedPluginHash;
588
- function getPluginHash(pluginPath, debug = false) {
645
+ function getPluginHash(pluginPath, debug = false, hookName = "openclaw-plugin") {
589
646
  if (!cachedPluginHash) {
590
647
  cachedPluginHash = computePluginHash(pluginPath);
591
648
  if (cachedPluginHash && debug) {
592
- debugLog(`Plugin hash computed: ${cachedPluginHash.slice(0, 16)}...`);
649
+ debugLog(`Plugin hash computed: ${cachedPluginHash.slice(0, 16)}...`, hookName);
593
650
  }
594
651
  }
595
652
  return cachedPluginHash || "";
@@ -631,21 +688,21 @@ function httpPost(url, body, headers, timeoutMs) {
631
688
  req.end();
632
689
  });
633
690
  }
634
- async function sendApprovalRequest(request, config, pluginPath) {
691
+ async function sendApprovalRequest(request, config, pluginPath, hookName = "openclaw-plugin") {
635
692
  if (!config.token) {
636
- throw new Error("No Agent Approve token configured");
693
+ throw new Error("Missing token. Download the iOS app, run npx agentapprove, and pair your device. Subscription required.");
637
694
  }
638
695
  let payload;
639
696
  if (config.e2eEnabled && config.e2eUserKey) {
640
697
  payload = applyApprovalE2E(request, config.e2eUserKey, config.e2eServerKey);
641
698
  if (config.debug) {
642
- debugLog("E2E encryption applied to approval request");
699
+ debugLog("E2E encryption applied to approval request", hookName);
643
700
  }
644
701
  } else {
645
702
  payload = applyPrivacyFilter(request, config.privacyTier);
646
703
  }
647
704
  const bodyStr = JSON.stringify(payload);
648
- const pluginHash = getPluginHash(pluginPath, config.debug);
705
+ const pluginHash = getPluginHash(pluginPath, config.debug, hookName);
649
706
  const hmacHeaders = buildHMACHeaders(bodyStr, config.token, config.hookVersion, pluginHash);
650
707
  const headers = {
651
708
  "Authorization": `Bearer ${config.token}`,
@@ -653,11 +710,14 @@ async function sendApprovalRequest(request, config, pluginPath) {
653
710
  };
654
711
  const url = `${config.apiUrl}/${config.apiVersion}/approve`;
655
712
  if (config.debug) {
656
- debugLog(`Sending approval request to ${url} for tool: ${request.toolName}`);
713
+ debugLog(`Requesting approval from ${url}`, hookName);
714
+ debugLog(`=== SENT TO ${url} ===`, hookName);
715
+ debugLogRawInline(bodyStr);
716
+ debugLog("=== END SENT ===", hookName);
657
717
  }
658
718
  const response = await httpPost(url, bodyStr, headers, config.timeout * 1e3);
659
719
  if (config.debug) {
660
- debugLog(`Response status: ${response.status}, body: ${response.body.slice(0, 200)}`);
720
+ debugLog(`Response: ${response.body || "<empty>"}`, hookName);
661
721
  }
662
722
  if (response.status !== 200) {
663
723
  throw new Error(`API returned status ${response.status}: ${response.body.slice(0, 200)}`);
@@ -671,23 +731,23 @@ async function sendApprovalRequest(request, config, pluginPath) {
671
731
  processConfigSync(parsed, config);
672
732
  return parsed;
673
733
  }
674
- async function sendEvent(event, config, pluginPath) {
734
+ async function sendEvent(event, config, pluginPath, hookName = "openclaw-plugin") {
675
735
  if (!config.token) return;
676
736
  const eventType = event.eventType;
677
737
  const toolName = event.toolName;
678
738
  if (config.debug) {
679
- debugLog(`Sending ${eventType || "event"}${toolName ? ` (${toolName})` : ""} (privacy: ${config.privacyTier})`);
739
+ debugLog(`Sending ${eventType || "event"}${toolName ? ` (${toolName})` : ""} (privacy: ${config.privacyTier})`, hookName);
680
740
  }
681
741
  try {
682
742
  let payload = applyEventPrivacyFilter(event, config.privacyTier);
683
743
  if (config.e2eEnabled && config.e2eUserKey) {
684
744
  payload = applyEventE2E(payload, config.e2eUserKey);
685
745
  if (config.debug) {
686
- debugLog(`E2E applied to event (type=${eventType})`);
746
+ debugLog(`E2E applied to event (type=${eventType})`, hookName);
687
747
  }
688
748
  }
689
749
  const bodyStr = JSON.stringify(payload);
690
- const pluginHash = getPluginHash(pluginPath, config.debug);
750
+ const pluginHash = getPluginHash(pluginPath, config.debug, hookName);
691
751
  const hmacHeaders = buildHMACHeaders(bodyStr, config.token, config.hookVersion, pluginHash);
692
752
  const headers = {
693
753
  "Authorization": `Bearer ${config.token}`,
@@ -695,13 +755,13 @@ async function sendEvent(event, config, pluginPath) {
695
755
  };
696
756
  const url = `${config.apiUrl}/${config.apiVersion}/events`;
697
757
  if (config.debug) {
698
- debugLog(`=== SENT TO ${url} ===`);
699
- debugLog(bodyStr.slice(0, 500));
700
- debugLog("=== END SENT ===");
758
+ debugLog(`=== SENT TO ${url} ===`, hookName);
759
+ debugLogRawInline(bodyStr);
760
+ debugLog("=== END SENT ===", hookName);
701
761
  }
702
762
  const response = await httpPost(url, bodyStr, headers, 5e3);
703
763
  if (config.debug) {
704
- debugLog(`send_event response: ${response.body.slice(0, 200)}`);
764
+ debugLog(`send_event response: ${response.body || "<empty>"}`, hookName);
705
765
  }
706
766
  if (response.status === 200) {
707
767
  try {
@@ -712,7 +772,7 @@ async function sendEvent(event, config, pluginPath) {
712
772
  }
713
773
  } catch (err) {
714
774
  if (config.debug) {
715
- debugLog(`Failed to send ${eventType || "event"}: ${err instanceof Error ? err.message : String(err)}`);
775
+ debugLog(`Failed to send ${eventType || "event"}: ${err instanceof Error ? err.message : String(err)}`, hookName);
716
776
  }
717
777
  }
718
778
  }
@@ -728,6 +788,19 @@ var gatewaySessionId = randomBytes2(12).toString("hex");
728
788
  var DEDUP_WINDOW_MS = 1200;
729
789
  var DEDUP_MAX_SIZE = 300;
730
790
  var recentCompletions = /* @__PURE__ */ new Map();
791
+ var HOOK_PLUGIN = "openclaw-plugin";
792
+ var HOOK_BEFORE_TOOL = "openclaw-before-tool";
793
+ var HOOK_AFTER_TOOL = "openclaw-after-tool";
794
+ var HOOK_SESSION_START = "openclaw-session-start";
795
+ var HOOK_SESSION_END = "openclaw-session-end";
796
+ var HOOK_LLM_INPUT = "openclaw-llm-input";
797
+ var HOOK_LLM_OUTPUT = "openclaw-llm-output";
798
+ var HOOK_AGENT_END = "openclaw-agent-end";
799
+ var HOOK_BEFORE_COMPACTION = "openclaw-before-compaction";
800
+ var HOOK_SUBAGENT_SPAWNED = "openclaw-subagent-spawned";
801
+ var HOOK_SUBAGENT_ENDED = "openclaw-subagent-ended";
802
+ var HOOK_COMMAND = "openclaw-command";
803
+ var HOOK_MESSAGE = "openclaw-message";
731
804
  function resolveConversationId() {
732
805
  return gatewaySessionId;
733
806
  }
@@ -826,10 +899,10 @@ function extractResultPreview(toolName, params, result, maxLen = 300) {
826
899
  if (str.length <= maxLen) return str;
827
900
  return str.slice(0, maxLen) + "...";
828
901
  }
829
- function handleFailBehavior(config, error, toolName, logger) {
902
+ function handleFailBehavior(config, error, toolName, logger, hookName = HOOK_PLUGIN) {
830
903
  logger.warn(`Agent Approve API error for tool "${toolName}": ${error.message}`);
831
904
  if (config.debug) {
832
- debugLog(`API error: ${error.message}, failBehavior: ${config.failBehavior}`);
905
+ debugLog(`API error: ${error.message}, failBehavior: ${config.failBehavior}`, hookName);
833
906
  }
834
907
  switch (config.failBehavior) {
835
908
  case "deny":
@@ -844,46 +917,65 @@ function handleFailBehavior(config, error, toolName, logger) {
844
917
  }
845
918
  function register(api) {
846
919
  const config = loadConfig(api.pluginConfig, api.logger);
847
- if (!config.token) {
848
- api.logger.warn(
849
- "Agent Approve: No token found. Run the Agent Approve installer to pair with your account."
850
- );
851
- return;
920
+ const missingTokenMessage = "Missing token. Download the iOS app, run npx agentapprove, and pair your device. Subscription required.";
921
+ const noTokenConfigured = !config.token;
922
+ if (noTokenConfigured) {
923
+ api.logger.warn(`Agent Approve: ${missingTokenMessage}`);
924
+ } else {
925
+ api.logger.info(`Agent Approve: Plugin loaded (privacy: ${config.privacyTier}, fail: ${config.failBehavior})`);
852
926
  }
853
- api.logger.info(`Agent Approve: Plugin loaded (privacy: ${config.privacyTier}, fail: ${config.failBehavior})`);
854
927
  if (config.debug) {
855
928
  const e2eStatus = !config.e2eEnabled ? "disabled" : !config.e2eUserKey ? "enabled (key missing)" : "enabled";
856
929
  debugLog(
857
- `Plugin loaded: v${config.hookVersion}, api=${config.apiUrl}, privacy=${config.privacyTier}, e2e=${e2eStatus}, debug=${config.debug}`
930
+ `Plugin loaded: v${config.hookVersion}, api=${config.apiUrl}, privacy=${config.privacyTier}, e2e=${e2eStatus}, debug=${config.debug}`,
931
+ HOOK_PLUGIN
858
932
  );
859
- debugLog(`Full config: agent=${config.agentName}, timeout=${config.timeout}s, fail=${config.failBehavior}`);
933
+ debugLog(`Full config: agent=${config.agentName}, timeout=${config.timeout}s, fail=${config.failBehavior}`, HOOK_PLUGIN);
860
934
  }
861
935
  api.on("before_tool_call", async (event, ctx) => {
936
+ if (config.debug) {
937
+ debugLogRaw({ event, ctx }, HOOK_BEFORE_TOOL);
938
+ debugLog("Started before_tool_call hook", HOOK_BEFORE_TOOL);
939
+ }
862
940
  const conversationId = resolveConversationId();
863
941
  const { toolType, displayName } = classifyTool(event.toolName, event.params);
864
942
  const command = extractCommand(event.toolName, event.params);
943
+ if (config.debug) {
944
+ debugLog(`Tool: ${displayName} (${toolType}) [agent: ${config.agentName}]`, HOOK_BEFORE_TOOL);
945
+ }
946
+ if (noTokenConfigured) {
947
+ return {
948
+ block: true,
949
+ blockReason: missingTokenMessage
950
+ };
951
+ }
865
952
  const request = {
866
953
  toolName: displayName,
867
954
  toolType,
868
955
  command,
869
956
  toolInput: event.params,
870
- agent: config.agentName,
957
+ agent: config.agentType,
958
+ agentName: config.agentName,
871
959
  hookType: "before_tool_call",
872
960
  sessionId: conversationId,
873
961
  conversationId,
874
962
  cwd: event.params.workdir || void 0,
963
+ projectPath: process.cwd(),
875
964
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
876
965
  };
877
966
  try {
878
- const response = await sendApprovalRequest(request, config, pluginFilePath);
967
+ const response = await sendApprovalRequest(request, config, pluginFilePath, HOOK_BEFORE_TOOL);
968
+ if (config.debug) {
969
+ debugLog(`Decision: ${response.decision}, Reason: ${response.reason || ""}`, HOOK_BEFORE_TOOL);
970
+ }
879
971
  if (response.decision === "approve" || response.decision === "allow") {
880
972
  if (config.debug) {
881
- debugLog(`Tool "${event.toolName}" approved${response.reason ? ": " + response.reason : ""}`);
973
+ debugLog("Tool approved", HOOK_BEFORE_TOOL);
882
974
  }
883
975
  return void 0;
884
976
  }
885
977
  if (config.debug) {
886
- debugLog(`Tool "${event.toolName}" denied${response.reason ? ": " + response.reason : ""}`);
978
+ debugLog("Tool denied", HOOK_BEFORE_TOOL);
887
979
  }
888
980
  return {
889
981
  block: true,
@@ -894,73 +986,89 @@ function register(api) {
894
986
  config,
895
987
  error instanceof Error ? error : new Error(String(error)),
896
988
  event.toolName,
897
- api.logger
989
+ api.logger,
990
+ HOOK_BEFORE_TOOL
898
991
  );
899
992
  }
900
993
  });
901
994
  api.on("after_tool_call", async (event, ctx) => {
995
+ if (config.debug) {
996
+ debugLogRaw({ event, ctx }, HOOK_AFTER_TOOL);
997
+ }
902
998
  const conversationId = resolveConversationId();
903
999
  if (isDuplicateCompletion(event.toolName, event.params)) {
904
1000
  if (config.debug) {
905
- debugLog(`Skipping duplicate tool_complete for "${event.toolName}"`);
1001
+ debugLog(`Skipping duplicate tool_complete for "${event.toolName}"`, HOOK_AFTER_TOOL);
906
1002
  }
907
1003
  return;
908
1004
  }
909
1005
  const { toolType } = classifyTool(event.toolName, event.params);
910
1006
  const resultPreview = extractResultPreview(event.toolName, event.params, event.result);
1007
+ const resultStr = event.result != null ? typeof event.result === "string" ? event.result : JSON.stringify(event.result) : void 0;
1008
+ const longPreview = resultStr && resultStr.length > 1e3 ? resultStr.slice(0, 1e3) + "..." : resultStr;
911
1009
  void sendEvent({
912
1010
  toolName: event.toolName,
913
1011
  toolType,
914
1012
  eventType: "tool_complete",
915
- agent: config.agentName,
1013
+ agent: config.agentType,
1014
+ agentName: config.agentName,
916
1015
  hookType: "after_tool_call",
917
1016
  sessionId: conversationId,
918
1017
  conversationId,
919
1018
  command: extractCommand(event.toolName, event.params),
1019
+ toolInput: event.params,
920
1020
  status: event.error ? "error" : "success",
921
1021
  response: event.error || resultPreview || void 0,
1022
+ responsePreview: longPreview,
922
1023
  durationMs: event.durationMs,
1024
+ cwd: event.params?.workdir || void 0,
923
1025
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
924
- }, config, pluginFilePath);
1026
+ }, config, pluginFilePath, HOOK_AFTER_TOOL);
925
1027
  });
926
1028
  api.on("session_start", async (event, ctx) => {
927
- const conversationId = resolveConversationId();
928
1029
  if (config.debug) {
929
- debugLog(`Session started: ${event.sessionId}${event.resumedFrom ? ` (resumed from ${event.resumedFrom})` : ""}`);
1030
+ debugLogRaw({ event, ctx }, HOOK_SESSION_START);
1031
+ debugLog(`Session started: ${event.sessionId}${event.resumedFrom ? ` (resumed from ${event.resumedFrom})` : ""}`, HOOK_SESSION_START);
930
1032
  }
1033
+ const conversationId = resolveConversationId();
931
1034
  void sendEvent({
932
1035
  eventType: "session_start",
933
- agent: config.agentName,
1036
+ agent: config.agentType,
1037
+ agentName: config.agentName,
934
1038
  hookType: "session_start",
935
1039
  sessionId: conversationId,
936
1040
  conversationId,
937
1041
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
938
- }, config, pluginFilePath);
1042
+ }, config, pluginFilePath, HOOK_SESSION_START);
939
1043
  });
940
1044
  api.on("session_end", async (event, ctx) => {
941
- const conversationId = resolveConversationId();
942
1045
  if (config.debug) {
943
- debugLog(`Session ended: ${event.sessionId} (${event.messageCount} messages, ${event.durationMs ?? "?"}ms)`);
1046
+ debugLogRaw({ event, ctx }, HOOK_SESSION_END);
1047
+ debugLog(`Session ended: ${event.sessionId} (${event.messageCount} messages, ${event.durationMs ?? "?"}ms)`, HOOK_SESSION_END);
944
1048
  }
1049
+ const conversationId = resolveConversationId();
945
1050
  void sendEvent({
946
1051
  eventType: "session_end",
947
- agent: config.agentName,
1052
+ agent: config.agentType,
1053
+ agentName: config.agentName,
948
1054
  hookType: "session_end",
949
1055
  sessionId: conversationId,
950
1056
  conversationId,
951
1057
  durationMs: event.durationMs,
952
1058
  sessionStats: { messageCount: event.messageCount },
953
1059
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
954
- }, config, pluginFilePath);
1060
+ }, config, pluginFilePath, HOOK_SESSION_END);
955
1061
  });
956
1062
  api.on("llm_input", async (event, ctx) => {
957
- const conversationId = resolveConversationId();
958
1063
  if (config.debug) {
959
- debugLog(`LLM input: model=${event.model}, prompt length=${event.prompt?.length ?? 0}`);
1064
+ debugLogRaw({ event, ctx }, HOOK_LLM_INPUT);
1065
+ debugLog(`LLM input: model=${event.model}, prompt length=${event.prompt?.length ?? 0}`, HOOK_LLM_INPUT);
960
1066
  }
1067
+ const conversationId = resolveConversationId();
961
1068
  void sendEvent({
962
1069
  eventType: "user_prompt",
963
- agent: config.agentName,
1070
+ agent: config.agentType,
1071
+ agentName: config.agentName,
964
1072
  hookType: "llm_input",
965
1073
  sessionId: conversationId,
966
1074
  conversationId,
@@ -968,18 +1076,20 @@ function register(api) {
968
1076
  prompt: event.prompt,
969
1077
  textLength: event.prompt?.length ?? 0,
970
1078
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
971
- }, config, pluginFilePath);
1079
+ }, config, pluginFilePath, HOOK_LLM_INPUT);
972
1080
  });
973
1081
  api.on("llm_output", async (event, ctx) => {
974
1082
  const conversationId = resolveConversationId();
975
1083
  const responseText = event.assistantTexts?.join("\n") || "";
976
1084
  const textLength = responseText.length;
977
1085
  if (config.debug) {
978
- debugLog(`LLM output: model=${event.model}, length=${textLength}${event.usage?.total ? `, tokens=${event.usage.total}` : ""}`);
1086
+ debugLogRaw({ event, ctx }, HOOK_LLM_OUTPUT);
1087
+ debugLog(`LLM output: model=${event.model}, length=${textLength}${event.usage?.total ? `, tokens=${event.usage.total}` : ""}`, HOOK_LLM_OUTPUT);
979
1088
  }
980
1089
  void sendEvent({
981
1090
  eventType: "response",
982
- agent: config.agentName,
1091
+ agent: config.agentType,
1092
+ agentName: config.agentName,
983
1093
  hookType: "llm_output",
984
1094
  sessionId: conversationId,
985
1095
  conversationId,
@@ -988,16 +1098,18 @@ function register(api) {
988
1098
  textPreview: responseText.slice(0, 200),
989
1099
  textLength,
990
1100
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
991
- }, config, pluginFilePath);
1101
+ }, config, pluginFilePath, HOOK_LLM_OUTPUT);
992
1102
  });
993
1103
  api.on("agent_end", async (event, ctx) => {
994
- const conversationId = resolveConversationId();
995
1104
  if (config.debug) {
996
- debugLog(`Agent ended: success=${event.success}${event.durationMs ? `, duration=${event.durationMs}ms` : ""}${event.error ? `, error=${event.error}` : ""}`);
1105
+ debugLogRaw({ event, ctx }, HOOK_AGENT_END);
1106
+ debugLog(`Agent ended: success=${event.success}${event.durationMs ? `, duration=${event.durationMs}ms` : ""}${event.error ? `, error=${event.error}` : ""}`, HOOK_AGENT_END);
997
1107
  }
1108
+ const conversationId = resolveConversationId();
998
1109
  void sendEvent({
999
1110
  eventType: "stop",
1000
- agent: config.agentName,
1111
+ agent: config.agentType,
1112
+ agentName: config.agentName,
1001
1113
  hookType: "agent_end",
1002
1114
  sessionId: conversationId,
1003
1115
  conversationId,
@@ -1005,31 +1117,35 @@ function register(api) {
1005
1117
  durationMs: event.durationMs,
1006
1118
  response: event.error || void 0,
1007
1119
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1008
- }, config, pluginFilePath);
1120
+ }, config, pluginFilePath, HOOK_AGENT_END);
1009
1121
  });
1010
1122
  api.on("before_compaction", async (event, ctx) => {
1011
- const conversationId = resolveConversationId();
1012
1123
  if (config.debug) {
1013
- debugLog(`Context compaction: ${event.messageCount} messages${event.tokenCount ? `, ${event.tokenCount} tokens` : ""}`);
1124
+ debugLogRaw({ event, ctx }, HOOK_BEFORE_COMPACTION);
1125
+ debugLog(`Context compaction: ${event.messageCount} messages${event.tokenCount ? `, ${event.tokenCount} tokens` : ""}`, HOOK_BEFORE_COMPACTION);
1014
1126
  }
1127
+ const conversationId = resolveConversationId();
1015
1128
  void sendEvent({
1016
1129
  eventType: "context_compact",
1017
- agent: config.agentName,
1130
+ agent: config.agentType,
1131
+ agentName: config.agentName,
1018
1132
  hookType: "before_compaction",
1019
1133
  sessionId: conversationId,
1020
1134
  conversationId,
1021
1135
  trigger: `${event.messageCount} messages${event.tokenCount ? `, ${event.tokenCount} tokens` : ""}`,
1022
1136
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1023
- }, config, pluginFilePath);
1137
+ }, config, pluginFilePath, HOOK_BEFORE_COMPACTION);
1024
1138
  });
1025
1139
  api.on("subagent_spawned", async (event, ctx) => {
1026
- const conversationId = resolveConversationId();
1027
1140
  if (config.debug) {
1028
- debugLog(`Subagent spawned: ${event.agentId} (${event.mode}${event.label ? `, label=${event.label}` : ""})`);
1141
+ debugLogRaw({ event, ctx }, HOOK_SUBAGENT_SPAWNED);
1142
+ debugLog(`Subagent spawned: ${event.agentId} (${event.mode}${event.label ? `, label=${event.label}` : ""})`, HOOK_SUBAGENT_SPAWNED);
1029
1143
  }
1144
+ const conversationId = resolveConversationId();
1030
1145
  void sendEvent({
1031
1146
  eventType: "subagent_start",
1032
- agent: config.agentName,
1147
+ agent: config.agentType,
1148
+ agentName: config.agentName,
1033
1149
  hookType: "subagent_spawned",
1034
1150
  sessionId: conversationId,
1035
1151
  conversationId,
@@ -1037,16 +1153,18 @@ function register(api) {
1037
1153
  subagentMode: event.mode,
1038
1154
  subagentLabel: event.label,
1039
1155
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1040
- }, config, pluginFilePath);
1156
+ }, config, pluginFilePath, HOOK_SUBAGENT_SPAWNED);
1041
1157
  });
1042
1158
  api.on("subagent_ended", async (event, ctx) => {
1043
- const conversationId = resolveConversationId();
1044
1159
  if (config.debug) {
1045
- debugLog(`Subagent ended: ${event.targetKind} reason=${event.reason}${event.outcome ? `, outcome=${event.outcome}` : ""}`);
1160
+ debugLogRaw({ event, ctx }, HOOK_SUBAGENT_ENDED);
1161
+ debugLog(`Subagent ended: ${event.targetKind} reason=${event.reason}${event.outcome ? `, outcome=${event.outcome}` : ""}`, HOOK_SUBAGENT_ENDED);
1046
1162
  }
1163
+ const conversationId = resolveConversationId();
1047
1164
  void sendEvent({
1048
1165
  eventType: "subagent_stop",
1049
- agent: config.agentName,
1166
+ agent: config.agentType,
1167
+ agentName: config.agentName,
1050
1168
  hookType: "subagent_ended",
1051
1169
  sessionId: conversationId,
1052
1170
  conversationId,
@@ -1054,7 +1172,7 @@ function register(api) {
1054
1172
  subagentType: event.targetKind,
1055
1173
  response: event.error || void 0,
1056
1174
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1057
- }, config, pluginFilePath);
1175
+ }, config, pluginFilePath, HOOK_SUBAGENT_ENDED);
1058
1176
  });
1059
1177
  api.registerHook(
1060
1178
  ["command:new", "command:stop", "command:reset"],
@@ -1065,18 +1183,20 @@ function register(api) {
1065
1183
  reset: "session_start"
1066
1184
  };
1067
1185
  if (config.debug) {
1068
- debugLog(`Command event: ${event.action}`);
1186
+ debugLogRaw(event, HOOK_COMMAND);
1187
+ debugLog(`Command event: ${event.action}`, HOOK_COMMAND);
1069
1188
  }
1070
1189
  void sendEvent({
1071
1190
  toolName: `command:${event.action}`,
1072
1191
  toolType: "command",
1073
1192
  eventType: eventTypeMap[event.action] || "command_event",
1074
- agent: config.agentName,
1193
+ agent: config.agentType,
1194
+ agentName: config.agentName,
1075
1195
  hookType: "command_event",
1076
1196
  sessionId: resolveConversationId(),
1077
1197
  conversationId: resolveConversationId(),
1078
1198
  timestamp: event.timestamp.toISOString()
1079
- }, config, pluginFilePath);
1199
+ }, config, pluginFilePath, HOOK_COMMAND);
1080
1200
  },
1081
1201
  { name: "agentapprove-command-monitor", description: "Log command events to Agent Approve" }
1082
1202
  );
@@ -1088,13 +1208,15 @@ function register(api) {
1088
1208
  const peer = isInbound ? event.context.from : event.context.to;
1089
1209
  const channelLabel = [provider, peer].filter(Boolean).join(":") || void 0;
1090
1210
  if (config.debug) {
1091
- debugLog(`Message event: ${event.action} ${channelLabel || "(no channel)"}`);
1211
+ debugLogRaw(event, HOOK_MESSAGE);
1212
+ debugLog(`Message event: ${event.action} ${channelLabel || "(no channel)"}`, HOOK_MESSAGE);
1092
1213
  }
1093
1214
  const payload = {
1094
1215
  toolName: `message:${event.action}`,
1095
1216
  toolType: "message_event",
1096
1217
  eventType: isInbound ? "user_prompt" : "response",
1097
- agent: config.agentName,
1218
+ agent: config.agentType,
1219
+ agentName: config.agentName,
1098
1220
  hookType: "session_event",
1099
1221
  sessionId: resolveConversationId(),
1100
1222
  conversationId: resolveConversationId(),
@@ -1110,7 +1232,7 @@ function register(api) {
1110
1232
  payload.prompt = `${peer}: ${content}`;
1111
1233
  }
1112
1234
  }
1113
- void sendEvent(payload, config, pluginFilePath);
1235
+ void sendEvent(payload, config, pluginFilePath, HOOK_MESSAGE);
1114
1236
  },
1115
1237
  { name: "agentapprove-session-monitor", description: "Log message events to Agent Approve" }
1116
1238
  );
@@ -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.6",
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.6",
3
+ "version": "0.1.8",
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": {