@agent-native/core 0.160.0 → 0.160.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.
@@ -6923,6 +6923,11 @@ export function createProductionAgentHandler(options) {
6923
6923
  .filter((key) => typeof key === "string")
6924
6924
  .slice(0, 200)
6925
6925
  : undefined;
6926
+ // The durable approval row is the authorization boundary. Do not require
6927
+ // the client to reproduce the original structured history exactly: the UI
6928
+ // may truncate tool arguments and intentionally assigns fresh replay ids.
6929
+ // The loop still consumes only a matching server-created grant for the
6930
+ // current owner/org/thread/turn/tool/input tuple.
6926
6931
  const exactApprovedToolCall = findApprovedStructuredToolCall(structuredHistory, requestedApprovedToolCalls);
6927
6932
  const firstRequestPayloadDetail = buildFirstRequestPayloadDetail({
6928
6933
  isFirstRequest: history.length === 0,
@@ -7001,14 +7006,7 @@ export function createProductionAgentHandler(options) {
7001
7006
  return consumeAgentToolApproval(approvalStoreBinding(binding));
7002
7007
  },
7003
7008
  };
7004
- const exactApprovedToolEntry = exactApprovedToolCall
7005
- ? requestActions[exactApprovedToolCall.name]
7006
- : undefined;
7007
- const approvedToolCallsForExecution = exactApprovedToolCall && exactApprovedToolEntry?.needsApproval
7008
- ? [
7009
- toolCallCacheKey(exactApprovedToolCall.name, exactApprovedToolCall.input),
7010
- ]
7011
- : undefined;
7009
+ const approvedToolCallsForExecution = requestedApprovedToolCalls;
7012
7010
  if (isBackgroundWorker &&
7013
7011
  (await isTurnAborted(effectiveThreadId, effectiveTurnId))) {
7014
7012
  await markRunAborted(runId, "user").catch(() => { });
@@ -7788,9 +7786,10 @@ export function createProductionAgentHandler(options) {
7788
7786
  ...(threadId
7789
7787
  ? { threadId: effectiveThreadId, turnId: effectiveTurnId }
7790
7788
  : {}),
7791
- // Human-in-the-loop approval grants for this turn (sanitized the
7792
- // request is untrusted; only the exact structured call is passed to
7793
- // the loop, where the durable grant is consumed atomically.
7789
+ // Human-in-the-loop approval grants for this turn. The request is
7790
+ // untrusted; the durable approval consumer below validates every key
7791
+ // against the authenticated owner/org/thread/turn and consumes it
7792
+ // atomically for the exact tool/input tuple.
7794
7793
  ...(approvedToolCallsForExecution
7795
7794
  ? { approvedToolCalls: approvedToolCallsForExecution }
7796
7795
  : {}),
@@ -33,6 +33,7 @@ export declare const AGENT_TOOL_APPROVAL_TABLE_SQL: {
33
33
  )`;
34
34
  };
35
35
  export declare const AGENT_TOOL_APPROVAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding\n ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)";
36
+ export declare const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = "CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical\n ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)";
36
37
  /**
37
38
  * Durable approval grants are created and consumed on request paths, but their
38
39
  * schema belongs to the release migration boundary in production.
@@ -33,6 +33,8 @@ export const AGENT_TOOL_APPROVAL_TABLE_SQL = {
33
33
  };
34
34
  export const AGENT_TOOL_APPROVAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_binding
35
35
  ON agent_tool_approvals(owner_email, org_id, thread_id, tool_name, call_id, status)`;
36
+ export const AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL = `CREATE INDEX IF NOT EXISTS idx_agent_tool_approvals_logical
37
+ ON agent_tool_approvals(owner_email, org_id, thread_id, turn_id, tool_name, approval_key_hash, status)`;
36
38
  /**
37
39
  * Durable approval grants are created and consumed on request paths, but their
38
40
  * schema belongs to the release migration boundary in production.
@@ -48,4 +50,12 @@ ${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
48
50
  ${AGENT_TOOL_APPROVAL_INDEX_SQL}`,
49
51
  },
50
52
  },
53
+ {
54
+ version: 2,
55
+ name: "agent-tool-approvals-logical-binding-index",
56
+ sql: {
57
+ postgres: AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL,
58
+ sqlite: AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL,
59
+ },
60
+ },
51
61
  ];
@@ -11,8 +11,10 @@ export interface AgentToolApprovalBinding {
11
11
  export declare function hashAgentToolApprovalKey(approvalKey: string): string;
12
12
  export declare function createAgentToolApproval(binding: AgentToolApprovalBinding): Promise<void>;
13
13
  /**
14
- * Atomically consume the server-created approval for one exact tool call.
15
- * Client history and approval keys are only lookup input; the pending row is
16
- * the authorization boundary and cannot be manufactured by the client.
14
+ * Atomically consume the server-created approval for one logical tool call.
15
+ * The model's call id is transport metadata and can change when Dispatch
16
+ * reconstructs a paused turn, so it is deliberately not part of authorization.
17
+ * The pending row remains the boundary: client history and approval keys alone
18
+ * cannot manufacture a grant.
17
19
  */
18
20
  export declare function consumeAgentToolApproval(binding: AgentToolApprovalBinding): Promise<boolean>;
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { getDbExec, isPostgres, retryOnDdlRace } from "../db/client.js";
3
3
  import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
4
- import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
4
+ import { AGENT_TOOL_APPROVAL_INDEX_SQL, AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL, AGENT_TOOL_APPROVAL_TABLE_SQL, } from "./tool-approval-migrations.js";
5
5
  const APPROVAL_TTL_MS = 15 * 60_000;
6
6
  const APPROVAL_CLEANUP_AGE_MS = 24 * 60 * 60_000;
7
7
  let initPromise;
@@ -12,11 +12,13 @@ async function ensureAgentToolApprovalTable() {
12
12
  if (isPostgres()) {
13
13
  await ensureTableExists("agent_tool_approvals", createSql);
14
14
  await ensureIndexExists("idx_agent_tool_approvals_binding", AGENT_TOOL_APPROVAL_INDEX_SQL);
15
+ await ensureIndexExists("idx_agent_tool_approvals_logical", AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL);
15
16
  return;
16
17
  }
17
18
  const client = getDbExec();
18
19
  await retryOnDdlRace(() => client.execute(createSql));
19
20
  await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_INDEX_SQL));
21
+ await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL));
20
22
  })().catch((error) => {
21
23
  initPromise = undefined;
22
24
  throw error;
@@ -65,9 +67,11 @@ export async function createAgentToolApproval(binding) {
65
67
  }
66
68
  }
67
69
  /**
68
- * Atomically consume the server-created approval for one exact tool call.
69
- * Client history and approval keys are only lookup input; the pending row is
70
- * the authorization boundary and cannot be manufactured by the client.
70
+ * Atomically consume the server-created approval for one logical tool call.
71
+ * The model's call id is transport metadata and can change when Dispatch
72
+ * reconstructs a paused turn, so it is deliberately not part of authorization.
73
+ * The pending row remains the boundary: client history and approval keys alone
74
+ * cannot manufacture a grant.
71
75
  */
72
76
  export async function consumeAgentToolApproval(binding) {
73
77
  await ensureAgentToolApprovalTable();
@@ -80,8 +84,8 @@ export async function consumeAgentToolApproval(binding) {
80
84
  WHERE owner_email = ?
81
85
  AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
82
86
  AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
87
+ AND ((turn_id IS NULL AND CAST(? AS TEXT) IS NULL) OR turn_id = ?)
83
88
  AND tool_name = ?
84
- AND call_id = ?
85
89
  AND approval_key_hash = ?
86
90
  AND status = 'pending'
87
91
  AND expires_at > ?
@@ -97,8 +101,9 @@ export async function consumeAgentToolApproval(binding) {
97
101
  binding.orgId ?? null,
98
102
  binding.threadId ?? null,
99
103
  binding.threadId ?? null,
104
+ binding.turnId ?? null,
105
+ binding.turnId ?? null,
100
106
  binding.toolName,
101
- binding.callId,
102
107
  hashAgentToolApprovalKey(binding.approvalKey),
103
108
  now,
104
109
  ],
@@ -6,6 +6,7 @@ import { assertValidComputerCommandEnvelope } from "../integrations/computer-sup
6
6
  import { serializeBoundedRemoteJson } from "../integrations/remote-json-safety.js";
7
7
  import { executeDenyCodeAgentApproval, executePendingCodeAgentApproval, } from "./code-agent-executor.js";
8
8
  import { appendCodeAgentTranscriptEvent, codeAgentRunTranscriptPath, codeAgentStoreRoot, createCodeAgentRunRecord, getCodeAgentRunRecord, listCodeAgentRunRecords, normalizeCodeAgentPermissionMode, queueCodeAgentFollowUp, updateCodeAgentRunRecord, } from "./code-agent-runs.js";
9
+ import { appendPortalTransferTranscript, parsePortalTransferContext, } from "./portal-transfer.js";
9
10
  import { loadPortalEnvironment, preparePortalWorkspace, parsePortalHandoff, } from "./portal-workspace.js";
10
11
  const DEVICE_PATH_ENV = "AGENT_NATIVE_REMOTE_DEVICE_PATH";
11
12
  const COMPUTER_BRIDGE_URL_ENV = "AGENT_NATIVE_COMPUTER_BRIDGE_URL";
@@ -203,6 +204,16 @@ class RemoteCodeAgentConnector {
203
204
  const metadata = isObject(command.params.metadata)
204
205
  ? command.params.metadata
205
206
  : {};
207
+ let portalTransfer = null;
208
+ try {
209
+ portalTransfer = parsePortalTransferContext(metadata.portalTransfer);
210
+ }
211
+ catch (error) {
212
+ return {
213
+ ok: false,
214
+ error: error instanceof Error ? error.message : String(error),
215
+ };
216
+ }
206
217
  let cwd = resolveCommandCwd(command.params.cwd ?? this.config.workspacePath);
207
218
  let portalWorkspace;
208
219
  let portalEnvironment;
@@ -226,6 +237,12 @@ class RemoteCodeAgentConnector {
226
237
  if (portalWorkspace && requestedRunId && !isSafeRunId(requestedRunId)) {
227
238
  return { ok: false, error: "Portal run id is invalid." };
228
239
  }
240
+ if (portalTransfer && (!portalWorkspace || !requestedRunId)) {
241
+ return {
242
+ ok: false,
243
+ error: "Portal transfer context requires a Portal run id and workspace.",
244
+ };
245
+ }
229
246
  if (portalWorkspace && requestedRunId) {
230
247
  const existing = getCodeAgentRunRecord(requestedRunId);
231
248
  const existingRemote = isObject(existing?.metadata?.remote)
@@ -240,12 +257,28 @@ class RemoteCodeAgentConnector {
240
257
  }
241
258
  return { ok: true, runId: existing.id, run: existing, resumed: true };
242
259
  }
260
+ const existingTransfer = isObject(existing?.metadata?.portalTransfer)
261
+ ? existing.metadata.portalTransfer
262
+ : undefined;
263
+ if (portalTransfer &&
264
+ existing &&
265
+ firstStringValue(existingTransfer?.sourceRunId) ===
266
+ portalTransfer.sourceRunId) {
267
+ this.remoteRunIds.add(existing.id);
268
+ this.transcriptCursors.set(existing.id, { offset: 0, seq: 0 });
269
+ if (!activeRunners.has(existing.id)) {
270
+ this.spawnRunner(existing.id, existing.cwd, existing.permissionMode, portalEnvironment?.values);
271
+ }
272
+ return { ok: true, runId: existing.id, run: existing, resumed: true };
273
+ }
243
274
  }
244
275
  const permissionMode = normalizeCodeAgentPermissionMode(command.params.permissionMode) ??
245
276
  "full-auto";
246
277
  const engine = firstStringValue(command.params.engine);
247
278
  const model = firstStringValue(command.params.model);
248
279
  const effort = firstStringValue(command.params.effort, command.params.reasoningEffort);
280
+ const metadataWithoutTransfer = { ...metadata };
281
+ delete metadataWithoutTransfer.portalTransfer;
249
282
  const run = createCodeAgentRunRecord({
250
283
  ...(portalWorkspace && requestedRunId ? { id: requestedRunId } : {}),
251
284
  goalId,
@@ -267,6 +300,14 @@ class RemoteCodeAgentConnector {
267
300
  { label: "Prompt", value: truncateForDisplay(prompt, 160) },
268
301
  { label: "Agent", value: "Remote connector" },
269
302
  { label: "Mode", value: permissionMode },
303
+ ...(portalTransfer
304
+ ? [
305
+ {
306
+ label: "Context",
307
+ value: `Imported ${portalTransfer.events.length} transcript events`,
308
+ },
309
+ ]
310
+ : []),
270
311
  ...(portalWorkspace
271
312
  ? [
272
313
  {
@@ -283,7 +324,7 @@ class RemoteCodeAgentConnector {
283
324
  : []),
284
325
  ],
285
326
  metadata: {
286
- ...metadata,
327
+ ...metadataWithoutTransfer,
287
328
  prompt,
288
329
  source: "remote-connector",
289
330
  engine,
@@ -313,14 +354,45 @@ class RemoteCodeAgentConnector {
313
354
  },
314
355
  }
315
356
  : {}),
357
+ ...(portalTransfer
358
+ ? {
359
+ portalTransfer: {
360
+ schemaVersion: 1,
361
+ sourceRunId: portalTransfer.sourceRunId,
362
+ ...(portalTransfer.sourceStatus
363
+ ? { sourceStatus: portalTransfer.sourceStatus }
364
+ : {}),
365
+ ...(portalTransfer.sourcePhase
366
+ ? { sourcePhase: portalTransfer.sourcePhase }
367
+ : {}),
368
+ eventCount: portalTransfer.events.length,
369
+ transferredAt: new Date().toISOString(),
370
+ },
371
+ }
372
+ : {}),
316
373
  },
317
374
  });
318
- appendCodeAgentTranscriptEvent({
319
- runId: run.id,
320
- kind: "user",
321
- message: prompt,
322
- metadata: { source: "remote-initial-prompt", commandId: command.id },
323
- });
375
+ if (portalTransfer) {
376
+ appendPortalTransferTranscript(portalTransfer, run.id);
377
+ appendCodeAgentTranscriptEvent({
378
+ runId: run.id,
379
+ kind: "user",
380
+ message: prompt,
381
+ metadata: {
382
+ source: "portal-transfer-continuation",
383
+ commandId: command.id,
384
+ sourceRunId: portalTransfer.sourceRunId,
385
+ },
386
+ });
387
+ }
388
+ else {
389
+ appendCodeAgentTranscriptEvent({
390
+ runId: run.id,
391
+ kind: "user",
392
+ message: prompt,
393
+ metadata: { source: "remote-initial-prompt", commandId: command.id },
394
+ });
395
+ }
324
396
  appendCodeAgentTranscriptEvent({
325
397
  runId: run.id,
326
398
  kind: "status",
@@ -0,0 +1,43 @@
1
+ export declare const PORTAL_TRANSFER_SCHEMA_VERSION: 1;
2
+ export declare const PORTAL_TRANSFER_MAX_CONTEXT_BYTES = 900000;
3
+ export type PortalTransferEventKind = "user" | "system" | "note" | "artifact" | "status";
4
+ export interface PortalTransferTranscriptEvent {
5
+ schemaVersion: 1;
6
+ id: string;
7
+ kind: PortalTransferEventKind;
8
+ message: string;
9
+ createdAt: string;
10
+ metadata?: Record<string, unknown>;
11
+ signal?: "credential-gap";
12
+ }
13
+ export interface PortalTransferContext {
14
+ schemaVersion: 1;
15
+ sourceRunId: string;
16
+ sourceStatus?: string;
17
+ sourcePhase?: string;
18
+ events: PortalTransferTranscriptEvent[];
19
+ }
20
+ export interface PortalTransferSourceEvent {
21
+ id?: unknown;
22
+ runId?: unknown;
23
+ kind?: unknown;
24
+ type?: unknown;
25
+ message?: unknown;
26
+ text?: unknown;
27
+ createdAt?: unknown;
28
+ metadata?: unknown;
29
+ signal?: unknown;
30
+ }
31
+ export declare function createPortalTransferContext(input: {
32
+ sourceRunId: string;
33
+ sourceStatus?: string;
34
+ sourcePhase?: string;
35
+ events: readonly PortalTransferSourceEvent[];
36
+ }): PortalTransferContext;
37
+ export declare function parsePortalTransferContext(value: unknown): PortalTransferContext | null;
38
+ export declare function appendPortalTransferTranscript(context: PortalTransferContext, runId: string): number;
39
+ export declare function portalTransferContinuationPrompt(input: {
40
+ hostLabel: string;
41
+ handoffId: string;
42
+ eventCount: number;
43
+ }): string;
@@ -0,0 +1,201 @@
1
+ import { serializeBoundedRemoteJson } from "../integrations/remote-json-safety.js";
2
+ import { appendCodeAgentTranscriptEvent } from "./code-agent-runs.js";
3
+ export const PORTAL_TRANSFER_SCHEMA_VERSION = 1;
4
+ export const PORTAL_TRANSFER_MAX_CONTEXT_BYTES = 900_000;
5
+ const BINARY_FIELD_NAMES = new Set([
6
+ "base64",
7
+ "dataurl",
8
+ "image",
9
+ "imagebase64",
10
+ "imagedata",
11
+ "imagebytes",
12
+ "screenshot",
13
+ "screenshotbase64",
14
+ "screenshotdata",
15
+ "bytes",
16
+ "buffer",
17
+ ]);
18
+ export function createPortalTransferContext(input) {
19
+ const sourceRunId = requireString(input.sourceRunId, "source run id");
20
+ const context = {
21
+ schemaVersion: PORTAL_TRANSFER_SCHEMA_VERSION,
22
+ sourceRunId,
23
+ ...(input.sourceStatus
24
+ ? { sourceStatus: requireString(input.sourceStatus, "source status") }
25
+ : {}),
26
+ ...(input.sourcePhase
27
+ ? { sourcePhase: requireString(input.sourcePhase, "source phase") }
28
+ : {}),
29
+ events: input.events.map((event, index) => normalizeSourceEvent(event, index)),
30
+ };
31
+ serializeBoundedRemoteJson(context, {
32
+ label: "Portal transcript context",
33
+ maxBytes: PORTAL_TRANSFER_MAX_CONTEXT_BYTES,
34
+ });
35
+ return context;
36
+ }
37
+ export function parsePortalTransferContext(value) {
38
+ if (value === undefined || value === null)
39
+ return null;
40
+ if (!isRecord(value) || value.schemaVersion !== 1) {
41
+ throw new Error("Portal transfer context has an unsupported version.");
42
+ }
43
+ const sourceRunId = requireString(value.sourceRunId, "source run id");
44
+ if (!Array.isArray(value.events)) {
45
+ throw new Error("Portal transfer context is missing transcript events.");
46
+ }
47
+ const events = value.events.map((event, index) => {
48
+ if (!isRecord(event)) {
49
+ throw new Error(`Portal transcript event ${index + 1} is invalid.`);
50
+ }
51
+ return normalizeSourceEvent(event, index);
52
+ });
53
+ const sourceStatus = optionalString(value.sourceStatus);
54
+ const sourcePhase = optionalString(value.sourcePhase);
55
+ const context = {
56
+ schemaVersion: 1,
57
+ sourceRunId,
58
+ ...(sourceStatus ? { sourceStatus } : {}),
59
+ ...(sourcePhase ? { sourcePhase } : {}),
60
+ events,
61
+ };
62
+ serializeBoundedRemoteJson(context, {
63
+ label: "Portal transcript context",
64
+ maxBytes: PORTAL_TRANSFER_MAX_CONTEXT_BYTES,
65
+ });
66
+ return context;
67
+ }
68
+ export function appendPortalTransferTranscript(context, runId) {
69
+ const targetRunId = requireString(runId, "target run id");
70
+ let appended = 0;
71
+ for (const event of context.events) {
72
+ appendCodeAgentTranscriptEvent({
73
+ id: event.id,
74
+ runId: targetRunId,
75
+ kind: event.kind,
76
+ message: event.message,
77
+ createdAt: event.createdAt,
78
+ ...(event.metadata
79
+ ? {
80
+ metadata: {
81
+ ...event.metadata,
82
+ portalTransfer: {
83
+ sourceRunId: context.sourceRunId,
84
+ sourceEventId: event.id,
85
+ },
86
+ },
87
+ }
88
+ : {
89
+ metadata: {
90
+ portalTransfer: {
91
+ sourceRunId: context.sourceRunId,
92
+ sourceEventId: event.id,
93
+ },
94
+ },
95
+ }),
96
+ ...(event.signal ? { signal: event.signal } : {}),
97
+ });
98
+ appended++;
99
+ }
100
+ return appended;
101
+ }
102
+ export function portalTransferContinuationPrompt(input) {
103
+ return [
104
+ "[Portal session continuation]",
105
+ `This coding session was moved to ${requireString(input.hostLabel, "Portal host label")}.`,
106
+ `Portal handoff: ${requireString(input.handoffId, "Portal handoff id")}`,
107
+ `The preceding ${input.eventCount} transcript event${input.eventCount === 1 ? "" : "s"} came from the original computer and is part of this session context.`,
108
+ "Review the transferred transcript and Portal workspace before acting. Continue the unfinished task from the latest state, and do not repeat work that is already complete.",
109
+ ].join("\n");
110
+ }
111
+ function normalizeSourceEvent(value, index) {
112
+ const id = requireString(value.id, `transcript event ${index + 1} id`);
113
+ const kind = normalizeKind(value.kind ?? value.type, index);
114
+ const messageValue = value.message ?? value.text;
115
+ if (typeof messageValue !== "string") {
116
+ throw new Error(`Portal transcript event ${index + 1} is missing text.`);
117
+ }
118
+ const createdAt = requireString(value.createdAt, `transcript event ${index + 1} timestamp`);
119
+ const metadata = sanitizeMetadata(value.metadata);
120
+ const signal = value.signal === "credential-gap" ? "credential-gap" : undefined;
121
+ return {
122
+ schemaVersion: 1,
123
+ id,
124
+ kind,
125
+ message: messageValue,
126
+ createdAt,
127
+ ...(metadata ? { metadata } : {}),
128
+ ...(signal ? { signal } : {}),
129
+ };
130
+ }
131
+ function normalizeKind(value, index) {
132
+ const kind = typeof value === "string" ? value.trim().toLowerCase() : "";
133
+ if (kind === "user" ||
134
+ kind === "system" ||
135
+ kind === "note" ||
136
+ kind === "artifact" ||
137
+ kind === "status") {
138
+ return kind;
139
+ }
140
+ if (kind === "assistant" || kind === "human" || kind === "prompt") {
141
+ return kind === "assistant" ? "system" : "user";
142
+ }
143
+ throw new Error(`Portal transcript event ${index + 1} has an invalid kind.`);
144
+ }
145
+ function sanitizeMetadata(value) {
146
+ if (!isRecord(value))
147
+ return undefined;
148
+ const sanitized = sanitizeObject(value);
149
+ return Object.keys(sanitized).length > 0 ? sanitized : undefined;
150
+ }
151
+ function sanitizeObject(value) {
152
+ const result = {};
153
+ let binaryOmitted = false;
154
+ for (const [key, child] of Object.entries(value)) {
155
+ if (isBinaryFieldName(key)) {
156
+ binaryOmitted = true;
157
+ continue;
158
+ }
159
+ const sanitized = sanitizeValue(child);
160
+ if (sanitized !== undefined)
161
+ result[key] = sanitized;
162
+ }
163
+ if (binaryOmitted)
164
+ result.binaryContentOmitted = true;
165
+ return result;
166
+ }
167
+ function sanitizeValue(value) {
168
+ if (value === null ||
169
+ typeof value === "string" ||
170
+ typeof value === "number" ||
171
+ typeof value === "boolean") {
172
+ return value;
173
+ }
174
+ if (Array.isArray(value)) {
175
+ return value
176
+ .map((item) => sanitizeValue(item))
177
+ .filter((item) => item !== undefined);
178
+ }
179
+ if (isRecord(value))
180
+ return sanitizeObject(value);
181
+ return undefined;
182
+ }
183
+ function isBinaryFieldName(key) {
184
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
185
+ return BINARY_FIELD_NAMES.has(normalized);
186
+ }
187
+ function isRecord(value) {
188
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
189
+ }
190
+ function requireString(value, label) {
191
+ const result = optionalString(value);
192
+ if (!result)
193
+ throw new Error(`Portal ${label} is missing.`);
194
+ return result;
195
+ }
196
+ function optionalString(value) {
197
+ if (typeof value !== "string")
198
+ return undefined;
199
+ const result = value.trim();
200
+ return result || undefined;
201
+ }
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
17
17
  id?: undefined;
18
18
  provider?: undefined;
19
19
  } | {
20
- error?: undefined;
21
20
  configured?: undefined;
22
21
  connectPath?: undefined;
23
22
  url: string;
24
23
  id: string;
25
24
  provider: string;
25
+ error?: undefined;
26
26
  }>;
27
27
  export default _default;
@@ -137,13 +137,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
137
137
  inputSchema: {
138
138
  type: string;
139
139
  properties: {
140
- count?: undefined;
141
140
  query?: undefined;
142
141
  minutes?: undefined;
143
142
  limit?: undefined;
144
143
  clientHint?: undefined;
145
144
  timestamp?: undefined;
146
145
  chapterId?: undefined;
146
+ count?: undefined;
147
147
  startAt?: undefined;
148
148
  endAt?: undefined;
149
149
  reason?: undefined;
@@ -159,7 +159,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
159
159
  inputSchema: {
160
160
  type: string;
161
161
  properties: {
162
- count?: undefined;
163
162
  query: {
164
163
  type: string;
165
164
  description: string;
@@ -175,6 +174,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
175
174
  clientHint?: undefined;
176
175
  timestamp?: undefined;
177
176
  chapterId?: undefined;
177
+ count?: undefined;
178
178
  startAt?: undefined;
179
179
  endAt?: undefined;
180
180
  reason?: undefined;
@@ -190,7 +190,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
190
190
  inputSchema: {
191
191
  type: string;
192
192
  properties: {
193
- count?: undefined;
194
193
  minutes: {
195
194
  type: string;
196
195
  description: string;
@@ -200,6 +199,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
200
199
  clientHint?: undefined;
201
200
  timestamp?: undefined;
202
201
  chapterId?: undefined;
202
+ count?: undefined;
203
203
  startAt?: undefined;
204
204
  endAt?: undefined;
205
205
  reason?: undefined;
@@ -216,7 +216,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
216
216
  type: string;
217
217
  required: string[];
218
218
  properties: {
219
- count?: undefined;
220
219
  query: {
221
220
  type: string;
222
221
  description: string;
@@ -235,6 +234,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
235
234
  };
236
235
  timestamp?: undefined;
237
236
  chapterId?: undefined;
237
+ count?: undefined;
238
238
  startAt?: undefined;
239
239
  endAt?: undefined;
240
240
  reason?: undefined;
@@ -250,7 +250,6 @@ export declare function screenMemoryMcpToolDefinitions(): ({
250
250
  type: string;
251
251
  required: string[];
252
252
  properties: {
253
- count?: undefined;
254
253
  query?: undefined;
255
254
  minutes?: undefined;
256
255
  limit?: undefined;
@@ -264,6 +263,7 @@ export declare function screenMemoryMcpToolDefinitions(): ({
264
263
  description: string;
265
264
  };
266
265
  chapterId?: undefined;
266
+ count?: undefined;
267
267
  startAt?: undefined;
268
268
  endAt?: undefined;
269
269
  includeMicrophone?: undefined;
@@ -314,13 +314,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
314
314
  type: string;
315
315
  required: string[];
316
316
  properties: {
317
- count?: undefined;
318
317
  query?: undefined;
319
318
  minutes?: undefined;
320
319
  limit?: undefined;
321
320
  clientHint?: undefined;
322
321
  timestamp?: undefined;
323
322
  chapterId?: undefined;
323
+ count?: undefined;
324
324
  startAt: {
325
325
  type: string;
326
326
  description: string;
@@ -349,13 +349,13 @@ export declare function screenMemoryMcpToolDefinitions(): ({
349
349
  type: string;
350
350
  required: string[];
351
351
  properties: {
352
- count?: undefined;
353
352
  query?: undefined;
354
353
  minutes?: undefined;
355
354
  limit?: undefined;
356
355
  clientHint?: undefined;
357
356
  timestamp?: undefined;
358
357
  chapterId?: undefined;
358
+ count?: undefined;
359
359
  startAt?: undefined;
360
360
  endAt?: undefined;
361
361
  reason?: undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.160.0",
3
+ "version": "0.160.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": {
@@ -427,8 +427,8 @@
427
427
  "y-protocols": "^1.0.7",
428
428
  "yjs": "^13.6.31",
429
429
  "zod": "^4.3.6",
430
- "@agent-native/recap-cli": "0.5.4",
431
- "@agent-native/toolkit": "^0.16.4"
430
+ "@agent-native/toolkit": "^0.16.4",
431
+ "@agent-native/recap-cli": "0.5.4"
432
432
  },
433
433
  "devDependencies": {
434
434
  "@ai-sdk/anthropic": "^3.0.71",