denchclaw 2.0.7 → 2.0.9

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.
Files changed (132) hide show
  1. package/apps/web/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  2. package/apps/web/.next/standalone/apps/web/.next/app-build-manifest.json +149 -148
  3. package/apps/web/.next/standalone/apps/web/.next/app-path-routes-manifest.json +24 -24
  4. package/apps/web/.next/standalone/apps/web/.next/build-manifest.json +5 -5
  5. package/apps/web/.next/standalone/apps/web/.next/react-loadable-manifest.json +1 -1
  6. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found/page.js +2 -2
  7. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  8. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  9. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  10. package/apps/web/.next/standalone/apps/web/.next/server/app/_not-found.rsc +2 -2
  11. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/active/route_client-reference-manifest.js +1 -1
  12. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/route.js +1 -1
  13. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/route_client-reference-manifest.js +1 -1
  14. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stop/route.js +1 -1
  15. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stop/route_client-reference-manifest.js +1 -1
  16. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/stream/route_client-reference-manifest.js +1 -1
  17. package/apps/web/.next/standalone/apps/web/.next/server/app/api/chat/subagents/route_client-reference-manifest.js +1 -1
  18. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/jobs/[jobId]/runs/route_client-reference-manifest.js +1 -1
  19. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/jobs/route_client-reference-manifest.js +1 -1
  20. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/runs/[sessionId]/route_client-reference-manifest.js +1 -1
  21. package/apps/web/.next/standalone/apps/web/.next/server/app/api/cron/runs/search-transcript/route_client-reference-manifest.js +1 -1
  22. package/apps/web/.next/standalone/apps/web/.next/server/app/api/feedback/route.js +1 -1
  23. package/apps/web/.next/standalone/apps/web/.next/server/app/api/feedback/route.js.nft.json +1 -1
  24. package/apps/web/.next/standalone/apps/web/.next/server/app/api/feedback/route_client-reference-manifest.js +1 -1
  25. package/apps/web/.next/standalone/apps/web/.next/server/app/api/memories/route_client-reference-manifest.js +1 -1
  26. package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/route_client-reference-manifest.js +1 -1
  27. package/apps/web/.next/standalone/apps/web/.next/server/app/api/profiles/switch/route_client-reference-manifest.js +1 -1
  28. package/apps/web/.next/standalone/apps/web/.next/server/app/api/sessions/[sessionId]/route_client-reference-manifest.js +1 -1
  29. package/apps/web/.next/standalone/apps/web/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
  30. package/apps/web/.next/standalone/apps/web/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  31. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/messages/route_client-reference-manifest.js +1 -1
  32. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/[id]/route_client-reference-manifest.js +1 -1
  33. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/route.js +1 -1
  34. package/apps/web/.next/standalone/apps/web/.next/server/app/api/web-sessions/route_client-reference-manifest.js +1 -1
  35. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/assets/[...path]/route_client-reference-manifest.js +1 -1
  36. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/browse/route_client-reference-manifest.js +1 -1
  37. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/browse-file/route_client-reference-manifest.js +1 -1
  38. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/context/route_client-reference-manifest.js +1 -1
  39. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/copy/route_client-reference-manifest.js +1 -1
  40. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/db/introspect/route_client-reference-manifest.js +1 -1
  41. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/db/query/route_client-reference-manifest.js +1 -1
  42. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/delete/route.js +1 -1
  43. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/delete/route_client-reference-manifest.js +1 -1
  44. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/file/route_client-reference-manifest.js +1 -1
  45. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route.js +1 -1
  46. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/init/route_client-reference-manifest.js +1 -1
  47. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/list/route_client-reference-manifest.js +1 -1
  48. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/mkdir/route_client-reference-manifest.js +1 -1
  49. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/move/route_client-reference-manifest.js +1 -1
  50. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/display-field/route_client-reference-manifest.js +1 -1
  51. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/[id]/route_client-reference-manifest.js +1 -1
  52. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/bulk-delete/route_client-reference-manifest.js +1 -1
  53. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/options/route_client-reference-manifest.js +1 -1
  54. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/route.js +1 -1
  55. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/entries/route_client-reference-manifest.js +1 -1
  56. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/[fieldId]/enum-rename/route_client-reference-manifest.js +1 -1
  57. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/[fieldId]/route_client-reference-manifest.js +1 -1
  58. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/fields/reorder/route_client-reference-manifest.js +1 -1
  59. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/route_client-reference-manifest.js +1 -1
  60. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/objects/[name]/views/route_client-reference-manifest.js +1 -1
  61. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/open-file/route_client-reference-manifest.js +1 -1
  62. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/path-info/route_client-reference-manifest.js +1 -1
  63. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/query/route_client-reference-manifest.js +1 -1
  64. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/raw-file/route_client-reference-manifest.js +1 -1
  65. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/rename/route_client-reference-manifest.js +1 -1
  66. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/reports/execute/route.js +1 -1
  67. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/reports/execute/route_client-reference-manifest.js +1 -1
  68. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/search-index/route_client-reference-manifest.js +1 -1
  69. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/suggest-files/route_client-reference-manifest.js +1 -1
  70. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/switch/route.js +1 -1
  71. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/switch/route_client-reference-manifest.js +1 -1
  72. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/thumbnail/route_client-reference-manifest.js +1 -1
  73. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/tree/route_client-reference-manifest.js +1 -1
  74. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/upload/route.js +1 -1
  75. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/upload/route_client-reference-manifest.js +1 -1
  76. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/virtual-file/route_client-reference-manifest.js +1 -1
  77. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/watch/route_client-reference-manifest.js +1 -1
  78. package/apps/web/.next/standalone/apps/web/.next/server/app/api/workspace/write-binary/route_client-reference-manifest.js +1 -1
  79. package/apps/web/.next/standalone/apps/web/.next/server/app/index.html +1 -72
  80. package/apps/web/.next/standalone/apps/web/.next/server/app/index.rsc +2 -2
  81. package/apps/web/.next/standalone/apps/web/.next/server/app/page.js +2 -2
  82. package/apps/web/.next/standalone/apps/web/.next/server/app/page.js.nft.json +1 -1
  83. package/apps/web/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  84. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page.js +10 -10
  85. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page.js.nft.json +1 -1
  86. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace/page_client-reference-manifest.js +1 -1
  87. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace.html +1 -1
  88. package/apps/web/.next/standalone/apps/web/.next/server/app/workspace.rsc +3 -3
  89. package/apps/web/.next/standalone/apps/web/.next/server/app-paths-manifest.json +24 -24
  90. package/apps/web/.next/standalone/apps/web/.next/server/chunks/5334.js +9 -0
  91. package/apps/web/.next/standalone/apps/web/.next/server/functions-config-manifest.json +11 -10
  92. package/apps/web/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +1 -1
  93. package/apps/web/.next/standalone/apps/web/.next/server/middleware-react-loadable-manifest.js +1 -1
  94. package/apps/web/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  95. package/apps/web/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  96. package/apps/web/.next/{static/chunks/9978-a2bf90231d08284f.js → standalone/apps/web/.next/static/chunks/4988-a31fb680255d2e53.js} +3 -3
  97. package/apps/web/.next/{static/chunks/7597.6cb599051f1a46f7.js → standalone/apps/web/.next/static/chunks/9978.7ad588863427f502.js} +1 -1
  98. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/layout-f0561292a97b3d54.js +1 -0
  99. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/workspace/{page-fe39ceef01137175.js → page-c589ce6d59e620cf.js} +1 -1
  100. package/apps/web/.next/standalone/apps/web/.next/static/chunks/df90ee31-b8ee23eaa0135739.js +1 -0
  101. package/apps/web/.next/standalone/apps/web/.next/static/chunks/{webpack-1032d39338965700.js → webpack-38d8ef58f812737b.js} +1 -1
  102. package/apps/web/.next/standalone/apps/web/public/fonts/Bookerly-Bold.ttf +0 -0
  103. package/apps/web/.next/standalone/apps/web/public/fonts/Bookerly-BoldItalic.ttf +0 -0
  104. package/apps/web/.next/standalone/apps/web/public/fonts/Bookerly-Regular.ttf +0 -0
  105. package/apps/web/.next/standalone/apps/web/public/fonts/Bookerly-RegularItalic.ttf +0 -0
  106. package/apps/web/.next/standalone/package.json +1 -1
  107. package/apps/web/.next/{standalone/apps/web/.next/static/chunks/9978-a2bf90231d08284f.js → static/chunks/4988-a31fb680255d2e53.js} +3 -3
  108. package/apps/web/.next/{standalone/apps/web/.next/static/chunks/7597.6cb599051f1a46f7.js → static/chunks/9978.7ad588863427f502.js} +1 -1
  109. package/apps/web/.next/static/chunks/app/layout-f0561292a97b3d54.js +1 -0
  110. package/apps/web/.next/static/chunks/app/workspace/{page-fe39ceef01137175.js → page-c589ce6d59e620cf.js} +1 -1
  111. package/apps/web/.next/static/chunks/df90ee31-b8ee23eaa0135739.js +1 -0
  112. package/apps/web/.next/static/chunks/{webpack-1032d39338965700.js → webpack-38d8ef58f812737b.js} +1 -1
  113. package/apps/web/public/fonts/Bookerly-Bold.ttf +0 -0
  114. package/apps/web/public/fonts/Bookerly-BoldItalic.ttf +0 -0
  115. package/apps/web/public/fonts/Bookerly-Regular.ttf +0 -0
  116. package/apps/web/public/fonts/Bookerly-RegularItalic.ttf +0 -0
  117. package/dist/entry.js +1 -1
  118. package/dist/{program-CfNvnqVx.js → program-BYwTE_Ot.js} +17 -4
  119. package/dist/{run-main-BWHpVYis.js → run-main-CWe8UBRN.js} +1 -1
  120. package/extensions/posthog-analytics/index.ts +1 -1
  121. package/extensions/posthog-analytics/lib/event-mappers.ts +89 -10
  122. package/extensions/posthog-analytics/lib/privacy.ts +135 -0
  123. package/package.json +1 -1
  124. package/apps/web/.next/standalone/apps/web/.next/server/chunks/3180.js +0 -9
  125. package/apps/web/.next/standalone/apps/web/.next/static/chunks/app/layout-af0e9292b7fe2ec1.js +0 -1
  126. package/apps/web/.next/standalone/apps/web/.next/static/chunks/df90ee31-33cbf7968acd3b6e.js +0 -1
  127. package/apps/web/.next/static/chunks/app/layout-af0e9292b7fe2ec1.js +0 -1
  128. package/apps/web/.next/static/chunks/df90ee31-33cbf7968acd3b6e.js +0 -1
  129. /package/apps/web/.next/standalone/apps/web/.next/static/{ezaYRoo0uHfvCgSL72gWA → MgHqcQXhG7LZyx8fD3wsG}/_buildManifest.js +0 -0
  130. /package/apps/web/.next/standalone/apps/web/.next/static/{ezaYRoo0uHfvCgSL72gWA → MgHqcQXhG7LZyx8fD3wsG}/_ssgManifest.js +0 -0
  131. /package/apps/web/.next/static/{ezaYRoo0uHfvCgSL72gWA → MgHqcQXhG7LZyx8fD3wsG}/_buildManifest.js +0 -0
  132. /package/apps/web/.next/static/{ezaYRoo0uHfvCgSL72gWA → MgHqcQXhG7LZyx8fD3wsG}/_ssgManifest.js +0 -0
