@agent-native/core 0.171.0 → 0.171.1

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.
@@ -1,6 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
 
3
3
  import { signA2AToken } from "@agent-native/core/a2a";
4
+ import {
5
+ ActionContractError,
6
+ AgentActionStopError,
7
+ } from "@agent-native/core/action";
4
8
  import { isFeatureFlagEnabled } from "@agent-native/core/feature-flags";
5
9
  import { fetchOrgApps, type OrgApp } from "@agent-native/core/mcp";
6
10
  import { getOrgDomain } from "@agent-native/core/org";
@@ -9,6 +13,7 @@ import { VERIFIED_FLEET_FLAG_MUTATIONS } from "../../shared/feature-flags.js";
9
13
  import type { AnalyticsAdminContext } from "./db-admin-connections.js";
10
14
 
11
15
  const TARGET_TIMEOUT_MS = 3_000;
16
+ const VERIFICATION_ATTEMPTS = 2;
12
17
  const CONCURRENCY = 4;
13
18
 
14
19
  export type FleetFlagState =
@@ -197,6 +202,8 @@ export type WorkspaceFeatureFlagFailurePhase =
197
202
  | "unsupported-target"
198
203
  | "target-action"
199
204
  | "persistence"
205
+ | "verification-timeout"
206
+ | "verification-network"
200
207
  | "verification";
201
208
 
