@agent-native/core 0.154.1 → 0.154.2
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 +1 -1
- package/corpus/templates/analytics/changelog/2026-08-12-feature-flags-can-be-managed-across-apps-whose-workspace-rec.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-12-kept-feature-flag-details-readable-by-stacking-rollout-contr.md +6 -0
- package/corpus/templates/analytics/scripts/preview-dashboard-report.ts +16 -5
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +98 -31
- package/corpus/templates/dispatch/changelog/2026-08-12-dispatch-now-appears-in-authorized-workspace-app-directories.md +6 -0
- package/corpus/templates/dispatch/server/lib/org-apps-directory.ts +3 -4
- package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +15 -3
- package/dist/a2a-claims.d.ts +1 -0
- package/dist/a2a-claims.js +3 -1
- package/dist/client/feature-flags/FeatureFlagsPanel.js +2 -2
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/feature-flags/a2a-action-route.js +10 -1
- package/dist/feature-flags/actions/set-feature-flag.d.ts +2 -1
- package/dist/feature-flags/actions/set-feature-flag.js +9 -2
- package/dist/mcp/org-directory.d.ts +3 -0
- package/dist/mcp/org-directory.js +15 -5
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/server/action-routes.js +10 -2
- package/dist/server/agent-discovery.d.ts +6 -2
- package/dist/server/agent-discovery.js +16 -9
- package/dist/server/identity-sso-store.d.ts +5 -4
- package/dist/server/identity-sso-store.js +5 -4
- package/package.json +3 -3
package/corpus/README.md
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
22
|
-
import { join } from "node:path";
|
|
22
|
+
import path, { join } from "node:path";
|
|
23
|
+
import { pathToFileURL } from "node:url";
|
|
23
24
|
|
|
24
25
|
import { runWithRequestContext } from "@agent-native/core/server/request-context";
|
|
25
26
|
import { eq } from "drizzle-orm";
|
|
@@ -215,7 +216,17 @@ async function renderPreview(
|
|
|
215
216
|
if (rendered.degradedPanelIds.length) process.exitCode = 1;
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
219
|
+
function isDirectRun(): boolean {
|
|
220
|
+
const entrypoint = process.argv[1];
|
|
221
|
+
return Boolean(
|
|
222
|
+
entrypoint &&
|
|
223
|
+
import.meta.url === pathToFileURL(path.resolve(entrypoint)).href,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (isDirectRun()) {
|
|
228
|
+
main().catch((err) => {
|
|
229
|
+
console.error("[preview] failed:", err);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
|
|
3
3
|
import { signA2AToken } from "@agent-native/core/a2a";
|
|
4
4
|
import { fetchOrgApps, type OrgApp } from "@agent-native/core/mcp";
|
|
5
|
+
import { getOrgDomain } from "@agent-native/core/org";
|
|
5
6
|
|
|
6
7
|
import type { AnalyticsAdminContext } from "./db-admin-connections.js";
|
|
7
8
|
|
|
@@ -31,11 +32,11 @@ export interface WorkspaceFeatureFlagsResult {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export interface WorkspaceFeatureFlagMutationResult {
|
|
34
|
-
contractVersion:
|
|
35
|
+
contractVersion: 2;
|
|
35
36
|
status: "ready";
|
|
36
37
|
key: string;
|
|
37
38
|
rules: Record<string, unknown>;
|
|
38
|
-
scope: { orgId: string | null };
|
|
39
|
+
scope: { orgId: string | null; orgDomain: string | null };
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
export interface WorkspaceFeatureFlagMutationInput {
|
|
@@ -66,14 +67,14 @@ export function validateWorkspaceFeatureFlagMutation(
|
|
|
66
67
|
body: unknown,
|
|
67
68
|
expected: {
|
|
68
69
|
key: string;
|
|
69
|
-
|
|
70
|
+
orgDomain: string;
|
|
70
71
|
rules?: Record<string, unknown>;
|
|
71
72
|
enabledForEmail?: string;
|
|
72
73
|
},
|
|
73
74
|
): WorkspaceFeatureFlagMutationResult {
|
|
74
75
|
const payload = body as Partial<WorkspaceFeatureFlagMutationResult> | null;
|
|
75
76
|
const valid =
|
|
76
|
-
payload?.contractVersion ===
|
|
77
|
+
payload?.contractVersion === 2 &&
|
|
77
78
|
payload.status === "ready" &&
|
|
78
79
|
payload.key === expected.key &&
|
|
79
80
|
!!payload.rules &&
|
|
@@ -81,7 +82,7 @@ export function validateWorkspaceFeatureFlagMutation(
|
|
|
81
82
|
!Array.isArray(payload.rules) &&
|
|
82
83
|
!!payload.scope &&
|
|
83
84
|
typeof payload.scope === "object" &&
|
|
84
|
-
payload.scope.
|
|
85
|
+
payload.scope.orgDomain === expected.orgDomain;
|
|
85
86
|
if (!valid)
|
|
86
87
|
throw new Error(
|
|
87
88
|
"The target app returned an unsupported or unverified feature flag mutation response.",
|
|
@@ -152,13 +153,43 @@ async function delegatedToken(
|
|
|
152
153
|
admin: AnalyticsAdminContext,
|
|
153
154
|
origin: string,
|
|
154
155
|
scope: "flags:read" | "flags:write",
|
|
156
|
+
resolvedOrgDomain?: string,
|
|
155
157
|
): Promise<string> {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
try {
|
|
159
|
+
const orgDomain =
|
|
160
|
+
resolvedOrgDomain ??
|
|
161
|
+
(await getOrgDomain(admin.orgId))?.trim().toLowerCase();
|
|
162
|
+
if (!orgDomain) throw new TargetCallFailure("token-generation");
|
|
163
|
+
return await signA2AToken(admin.userEmail, orgDomain, undefined, {
|
|
164
|
+
expiresIn: "120s",
|
|
165
|
+
preferGlobalSecret: true,
|
|
166
|
+
audience: origin,
|
|
167
|
+
extraClaims: { org_id: admin.orgId, scope, jti: randomUUID() },
|
|
168
|
+
});
|
|
169
|
+
} catch {
|
|
170
|
+
throw new TargetCallFailure("token-generation");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
type TargetFailureReason = "token-generation" | "timeout" | "network";
|
|
175
|
+
|
|
176
|
+
class TargetCallFailure extends Error {
|
|
177
|
+
constructor(readonly reason: TargetFailureReason) {
|
|
178
|
+
super(`Feature flag target call failed: ${reason}`);
|
|
179
|
+
this.name = "TargetCallFailure";
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function classifyWorkspaceFeatureFlagTargetFailure(
|
|
184
|
+
error: unknown,
|
|
185
|
+
): TargetFailureReason {
|
|
186
|
+
if (error instanceof TargetCallFailure) return error.reason;
|
|
187
|
+
if (
|
|
188
|
+
error instanceof Error &&
|
|
189
|
+
(error.name === "TimeoutError" || error.name === "AbortError")
|
|
190
|
+
)
|
|
191
|
+
return "timeout";
|
|
192
|
+
return "network";
|
|
162
193
|
}
|
|
163
194
|
|
|
164
195
|
async function callTarget(
|
|
@@ -166,25 +197,34 @@ async function callTarget(
|
|
|
166
197
|
admin: AnalyticsAdminContext,
|
|
167
198
|
action: "list-feature-flags" | "set-feature-flag",
|
|
168
199
|
body: Record<string, unknown>,
|
|
200
|
+
orgDomain?: string,
|
|
169
201
|
): Promise<{ status: number; body: unknown }> {
|
|
170
202
|
const origin = targetOrigin(app);
|
|
171
203
|
const token = await delegatedToken(
|
|
172
204
|
admin,
|
|
173
205
|
origin,
|
|
174
206
|
action === "list-feature-flags" ? "flags:read" : "flags:write",
|
|
207
|
+
orgDomain,
|
|
175
208
|
);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
209
|
+
let response: Response;
|
|
210
|
+
try {
|
|
211
|
+
response = await fetch(`${origin}/_agent-native/actions/${action}`, {
|
|
212
|
+
method: action === "list-feature-flags" ? "GET" : "POST",
|
|
213
|
+
headers: {
|
|
214
|
+
Authorization: `Bearer ${token}`,
|
|
215
|
+
Accept: "application/json",
|
|
216
|
+
...(action === "set-feature-flag"
|
|
217
|
+
? { "Content-Type": "application/json" }
|
|
218
|
+
: {}),
|
|
219
|
+
},
|
|
220
|
+
...(action === "set-feature-flag" ? { body: JSON.stringify(body) } : {}),
|
|
221
|
+
signal: AbortSignal.timeout(TARGET_TIMEOUT_MS),
|
|
222
|
+
});
|
|
223
|
+
} catch (error) {
|
|
224
|
+
throw new TargetCallFailure(
|
|
225
|
+
classifyWorkspaceFeatureFlagTargetFailure(error),
|
|
226
|
+
);
|
|
227
|
+
}
|
|
188
228
|
let parsed: unknown = null;
|
|
189
229
|
try {
|
|
190
230
|
parsed = await response.json();
|
|
@@ -209,7 +249,7 @@ export function classifyWorkspaceFeatureFlagList(
|
|
|
209
249
|
if (result.status === 404 || result.status === 405)
|
|
210
250
|
return { ...base, state: "unsupported" };
|
|
211
251
|
if (result.status < 200 || result.status >= 300)
|
|
212
|
-
return { ...base, state: "unknown-legacy" };
|
|
252
|
+
return { ...base, state: "unknown-legacy", reason: "target-execution" };
|
|
213
253
|
const payload = result.body as {
|
|
214
254
|
flags?: unknown;
|
|
215
255
|
canManage?: unknown;
|
|
@@ -254,7 +294,11 @@ async function mapBounded<T, R>(
|
|
|
254
294
|
export async function listWorkspaceFeatureFlags(
|
|
255
295
|
admin: AnalyticsAdminContext,
|
|
256
296
|
): Promise<WorkspaceFeatureFlagsResult> {
|
|
257
|
-
const apps = await fetchOrgApps({
|
|
297
|
+
const apps = await fetchOrgApps({
|
|
298
|
+
selfId: "analytics",
|
|
299
|
+
includeDirectoryApp: true,
|
|
300
|
+
serviceOrgId: admin.orgId,
|
|
301
|
+
});
|
|
258
302
|
if (apps.length === 0) return { directoryStatus: "unavailable", apps: [] };
|
|
259
303
|
const entries = await mapBounded(apps, async (app) => {
|
|
260
304
|
try {
|
|
@@ -262,13 +306,14 @@ export async function listWorkspaceFeatureFlags(
|
|
|
262
306
|
app,
|
|
263
307
|
await callTarget(app, admin, "list-feature-flags", {}),
|
|
264
308
|
);
|
|
265
|
-
} catch {
|
|
309
|
+
} catch (error) {
|
|
266
310
|
return {
|
|
267
311
|
appId: app.id,
|
|
268
312
|
appName: app.name,
|
|
269
313
|
appOrigin: targetOrigin(app),
|
|
270
314
|
state: "unreachable" as const,
|
|
271
315
|
flags: [],
|
|
316
|
+
reason: classifyWorkspaceFeatureFlagTargetFailure(error),
|
|
272
317
|
};
|
|
273
318
|
}
|
|
274
319
|
});
|
|
@@ -279,14 +324,31 @@ export async function setWorkspaceFeatureFlag(
|
|
|
279
324
|
admin: AnalyticsAdminContext,
|
|
280
325
|
input: WorkspaceFeatureFlagMutationInput,
|
|
281
326
|
): Promise<WorkspaceFeatureFlagMutationResult> {
|
|
282
|
-
const apps = await fetchOrgApps({
|
|
327
|
+
const apps = await fetchOrgApps({
|
|
328
|
+
selfId: "analytics",
|
|
329
|
+
includeDirectoryApp: true,
|
|
330
|
+
serviceOrgId: admin.orgId,
|
|
331
|
+
});
|
|
283
332
|
const app = apps.find((candidate) => candidate.id === input.appId);
|
|
284
333
|
if (!app)
|
|
285
334
|
throw new Error(
|
|
286
335
|
"The requested app is not available in this organization directory.",
|
|
287
336
|
);
|
|
288
337
|
const targetInput = workspaceFeatureFlagTargetInput(input);
|
|
289
|
-
|
|
338
|
+
let orgDomain: string | undefined;
|
|
339
|
+
try {
|
|
340
|
+
orgDomain = (await getOrgDomain(admin.orgId))?.trim().toLowerCase();
|
|
341
|
+
} catch {
|
|
342
|
+
throw new TargetCallFailure("token-generation");
|
|
343
|
+
}
|
|
344
|
+
if (!orgDomain) throw new TargetCallFailure("token-generation");
|
|
345
|
+
const result = await callTarget(
|
|
346
|
+
app,
|
|
347
|
+
admin,
|
|
348
|
+
"set-feature-flag",
|
|
349
|
+
targetInput,
|
|
350
|
+
orgDomain,
|
|
351
|
+
);
|
|
290
352
|
if (result.status === 401 || result.status === 403)
|
|
291
353
|
throw new Error("The target app denied this delegated flag operation.");
|
|
292
354
|
if (result.status === 404 || result.status === 405)
|
|
@@ -297,7 +359,7 @@ export async function setWorkspaceFeatureFlag(
|
|
|
297
359
|
);
|
|
298
360
|
return validateWorkspaceFeatureFlagMutation(result.body, {
|
|
299
361
|
key: input.key,
|
|
300
|
-
|
|
362
|
+
orgDomain,
|
|
301
363
|
...(input.operation === "replace-rules" && input.rules
|
|
302
364
|
? {
|
|
303
365
|
rules: {
|
|
@@ -324,7 +386,11 @@ export async function getWorkspaceFlagTarget(
|
|
|
324
386
|
admin: AnalyticsAdminContext,
|
|
325
387
|
appId: string,
|
|
326
388
|
): Promise<FleetFlagApp> {
|
|
327
|
-
const apps = await fetchOrgApps({
|
|
389
|
+
const apps = await fetchOrgApps({
|
|
390
|
+
selfId: "analytics",
|
|
391
|
+
includeDirectoryApp: true,
|
|
392
|
+
serviceOrgId: admin.orgId,
|
|
393
|
+
});
|
|
328
394
|
const app = apps.find((candidate) => candidate.id === appId);
|
|
329
395
|
if (!app)
|
|
330
396
|
throw new Error(
|
|
@@ -335,13 +401,14 @@ export async function getWorkspaceFlagTarget(
|
|
|
335
401
|
app,
|
|
336
402
|
await callTarget(app, admin, "list-feature-flags", {}),
|
|
337
403
|
);
|
|
338
|
-
} catch {
|
|
404
|
+
} catch (error) {
|
|
339
405
|
return {
|
|
340
406
|
appId: app.id,
|
|
341
407
|
appName: app.name,
|
|
342
408
|
appOrigin: targetOrigin(app),
|
|
343
409
|
state: "unreachable",
|
|
344
410
|
flags: [],
|
|
411
|
+
reason: classifyWorkspaceFeatureFlagTargetFailure(error),
|
|
345
412
|
};
|
|
346
413
|
}
|
|
347
414
|
}
|
|
@@ -278,14 +278,13 @@ export interface DiscoveredAppLike {
|
|
|
278
278
|
|
|
279
279
|
/**
|
|
280
280
|
* Shape the discovered-agent list into the directory response. The input is
|
|
281
|
-
* Dispatch's EXISTING connected-apps registry — `discoverAgents(
|
|
281
|
+
* Dispatch's EXISTING connected-apps registry — `discoverAgents()`
|
|
282
282
|
* from `@agent-native/core/server/agent-discovery`, the same source
|
|
283
283
|
* `list-connected-agents` and the `call-agent` delegation path use. It is
|
|
284
284
|
* already allow-list-respecting (hidden first-party templates are excluded
|
|
285
285
|
* from `BUILTIN_AGENTS` unless `defaultAgent`), so no second filter is
|
|
286
|
-
* needed here; we only drop entries without a usable absolute http(s) URL
|
|
287
|
-
*
|
|
288
|
-
* just called).
|
|
286
|
+
* needed here; we only drop entries without a usable absolute http(s) URL.
|
|
287
|
+
* Callers that identify themselves may also request self-filtering.
|
|
289
288
|
*/
|
|
290
289
|
export function buildOrgAppsResponse(input: {
|
|
291
290
|
org: string;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* APP-LIST SOURCE — Dispatch's existing connected-apps registry.
|
|
26
26
|
* -------------------------------------------------------------
|
|
27
|
-
* Dispatch already has a connected-apps concept: `discoverAgents(
|
|
27
|
+
* Dispatch already has a connected-apps concept: `discoverAgents()`
|
|
28
28
|
* from `@agent-native/core/server/agent-discovery` (the same source
|
|
29
29
|
* `list-connected-agents` and the `call-agent` delegation path use). It
|
|
30
30
|
* returns the allow-listed first-party apps with their prod URLs PLUS any
|
|
@@ -143,7 +143,16 @@ const orgAppsHandler = defineEventHandler(
|
|
|
143
143
|
const apps: DiscoveredAppLike[] = await runWithRequestContext(
|
|
144
144
|
{ userEmail: verified.email, orgId: localOrg.orgId },
|
|
145
145
|
async () => {
|
|
146
|
-
const
|
|
146
|
+
const includeDirectoryApp =
|
|
147
|
+
getRequestHeader(event, "x-agent-native-include-directory-app") ===
|
|
148
|
+
"1";
|
|
149
|
+
const discovered = await discoverAgents(
|
|
150
|
+
includeDirectoryApp ? undefined : SELF_APP_ID,
|
|
151
|
+
{
|
|
152
|
+
preferLocalUrls:
|
|
153
|
+
process.env.AGENT_NATIVE_PREFER_LOCAL_APP_URLS === "1",
|
|
154
|
+
},
|
|
155
|
+
);
|
|
147
156
|
return discovered.map((a) => ({
|
|
148
157
|
id: a.id,
|
|
149
158
|
name: a.name,
|
|
@@ -165,7 +174,10 @@ const orgAppsHandler = defineEventHandler(
|
|
|
165
174
|
const body = buildOrgAppsResponse({
|
|
166
175
|
org: orgLabel,
|
|
167
176
|
apps,
|
|
168
|
-
selfId:
|
|
177
|
+
selfId:
|
|
178
|
+
getRequestHeader(event, "x-agent-native-include-directory-app") === "1"
|
|
179
|
+
? undefined
|
|
180
|
+
: SELF_APP_ID,
|
|
169
181
|
});
|
|
170
182
|
|
|
171
183
|
// Short, cacheable, read-only. Private (per-org) so shared caches must
|
package/dist/a2a-claims.d.ts
CHANGED
package/dist/a2a-claims.js
CHANGED
|
@@ -24,10 +24,12 @@ export async function verifyA2ATokenWithClaims(token, event) {
|
|
|
24
24
|
? raw.scope.split(/\s+/).filter(Boolean)
|
|
25
25
|
: [];
|
|
26
26
|
const issuer = typeof raw.iss === "string" ? raw.iss.trim() : "";
|
|
27
|
-
|
|
27
|
+
const orgDomain = identity.orgDomain?.trim().toLowerCase() ?? "";
|
|
28
|
+
return orgId && orgDomain && jti
|
|
28
29
|
? {
|
|
29
30
|
email: identity.email,
|
|
30
31
|
orgId,
|
|
32
|
+
orgDomain,
|
|
31
33
|
jti,
|
|
32
34
|
...(issuer ? { issuer } : {}),
|
|
33
35
|
scope: scopes,
|
|
@@ -117,9 +117,9 @@ function FeatureFlagRow({ flag, onMutate, isPending, error, }) {
|
|
|
117
117
|
const actor = formatActor(flag.rules.updatedBy);
|
|
118
118
|
const when = formatWhen(flag.rules.updatedAt);
|
|
119
119
|
const metadata = [actor, when].filter(Boolean).join(" · ");
|
|
120
|
-
return (_jsxs("article", { id: `feature-flag-${flag.key}`, className: "grid gap-4 scroll-mt-24 border-b border-border py-5 last:border-b-0
|
|
120
|
+
return (_jsxs("article", { id: `feature-flag-${flag.key}`, className: "grid gap-4 scroll-mt-24 border-b border-border py-5 last:border-b-0 lg:grid-cols-[minmax(0,1fr)_auto] lg:items-center", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("h3", { className: "truncate text-sm font-medium text-foreground", children: flag.displayName ?? flag.key }), flag.displayName && flag.displayName !== flag.key ? (_jsx("code", { className: "truncate text-xs text-muted-foreground", children: flag.key })) : null, _jsx(Badge, { variant: flag.rules.mode === "on" ? "secondary" : "outline", className: "ms-auto shrink-0", children: modeLabel(flag.rules, t) })] }), flag.description ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: flag.description })) : null, _jsxs("div", { className: "mt-3 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [_jsxs("span", { children: [t("featureFlags.enabledFor"), ": ", audienceLabel(flag.rules, t)] }), metadata ? (_jsx("span", { children: t("featureFlags.lastChanged", { metadata }) })) : null] }), _jsx("div", { className: "mt-2 flex items-center gap-1.5 text-xs font-medium text-foreground", children: flag.enabledForCurrentUser === true ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "size-4 text-emerald-500" }), t("featureFlags.youHaveAccess")] })) : flag.enabledForCurrentUser === false ? (_jsx(_Fragment, { children: t("featureFlags.youDoNotHaveAccess") })) : (_jsx(_Fragment, { children: t("featureFlags.currentUserUnknown") })) }), error ? (_jsx("p", { className: "mt-3 text-sm text-destructive", children: t("featureFlags.mutationUnverified", {
|
|
121
121
|
name: flag.displayName ?? flag.key,
|
|
122
|
-
}) })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2
|
|
122
|
+
}) })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 lg:justify-end", children: [_jsxs(Button, { type: "button", variant: "outline", size: "sm", className: "text-xs", disabled: isPending || flag.enabledForCurrentUser === true, onClick: () => onMutate({
|
|
123
123
|
key: flag.key,
|
|
124
124
|
operation: "enable-for-current-user",
|
|
125
125
|
}), children: [flag.enabledForCurrentUser === true ? (_jsx(IconCheck, {})) : (_jsx(IconUserCheck, {})), flag.enabledForCurrentUser === true
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getHeader } from "h3";
|
|
2
2
|
import * as jose from "jose";
|
|
3
3
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
4
|
+
import { resolveOrgByDomain } from "../org/context.js";
|
|
5
|
+
import { consumeOneTimeJti } from "../server/identity-sso-store.js";
|
|
4
6
|
const FLAG_ACTION_SCOPES = {
|
|
5
7
|
"list-feature-flags": "flags:read",
|
|
6
8
|
"set-feature-flag": "flags:write",
|
|
@@ -37,9 +39,16 @@ export function createFeatureFlagA2AActionRouteAuth(actionName) {
|
|
|
37
39
|
if (!claims || !claims.scope.includes(FLAG_ACTION_SCOPES[actionName])) {
|
|
38
40
|
throw new Error("Invalid feature flag delegation");
|
|
39
41
|
}
|
|
42
|
+
const localOrg = await resolveOrgByDomain(claims.orgDomain);
|
|
43
|
+
if (!localOrg)
|
|
44
|
+
throw new Error("Invalid feature flag delegation");
|
|
45
|
+
if (actionName === "set-feature-flag" &&
|
|
46
|
+
(await consumeOneTimeJti(claims.jti))) {
|
|
47
|
+
throw new Error("Invalid feature flag delegation");
|
|
48
|
+
}
|
|
40
49
|
return {
|
|
41
50
|
owner: claims.email,
|
|
42
|
-
orgId:
|
|
51
|
+
orgId: localOrg.orgId,
|
|
43
52
|
anonymous: false,
|
|
44
53
|
delegationJti: claims.jti,
|
|
45
54
|
delegationIssuer: claims.issuer,
|
|
@@ -14,12 +14,13 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
14
14
|
percentage?: number;
|
|
15
15
|
};
|
|
16
16
|
}, {
|
|
17
|
-
contractVersion:
|
|
17
|
+
contractVersion: 2;
|
|
18
18
|
status: "ready";
|
|
19
19
|
key: string;
|
|
20
20
|
rules: import("../store.js").FeatureFlagRules;
|
|
21
21
|
scope: {
|
|
22
22
|
orgId: string;
|
|
23
|
+
orgDomain: string;
|
|
23
24
|
};
|
|
24
25
|
}>;
|
|
25
26
|
export default _default;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { defineAction } from "../../action.js";
|
|
3
|
+
import { getOrgDomain } from "../../org/context.js";
|
|
3
4
|
import { requireFeatureFlagManager } from "../permissions.js";
|
|
4
5
|
import { getFeatureFlagDefinition } from "../registry.js";
|
|
5
6
|
import { defaultFeatureFlagRules, mutateFeatureFlagRules, normalizeFeatureFlagRules, } from "../store.js";
|
|
@@ -47,6 +48,12 @@ export default defineAction({
|
|
|
47
48
|
if (!getFeatureFlagDefinition(args.key)) {
|
|
48
49
|
throw new Error(`Unknown feature flag: ${args.key}`);
|
|
49
50
|
}
|
|
51
|
+
const orgDomain = manager.orgId
|
|
52
|
+
? (await getOrgDomain(manager.orgId))?.trim().toLowerCase() || null
|
|
53
|
+
: null;
|
|
54
|
+
if (ctx?.caller === "a2a" && manager.orgId && !orgDomain) {
|
|
55
|
+
throw new Error("Feature flag organization domain is unavailable.");
|
|
56
|
+
}
|
|
50
57
|
const persistedRules = await mutateFeatureFlagRules(args.key, manager, (current) => {
|
|
51
58
|
let rules;
|
|
52
59
|
if (args.operation === "off") {
|
|
@@ -67,11 +74,11 @@ export default defineAction({
|
|
|
67
74
|
return { ...rules, updatedAt: Date.now(), updatedBy: manager.email };
|
|
68
75
|
});
|
|
69
76
|
return {
|
|
70
|
-
contractVersion:
|
|
77
|
+
contractVersion: 2,
|
|
71
78
|
status: "ready",
|
|
72
79
|
key: args.key,
|
|
73
80
|
rules: persistedRules,
|
|
74
|
-
scope: { orgId: manager.orgId },
|
|
81
|
+
scope: { orgId: manager.orgId, orgDomain },
|
|
75
82
|
};
|
|
76
83
|
},
|
|
77
84
|
});
|
|
@@ -71,11 +71,14 @@ export declare function resolveOrgDirectoryOrigin(env?: NodeJS.ProcessEnv): stri
|
|
|
71
71
|
*
|
|
72
72
|
* @param opts.selfId Current app id (so it's stripped from the result).
|
|
73
73
|
* @param opts.selfOrigin Current app origin (so it's stripped by origin too).
|
|
74
|
+
* @param opts.includeDirectoryApp Request the directory authority itself. The
|
|
75
|
+
* server keeps legacy self-filtering unless this explicit signal is present.
|
|
74
76
|
* @param opts.env Injectable env (tests). Defaults to `process.env`.
|
|
75
77
|
*/
|
|
76
78
|
export declare function fetchOrgApps(opts?: {
|
|
77
79
|
selfId?: string;
|
|
78
80
|
selfOrigin?: string;
|
|
81
|
+
includeDirectoryApp?: boolean;
|
|
79
82
|
serviceOrgId?: string;
|
|
80
83
|
env?: NodeJS.ProcessEnv;
|
|
81
84
|
}): Promise<OrgApp[]>;
|
|
@@ -109,18 +109,23 @@ function sameOrigin(a, b) {
|
|
|
109
109
|
return a.replace(/\/+$/, "") === b.replace(/\/+$/, "");
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
function scopedCacheKey(origin, auth) {
|
|
112
|
+
function scopedCacheKey(origin, auth, includeDirectoryApp = false) {
|
|
113
113
|
return [
|
|
114
114
|
origin,
|
|
115
115
|
`user:${auth.userEmail ?? ""}`,
|
|
116
116
|
`org:${auth.orgId ?? auth.orgDomain ?? ""}`,
|
|
117
|
+
`include-directory:${includeDirectoryApp ? "1" : "0"}`,
|
|
117
118
|
].join("|");
|
|
118
119
|
}
|
|
119
120
|
function authTokenAttempts(auth) {
|
|
120
121
|
return [auth.apiKey, ...(auth.apiKeyFallbacks ?? [])].filter((token) => typeof token === "string" && token.length > 0);
|
|
121
122
|
}
|
|
122
|
-
function serviceScopedCacheKey(origin, orgId) {
|
|
123
|
-
return [
|
|
123
|
+
function serviceScopedCacheKey(origin, orgId, includeDirectoryApp = false) {
|
|
124
|
+
return [
|
|
125
|
+
origin,
|
|
126
|
+
`service-org:${orgId}`,
|
|
127
|
+
`include-directory:${includeDirectoryApp ? "1" : "0"}`,
|
|
128
|
+
].join("|");
|
|
124
129
|
}
|
|
125
130
|
/**
|
|
126
131
|
* Fetch the org's first-party sibling apps from the org directory.
|
|
@@ -133,6 +138,8 @@ function serviceScopedCacheKey(origin, orgId) {
|
|
|
133
138
|
*
|
|
134
139
|
* @param opts.selfId Current app id (so it's stripped from the result).
|
|
135
140
|
* @param opts.selfOrigin Current app origin (so it's stripped by origin too).
|
|
141
|
+
* @param opts.includeDirectoryApp Request the directory authority itself. The
|
|
142
|
+
* server keeps legacy self-filtering unless this explicit signal is present.
|
|
136
143
|
* @param opts.env Injectable env (tests). Defaults to `process.env`.
|
|
137
144
|
*/
|
|
138
145
|
export async function fetchOrgApps(opts) {
|
|
@@ -155,7 +162,7 @@ export async function fetchOrgApps(opts) {
|
|
|
155
162
|
let ttl = EMPTY_TTL_MS;
|
|
156
163
|
const serviceOrgId = opts?.serviceOrgId?.trim();
|
|
157
164
|
if (serviceOrgId) {
|
|
158
|
-
const serviceCacheKey = serviceScopedCacheKey(origin, serviceOrgId);
|
|
165
|
+
const serviceCacheKey = serviceScopedCacheKey(origin, serviceOrgId, opts?.includeDirectoryApp);
|
|
159
166
|
const cached = cache.get(serviceCacheKey);
|
|
160
167
|
if (cached && cached.expiresAt > Date.now()) {
|
|
161
168
|
return stripSelf(cached.apps);
|
|
@@ -174,7 +181,7 @@ export async function fetchOrgApps(opts) {
|
|
|
174
181
|
}
|
|
175
182
|
if (!cacheKey) {
|
|
176
183
|
const now = Date.now();
|
|
177
|
-
cacheKey = scopedCacheKey(origin, auth);
|
|
184
|
+
cacheKey = scopedCacheKey(origin, auth, opts?.includeDirectoryApp);
|
|
178
185
|
const cached = cache.get(cacheKey);
|
|
179
186
|
if (cached && cached.expiresAt > now) {
|
|
180
187
|
return stripSelf(cached.apps);
|
|
@@ -186,6 +193,9 @@ export async function fetchOrgApps(opts) {
|
|
|
186
193
|
headers: {
|
|
187
194
|
Authorization: `Bearer ${attempts[i]}`,
|
|
188
195
|
Accept: "application/json",
|
|
196
|
+
...(opts?.includeDirectoryApp
|
|
197
|
+
? { "X-Agent-Native-Include-Directory-App": "1" }
|
|
198
|
+
: {}),
|
|
189
199
|
},
|
|
190
200
|
signal: AbortSignal.timeout(4000),
|
|
191
201
|
});
|
|
@@ -2,7 +2,7 @@ import { createError, defineEventHandler, setResponseStatus, setResponseHeader,
|
|
|
2
2
|
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
3
3
|
import { isAgentActionStopError } from "../action.js";
|
|
4
4
|
import { declaresFeatureFlagDelegation } from "../feature-flags/a2a-action-route.js";
|
|
5
|
-
import { resolveOrgIdForEmail } from "../org/context.js";
|
|
5
|
+
import { resolveOrgByDomain, resolveOrgIdForEmail } from "../org/context.js";
|
|
6
6
|
import { readBody } from "../server/h3-helpers.js";
|
|
7
7
|
import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
|
|
8
8
|
import { isMcpEmbedCorsOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, shouldAllowMcpEmbedCredentials, } from "../shared/mcp-embed-headers.js";
|
|
@@ -12,6 +12,7 @@ import { captureError } from "./capture-error.js";
|
|
|
12
12
|
import { getAllowedCorsOrigin as resolveAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
|
|
13
13
|
import { resolveEmbedSessionFromRequest, resolvedEmbedCapabilityScope, } from "./embed-session.js";
|
|
14
14
|
import { getHttpRequestTelemetryId } from "./http-response-telemetry.js";
|
|
15
|
+
import { consumeOneTimeJti } from "./identity-sso-store.js";
|
|
15
16
|
function requiredClientCompatibilityVersion() {
|
|
16
17
|
const configured = typeof __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__ === "string"
|
|
17
18
|
? __AGENT_NATIVE_CLIENT_COMPATIBILITY_VERSION__
|
|
@@ -52,9 +53,16 @@ async function resolveFeatureFlagA2ACaller(event, actionName) {
|
|
|
52
53
|
const claims = await verifyA2ATokenWithClaims(token, event);
|
|
53
54
|
if (!claims || !claims.scope.includes(required))
|
|
54
55
|
throw new Error("Invalid feature flag delegation");
|
|
56
|
+
const localOrg = await resolveOrgByDomain(claims.orgDomain);
|
|
57
|
+
if (!localOrg)
|
|
58
|
+
throw new Error("Invalid feature flag delegation");
|
|
59
|
+
if (actionName === "set-feature-flag" &&
|
|
60
|
+
(await consumeOneTimeJti(claims.jti))) {
|
|
61
|
+
throw new Error("Invalid feature flag delegation");
|
|
62
|
+
}
|
|
55
63
|
return {
|
|
56
64
|
owner: claims.email,
|
|
57
|
-
orgId:
|
|
65
|
+
orgId: localOrg.orgId,
|
|
58
66
|
anonymous: false,
|
|
59
67
|
delegationJti: claims.jti,
|
|
60
68
|
delegationIssuer: claims.issuer,
|
|
@@ -70,12 +70,16 @@ export declare function shouldIncludeRemoteAgentManifest(manifest: {
|
|
|
70
70
|
/**
|
|
71
71
|
* Get built-in agents (static, no DB). Used as fallback and for seeding.
|
|
72
72
|
*/
|
|
73
|
-
export declare function getBuiltinAgents(selfAppId?: string
|
|
73
|
+
export declare function getBuiltinAgents(selfAppId?: string, options?: {
|
|
74
|
+
preferLocalUrls?: boolean;
|
|
75
|
+
}): DiscoveredAgent[];
|
|
74
76
|
/**
|
|
75
77
|
* Discover all agents: built-in + custom agents stored as resources.
|
|
76
78
|
* Custom agents override built-in agents with the same ID.
|
|
77
79
|
*/
|
|
78
|
-
export declare function discoverAgents(selfAppId?: string
|
|
80
|
+
export declare function discoverAgents(selfAppId?: string, options?: {
|
|
81
|
+
preferLocalUrls?: boolean;
|
|
82
|
+
}): Promise<DiscoveredAgent[]>;
|
|
79
83
|
/**
|
|
80
84
|
* Look up a single agent by ID or name (case-insensitive).
|
|
81
85
|
*/
|
|
@@ -191,13 +191,13 @@ export function shouldIncludeRemoteAgentManifest(manifest, selfAppId) {
|
|
|
191
191
|
/**
|
|
192
192
|
* Get built-in agents (static, no DB). Used as fallback and for seeding.
|
|
193
193
|
*/
|
|
194
|
-
export function getBuiltinAgents(selfAppId) {
|
|
194
|
+
export function getBuiltinAgents(selfAppId, options) {
|
|
195
195
|
const normalizedSelfAppId = selfAppId ? normalizeAgentId(selfAppId) : "";
|
|
196
196
|
return BUILTIN_AGENTS.filter((app) => app.id !== normalizedSelfAppId && app.url).map((app) => ({
|
|
197
197
|
id: app.id,
|
|
198
198
|
name: app.name,
|
|
199
199
|
description: app.description,
|
|
200
|
-
url: resolveAgentUrl(app),
|
|
200
|
+
url: resolveAgentUrl(app, options?.preferLocalUrls),
|
|
201
201
|
color: app.color,
|
|
202
202
|
}));
|
|
203
203
|
}
|
|
@@ -205,8 +205,8 @@ export function getBuiltinAgents(selfAppId) {
|
|
|
205
205
|
* Discover all agents: built-in + custom agents stored as resources.
|
|
206
206
|
* Custom agents override built-in agents with the same ID.
|
|
207
207
|
*/
|
|
208
|
-
export async function discoverAgents(selfAppId) {
|
|
209
|
-
const builtins = getBuiltinAgents(selfAppId);
|
|
208
|
+
export async function discoverAgents(selfAppId, options) {
|
|
209
|
+
const builtins = getBuiltinAgents(selfAppId, options);
|
|
210
210
|
const agentsById = new Map();
|
|
211
211
|
// Start with built-ins
|
|
212
212
|
for (const agent of builtins) {
|
|
@@ -249,6 +249,11 @@ export async function discoverAgents(selfAppId) {
|
|
|
249
249
|
url = builtin.url;
|
|
250
250
|
}
|
|
251
251
|
const builtin = agentsById.get(manifestId);
|
|
252
|
+
if (options?.preferLocalUrls && builtin) {
|
|
253
|
+
const isBuiltinAgent = BUILTIN_AGENTS.some((candidate) => candidate.id === manifestId);
|
|
254
|
+
if (isBuiltinAgent)
|
|
255
|
+
url = builtin.url;
|
|
256
|
+
}
|
|
252
257
|
const isLegacyAssetsManifest = manifest.id.trim().toLowerCase() !== manifestId;
|
|
253
258
|
if (isLegacyAssetsManifest && builtin?.url) {
|
|
254
259
|
try {
|
|
@@ -280,7 +285,7 @@ export async function discoverAgents(selfAppId) {
|
|
|
280
285
|
}
|
|
281
286
|
// Overlay sibling workspace apps last so same-origin workspaces prefer the
|
|
282
287
|
// app mounted in this workspace over the public template with the same id.
|
|
283
|
-
for (const agent of await discoverWorkspaceAgents(selfAppId)) {
|
|
288
|
+
for (const agent of await discoverWorkspaceAgents(selfAppId, options)) {
|
|
284
289
|
agentsById.set(agent.id, agent);
|
|
285
290
|
}
|
|
286
291
|
return Array.from(agentsById.values());
|
|
@@ -347,8 +352,8 @@ function isHostedRuntime() {
|
|
|
347
352
|
function shouldUseLocalAgentUrls() {
|
|
348
353
|
return !isHostedRuntime();
|
|
349
354
|
}
|
|
350
|
-
function resolveAgentUrl(app) {
|
|
351
|
-
if (shouldUseLocalAgentUrls()) {
|
|
355
|
+
function resolveAgentUrl(app, preferLocalUrls = false) {
|
|
356
|
+
if (preferLocalUrls || shouldUseLocalAgentUrls()) {
|
|
352
357
|
return app.devUrl || `http://localhost:${app.devPort}`;
|
|
353
358
|
}
|
|
354
359
|
return app.url;
|
|
@@ -527,7 +532,7 @@ function workspaceAppUrl(app, hostedLoopbackFallbackUrl) {
|
|
|
527
532
|
return null;
|
|
528
533
|
}
|
|
529
534
|
}
|
|
530
|
-
async function discoverWorkspaceAgents(selfAppId) {
|
|
535
|
+
async function discoverWorkspaceAgents(selfAppId, options) {
|
|
531
536
|
const workspaceApps = loadWorkspaceAppsManifest();
|
|
532
537
|
if (!workspaceApps)
|
|
533
538
|
return [];
|
|
@@ -537,7 +542,9 @@ async function discoverWorkspaceAgents(selfAppId) {
|
|
|
537
542
|
.map((app) => {
|
|
538
543
|
const withOverride = applyWorkspaceAppMetadataOverride(app, metadataSettings);
|
|
539
544
|
const builtin = BUILTIN_AGENTS.find((agent) => agent.id === withOverride.id);
|
|
540
|
-
const url =
|
|
545
|
+
const url = options?.preferLocalUrls && builtin
|
|
546
|
+
? resolveAgentUrl(builtin, true)
|
|
547
|
+
: workspaceAppUrl(withOverride, builtin?.url);
|
|
541
548
|
if (!url)
|
|
542
549
|
return null;
|
|
543
550
|
return {
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - `identity_sso_flow_state` binds state to the exact app, client,
|
|
9
9
|
* authority, callback, and PKCE challenge. State is single-use.
|
|
10
|
-
* - `identity_sso_jti`
|
|
11
|
-
* server-to-server
|
|
12
|
-
*
|
|
10
|
+
* - `identity_sso_jti` is the legacy-named shared replay guard for short-lived
|
|
11
|
+
* server-to-server assertions, including identity SSO and privileged A2A
|
|
12
|
+
* mutations.
|
|
13
13
|
*
|
|
14
14
|
* Uses the same portable raw-SQL pattern as the other framework stores. DDL is
|
|
15
15
|
* additive and lazy, and PostgreSQL creation goes through the DDL guard.
|
|
@@ -59,4 +59,5 @@ export declare function consumeSsoState(state: string, expected: SsoStateBinding
|
|
|
59
59
|
* guarantee, and refusing a login is safer than accepting an unverifiable
|
|
60
60
|
* replay boundary.
|
|
61
61
|
*/
|
|
62
|
-
export declare function
|
|
62
|
+
export declare function consumeOneTimeJti(jti: string | undefined): Promise<boolean>;
|
|
63
|
+
export declare const isJtiReplayed: typeof consumeOneTimeJti;
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*
|
|
8
8
|
* - `identity_sso_flow_state` binds state to the exact app, client,
|
|
9
9
|
* authority, callback, and PKCE challenge. State is single-use.
|
|
10
|
-
* - `identity_sso_jti`
|
|
11
|
-
* server-to-server
|
|
12
|
-
*
|
|
10
|
+
* - `identity_sso_jti` is the legacy-named shared replay guard for short-lived
|
|
11
|
+
* server-to-server assertions, including identity SSO and privileged A2A
|
|
12
|
+
* mutations.
|
|
13
13
|
*
|
|
14
14
|
* Uses the same portable raw-SQL pattern as the other framework stores. DDL is
|
|
15
15
|
* additive and lazy, and PostgreSQL creation goes through the DDL guard.
|
|
@@ -321,7 +321,7 @@ export async function consumeSsoState(state, expected) {
|
|
|
321
321
|
* guarantee, and refusing a login is safer than accepting an unverifiable
|
|
322
322
|
* replay boundary.
|
|
323
323
|
*/
|
|
324
|
-
export async function
|
|
324
|
+
export async function consumeOneTimeJti(jti) {
|
|
325
325
|
if (!jti)
|
|
326
326
|
return true;
|
|
327
327
|
try {
|
|
@@ -352,3 +352,4 @@ export async function isJtiReplayed(jti) {
|
|
|
352
352
|
return true;
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
+
export const isJtiReplayed = consumeOneTimeJti;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.154.
|
|
3
|
+
"version": "0.154.2",
|
|
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": {
|
|
@@ -421,8 +421,8 @@
|
|
|
421
421
|
"y-protocols": "^1.0.7",
|
|
422
422
|
"yjs": "^13.6.31",
|
|
423
423
|
"zod": "^4.3.6",
|
|
424
|
-
"@agent-native/
|
|
425
|
-
"@agent-native/
|
|
424
|
+
"@agent-native/recap-cli": "0.5.4",
|
|
425
|
+
"@agent-native/toolkit": "^0.14.2"
|
|
426
426
|
},
|
|
427
427
|
"devDependencies": {
|
|
428
428
|
"@ai-sdk/anthropic": "^3.0.71",
|