@agent-native/core 0.107.1 → 0.108.0
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 +12 -0
- package/corpus/core/docs/content/actions.mdx +48 -0
- package/corpus/core/docs/content/locales/ar-SA/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/de-DE/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/es-ES/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/fr-FR/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/hi-IN/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/ja-JP/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/ko-KR/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/pt-BR/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/zh-CN/actions.mdx +38 -0
- package/corpus/core/docs/content/locales/zh-TW/actions.mdx +38 -0
- package/corpus/core/package.json +5 -1
- package/corpus/core/src/a2a/artifact-response.ts +32 -10
- package/corpus/core/src/a2a-claims.ts +52 -0
- package/corpus/core/src/action.ts +6 -2
- package/corpus/core/src/audit/record.ts +6 -0
- package/corpus/core/src/client/feature-flags/FeatureFlagsPanel.tsx +519 -0
- package/corpus/core/src/client/feature-flags/helpers.ts +77 -0
- package/corpus/core/src/client/feature-flags/index.ts +13 -0
- package/corpus/core/src/client/feature-flags/types.ts +29 -0
- package/corpus/core/src/client/feature-flags/use-feature-flag.ts +26 -0
- package/corpus/core/src/client/index.ts +11 -0
- package/corpus/core/src/extensions/routes.ts +1 -1
- package/corpus/core/src/feature-flags/a2a-action-route.ts +56 -0
- package/corpus/core/src/feature-flags/actions/get-feature-flags.ts +24 -0
- package/corpus/core/src/feature-flags/actions/list-feature-flags.ts +69 -0
- package/corpus/core/src/feature-flags/actions/set-feature-flag.ts +89 -0
- package/corpus/core/src/feature-flags/index.ts +21 -0
- package/corpus/core/src/feature-flags/permissions.ts +53 -0
- package/corpus/core/src/feature-flags/plugin.ts +44 -0
- package/corpus/core/src/feature-flags/registry.ts +96 -0
- package/corpus/core/src/feature-flags/store.ts +176 -0
- package/corpus/core/src/index.ts +15 -0
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +107 -3
- package/corpus/core/src/integrations/pending-tasks-store.ts +98 -1
- package/corpus/core/src/integrations/plugin.ts +129 -4
- package/corpus/core/src/integrations/webhook-handler.ts +339 -36
- package/corpus/core/src/localization/default-messages.ts +47 -0
- package/corpus/core/src/server/action-discovery.ts +12 -0
- package/corpus/core/src/server/action-routes.ts +47 -5
- package/corpus/core/src/server/agent-chat-plugin.ts +93 -8
- package/corpus/core/src/server/index.ts +1 -0
- package/corpus/core/src/settings/org-settings.ts +13 -0
- package/corpus/core/src/settings/store.ts +57 -0
- package/corpus/core/src/templates/default/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/core/src/templates/default/AGENTS.md +1 -0
- package/corpus/core/src/templates/headless/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/core/src/templates/workspace-core/AGENTS.md +1 -1
- package/corpus/core/src/templates/workspace-root/AGENTS.md +1 -1
- package/corpus/core/src/vite/action-types-plugin.ts +12 -0
- package/corpus/templates/analytics/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/analytics/AGENTS.md +15 -3
- package/corpus/templates/analytics/actions/list-workspace-feature-flags.ts +15 -0
- package/corpus/templates/analytics/actions/navigate.ts +2 -2
- package/corpus/templates/analytics/actions/set-workspace-feature-flag.ts +45 -0
- package/corpus/templates/analytics/actions/view-screen.ts +13 -0
- package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +243 -0
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +11 -3
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +3 -1
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +17 -0
- package/corpus/templates/analytics/app/i18n-data.ts +251 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +22 -2
- package/corpus/templates/analytics/changelog/2026-07-17-manage-feature-flag-rollouts-across-your-organization-s-apps.md +6 -0
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +13 -5
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +347 -0
- package/corpus/templates/assets/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/brain/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/calendar/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/clips/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/clips/server/plugins/feature-flags.ts +11 -0
- package/corpus/templates/clips/shared/feature-flags.ts +30 -37
- package/corpus/templates/content/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/design/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/design/AGENTS.md +5 -3
- package/corpus/templates/design/actions/add-fusion-screens.ts +4 -6
- package/corpus/templates/design/actions/apply-fusion-edits.ts +4 -6
- package/corpus/templates/design/actions/create-fusion-app.ts +5 -4
- package/corpus/templates/design/actions/deploy-fusion-app.ts +4 -3
- package/corpus/templates/design/actions/get-fusion-deploy-status.ts +4 -6
- package/corpus/templates/design/actions/list-fusion-edits.ts +4 -3
- package/corpus/templates/design/actions/push-fusion-app.ts +4 -6
- package/corpus/templates/design/actions/queue-fusion-edit.ts +4 -3
- package/corpus/templates/design/actions/send-fusion-message.ts +4 -6
- package/corpus/templates/design/actions/sync-fusion-app.ts +4 -3
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +8 -6
- package/corpus/templates/design/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/design/shared/full-app.ts +10 -7
- package/corpus/templates/dispatch/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/forms/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/macros/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/mail/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/plan/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/slides/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/tasks/.agents/skills/feature-flags/SKILL.md +169 -0
- package/dist/a2a/artifact-response.d.ts +5 -1
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +18 -12
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/a2a-claims.d.ts +10 -0
- package/dist/a2a-claims.d.ts.map +1 -0
- package/dist/a2a-claims.js +41 -0
- package/dist/a2a-claims.js.map +1 -0
- package/dist/action.d.ts +6 -2
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js.map +1 -1
- package/dist/audit/record.d.ts +3 -0
- package/dist/audit/record.d.ts.map +1 -1
- package/dist/audit/record.js +3 -0
- package/dist/audit/record.js.map +1 -1
- package/dist/client/feature-flags/FeatureFlagsPanel.d.ts +10 -0
- package/dist/client/feature-flags/FeatureFlagsPanel.d.ts.map +1 -0
- package/dist/client/feature-flags/FeatureFlagsPanel.js +143 -0
- package/dist/client/feature-flags/FeatureFlagsPanel.js.map +1 -0
- package/dist/client/feature-flags/helpers.d.ts +16 -0
- package/dist/client/feature-flags/helpers.d.ts.map +1 -0
- package/dist/client/feature-flags/helpers.js +50 -0
- package/dist/client/feature-flags/helpers.js.map +1 -0
- package/dist/client/feature-flags/index.d.ts +5 -0
- package/dist/client/feature-flags/index.d.ts.map +1 -0
- package/dist/client/feature-flags/index.js +4 -0
- package/dist/client/feature-flags/index.js.map +1 -0
- package/dist/client/feature-flags/types.d.ts +27 -0
- package/dist/client/feature-flags/types.d.ts.map +1 -0
- package/dist/client/feature-flags/types.js +2 -0
- package/dist/client/feature-flags/types.js.map +1 -0
- package/dist/client/feature-flags/use-feature-flag.d.ts +8 -0
- package/dist/client/feature-flags/use-feature-flag.d.ts.map +1 -0
- package/dist/client/feature-flags/use-feature-flag.js +15 -0
- package/dist/client/feature-flags/use-feature-flag.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +1 -1
- package/dist/extensions/routes.js.map +1 -1
- package/dist/feature-flags/a2a-action-route.d.ts +14 -0
- package/dist/feature-flags/a2a-action-route.d.ts.map +1 -0
- package/dist/feature-flags/a2a-action-route.js +50 -0
- package/dist/feature-flags/a2a-action-route.js.map +1 -0
- package/dist/feature-flags/actions/get-feature-flags.d.ts +3 -0
- package/dist/feature-flags/actions/get-feature-flags.d.ts.map +1 -0
- package/dist/feature-flags/actions/get-feature-flags.js +18 -0
- package/dist/feature-flags/actions/get-feature-flags.js.map +1 -0
- package/dist/feature-flags/actions/list-feature-flags.d.ts +28 -0
- package/dist/feature-flags/actions/list-feature-flags.d.ts.map +1 -0
- package/dist/feature-flags/actions/list-feature-flags.js +60 -0
- package/dist/feature-flags/actions/list-feature-flags.js.map +1 -0
- package/dist/feature-flags/actions/set-feature-flag.d.ts +26 -0
- package/dist/feature-flags/actions/set-feature-flag.d.ts.map +1 -0
- package/dist/feature-flags/actions/set-feature-flag.js +78 -0
- package/dist/feature-flags/actions/set-feature-flag.js.map +1 -0
- package/dist/feature-flags/index.d.ts +5 -0
- package/dist/feature-flags/index.d.ts.map +1 -0
- package/dist/feature-flags/index.js +5 -0
- package/dist/feature-flags/index.js.map +1 -0
- package/dist/feature-flags/permissions.d.ts +13 -0
- package/dist/feature-flags/permissions.d.ts.map +1 -0
- package/dist/feature-flags/permissions.js +37 -0
- package/dist/feature-flags/permissions.js.map +1 -0
- package/dist/feature-flags/plugin.d.ts +17 -0
- package/dist/feature-flags/plugin.d.ts.map +1 -0
- package/dist/feature-flags/plugin.js +29 -0
- package/dist/feature-flags/plugin.js.map +1 -0
- package/dist/feature-flags/registry.d.ts +23 -0
- package/dist/feature-flags/registry.d.ts.map +1 -0
- package/dist/feature-flags/registry.js +59 -0
- package/dist/feature-flags/registry.js.map +1 -0
- package/dist/feature-flags/store.d.ts +31 -0
- package/dist/feature-flags/store.d.ts.map +1 -0
- package/dist/feature-flags/store.js +124 -0
- package/dist/feature-flags/store.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +62 -5
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/pending-tasks-store.d.ts +3 -0
- package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
- package/dist/integrations/pending-tasks-store.js +75 -1
- package/dist/integrations/pending-tasks-store.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +81 -5
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts +23 -2
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +257 -35
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/localization/default-messages.d.ts +42 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +42 -0
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +12 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts +3 -0
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +45 -5
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +6 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +71 -6
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/settings/org-settings.d.ts +2 -0
- package/dist/settings/org-settings.d.ts.map +1 -1
- package/dist/settings/org-settings.js +5 -1
- package/dist/settings/org-settings.js.map +1 -1
- package/dist/settings/store.d.ts +8 -0
- package/dist/settings/store.d.ts.map +1 -1
- package/dist/settings/store.js +48 -0
- package/dist/settings/store.js.map +1 -1
- package/dist/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
- package/dist/templates/default/.agents/skills/feature-flags/SKILL.md +169 -0
- package/dist/templates/default/AGENTS.md +1 -0
- package/dist/templates/headless/.agents/skills/feature-flags/SKILL.md +169 -0
- package/dist/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +169 -0
- package/dist/templates/workspace-core/AGENTS.md +1 -1
- package/dist/templates/workspace-root/AGENTS.md +1 -1
- package/dist/vite/action-types-plugin.d.ts.map +1 -1
- package/dist/vite/action-types-plugin.js +12 -0
- package/dist/vite/action-types-plugin.js.map +1 -1
- package/docs/content/actions.mdx +48 -0
- package/docs/content/locales/ar-SA/actions.mdx +38 -0
- package/docs/content/locales/de-DE/actions.mdx +38 -0
- package/docs/content/locales/es-ES/actions.mdx +38 -0
- package/docs/content/locales/fr-FR/actions.mdx +38 -0
- package/docs/content/locales/hi-IN/actions.mdx +38 -0
- package/docs/content/locales/ja-JP/actions.mdx +38 -0
- package/docs/content/locales/ko-KR/actions.mdx +38 -0
- package/docs/content/locales/pt-BR/actions.mdx +38 -0
- package/docs/content/locales/zh-CN/actions.mdx +38 -0
- package/docs/content/locales/zh-TW/actions.mdx +38 -0
- package/package.json +5 -1
- package/src/templates/chat/.agents/skills/feature-flags/SKILL.md +169 -0
- package/src/templates/default/.agents/skills/feature-flags/SKILL.md +169 -0
- package/src/templates/default/AGENTS.md +1 -0
- package/src/templates/headless/.agents/skills/feature-flags/SKILL.md +169 -0
- package/src/templates/workspace-core/.agents/skills/feature-flags/SKILL.md +169 -0
- package/src/templates/workspace-core/AGENTS.md +1 -1
- package/src/templates/workspace-root/AGENTS.md +1 -1
- package/corpus/templates/clips/actions/get-feature-flags.ts +0 -30
|
@@ -9,8 +9,10 @@ import {
|
|
|
9
9
|
getRequestURL,
|
|
10
10
|
} from "h3";
|
|
11
11
|
|
|
12
|
+
import { verifyA2ATokenWithClaims } from "../a2a-claims.js";
|
|
12
13
|
import { isAgentActionStopError } from "../action.js";
|
|
13
14
|
import type { ActionEntry } from "../agent/production-agent.js";
|
|
15
|
+
import { declaresFeatureFlagDelegation } from "../feature-flags/a2a-action-route.js";
|
|
14
16
|
import { resolveOrgIdForEmail } from "../org/context.js";
|
|
15
17
|
import { readBody } from "../server/h3-helpers.js";
|
|
16
18
|
import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
|
|
@@ -39,6 +41,30 @@ import { runWithRequestContext } from "./request-context.js";
|
|
|
39
41
|
|
|
40
42
|
const ROUTE_PREFIX = "/_agent-native/actions";
|
|
41
43
|
|
|
44
|
+
async function resolveFeatureFlagA2ACaller(event: any, actionName: string) {
|
|
45
|
+
const required =
|
|
46
|
+
actionName === "list-feature-flags"
|
|
47
|
+
? "flags:read"
|
|
48
|
+
: actionName === "set-feature-flag"
|
|
49
|
+
? "flags:write"
|
|
50
|
+
: null;
|
|
51
|
+
if (!required) return null;
|
|
52
|
+
const authorization = getHeader(event, "authorization");
|
|
53
|
+
if (!authorization?.startsWith("Bearer ")) return null;
|
|
54
|
+
const token = authorization.slice(7);
|
|
55
|
+
if (!declaresFeatureFlagDelegation(token)) return null;
|
|
56
|
+
const claims = await verifyA2ATokenWithClaims(token, event);
|
|
57
|
+
if (!claims || !claims.scope.includes(required))
|
|
58
|
+
throw new Error("Invalid feature flag delegation");
|
|
59
|
+
return {
|
|
60
|
+
owner: claims.email,
|
|
61
|
+
orgId: claims.orgId,
|
|
62
|
+
anonymous: false,
|
|
63
|
+
delegationJti: claims.jti,
|
|
64
|
+
delegationIssuer: claims.issuer,
|
|
65
|
+
} as ActionRouteResolvedCaller;
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
export function parseActionSearchParams(
|
|
43
69
|
searchParams: URLSearchParams,
|
|
44
70
|
): Record<string, any> {
|
|
@@ -189,6 +215,9 @@ export type ActionRouteResolvedCaller = AgentRunOwnerContext & {
|
|
|
189
215
|
* must not leak into the token caller's request context.
|
|
190
216
|
*/
|
|
191
217
|
orgId?: string;
|
|
218
|
+
/** Verified A2A correlation and issuer metadata for the audit row. */
|
|
219
|
+
delegationJti?: string;
|
|
220
|
+
delegationIssuer?: string;
|
|
192
221
|
};
|
|
193
222
|
|
|
194
223
|
export interface ActionRouteAuthAdapter {
|
|
@@ -336,10 +365,14 @@ export function mountActionRoutes(
|
|
|
336
365
|
// through, so a live same-origin session cookie can't silently execute
|
|
337
366
|
// the request as the logged-in user.
|
|
338
367
|
let resolvedCaller: ActionRouteResolvedCaller | null = null;
|
|
339
|
-
|
|
368
|
+
{
|
|
340
369
|
let caller: ActionRouteResolvedCaller | null;
|
|
341
370
|
try {
|
|
342
|
-
caller =
|
|
371
|
+
caller = options?.actionRouteAuth?.resolveCaller
|
|
372
|
+
? await options.actionRouteAuth.resolveCaller(event)
|
|
373
|
+
: null;
|
|
374
|
+
if (!caller)
|
|
375
|
+
caller = await resolveFeatureFlagA2ACaller(event, name);
|
|
343
376
|
} catch {
|
|
344
377
|
throw createError({
|
|
345
378
|
statusCode: 401,
|
|
@@ -463,14 +496,23 @@ export function mountActionRoutes(
|
|
|
463
496
|
// userEmail / orgId mirror the request context resolved above (do
|
|
464
497
|
// NOT inject a dev identity — leave undefined when unauthenticated).
|
|
465
498
|
try {
|
|
466
|
-
const caller =
|
|
467
|
-
? "
|
|
468
|
-
:
|
|
499
|
+
const caller = resolvedCaller
|
|
500
|
+
? "a2a"
|
|
501
|
+
: isFrontendActionRequest(event)
|
|
502
|
+
? "frontend"
|
|
503
|
+
: "http";
|
|
469
504
|
const result = await entry.run(params, {
|
|
470
505
|
userEmail,
|
|
471
506
|
orgId: orgId ?? null,
|
|
472
507
|
caller,
|
|
473
508
|
actionName: name,
|
|
509
|
+
...(resolvedCaller?.delegationJti
|
|
510
|
+
? {
|
|
511
|
+
networkProtocol: "a2a",
|
|
512
|
+
networkId: resolvedCaller.delegationJti,
|
|
513
|
+
networkPeer: resolvedCaller.delegationIssuer,
|
|
514
|
+
}
|
|
515
|
+
: {}),
|
|
474
516
|
});
|
|
475
517
|
|
|
476
518
|
// Auto-refresh the UI after a successful mutating action. GET
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
} from "h3";
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
|
+
appendA2AArtifactLinks,
|
|
17
18
|
buildA2ARecoverableArtifactMessage,
|
|
18
19
|
type A2AToolResultSummary,
|
|
19
20
|
} from "../a2a/artifact-response.js";
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
createA2AApproval,
|
|
28
29
|
updateTaskStatusMessage,
|
|
29
30
|
} from "../a2a/task-store.js";
|
|
31
|
+
import type { Message as A2AMessage } from "../a2a/types.js";
|
|
30
32
|
import type { ActionHttpConfig } from "../action.js";
|
|
31
33
|
import {
|
|
32
34
|
canUpdateAgentAppModelDefaultSettings,
|
|
@@ -275,6 +277,74 @@ export { loadRunCodeToolEntries };
|
|
|
275
277
|
export { shouldDisableRecurringJobsRuntime };
|
|
276
278
|
export { finalizeClaimedAgentChatProcessRunFailure };
|
|
277
279
|
|
|
280
|
+
export function createSerializedA2ATaskStatusWriter(
|
|
281
|
+
taskId: string,
|
|
282
|
+
writeStatus: (
|
|
283
|
+
taskId: string,
|
|
284
|
+
message: A2AMessage,
|
|
285
|
+
) => Promise<void> = updateTaskStatusMessage,
|
|
286
|
+
onError: (error: unknown) => void = (error) => {
|
|
287
|
+
console.error(
|
|
288
|
+
`[A2A] Failed to persist recoverable artifact message for task ${taskId}:`,
|
|
289
|
+
error,
|
|
290
|
+
);
|
|
291
|
+
},
|
|
292
|
+
): {
|
|
293
|
+
enqueue: (message: A2AMessage) => void;
|
|
294
|
+
flush: () => Promise<void>;
|
|
295
|
+
} {
|
|
296
|
+
const maxAttempts = 3;
|
|
297
|
+
let latestWrite: Promise<void> = Promise.resolve();
|
|
298
|
+
|
|
299
|
+
const persistWithRetry = async (message: A2AMessage): Promise<void> => {
|
|
300
|
+
let lastError: unknown;
|
|
301
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
302
|
+
try {
|
|
303
|
+
await writeStatus(taskId, message);
|
|
304
|
+
return;
|
|
305
|
+
} catch (error) {
|
|
306
|
+
lastError = error;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
onError(lastError);
|
|
311
|
+
} catch {
|
|
312
|
+
// The durable-write error below remains the authoritative failure.
|
|
313
|
+
}
|
|
314
|
+
throw lastError;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
enqueue(message) {
|
|
319
|
+
// A newer checkpoint supersedes an earlier failed checkpoint, so keep
|
|
320
|
+
// the queue moving. flush() still rejects when the latest write itself
|
|
321
|
+
// cannot be made durable after bounded retries.
|
|
322
|
+
latestWrite = latestWrite
|
|
323
|
+
.catch(() => undefined)
|
|
324
|
+
.then(() => {
|
|
325
|
+
return persistWithRetry(message);
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
flush() {
|
|
329
|
+
return latestWrite;
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export async function resolveA2ARecoverableArtifactSecret(
|
|
335
|
+
orgId: string | null | undefined = getRequestOrgId(),
|
|
336
|
+
): Promise<string | undefined> {
|
|
337
|
+
const globalSecret = process.env.A2A_SECRET?.trim();
|
|
338
|
+
if (globalSecret) return globalSecret;
|
|
339
|
+
if (!orgId) return undefined;
|
|
340
|
+
try {
|
|
341
|
+
const { getOrgA2ASecret } = await import("../org/context.js");
|
|
342
|
+
return (await getOrgA2ASecret(orgId))?.trim() || undefined;
|
|
343
|
+
} catch {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
278
348
|
export function buildLeanRunPolicyPrompt(
|
|
279
349
|
codeEditingSurfaceRestriction: string,
|
|
280
350
|
prodCodeExecPromptNote: string,
|
|
@@ -1468,6 +1538,10 @@ export function createAgentChatPlugin(
|
|
|
1468
1538
|
const a2aEvents: AgentChatEvent[] = [];
|
|
1469
1539
|
const a2aToolResults: A2AToolResultSummary[] = [];
|
|
1470
1540
|
let lastRecoverableArtifactText = "";
|
|
1541
|
+
const recoverableArtifactSecret =
|
|
1542
|
+
await resolveA2ARecoverableArtifactSecret();
|
|
1543
|
+
const recoverableArtifactStatusWriter =
|
|
1544
|
+
createSerializedA2ATaskStatusWriter(context.taskId);
|
|
1471
1545
|
const controller = new AbortController();
|
|
1472
1546
|
|
|
1473
1547
|
console.log(
|
|
@@ -1504,16 +1578,28 @@ export function createAgentChatPlugin(
|
|
|
1504
1578
|
isError: event.isError,
|
|
1505
1579
|
completedSideEffect: event.completedSideEffect,
|
|
1506
1580
|
});
|
|
1507
|
-
const
|
|
1581
|
+
const artifactBaseUrl = resolveArtifactBaseUrl(context.event);
|
|
1582
|
+
const recoverableArtifactMessage =
|
|
1508
1583
|
buildA2ARecoverableArtifactMessage(a2aToolResults, {
|
|
1509
|
-
baseUrl:
|
|
1584
|
+
baseUrl: artifactBaseUrl,
|
|
1510
1585
|
});
|
|
1586
|
+
const recoverableArtifactText = recoverableArtifactMessage
|
|
1587
|
+
? appendA2AArtifactLinks(
|
|
1588
|
+
recoverableArtifactMessage,
|
|
1589
|
+
a2aToolResults,
|
|
1590
|
+
{
|
|
1591
|
+
baseUrl: artifactBaseUrl,
|
|
1592
|
+
includePersistedArtifactMarker: true,
|
|
1593
|
+
persistedArtifactSecret: recoverableArtifactSecret,
|
|
1594
|
+
},
|
|
1595
|
+
)
|
|
1596
|
+
: null;
|
|
1511
1597
|
if (
|
|
1512
1598
|
recoverableArtifactText &&
|
|
1513
1599
|
recoverableArtifactText !== lastRecoverableArtifactText
|
|
1514
1600
|
) {
|
|
1515
1601
|
lastRecoverableArtifactText = recoverableArtifactText;
|
|
1516
|
-
|
|
1602
|
+
recoverableArtifactStatusWriter.enqueue({
|
|
1517
1603
|
role: "agent",
|
|
1518
1604
|
metadata: { agentNativeRecoverableArtifacts: true },
|
|
1519
1605
|
parts: [
|
|
@@ -1522,11 +1608,6 @@ export function createAgentChatPlugin(
|
|
|
1522
1608
|
text: recoverableArtifactText,
|
|
1523
1609
|
},
|
|
1524
1610
|
],
|
|
1525
|
-
}).catch((err) => {
|
|
1526
|
-
console.error(
|
|
1527
|
-
`[A2A] Failed to persist recoverable artifact message for task ${context.taskId}:`,
|
|
1528
|
-
err,
|
|
1529
|
-
);
|
|
1530
1611
|
});
|
|
1531
1612
|
}
|
|
1532
1613
|
} else if (event.type === "error") {
|
|
@@ -1548,6 +1629,10 @@ export function createAgentChatPlugin(
|
|
|
1548
1629
|
},
|
|
1549
1630
|
);
|
|
1550
1631
|
|
|
1632
|
+
// The continuation can observe terminal output immediately, so make
|
|
1633
|
+
// its latest mutation checkpoint durable first.
|
|
1634
|
+
await recoverableArtifactStatusWriter.flush();
|
|
1635
|
+
|
|
1551
1636
|
const approval = [...a2aEvents]
|
|
1552
1637
|
.reverse()
|
|
1553
1638
|
.find(
|
|
@@ -180,6 +180,7 @@ export { createSentryPlugin, defaultSentryPlugin } from "./sentry-plugin.js";
|
|
|
180
180
|
// (which references "defaultOrgPlugin" from @agent-native/core/server) can
|
|
181
181
|
// resolve it during the deploy build worker-entry generation.
|
|
182
182
|
export { createOrgPlugin, defaultOrgPlugin } from "../org/plugin.js";
|
|
183
|
+
export { createFeatureFlagsPlugin } from "../feature-flags/plugin.js";
|
|
183
184
|
export {
|
|
184
185
|
createContextXrayPlugin,
|
|
185
186
|
defaultContextXrayPlugin,
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
getSetting,
|
|
13
|
+
mutateSetting,
|
|
13
14
|
putSetting,
|
|
14
15
|
deleteSetting,
|
|
15
16
|
getAllSettings,
|
|
@@ -40,6 +41,18 @@ export async function putOrgSetting(
|
|
|
40
41
|
return putSetting(orgKey(orgId, key), value, options);
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
/** Atomically derive and persist an org-scoped setting. */
|
|
45
|
+
export async function mutateOrgSetting(
|
|
46
|
+
orgId: string,
|
|
47
|
+
key: string,
|
|
48
|
+
updater: (
|
|
49
|
+
current: Record<string, unknown> | null,
|
|
50
|
+
) => Record<string, unknown> | Promise<Record<string, unknown>>,
|
|
51
|
+
options?: StoreWriteOptions,
|
|
52
|
+
): Promise<Record<string, unknown>> {
|
|
53
|
+
return mutateSetting(orgKey(orgId, key), updater, options);
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
/** Delete an org-scoped setting. */
|
|
44
57
|
export async function deleteOrgSetting(
|
|
45
58
|
orgId: string,
|
|
@@ -128,6 +128,63 @@ export interface StoreWriteOptions {
|
|
|
128
128
|
requestSource?: string;
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
const SETTINGS_MUTATION_ATTEMPTS = 25;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Atomically derive and persist one setting with an optimistic raw-value CAS.
|
|
135
|
+
* This works across SQLite/libSQL and Postgres and remains safe across
|
|
136
|
+
* horizontally scaled processes where an in-memory mutex would not.
|
|
137
|
+
* The updater may run more than once after contention and must not perform
|
|
138
|
+
* external side effects.
|
|
139
|
+
*/
|
|
140
|
+
export async function mutateSetting(
|
|
141
|
+
key: string,
|
|
142
|
+
updater: (
|
|
143
|
+
current: Record<string, unknown> | null,
|
|
144
|
+
) => Record<string, unknown> | Promise<Record<string, unknown>>,
|
|
145
|
+
options?: StoreWriteOptions,
|
|
146
|
+
): Promise<Record<string, unknown>> {
|
|
147
|
+
await ensureTable();
|
|
148
|
+
const client = getDbExec();
|
|
149
|
+
const table = settingsTable();
|
|
150
|
+
for (let attempt = 0; attempt < SETTINGS_MUTATION_ATTEMPTS; attempt += 1) {
|
|
151
|
+
// Deliberately bypass the request cache: a failed CAS means another
|
|
152
|
+
// request committed a newer value and the next attempt must reread it.
|
|
153
|
+
const snapshot = await client.execute({
|
|
154
|
+
sql: `SELECT value FROM ${table} WHERE key = ?`,
|
|
155
|
+
args: [key],
|
|
156
|
+
});
|
|
157
|
+
const raw =
|
|
158
|
+
snapshot.rows.length === 0 ? null : (snapshot.rows[0]?.value as string);
|
|
159
|
+
const current = raw == null ? null : JSON.parse(raw);
|
|
160
|
+
const next = await updater(current);
|
|
161
|
+
const nextRaw = JSON.stringify(next);
|
|
162
|
+
const timestamp = Date.now();
|
|
163
|
+
const result =
|
|
164
|
+
raw == null
|
|
165
|
+
? await client.execute({
|
|
166
|
+
sql: isPostgres()
|
|
167
|
+
? `INSERT INTO ${table} (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT (key) DO NOTHING`
|
|
168
|
+
: `INSERT OR IGNORE INTO ${table} (key, value, updated_at) VALUES (?, ?, ?)`,
|
|
169
|
+
args: [key, nextRaw, timestamp],
|
|
170
|
+
})
|
|
171
|
+
: await client.execute({
|
|
172
|
+
sql: `UPDATE ${table} SET value = ?, updated_at = ? WHERE key = ? AND value = ?`,
|
|
173
|
+
args: [nextRaw, timestamp, key, raw],
|
|
174
|
+
});
|
|
175
|
+
if (result.rowsAffected === 0) continue;
|
|
176
|
+
requestSettingsCache()?.set(key, nextRaw);
|
|
177
|
+
_emitter.emit("settings", {
|
|
178
|
+
source: "settings",
|
|
179
|
+
type: "change",
|
|
180
|
+
key,
|
|
181
|
+
...(options?.requestSource && { requestSource: options.requestSource }),
|
|
182
|
+
});
|
|
183
|
+
return JSON.parse(nextRaw);
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`Setting ${key} changed too many times; retry the mutation.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
131
188
|
export async function putSetting(
|
|
132
189
|
key: string,
|
|
133
190
|
value: Record<string, unknown>,
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature-flags
|
|
3
|
+
description: >-
|
|
4
|
+
Declare, evaluate, manage, and remove framework feature flags. Use when
|
|
5
|
+
shipping a capability gradually, targeting users or organizations, or
|
|
6
|
+
replacing a compile-time rollout switch with a production-safe runtime flag.
|
|
7
|
+
scope: dev
|
|
8
|
+
metadata:
|
|
9
|
+
internal: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Feature Flags
|
|
13
|
+
|
|
14
|
+
A feature flag is a boolean declared in app code, evaluated locally by Core,
|
|
15
|
+
and managed from the Analytics fleet control plane. Code owns whether a flag
|
|
16
|
+
exists. Runtime settings own only its rollout state.
|
|
17
|
+
|
|
18
|
+
Flags let an app deploy dormant code and turn it on in the real environment
|
|
19
|
+
without another deployment. They are not experiments: do not add variants,
|
|
20
|
+
hypotheses, conversion metrics, exposure tracking, or lifecycle states.
|
|
21
|
+
|
|
22
|
+
## When to use one
|
|
23
|
+
|
|
24
|
+
Use a flag for a reversible rollout of a user-facing capability whose dormant
|
|
25
|
+
code is safe to deploy. Flags are useful for production dogfooding, exact-user
|
|
26
|
+
or organization pilots, and deterministic percentage rollouts.
|
|
27
|
+
|
|
28
|
+
Do not use a flag for authentication, authorization, secrets, audit enablement,
|
|
29
|
+
SSR cache behavior, or another security boundary. Client hiding is presentation
|
|
30
|
+
only; every guarded server action must evaluate the same registered flag.
|
|
31
|
+
|
|
32
|
+
## Agent workflow
|
|
33
|
+
|
|
34
|
+
### 1. Declare
|
|
35
|
+
|
|
36
|
+
Keep definitions in a shared TypeScript module so server and client code use the
|
|
37
|
+
same stable key. Flags are boolean and default-off.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { defineFeatureFlag } from "@agent-native/core/feature-flags";
|
|
41
|
+
|
|
42
|
+
export const FULL_APP_BUILDING = defineFeatureFlag({
|
|
43
|
+
key: "full-app-building",
|
|
44
|
+
displayName: "Full app building",
|
|
45
|
+
description: "Create and edit Fusion-backed applications.",
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Keys are immutable, never reused, and contain only letters, numbers, dots,
|
|
50
|
+
underscores, or hyphens. Prefer a concise app-owned name. Do not create flag
|
|
51
|
+
definitions or rollout rows from Analytics.
|
|
52
|
+
|
|
53
|
+
### 2. Register
|
|
54
|
+
|
|
55
|
+
Register app definitions from a Nitro plugin before actions are discovered.
|
|
56
|
+
Do not add app-specific flags to a Core registry.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createFeatureFlagsPlugin } from "@agent-native/core/server";
|
|
60
|
+
|
|
61
|
+
import { FULL_APP_BUILDING } from "../../shared/feature-flags.js";
|
|
62
|
+
|
|
63
|
+
export default createFeatureFlagsPlugin({ flags: [FULL_APP_BUILDING] });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 3. Guard server and client
|
|
67
|
+
|
|
68
|
+
The server action is the enforcement boundary:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
|
|
72
|
+
|
|
73
|
+
run: async (args, ctx) => {
|
|
74
|
+
if (!(await isFeatureFlagEnabled(FULL_APP_BUILDING, ctx))) {
|
|
75
|
+
throw new Error("Full app building is not enabled for this account.");
|
|
76
|
+
}
|
|
77
|
+
// guarded operation
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use the client hook only to hide or reveal hydrated UI:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { useFeatureFlag } from "@agent-native/core/client";
|
|
85
|
+
|
|
86
|
+
const enabled = useFeatureFlag(FULL_APP_BUILDING.key);
|
|
87
|
+
return enabled ? <FullAppOption /> : null;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The client hook intentionally returns false while loading or for an unknown
|
|
91
|
+
flag. Never replace that fail-closed behavior with app-local bucketing or a
|
|
92
|
+
compile-time fallback. Never evaluate personalized flags in the public SSR
|
|
93
|
+
shell; it is shared and cached for every visitor.
|
|
94
|
+
|
|
95
|
+
### 4. Verify and roll out
|
|
96
|
+
|
|
97
|
+
1. Verify the off path before changing rollout state.
|
|
98
|
+
2. Confirm the registered flag appears in **Analytics → Feature flags** for the
|
|
99
|
+
app and is Off by default.
|
|
100
|
+
3. Use **Enable for me** for initial production dogfood.
|
|
101
|
+
4. Expand to exact emails, organization IDs, or a percentage only from
|
|
102
|
+
Analytics.
|
|
103
|
+
5. Confirm the client presentation and authoritative server action agree.
|
|
104
|
+
|
|
105
|
+
## Management contract
|
|
106
|
+
|
|
107
|
+
Core mounts three actions in registered apps:
|
|
108
|
+
|
|
109
|
+
| Action | Purpose |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `get-feature-flags` | Return the current caller's evaluated boolean values. |
|
|
112
|
+
| `list-feature-flags` | Return definitions and rollout metadata to an authorized operator. |
|
|
113
|
+
| `set-feature-flag` | Atomically turn a flag off, enable it for the operator, or replace targeting rules. |
|
|
114
|
+
|
|
115
|
+
Analytics calls the app-local operator actions through narrowly scoped A2A
|
|
116
|
+
delegation. Tokens require an exact audience, organization, scope, operator
|
|
117
|
+
role, and audit correlation id. Management is permission-checked and audited
|
|
118
|
+
by the target app. Never manage flags through generic settings routes, raw SQL,
|
|
119
|
+
or per-app toggle UIs.
|
|
120
|
+
|
|
121
|
+
## Rollout semantics
|
|
122
|
+
|
|
123
|
+
The operator modes are **Off**, **Targeted**, and **Everyone**. Core stores them
|
|
124
|
+
as `off`, `rules`, and `on`.
|
|
125
|
+
|
|
126
|
+
Targeted rules combine exact normalized emails, exact organization IDs, and a
|
|
127
|
+
percentage with OR semantics. Exact matches are checked first. Percentage
|
|
128
|
+
buckets use Core's stable hash of the flag key and authenticated user identity;
|
|
129
|
+
anonymous callers fail closed. Raising a percentage preserves the users already
|
|
130
|
+
included at a lower percentage. Do not implement bucketing in app code.
|
|
131
|
+
|
|
132
|
+
Unknown definitions, missing state, malformed state, storage errors, and
|
|
133
|
+
evaluation errors all return the code default (`false` in v1). Explicit Off
|
|
134
|
+
wins over every target; Everyone enables every authenticated caller.
|
|
135
|
+
|
|
136
|
+
## Remove a flag
|
|
137
|
+
|
|
138
|
+
After a rollout is permanent:
|
|
139
|
+
|
|
140
|
+
1. Replace guarded branches with the chosen behavior.
|
|
141
|
+
2. Delete the server and client gates.
|
|
142
|
+
3. Delete the definition and registration entry.
|
|
143
|
+
4. Verify the flag disappears from the Analytics fleet.
|
|
144
|
+
5. Remove stale tests and rollout instructions.
|
|
145
|
+
|
|
146
|
+
A permanent flag is just an if statement with a pension plan.
|
|
147
|
+
|
|
148
|
+
## Verification checklist
|
|
149
|
+
|
|
150
|
+
- Unknown and unregistered keys evaluate false.
|
|
151
|
+
- UI hiding and server enforcement use the same registered key.
|
|
152
|
+
- Exact-user, organization, deterministic percentage, Everyone, and Off paths
|
|
153
|
+
have focused tests.
|
|
154
|
+
- Increasing a percentage is monotonic; anonymous percentage evaluation is off.
|
|
155
|
+
- Unauthorized callers cannot list targeting details or mutate flags.
|
|
156
|
+
- Mutations are atomic, read back stored state, emit refresh, and appear in the
|
|
157
|
+
audit log with the flag key.
|
|
158
|
+
- Analytics represents ready, no-definition, unsupported, forbidden, legacy,
|
|
159
|
+
and unreachable directory apps honestly.
|
|
160
|
+
- Future agents can find this skill from root `AGENTS.md`, and
|
|
161
|
+
`pnpm guard:workspace-skills` passes after syncing generated copies.
|
|
162
|
+
|
|
163
|
+
## Related skills
|
|
164
|
+
|
|
165
|
+
- **adding-a-feature** — preserve UI/action/instruction/application-state parity
|
|
166
|
+
- **actions** — define and call guarded app operations
|
|
167
|
+
- **audit-log** — inspect automatic action mutation history
|
|
168
|
+
- **reliable-mutations** — make rollout changes atomic and provable
|
|
169
|
+
- **security** — keep security controls out of feature flags
|
|
@@ -158,6 +158,7 @@ Skills in `.agents/skills/` provide detailed guidance for each architectural rul
|
|
|
158
158
|
| ---------------------- | --------------------------------------------------------------------------------- |
|
|
159
159
|
| `agent-native-docs` | Before using advanced Agent Native framework APIs or generated-app features |
|
|
160
160
|
| `adding-a-feature` | **Read first when adding ANY new feature** — the four-area parity checklist |
|
|
161
|
+
| `feature-flags` | Before shipping a staged production rollout or replacing a compile-time switch |
|
|
161
162
|
| `real-time-sync` | Before wiring data fetching for anything the agent can mutate (must auto-refresh) |
|
|
162
163
|
| `storing-data` | Before storing or reading any app state |
|
|
163
164
|
| `internationalization` | Before adding or editing visible UI copy, prompts, toasts, labels, or formatting |
|