@agent-native/core 0.160.0 → 0.161.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.
Files changed (97) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/actions/bigquery.ts +57 -0
  3. package/corpus/templates/analytics/actions/restore-analysis-revision.ts +5 -2
  4. package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +5 -2
  5. package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +14 -3
  6. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +14 -3
  7. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -0
  8. package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +1 -0
  9. package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +16 -1
  10. package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +19 -0
  11. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +0 -1
  12. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +14 -0
  13. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +19 -0
  14. package/corpus/templates/analytics/changelog/2026-08-17-fixed-dashboard-editing-focus-history-recovery-panel-setup-f.md +6 -0
  15. package/corpus/templates/calendar/actions/create-event.ts +7 -3
  16. package/corpus/templates/calendar/actions/manage-event-draft.ts +8 -0
  17. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +64 -0
  18. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +5 -1
  19. package/corpus/templates/calendar/app/hooks/use-events.ts +1 -0
  20. package/corpus/templates/calendar/changelog/2026-08-17-booking-links-now-respect-each-required-host-s-saved-availab.md +6 -0
  21. package/corpus/templates/calendar/server/lib/booking-host-availability.ts +76 -0
  22. package/corpus/templates/calendar/server/lib/google-calendar.ts +2 -0
  23. package/corpus/templates/calendar/shared/api.ts +1 -1
  24. package/corpus/templates/clips/app/components/player/signed-out-share-actions.tsx +47 -0
  25. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +3 -1
  26. package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -19
  27. package/corpus/templates/clips/changelog/2026-08-17-shared-clips-sign-in-comment-no-audio.md +6 -0
  28. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +6 -5
  29. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +13 -0
  30. package/corpus/templates/content/AGENTS.md +2 -0
  31. package/corpus/templates/content/actions/_blocks-field-identity.ts +17 -0
  32. package/corpus/templates/content/actions/_database-block-actions.ts +958 -0
  33. package/corpus/templates/content/actions/_database-row-mutation.ts +7 -7
  34. package/corpus/templates/content/actions/list-content-database-blocks.ts +31 -0
  35. package/corpus/templates/content/actions/mutate-content-database-block.ts +49 -0
  36. package/corpus/templates/content/changelog/2026-08-10-agents-can-safely-insert-update-upsert-delete-and-reorder-on.md +6 -0
  37. package/corpus/templates/content/docs/product/capabilities/content.object.block.md +6 -2
  38. package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +2 -1
  39. package/corpus/templates/content/parity/matrix.md +30 -30
  40. package/corpus/templates/content/parity/matrix.ts +4 -1
  41. package/corpus/templates/content/shared/blocks-field-identity.ts +114 -37
  42. package/corpus/templates/content/shared/database-block-actions.ts +82 -0
  43. package/corpus/templates/content/shared/database-block-mutations.ts +366 -0
  44. package/corpus/templates/content/shared/nfm.ts +3 -2
  45. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +24 -0
  46. package/corpus/templates/mail/actions/get-mail-settings.ts +1 -1
  47. package/corpus/templates/mail/actions/update-mail-settings.ts +7 -3
  48. package/corpus/templates/mail/app/i18n/en-US.ts +3 -1
  49. package/corpus/templates/mail/app/pages/SettingsPage.tsx +63 -0
  50. package/corpus/templates/mail/changelog/2026-08-17-gmail-signatures-now-preserve-images-and-support-paste-or-up.md +6 -0
  51. package/corpus/templates/mail/changelog/2026-08-17-mail-now-updates-durable-drafting-preferences-without-openin.md +6 -0
  52. package/corpus/templates/mail/server/plugins/agent-chat.ts +19 -0
  53. package/corpus/templates/mail/shared/gmail-signature.ts +14 -2
  54. package/corpus/templates/mail/shared/signature.ts +1 -10
  55. package/corpus/templates/slides/actions/generate-slides-ai.ts +2 -0
  56. package/corpus/templates/slides/actions/patch-deck.ts +1 -1
  57. package/corpus/templates/slides/app/components/ThemeToggle.tsx +1 -0
  58. package/corpus/templates/slides/app/components/editor/SlideThumbnailContextMenu.tsx +59 -0
  59. package/corpus/templates/slides/app/global.css +1 -0
  60. package/corpus/templates/slides/app/lib/normalize-slide-padding.ts +40 -11
  61. package/corpus/templates/slides/changelog/2026-08-17-stable-layout-and-slide-editing.md +6 -0
  62. package/corpus/templates/slides/shared/deck-title.ts +12 -4
  63. package/dist/agent/production-agent.js +27 -15
  64. package/dist/agent/tool-approval-migrations.d.ts +2 -0
  65. package/dist/agent/tool-approval-migrations.js +20 -0
  66. package/dist/agent/tool-approval-store.d.ts +17 -3
  67. package/dist/agent/tool-approval-store.js +56 -6
  68. package/dist/agent/types.d.ts +3 -1
  69. package/dist/cli/code-agent-connector.js +79 -7
  70. package/dist/cli/portal-transfer.d.ts +43 -0
  71. package/dist/cli/portal-transfer.js +201 -0
  72. package/dist/client/AssistantChat.js +3 -1
  73. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  74. package/dist/localization/core-messages/ar-SA.js +1 -0
  75. package/dist/localization/core-messages/de-DE.js +1 -0
  76. package/dist/localization/core-messages/en-US.d.ts +1 -0
  77. package/dist/localization/core-messages/en-US.js +1 -0
  78. package/dist/localization/core-messages/es-ES.js +1 -0
  79. package/dist/localization/core-messages/fr-FR.js +1 -0
  80. package/dist/localization/core-messages/hi-IN.js +1 -0
  81. package/dist/localization/core-messages/ja-JP.js +1 -0
  82. package/dist/localization/core-messages/ko-KR.js +1 -0
  83. package/dist/localization/core-messages/pt-BR.js +1 -0
  84. package/dist/localization/core-messages/zh-CN.js +1 -0
  85. package/dist/localization/core-messages/zh-TW.js +1 -0
  86. package/dist/localization/core-messages.d.ts +1 -0
  87. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  88. package/dist/resources/handlers.d.ts +1 -1
  89. package/dist/scripts/resources/read.js +4 -4
  90. package/dist/scripts/resources/save-memory.js +15 -2
  91. package/dist/scripts/resources/write.js +3 -3
  92. package/dist/server/agent-chat/script-entries.d.ts +1 -0
  93. package/dist/server/agent-chat/script-entries.js +8 -0
  94. package/dist/server/realtime-token.d.ts +1 -1
  95. package/dist/server/transcribe-voice.d.ts +1 -1
  96. package/docs/content/onboarding.mdx +13 -2
  97. package/package.json +1 -1
