@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
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { getOrgSetting, mutateOrgSetting } from "../settings/org-settings.js";
|
|
2
|
+
import { getSetting, mutateSetting } from "../settings/store.js";
|
|
3
|
+
import {
|
|
4
|
+
getFeatureFlagDefinition,
|
|
5
|
+
type FeatureFlagDefinition,
|
|
6
|
+
} from "./registry.js";
|
|
7
|
+
|
|
8
|
+
export type FeatureFlagMode = "off" | "on" | "rules";
|
|
9
|
+
|
|
10
|
+
export interface FeatureFlagRules {
|
|
11
|
+
version: 1;
|
|
12
|
+
mode: FeatureFlagMode;
|
|
13
|
+
emails: string[];
|
|
14
|
+
orgIds: string[];
|
|
15
|
+
percentage: number;
|
|
16
|
+
updatedAt: number | null;
|
|
17
|
+
updatedBy: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FeatureFlagScope {
|
|
21
|
+
userEmail?: string;
|
|
22
|
+
/** Canonical authenticated identity. V1 callers use normalized email. */
|
|
23
|
+
userKey?: string;
|
|
24
|
+
orgId?: string | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const FEATURE_FLAG_SETTINGS_PREFIX = "feature-flag:";
|
|
28
|
+
|
|
29
|
+
function settingKey(key: string): string {
|
|
30
|
+
return `${FEATURE_FLAG_SETTINGS_PREFIX}${key}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function defaultFeatureFlagRules(): FeatureFlagRules {
|
|
34
|
+
return {
|
|
35
|
+
version: 1,
|
|
36
|
+
mode: "off",
|
|
37
|
+
emails: [],
|
|
38
|
+
orgIds: [],
|
|
39
|
+
percentage: 0,
|
|
40
|
+
updatedAt: null,
|
|
41
|
+
updatedBy: null,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stringList(value: unknown): string[] {
|
|
46
|
+
if (!Array.isArray(value)) return [];
|
|
47
|
+
return [
|
|
48
|
+
...new Set(
|
|
49
|
+
value
|
|
50
|
+
.filter((item): item is string => typeof item === "string")
|
|
51
|
+
.map((item) => item.trim())
|
|
52
|
+
.filter(Boolean),
|
|
53
|
+
),
|
|
54
|
+
].sort();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeFeatureFlagRules(value: unknown): FeatureFlagRules {
|
|
58
|
+
if (!value || typeof value !== "object") return defaultFeatureFlagRules();
|
|
59
|
+
const raw = value as Record<string, unknown>;
|
|
60
|
+
const mode: FeatureFlagMode =
|
|
61
|
+
raw.mode === "on" || raw.mode === "rules" || raw.mode === "off"
|
|
62
|
+
? raw.mode
|
|
63
|
+
: "off";
|
|
64
|
+
const percentage =
|
|
65
|
+
typeof raw.percentage === "number" && Number.isFinite(raw.percentage)
|
|
66
|
+
? Math.max(0, Math.min(100, Math.floor(raw.percentage)))
|
|
67
|
+
: 0;
|
|
68
|
+
return {
|
|
69
|
+
version: 1,
|
|
70
|
+
mode,
|
|
71
|
+
emails: stringList(raw.emails).map((email) => email.toLowerCase()),
|
|
72
|
+
orgIds: stringList(raw.orgIds),
|
|
73
|
+
percentage,
|
|
74
|
+
updatedAt:
|
|
75
|
+
typeof raw.updatedAt === "number" && Number.isSafeInteger(raw.updatedAt)
|
|
76
|
+
? raw.updatedAt
|
|
77
|
+
: null,
|
|
78
|
+
updatedBy:
|
|
79
|
+
typeof raw.updatedBy === "string" && raw.updatedBy.trim()
|
|
80
|
+
? raw.updatedBy.trim().toLowerCase()
|
|
81
|
+
: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function getFeatureFlagRules(
|
|
86
|
+
key: string,
|
|
87
|
+
scope: Pick<FeatureFlagScope, "orgId">,
|
|
88
|
+
): Promise<FeatureFlagRules> {
|
|
89
|
+
if (!getFeatureFlagDefinition(key)) return defaultFeatureFlagRules();
|
|
90
|
+
// An organization-specific rule overrides the global rule. The fallback is
|
|
91
|
+
// what makes global exact-org targeting meaningful for callers in an org.
|
|
92
|
+
const stored = scope.orgId?.trim()
|
|
93
|
+
? ((await getOrgSetting(scope.orgId, settingKey(key))) ??
|
|
94
|
+
(await getSetting(settingKey(key))))
|
|
95
|
+
: await getSetting(settingKey(key));
|
|
96
|
+
return normalizeFeatureFlagRules(stored);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Atomically derive one flag's scoped rules. An org's first override starts
|
|
101
|
+
* from the global fallback, then becomes independently CAS-protected.
|
|
102
|
+
*/
|
|
103
|
+
export async function mutateFeatureFlagRules(
|
|
104
|
+
key: string,
|
|
105
|
+
scope: Pick<FeatureFlagScope, "orgId">,
|
|
106
|
+
updater: (
|
|
107
|
+
current: FeatureFlagRules,
|
|
108
|
+
) => FeatureFlagRules | Promise<FeatureFlagRules>,
|
|
109
|
+
): Promise<FeatureFlagRules> {
|
|
110
|
+
if (!getFeatureFlagDefinition(key)) {
|
|
111
|
+
throw new Error(`Unknown feature flag: ${key}`);
|
|
112
|
+
}
|
|
113
|
+
const mutate = async (stored: Record<string, unknown> | null) => {
|
|
114
|
+
const fallback =
|
|
115
|
+
stored == null && scope.orgId?.trim()
|
|
116
|
+
? await getSetting(settingKey(key))
|
|
117
|
+
: null;
|
|
118
|
+
return {
|
|
119
|
+
...(await updater(normalizeFeatureFlagRules(stored ?? fallback))),
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
const persisted = scope.orgId?.trim()
|
|
123
|
+
? await mutateOrgSetting(scope.orgId, settingKey(key), mutate)
|
|
124
|
+
: await mutateSetting(settingKey(key), mutate);
|
|
125
|
+
return normalizeFeatureFlagRules(persisted);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function rolloutBucket(input: string): number {
|
|
129
|
+
// FNV-1a is deliberately tiny, deterministic, and independent of runtime.
|
|
130
|
+
let hash = 0x811c9dc5;
|
|
131
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
132
|
+
hash ^= input.charCodeAt(index);
|
|
133
|
+
hash = Math.imul(hash, 0x01000193);
|
|
134
|
+
}
|
|
135
|
+
return (hash >>> 0) % 100;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function evaluateFeatureFlagRules(
|
|
139
|
+
key: string,
|
|
140
|
+
rules: FeatureFlagRules,
|
|
141
|
+
scope: FeatureFlagScope,
|
|
142
|
+
): boolean {
|
|
143
|
+
if (rules.mode === "off") return false;
|
|
144
|
+
if (rules.mode === "on") return true;
|
|
145
|
+
const email = scope.userEmail?.trim().toLowerCase();
|
|
146
|
+
if (email && rules.emails.includes(email)) return true;
|
|
147
|
+
if (scope.orgId && rules.orgIds.includes(scope.orgId)) return true;
|
|
148
|
+
const userKey = scope.userKey?.trim() || email;
|
|
149
|
+
if (!userKey || rules.percentage <= 0) return false;
|
|
150
|
+
return rolloutBucket(`${key}:${userKey}`) < rules.percentage;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function evaluateFeatureFlag(
|
|
154
|
+
key: string,
|
|
155
|
+
scope: FeatureFlagScope = {},
|
|
156
|
+
): Promise<boolean> {
|
|
157
|
+
if (!getFeatureFlagDefinition(key)) return false;
|
|
158
|
+
try {
|
|
159
|
+
return evaluateFeatureFlagRules(
|
|
160
|
+
key,
|
|
161
|
+
await getFeatureFlagRules(key, scope),
|
|
162
|
+
scope,
|
|
163
|
+
);
|
|
164
|
+
} catch {
|
|
165
|
+
// A feature flag must never become an availability dependency.
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Ergonomic app-action guard. Accepts either a registered definition or its key. */
|
|
171
|
+
export async function isFeatureFlagEnabled(
|
|
172
|
+
flag: string | FeatureFlagDefinition,
|
|
173
|
+
scope: FeatureFlagScope = {},
|
|
174
|
+
): Promise<boolean> {
|
|
175
|
+
return evaluateFeatureFlag(typeof flag === "string" ? flag : flag.key, scope);
|
|
176
|
+
}
|
package/corpus/core/src/index.ts
CHANGED
|
@@ -142,6 +142,21 @@ export {
|
|
|
142
142
|
type AutomationWorkflowDefinition,
|
|
143
143
|
type JsonSchema,
|
|
144
144
|
} from "./automation/index.js";
|
|
145
|
+
export {
|
|
146
|
+
createFeatureFlagsPlugin,
|
|
147
|
+
defineFeatureFlag,
|
|
148
|
+
defineFeatureFlags,
|
|
149
|
+
evaluateFeatureFlag,
|
|
150
|
+
getFeatureFlagDefinition,
|
|
151
|
+
getFeatureFlagRules,
|
|
152
|
+
isFeatureFlagEnabled,
|
|
153
|
+
listFeatureFlags,
|
|
154
|
+
registerFeatureFlags,
|
|
155
|
+
type FeatureFlagDefinition,
|
|
156
|
+
type FeatureFlagMode,
|
|
157
|
+
type FeatureFlagRules,
|
|
158
|
+
type FeatureFlagScope,
|
|
159
|
+
} from "./feature-flags/index.js";
|
|
145
160
|
|
|
146
161
|
// Server
|
|
147
162
|
export {
|
|
@@ -171,11 +171,22 @@ async function processClaimedContinuation(
|
|
|
171
171
|
...(auth.apiKeyFallbacks ? { fallbackApiKeys: auth.apiKeyFallbacks } : {}),
|
|
172
172
|
});
|
|
173
173
|
const deadline = Date.now() + PROCESSOR_WAIT_MS;
|
|
174
|
+
const recoverableArtifactSecrets =
|
|
175
|
+
await resolveContinuationArtifactSecrets(continuation);
|
|
174
176
|
let task: Task | null = null;
|
|
177
|
+
let latestRecoverableArtifactText: string | null = null;
|
|
175
178
|
|
|
176
179
|
try {
|
|
177
180
|
while (Date.now() < deadline) {
|
|
178
181
|
task = await client.getTask(continuation.a2aTaskId);
|
|
182
|
+
const recoverableArtifactText = extractVerifiedRecoverableArtifactText(
|
|
183
|
+
task,
|
|
184
|
+
continuation.agentUrl,
|
|
185
|
+
recoverableArtifactSecrets,
|
|
186
|
+
);
|
|
187
|
+
if (recoverableArtifactText) {
|
|
188
|
+
latestRecoverableArtifactText = recoverableArtifactText;
|
|
189
|
+
}
|
|
179
190
|
if (TERMINAL_STATES.has(task.status.state)) break;
|
|
180
191
|
await reportA2AContinuationProgress(continuation, progress, task);
|
|
181
192
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
@@ -183,6 +194,17 @@ async function processClaimedContinuation(
|
|
|
183
194
|
} catch (err) {
|
|
184
195
|
if (isTransientA2APollError(err)) {
|
|
185
196
|
if (shouldStopPollingRemoteTask(continuation)) {
|
|
197
|
+
if (latestRecoverableArtifactText) {
|
|
198
|
+
await deliverAndCompleteA2AContinuation(
|
|
199
|
+
continuation,
|
|
200
|
+
adapter,
|
|
201
|
+
formatRecoverableArtifactFallbackText(
|
|
202
|
+
latestRecoverableArtifactText,
|
|
203
|
+
),
|
|
204
|
+
progress,
|
|
205
|
+
);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
186
208
|
await notifyAndFailA2AContinuation(
|
|
187
209
|
continuation,
|
|
188
210
|
adapter,
|
|
@@ -209,6 +231,15 @@ async function processClaimedContinuation(
|
|
|
209
231
|
|
|
210
232
|
if (!task || !TERMINAL_STATES.has(task.status.state)) {
|
|
211
233
|
if (shouldStopPollingRemoteTask(continuation)) {
|
|
234
|
+
if (latestRecoverableArtifactText) {
|
|
235
|
+
await deliverAndCompleteA2AContinuation(
|
|
236
|
+
continuation,
|
|
237
|
+
adapter,
|
|
238
|
+
formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
|
|
239
|
+
progress,
|
|
240
|
+
);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
212
243
|
await notifyAndFailA2AContinuation(
|
|
213
244
|
continuation,
|
|
214
245
|
adapter,
|
|
@@ -222,6 +253,15 @@ async function processClaimedContinuation(
|
|
|
222
253
|
}
|
|
223
254
|
|
|
224
255
|
if (task.status.state !== "completed") {
|
|
256
|
+
if (latestRecoverableArtifactText) {
|
|
257
|
+
await deliverAndCompleteA2AContinuation(
|
|
258
|
+
continuation,
|
|
259
|
+
adapter,
|
|
260
|
+
formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
|
|
261
|
+
progress,
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
225
265
|
const reason =
|
|
226
266
|
extractTaskText(task) ||
|
|
227
267
|
`Remote A2A task ${continuation.a2aTaskId} ended with state ${task.status.state}`;
|
|
@@ -234,6 +274,15 @@ async function processClaimedContinuation(
|
|
|
234
274
|
continuation.agentUrl,
|
|
235
275
|
);
|
|
236
276
|
if (!text.trim()) {
|
|
277
|
+
if (latestRecoverableArtifactText) {
|
|
278
|
+
await deliverAndCompleteA2AContinuation(
|
|
279
|
+
continuation,
|
|
280
|
+
adapter,
|
|
281
|
+
formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
|
|
282
|
+
progress,
|
|
283
|
+
);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
237
286
|
await notifyAndFailA2AContinuation(
|
|
238
287
|
continuation,
|
|
239
288
|
adapter,
|
|
@@ -382,6 +431,8 @@ async function deliverAndCompleteA2AContinuation(
|
|
|
382
431
|
`${deliveryContinuation.platform} response delivery timed out`,
|
|
383
432
|
);
|
|
384
433
|
let persistenceError: unknown;
|
|
434
|
+
const artifactSecrets =
|
|
435
|
+
await resolveContinuationArtifactSecrets(deliveryContinuation);
|
|
385
436
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
386
437
|
try {
|
|
387
438
|
await persistA2AContinuationDelivery(
|
|
@@ -389,6 +440,7 @@ async function deliverAndCompleteA2AContinuation(
|
|
|
389
440
|
outgoing,
|
|
390
441
|
deliveryReceipt,
|
|
391
442
|
text,
|
|
443
|
+
artifactSecrets,
|
|
392
444
|
);
|
|
393
445
|
persistenceError = undefined;
|
|
394
446
|
break;
|
|
@@ -463,6 +515,7 @@ async function persistA2AContinuationDelivery(
|
|
|
463
515
|
outgoing: OutgoingMessage,
|
|
464
516
|
receipt: PlatformDeliveryReceipt,
|
|
465
517
|
artifactText: string,
|
|
518
|
+
artifactSecrets: readonly string[],
|
|
466
519
|
): Promise<void> {
|
|
467
520
|
const mapping = await getThreadMapping(
|
|
468
521
|
continuation.platform,
|
|
@@ -480,9 +533,12 @@ async function persistA2AContinuationDelivery(
|
|
|
480
533
|
}
|
|
481
534
|
if (!Array.isArray(repo.messages)) repo.messages = [];
|
|
482
535
|
|
|
483
|
-
const artifacts = extractA2AArtifactIdentities(
|
|
484
|
-
{ tool: "call-agent", result: artifactText },
|
|
485
|
-
|
|
536
|
+
const artifacts = extractA2AArtifactIdentities(
|
|
537
|
+
[{ tool: "call-agent", result: artifactText }],
|
|
538
|
+
{
|
|
539
|
+
persistedArtifactSecrets: artifactSecrets,
|
|
540
|
+
},
|
|
541
|
+
);
|
|
486
542
|
const metadata: Record<string, unknown> = {
|
|
487
543
|
integrationDeliveryAttempted: true,
|
|
488
544
|
integrationDelivery: {
|
|
@@ -726,6 +782,24 @@ async function signFreshContinuationTokens(
|
|
|
726
782
|
return tokens;
|
|
727
783
|
}
|
|
728
784
|
|
|
785
|
+
async function resolveContinuationArtifactSecrets(
|
|
786
|
+
continuation: A2AContinuation,
|
|
787
|
+
): Promise<string[]> {
|
|
788
|
+
const secrets: string[] = [];
|
|
789
|
+
const add = (secret: string | null | undefined) => {
|
|
790
|
+
const value = secret?.trim();
|
|
791
|
+
if (value && !secrets.includes(value)) secrets.push(value);
|
|
792
|
+
};
|
|
793
|
+
add(process.env.A2A_SECRET);
|
|
794
|
+
if (continuation.orgId) {
|
|
795
|
+
try {
|
|
796
|
+
const { getOrgA2ASecret } = await import("../org/context.js");
|
|
797
|
+
add(await getOrgA2ASecret(continuation.orgId));
|
|
798
|
+
} catch {}
|
|
799
|
+
}
|
|
800
|
+
return secrets;
|
|
801
|
+
}
|
|
802
|
+
|
|
729
803
|
function isLikelyJwt(token: string): boolean {
|
|
730
804
|
return token.split(".").length === 3;
|
|
731
805
|
}
|
|
@@ -740,6 +814,36 @@ function extractTaskText(task: Task): string {
|
|
|
740
814
|
.join("\n");
|
|
741
815
|
}
|
|
742
816
|
|
|
817
|
+
function extractVerifiedRecoverableArtifactText(
|
|
818
|
+
task: Task,
|
|
819
|
+
agentUrl: string,
|
|
820
|
+
artifactSecrets: readonly string[],
|
|
821
|
+
): string | null {
|
|
822
|
+
if (task.status.message?.metadata?.agentNativeRecoverableArtifacts !== true) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const text = formatContinuationArtifactText(extractTaskText(task), agentUrl);
|
|
827
|
+
if (!text.trim()) return null;
|
|
828
|
+
|
|
829
|
+
// Require the signed identity ledger so arbitrary peer progress prose cannot
|
|
830
|
+
// prematurely complete the continuation.
|
|
831
|
+
const artifacts = extractA2AArtifactIdentities(
|
|
832
|
+
[{ tool: "call-agent", result: text }],
|
|
833
|
+
{
|
|
834
|
+
persistedArtifactSecrets: artifactSecrets,
|
|
835
|
+
},
|
|
836
|
+
);
|
|
837
|
+
return artifacts.length > 0 ? text : null;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function formatRecoverableArtifactFallbackText(text: string): string {
|
|
841
|
+
return text.replace(
|
|
842
|
+
"The agent is still working on the full response, but these verified artifacts already exist:",
|
|
843
|
+
"The downstream agent did not finish its full response, but these verified artifacts already exist:",
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
743
847
|
function formatContinuationArtifactText(
|
|
744
848
|
text: string,
|
|
745
849
|
agentUrl: string,
|
|
@@ -272,6 +272,19 @@ export async function claimPendingTask(
|
|
|
272
272
|
AND active.status = 'processing'
|
|
273
273
|
AND active.id <> integration_pending_tasks.id
|
|
274
274
|
)
|
|
275
|
+
AND NOT EXISTS (
|
|
276
|
+
SELECT 1 FROM integration_pending_tasks earlier
|
|
277
|
+
WHERE earlier.platform = integration_pending_tasks.platform
|
|
278
|
+
AND earlier.external_thread_id = integration_pending_tasks.external_thread_id
|
|
279
|
+
AND earlier.status = 'pending'
|
|
280
|
+
AND (
|
|
281
|
+
earlier.created_at < integration_pending_tasks.created_at
|
|
282
|
+
OR (
|
|
283
|
+
earlier.created_at = integration_pending_tasks.created_at
|
|
284
|
+
AND earlier.id < integration_pending_tasks.id
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
)
|
|
275
288
|
RETURNING id, platform, external_thread_id, payload, owner_email, org_id, status, attempts, error_message, created_at, updated_at, completed_at`
|
|
276
289
|
: `UPDATE integration_pending_tasks
|
|
277
290
|
SET status = ?, attempts = attempts + 1, updated_at = ?
|
|
@@ -282,6 +295,19 @@ export async function claimPendingTask(
|
|
|
282
295
|
AND active.external_thread_id = integration_pending_tasks.external_thread_id
|
|
283
296
|
AND active.status = 'processing'
|
|
284
297
|
AND active.id <> integration_pending_tasks.id
|
|
298
|
+
)
|
|
299
|
+
AND NOT EXISTS (
|
|
300
|
+
SELECT 1 FROM integration_pending_tasks earlier
|
|
301
|
+
WHERE earlier.platform = integration_pending_tasks.platform
|
|
302
|
+
AND earlier.external_thread_id = integration_pending_tasks.external_thread_id
|
|
303
|
+
AND earlier.status = 'pending'
|
|
304
|
+
AND (
|
|
305
|
+
earlier.created_at < integration_pending_tasks.created_at
|
|
306
|
+
OR (
|
|
307
|
+
earlier.created_at = integration_pending_tasks.created_at
|
|
308
|
+
AND earlier.id < integration_pending_tasks.id
|
|
309
|
+
)
|
|
310
|
+
)
|
|
285
311
|
)`,
|
|
286
312
|
args: ["processing", now, id],
|
|
287
313
|
});
|
|
@@ -312,7 +338,7 @@ export async function getNextPendingTaskIdForThread(
|
|
|
312
338
|
const { rows } = await getDbExec().execute({
|
|
313
339
|
sql: `SELECT id FROM integration_pending_tasks
|
|
314
340
|
WHERE platform = ? AND external_thread_id = ? AND status = 'pending'
|
|
315
|
-
ORDER BY created_at ASC LIMIT 1`,
|
|
341
|
+
ORDER BY created_at ASC, id ASC LIMIT 1`,
|
|
316
342
|
args: [platform, externalThreadId],
|
|
317
343
|
});
|
|
318
344
|
return rows[0]?.id ? String(rows[0].id) : null;
|
|
@@ -354,6 +380,77 @@ export async function markTaskRetryable(
|
|
|
354
380
|
});
|
|
355
381
|
}
|
|
356
382
|
|
|
383
|
+
export async function stageTaskDeliveryPayload(
|
|
384
|
+
id: string,
|
|
385
|
+
payload: string,
|
|
386
|
+
): Promise<void> {
|
|
387
|
+
await ensureTable();
|
|
388
|
+
const client = getDbExec();
|
|
389
|
+
const now = Date.now();
|
|
390
|
+
const result = await client.execute({
|
|
391
|
+
sql: `UPDATE integration_pending_tasks
|
|
392
|
+
SET payload = ?, updated_at = ?
|
|
393
|
+
WHERE id = ? AND status = 'processing'`,
|
|
394
|
+
args: [payload, now, id],
|
|
395
|
+
});
|
|
396
|
+
const affected = Number(
|
|
397
|
+
(result as { rowsAffected?: number }).rowsAffected ??
|
|
398
|
+
(result as { rowCount?: number }).rowCount ??
|
|
399
|
+
0,
|
|
400
|
+
);
|
|
401
|
+
if (affected === 0) {
|
|
402
|
+
throw new Error("Integration task is no longer claimable for delivery");
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export async function markTaskDeliveryRetryable(
|
|
407
|
+
id: string,
|
|
408
|
+
payload: string,
|
|
409
|
+
errorMessage: string,
|
|
410
|
+
): Promise<void> {
|
|
411
|
+
await ensureTable();
|
|
412
|
+
const client = getDbExec();
|
|
413
|
+
const result = await client.execute({
|
|
414
|
+
sql: `UPDATE integration_pending_tasks
|
|
415
|
+
SET status = ?, payload = ?, updated_at = ?, error_message = ?
|
|
416
|
+
WHERE id = ? AND status = 'processing'`,
|
|
417
|
+
args: ["pending", payload, Date.now(), errorMessage.slice(0, 2000), id],
|
|
418
|
+
});
|
|
419
|
+
const affected = Number(
|
|
420
|
+
(result as { rowsAffected?: number }).rowsAffected ??
|
|
421
|
+
(result as { rowCount?: number }).rowCount ??
|
|
422
|
+
0,
|
|
423
|
+
);
|
|
424
|
+
if (affected === 0) {
|
|
425
|
+
throw new Error(
|
|
426
|
+
"Integration task is no longer claimable for delivery retry",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
export async function failTaskDeliveryTransition(
|
|
432
|
+
id: string,
|
|
433
|
+
errorMessage: string,
|
|
434
|
+
): Promise<void> {
|
|
435
|
+
await ensureTable();
|
|
436
|
+
const result = await getDbExec().execute({
|
|
437
|
+
sql: `UPDATE integration_pending_tasks
|
|
438
|
+
SET status = ?, updated_at = ?, error_message = ?, payload = ?
|
|
439
|
+
WHERE id = ? AND status = 'processing'`,
|
|
440
|
+
args: ["failed", Date.now(), errorMessage.slice(0, 2000), "{}", id],
|
|
441
|
+
});
|
|
442
|
+
const affected = Number(
|
|
443
|
+
(result as { rowsAffected?: number }).rowsAffected ??
|
|
444
|
+
(result as { rowCount?: number }).rowCount ??
|
|
445
|
+
0,
|
|
446
|
+
);
|
|
447
|
+
if (affected === 0) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
"Integration task delivery failure transition lost its race",
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
357
454
|
/** Mark a task as failed and stash an error message. */
|
|
358
455
|
export async function markTaskFailed(
|
|
359
456
|
id: string,
|