@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.
- package/dist/agent/production-agent.js +10 -11
- package/dist/agent/tool-approval-migrations.d.ts +1 -0
- package/dist/agent/tool-approval-migrations.js +10 -0
- package/dist/agent/tool-approval-store.d.ts +5 -3
- package/dist/agent/tool-approval-store.js +11 -6
- package/dist/cli/code-agent-connector.js +79 -7
- package/dist/cli/portal-transfer.d.ts +43 -0
- package/dist/cli/portal-transfer.js +201 -0
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/package.json +3 -3
|
@@ -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
|
|
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
|
|
7792
|
-
//
|
|
7793
|
-
//
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
-
...
|
|
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
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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.
|
|
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/
|
|
431
|
-
"@agent-native/
|
|
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",
|