@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/bigquery.ts +57 -0
- package/corpus/templates/analytics/actions/restore-analysis-revision.ts +5 -2
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +5 -2
- package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +14 -3
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +14 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +1 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +16 -1
- package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +19 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +0 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-sync.ts +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +19 -0
- package/corpus/templates/analytics/changelog/2026-08-17-fixed-dashboard-editing-focus-history-recovery-panel-setup-f.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +7 -3
- package/corpus/templates/calendar/actions/manage-event-draft.ts +8 -0
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +64 -0
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +5 -1
- package/corpus/templates/calendar/app/hooks/use-events.ts +1 -0
- package/corpus/templates/calendar/changelog/2026-08-17-booking-links-now-respect-each-required-host-s-saved-availab.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-host-availability.ts +76 -0
- package/corpus/templates/calendar/server/lib/google-calendar.ts +2 -0
- package/corpus/templates/calendar/shared/api.ts +1 -1
- package/corpus/templates/clips/app/components/player/signed-out-share-actions.tsx +47 -0
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +3 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -19
- package/corpus/templates/clips/changelog/2026-08-17-shared-clips-sign-in-comment-no-audio.md +6 -0
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +6 -5
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +13 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/_blocks-field-identity.ts +17 -0
- package/corpus/templates/content/actions/_database-block-actions.ts +958 -0
- package/corpus/templates/content/actions/_database-row-mutation.ts +7 -7
- package/corpus/templates/content/actions/list-content-database-blocks.ts +31 -0
- package/corpus/templates/content/actions/mutate-content-database-block.ts +49 -0
- package/corpus/templates/content/changelog/2026-08-10-agents-can-safely-insert-update-upsert-delete-and-reorder-on.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.object.block.md +6 -2
- package/corpus/templates/content/docs/product/capabilities/content.object.blocks-field.md +2 -1
- package/corpus/templates/content/parity/matrix.md +30 -30
- package/corpus/templates/content/parity/matrix.ts +4 -1
- package/corpus/templates/content/shared/blocks-field-identity.ts +114 -37
- package/corpus/templates/content/shared/database-block-actions.ts +82 -0
- package/corpus/templates/content/shared/database-block-mutations.ts +366 -0
- package/corpus/templates/content/shared/nfm.ts +3 -2
- package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +24 -0
- package/corpus/templates/mail/actions/get-mail-settings.ts +1 -1
- package/corpus/templates/mail/actions/update-mail-settings.ts +7 -3
- package/corpus/templates/mail/app/i18n/en-US.ts +3 -1
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +63 -0
- package/corpus/templates/mail/changelog/2026-08-17-gmail-signatures-now-preserve-images-and-support-paste-or-up.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-17-mail-now-updates-durable-drafting-preferences-without-openin.md +6 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +19 -0
- package/corpus/templates/mail/shared/gmail-signature.ts +14 -2
- package/corpus/templates/mail/shared/signature.ts +1 -10
- package/corpus/templates/slides/actions/generate-slides-ai.ts +2 -0
- package/corpus/templates/slides/actions/patch-deck.ts +1 -1
- package/corpus/templates/slides/app/components/ThemeToggle.tsx +1 -0
- package/corpus/templates/slides/app/components/editor/SlideThumbnailContextMenu.tsx +59 -0
- package/corpus/templates/slides/app/global.css +1 -0
- package/corpus/templates/slides/app/lib/normalize-slide-padding.ts +40 -11
- package/corpus/templates/slides/changelog/2026-08-17-stable-layout-and-slide-editing.md +6 -0
- package/corpus/templates/slides/shared/deck-title.ts +12 -4
- package/dist/agent/production-agent.js +27 -15
- package/dist/agent/tool-approval-migrations.d.ts +2 -0
- package/dist/agent/tool-approval-migrations.js +20 -0
- package/dist/agent/tool-approval-store.d.ts +17 -3
- package/dist/agent/tool-approval-store.js +56 -6
- package/dist/agent/types.d.ts +3 -1
- 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/client/AssistantChat.js +3 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +1 -0
- package/dist/localization/core-messages/de-DE.js +1 -0
- package/dist/localization/core-messages/en-US.d.ts +1 -0
- package/dist/localization/core-messages/en-US.js +1 -0
- package/dist/localization/core-messages/es-ES.js +1 -0
- package/dist/localization/core-messages/fr-FR.js +1 -0
- package/dist/localization/core-messages/hi-IN.js +1 -0
- package/dist/localization/core-messages/ja-JP.js +1 -0
- package/dist/localization/core-messages/ko-KR.js +1 -0
- package/dist/localization/core-messages/pt-BR.js +1 -0
- package/dist/localization/core-messages/zh-CN.js +1 -0
- package/dist/localization/core-messages/zh-TW.js +1 -0
- package/dist/localization/core-messages.d.ts +1 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/resources/read.js +4 -4
- package/dist/scripts/resources/save-memory.js +15 -2
- package/dist/scripts/resources/write.js +3 -3
- package/dist/server/agent-chat/script-entries.d.ts +1 -0
- package/dist/server/agent-chat/script-entries.js +8 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/onboarding.mdx +13 -2
- 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
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
],
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -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
|
-
...
|
|
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
|
+
}
|
|
@@ -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:
|
|
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}}";
|