@agent-native/core 0.220.0-nightly-20261024064224 → 0.220.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.
@@ -264,7 +264,7 @@ export async function processA2ATaskFromQueue(taskId, config, event) {
264
264
  }, A2A_PROCESSING_HEARTBEAT_MS);
265
265
  heartbeat.unref?.();
266
266
  try {
267
- await processClaimedA2ATask(taskId, claimed, config, workerAbort.signal, event);
267
+ await processClaimedA2ATask(taskId, claimed, config, workerAbort.signal, event, fence.generation);
268
268
  }
269
269
  finally {
270
270
  clearInterval(heartbeat);
@@ -285,7 +285,7 @@ export async function processA2ATaskFromQueue(taskId, config, event) {
285
285
  }
286
286
  }
287
287
  }
288
- async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
288
+ async function processClaimedA2ATask(taskId, claimed, config, signal, event, processingAttemptId) {
289
289
  const message = claimed.history?.[0];
290
290
  if (!message) {
291
291
  await settleProcessingA2ATask(taskId, {
@@ -378,7 +378,7 @@ async function processClaimedA2ATask(taskId, claimed, config, signal, event) {
378
378
  ? { orgScope: "personal" }
379
379
  : {}),
380
380
  ...(requestOrigin ? { requestOrigin } : {}),
381
- }, () => runHandlerAndPersist(taskId, message, config, contextId, callerMetadata, event, sourceContext, verifiedEmail, signal));
381
+ }, () => runHandlerAndPersist(taskId, message, config, contextId, callerMetadata, event, sourceContext, verifiedEmail, signal, processingAttemptId));
382
382
  }
