@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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/corpus/core/src/agent/engine/anthropic-engine.ts +20 -3
- package/corpus/core/src/agent/engine/builder-engine.ts +56 -9
- package/corpus/core/src/agent/engine/first-event-timeout.ts +64 -0
- package/corpus/core/src/cli/pr-visual-recap-workflow.ts +2 -3
- package/corpus/core/src/client/AssistantChat.tsx +7 -3
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +1 -1
- package/corpus/core/src/extensions/actions.ts +219 -0
- package/corpus/core/src/integrations/computer-supervision-store.ts +4 -4
- package/corpus/core/src/integrations/pending-tasks-store.ts +1 -1
- package/corpus/core/src/integrations/remote-commands-store.ts +5 -5
- package/corpus/core/src/integrations/remote-devices-store.ts +3 -3
- package/corpus/core/src/integrations/remote-push-store.ts +3 -3
- package/corpus/core/src/observability/traces.ts +38 -1
- package/corpus/core/src/server/action-routes.ts +23 -1
- package/corpus/core/src/server/http-response-telemetry.ts +6 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/account-deep-dive.ts +41 -6
- package/corpus/templates/analytics/changelog/2026-07-20-account-deep-dives-finish-with-partial-data-when-hubspot-lo.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-20-dashboard-email-reports-now-include-an-image-when-a-single-p.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +11 -3
- package/corpus/templates/calendar/actions/create-event.ts +5 -0
- package/corpus/templates/calendar/app/components/ThemeToggle.tsx +4 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +0 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +309 -421
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +349 -0
- package/corpus/templates/calendar/app/i18n-data.ts +110 -0
- package/corpus/templates/calendar/app/lib/event-form-utils.ts +6 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +3 -0
- package/corpus/templates/calendar/app/root.tsx +4 -2
- package/corpus/templates/calendar/changelog/2026-07-20-events-can-be-updated-inline-with-date-time-timezone-and-rep.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-20-public-booking-pages-no-longer-hydrate-with-a-theme-mismatch.md +6 -0
- package/corpus/templates/calendar/shared/api.ts +1 -0
- package/corpus/templates/clips/AGENTS.md +7 -0
- package/corpus/templates/clips/actions/create-recording.ts +51 -10
- package/corpus/templates/clips/actions/get-recording-player-data.ts +34 -5
- package/corpus/templates/clips/actions/lib/transcript-preview.ts +48 -0
- package/corpus/templates/clips/actions/list-recordings.ts +15 -4
- package/corpus/templates/clips/actions/regenerate-summary.ts +32 -0
- package/corpus/templates/clips/actions/search-recordings.ts +28 -6
- package/corpus/templates/clips/actions/view-screen.ts +54 -47
- package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -5
- package/corpus/templates/clips/app/components/library/recording-card.tsx +29 -32
- package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +13 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +48 -0
- package/corpus/templates/clips/app/lib/fmp4.ts +26 -4
- package/corpus/templates/clips/app/lib/mse-video-loader.ts +13 -2
- package/corpus/templates/clips/changelog/2026-07-20-agent-summaries-use-bounded-transcript-context.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clip-summaries-now-finish-without-sending-a-long-transcript-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-clips-agents-only-discover-recordings-you-own-or-have-alread.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-hosted-recordings-no-longer-start-with-an-impossible-buffered-upload.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-meeting-recordings-now-offer-a-cleaner-transcript-view-with-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-rewind-capture-status.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-20-shared-clips-can-be-shared-with-limited-access-and-cards-sho.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +81 -71
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +12 -8
- package/corpus/templates/clips/desktop/src/lib/rewind-status.ts +103 -0
- package/corpus/templates/clips/desktop/src/overlays/pill-logo.tsx +2 -15
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +107 -340
- package/corpus/templates/clips/desktop/src/styles.css +37 -145
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +6 -7
- package/corpus/templates/clips/server/lib/agent-recording-access.ts +73 -0
- package/corpus/templates/clips/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/plan/app/lib/plan-local-bridge.ts +18 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +10 -11
- package/corpus/templates/plan/changelog/2026-07-20-bridged-local-plans-now-load-comments-from-the-local-workspace.md +6 -0
- package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
- package/dist/agent/engine/ai-sdk-engine.js +27 -4
- package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
- package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
- package/dist/agent/engine/anthropic-engine.js +19 -4
- package/dist/agent/engine/anthropic-engine.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +40 -7
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/engine/first-event-timeout.d.ts +25 -0
- package/dist/agent/engine/first-event-timeout.d.ts.map +1 -0
- package/dist/agent/engine/first-event-timeout.js +49 -0
- package/dist/agent/engine/first-event-timeout.js.map +1 -0
- package/dist/cli/pr-visual-recap-workflow.d.ts +2 -2
- package/dist/cli/pr-visual-recap-workflow.d.ts.map +1 -1
- package/dist/cli/pr-visual-recap-workflow.js +2 -2
- package/dist/cli/pr-visual-recap-workflow.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +7 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +1 -1
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +193 -1
- package/dist/extensions/actions.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/computer-supervision-store.js +4 -4
- package/dist/integrations/computer-supervision-store.js.map +1 -1
- package/dist/integrations/pending-tasks-store.js +1 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/remote-commands-store.js +5 -5
- package/dist/integrations/remote-commands-store.js.map +1 -1
- package/dist/integrations/remote-devices-store.js +3 -3
- package/dist/integrations/remote-devices-store.js.map +1 -1
- package/dist/integrations/remote-push-store.js +3 -3
- package/dist/integrations/remote-push-store.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.d.ts.map +1 -1
- package/dist/observability/traces.js +23 -1
- package/dist/observability/traces.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +23 -1
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/http-response-telemetry.d.ts +2 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +5 -1
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
- package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
- package/package.json +2 -2
- package/src/agent/engine/ai-sdk-engine.ts +31 -3
- package/src/agent/engine/anthropic-engine.ts +20 -3
- package/src/agent/engine/builder-engine.ts +56 -9
- package/src/agent/engine/first-event-timeout.ts +64 -0
- package/src/cli/pr-visual-recap-workflow.ts +2 -3
- package/src/client/AssistantChat.tsx +7 -3
- package/src/client/resources/ResourcesPanel.tsx +1 -1
- package/src/extensions/actions.ts +219 -0
- package/src/integrations/computer-supervision-store.ts +4 -4
- package/src/integrations/pending-tasks-store.ts +1 -1
- package/src/integrations/remote-commands-store.ts +5 -5
- package/src/integrations/remote-devices-store.ts +3 -3
- package/src/integrations/remote-push-store.ts +3 -3
- package/src/observability/traces.ts +38 -1
- package/src/server/action-routes.ts +23 -1
- package/src/server/http-response-telemetry.ts +6 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +19 -0
- package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +1 -1
- 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
|
-
|
|
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
|
-
|
|
385
|
-
|
|
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
|
-
|
|
392
|
-
|
|
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) =>
|