@agent-native/core 0.114.9 → 0.114.11

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 (150) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +31 -3
  5. package/corpus/core/src/agent/engine/anthropic-engine.ts +20 -3
  6. package/corpus/core/src/agent/engine/builder-engine.ts +56 -9
  7. package/corpus/core/src/agent/engine/first-event-timeout.ts +64 -0
  8. package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
  9. package/corpus/core/src/client/AssistantChat.tsx +7 -3
  10. package/corpus/core/src/client/resources/ResourcesPanel.tsx +1 -1
  11. package/corpus/core/src/extensions/actions.ts +219 -0
  12. package/corpus/core/src/integrations/computer-supervision-store.ts +4 -4
  13. package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
  14. package/corpus/core/src/integrations/remote-commands-store.ts +5 -5
  15. package/corpus/core/src/integrations/remote-devices-store.ts +3 -3
  16. package/corpus/core/src/integrations/remote-push-store.ts +3 -3
  17. package/corpus/core/src/observability/traces.ts +38 -1
  18. package/corpus/core/src/server/action-routes.ts +23 -1
  19. package/corpus/core/src/server/http-response-telemetry.ts +6 -1
  20. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
  21. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
  22. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  23. package/corpus/templates/analytics/actions/account-deep-dive.ts +41 -6
  24. package/corpus/templates/analytics/changelog/2026-07-20-account-deep-dives-finish-with-partial-data-when-hubspot-lo.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-07-20-dashboard-email-reports-now-include-an-image-when-a-single-p.md +6 -0
  26. package/corpus/templates/analytics/server/lib/dashboard-report.ts +11 -3
  27. package/corpus/templates/calendar/actions/create-event.ts +5 -0
  28. package/corpus/templates/calendar/app/components/ThemeToggle.tsx +4 -1
  29. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +0 -1
  30. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +309 -421
  31. package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +349 -0
  32. package/corpus/templates/calendar/app/i18n-data.ts +110 -0
  33. package/corpus/templates/calendar/app/lib/event-form-utils.ts +6 -0
  34. package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
  35. package/corpus/templates/calendar/app/root.tsx +4 -2
  36. package/corpus/templates/calendar/changelog/2026-07-20-events-can-be-updated-inline-with-date-time-timezone-and-rep.md +6 -0
  37. package/corpus/templates/calendar/changelog/2026-07-20-public-booking-pages-no-longer-hydrate-with-a-theme-mismatch.md +6 -0
  38. package/corpus/templates/calendar/shared/api.ts +1 -0
  39. package/corpus/templates/clips/AGENTS.md +7 -0
  40. package/corpus/templates/clips/actions/create-recording.ts +51 -10
  41. package/corpus/templates/clips/actions/get-recording-player-data.ts +34 -5
  42. package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
  43. package/corpus/templates/clips/actions/list-recordings.ts +15 -4
  44. package/corpus/templates/clips/actions/regenerate-summary.ts +32 -0
  45. package/corpus/templates/clips/actions/search-recordings.ts +28 -6
  46. package/corpus/templates/clips/actions/view-screen.ts +54 -47
  47. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -5
  48. package/corpus/templates/clips/app/components/library/recording-card.tsx +29 -32
  49. package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +13 -0
  50. package/corpus/templates/clips/app/components/player/video-player.tsx +48 -0
  51. package/corpus/templates/clips/app/lib/fmp4.ts +26 -4
  52. package/corpus/templates/clips/app/lib/mse-video-loader.ts +13 -2
  53. package/corpus/templates/clips/changelog/2026-07-20-agent-summaries-use-bounded-transcript-context.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-20-clip-summaries-now-finish-without-sending-a-long-transcript-.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-20-clips-agents-only-discover-recordings-you-own-or-have-alread.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-20-hosted-recordings-no-longer-start-with-an-impossible-buffered-upload.md +6 -0
  57. package/corpus/templates/clips/changelog/2026-07-20-meeting-recordings-now-offer-a-cleaner-transcript-view-with-.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-07-20-rewind-capture-status.md +6 -0
  59. package/corpus/templates/clips/changelog/2026-07-20-shared-clips-can-be-shared-with-limited-access-and-cards-sho.md +6 -0
  60. package/corpus/templates/clips/desktop/src/app.tsx +81 -71
  61. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +12 -8
  62. package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +103 -0
  63. package/corpus/templates/clips/desktop/src/overlays/pill-logo.tsx +2 -15
  64. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +107 -340
  65. package/corpus/templates/clips/desktop/src/styles.css +37 -145
  66. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +6 -7
  67. package/corpus/templates/clips/server/lib/agent-recording-access.ts +73 -0
  68. package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -2
  69. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +18 -0
  70. package/corpus/templates/plan/app/pages/PlansPage.tsx +10 -11
  71. package/corpus/templates/plan/changelog/2026-07-20-bridged-local-plans-now-load-comments-from-the-local-workspace.md +6 -0
  72. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  73. package/dist/agent/engine/ai-sdk-engine.js +27 -4
  74. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  75. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  76. package/dist/agent/engine/anthropic-engine.js +19 -4
  77. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  78. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  79. package/dist/agent/engine/builder-engine.js +40 -7
  80. package/dist/agent/engine/builder-engine.js.map +1 -1
  81. package/dist/agent/engine/first-event-timeout.d.ts +25 -0
  82. package/dist/agent/engine/first-event-timeout.d.ts.map +1 -0
  83. package/dist/agent/engine/first-event-timeout.js +49 -0
  84. package/dist/agent/engine/first-event-timeout.js.map +1 -0
  85. package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
  86. package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
  87. package/dist/cli/pr-visual-recap-workflow.js +2 -2
  88. package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
  89. package/dist/client/AssistantChat.d.ts.map +1 -1
  90. package/dist/client/AssistantChat.js +7 -3
  91. package/dist/client/AssistantChat.js.map +1 -1
  92. package/dist/client/resources/ResourcesPanel.js +1 -1
  93. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  94. package/dist/collab/awareness.d.ts +2 -2
  95. package/dist/collab/awareness.d.ts.map +1 -1
  96. package/dist/collab/routes.d.ts +1 -1
  97. package/dist/collab/struct-routes.d.ts +1 -1
  98. package/dist/extensions/actions.d.ts.map +1 -1
  99. package/dist/extensions/actions.js +193 -1
  100. package/dist/extensions/actions.js.map +1 -1
  101. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  102. package/dist/integrations/computer-supervision-store.js +4 -4
  103. package/dist/integrations/computer-supervision-store.js.map +1 -1
  104. package/dist/integrations/pending-tasks-store.js +1 -1
  105. package/dist/integrations/pending-tasks-store.js.map +1 -1
  106. package/dist/integrations/remote-commands-store.js +5 -5
  107. package/dist/integrations/remote-commands-store.js.map +1 -1
  108. package/dist/integrations/remote-devices-store.js +3 -3
  109. package/dist/integrations/remote-devices-store.js.map +1 -1
  110. package/dist/integrations/remote-push-store.js +3 -3
  111. package/dist/integrations/remote-push-store.js.map +1 -1
  112. package/dist/notifications/routes.d.ts +2 -2
  113. package/dist/observability/routes.d.ts +3 -3
  114. package/dist/observability/traces.d.ts.map +1 -1
  115. package/dist/observability/traces.js +23 -1
  116. package/dist/observability/traces.js.map +1 -1
  117. package/dist/progress/routes.d.ts +1 -1
  118. package/dist/resources/handlers.d.ts +1 -1
  119. package/dist/secrets/routes.d.ts +3 -3
  120. package/dist/server/action-routes.d.ts.map +1 -1
  121. package/dist/server/action-routes.js +23 -1
  122. package/dist/server/action-routes.js.map +1 -1
  123. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  124. package/dist/server/http-response-telemetry.d.ts +2 -0
  125. package/dist/server/http-response-telemetry.d.ts.map +1 -1
  126. package/dist/server/http-response-telemetry.js +5 -1
  127. package/dist/server/http-response-telemetry.js.map +1 -1
  128. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
  129. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
  130. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  131. package/package.json +2 -2
  132. package/src/agent/engine/ai-sdk-engine.ts +31 -3
  133. package/src/agent/engine/anthropic-engine.ts +20 -3
  134. package/src/agent/engine/builder-engine.ts +56 -9
  135. package/src/agent/engine/first-event-timeout.ts +64 -0
  136. package/src/cli/pr-visual-recap-workflow.ts +2 -3
  137. package/src/client/AssistantChat.tsx +7 -3
  138. package/src/client/resources/ResourcesPanel.tsx +1 -1
  139. package/src/extensions/actions.ts +219 -0
  140. package/src/integrations/computer-supervision-store.ts +4 -4
  141. package/src/integrations/pending-tasks-store.ts +1 -1
  142. package/src/integrations/remote-commands-store.ts +5 -5
  143. package/src/integrations/remote-devices-store.ts +3 -3
  144. package/src/integrations/remote-push-store.ts +3 -3
  145. package/src/observability/traces.ts +38 -1
  146. package/src/server/action-routes.ts +23 -1
  147. package/src/server/http-response-telemetry.ts +6 -1
  148. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
  149. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
  150. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