@@ -1342,6 +1342,19 @@ async function installBundledPlugins(params) {
1342
1342
  errorMessage: "Failed to set posthog-analytics API key."
1343
1343
  });
1344
1344
  }
1345
+ try {
1346
+ await runOpenClawOrThrow({
1347
+ openclawCommand: params.openclawCommand,
1348
+ args: [
1349
+ "--profile",
1350
+ params.profile,
1351
+ "gateway",
1352
+ "restart"
1353
+ ],
1354
+ timeoutMs: 6e4,
1355
+ errorMessage: "Failed to restart gateway after plugin install."
1356
+ });
1357
+ } catch {}
1345
1358
  return true;
1346
1359
  } catch {
1347
1360
  return false;
@@ -2824,7 +2837,7 @@ function registerTelemetryCommand(program) {
2824
2837
  cmd.command("disable").description("Disable anonymous telemetry").action(() => {
2825
2838
  writeTelemetryConfig({ enabled: false });
2826
2839
  console.log("Telemetry has been disabled.");
2827
- console.log("You can re-enable it anytime with: denchclaw telemetry enable");
2840
+ console.log("You can re-enable it anytime with: npx denchclaw telemetry enable");
2828
2841
  });
2829
2842
  cmd.command("enable").description("Enable anonymous telemetry").action(() => {
2830
2843
  writeTelemetryConfig({ enabled: true });
@@ -2833,7 +2846,7 @@ function registerTelemetryCommand(program) {
2833
2846
  const privacyCmd = cmd.command("privacy").description("Control whether message content is included in telemetry");
2834
2847
  privacyCmd.command("on").description("Enable privacy mode (redacts message content, default)").action(() => {
2835
2848
  if (!isTelemetryEnabled()) {
2836
- console.log("Telemetry is currently disabled. Enable it first with: denchclaw telemetry enable");
2849
+ console.log("Telemetry is currently disabled. Enable it first with: npx denchclaw telemetry enable");
2837
2850
  return;
2838
2851
  }
2839
2852
  writeTelemetryConfig({ privacyMode: true });
@@ -2841,12 +2854,12 @@ function registerTelemetryCommand(program) {
2841
2854
  });
2842
2855
  privacyCmd.command("off").description("Disable privacy mode (sends full message content)").action(() => {
2843
2856
  if (!isTelemetryEnabled()) {
2844
- console.log("Telemetry is currently disabled. Enable it first with: denchclaw telemetry enable");
2857
+ console.log("Telemetry is currently disabled. Enable it first with: npx denchclaw telemetry enable");
2845
2858
  return;
2846
2859
  }
2847
2860
  writeTelemetryConfig({ privacyMode: false });
2848
2861
  console.log("Privacy mode disabled. Full message content and tool results will be captured.");
2849
- console.log("Re-enable anytime with: denchclaw telemetry privacy on");
2862
+ console.log("Re-enable anytime with: npx denchclaw telemetry privacy on");
2850
2863
  });
2851
2864
  }
2852
2865
 
@@ -219,7 +219,7 @@ async function runCli(argv = process$1.argv) {
219
219
  process$1.exitCode = await delegateToGlobalOpenClaw(parseArgv);
220
220
  return;
221
221
  }
222
- const { buildProgram } = await import("./program-CfNvnqVx.js");
222
+ const { buildProgram } = await import("./program-BYwTE_Ot.js");
223
223
  await buildProgram().parseAsync(parseArgv);
224
224
  }
225
225
 
@@ -109,7 +109,7 @@ export default function register(api: any) {
109
109
  }
110
110
 
111
111
  emitGeneration(ph, traceCtx, sk, event, getPrivacyMode());
112
- emitTrace(ph, traceCtx, sk);
112
+ emitTrace(ph, traceCtx, sk, event, getPrivacyMode());
113
113
  emitCustomEvent(ph, "dench_turn_completed", {
114
114
  session_id: sk,
115
115
  run_id: ctx.runId,
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import os from "node:os";
3
3
  import type { PostHogClient } from "./posthog-client.js";
4
4
  import type { TraceContextManager } from "./trace-context.js";
5
- import { sanitizeForCapture, stripSecrets } from "./privacy.js";
5
+ import { sanitizeMessages, sanitizeOutputChoices, stripSecrets } from "./privacy.js";
6
6
 
7
7
  function getAnonymousId(): string {
8
8
  try {
@@ -15,8 +15,6 @@ function getAnonymousId(): string {
15
15
 
16
16
  /**
17
17
  * Extract actual token counts and cost from OpenClaw's per-message usage metadata.
18
- * Each assistant message in the agent_end messages array includes:
19
- * usage: { input, output, cacheRead, cacheWrite, cost: { total, input, output, ... } }
20
18
  */
21
19
  export function extractUsageFromMessages(messages: unknown): {
22
20
  inputTokens: number;
@@ -52,25 +50,25 @@ export function extractToolNamesFromMessages(messages: unknown): string[] {
52
50
  if (!msg || typeof msg !== "object") continue;
53
51
  const m = msg as Record<string, unknown>;
54
52
 
55
- // OpenAI format: assistant message with tool_calls array
56
53
  if (Array.isArray(m.tool_calls)) {
57
54
  for (const tc of m.tool_calls) {
58
55
  const name = (tc as any)?.function?.name ?? (tc as any)?.name;
59
56
  if (typeof name === "string" && name) names.push(name);
60
57
  }
61
58
  }
62
- // Anthropic format: content blocks with type "tool_use"
63
59
  if (Array.isArray(m.content)) {
64
60
  for (const block of m.content) {
65
61
  if ((block as any)?.type === "tool_use" && typeof (block as any)?.name === "string") {
66
62
  names.push((block as any).name);
67
63
  }
64
+ if ((block as any)?.type === "toolCall" && typeof (block as any)?.name === "string") {
65
+ names.push((block as any).name);
66
+ }
68
67
  if ((block as any)?.type === "tool-call" && typeof (block as any)?.toolName === "string") {
69
68
  names.push((block as any).toolName);
70
69
  }
71
70
  }
72
71
  }
73
- // Tool result messages have a "name" field with the tool name
74
72
  if (m.role === "tool" && typeof m.name === "string") {
75
73
  names.push(m.name);
76
74
  }
@@ -81,8 +79,6 @@ export function extractToolNamesFromMessages(messages: unknown): string[] {
81
79
  /**
82
80
  * Normalize OpenClaw's message format into OpenAI-compatible output choices
83
81
  * so PostHog can extract tool calls for the Tools tab.
84
- * Converts Anthropic-style content blocks (type: "toolCall") to OpenAI's
85
- * tool_calls array format.
86
82
  */
87
83
  export function normalizeOutputForPostHog(messages: unknown): unknown[] | undefined {
88
84
  if (!Array.isArray(messages)) return undefined;
@@ -135,6 +131,80 @@ export function normalizeOutputForPostHog(messages: unknown): unknown[] | undefi
135
131
  return choices.length > 0 ? choices : undefined;
136
132
  }
137
133
 
134
+ /**
135
+ * Build full conversation state for the $ai_trace event.
136
+ * Splits messages into input (user/tool/system) and output (assistant) arrays,
137
+ * preserving chronological order so PostHog renders the full conversation.
138
+ */
139
+ export function buildTraceState(
140
+ messages: unknown,
141
+ privacyMode: boolean,
142
+ ): { inputState: unknown; outputState: unknown } {
143
+ if (!Array.isArray(messages)) return { inputState: undefined, outputState: undefined };
144
+
145
+ const inputMessages: unknown[] = [];
146
+ const outputMessages: unknown[] = [];
147
+
148
+ for (const msg of messages) {
149
+ if (!msg || typeof msg !== "object") continue;
150
+ const m = msg as Record<string, unknown>;
151
+
152
+ const extractText = () => {
153
+ if (Array.isArray(m.content)) {
154
+ return (m.content as Array<Record<string, unknown>>)
155
+ .filter((b) => b.type === "text")
156
+ .map((b) => b.text)
157
+ .join("");
158
+ }
159
+ return typeof m.content === "string" ? m.content : null;
160
+ };
161
+
162
+ if (m.role === "assistant") {
163
+ const content = privacyMode ? "[REDACTED]" : extractText();
164
+ const entry: Record<string, unknown> = { role: "assistant", content };
165
+
166
+ const toolNames = extractToolNamesFromSingleMessage(m);
167
+ if (toolNames.length > 0) {
168
+ entry.tool_calls = toolNames.map((name) => ({
169
+ type: "function",
170
+ function: { name },
171
+ }));
172
+ }
173
+
174
+ outputMessages.push(entry);
175
+ } else if (m.role === "user" || m.role === "tool" || m.role === "toolResult" || m.role === "system") {
176
+ const content = privacyMode ? "[REDACTED]" : extractText();
177
+ const entry: Record<string, unknown> = { role: m.role, content };
178
+ if (m.name) entry.name = m.name;
179
+ if (m.toolName) entry.toolName = m.toolName;
180
+ inputMessages.push(entry);
181
+ }
182
+ }
183
+
184
+ return {
185
+ inputState: inputMessages.length > 0 ? inputMessages : undefined,
186
+ outputState: outputMessages.length > 0 ? outputMessages : undefined,
187
+ };
188
+ }
189
+
190
+ function extractToolNamesFromSingleMessage(m: Record<string, unknown>): string[] {
191
+ const names: string[] = [];
192
+ if (Array.isArray(m.tool_calls)) {
193
+ for (const tc of m.tool_calls) {
194
+ const name = (tc as any)?.function?.name ?? (tc as any)?.name;
195
+ if (typeof name === "string" && name) names.push(name);
196
+ }
197
+ }
198
+ if (Array.isArray(m.content)) {
199
+ for (const block of m.content) {
200
+ if ((block as any)?.type === "toolCall" && typeof (block as any)?.name === "string") {
201
+ names.push((block as any).name);
202
+ }
203
+ }
204
+ }
205
+ return names;
206
+ }
207
+
138
208
  /**
139
209
  * Emit a `$ai_generation` event from the agent_end hook data.
140
210
  */
@@ -187,10 +257,10 @@ export function emitGeneration(
187
257
  if (extracted.totalCostUsd > 0) properties.$ai_total_cost_usd = extracted.totalCostUsd;
188
258
  }
189
259
 
190
- properties.$ai_input = sanitizeForCapture(trace.input, privacyMode);
260
+ properties.$ai_input = sanitizeMessages(trace.input, privacyMode);
191
261
 
192
262
  const outputChoices = normalizeOutputForPostHog(event.messages);
193
- properties.$ai_output_choices = sanitizeForCapture(
263
+ properties.$ai_output_choices = sanitizeOutputChoices(
194
264
  outputChoices ?? event.output ?? event.messages,
195
265
  privacyMode,
196
266
  );
@@ -264,6 +334,8 @@ export function emitTrace(
264
334
  ph: PostHogClient,
265
335
  traceCtx: TraceContextManager,
266
336
  sessionKey: string,
337
+ event?: any,
338
+ privacyMode?: boolean,
267
339
  ): void {
268
340
  try {
269
341
  const trace = traceCtx.getTrace(sessionKey);
@@ -273,6 +345,11 @@ export function emitTrace(
273
345
  ? (Date.now() - trace.startedAt) / 1_000
274
346
  : undefined;
275
347
 
348
+ const { inputState, outputState } = buildTraceState(
349
+ event?.messages,
350
+ privacyMode ?? true,
351
+ );
352
+
276
353
  ph.capture({
277
354
  distinctId: getAnonymousId(),
278
355
  event: "$ai_trace",
@@ -281,6 +358,8 @@ export function emitTrace(
281
358
  $ai_session_id: trace.sessionId,
282
359
  $ai_latency: latency,
283
360
  $ai_span_name: "agent_run",
361
+ $ai_input_state: inputState,
362
+ $ai_output_state: outputState,
284
363
  tool_count: trace.toolSpans.length,
285
364
  },
286
365
  });
@@ -81,3 +81,138 @@ export function sanitizeForCapture(
81
81
  if (privacyMode) return REDACTED;
82
82
  return stripSecrets(value);
83
83
  }
84
+
85
+ /**
86
+ * Redact a tool_calls array while preserving tool name, id, and type metadata.
87
+ * Only arguments are redacted.
88
+ */
89
+ function redactToolCalls(toolCalls: unknown[]): unknown[] {
90
+ return toolCalls.map((tc: any) => {
91
+ if (!tc || typeof tc !== "object") return tc;
92
+ const out: Record<string, unknown> = {
93
+ id: tc.id,
94
+ type: tc.type ?? "function",
95
+ };
96
+ if (tc.function && typeof tc.function === "object") {
97
+ out.function = {
98
+ name: tc.function.name,
99
+ arguments: REDACTED,
100
+ };
101
+ }
102
+ if (tc.name) out.name = tc.name;
103
+ return out;
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Redact Anthropic-format content blocks while preserving tool metadata.
109
+ * Text blocks get redacted; toolCall blocks keep their name but redact arguments.
110
+ */
111
+ function redactContentBlocks(blocks: unknown[]): unknown[] {
112
+ return blocks.map((block: any) => {
113
+ if (!block || typeof block !== "object") return block;
114
+ if (block.type === "text") {
115
+ return { type: "text", text: REDACTED };
116
+ }
117
+ if (block.type === "toolCall") {
118
+ return {
119
+ type: "toolCall",
120
+ id: block.id ?? block.toolCallId,
121
+ name: block.name,
122
+ arguments: REDACTED,
123
+ };
124
+ }
125
+ if (block.type === "tool_use") {
126
+ return {
127
+ type: "tool_use",
128
+ id: block.id,
129
+ name: block.name,
130
+ input: REDACTED,
131
+ };
132
+ }
133
+ if (block.type === "thinking") {
134
+ return { type: "thinking", text: REDACTED };
135
+ }
136
+ return { type: block.type };
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Structure-preserving message redaction for PostHog message-array fields.
142
+ * Preserves: role, tool names, tool_call IDs, message ordering, tool types.
143
+ * Redacts: text content, tool arguments, tool results.
144
+ */
145
+ export function redactMessagesStructured(messages: unknown): unknown {
146
+ if (!Array.isArray(messages)) return messages;
147
+ return messages.map((msg: any) => {
148
+ if (!msg || typeof msg !== "object") return msg;
149
+ const out: Record<string, unknown> = { role: msg.role };
150
+
151
+ if (msg.name) out.name = msg.name;
152
+ if (msg.tool_call_id) out.tool_call_id = msg.tool_call_id;
153
+ if (msg.toolCallId) out.toolCallId = msg.toolCallId;
154
+ if (msg.toolName) out.toolName = msg.toolName;
155
+ if (msg.isError != null) out.isError = msg.isError;
156
+ if (msg.stopReason) out.stopReason = msg.stopReason;
157
+ if (msg.model) out.model = msg.model;
158
+ if (msg.provider) out.provider = msg.provider;
159
+
160
+ if (Array.isArray(msg.content)) {
161
+ out.content = redactContentBlocks(msg.content);
162
+ } else {
163
+ out.content = REDACTED;
164
+ }
165
+
166
+ if (Array.isArray(msg.tool_calls)) {
167
+ out.tool_calls = redactToolCalls(msg.tool_calls);
168
+ }
169
+
170
+ return out;
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Structure-preserving redaction for normalized OpenAI-format output choices.
176
+ * Keeps role, tool_calls[].function.name, tool_calls[].type.
177
+ * Redacts text content and tool arguments.
178
+ */
179
+ export function redactOutputChoicesStructured(choices: unknown): unknown {
180
+ if (!Array.isArray(choices)) return choices;
181
+ return choices.map((choice: any) => {
182
+ if (!choice || typeof choice !== "object") return choice;
183
+ const out: Record<string, unknown> = {
184
+ role: choice.role,
185
+ content: choice.content != null ? REDACTED : null,
186
+ };
187
+ if (Array.isArray(choice.tool_calls)) {
188
+ out.tool_calls = redactToolCalls(choice.tool_calls);
189
+ }
190
+ return out;
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Sanitize messages for PostHog capture, preserving structure in privacy mode.
196
+ * Privacy on: redacts text content/arguments but keeps role, tool names, ordering.
197
+ * Privacy off: only strips credential patterns.
198
+ */
199
+ export function sanitizeMessages(
200
+ messages: unknown,
201
+ privacyMode: boolean,
202
+ ): unknown {
203
+ if (privacyMode) return redactMessagesStructured(messages);
204
+ return stripSecrets(messages);
205
+ }
206
+
207
+ /**
208
+ * Sanitize normalized output choices for PostHog capture, preserving structure.
209
+ * Privacy on: redacts text content/arguments but keeps role, tool names.
210
+ * Privacy off: only strips credential patterns.
211
+ */
212
+ export function sanitizeOutputChoices(
213
+ choices: unknown,
214
+ privacyMode: boolean,
215
+ ): unknown {
216
+ if (privacyMode) return redactOutputChoicesStructured(choices);
217
+ return stripSecrets(choices);
218
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "denchclaw",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "Fully Managed OpenClaw Framework for managing your CRM, Sales Automation and Outreach agents. The only local productivity tool you need.",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/DenchHQ/DenchClaw#readme",