@agent-native/core 0.114.2 → 0.114.4
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 +30 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/a2a/artifact-response.ts +42 -16
- package/corpus/core/src/a2a/client.ts +4 -0
- package/corpus/core/src/a2a/handlers.ts +133 -2
- package/corpus/core/src/a2a/index.ts +1 -0
- package/corpus/core/src/a2a/types.ts +14 -0
- package/corpus/core/src/db/request-telemetry.ts +32 -8
- package/corpus/core/src/integrations/index.ts +6 -0
- package/corpus/core/src/integrations/pending-tasks-store.ts +89 -0
- package/corpus/core/src/integrations/webhook-handler.ts +17 -7
- package/corpus/core/src/scripts/call-agent.ts +54 -10
- package/corpus/core/src/server/agent-discovery.ts +22 -0
- package/corpus/core/src/settings/store.ts +15 -6
- package/corpus/core/src/shared/optional-node-builtins.ts +66 -0
- package/corpus/core/src/templates/default/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/core/src/templates/headless/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/core/src/vite/client.ts +5 -0
- package/corpus/templates/analytics/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/assets/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +2 -2
- package/corpus/templates/brain/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/clips/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/clips/shared/feature-flags.ts +1 -1
- package/corpus/templates/content/.agents/skills/content/SKILL.md +19 -5
- package/corpus/templates/content/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/content/changelog/2026-07-17-slack-created-database-entries-now-record-slack-as-their-sub.md +6 -0
- package/corpus/templates/design/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/design/actions/update-file.ts +10 -1
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +30 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +14 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +547 -50
- package/corpus/templates/design/app/components/design/dnd-debug.ts +27 -0
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +19 -2
- package/corpus/templates/design/app/i18n-data.ts +19 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +46 -2
- package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +4 -9
- package/corpus/templates/design/app/pages/design-editor/history.ts +14 -1
- package/corpus/templates/design/changelog/2026-07-17-drawn-shapes-stay-free-positioned-when-dragged.md +6 -0
- package/corpus/templates/design/changelog/2026-07-17-shapes-show-a-live-outline-while-you-draw.md +6 -0
- package/corpus/templates/design/shared/drag-reflow.ts +375 -0
- package/corpus/templates/design/shared/full-app.ts +1 -1
- package/corpus/templates/dispatch/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/macros/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/plan/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/slides/.agents/skills/feature-flags/SKILL.md +1 -1
- package/corpus/templates/tasks/.agents/skills/feature-flags/SKILL.md +1 -1
- package/dist/a2a/artifact-response.d.ts +5 -0
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +20 -5
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/a2a/client.d.ts +3 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +2 -0
- package/dist/a2a/client.js.map +1 -1
- package/dist/a2a/handlers.d.ts.map +1 -1
- package/dist/a2a/handlers.js +100 -8
- package/dist/a2a/handlers.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/a2a/types.d.ts +12 -0
- package/dist/a2a/types.d.ts.map +1 -1
- package/dist/a2a/types.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/request-telemetry.d.ts.map +1 -1
- package/dist/db/request-telemetry.js +24 -6
- package/dist/db/request-telemetry.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/index.d.ts +1 -0
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js +1 -0
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts +9 -0
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +60 -0
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +13 -7
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/scripts/call-agent.d.ts.map +1 -1
- package/dist/scripts/call-agent.js +39 -10
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-discovery.d.ts +2 -0
- package/dist/server/agent-discovery.d.ts.map +1 -1
- package/dist/server/agent-discovery.js +19 -0
- package/dist/server/agent-discovery.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/settings/store.d.ts +1 -1
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +14 -6
- package/dist/settings/store.js.map +1 -1
- package/dist/shared/optional-node-builtins.d.ts +23 -0
- package/dist/shared/optional-node-builtins.d.ts.map +1 -0
- package/dist/shared/optional-node-builtins.js +35 -0
- package/dist/shared/optional-node-builtins.js.map +1 -0
- package/dist/templates/chat/.agents/skills/feature-flags/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/feature-flags/SKILL.md +1 -1
- package/dist/templates/headless/.agents/skills/feature-flags/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +2 -1
- package/src/a2a/artifact-response.ts +42 -16
- package/src/a2a/client.ts +4 -0
- package/src/a2a/handlers.ts +133 -2
- package/src/a2a/index.ts +1 -0
- package/src/a2a/types.ts +14 -0
- package/src/db/request-telemetry.ts +32 -8
- package/src/integrations/index.ts +6 -0
- package/src/integrations/pending-tasks-store.ts +89 -0
- package/src/integrations/webhook-handler.ts +17 -7
- package/src/scripts/call-agent.ts +54 -10
- package/src/server/agent-discovery.ts +22 -0
- package/src/settings/store.ts +15 -6
- package/src/shared/optional-node-builtins.ts +66 -0
- package/src/templates/chat/.agents/skills/feature-flags/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/feature-flags/SKILL.md +1 -1
- package/src/templates/headless/.agents/skills/feature-flags/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +1 -1
- package/src/vite/client.ts +5 -0
package/corpus/README.md
CHANGED
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.114.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f3dcee3: Suppress provisional artifact warnings while delegated work is still running, and preserve verified Slack provenance for cross-app intake through an audience-scoped, fail-closed resolver.
|
|
8
|
+
|
|
9
|
+
## 0.114.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- c47eb50: fix(core): keep feature-flag definitions out of the browser server graph
|
|
14
|
+
|
|
15
|
+
App-shared config that imported feature-flag definitions from
|
|
16
|
+
`@agent-native/core/feature-flags` pulled the barrel's server re-exports
|
|
17
|
+
(`store` → `settings/store` → `db/client` → request telemetry) into the client
|
|
18
|
+
dev graph. Vite's dev server does not tree-shake, so the browser evaluated that
|
|
19
|
+
server chain and `request-telemetry`'s top-level `new AsyncLocalStorage()` threw
|
|
20
|
+
against the externalized `node:async_hooks` stub, breaking app load in dev
|
|
21
|
+
(production tree-shakes it away and was unaffected).
|
|
22
|
+
- Add a client-safe `@agent-native/core/feature-flags/registry` entry for
|
|
23
|
+
`defineFeatureFlag` / `defineFeatureFlags` / `registerFeatureFlags` so shared
|
|
24
|
+
config no longer imports the server barrel.
|
|
25
|
+
- Make `db/request-telemetry` and `settings/store` resolve `AsyncLocalStorage` /
|
|
26
|
+
`EventEmitter` lazily via `process.getBuiltinModule` (matching
|
|
27
|
+
`server/request-context`) instead of a top-level value import, so the modules
|
|
28
|
+
can be evaluated in any runtime without tripping the browser stub.
|
|
29
|
+
- Add a regression guard asserting the client-safe registry entry never reaches
|
|
30
|
+
the server layer and that those modules never statically value-import the
|
|
31
|
+
builtins.
|
|
32
|
+
|
|
3
33
|
## 0.114.2
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.114.
|
|
3
|
+
"version": "0.114.4",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -159,6 +159,7 @@
|
|
|
159
159
|
"./application-state": "./dist/application-state/index.js",
|
|
160
160
|
"./settings": "./dist/settings/index.js",
|
|
161
161
|
"./feature-flags": "./dist/feature-flags/index.js",
|
|
162
|
+
"./feature-flags/registry": "./dist/feature-flags/registry.js",
|
|
162
163
|
"./feature-flags/actions/get-feature-flags": "./dist/feature-flags/actions/get-feature-flags.js",
|
|
163
164
|
"./feature-flags/actions/list-feature-flags": "./dist/feature-flags/actions/list-feature-flags.js",
|
|
164
165
|
"./feature-flags/actions/set-feature-flag": "./dist/feature-flags/actions/set-feature-flag.js",
|
|
@@ -14,6 +14,11 @@ export interface A2AArtifactResponseOptions {
|
|
|
14
14
|
persistedArtifactSecret?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface GuardedA2AArtifactResponse {
|
|
18
|
+
text: string;
|
|
19
|
+
rejectedUnverifiedArtifactReferences: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
export interface A2AArtifactIdentityOptions {
|
|
18
23
|
persistedArtifactSecrets?: readonly string[];
|
|
19
24
|
}
|
|
@@ -1395,11 +1400,11 @@ function formatUnverifiedArtifactMessage(
|
|
|
1395
1400
|
: message;
|
|
1396
1401
|
}
|
|
1397
1402
|
|
|
1398
|
-
export function
|
|
1403
|
+
export function guardA2AArtifactResponse(
|
|
1399
1404
|
responseText: string,
|
|
1400
1405
|
toolResults: A2AToolResultSummary[],
|
|
1401
1406
|
options: A2AArtifactResponseOptions = {},
|
|
1402
|
-
):
|
|
1407
|
+
): GuardedA2AArtifactResponse {
|
|
1403
1408
|
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
1404
1409
|
const includeReferencedArtifacts =
|
|
1405
1410
|
options.includeReferencedArtifacts ?? false;
|
|
@@ -1440,7 +1445,10 @@ export function appendA2AArtifactLinks(
|
|
|
1440
1445
|
) ||
|
|
1441
1446
|
/\b(?:done|created|ready|here(?:'s| is)|complete|finished)\b/i.test(text))
|
|
1442
1447
|
) {
|
|
1443
|
-
return
|
|
1448
|
+
return {
|
|
1449
|
+
text: finalize(formatIncompleteDesignMessage(incompleteShells)),
|
|
1450
|
+
rejectedUnverifiedArtifactReferences: false,
|
|
1451
|
+
};
|
|
1444
1452
|
}
|
|
1445
1453
|
|
|
1446
1454
|
const unverifiedRefs = findUnverifiedArtifactReferences(
|
|
@@ -1454,18 +1462,21 @@ export function appendA2AArtifactLinks(
|
|
|
1454
1462
|
generatedDesigns,
|
|
1455
1463
|
);
|
|
1456
1464
|
if (unverifiedRefs.length > 0) {
|
|
1457
|
-
return
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1465
|
+
return {
|
|
1466
|
+
text: finalize(
|
|
1467
|
+
formatUnverifiedArtifactMessage(
|
|
1468
|
+
unverifiedRefs,
|
|
1469
|
+
documents,
|
|
1470
|
+
decks,
|
|
1471
|
+
dashboards,
|
|
1472
|
+
analyses,
|
|
1473
|
+
images,
|
|
1474
|
+
generatedDesigns,
|
|
1475
|
+
baseUrl,
|
|
1476
|
+
),
|
|
1467
1477
|
),
|
|
1468
|
-
|
|
1478
|
+
rejectedUnverifiedArtifactReferences: true,
|
|
1479
|
+
};
|
|
1469
1480
|
}
|
|
1470
1481
|
|
|
1471
1482
|
const missingLines: string[] = [];
|
|
@@ -1541,10 +1552,25 @@ export function appendA2AArtifactLinks(
|
|
|
1541
1552
|
}
|
|
1542
1553
|
|
|
1543
1554
|
if (missingLines.length === 0) {
|
|
1544
|
-
return
|
|
1555
|
+
return {
|
|
1556
|
+
text: finalize(text),
|
|
1557
|
+
rejectedUnverifiedArtifactReferences: false,
|
|
1558
|
+
};
|
|
1545
1559
|
}
|
|
1560
|
+
|
|
1546
1561
|
const artifactBlock = `Artifacts:\n${missingLines.join("\n")}`;
|
|
1547
|
-
return
|
|
1562
|
+
return {
|
|
1563
|
+
text: finalize(text ? `${text}\n\n${artifactBlock}` : artifactBlock),
|
|
1564
|
+
rejectedUnverifiedArtifactReferences: false,
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
export function appendA2AArtifactLinks(
|
|
1569
|
+
responseText: string,
|
|
1570
|
+
toolResults: A2AToolResultSummary[],
|
|
1571
|
+
options: A2AArtifactResponseOptions = {},
|
|
1572
|
+
): string {
|
|
1573
|
+
return guardA2AArtifactResponse(responseText, toolResults, options).text;
|
|
1548
1574
|
}
|
|
1549
1575
|
|
|
1550
1576
|
export function buildA2ARecoverableArtifactMessage(
|
|
@@ -5,6 +5,7 @@ import { sanitizeA2ACorrelationMetadata } from "./correlation.js";
|
|
|
5
5
|
import type {
|
|
6
6
|
A2AApprovedAction,
|
|
7
7
|
A2ACorrelationMetadata,
|
|
8
|
+
A2ASourceContextReference,
|
|
8
9
|
A2AReadOnlyActionResult,
|
|
9
10
|
AgentCard,
|
|
10
11
|
JsonRpcRequest,
|
|
@@ -632,6 +633,8 @@ export async function callAgent(
|
|
|
632
633
|
requestOrigin?: string;
|
|
633
634
|
/** Exact downstream actions explicitly authorized in the caller's chat. */
|
|
634
635
|
approvedActions?: A2AApprovedAction[];
|
|
636
|
+
/** Opaque provenance reference resolved by the receiver through Dispatch. */
|
|
637
|
+
sourceContext?: A2ASourceContextReference;
|
|
635
638
|
/** Bounded telemetry-only lineage forwarded to the receiving app. */
|
|
636
639
|
correlation?: A2ACorrelationMetadata;
|
|
637
640
|
/** Stable caller-generated key for one message submission. */
|
|
@@ -673,6 +676,7 @@ export async function callAgent(
|
|
|
673
676
|
if (opts?.userEmail) metadata.userEmail = opts.userEmail;
|
|
674
677
|
if (opts?.orgDomain) metadata.orgDomain = opts.orgDomain;
|
|
675
678
|
if (opts?.requestOrigin) metadata.requestOrigin = opts.requestOrigin;
|
|
679
|
+
if (opts?.sourceContext) metadata.sourceContext = opts.sourceContext;
|
|
676
680
|
Object.assign(metadata, sanitizeA2ACorrelationMetadata(opts?.correlation));
|
|
677
681
|
|
|
678
682
|
// Default to async + poll. The receiving A2A server's `_process-task` route
|
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
resolveAgentChatProcessRunDispatchPath,
|
|
11
11
|
} from "../agent/durable-background.js";
|
|
12
12
|
import { trackingIdentityProperties } from "../observability/tracking-identity.js";
|
|
13
|
+
import { getA2ASecretByDomain } from "../org/context.js";
|
|
14
|
+
import { findWorkspaceDispatchAgent } from "../server/agent-discovery.js";
|
|
13
15
|
import { withConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
14
16
|
import { getOrigin, isConfiguredAppOrigin } from "../server/google-oauth.js";
|
|
15
17
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
hasConfiguredA2ASecret,
|
|
20
22
|
isA2AProductionRuntime,
|
|
21
23
|
} from "./auth-policy.js";
|
|
24
|
+
import { callAction } from "./client.js";
|
|
22
25
|
import { sanitizeA2ACorrelationMetadata } from "./correlation.js";
|
|
23
26
|
import {
|
|
24
27
|
createTask,
|
|
@@ -39,6 +42,8 @@ import {
|
|
|
39
42
|
} from "./task-store.js";
|
|
40
43
|
import type {
|
|
41
44
|
A2AApprovedAction,
|
|
45
|
+
A2ASourceContext,
|
|
46
|
+
A2ASourceContextReference,
|
|
42
47
|
A2AConfig,
|
|
43
48
|
A2AHandler,
|
|
44
49
|
A2AHandlerContext,
|
|
@@ -84,6 +89,107 @@ function trustedApprovedActions(
|
|
|
84
89
|
return approved.length > 0 ? approved : undefined;
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
function sourceContextReference(
|
|
93
|
+
value: unknown,
|
|
94
|
+
): A2ASourceContextReference | undefined {
|
|
95
|
+
if (!value || typeof value !== "object") {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
const candidate = value as Record<string, unknown>;
|
|
99
|
+
if (
|
|
100
|
+
candidate.platform !== "slack" ||
|
|
101
|
+
typeof candidate.integrationTaskId !== "string"
|
|
102
|
+
) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const integrationTaskId = candidate.integrationTaskId;
|
|
106
|
+
if (
|
|
107
|
+
!integrationTaskId ||
|
|
108
|
+
integrationTaskId !== integrationTaskId.trim() ||
|
|
109
|
+
integrationTaskId.length > 200
|
|
110
|
+
) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
return { platform: "slack", integrationTaskId };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function resolvedSlackSourceContext(
|
|
117
|
+
value: unknown,
|
|
118
|
+
): A2ASourceContext | undefined {
|
|
119
|
+
if (!value || typeof value !== "object") return undefined;
|
|
120
|
+
const candidate = value as Record<string, unknown>;
|
|
121
|
+
if (
|
|
122
|
+
candidate.platform !== "slack" ||
|
|
123
|
+
typeof candidate.sourceUrl !== "string"
|
|
124
|
+
) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
const sourceUrl = candidate.sourceUrl;
|
|
128
|
+
if (!sourceUrl || sourceUrl !== sourceUrl.trim()) return undefined;
|
|
129
|
+
try {
|
|
130
|
+
const parsed = new URL(sourceUrl);
|
|
131
|
+
const isSlackHost =
|
|
132
|
+
parsed.hostname === "slack.com" || parsed.hostname.endsWith(".slack.com");
|
|
133
|
+
if (
|
|
134
|
+
parsed.protocol !== "https:" ||
|
|
135
|
+
!isSlackHost ||
|
|
136
|
+
parsed.username ||
|
|
137
|
+
parsed.password ||
|
|
138
|
+
parsed.port
|
|
139
|
+
) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
return { platform: "slack", sourceUrl };
|
|
143
|
+
} catch {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function trustedSourceContext(
|
|
149
|
+
value: unknown,
|
|
150
|
+
event: any | undefined,
|
|
151
|
+
): Promise<A2ASourceContext | undefined> {
|
|
152
|
+
const verifiedEmail = event?.context?.__a2aVerifiedEmail as
|
|
153
|
+
| string
|
|
154
|
+
| undefined;
|
|
155
|
+
const reference = sourceContextReference(value);
|
|
156
|
+
if (
|
|
157
|
+
!verifiedEmail ||
|
|
158
|
+
event?.context?.__a2aAudienceVerified !== true ||
|
|
159
|
+
!reference
|
|
160
|
+
) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const dispatch = findWorkspaceDispatchAgent();
|
|
165
|
+
if (!dispatch) return undefined;
|
|
166
|
+
const orgDomain = event?.context?.__a2aOrgDomain as string | undefined;
|
|
167
|
+
let orgSecret: string | undefined;
|
|
168
|
+
if (orgDomain) {
|
|
169
|
+
try {
|
|
170
|
+
orgSecret = (await getA2ASecretByDomain(orgDomain)) ?? undefined;
|
|
171
|
+
} catch {}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const result = await callAction(
|
|
176
|
+
dispatch.url,
|
|
177
|
+
"resolve-integration-source-context",
|
|
178
|
+
{ integrationTaskId: reference.integrationTaskId },
|
|
179
|
+
{
|
|
180
|
+
userEmail: verifiedEmail,
|
|
181
|
+
orgDomain,
|
|
182
|
+
orgSecret,
|
|
183
|
+
requestTimeoutMs: 5_000,
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
if (result.status !== "completed") return undefined;
|
|
187
|
+
return resolvedSlackSourceContext(JSON.parse(result.output));
|
|
188
|
+
} catch {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
87
193
|
/**
|
|
88
194
|
* Request origin is routing/link context, not an identity signal. Accept only
|
|
89
195
|
* an absolute HTTP(S) origin from caller metadata so queued runs can preserve
|
|
@@ -142,6 +248,7 @@ function trustedA2AMetadata(
|
|
|
142
248
|
): Record<string, unknown> | undefined {
|
|
143
249
|
if (!metadata) return undefined;
|
|
144
250
|
const trusted = { ...metadata };
|
|
251
|
+
delete trusted.sourceContext;
|
|
145
252
|
const requestOrigin = requestOriginForContext(metadata, event);
|
|
146
253
|
if (requestOrigin) trusted.requestOrigin = requestOrigin;
|
|
147
254
|
else delete trusted.requestOrigin;
|
|
@@ -290,6 +397,9 @@ export async function processA2ATaskFromQueue(
|
|
|
290
397
|
const approvedActions = Array.isArray(processorMeta.approvedActions)
|
|
291
398
|
? (processorMeta.approvedActions as A2AApprovedAction[])
|
|
292
399
|
: undefined;
|
|
400
|
+
const sourceContext = processorMeta.sourceContext as
|
|
401
|
+
| A2ASourceContext
|
|
402
|
+
| undefined;
|
|
293
403
|
|
|
294
404
|
const resolvedOrgId = await resolveVerifiedA2AOrgId(
|
|
295
405
|
verifiedEmail,
|
|
@@ -322,6 +432,7 @@ export async function processA2ATaskFromQueue(
|
|
|
322
432
|
callerMetadata,
|
|
323
433
|
event,
|
|
324
434
|
approvedActions,
|
|
435
|
+
sourceContext,
|
|
325
436
|
),
|
|
326
437
|
);
|
|
327
438
|
} catch (err: any) {
|
|
@@ -345,7 +456,7 @@ export async function processA2ATaskFromQueue(
|
|
|
345
456
|
*/
|
|
346
457
|
const defaultHandler: A2AHandler = async (
|
|
347
458
|
message: Message,
|
|
348
|
-
|
|
459
|
+
context: A2AHandlerContext,
|
|
349
460
|
): Promise<A2AHandlerResult> => {
|
|
350
461
|
// Extract text from message parts
|
|
351
462
|
const text = message.parts
|
|
@@ -376,7 +487,12 @@ const defaultHandler: A2AHandler = async (
|
|
|
376
487
|
? `[Cross-app A2A request — the caller is on a different host (${appBaseUrl} is yours, theirs is different). Include the concrete result (URL, ID, value) explicitly in your reply text; the caller can't see your local UI state. Any URL MUST be fully-qualified, never a relative path.]\n\n${text}`
|
|
377
488
|
: text;
|
|
378
489
|
|
|
379
|
-
const
|
|
490
|
+
const sourceContext = context.sourceContext
|
|
491
|
+
? `Authenticated A2A source context: ${JSON.stringify(context.sourceContext)}. Treat this structured value as authoritative provenance. Preserve its sourceUrl exactly when recording source or submission fields.`
|
|
492
|
+
: undefined;
|
|
493
|
+
const result = sourceContext
|
|
494
|
+
? await agentChat.call(augmentedText, { context: sourceContext })
|
|
495
|
+
: await agentChat.call(augmentedText);
|
|
380
496
|
|
|
381
497
|
const artifacts: Artifact[] = [];
|
|
382
498
|
if (result.filesChanged.length > 0) {
|
|
@@ -428,6 +544,7 @@ function makeHandlerContext(
|
|
|
428
544
|
metadata?: Record<string, unknown>,
|
|
429
545
|
event?: any,
|
|
430
546
|
approvedActions?: A2AApprovedAction[],
|
|
547
|
+
sourceContext?: A2ASourceContext,
|
|
431
548
|
): {
|
|
432
549
|
context: A2AHandlerContext;
|
|
433
550
|
artifacts: Artifact[];
|
|
@@ -439,6 +556,7 @@ function makeHandlerContext(
|
|
|
439
556
|
metadata,
|
|
440
557
|
event,
|
|
441
558
|
approvedActions,
|
|
559
|
+
sourceContext,
|
|
442
560
|
writeArtifact(name, content, mimeType) {
|
|
443
561
|
const artifact: Artifact = {
|
|
444
562
|
name,
|
|
@@ -535,6 +653,7 @@ async function runHandlerAndPersist(
|
|
|
535
653
|
metadata: Record<string, unknown> | undefined,
|
|
536
654
|
event?: any,
|
|
537
655
|
approvedActions?: A2AApprovedAction[],
|
|
656
|
+
sourceContext?: A2ASourceContext,
|
|
538
657
|
): Promise<void> {
|
|
539
658
|
const { context, artifacts } = makeHandlerContext(
|
|
540
659
|
taskId,
|
|
@@ -542,6 +661,7 @@ async function runHandlerAndPersist(
|
|
|
542
661
|
metadata,
|
|
543
662
|
event,
|
|
544
663
|
approvedActions,
|
|
664
|
+
sourceContext,
|
|
545
665
|
);
|
|
546
666
|
try {
|
|
547
667
|
const result = getHandler(config)(message, context);
|
|
@@ -625,6 +745,10 @@ async function handleSend(
|
|
|
625
745
|
const contextId = params.contextId as string | undefined;
|
|
626
746
|
const metadata = params.metadata as Record<string, unknown> | undefined;
|
|
627
747
|
const approvedActions = trustedApprovedActions(params.approvedActions, event);
|
|
748
|
+
const sourceContext = await trustedSourceContext(
|
|
749
|
+
metadata?.sourceContext,
|
|
750
|
+
event,
|
|
751
|
+
);
|
|
628
752
|
|
|
629
753
|
// The JWT-verified caller email (set by mountA2A in server.ts) is the
|
|
630
754
|
// single source of truth for task ownership — bound at creation, checked
|
|
@@ -714,6 +838,7 @@ async function handleSend(
|
|
|
714
838
|
contextId: contextId ?? null,
|
|
715
839
|
callerMetadata: safeMetadata ?? null,
|
|
716
840
|
approvedActions: approvedActions ?? null,
|
|
841
|
+
sourceContext: sourceContext ?? null,
|
|
717
842
|
},
|
|
718
843
|
};
|
|
719
844
|
const { task, reused } = await createOrReuseTask(
|
|
@@ -774,6 +899,7 @@ async function handleSend(
|
|
|
774
899
|
trustedA2AMetadata(metadata, event),
|
|
775
900
|
event,
|
|
776
901
|
approvedActions,
|
|
902
|
+
sourceContext,
|
|
777
903
|
);
|
|
778
904
|
|
|
779
905
|
try {
|
|
@@ -855,6 +981,10 @@ async function handleStream(
|
|
|
855
981
|
const contextId = params.contextId as string | undefined;
|
|
856
982
|
const metadata = params.metadata as Record<string, unknown> | undefined;
|
|
857
983
|
const approvedActions = trustedApprovedActions(params.approvedActions, event);
|
|
984
|
+
const sourceContext = await trustedSourceContext(
|
|
985
|
+
metadata?.sourceContext,
|
|
986
|
+
event,
|
|
987
|
+
);
|
|
858
988
|
const { ownerEmail: ownerEmailForTask, ownerScope: ownerScopeForTask } =
|
|
859
989
|
verifiedTaskOwner(event);
|
|
860
990
|
|
|
@@ -875,6 +1005,7 @@ async function handleStream(
|
|
|
875
1005
|
trustedA2AMetadata(metadata, event),
|
|
876
1006
|
event,
|
|
877
1007
|
approvedActions,
|
|
1008
|
+
sourceContext,
|
|
878
1009
|
);
|
|
879
1010
|
|
|
880
1011
|
try {
|
|
@@ -132,6 +132,18 @@ export interface A2AApprovedAction {
|
|
|
132
132
|
input: unknown;
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/** Structured provenance accepted only from an authenticated A2A caller. */
|
|
136
|
+
export interface A2ASourceContext {
|
|
137
|
+
platform: "slack";
|
|
138
|
+
sourceUrl: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Opaque reference that a receiver must resolve through its trusted Dispatch app. */
|
|
142
|
+
export interface A2ASourceContextReference {
|
|
143
|
+
platform: "slack";
|
|
144
|
+
integrationTaskId: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
135
147
|
/**
|
|
136
148
|
* Telemetry-only cross-app correlation. Receivers must never use these
|
|
137
149
|
* caller-supplied values for identity, ownership, org scoping, access, or
|
|
@@ -156,6 +168,8 @@ export interface A2AHandlerContext {
|
|
|
156
168
|
event?: unknown;
|
|
157
169
|
/** Exact one-time action grants from a JWT-authenticated caller. */
|
|
158
170
|
approvedActions?: A2AApprovedAction[];
|
|
171
|
+
/** Receiver-validated provenance from a JWT-authenticated caller. */
|
|
172
|
+
sourceContext?: A2ASourceContext;
|
|
159
173
|
writeArtifact: (name: string, content: string, mimeType?: string) => string;
|
|
160
174
|
}
|
|
161
175
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAsyncLocalStorageCtor } from "../shared/optional-node-builtins.js";
|
|
2
2
|
|
|
3
3
|
export type DatabaseOperationKind = "connect" | "query";
|
|
4
4
|
|
|
@@ -30,15 +30,39 @@ interface StartupDatabaseTelemetryState {
|
|
|
30
30
|
claimed: boolean;
|
|
31
31
|
telemetry: DatabaseRequestTelemetry;
|
|
32
32
|
}
|
|
33
|
+
interface TelemetryStorage {
|
|
34
|
+
getStore(): DatabaseRequestTelemetry | undefined;
|
|
35
|
+
run<T>(store: DatabaseRequestTelemetry, fn: () => T): T;
|
|
36
|
+
enterWith(store: DatabaseRequestTelemetry): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
type GlobalWithDatabaseTelemetry = typeof globalThis & {
|
|
34
|
-
[STORAGE_KEY]?:
|
|
40
|
+
[STORAGE_KEY]?: TelemetryStorage;
|
|
35
41
|
[STARTUP_STATE_KEY]?: StartupDatabaseTelemetryState;
|
|
36
42
|
};
|
|
37
43
|
|
|
38
44
|
const globalRef = globalThis as GlobalWithDatabaseTelemetry;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
|
|
46
|
+
// AsyncLocalStorage is resolved lazily, never at module load: this module can
|
|
47
|
+
// land in the browser dev graph, where a top-level `new AsyncLocalStorage()`
|
|
48
|
+
// would throw against Vite's externalized `node:async_hooks` stub. On non-Node
|
|
49
|
+
// runtimes telemetry no-ops.
|
|
50
|
+
const NOOP_STORAGE: TelemetryStorage = {
|
|
51
|
+
getStore: () => undefined,
|
|
52
|
+
run: (_store, fn) => fn(),
|
|
53
|
+
enterWith: () => {},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function getStorage(): TelemetryStorage {
|
|
57
|
+
const existing = globalRef[STORAGE_KEY];
|
|
58
|
+
if (existing) return existing;
|
|
59
|
+
const Ctor = getAsyncLocalStorageCtor();
|
|
60
|
+
const created: TelemetryStorage = Ctor
|
|
61
|
+
? new Ctor<DatabaseRequestTelemetry>()
|
|
62
|
+
: NOOP_STORAGE;
|
|
63
|
+
globalRef[STORAGE_KEY] = created;
|
|
64
|
+
return created;
|
|
65
|
+
}
|
|
42
66
|
|
|
43
67
|
export function createDatabaseRequestTelemetry(): DatabaseRequestTelemetry {
|
|
44
68
|
return {
|
|
@@ -66,7 +90,7 @@ const startupState =
|
|
|
66
90
|
});
|
|
67
91
|
|
|
68
92
|
function currentDatabaseTelemetry(): DatabaseRequestTelemetry | undefined {
|
|
69
|
-
const requestTelemetry =
|
|
93
|
+
const requestTelemetry = getStorage().getStore();
|
|
70
94
|
if (requestTelemetry) return requestTelemetry;
|
|
71
95
|
if (!startupState.claimed && Date.now() <= startupState.captureUntil) {
|
|
72
96
|
return startupState.telemetry;
|
|
@@ -86,13 +110,13 @@ export function runWithDatabaseRequestTelemetry<T>(
|
|
|
86
110
|
telemetry: DatabaseRequestTelemetry,
|
|
87
111
|
fn: () => T,
|
|
88
112
|
): T {
|
|
89
|
-
return
|
|
113
|
+
return getStorage().run(telemetry, fn);
|
|
90
114
|
}
|
|
91
115
|
|
|
92
116
|
export function enterDatabaseRequestTelemetry(
|
|
93
117
|
telemetry: DatabaseRequestTelemetry,
|
|
94
118
|
): void {
|
|
95
|
-
|
|
119
|
+
getStorage().enterWith(telemetry);
|
|
96
120
|
}
|
|
97
121
|
|
|
98
122
|
export function beginDatabaseOperation(
|
|
@@ -195,6 +195,12 @@ export type {
|
|
|
195
195
|
IntegrationScopePolicy,
|
|
196
196
|
} from "./scope-store.js";
|
|
197
197
|
|
|
198
|
+
export {
|
|
199
|
+
resolveIntegrationSourceContext,
|
|
200
|
+
sourceContextFromPendingTask,
|
|
201
|
+
type ResolvedIntegrationSourceContext,
|
|
202
|
+
} from "./pending-tasks-store.js";
|
|
203
|
+
|
|
198
204
|
export {
|
|
199
205
|
getIntegrationBudgetSnapshot,
|
|
200
206
|
getIntegrationUsageBudget,
|
|
@@ -246,6 +246,95 @@ export async function getPendingTask(id: string): Promise<PendingTask | null> {
|
|
|
246
246
|
return rowToTask(rows[0] as Record<string, unknown>);
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
export interface ResolvedIntegrationSourceContext {
|
|
250
|
+
platform: "slack";
|
|
251
|
+
sourceUrl: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
type PendingTaskSourceRow = Pick<
|
|
255
|
+
PendingTask,
|
|
256
|
+
"platform" | "payload" | "ownerEmail" | "orgId"
|
|
257
|
+
>;
|
|
258
|
+
|
|
259
|
+
export function sourceContextFromPendingTask(
|
|
260
|
+
task: PendingTaskSourceRow | null,
|
|
261
|
+
ownerEmail: string,
|
|
262
|
+
orgId: string | null,
|
|
263
|
+
): ResolvedIntegrationSourceContext | null {
|
|
264
|
+
if (
|
|
265
|
+
!task ||
|
|
266
|
+
task.ownerEmail !== ownerEmail ||
|
|
267
|
+
task.orgId !== orgId ||
|
|
268
|
+
task.platform !== "slack"
|
|
269
|
+
) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const payload = JSON.parse(task.payload) as Record<string, unknown>;
|
|
275
|
+
const incoming = payload.incoming;
|
|
276
|
+
if (!incoming || typeof incoming !== "object") return null;
|
|
277
|
+
const incomingRecord = incoming as Record<string, unknown>;
|
|
278
|
+
if (incomingRecord.platform !== "slack") return null;
|
|
279
|
+
const sourceUrl = incomingRecord.sourceUrl;
|
|
280
|
+
if (
|
|
281
|
+
typeof sourceUrl !== "string" ||
|
|
282
|
+
!sourceUrl ||
|
|
283
|
+
sourceUrl !== sourceUrl.trim()
|
|
284
|
+
) {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const parsed = new URL(sourceUrl);
|
|
289
|
+
const isSlackHost =
|
|
290
|
+
parsed.hostname === "slack.com" || parsed.hostname.endsWith(".slack.com");
|
|
291
|
+
if (
|
|
292
|
+
parsed.protocol !== "https:" ||
|
|
293
|
+
!isSlackHost ||
|
|
294
|
+
parsed.username ||
|
|
295
|
+
parsed.password ||
|
|
296
|
+
parsed.port
|
|
297
|
+
) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
return { platform: "slack", sourceUrl };
|
|
301
|
+
} catch {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Resolve trusted Slack provenance without exposing the stored task payload. */
|
|
307
|
+
export async function resolveIntegrationSourceContext(
|
|
308
|
+
id: string,
|
|
309
|
+
ownerEmail: string,
|
|
310
|
+
orgId: string | null,
|
|
311
|
+
): Promise<ResolvedIntegrationSourceContext | null> {
|
|
312
|
+
await ensureTable();
|
|
313
|
+
const client = getDbExec();
|
|
314
|
+
const { rows } = await client.execute({
|
|
315
|
+
sql: `SELECT platform, payload, owner_email, org_id
|
|
316
|
+
FROM integration_pending_tasks
|
|
317
|
+
WHERE id = ?
|
|
318
|
+
AND owner_email = ?
|
|
319
|
+
AND (org_id = ? OR (org_id IS NULL AND ? IS NULL))
|
|
320
|
+
AND platform = 'slack'
|
|
321
|
+
LIMIT 1`,
|
|
322
|
+
args: [id, ownerEmail, orgId, orgId],
|
|
323
|
+
});
|
|
324
|
+
if (rows.length === 0) return null;
|
|
325
|
+
const row = rows[0] as Record<string, unknown>;
|
|
326
|
+
return sourceContextFromPendingTask(
|
|
327
|
+
{
|
|
328
|
+
platform: row.platform as string,
|
|
329
|
+
payload: row.payload as string,
|
|
330
|
+
ownerEmail: row.owner_email as string,
|
|
331
|
+
orgId: (row.org_id as string | null) ?? null,
|
|
332
|
+
},
|
|
333
|
+
ownerEmail,
|
|
334
|
+
orgId,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
249
338
|
/**
|
|
250
339
|
* Atomically claim a task: transition pending → processing and increment
|
|
251
340
|
* attempts. Returns the updated task if the transition succeeded, otherwise
|