@@ -1,8 +1,11 @@
1
+ import { randomUUID } from "node:crypto";
2
+
1
3
  import { ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER } from "../action-ui.js";
2
4
  import { AgentActionStopError, type ActionRunContext } from "../action.js";
3
5
  import type { ActionEntry } from "../agent/production-agent.js";
4
6
  import type { AgentChatAttachment } from "../agent/types.js";
5
7
  import { writeAppState } from "../application-state/script-helpers.js";
8
+ import { getDbExec, isPostgres } from "../db/client.js";
6
9
  import { readResource } from "../resources/script-helpers.js";
7
10
  import {
8
11
  getRequestOrgId,
@@ -36,6 +39,7 @@ import {
36
39
  import {
37
40
  createExtension,
38
41
  deleteExtension,
42
+ ensureExtensionsTables,
39
43
  findRecentDuplicateExtension,
40
44
  getHiddenExtensionIdsForCurrentUser,
41
45
  getExtension,
@@ -45,6 +49,7 @@ import {
45
49
  hideExtension,
46
50
  listExtensionHistory,
47
51
  listExtensions,
52
+ notifyExtensionChangeForResource,
48
53
  restoreExtensionHistoryVersion,
49
54
  unhideExtension,
50
55
  updateExtension,
@@ -818,6 +823,220 @@ export function createExtensionActionEntries(): Record<string, ActionEntry> {
818
823
  },
819
824
  },
820
825
 
826
+ "extension-data-set": {
827
+ tool: {
828
+ description:
829
+ "Write a value to an extension's extensionData store (the tool_data table) from the agent side. Use this when the agent needs to update or seed data that an extension reads via extensionData.get() at render time. Requires editor access to the extension.",
830
+ parameters: {
831
+ type: "object",
832
+ properties: {
833
+ extensionId: {
834
+ type: "string",
835
+ description: "Extension id whose data store to write to.",
836
+ },
837
+ collection: {
838
+ type: "string",
839
+ description: "Collection name within the extension's data store.",
840
+ },
841
+ itemId: {
842
+ type: "string",
843
+ description:
844
+ "Item id within the collection. Upserts if it already exists.",
845
+ },
846
+ data: {
847
+ description:
848
+ "The data value to store. Objects are JSON-serialized automatically.",
849
+ },
850
+ scope: {
851
+ type: "string",
852
+ enum: ["user", "org"],
853
+ description:
854
+ "Storage scope. 'user' (default) is private to the current user; 'org' is shared across the organization.",
855
+ },
856
+ },
857
+ required: ["extensionId", "collection", "itemId", "data"],
858
+ },
859
+ },
860
+ run: async (args) => {
861
+ const extensionId = String(args?.extensionId ?? "").trim();
862
+ const collection = String(args?.collection ?? "").trim();
863
+ const itemId = String(args?.itemId ?? "").trim();
864
+ if (!extensionId || !collection || !itemId)
865
+ return "Error: extensionId, collection, and itemId are required.";
866
+ if (args?.data === undefined) return "Error: data is required.";
867
+
868
+ await ensureExtensionsTables();
869
+ const access = await resolveAccess("extension", extensionId);
870
+ if (!access || access.role === "viewer")
871
+ return `Error: editor access required for extension ${extensionId}.`;
872
+
873
+ const userEmail = getRequestUserEmail()?.toLowerCase() ?? "";
874
+ const scope = args?.scope === "org" ? "org" : "user";
875
+ const orgId = getRequestOrgId();
876
+ if (scope === "org" && !orgId)
877
+ return "Error: org context required for scope=org.";
878
+
879
+ const data =
880
+ typeof args.data === "string" ? args.data : JSON.stringify(args.data);
881
+ const MAX_BYTES = 1024 * 1024;
882
+ if (Buffer.byteLength(data, "utf8") > MAX_BYTES)
883
+ return `Error: data exceeds ${MAX_BYTES} byte limit. Store large payloads in file storage instead.`;
884
+
885
+ const now = new Date().toISOString();
886
+ const scopeKey = scope === "org" ? `org:${orgId}` : userEmail;
887
+ const client = getDbExec();
888
+ const pg = isPostgres();
889
+ const conflictClause = pg
890
+ ? `ON CONFLICT (tool_id, collection, scope_key, item_id)
891
+ DO UPDATE SET data = EXCLUDED.data, updated_at = EXCLUDED.updated_at`
892
+ : `ON CONFLICT (tool_id, collection, scope_key, item_id)
893
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`;
894
+
895
+ await client.execute({
896
+ sql: `UPDATE tools SET updated_at = ? WHERE id = ?`,
897
+ args: [now, extensionId],
898
+ });
899
+ await client.execute({
900
+ sql: `INSERT INTO tool_data (id, tool_id, collection, item_id, data, owner_email, scope, org_id, scope_key, created_at, updated_at)
901
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
902
+ ${conflictClause}`,
903
+ args: [
904
+ randomUUID(),
905
+ extensionId,
906
+ collection,
907
+ itemId,
908
+ data,
909
+ userEmail,
910
+ scope,
911
+ scope === "org" ? orgId! : null,
912
+ scopeKey,
913
+ now,
914
+ now,
915
+ ],
916
+ });
917
+ await notifyExtensionChangeForResource(extensionId);
918
+
919
+ return {
920
+ ok: true,
921
+ id: itemId,
922
+ extensionId,
923
+ collection,
924
+ scope,
925
+ updatedAt: now,
926
+ };
927
+ },
928
+ },
929
+
930
+ "extension-data-get": {
931
+ tool: {
932
+ description:
933
+ "Read items from an extension's extensionData store (the tool_data table) from the agent side. Use this to inspect what data an extension currently has stored, or to verify a write succeeded. Requires viewer access to the extension.",
934
+ parameters: {
935
+ type: "object",
936
+ properties: {
937
+ extensionId: {
938
+ type: "string",
939
+ description: "Extension id whose data store to read from.",
940
+ },
941
+ collection: {
942
+ type: "string",
943
+ description: "Collection name within the extension's data store.",
944
+ },
945
+ itemId: {
946
+ type: "string",
947
+ description:
948
+ "Optional specific item id. If omitted, lists all items in the collection.",
949
+ },
950
+ scope: {
951
+ type: "string",
952
+ enum: ["user", "org", "all"],
953
+ description:
954
+ "Storage scope to read. 'user' (default) reads the current user's items; 'org' reads organization-shared items; 'all' reads both.",
955
+ },
956
+ limit: {
957
+ type: "number",
958
+ description:
959
+ "Maximum items to return when listing (default 100, max 1000).",
960
+ },
961
+ },
962
+ required: ["extensionId", "collection"],
963
+ },
964
+ },
965
+ run: async (args) => {
966
+ const extensionId = String(args?.extensionId ?? "").trim();
967
+ const collection = String(args?.collection ?? "").trim();
968
+ if (!extensionId || !collection)
969
+ return "Error: extensionId and collection are required.";
970
+
971
+ await ensureExtensionsTables();
972
+ const access = await resolveAccess("extension", extensionId);
973
+ if (!access) return `Error: no access to extension ${extensionId}.`;
974
+
975
+ const userEmail = getRequestUserEmail()?.toLowerCase() ?? "";
976
+ const scope = args?.scope ?? "user";
977
+ const orgId = getRequestOrgId();
978
+ if ((scope === "org" || scope === "all") && !orgId)
979
+ return "Error: org context required for scope=org or scope=all.";
980
+ const itemId = args?.itemId ? String(args.itemId).trim() : null;
981
+ const limit = Math.min(Math.max(1, Number(args?.limit) || 100), 1000);
982
+ const client = getDbExec();
983
+
984
+ if (itemId) {
985
+ const scopeClause =
986
+ scope === "org"
987
+ ? `AND scope = 'org' AND org_id = ?`
988
+ : scope === "all"
989
+ ? `AND ((scope = 'user' AND lower(owner_email) = ?) OR (scope = 'org' AND org_id = ?))`
990
+ : `AND scope = 'user' AND lower(owner_email) = ?`;
991
+ const scopeArgs =
992
+ scope === "org"
993
+ ? [orgId ?? ""]
994
+ : scope === "all"
995
+ ? [userEmail, orgId ?? ""]
996
+ : [userEmail];
997
+
998
+ const result = await client.execute({
999
+ sql: `SELECT COALESCE(item_id, id) AS id, data, scope, owner_email, updated_at
1000
+ FROM tool_data
1001
+ WHERE tool_id = ? AND collection = ? AND COALESCE(item_id, id) = ? ${scopeClause}`,
1002
+ args: [extensionId, collection, itemId, ...scopeArgs],
1003
+ });
1004
+ const row = result.rows?.[0];
1005
+ if (!row) return { found: false, extensionId, collection, itemId };
1006
+ return { found: true, ...row };
1007
+ }
1008
+
1009
+ const scopeClause =
1010
+ scope === "org"
1011
+ ? `AND scope = 'org' AND org_id = ?`
1012
+ : scope === "all"
1013
+ ? `AND ((scope = 'user' AND lower(owner_email) = ?) OR (scope = 'org' AND org_id = ?))`
1014
+ : `AND scope = 'user' AND lower(owner_email) = ?`;
1015
+ const scopeArgs =
1016
+ scope === "org"
1017
+ ? [orgId ?? ""]
1018
+ : scope === "all"
1019
+ ? [userEmail, orgId ?? ""]
1020
+ : [userEmail];
1021
+
1022
+ const result = await client.execute({
1023
+ sql: `SELECT COALESCE(item_id, id) AS id, data, scope, owner_email, updated_at
1024
+ FROM tool_data
1025
+ WHERE tool_id = ? AND collection = ? ${scopeClause}
1026
+ ORDER BY updated_at DESC
1027
+ LIMIT ?`,
1028
+ args: [extensionId, collection, ...scopeArgs, limit],
1029
+ });
1030
+ return {
1031
+ extensionId,
1032
+ collection,
1033
+ scope,
1034
+ count: result.rows?.length ?? 0,
1035
+ items: result.rows ?? [],
1036
+ };
1037
+ },
1038
+ },
1039
+
821
1040
  "delete-extension": {
822
1041
  tool: {
823
1042
  description:
@@ -175,7 +175,7 @@ export async function decideComputerApproval(input: {
175
175
  SET status = ?, decision_result_json = ?, decided_by = ?,
176
176
  decided_at = ?, updated_at = ?
177
177
  WHERE id = ? AND owner_email = ?
178
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
178
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
179
179
  AND action_hash = ? AND status = 'pending' AND expires_at > ?`,
180
180
  args: [
181
181
  input.decision,
@@ -204,7 +204,7 @@ export async function getComputerApprovalForOwner(input: {
204
204
  const { rows } = await getDbExec().execute({
205
205
  sql: `SELECT * FROM integration_computer_approvals
206
206
  WHERE id = ? AND owner_email = ?
207
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
207
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
208
208
  LIMIT 1`,
209
209
  args: [
210
210
  input.id,
@@ -228,7 +228,7 @@ export async function listComputerApprovalsForOwner(input: {
228
228
  await ensureComputerApprovalStore();
229
229
  const clauses = [
230
230
  "owner_email = ?",
231
- "((org_id IS NULL AND ? IS NULL) OR org_id = ?)",
231
+ "((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)",
232
232
  ];
233
233
  const args: Array<string | number | null> = [
234
234
  input.ownerEmail,
@@ -295,7 +295,7 @@ export async function authorizeComputerOperation(
295
295
  const { rows } = await client.execute({
296
296
  sql: `SELECT * FROM integration_computer_approvals
297
297
  WHERE id = ? AND owner_email = ?
298
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
298
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
299
299
  AND device_id = ? AND task_id = ? AND run_id = ?
300
300
  LIMIT 1`,
301
301
  args: [
@@ -316,7 +316,7 @@ export async function resolveIntegrationSourceContext(
316
316
  FROM integration_pending_tasks
317
317
  WHERE id = ?
318
318
  AND owner_email = ?
319
- AND (org_id = ? OR (org_id IS NULL AND ? IS NULL))
319
+ AND (org_id = ? OR (org_id IS NULL AND CAST(? AS TEXT) IS NULL))
320
320
  AND platform = 'slack'
321
321
  LIMIT 1`,
322
322
  args: [id, ownerEmail, orgId, orgId],
@@ -368,7 +368,7 @@ export async function listRemoteCommandsForOwner(input: {
368
368
  const { rows } = await getDbExec().execute({
369
369
  sql: `SELECT * FROM integration_remote_commands
370
370
  WHERE owner_email = ?
371
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
371
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
372
372
  ORDER BY updated_at DESC
373
373
  LIMIT ?`,
374
374
  args: [input.ownerEmail, input.orgId ?? null, input.orgId ?? null, limit],
@@ -435,7 +435,7 @@ export async function claimNextComputerCommand(input: {
435
435
  const { rows } = await client.execute({
436
436
  sql: `SELECT * FROM integration_remote_commands
437
437
  WHERE device_id = ? AND owner_email = ?
438
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
438
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
439
439
  AND kind = 'computer-operation' AND status = 'pending'
440
440
  AND next_check_at <= ?${operationClassClause}
441
441
  ORDER BY computer_sequence ASC, created_at ASC
@@ -477,7 +477,7 @@ export async function claimNextComputerCommand(input: {
477
477
  sql: `UPDATE integration_remote_commands
478
478
  SET status = 'failed', error_message = ?, completed_at = ?, updated_at = ?
479
479
  WHERE id = ? AND device_id = ? AND owner_email = ?
480
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
480
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
481
481
  AND status = 'pending'`,
482
482
  args: [
483
483
  error instanceof Error
@@ -500,13 +500,13 @@ export async function claimNextComputerCommand(input: {
500
500
  ? `UPDATE integration_remote_commands
501
501
  SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?
502
502
  WHERE id = ? AND device_id = ? AND owner_email = ?
503
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
503
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
504
504
  AND status = 'pending' AND lease_expires_at > ?
505
505
  RETURNING *`
506
506
  : `UPDATE integration_remote_commands
507
507
  SET status = 'claimed', attempts = attempts + 1, claimed_at = ?, updated_at = ?
508
508
  WHERE id = ? AND device_id = ? AND owner_email = ?
509
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
509
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
510
510
  AND status = 'pending' AND lease_expires_at > ?`,
511
511
  args: [
512
512
  now,
@@ -243,7 +243,7 @@ export async function getRemoteDeviceForOwner(input: {
243
243
  sql: `SELECT * FROM integration_remote_devices
244
244
  WHERE id = ?
245
245
  AND owner_email = ?
246
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
246
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
247
247
  LIMIT 1`,
248
248
  args: [
249
249
  input.id,
@@ -287,7 +287,7 @@ export async function listRemoteDevicesForOwner(input: {
287
287
  const { rows } = await getDbExec().execute({
288
288
  sql: `SELECT * FROM integration_remote_devices
289
289
  WHERE owner_email = ?
290
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)${statusClause}
290
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)${statusClause}
291
291
  ORDER BY COALESCE(last_seen_at, updated_at) DESC
292
292
  LIMIT ?`,
293
293
  args,
@@ -379,7 +379,7 @@ export async function revokeRemoteDeviceForOwner(input: {
379
379
  updated_at = ?
380
380
  WHERE id = ?
381
381
  AND owner_email = ?
382
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)`,
382
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)`,
383
383
  args: [
384
384
  now,
385
385
  now,
@@ -330,7 +330,7 @@ export async function listRemotePushRegistrationsForOwner(input: {
330
330
  const { rows } = await getDbExec().execute({
331
331
  sql: `SELECT * FROM integration_remote_push_registrations
332
332
  WHERE owner_email = ?
333
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)${statusClause}
333
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)${statusClause}
334
334
  ORDER BY COALESCE(last_seen_at, updated_at) DESC
335
335
  LIMIT ?`,
336
336
  args: [input.ownerEmail, input.orgId ?? null, input.orgId ?? null, limit],
@@ -352,7 +352,7 @@ export async function unregisterRemotePushRegistrationForOwner(input: {
352
352
  sql: `UPDATE integration_remote_push_registrations
353
353
  SET status = 'inactive', updated_at = ?
354
354
  WHERE owner_email = ?
355
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)
355
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
356
356
  AND (${input.id ? "id = ?" : "0 = 1"} OR ${
357
357
  tokenHash ? "token_hash = ?" : "0 = 1"
358
358
  })`,
@@ -432,7 +432,7 @@ export async function listRemotePushNotificationsForOwner(input: {
432
432
  const { rows } = await getDbExec().execute({
433
433
  sql: `SELECT * FROM integration_remote_push_notifications
434
434
  WHERE owner_email = ?
435
- AND ((org_id IS NULL AND ? IS NULL) OR org_id = ?)${statusClause}
435
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)${statusClause}
436
436
  ORDER BY created_at DESC
437
437
  LIMIT ?`,
438
438
  args,
@@ -27,6 +27,9 @@ function costUsdFromCenticents(value: number): number {
27
27
  }
28
28
 
29
29
  const MAX_TRACKED_GENERATION_TOOL_CALLS = 50;
30
+ const MAX_TOOL_ERROR_MESSAGE_LENGTH = 500;
31
+ const STANDALONE_API_KEY_PATTERN =
32
+ /\b(?:sk-(?:proj-|ant-)?[A-Za-z0-9_-]{8,}|(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{8,}|AIza[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{16,})\b/g;
30
33
 
31
34
  type GenerationToolCall = {
32
35
  name: string;
@@ -34,8 +37,35 @@ type GenerationToolCall = {
34
37
  duration_ms: number;
35
38
  status: "success" | "error";
36
39
  error_class: "tool_error" | "legacy_inferred_error" | "interrupted" | null;
40
+ error_message?: string;
37
41
  };
38
42
 
43
+ function truncateToolErrorMessage(value: string): string {
44
+ return value.length > MAX_TOOL_ERROR_MESSAGE_LENGTH
45
+ ? `${value.slice(0, MAX_TOOL_ERROR_MESSAGE_LENGTH)}…`
46
+ : value;
47
+ }
48
+
49
+ function redactToolErrorMessage(value: string): string {
50
+ const credentialName =
51
+ "authorization|cookie|api[_ -]?key|password|secret|token|access[_ -]?token|refresh[_ -]?token";
52
+ const labeledCredential = `(["']?\\b(?:${credentialName})\\b["']?\\s*[:=]\\s*["']?)`;
53
+ return value
54
+ .replace(
55
+ new RegExp(
56
+ `${labeledCredential}(?:Bearer|Basic)\\s+[^"'\\s,;)}\\]]+`,
57
+ "gi",
58
+ ),
59
+ "$1[REDACTED]",
60
+ )
61
+ .replace(
62
+ new RegExp(`${labeledCredential}[^"'\\s,;)}\\[\\]]+`, "gi"),
63
+ "$1[REDACTED]",
64
+ )
65
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "[REDACTED]")
66
+ .replace(STANDALONE_API_KEY_PATTERN, "[REDACTED]");
67
+ }
68
+
39
69
  function emitLlmGenerationTrackingEvent(args: {
40
70
  runId: string;
41
71
  threadId: string | null;
@@ -439,6 +469,10 @@ export async function instrumentAgentLoop(opts: {
439
469
  : explicitError
440
470
  ? "tool_error"
441
471
  : "legacy_inferred_error",
472
+ error_message:
473
+ isError && config.captureToolResults
474
+ ? truncateToolErrorMessage(redactToolErrorMessage(event.result))
475
+ : undefined,
442
476
  });
443
477
  }
444
478
 
@@ -532,6 +566,7 @@ export async function instrumentAgentLoop(opts: {
532
566
  for (const [counter, pending] of pendingTools) {
533
567
  toolCallCount += 1;
534
568
  failedTools += 1;
569
+ const interruptedMessage = "Tool call interrupted before completion";
535
570
  if (counter < MAX_TRACKED_GENERATION_TOOL_CALLS) {
536
571
  generationToolCalls.set(counter, {
537
572
  name: pending.toolName,
@@ -539,9 +574,11 @@ export async function instrumentAgentLoop(opts: {
539
574
  duration_ms: Math.max(0, runEnd - pending.startMs),
540
575
  status: "error",
541
576
  error_class: "interrupted",
577
+ error_message: config.captureToolResults
578
+ ? interruptedMessage
579
+ : undefined,
542
580
  });
543
581
  }
544
- const interruptedMessage = "Tool call interrupted before completion";
545
582
  if (pending.otelSpan) {
546
583
  openOtelToolSpans.delete(pending.otelSpan);
547
584
  endAgentSpan(pending.otelSpan, {
@@ -26,10 +26,12 @@ import {
26
26
  seedAgentRunOwnerContext,
27
27
  type AgentRunOwnerContext,
28
28
  } from "./agent-run-context.js";
29
+ import { captureError } from "./capture-error.js";
29
30
  import {
30
31
  getAllowedCorsOrigin as resolveAllowedCorsOrigin,
31
32
  readCorsAllowedOrigins,
32
33
  } from "./cors-origins.js";
34
+ import { getHttpRequestTelemetryId } from "./http-response-telemetry.js";
33
35
 
34
36
  declare const __AGENT_NATIVE_BUILD_ID__: string | undefined;
35
37
  declare const __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__: string | undefined;
@@ -640,7 +642,27 @@ export function mountActionRoutes(
640
642
  if (isUserFacing) {
641
643
  return { error: msg };
642
644
  }
643
- console.error(`[agent-native] action '${name}' failed:`, err);
645
+ const requestId = getHttpRequestTelemetryId(event);
646
+ const captureId = captureError(err, {
647
+ route: routePath,
648
+ method: reqMethod,
649
+ tags: {
650
+ action: name,
651
+ caller: resolvedCaller
652
+ ? "a2a"
653
+ : isFrontendActionRequest(event)
654
+ ? "frontend"
655
+ : "http",
656
+ status_code: String(status),
657
+ },
658
+ ...(requestId ? { extra: { request_id: requestId } } : {}),
659
+ });
660
+ console.error(`[agent-native] action '${name}' failed:`, {
661
+ action: name,
662
+ ...(requestId ? { requestId } : {}),
663
+ ...(captureId ? { captureId } : {}),
664
+ error: err?.stack ?? String(err),
665
+ });
644
666
  return { error: "Internal server error" };
645
667
  }
646
668
  },
@@ -284,11 +284,16 @@ function emitTelemetry(
284
284
  function requestTelemetryState(
285
285
  event: H3Event,
286
286
  ): HttpRequestTelemetryState | undefined {
287
- return (event.context as Record<PropertyKey, unknown>)[
287
+ return (event.context as Record<PropertyKey, unknown> | undefined)?.[
288
288
  REQUEST_TELEMETRY_KEY
289
289
  ] as HttpRequestTelemetryState | undefined;
290
290
  }
291
291
 
292
+ /** Return the durable request id while a request is still being handled. */
293
+ export function getHttpRequestTelemetryId(event: H3Event): string | undefined {
294
+ return requestTelemetryState(event)?.requestId;
295
+ }
296
+
292
297
  function appendServerTiming(
293
298
  response: Response,
294
299
  event: H3Event,
@@ -153,6 +153,25 @@ persistence** — it handles everything automatically. Only use
153
153
  `dbQuery`/`dbExec` when querying the app's existing tables. See
154
154
  `references/api.md` for the full `get`/`remove`/scope reference.
155
155
 
156
+ ### Agent-side extension data access
157
+
158
+ The agent can read and write `extensionData` directly using two dedicated
159
+ actions — no need to go through the iframe bridge or raw SQL:
160
+
161
+ | Action | Purpose |
162
+ | --------------------- | ------------------------------------------------- |
163
+ | `extension-data-set` | Upsert an item in an extension's data store |
164
+ | `extension-data-get` | Read items from an extension's data store |
165
+
166
+ Use `extension-data-set` when the agent needs to seed, refresh, or update
167
+ data that an extension reads at render time via `extensionData.get()`. This
168
+ is the correct path for agent-driven dashboard refreshes — the agent
169
+ fetches fresh data from providers, then writes the merged result with
170
+ `extension-data-set`, and the extension picks it up on next load.
171
+
172
+ Use `extension-data-get` to inspect what data an extension currently stores,
173
+ or to verify a write succeeded.
174
+
156
175
  ## What extensions are
157
176
 
158
177
  Extensions are mini Alpine.js apps that run inside sandboxed iframes. They
@@ -32,7 +32,7 @@ await putSetting("observability-config", {
32
32
  enabled: true,
33
33
  capturePrompts: false,
34
34
  captureToolArgs: true, // capture action input args
35
- captureToolResults: false,
35
+ captureToolResults: false, // include failed tool error text on tracked $ai_generation tool call entries
36
36
  evalSampleRate: 0.05, // 5% of runs get LLM-as-judge eval
37
37
  inferredSentimentEnabled: false,
38
38
  inferredSentimentSampleRate: 0,
@@ -160,7 +160,7 @@ Other framework-level baseline events:
160
160
  - `signup` from Better Auth user creation, with `auth_provider`, `auth_user_id`, and first-touch referral attribution (`referral_source`, `referrer_user`, `referral_medium`, `referral_campaign`, `utm_*`, `first_touch_path`, `landing_referrer` — see "Referral / viral attribution" above)
161
161
  - `builder connect clicked` and `builder connect popup blocked` from browser Connect Builder CTAs
162
162
  - `builder connect started`, `builder connect succeeded`, `builder connect failed`, `builder disconnect succeeded`, and `builder disconnect failed` from the Builder connection routes, with LLM connection context when resolvable
163
- - `$ai_generation` from instrumented agent loops, with PostHog AI Observability fields such as `$ai_trace_id`, `$ai_session_id`, `$ai_model`, `$ai_provider`, `$ai_input_tokens`, `$ai_output_tokens`, `$ai_latency`, `$ai_total_cost_usd`, and mirrored Agent Native query fields such as `run_id`, `thread_id`, `cost_cents_x100`, `duration_ms`, `tool_calls`, and `status`. A bounded `tools` array contains names, start offsets, durations, statuses, and coarse error classes only; interrupted tools and failed runs remain visible, and delegated runs include protocol/task/parent-run/parent-turn correlation. Prompt, tool argument, result, and output content is excluded.
163
+ - `$ai_generation` from instrumented agent loops, with PostHog AI Observability fields such as `$ai_trace_id`, `$ai_session_id`, `$ai_model`, `$ai_provider`, `$ai_input_tokens`, `$ai_output_tokens`, `$ai_latency`, `$ai_total_cost_usd`, and mirrored Agent Native query fields such as `run_id`, `thread_id`, `cost_cents_x100`, `duration_ms`, `tool_calls`, and `status`. A bounded `tools` array contains names, start offsets, durations, statuses, and coarse error classes only; interrupted tools and failed runs remain visible, and delegated runs include protocol/task/parent-run/parent-turn correlation. Prompt, tool argument, result, and output content is excluded unless `captureToolResults` is opted in (see the `observability` skill), in which case each failed tool call also carries a `error_message` string truncated to 500 characters and already scrubbed of bearer tokens, API keys, and key/value secret patterns.
164
164
 
165
165
  For new lifecycle events, call `track()` server-side when the server is the source of truth, and `trackEvent()` client-side only for browser interactions.
166
166
 
@@ -381,16 +381,51 @@ export default defineAction({
381
381
  run: async (args) => {
382
382
  const trimmedQuery = args.query.trim();
383
383
  const gaps: string[] = [];
384
- const [dealResult, pipelines, owners] = await Promise.all([
385
- searchHubSpotObjects({
384
+
385
+ const enrichmentLookups = Promise.allSettled([
386
+ getDealPipelines(),
387
+ getDealOwners(),
388
+ ]);
389
+ let dealResult;
390
+ try {
391
+ dealResult = await searchHubSpotObjects({
386
392
  objectType: "deals",
387
393
  query: trimmedQuery,
388
394
  properties: DEAL_DEEP_DIVE_PROPERTIES,
389
395
  limit: args.dealLimit,
390
- }),
391
- getDealPipelines(),
392
- getDealOwners(),
393
- ]);
396
+ });
397
+ } catch (err) {
398
+ throw new Error(
399
+ `HubSpot deal search failed for "${trimmedQuery}": ${
400
+ err instanceof Error ? err.message : String(err)
401
+ }`,
402
+ );
403
+ }
404
+ const [pipelinesSettled, ownersSettled] = await enrichmentLookups;
405
+
406
+ let pipelines: Pipeline[] = [];
407
+ if (pipelinesSettled.status === "fulfilled") {
408
+ pipelines = pipelinesSettled.value;
409
+ } else {
410
+ const err = pipelinesSettled.reason;
411
+ gaps.push(
412
+ `HubSpot deal pipelines: ${
413
+ err instanceof Error ? err.message : String(err)
414
+ }`,
415
+ );
416
+ }
417
+
418
+ let owners: Record<string, string> = {};
419
+ if (ownersSettled.status === "fulfilled") {
420
+ owners = ownersSettled.value;
421
+ } else {
422
+ const err = ownersSettled.reason;
423
+ gaps.push(
424
+ `HubSpot deal owners: ${
425
+ err instanceof Error ? err.message : String(err)
426
+ }`,
427
+ );
428
+ }
394
429
 
395
430
  const lookups = stageLookups(getVisiblePipelines(pipelines));
396
431
  const deals = dealResult.records.map((deal) =>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-20
4
+ ---
5
+
6
+ Account deep dives now finish with partial data when HubSpot pipeline or owner lookups fail, and failed deal searches report a clear error.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-20
4
+ ---
5
+
6
+ Dashboard email reports now include an image when a single panel is still loading.