@@ -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_RECOVERY_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,15 @@ 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);
16
+ await ensureIndexExists("idx_agent_tool_approvals_recovery", AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL);
15
17
  return;
16
18
  }
17
19
  const client = getDbExec();
18
20
  await retryOnDdlRace(() => client.execute(createSql));
19
21
  await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_INDEX_SQL));
22
+ await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_LOGICAL_INDEX_SQL));
23
+ await retryOnDdlRace(() => client.execute(AGENT_TOOL_APPROVAL_RECOVERY_INDEX_SQL));
20
24
  })().catch((error) => {
21
25
  initPromise = undefined;
22
26
  throw error;
@@ -27,6 +31,49 @@ async function ensureAgentToolApprovalTable() {
27
31
  export function hashAgentToolApprovalKey(approvalKey) {
28
32
  return createHash("sha256").update(approvalKey).digest("hex");
29
33
  }
34
+ /**
35
+ * Recover the durable scope for an approval continuation when a transport
36
+ * drops the original turn id. A single pending logical turn is safe to
37
+ * recover; multiple turns are deliberately ambiguous and stay unmatched.
38
+ */
39
+ export async function resolveAgentToolApprovalTurnId(binding) {
40
+ const approvalKeys = [...new Set(binding.approvalKeys)].slice(0, 200);
41
+ if (!binding.threadId || approvalKeys.length === 0)
42
+ return null;
43
+ await ensureAgentToolApprovalTable();
44
+ const now = Date.now();
45
+ const hashes = approvalKeys.map(hashAgentToolApprovalKey);
46
+ const placeholders = hashes.map(() => "?").join(", ");
47
+ const result = await getDbExec().execute({
48
+ sql: `SELECT turn_id FROM agent_tool_approvals
49
+ WHERE owner_email = ?
50
+ AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
51
+ AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
52
+ AND approval_key_hash IN (${placeholders})
53
+ AND status = 'pending'
54
+ AND expires_at > ?
55
+ AND turn_id IS NOT NULL`,
56
+ args: [
57
+ binding.ownerEmail,
58
+ binding.orgId ?? null,
59
+ binding.orgId ?? null,
60
+ binding.threadId,
61
+ binding.threadId,
62
+ ...hashes,
63
+ now,
64
+ ],
65
+ });
66
+ const turnIds = new Set((result.rows ?? [])
67
+ .map((row) => {
68
+ const value = row.turn_id;
69
+ return typeof value === "string" && value.trim() ? value.trim() : null;
70
+ })
71
+ .filter((turnId) => turnId !== null));
72
+ const requestedTurnId = binding.requestedTurnId?.trim();
73
+ if (requestedTurnId && turnIds.has(requestedTurnId))
74
+ return requestedTurnId;
75
+ return turnIds.size === 1 ? [...turnIds][0] : null;
76
+ }
30
77
  export async function createAgentToolApproval(binding) {
31
78
  await ensureAgentToolApprovalTable();
32
79
  const now = Date.now();
@@ -65,9 +112,11 @@ export async function createAgentToolApproval(binding) {
65
112
  }
66
113
  }
67
114
  /**
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.
115
+ * Atomically consume the server-created approval for one logical tool call.
116
+ * The model's call id is transport metadata and can change when Dispatch
117
+ * reconstructs a paused turn, so it is deliberately not part of authorization.
118
+ * The pending row remains the boundary: client history and approval keys alone
119
+ * cannot manufacture a grant.
71
120
  */
72
121
  export async function consumeAgentToolApproval(binding) {
73
122
  await ensureAgentToolApprovalTable();
@@ -80,8 +129,8 @@ export async function consumeAgentToolApproval(binding) {
80
129
  WHERE owner_email = ?
81
130
  AND ((org_id IS NULL AND CAST(? AS TEXT) IS NULL) OR org_id = ?)
82
131
  AND ((thread_id IS NULL AND CAST(? AS TEXT) IS NULL) OR thread_id = ?)
132
+ AND ((turn_id IS NULL AND CAST(? AS TEXT) IS NULL) OR turn_id = ?)
83
133
  AND tool_name = ?
84
- AND call_id = ?
85
134
  AND approval_key_hash = ?
86
135
  AND status = 'pending'
87
136
  AND expires_at > ?
@@ -97,8 +146,9 @@ export async function consumeAgentToolApproval(binding) {
97
146
  binding.orgId ?? null,
98
147
  binding.threadId ?? null,
99
148
  binding.threadId ?? null,
149
+ binding.turnId ?? null,
150
+ binding.turnId ?? null,
100
151
  binding.toolName,
101
- binding.callId,
102
152
  hashAgentToolApprovalKey(binding.approvalKey),
103
153
  now,
104
154
  ],
@@ -232,7 +232,9 @@ export interface AgentChatRequest {
232
232
  * `approval_required`; the client re-issues the turn (typically an empty
233
233
  * continuation) with the approved call's key here so the gate lets it run.
234
234
  * Keys not present here keep the action paused. The model never sees or sets
235
- * this — it is supplied by the human's approve affordance.
235
+ * this — it is supplied by the human's approve affordance. Clients should
236
+ * preserve the original turnId; the server can recover one uniquely pending
237
+ * durable grant when a transport drops it, but refuses ambiguous matches.
236
238
  */
237
239
  approvedToolCalls?: string[];
238
240
  }
@@ -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
+ }
@@ -4094,7 +4094,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4094
4094
  !authError &&
4095
4095
  showEmptyState &&
4096
4096
  !isRestoring;
4097
- const threadRestoreErrorSurface = threadRestoreError ? (_jsxs("div", { role: "alert", className: "flex max-w-[320px] flex-col items-center gap-3 rounded-lg border border-border bg-muted/30 px-4 py-3 text-center", children: [_jsx(IconRefresh, { className: "h-5 w-5 text-muted-foreground" }), _jsx("p", { className: "text-sm text-muted-foreground", children: t("agentChat.message.restoreRequestFailed") }), _jsx("button", { type: "button", onClick: retryThreadRestore, className: "rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: t("agentChat.common.retry") })] })) : null;
4097
+ const threadRestoreErrorSurface = threadRestoreError ? (_jsxs("div", { role: "alert", className: "flex max-w-[320px] flex-col items-center gap-3 rounded-lg border border-border bg-muted/30 px-4 py-3 text-center", children: [_jsx(IconRefresh, { className: "h-5 w-5 text-muted-foreground" }), _jsx("p", { className: "text-sm text-muted-foreground", children: threadRestoreError === "not-found"
4098
+ ? t("agentChat.message.threadNotFound")
4099
+ : t("agentChat.message.restoreRequestFailed") }), _jsx("button", { type: "button", onClick: retryThreadRestore, className: "rounded-md border border-border px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent", children: t("agentChat.common.retry") })] })) : null;
4098
4100
  // Clarifying-question surface: the `ask-question` action writes a
4099
4101
  // GuidedQuestionPayload to application_state under "guided-questions". The
4100
4102
  // hook polls that key, and on submit/skip composes the answer as a normal
@@ -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;
@@ -324,6 +324,7 @@ const messages = {
324
324
  "message.restoreFailed": "فشلت الاستعادة ({{status}}).",
325
325
  "message.restoreQuestion": "هل تريد الاستعادة إلى هنا؟",
326
326
  "message.restoreRequestFailed": "فشل طلب الاستعادة.",
327
+ "message.threadNotFound": "لم تعد سلسلة الدردشة هذه متاحة. ابدأ دردشة جديدة أو أعد المحاولة إذا كان ذلك غير متوقع.",
327
328
  "message.restoring": "جارٍ الاستعادة...",
328
329
  "message.revertHere": "الرجوع إلى هنا",
329
330
  "message.sentAt": "أُرسلت في {{time}}",
@@ -94,6 +94,7 @@ const messages = {
94
94
  "message.restoreFailed": "Wiederherstellung fehlgeschlagen ({{status}}).",
95
95
  "message.restoreQuestion": "Bis hierher wiederherstellen?",
96
96
  "message.restoreRequestFailed": "Wiederherstellungsanfrage fehlgeschlagen.",
97
+ "message.threadNotFound": "Dieser Chat-Thread ist nicht mehr verfügbar. Starte einen neuen Chat oder versuche es erneut, falls das unerwartet war.",
97
98
  "message.restoring": "Wird wiederhergestellt...",
98
99
  "message.revertHere": "Bis hierher zurücksetzen",
99
100
  "message.sentAt": "Gesendet {{time}}",
@@ -312,6 +312,7 @@ declare const messages: {
312
312
  readonly "message.restoreFailed": "Restore failed ({{status}}).";
313
313
  readonly "message.restoreQuestion": "Restore to here?";
314
314
  readonly "message.restoreRequestFailed": "Restore request failed.";
315
+ readonly "message.threadNotFound": "This chat thread is no longer available. Start a new chat or retry if this was unexpected.";
315
316
  readonly "message.restoring": "Restoring...";
316
317
  readonly "message.revertHere": "Revert to here";
317
318
  readonly "message.sentAt": "Sent {{time}}";
@@ -312,6 +312,7 @@ const messages = {
312
312
  "message.restoreFailed": "Restore failed ({{status}}).",
313
313
  "message.restoreQuestion": "Restore to here?",
314
314
  "message.restoreRequestFailed": "Restore request failed.",
315
+ "message.threadNotFound": "This chat thread is no longer available. Start a new chat or retry if this was unexpected.",
315
316
  "message.restoring": "Restoring...",
316
317
  "message.revertHere": "Revert to here",
317
318
  "message.sentAt": "Sent {{time}}",
@@ -94,6 +94,7 @@ const messages = {
94
94
  "message.restoreFailed": "Error al restaurar ({{status}}).",
95
95
  "message.restoreQuestion": "¿Restaurar hasta aquí?",
96
96
  "message.restoreRequestFailed": "Error en la solicitud de restauración.",
97
+ "message.threadNotFound": "Este hilo de chat ya no está disponible. Inicia un chat nuevo o inténtalo de nuevo si esto no era esperado.",
97
98
  "message.restoring": "Restaurando...",
98
99
  "message.revertHere": "Revertir hasta aquí",
99
100
  "message.sentAt": "Enviado a las {{time}}",
@@ -94,6 +94,7 @@ const messages = {
94
94
  "message.restoreFailed": "Échec de la restauration ({{status}}).",
95
95
  "message.restoreQuestion": "Restaurer jusqu’ici ?",
96
96
  "message.restoreRequestFailed": "Échec de la demande de restauration.",
97
+ "message.threadNotFound": "Ce fil de discussion n’est plus disponible. Démarrez une nouvelle discussion ou réessayez si cela est inattendu.",
97
98
  "message.restoring": "Restauration...",
98
99
  "message.revertHere": "Revenir jusqu’ici",
99
100
  "message.sentAt": "Envoyé à {{time}}",
@@ -312,6 +312,7 @@ const messages = {
312
312
  "message.restoreFailed": "पुनर्स्थापना विफल रही ({{status}})।",
313
313
  "message.restoreQuestion": "यहाँ तक पुनर्स्थापित करें?",
314
314
  "message.restoreRequestFailed": "पुनर्स्थापना अनुरोध विफल रहा।",
315
+ "message.threadNotFound": "यह चैट थ्रेड अब उपलब्ध नहीं है। नई चैट शुरू करें या यदि यह अप्रत्याशित है तो फिर कोशिश करें।",
315
316
  "message.restoring": "पुनर्स्थापित किया जा रहा है...",
316
317
  "message.revertHere": "यहाँ तक वापस जाएँ",
317
318
  "message.sentAt": "{{time}} पर भेजा गया",
@@ -309,6 +309,7 @@ const messages = {
309
309
  "message.restoreFailed": "復元に失敗しました({{status}})。",
310
310
  "message.restoreQuestion": "ここまで復元しますか?",
311
311
  "message.restoreRequestFailed": "復元リクエストに失敗しました。",
312
+ "message.threadNotFound": "このチャットスレッドは利用できなくなりました。新しいチャットを開始するか、想定外の場合は再試行してください。",
312
313
  "message.restoring": "復元中...",
313
314
  "message.revertHere": "ここまで戻す",
314
315
  "message.sentAt": "{{time}} に送信",
@@ -309,6 +309,7 @@ const messages = {
309
309
  "message.restoreFailed": "복원 실패({{status}}).",
310
310
  "message.restoreQuestion": "여기로 복원하시겠습니까?",
311
311
  "message.restoreRequestFailed": "복원 요청에 실패했습니다.",
312
+ "message.threadNotFound": "이 채팅 스레드는 더 이상 사용할 수 없습니다. 새 채팅을 시작하거나 예상치 못한 문제라면 다시 시도하세요.",
312
313
  "message.restoring": "복원 중...",
313
314
  "message.revertHere": "여기로 되돌리기",
314
315
  "message.sentAt": "{{time}}에 전송",
@@ -94,6 +94,7 @@ const messages = {
94
94
  "message.restoreFailed": "Falha ao restaurar ({{status}}).",
95
95
  "message.restoreQuestion": "Restaurar até aqui?",
96
96
  "message.restoreRequestFailed": "Falha na solicitação de restauração.",
97
+ "message.threadNotFound": "Esta conversa não está mais disponível. Inicie uma nova conversa ou tente novamente se isso for inesperado.",
97
98
  "message.restoring": "Restaurando...",
98
99
  "message.revertHere": "Reverter até aqui",
99
100
  "message.sentAt": "Enviado às {{time}}",
@@ -309,6 +309,7 @@ const messages = {
309
309
  "message.restoreFailed": "恢复失败({{status}})。",
310
310
  "message.restoreQuestion": "恢复到此处?",
311
311
  "message.restoreRequestFailed": "恢复请求失败。",
312
+ "message.threadNotFound": "此聊天线程已不可用。请开始新聊天;如果这是意外情况,也可以重试。",
312
313
  "message.restoring": "正在恢复...",
313
314
  "message.revertHere": "还原到此处",
314
315
  "message.sentAt": "发送于 {{time}}",
@@ -309,6 +309,7 @@ const messages = {
309
309
  "message.restoreFailed": "還原失敗({{status}})。",
310
310
  "message.restoreQuestion": "要還原到這裡嗎?",
311
311
  "message.restoreRequestFailed": "還原要求失敗。",
312
+ "message.threadNotFound": "此聊天串已無法使用。請開始新的聊天;如果這是意外情況,也可以重試。",
312
313
  "message.restoring": "正在還原...",
313
314
  "message.revertHere": "還原到這裡",
314
315
  "message.sentAt": "傳送於 {{time}}",
@@ -314,6 +314,7 @@ export declare const englishAgentChatMessages: {
314
314
  readonly "message.restoreFailed": "Restore failed ({{status}}).";
315
315
  readonly "message.restoreQuestion": "Restore to here?";
316
316
  readonly "message.restoreRequestFailed": "Restore request failed.";
317
+ readonly "message.threadNotFound": "This chat thread is no longer available. Start a new chat or retry if this was unexpected.";
317
318
  readonly "message.restoring": "Restoring...";
318
319
  readonly "message.revertHere": "Revert to here";
319
320
  readonly "message.sentAt": "Sent {{time}}";