383
383
  catch (err) {
384
384
  try {
@@ -471,11 +471,12 @@ function jsonRpcError(id, code, message) {
471
471
  function jsonRpcResult(id, result) {
472
472
  return { jsonrpc: "2.0", id, result };
473
473
  }
474
- function makeHandlerContext(taskId, contextId, metadata, event, sourceContext, ownerEmail, signal) {
474
+ function makeHandlerContext(taskId, contextId, metadata, event, sourceContext, ownerEmail, signal, processingAttemptId) {
475
475
  const artifacts = [];
476
476
  const pendingFileArtifacts = [];
477
477
  const context = {
478
478
  taskId,
479
+ processingAttemptId,
479
480
  contextId,
480
481
  metadata,
481
482
  event,
@@ -573,8 +574,8 @@ async function withA2ARequestContext(metadata, event, fn) {
573
574
  ...(requestOrigin ? { requestOrigin } : {}),
574
575
  }, fn);
575
576
  }
576
- async function runHandlerAndPersist(taskId, message, config, contextId, metadata, event, sourceContext, ownerEmail, signal) {
577
- const { context, artifacts, persistFileArtifacts } = makeHandlerContext(taskId, contextId, metadata, event, sourceContext, ownerEmail, signal);
577
+ async function runHandlerAndPersist(taskId, message, config, contextId, metadata, event, sourceContext, ownerEmail, signal, processingAttemptId) {
578
+ const { context, artifacts, persistFileArtifacts } = makeHandlerContext(taskId, contextId, metadata, event, sourceContext, ownerEmail, signal, processingAttemptId);
578
579
  try {
579
580
  signal?.throwIfAborted();
580
581
  const result = getHandler(config)(message, context);
@@ -149,6 +149,7 @@ export interface A2ACorrelationMetadata {
149
149
  }
150
150
  export interface A2AHandlerContext {
151
151
  taskId: string;
152
+ processingAttemptId?: string;
152
153
  contextId?: string;
153
154
  metadata?: Record<string, unknown>;
154
155
  event?: unknown;
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
139
139
  duplicate?: undefined;
140
140
  eventId?: undefined;
141
141
  } | {
142
+ error?: undefined;
142
143
  accepted: boolean;
143
144
  duplicate: boolean;
144
145
  eventId: string;
145
- error?: undefined;
146
146
  }>;
@@ -13,12 +13,12 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
13
13
  };
14
14
  }>>;
15
15
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
- error: string;
17
16
  ok?: undefined;
17
+ error: string;
18
18
  } | {
19
+ error?: undefined;
19
20
  liveCopy: "derived" | "not_derived" | "unchanged";
20
21
  liveCopyReason?: string;
21
- error?: undefined;
22
22
  ok: boolean;
23
23
  } | {
24
24
  error?: undefined;
@@ -2,22 +2,22 @@ export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("
2
2
  ok?: undefined;
3
3
  error: string;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
8
8
  export declare const postCollabPatch: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
9
9
  ok?: undefined;
10
10
  error: string;
11
11
  } | {
12
- ok: boolean;
13
12
  error?: undefined;
13
+ ok: boolean;
14
14
  }>>;
15
15
  export declare const getCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
16
16
  error: string;
17
17
  docId?: undefined;
18
18
  data?: undefined;
19
19
  } | {
20
+ error?: undefined;
20
21
  docId: string;
21
22
  data: any;
22
- error?: undefined;
23
23
  }>>;
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- configured?: undefined;
46
45
  error?: undefined;
46
+ configured?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -13,10 +13,10 @@ declare const _default: import("../../action.js").ActionDefinition<{
13
13
  enabled?: undefined;
14
14
  nextRun?: undefined;
15
15
  } | {
16
+ deleted?: undefined;
16
17
  resourceId: string;
17
18
  name: string;
18
19
  enabled: boolean;
19
20
  nextRun: string;
20
- deleted?: undefined;
21
21
  }>;
22
22
  export default _default;
@@ -21,17 +21,18 @@
21
21
  *
22
22
  * ## Resolution + safety model
23
23
  *
24
- * - The directory origin is read from env: `AGENT_NATIVE_ORG_DIRECTORY_URL`
25
- * (dedicated) or `AGENT_NATIVE_IDENTITY_HUB_URL` (Dispatch is also the
26
- * identity hub). When *neither* is set the feature is simply inactive —
27
- * `fetchOrgApps()` returns `[]` and nothing changes anywhere (asserted by
28
- * a test). This makes the whole feature opt-in and back-compat.
24
+ * - A selected org's bound workspace URL wins for org-scoped requests; the
25
+ * deployment's `AGENT_NATIVE_ORG_DIRECTORY_URL` or
26
+ * `AGENT_NATIVE_IDENTITY_HUB_URL` is the fallback when no binding exists.
27
+ * When neither is set the feature is inactive — `fetchOrgApps()` returns
28
+ * `[]` and nothing changes anywhere (asserted by a test).
29
29
  * - `fetchOrgAppsResult()` preserves directory availability for callers
30
30
  * that cannot mistake a failed lookup for an empty directory.
31
31
  * `fetchOrgApps()` remains the best-effort compatibility wrapper: on ANY
32
32
  * error it returns `[]` and NEVER throws, so cross-app verbs still degrade
33
33
  * silently to their exact current local-only behavior.
34
- * - Credentialed requests require HTTPS and use manual redirects. A short
34
+ * - Mounted `/dispatch` requests retry at the host root only after a 404.
35
+ * Credentialed requests require HTTPS and use manual redirects. A short
35
36
  * in-memory TTL cache (default 60s) is keyed by directory origin and
36
37
  * caller identity/org scope so sibling app lists never cross tenants.
37
38
  * Successful responses are cached for 60s; transient unavailable results
@@ -51,7 +52,7 @@ export interface OrgApp {
51
52
  whenToUse?: string[];
52
53
  aliases?: string[];
53
54
  }
54
- export type OrgDirectoryUnavailableReason = "not-configured" | "authentication" | "unauthorized" | "authorization" | "invalid-origin" | "missing-route" | "timeout" | "network" | "invalid-response" | "ssrf-blocked" | "server-error" | "http-error";
55
+ export type OrgDirectoryUnavailableReason = "not-configured" | "authentication" | "unauthorized" | "authorization" | "invalid-origin" | "missing-route" | "timeout" | "network" | "invalid-response" | "ssrf-blocked" | "workspace-url-unavailable" | "server-error" | "http-error";
55
56
  export type OrgDirectoryFetchResult = {
56
57
  status: "available";
57
58
  apps: OrgApp[];
@@ -127,11 +127,92 @@ function serviceScopedCacheKey(origin, orgId, includeDirectoryApp = false, stric
127
127
  ].join("|");
128
128
  }
129
129
  async function fetchOrgAppsResultInternal(opts, strictValidation = true) {
130
+ const env = opts?.env ?? process.env;
131
+ const directoryBase = await resolveDirectoryBaseUrl(opts, env);
132
+ if (!directoryBase.ok)
133
+ return directoryBase.result;
134
+ const result = await fetchOrgAppsAtBaseUrl({ ...opts, directoryBaseUrl: directoryBase.url }, strictValidation);
135
+ if (result.status !== "unavailable" || result.reason !== "missing-route") {
136
+ return result;
137
+ }
138
+ const rootUrl = new URL(directoryBase.url).origin;
139
+ if (rootUrl === directoryBase.url.replace(/\/+$/, ""))
140
+ return result;
141
+ return fetchOrgAppsAtBaseUrl({ ...opts, directoryBaseUrl: rootUrl }, strictValidation);
142
+ }
143
+ async function resolveDirectoryBaseUrl(opts, env) {
144
+ if (opts?.directoryBaseUrl !== undefined) {
145
+ const url = normalizeDirectoryBaseUrl(opts.directoryBaseUrl);
146
+ return url
147
+ ? { ok: true, url }
148
+ : {
149
+ ok: false,
150
+ result: { status: "unavailable", reason: "invalid-origin" },
151
+ };
152
+ }
153
+ const serviceOrgId = opts?.serviceOrgId?.trim();
154
+ let orgId = serviceOrgId || undefined;
155
+ try {
156
+ if (!orgId) {
157
+ const { getRequestOrgId } = await import("../server/request-context.js");
158
+ orgId = getRequestOrgId();
159
+ }
160
+ if (orgId) {
161
+ const { getOrgWorkspaceUrl } = await import("../org/context.js");
162
+ const workspaceUrl = await getOrgWorkspaceUrl(orgId);
163
+ if (workspaceUrl) {
164
+ const url = normalizeOrgWorkspaceDirectoryBaseUrl(workspaceUrl);
165
+ return url
166
+ ? { ok: true, url }
167
+ : {
168
+ ok: false,
169
+ result: { status: "unavailable", reason: "invalid-origin" },
170
+ };
171
+ }
172
+ }
173
+ }
174
+ catch {
175
+ return {
176
+ ok: false,
177
+ result: { status: "unavailable", reason: "workspace-url-unavailable" },
178
+ };
179
+ }
180
+ const configuredUrl = configuredOrgDirectoryUrl(env);
181
+ const url = resolveOrgDirectoryOrigin(env);
182
+ if (url)
183
+ return { ok: true, url };
184
+ return {
185
+ ok: false,
186
+ result: {
187
+ status: "unavailable",
188
+ reason: configuredUrl ? "invalid-origin" : "not-configured",
189
+ },
190
+ };
191
+ }
192
+ function normalizeOrgWorkspaceDirectoryBaseUrl(value) {
193
+ try {
194
+ const url = new URL(value);
195
+ if (url.protocol !== "https:" ||
196
+ url.username ||
197
+ url.password ||
198
+ url.search ||
199
+ url.hash ||
200
+ (url.pathname !== "/" && url.pathname !== "")) {
201
+ return null;
202
+ }
203
+ return `${url.origin}/dispatch`;
204
+ }
205
+ catch {
206
+ // coercion-ok: Invalid bindings become typed invalid-origin failures above.
207
+ return null;
208
+ }
209
+ }
210
+ async function fetchOrgAppsAtBaseUrl(opts, strictValidation) {
130
211
  const env = opts?.env ?? process.env;
131
212
  const hasExplicitBaseUrl = opts?.directoryBaseUrl !== undefined;
132
213
  const hasConfiguredEnvUrl = configuredOrgDirectoryUrl(env) !== null;
133
214
  const origin = hasExplicitBaseUrl
134
- ? normalizeDirectoryBaseUrl(opts?.directoryBaseUrl ?? "")
215
+ ? normalizeDirectoryBaseUrl(opts.directoryBaseUrl ?? "")
135
216
  : resolveOrgDirectoryOrigin(env);
136
217
  if (!origin) {
137
218
  return {
@@ -1,21 +1,21 @@
1
1
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
2
2
  count: number;
3
- ok?: undefined;
4
3
  updated?: undefined;
5
4
  error?: undefined;
5
+ ok?: undefined;
6
6
  } | {
7
7
  count?: undefined;
8
8
  updated: number;
9
- ok?: undefined;
10
9
  error?: undefined;
10
+ ok?: undefined;
11
11
  } | {
12
12
  count?: undefined;
13
+ updated?: undefined;
13
14
  error: string;
14
15
  ok?: undefined;
15
- updated?: undefined;
16
16
  } | {
17
17
  count?: undefined;
18
- ok: boolean;
19
18
  updated?: undefined;
19
+ ok: boolean;
20
20
  error?: undefined;
21
21
  }>>;
@@ -27,43 +27,43 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
27
27
  thumbsUpRate: number;
28
28
  avgEvalScore: number;
29
29
  } | {
30
- ok?: undefined;
31
30
  error: string;
32
31
  message: string;
33
32
  summary?: undefined;
34
33
  spans?: undefined;
35
- } | {
36
34
  ok?: undefined;
35
+ } | {
37
36
  message?: undefined;
38
37
  summary: import("./types.js").TraceSummary;
39
38
  spans: import("./types.js").TraceSpan[];
40
39
  error?: undefined;
41
- } | {
42
40
  ok?: undefined;
41
+ } | {
43
42
  message?: undefined;
44
43
  summary?: undefined;
45
44
  spans?: undefined;
46
45
  traceMissing: true;
47
46
  id: string;
48
47
  error?: undefined;
49
- } | {
50
48
  ok?: undefined;
49
+ } | {
51
50
  message?: undefined;
52
51
  summary?: undefined;
53
52
  spans?: undefined;
54
53
  traceMissing?: undefined;
55
54
  id: string;
56
55
  error?: undefined;
57
- } | {
58
56
  ok?: undefined;
57
+ } | {
59
58
  message?: undefined;
60
59
  summary?: undefined;
61
60
  spans?: undefined;
62
61
  error: any;
62
+ ok?: undefined;
63
63
  } | {
64
64
  message?: undefined;
65
65
  summary?: undefined;
66
66
  spans?: undefined;
67
- ok: boolean;
68
67
  error?: undefined;
68
+ ok: boolean;
69
69
  }>>;
@@ -57,39 +57,39 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
57
57
  } | {
58
58
  error: string;
59
59
  errorCode?: undefined;
60
- status?: undefined;
61
60
  ok?: undefined;
61
+ status?: undefined;
62
62
  } | {
63
63
  error: string;
64
64
  errorCode: string;
65
- status?: undefined;
66
65
  ok?: undefined;
66
+ status?: undefined;
67
67
  } | {
68
+ error?: undefined;
68
69
  errorCode?: undefined;
69
70
  ok: boolean;
70
71
  status: string;
71
- error?: undefined;
72
72
  } | {
73
+ ok?: undefined;
73
74
  error: string;
74
75
  removed?: undefined;
75
- ok?: undefined;
76
76
  } | {
77
+ error?: undefined;
77
78
  ok: boolean;
78
79
  removed: boolean;
79
- error?: undefined;
80
80
  }>>;
81
81
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
82
+ ok?: undefined;
82
83
  error: string;
83
84
  note?: undefined;
84
- ok?: undefined;
85
85
  } | {
86
+ error?: undefined;
86
87
  ok: boolean;
87
88
  note?: undefined;
88
- error?: undefined;
89
89
  } | {
90
+ error?: undefined;
90
91
  ok: boolean;
91
92
  note: string;
92
- error?: undefined;
93
93
  } | {
94
94
  note?: undefined;
95
95
  ok: boolean;
@@ -117,11 +117,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
117
117
  } | {
118
118
  error: string;
119
119
  } | {
120
+ error?: undefined;
120
121
  ok: boolean;
121
122
  key: string;
122
- error?: undefined;
123
123
  } | {
124
+ error?: undefined;
124
125
  ok: boolean;
125
126
  removed: boolean;
126
- error?: undefined;
127
127
  }>>;
@@ -1,5 +1,5 @@
1
1
  import type { Message as A2AMessage } from "../a2a/types.js";
2
- import { type ActionEntry, type AgentActionSurfaceDetails, type AgentActionSurfaceResolution } from "../agent/production-agent.js";
2
+ import { type ActionEntry, type AgentActionSurfaceDetails, type AgentActionSurfaceResolution, type AgentLoopUsage } from "../agent/production-agent.js";
3
3
  import { type ActiveRun } from "../agent/run-manager.js";
4
4
  import { foldAgentChatRunCompletion } from "../agent/thread-data-builder.js";
5
5
  import type { AgentChatEvent, AgentChatScope } from "../agent/types.js";
@@ -79,6 +79,15 @@ export declare function createSerializedA2ATaskStatusWriter(taskId: string, writ
79
79
  flush: () => Promise<void>;
80
80
  };
81
81
  export declare function runA2AAgentLoopWithRunManager(runOptions: Parameters<typeof runA2AAgentLoop>[0], pluginOptions: Pick<AgentChatPluginOptions, "delegatedRunPolicy" | "finalResponseGuard" | "runSoftTimeoutMs">, timeoutOptions: Parameters<typeof runA2AAgentLoop>[2], delegatedOptions?: Parameters<typeof runA2AAgentLoop>[3], taskSignal?: AbortSignal): Promise<Awaited<ReturnType<typeof runA2AAgentLoop>>>;
82
+ export declare function runA2AWithUsageLedger<T>(run: Promise<T>, input: {
83
+ usage: AgentLoopUsage;
84
+ ownerEmail: string;
85
+ orgId: string | null;
86
+ appId?: string;
87
+ taskId: string;
88
+ processingAttemptId?: string;
89
+ persistUsage?: (record: import("../usage/store.js").UsageRecord) => Promise<void>;
90
+ }): Promise<T>;
82
91
  export declare function resolveA2ARecoverableArtifactSecret(orgId?: string | null | undefined): Promise<string | undefined>;
83
92
  export declare function buildLeanRunPolicyPrompt(codeEditingSurfaceRestriction: string, prodCodeExecPromptNote: string): string;
84
93
  export declare function filterPromptActionsToSurface(actions: Record<string, ActionEntry>, allowedActionNames?: readonly string[]): Record<string, ActionEntry>;
@@ -20,7 +20,7 @@ import { SYSTEM_PROMPT_CACHE_SPLIT } from "../agent/engine/prompt-cache.js";
20
20
  import { PROVIDER_TO_ENV } from "../agent/engine/provider-env-vars.js";
21
21
  import { currentExecutionFence, withExecutionFence, } from "../agent/execution-fence.js";
22
22
  import { hostedHarnessSystemPrompt } from "../agent/harness/hosted.js";
23
- import { createProductionAgentHandler, endsAtContinuationBoundary, actionsToEngineTools, executeAgentToolCall, filterActionsByAllowedNames, normalizeAgentActionSurfaceResolution, getActiveRunForThreadAsync, getJevContextCredentials, abortRunDurably, abortRunAndTurnDurably, abortRunAtSafeBoundaryDurably, abortTurnByRefDurably, abortTurnDurably, subscribeToRun, } from "../agent/production-agent.js";
23
+ import { createProductionAgentHandler, endsAtContinuationBoundary, actionsToEngineTools, executeAgentToolCall, filterActionsByAllowedNames, normalizeAgentActionSurfaceResolution, getActiveRunForThreadAsync, getJevContextCredentials, abortRunDurably, abortRunAndTurnDurably, abortRunAtSafeBoundaryDurably, abortTurnByRefDurably, abortTurnDurably, subscribeToRun, mergeAgentLoopUsage, } from "../agent/production-agent.js";
24
24
  import { applyProviderModelSelection, providerForEngineName, resolveProviderModelSelectionAtScope, } from "../agent/provider-model-selection.js";
25
25
  import { startRun } from "../agent/run-manager.js";
26
26
  import { callerHasRunAccess, callerHasThreadAccess, } from "../agent/run-ownership.js";
@@ -519,6 +519,56 @@ export async function runA2AAgentLoopWithRunManager(runOptions, pluginOptions, t
519
519
  }
520
520
  return result;
521
521
  }
522
+ export async function runA2AWithUsageLedger(run, input) {
523
+ const outcome = await run.then((value) => ({ ok: true, value }), (error) => ({ ok: false, error }));
524
+ let usageError;
525
+ let usageFailed = false;
526
+ if (input.usage.usageReported) {
527
+ try {
528
+ const record = {
529
+ ownerEmail: input.ownerEmail,
530
+ inputTokens: input.usage.inputTokens,
531
+ outputTokens: input.usage.outputTokens,
532
+ cacheReadTokens: input.usage.cacheReadTokens,
533
+ cacheWriteTokens: input.usage.cacheWriteTokens,
534
+ builderCreditsUsed: input.usage.builderCreditsUsed,
535
+ engineName: input.usage.engineName,
536
+ model: input.usage.model,
537
+ app: input.appId,
538
+ orgId: input.orgId ?? undefined,
539
+ label: "a2a",
540
+ // A replay in the same lease replaces its receipt; later leases
541
+ // retain their own usage segments under a different ref.
542
+ refId: input.processingAttemptId
543
+ ? `${input.taskId}:${input.processingAttemptId}`
544
+ : input.taskId,
545
+ runId: input.taskId,
546
+ threadId: input.taskId,
547
+ taskId: input.taskId,
548
+ };
549
+ if (input.persistUsage) {
550
+ await input.persistUsage(record);
551
+ }
552
+ else {
553
+ const { recordUsage } = await import("../usage/store.js");
554
+ await recordUsage(record);
555
+ }
556
+ }
557
+ catch (error) {
558
+ usageFailed = true;
559
+ usageError = error;
560
+ }
561
+ }
562
+ if (!outcome.ok) {
563
+ if (usageFailed) {
564
+ throw new AggregateError([outcome.error, usageError], "A2A run and usage persistence both failed.");
565
+ }
566
+ throw outcome.error;
567
+ }
568
+ if (usageFailed)
569
+ throw usageError;
570
+ return outcome.value;
571
+ }
522
572
  const A2A_ACTIVITY_CHECKPOINT_MIN_INTERVAL_MS = 2_000;
523
573
  export async function resolveA2ARecoverableArtifactSecret(orgId = getRequestOrgId()) {
524
574
  const globalSecret = process.env.A2A_SECRET?.trim();
@@ -828,12 +878,12 @@ export function createAgentChatPlugin(options) {
828
878
  const initPromise = (async () => {
829
879
  const { awaitBootstrap } = await import("./framework-request-handler.js");
830
880
  await awaitBootstrap(nitroApp);
831
- const appProfileAppId = options?.appId ??
881
+ const runtimeAppId = options?.appId ??
832
882
  getAppConfig().app.id ??
833
883
  getAppConfig().app.template ??
834
884
  getAppConfig().app.workspaceId ??
835
- getAppConfig().app.packageName ??
836
- "app";
885
+ getAppConfig().app.packageName;
886
+ const appProfileAppId = runtimeAppId ?? "app";
837
887
  // Never make route readiness wait on an org-global stale-run sweep.
838
888
  // `/runs/active` reaps the requested run on demand, and the periodic
839
889
  // recovery sweep below handles abandoned runs with no connected client.
@@ -2171,6 +2221,14 @@ export function createAgentChatPlugin(options) {
2171
2221
  const a2aEvents = [];
2172
2222
  const a2aToolResults = [];
2173
2223
  let a2aOutcome;
2224
+ const a2aUsage = {
2225
+ inputTokens: 0,
2226
+ outputTokens: 0,
2227
+ cacheReadTokens: 0,
2228
+ cacheWriteTokens: 0,
2229
+ engineName: a2aEngine.name,
2230
+ model,
2231
+ };
2174
2232
  let lastRecoverableArtifactText = "";
2175
2233
  let activityState = createA2AAgentActivityState();
2176
2234
  let lastActivityCheckpointAt = 0;
@@ -2209,7 +2267,7 @@ export function createAgentChatPlugin(options) {
2209
2267
  correlation.callerThreadId ??
2210
2268
  context.taskId;
2211
2269
  console.log(`[A2A] Starting agent loop: ${a2aToolSurface.tools.length}/${a2aToolSurface.availableTools.length} initial tools, prompt ${systemPrompt.length} chars`);
2212
- await runA2AAgentLoopWithRunManager({
2270
+ const a2aRun = runA2AAgentLoopWithRunManager({
2213
2271
  engine: a2aEngine,
2214
2272
  model,
2215
2273
  systemPrompt,
@@ -2225,7 +2283,7 @@ export function createAgentChatPlugin(options) {
2225
2283
  orgId: getRequestOrgId() ?? null,
2226
2284
  // Without it, every action with an `access` policy is refused
2227
2285
  // for lack of an application identity.
2228
- appId: options?.appId,
2286
+ appId: runtimeAppId,
2229
2287
  executionMode: "act",
2230
2288
  runId: context.taskId,
2231
2289
  networkProtocol: "a2a",
@@ -2239,6 +2297,12 @@ export function createAgentChatPlugin(options) {
2239
2297
  onOutcome: (outcome) => {
2240
2298
  a2aOutcome = outcome;
2241
2299
  },
2300
+ onUsage: (usage) => {
2301
+ mergeAgentLoopUsage(a2aUsage, {
2302
+ ...usage,
2303
+ usageReported: true,
2304
+ });
2305
+ },
2242
2306
  send: (event) => {
2243
2307
  a2aEvents.push(event);
2244
2308
  const nextActivityState = applyA2AAgentActivityEvent(activityState, event);
@@ -2318,6 +2382,14 @@ export function createAgentChatPlugin(options) {
2318
2382
  },
2319
2383
  },
2320
2384
  }, context.signal);
2385
+ await runA2AWithUsageLedger(a2aRun, {
2386
+ usage: a2aUsage,
2387
+ ownerEmail: userEmail,
2388
+ orgId: getRequestOrgId() ?? null,
2389
+ appId: runtimeAppId,
2390
+ taskId: context.taskId,
2391
+ processingAttemptId: context.processingAttemptId,
2392
+ });
2321
2393
  // The continuation can observe terminal output immediately, so make
2322
2394
  // its latest mutation checkpoint durable first.
2323
2395
  checkpointActivity(true);
@@ -577,59 +577,21 @@ function mergeDiscoveredAgent(agentsById, candidate) {
577
577
  async function discoverOrgDirectoryApps(selfAppId) {
578
578
  const selfId = selfAppId ? normalizeAgentId(selfAppId) : undefined;
579
579
  try {
580
- const { fetchOrgAppsResult, resolveOrgDirectoryOrigin } = await import("../mcp/org-directory.js");
581
- const configuredBaseUrl = resolveOrgDirectoryOrigin();
582
- let result = await fetchOrgAppsResult({ selfId });
583
- let baseUrl = configuredBaseUrl;
584
- if (result.status === "unavailable" && result.reason === "not-configured") {
585
- const orgId = getRequestOrgId();
586
- if (!orgId) {
587
- return {
588
- status: "setup-required",
589
- reason: "organization-not-selected",
590
- };
591
- }
592
- const { getOrgWorkspaceUrl } = await import("../org/context.js");
593
- const workspaceUrl = await getOrgWorkspaceUrl(orgId);
594
- if (!workspaceUrl) {
595
- return { status: "setup-required", reason: "workspace-url-not-set" };
596
- }
597
- let workspaceOrigin;
598
- try {
599
- const parsed = new URL(workspaceUrl);
600
- if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
601
- parsed.username ||
602
- parsed.password ||
603
- parsed.search ||
604
- parsed.hash ||
605
- (parsed.pathname !== "/" && parsed.pathname !== "")) {
606
- return { status: "setup-required", reason: "invalid-workspace-url" };
607
- }
608
- workspaceOrigin = parsed.origin;
609
- }
610
- catch {
611
- return { status: "setup-required", reason: "invalid-workspace-url" };
612
- }
613
- baseUrl = `${workspaceOrigin}/dispatch`;
614
- result = await fetchOrgAppsResult({ directoryBaseUrl: baseUrl, selfId });
615
- }
616
- if (result.status === "unavailable" &&
617
- result.reason === "missing-route" &&
618
- baseUrl) {
619
- const rootUrl = new URL(baseUrl).origin;
620
- if (rootUrl !== baseUrl.replace(/\/+$/, "")) {
621
- result = await fetchOrgAppsResult({
622
- directoryBaseUrl: rootUrl,
623
- selfId,
624
- });
625
- }
626
- }
580
+ const { fetchOrgAppsResult } = await import("../mcp/org-directory.js");
581
+ const result = await fetchOrgAppsResult({ selfId });
627
582
  if (result.status === "available")
628
583
  return result;
629
584
  if (result.reason === "authorization")
630
585
  return { status: "forbidden" };
631
- if (result.reason === "not-configured" ||
632
- result.reason === "invalid-origin") {
586
+ if (result.reason === "not-configured") {
587
+ return {
588
+ status: "setup-required",
589
+ reason: getRequestOrgId()
590
+ ? "workspace-url-not-set"
591
+ : "organization-not-selected",
592
+ };
593
+ }
594
+ if (result.reason === "invalid-origin") {
633
595
  return { status: "setup-required", reason: result.reason };
634
596
  }
635
597
  return { status: "unavailable", reason: result.reason };
@@ -67,12 +67,12 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
67
67
  code: any;
68
68
  } | {
69
69
  errorCode?: undefined;
70
- error?: undefined;
71
70
  code?: undefined;
72
71
  ok: boolean;
73
72
  key: string;
74
73
  baseUrlKey?: string;
75
74
  scope: AgentEngineApiKeyScope;
76
75
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
76
+ error?: undefined;
77
77
  }>>;
78
78
  export {};
@@ -8,17 +8,17 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
8
8
  * resolveEngine's credential detection.
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
- ok?: undefined;
12
11
  error: string;
12
+ ok?: undefined;
13
13
  scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
16
16
  error: any;
17
17
  scope?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  ok: boolean;
20
21
  scope: "org" | "user";
21
- error?: undefined;
22
22
  }>>;
23
23
  export interface DefaultModelSelectionRequest {
24
24
  engine: string;
@@ -1,9 +1,9 @@
1
1
  export declare function createAgentEngineOllamaModelsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
2
+ ok?: undefined;
2
3
  error: string;
3
4
  models?: undefined;
4
- ok?: undefined;
5
5
  } | {
6
+ error?: undefined;
6
7
  ok: boolean;
7
8
  models: string[];
8
- error?: undefined;
9
9
  }>>;
@@ -588,8 +588,8 @@ export declare function createPublicRemoteAgentsHandler(discover?: PublicAgentDi
588
588
  error: string;
589
589
  agents?: undefined;
590
590
  } | {
591
- agents: Omit<import("./agent-discovery.js").DiscoveredAgent, "auth" | "kind">[];
592
591
  error?: undefined;
592
+ agents: Omit<import("./agent-discovery.js").DiscoveredAgent, "auth" | "kind">[];
593
593
  }>>;
594
594
  export declare function getBuilderConnectErrorDisposition(error: unknown, connectAttemptId: string | null): "correlated" | "legacy" | null;
595
595
  export declare function getBuilderConnectErrorKey(ownerEmail: string, connectAttemptId?: string | null): string;
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
54
54
  expiresAt?: undefined;
55
55
  ttlSeconds?: undefined;
56
56
  } | {
57
+ error?: undefined;
57
58
  token: string;
58
59
  expiresAt: string;
59
60
  ttlSeconds: number;
60
- error?: undefined;
61
61
  }>>;
@@ -67,6 +67,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
67
67
  triggerType: "event" | "manual" | "schedule" | "webhook";
68
68
  body: string;
69
69
  }, {
70
+ updated?: undefined;
71
+ deleted?: undefined;
70
72
  created: boolean;
71
73
  resourceId: string;
72
74
  name: string;
@@ -87,17 +89,17 @@ declare const _default: import("../../action.js").ActionDefinition<{
87
89
  executionCwd: string;
88
90
  mcpTools: string[];
89
91
  webhookPath: string;
90
- deleted?: undefined;
91
- updated?: undefined;
92
92
  changed?: undefined;
93
93
  previouslyEnabled?: undefined;
94
94
  requestedEnabled?: undefined;
95
95
  } | {
96
+ updated?: undefined;
97
+ enabled?: undefined;
98
+ nextRun?: undefined;
96
99
  created?: undefined;
97
100
  deleted: boolean;
98
101
  resourceId: string;
99
102
  name: string;
100
- updated?: undefined;
101
103
  scope?: undefined;
102
104
  triggerType?: undefined;
103
105
  event?: undefined;
@@ -105,8 +107,6 @@ declare const _default: import("../../action.js").ActionDefinition<{
105
107
  timezone?: undefined;
106
108
  condition?: undefined;
107
109
  body?: undefined;
108
- enabled?: undefined;
109
- nextRun?: undefined;
110
110
  createdBy?: undefined;
111
111
  model?: undefined;
112
112
  reasoningEffort?: undefined;
@@ -119,8 +119,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
119
119
  requestedEnabled?: undefined;
120
120
  webhookPath?: undefined;
121
121
  } | {
122
- created?: undefined;
123
122
  deleted?: undefined;
123
+ created?: undefined;
124
124
  updated: boolean;
125
125
  resourceId: string;
126
126
  name: string;
@@ -252,48 +252,51 @@ export async function recordUsage(recordOrOwner, inputTokens, outputTokens, mode
252
252
  const resolvedLabel = label ?? "chat";
253
253
  const resolvedRef = refId ?? "";
254
254
  const resolvedOrgId = orgId ?? getRequestOrgId() ?? null;
255
- if (resolvedRef) {
256
- await client.execute({
257
- sql: `DELETE FROM token_usage
258
- WHERE label = ? AND ref_id = ?
259
- AND (org_id IS NULL OR org_id = ?)`,
260
- args: [resolvedLabel, resolvedRef, resolvedOrgId],
261
- });
262
- }
263
255
  const resolvedCostSource = costSource ?? (costCentsX100 == null ? "estimated" : "reported");
264
256
  const costX100 = resolvedCostSource === "unavailable"
265
257
  ? 0
266
258
  : (costCentsX100 ??
267
259
  calculateCost(inTok, outTok, modelName, cacheReadTokens, cacheWriteTokens));
268
260
  const id = Date.now() * 1000 + Math.floor(Math.random() * 1000);
261
+ const insertArgs = [
262
+ id,
263
+ ownerEmail,
264
+ inTok,
265
+ outTok,
266
+ cacheReadTokens,
267
+ cacheWriteTokens,
268
+ costX100,
269
+ builderCreditsUsed ?? null,
270
+ engineName ?? null,
271
+ resolvedCostSource,
272
+ modelName,
273
+ resolvedLabel,
274
+ resolvedApp,
275
+ resolvedRef,
276
+ resolvedOrgId,
277
+ runId ?? null,
278
+ threadId ?? null,
279
+ taskId ?? null,
280
+ integrationScopeId ?? null,
281
+ sourcePlatform ?? null,
282
+ sourceId ?? null,
283
+ Date.now(),
284
+ ];
285
+ const replaceRefSql = resolvedRef
286
+ ? `WITH prior_usage AS (
287
+ DELETE FROM token_usage
288
+ WHERE label = ? AND ref_id = ?
289
+ AND (org_id IS NULL OR org_id = ?)
290
+ RETURNING id
291
+ ) `
292
+ : "";
269
293
  await client.execute({
270
- sql: `INSERT INTO token_usage
294
+ sql: `${replaceRefSql}INSERT INTO token_usage
271
295
  (id, owner_email, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, cost_cents_x100, builder_credits_used, engine_name, cost_source, model, label, app, ref_id, org_id, run_id, thread_id, task_id, integration_scope_id, source_platform, source_id, created_at)
272
296
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
273
- args: [
274
- id,
275
- ownerEmail,
276
- inTok,
277
- outTok,
278
- cacheReadTokens,
279
- cacheWriteTokens,
280
- costX100,
281
- builderCreditsUsed ?? null,
282
- engineName ?? null,
283
- resolvedCostSource,
284
- modelName,
285
- resolvedLabel,
286
- resolvedApp,
287
- resolvedRef,
288
- resolvedOrgId,
289
- runId ?? null,
290
- threadId ?? null,
291
- taskId ?? null,
292
- integrationScopeId ?? null,
293
- sourcePlatform ?? null,
294
- sourceId ?? null,
295
- Date.now(),
296
- ],
297
+ args: resolvedRef
298
+ ? [resolvedLabel, resolvedRef, resolvedOrgId, ...insertArgs]
299
+ : insertArgs,
297
300
  });
298
301
  void import("./alerts-store.js")
299
302
  .then(({ enqueueUsageAlertEvaluation }) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.220.0-nightly-20261024064224",
3
+ "version": "0.220.0",
4
4
  "description": "The agentic application framework for building autonomous agents with intuitive UIs",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -730,8 +730,8 @@
730
730
  "yaml": "^2.9.0",
731
731
  "yjs": "^13.6.33",
732
732
  "zod": "^4.3.6",
733
- "@agent-native/agentkit": "^0.220.0-nightly-20261024064224",
734
- "@agent-native/recap-cli": "^0.5.88"
733
+ "@agent-native/agentkit": "^0.220.0",
734
+ "@agent-native/recap-cli": "^0.5.89"
735
735
  },
736
736
  "devDependencies": {
737
737
  "@ai-sdk/anthropic": "^3.0.71",
@@ -801,7 +801,7 @@
801
801
  "xlsx": "^0.18.5"
802
802
  },
803
803
  "peerDependencies": {
804
- "@agent-native/core-corpus": "0.220.0-nightly-20261024064224",
804
+ "@agent-native/core-corpus": ">=0.196.0",
805
805
  "@ai-sdk/anthropic": ">=3",
806
806
  "@ai-sdk/cohere": ">=3",
807
807
  "@ai-sdk/google": ">=3",