202
209
  const FAILURE_MESSAGES: Record<WorkspaceFeatureFlagFailurePhase, string> = {
@@ -211,16 +218,42 @@ const FAILURE_MESSAGES: Record<WorkspaceFeatureFlagFailurePhase, string> = {
211
218
  "target-action": "The target app could not complete the feature flag action.",
212
219
  persistence:
213
220
  "The target app did not persist the requested feature flag rules.",
221
+ "verification-timeout":
222
+ "The feature flag change persisted, but the target app timed out during verification.",
223
+ "verification-network":
224
+ "The feature flag change persisted, but Analytics could not reach the target app during verification.",
214
225
  verification: "Analytics could not verify the persisted feature flag change.",
215
226
  };
216
227
 
217
- export class WorkspaceFeatureFlagFailure extends Error {
228
+ export class WorkspaceFeatureFlagFailure extends AgentActionStopError {
218
229
  constructor(readonly phase: WorkspaceFeatureFlagFailurePhase) {
219
- super(`[${phase}] ${FAILURE_MESSAGES[phase]}`);
230
+ const message = `[${phase}] ${FAILURE_MESSAGES[phase]}`;
231
+ const errorCode = `workspace_feature_flag_${phase.replace("-", "_")}`;
232
+ super(message, {
233
+ errorCode,
234
+ details: { phase },
235
+ toolResult: JSON.stringify({ error: errorCode, phase, message }),
236
+ });
220
237
  this.name = "WorkspaceFeatureFlagFailure";
221
238
  }
222
239
  }
223
240
 
241
+ class WorkspaceFeatureFlagSetupFailure extends ActionContractError {
242
+ readonly phase: "directory" | "token-generation";
243
+
244
+ constructor(phase: "directory" | "token-generation") {
245
+ const message = `[${phase}] ${FAILURE_MESSAGES[phase]}`;
246
+ const errorCode = `workspace_feature_flag_${phase.replace("-", "_")}`;
247
+ super(message, {
248
+ errorCode,
249
+ details: { phase },
250
+ statusCode: 503,
251
+ });
252
+ this.name = "WorkspaceFeatureFlagSetupFailure";
253
+ this.phase = phase;
254
+ }
255
+ }
256
+
224
257
  type TargetFailureReason = "token-generation" | "timeout" | "network";
225
258
 
226
259
  class TargetCallFailure extends Error {
@@ -242,6 +275,16 @@ export function classifyWorkspaceFeatureFlagTargetFailure(
242
275
  return "network";
243
276
  }
244
277
 
278
+ function isSyntaxError(error: unknown): boolean {
279
+ return (
280
+ error instanceof SyntaxError ||
281
+ (!!error &&
282
+ typeof error === "object" &&
283
+ "name" in error &&
284
+ error.name === "SyntaxError")
285
+ );
286
+ }
287
+
245
288
  function targetFailure(error: unknown): WorkspaceFeatureFlagFailure {
246
289
  const reason = classifyWorkspaceFeatureFlagTargetFailure(error);
247
290
  return new WorkspaceFeatureFlagFailure(reason);
@@ -259,10 +302,10 @@ async function resolveTargetApp(
259
302
  serviceOrgId: admin.orgId,
260
303
  });
261
304
  } catch {
262
- throw new WorkspaceFeatureFlagFailure("directory");
305
+ throw new WorkspaceFeatureFlagSetupFailure("directory");
263
306
  }
264
307
  const app = apps.find((candidate) => candidate.id === appId);
265
- if (!app) throw new WorkspaceFeatureFlagFailure("directory");
308
+ if (!app) throw new WorkspaceFeatureFlagSetupFailure("directory");
266
309
  return app;
267
310
  }
268
311
 
@@ -302,12 +345,41 @@ async function callTarget(
302
345
  let parsed: unknown = null;
303
346
  try {
304
347
  parsed = await response.json();
305
- } catch {
306
- // A legacy/non-action endpoint is classified below without reflecting body.
348
+ } catch (error) {
349
+ const successfulResponse = response.status >= 200 && response.status < 300;
350
+ if (successfulResponse && !isSyntaxError(error))
351
+ throw new TargetCallFailure(
352
+ classifyWorkspaceFeatureFlagTargetFailure(error),
353
+ );
354
+ // Invalid legacy payloads and non-success statuses are classified below.
307
355
  }
308
356
  return { status: response.status, body: parsed };
309
357
  }
310
358
 
359
+ async function readBackTarget(
360
+ app: OrgApp,
361
+ admin: AnalyticsAdminContext,
362
+ orgDomain: string,
363
+ ): Promise<{ status: number; body: unknown }> {
364
+ for (let attempt = 1; attempt <= VERIFICATION_ATTEMPTS; attempt += 1) {
365
+ try {
366
+ return await callTarget(app, admin, "list-feature-flags", {}, orgDomain);
367
+ } catch (error) {
368
+ const reason = classifyWorkspaceFeatureFlagTargetFailure(error);
369
+ const retryable = reason === "timeout" || reason === "network";
370
+ if (retryable && attempt < VERIFICATION_ATTEMPTS) continue;
371
+ throw new WorkspaceFeatureFlagFailure(
372
+ reason === "timeout"
373
+ ? "verification-timeout"
374
+ : reason === "network"
375
+ ? "verification-network"
376
+ : reason,
377
+ );
378
+ }
379
+ }
380
+ throw new WorkspaceFeatureFlagFailure("verification");
381
+ }
382
+
311
383
  export function classifyWorkspaceFeatureFlagList(
312
384
  app: OrgApp,
313
385
  result: { status: number; body: unknown },
@@ -404,9 +476,10 @@ export async function setWorkspaceFeatureFlag(
404
476
  try {
405
477
  orgDomain = (await getOrgDomain(admin.orgId))?.trim().toLowerCase();
406
478
  } catch {
407
- throw new WorkspaceFeatureFlagFailure("token-generation");
479
+ throw new WorkspaceFeatureFlagSetupFailure("token-generation");
408
480
  }
409
- if (!orgDomain) throw new WorkspaceFeatureFlagFailure("token-generation");
481
+ if (!orgDomain)
482
+ throw new WorkspaceFeatureFlagSetupFailure("token-generation");
410
483
  let result: Awaited<ReturnType<typeof callTarget>>;
411
484
  try {
412
485
  result = await callTarget(
@@ -417,6 +490,8 @@ export async function setWorkspaceFeatureFlag(
417
490
  orgDomain,
418
491
  );
419
492
  } catch (error) {
493
+ if (classifyWorkspaceFeatureFlagTargetFailure(error) === "token-generation")
494
+ throw new WorkspaceFeatureFlagSetupFailure("token-generation");
420
495
  throw targetFailure(error);
421
496
  }
422
497
  if (result.status === 401 || result.status === 403)
@@ -465,18 +540,7 @@ export async function setWorkspaceFeatureFlag(
465
540
  return mutation;
466
541
  }
467
542
 
468
- let readBack: Awaited<ReturnType<typeof callTarget>>;
469
- try {
470
- readBack = await callTarget(
471
- app,
472
- admin,
473
- "list-feature-flags",
474
- {},
475
- orgDomain,
476
- );
477
- } catch (error) {
478
- throw targetFailure(error);
479
- }
543
+ const readBack = await readBackTarget(app, admin, orgDomain);
480
544
  if (readBack.status === 401 || readBack.status === 403)
481
545
  throw new WorkspaceFeatureFlagFailure("authorization");
482
546
  if (readBack.status === 404 || readBack.status === 405)
package/dist/action.d.ts CHANGED
@@ -130,8 +130,10 @@ export interface ActionRunContext {
130
130
  */
131
131
  export type ActionAuthorize<TArgs> = (args: TArgs, ctx?: ActionRunContext) => void | boolean | Promise<void | boolean>;
132
132
  export interface AgentActionStopOptions {
133
- /** Optional stable code surfaced in run metadata and tests. */
133
+ /** Optional stable code safe to surface in run metadata and action transports. */
134
134
  errorCode?: string;
135
+ /** Safe structured context for callers. Never include secrets or raw driver errors. */
136
+ details?: Record<string, unknown>;
135
137
  /** Optional short tool-result text. Defaults to the user-facing message. */
136
138
  toolResult?: string;
137
139
  }
@@ -164,6 +166,7 @@ export declare function isActionContractError(error: unknown): error is ActionCo
164
166
  export declare class AgentActionStopError extends Error {
165
167
  readonly agentNativeStop = true;
166
168
  readonly errorCode?: string;
169
+ readonly details?: Record<string, unknown>;
167
170
  readonly toolResult?: string;
168
171
  constructor(message: string, options?: AgentActionStopOptions);
169
172
  }
package/dist/action.js CHANGED
@@ -34,11 +34,13 @@ export function isActionContractError(error) {
34
34
  export class AgentActionStopError extends Error {
35
35
  agentNativeStop = true;
36
36
  errorCode;
37
+ details;
37
38
  toolResult;
38
39
  constructor(message, options = {}) {
39
40
  super(message);
40
41
  this.name = "AgentActionStopError";
41
42
  this.errorCode = options.errorCode;
43
+ this.details = options.details;
42
44
  this.toolResult = options.toolResult;
43
45
  }
44
46
  }
@@ -366,6 +366,14 @@ async function performActionFetch(name, method, params, options) {
366
366
  }
367
367
  const error = new Error(`Action ${name} failed: ${message}`);
368
368
  error.status = res.status;
369
+ if (typeof data?.errorCode === "string") {
370
+ error.errorCode = data.errorCode;
371
+ }
372
+ if (data?.details &&
373
+ typeof data.details === "object" &&
374
+ !Array.isArray(data.details)) {
375
+ error.details = data.details;
376
+ }
369
377
  throw error;
370
378
  }
371
379
  // 2xx but the body couldn't even be read (mid-stream abort, decode failure,
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
+ error?: undefined;
65
66
  states: {
66
67
  clientId: number;
67
68
  state: string;
68
69
  }[];
69
- error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
+ error?: undefined;
80
81
  users: {
81
82
  clientId: number;
82
83
  lastSeen: number;
83
84
  }[];
84
- error?: undefined;
85
85
  }>>;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
+ error?: undefined;
44
45
  summary: import("./types.js").TraceSummary;
45
46
  spans: import("./types.js").TraceSpan[];
46
47
  id?: undefined;
47
- error?: undefined;
48
48
  ok?: undefined;
49
49
  } | {
50
+ error?: undefined;
50
51
  summary?: undefined;
51
52
  spans?: undefined;
52
53
  id: string;
53
- error?: undefined;
54
54
  ok?: undefined;
55
55
  } | {
56
56
  summary?: undefined;
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
59
59
  error: any;
60
60
  ok?: undefined;
61
61
  } | {
62
+ error?: undefined;
62
63
  summary?: undefined;
63
64
  spans?: undefined;
64
65
  id?: undefined;
65
- error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- ok: boolean;
19
18
  error?: undefined;
19
+ ok: boolean;
20
20
  }>>;
@@ -51,8 +51,8 @@ export declare function handleDeleteResource(event: any): Promise<{
51
51
  error: string;
52
52
  ok?: undefined;
53
53
  } | {
54
- ok: boolean;
55
54
  error?: undefined;
55
+ ok: boolean;
56
56
  }>;
57
57
  /** POST /_agent-native/resources/upload — upload a file as a resource */
58
58
  export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
73
73
  runId: string | null;
74
74
  expiresAt: number | null;
75
75
  metadata: string | null;
76
+ error?: undefined;
76
77
  url: string;
77
78
  provider: string;
78
79
  storageSetupRequired?: undefined;
79
- error?: undefined;
80
80
  } | {
81
81
  error: string;
82
82
  storageSetupRequired: boolean;
@@ -543,19 +543,23 @@ export function mountActionRoutes(nitroApp, actions, options) {
543
543
  setResponseStatus(event, status);
544
544
  // Only echo the raw message for known-safe cases:
545
545
  // - validation errors (deterministic, parameter-shape only)
546
+ // - action contract errors (explicitly safe on every transport)
546
547
  // - explicit user-facing errors (AgentActionStopError / fail())
547
548
  // - errors with an explicit statusCode < 500 (client errors)
548
549
  // For uncategorized 500s, return a generic message and keep the
549
550
  // real detail server-side only — it can contain DB/driver/
550
551
  // upstream text we must not leak to HTTP callers.
551
552
  const isUserFacing = isValidationError ||
553
+ isActionContractError(err) ||
552
554
  isAgentActionStopError(err) ||
553
555
  (explicitStatus !== undefined && explicitStatus < 500);
554
556
  if (isUserFacing) {
555
- return isActionContractError(err)
557
+ return isActionContractError(err) || isAgentActionStopError(err)
556
558
  ? {
557
559
  error: msg,
558
- errorCode: err.errorCode,
560
+ ...(typeof err.errorCode === "string"
561
+ ? { errorCode: err.errorCode }
562
+ : {}),
559
563
  ...(err.details === undefined
560
564
  ? {}
561
565
  : { details: err.details }),
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
26
26
  expiresAt?: undefined;
27
27
  ttlSeconds?: undefined;
28
28
  } | {
29
+ error?: undefined;
29
30
  token: string;
30
31
  expiresAt: string;
31
32
  ttlSeconds: number;
32
- error?: undefined;
33
33
  }>>;
@@ -20,6 +20,6 @@ export declare function createTranscribeVoiceHandler(): import("h3").EventHandle
20
20
  error: string;
21
21
  text?: undefined;
22
22
  } | {
23
- text: string;
24
23
  error?: undefined;
24
+ text: string;
25
25
  }>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.171.0",
3
+ "version": "0.171.1",
4
4
  "description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {