@agent-native/core 0.188.0 → 0.189.0-nightly-20260925200527
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/content/actions/list-document-versions.ts +102 -2
- package/corpus/templates/content/actions/restore-document-version.ts +50 -2
- package/corpus/templates/content/app/components/layout/Layout.tsx +6 -1
- package/corpus/templates/content/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
- package/corpus/templates/content/server/lib/document-history.ts +59 -8
- package/corpus/templates/content/server/lib/document-version-context.ts +5 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +164 -0
- package/corpus/templates/design/actions/list-design-versions.ts +3 -1
- package/corpus/templates/design/app/components/layout/Layout.tsx +17 -2
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +10 -3
- package/corpus/templates/design/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
- package/corpus/templates/design/server/lib/design-versions.ts +136 -20
- package/corpus/templates/design/server/plugins/agent-chat.ts +18 -2
- package/corpus/templates/slides/actions/list-deck-versions.ts +62 -10
- package/corpus/templates/slides/app/components/layout/Layout.tsx +10 -2
- package/corpus/templates/slides/changelog/2026-09-25-chat-messages-can-be-reverted-to-their-autosaved-version-inc.md +6 -0
- package/corpus/templates/slides/server/lib/deck-versions.ts +36 -1
- package/corpus/templates/slides/server/plugins/agent-chat.ts +26 -2
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/AssistantChat.js +112 -35
- package/dist/client/chat/assistant-chat-history-version.d.ts +1 -0
- package/dist/client/chat/message-components.d.ts +8 -1
- package/dist/client/chat/message-components.js +52 -11
- package/dist/client/resources/mcp-integration-catalog.js +1 -1
- package/dist/client/resources/mcp-integration-logos.js +29 -30
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +2 -0
- package/dist/localization/core-messages/de-DE.js +2 -0
- package/dist/localization/core-messages/en-US.d.ts +2 -0
- package/dist/localization/core-messages/en-US.js +2 -0
- package/dist/localization/core-messages/es-ES.js +2 -0
- package/dist/localization/core-messages/fr-FR.js +2 -0
- package/dist/localization/core-messages/hi-IN.js +2 -0
- package/dist/localization/core-messages/ja-JP.js +2 -0
- package/dist/localization/core-messages/ko-KR.js +2 -0
- package/dist/localization/core-messages/pt-BR.js +2 -0
- package/dist/localization/core-messages/zh-CN.js +2 -0
- package/dist/localization/core-messages/zh-TW.js +2 -0
- package/dist/localization/core-messages.d.ts +2 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +4 -4
- package/dist/org/handlers.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/plugin-options.d.ts +2 -0
- package/dist/server/agent-chat-plugin.d.ts +1 -0
- package/dist/server/agent-chat-plugin.js +26 -0
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/onboarding-html.js +28 -5
- package/docs/content/deployment.mdx +12 -1
- package/package.json +1 -1
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { captureError } from "@agent-native/core/server";
|
|
19
19
|
import { getRequestUserEmail } from "@agent-native/core/server/request-context";
|
|
20
20
|
import { accessFilter, assertAccess } from "@agent-native/core/sharing";
|
|
21
|
-
import { and, asc, desc, eq, isNull, sql } from "drizzle-orm";
|
|
21
|
+
import { and, asc, desc, eq, isNull, like, sql } from "drizzle-orm";
|
|
22
22
|
import { nanoid } from "nanoid";
|
|
23
23
|
|
|
24
24
|
import {
|
|
@@ -49,6 +49,7 @@ export interface DesignVersionChatContext {
|
|
|
49
49
|
runId?: string;
|
|
50
50
|
turnId?: string;
|
|
51
51
|
actionName?: string;
|
|
52
|
+
phase?: "start" | "end";
|
|
52
53
|
surface?: "editor";
|
|
53
54
|
/** Which editor-surface caller wrote this checkpoint. Only set alongside
|
|
54
55
|
* `surface: "editor"` — see Throttle 1 below for why it matters. */
|
|
@@ -172,6 +173,15 @@ function stableStringify(value: unknown): string {
|
|
|
172
173
|
.join(",")}}`;
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
function designVersionIdForIdempotencyKey(
|
|
177
|
+
designId: string,
|
|
178
|
+
idempotencyKey: string,
|
|
179
|
+
) {
|
|
180
|
+
return `design-version-${createHash("sha256")
|
|
181
|
+
.update(stableStringify({ designId, idempotencyKey }))
|
|
182
|
+
.digest("hex")}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
175
185
|
function nextRevisionTimestamp(previous: string | null | undefined): string {
|
|
176
186
|
const previousMs = previous ? Date.parse(previous) : Number.NaN;
|
|
177
187
|
return new Date(
|
|
@@ -204,6 +214,9 @@ function parseChatContext(
|
|
|
204
214
|
context[key] = candidate;
|
|
205
215
|
}
|
|
206
216
|
}
|
|
217
|
+
if (value.phase === "start" || value.phase === "end") {
|
|
218
|
+
context.phase = value.phase;
|
|
219
|
+
}
|
|
207
220
|
if (value.surface === "editor") context.surface = "editor";
|
|
208
221
|
if (value.caller === "frontend" || value.caller === "webmcp") {
|
|
209
222
|
context.caller = value.caller;
|
|
@@ -414,7 +427,7 @@ function chatContextKey(
|
|
|
414
427
|
if (!context || context.surface === "editor") return null;
|
|
415
428
|
const scope = context.threadId ?? "";
|
|
416
429
|
const turn = context.turnId ?? context.runId ?? "";
|
|
417
|
-
return turn ? `${scope}:${turn}` : null;
|
|
430
|
+
return turn ? `${scope}:${turn}:${context.phase ?? ""}` : null;
|
|
418
431
|
}
|
|
419
432
|
|
|
420
433
|
function actionChatContext(
|
|
@@ -621,6 +634,7 @@ async function captureDesignVersion(
|
|
|
621
634
|
chatContext?: DesignVersionChatContext;
|
|
622
635
|
deletionGeometry?: ComponentDeletionGeometry;
|
|
623
636
|
preferStoredFileContent?: boolean;
|
|
637
|
+
idempotencyKey?: string;
|
|
624
638
|
},
|
|
625
639
|
access: DesignAccess,
|
|
626
640
|
database?: DesignDatabase,
|
|
@@ -735,16 +749,18 @@ async function captureDesignVersion(
|
|
|
735
749
|
};
|
|
736
750
|
}
|
|
737
751
|
}
|
|
738
|
-
const id =
|
|
739
|
-
.
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
752
|
+
const id = options.idempotencyKey
|
|
753
|
+
? designVersionIdForIdempotencyKey(designId, options.idempotencyKey)
|
|
754
|
+
: `design-version-${createHash("sha256")
|
|
755
|
+
.update(
|
|
756
|
+
stableStringify({
|
|
757
|
+
designId,
|
|
758
|
+
previousVersionId: latest?.id ?? "initial",
|
|
759
|
+
chatContextKey: chatContextKey(options.chatContext),
|
|
760
|
+
stateHash,
|
|
761
|
+
}),
|
|
762
|
+
)
|
|
763
|
+
.digest("hex")}`;
|
|
748
764
|
const snapshot = JSON.stringify({
|
|
749
765
|
schemaVersion: 1,
|
|
750
766
|
snapshotKind: "design-history",
|
|
@@ -877,6 +893,41 @@ export async function createDesignVersionSnapshot(
|
|
|
877
893
|
});
|
|
878
894
|
}
|
|
879
895
|
|
|
896
|
+
export async function createDesignChatBeginningSnapshot(
|
|
897
|
+
designId: string,
|
|
898
|
+
run: { threadId: string; runId: string },
|
|
899
|
+
) {
|
|
900
|
+
return withDesignVersionLock(designId, async () => {
|
|
901
|
+
const access = await assertAccess("design", designId, "editor");
|
|
902
|
+
const rows = await getDb()
|
|
903
|
+
.select({ id: schema.designVersions.id })
|
|
904
|
+
.from(schema.designVersions)
|
|
905
|
+
.where(
|
|
906
|
+
and(
|
|
907
|
+
eq(schema.designVersions.designId, designId),
|
|
908
|
+
eq(
|
|
909
|
+
schema.designVersions.id,
|
|
910
|
+
designVersionIdForIdempotencyKey(
|
|
911
|
+
designId,
|
|
912
|
+
`chat-start:${run.threadId}`,
|
|
913
|
+
),
|
|
914
|
+
),
|
|
915
|
+
),
|
|
916
|
+
)
|
|
917
|
+
.limit(1);
|
|
918
|
+
if (rows.length) return null;
|
|
919
|
+
return captureDesignVersion(
|
|
920
|
+
designId,
|
|
921
|
+
{
|
|
922
|
+
label: "Before chat",
|
|
923
|
+
chatContext: { ...run, phase: "start" },
|
|
924
|
+
idempotencyKey: `chat-start:${run.threadId}`,
|
|
925
|
+
},
|
|
926
|
+
access,
|
|
927
|
+
);
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
880
931
|
function checkpointSkipReason(
|
|
881
932
|
error: unknown,
|
|
882
933
|
): DesignVersionCheckpointSkipReason {
|
|
@@ -1140,6 +1191,7 @@ export async function snapshotDesignBeforeAgentEditInVersionLock(
|
|
|
1140
1191
|
export async function listDesignVersions(
|
|
1141
1192
|
designId: string,
|
|
1142
1193
|
limit: number,
|
|
1194
|
+
threadId?: string,
|
|
1143
1195
|
): Promise<{
|
|
1144
1196
|
designId: string;
|
|
1145
1197
|
count: number;
|
|
@@ -1147,7 +1199,8 @@ export async function listDesignVersions(
|
|
|
1147
1199
|
versions: DesignVersionListEntry[];
|
|
1148
1200
|
}> {
|
|
1149
1201
|
await assertAccess("design", designId, "viewer");
|
|
1150
|
-
const
|
|
1202
|
+
const db = getDb();
|
|
1203
|
+
const rows = await db
|
|
1151
1204
|
.select({
|
|
1152
1205
|
id: schema.designVersions.id,
|
|
1153
1206
|
label: schema.designVersions.label,
|
|
@@ -1163,11 +1216,46 @@ export async function listDesignVersions(
|
|
|
1163
1216
|
desc(schema.designVersions.id),
|
|
1164
1217
|
)
|
|
1165
1218
|
.limit(limit);
|
|
1219
|
+
const beginningRows = await db
|
|
1220
|
+
.select({
|
|
1221
|
+
id: schema.designVersions.id,
|
|
1222
|
+
label: schema.designVersions.label,
|
|
1223
|
+
createdAt: schema.designVersions.createdAt,
|
|
1224
|
+
chatContext: schema.designVersions.chatContext,
|
|
1225
|
+
fileCount: schema.designVersions.fileCount,
|
|
1226
|
+
})
|
|
1227
|
+
.from(schema.designVersions)
|
|
1228
|
+
.where(
|
|
1229
|
+
threadId
|
|
1230
|
+
? and(
|
|
1231
|
+
eq(schema.designVersions.designId, designId),
|
|
1232
|
+
eq(
|
|
1233
|
+
schema.designVersions.id,
|
|
1234
|
+
designVersionIdForIdempotencyKey(
|
|
1235
|
+
designId,
|
|
1236
|
+
`chat-start:${threadId}`,
|
|
1237
|
+
),
|
|
1238
|
+
),
|
|
1239
|
+
)
|
|
1240
|
+
: and(
|
|
1241
|
+
eq(schema.designVersions.designId, designId),
|
|
1242
|
+
like(schema.designVersions.chatContext, '%"phase":"start"%'),
|
|
1243
|
+
),
|
|
1244
|
+
)
|
|
1245
|
+
.orderBy(
|
|
1246
|
+
asc(isNull(schema.designVersions.createdAt)),
|
|
1247
|
+
asc(schema.designVersions.createdAt),
|
|
1248
|
+
)
|
|
1249
|
+
.limit(threadId ? 1 : limit);
|
|
1250
|
+
const rowsById = new Map(
|
|
1251
|
+
[...rows, ...beginningRows].map((row) => [row.id, row]),
|
|
1252
|
+
);
|
|
1166
1253
|
|
|
1167
1254
|
const regular: DesignVersionListEntry[] = [];
|
|
1168
1255
|
const chat = new Map<string, DesignVersionListEntry>();
|
|
1256
|
+
const activeStartEntries: DesignVersionListEntry[] = [];
|
|
1169
1257
|
let invalidCount = 0;
|
|
1170
|
-
for (const row of
|
|
1258
|
+
for (const row of rowsById.values()) {
|
|
1171
1259
|
let chatContext: DesignVersionChatContext | undefined;
|
|
1172
1260
|
try {
|
|
1173
1261
|
chatContext = parseStoredChatContext(row.chatContext);
|
|
@@ -1195,19 +1283,47 @@ export async function listDesignVersions(
|
|
|
1195
1283
|
regular.push(entry);
|
|
1196
1284
|
continue;
|
|
1197
1285
|
}
|
|
1198
|
-
const previous = chat.get(key);
|
|
1199
1286
|
if (
|
|
1200
|
-
|
|
1201
|
-
|
|
1287
|
+
threadId &&
|
|
1288
|
+
chatContext?.threadId === threadId &&
|
|
1289
|
+
chatContext.phase === "start"
|
|
1202
1290
|
) {
|
|
1203
|
-
|
|
1291
|
+
activeStartEntries.push(entry);
|
|
1292
|
+
continue;
|
|
1204
1293
|
}
|
|
1294
|
+
const previous = chat.get(key);
|
|
1295
|
+
const replacePrevious =
|
|
1296
|
+
!previous ||
|
|
1297
|
+
(chatContext?.phase === "end"
|
|
1298
|
+
? versionTime(entry.createdAt) > versionTime(previous.createdAt)
|
|
1299
|
+
: versionTime(entry.createdAt) < versionTime(previous.createdAt));
|
|
1300
|
+
if (replacePrevious) chat.set(key, entry);
|
|
1205
1301
|
}
|
|
1206
1302
|
|
|
1207
|
-
const versions = [...regular, ...chat.values()].sort(
|
|
1303
|
+
const versions = [...regular, ...chat.values(), ...activeStartEntries].sort(
|
|
1208
1304
|
(left, right) => versionTime(right.createdAt) - versionTime(left.createdAt),
|
|
1209
1305
|
);
|
|
1210
|
-
|
|
1306
|
+
const limitedVersions = versions.slice(0, limit);
|
|
1307
|
+
const activeStart = threadId
|
|
1308
|
+
? versions.find(
|
|
1309
|
+
(version) =>
|
|
1310
|
+
version.chatContext?.threadId === threadId &&
|
|
1311
|
+
version.chatContext.phase === "start",
|
|
1312
|
+
)
|
|
1313
|
+
: undefined;
|
|
1314
|
+
if (activeStart && !limitedVersions.includes(activeStart)) {
|
|
1315
|
+
limitedVersions[limitedVersions.length - 1] = activeStart;
|
|
1316
|
+
limitedVersions.sort(
|
|
1317
|
+
(left, right) =>
|
|
1318
|
+
versionTime(right.createdAt) - versionTime(left.createdAt),
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
designId,
|
|
1323
|
+
count: limitedVersions.length,
|
|
1324
|
+
invalidCount,
|
|
1325
|
+
versions: limitedVersions,
|
|
1326
|
+
};
|
|
1211
1327
|
}
|
|
1212
1328
|
|
|
1213
1329
|
interface RestoreFile {
|
|
@@ -232,21 +232,37 @@ async function autosaveDesignAfterAgentTurn(
|
|
|
232
232
|
},
|
|
233
233
|
): Promise<void> {
|
|
234
234
|
if (scope.type !== "design" || !(await hasDesignEdit(run, scope.id))) return;
|
|
235
|
+
if (!run.threadId || !run.runId) return;
|
|
235
236
|
|
|
236
237
|
const { createDesignVersionSnapshot } =
|
|
237
238
|
await import("../lib/design-versions.js");
|
|
238
239
|
await createDesignVersionSnapshot(scope.id, {
|
|
239
240
|
label: "Chat autosave",
|
|
240
241
|
chatContext: {
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
threadId: run.threadId,
|
|
243
|
+
runId: run.runId,
|
|
243
244
|
...(run.turnId ? { turnId: run.turnId } : {}),
|
|
245
|
+
phase: "end",
|
|
244
246
|
},
|
|
245
247
|
});
|
|
246
248
|
}
|
|
247
249
|
|
|
250
|
+
async function autosaveDesignBeforeAgentTurn(
|
|
251
|
+
scope: { type: string; id: string },
|
|
252
|
+
run: { threadId?: string; runId?: string },
|
|
253
|
+
): Promise<void> {
|
|
254
|
+
if (scope.type !== "design" || !run.threadId || !run.runId) return;
|
|
255
|
+
const { createDesignChatBeginningSnapshot } =
|
|
256
|
+
await import("../lib/design-versions.js");
|
|
257
|
+
await createDesignChatBeginningSnapshot(scope.id, {
|
|
258
|
+
threadId: run.threadId,
|
|
259
|
+
runId: run.runId,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
248
263
|
export default createAgentChatPlugin({
|
|
249
264
|
appId: "design",
|
|
265
|
+
onAgentTurnStart: autosaveDesignBeforeAgentTurn,
|
|
250
266
|
onAgentTurnComplete: autosaveDesignAfterAgentTurn,
|
|
251
267
|
actions: guardRepromptActionRegistry(
|
|
252
268
|
loadActionsFromStaticRegistry(actionsRegistry),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import { assertAccess } from "@agent-native/core/sharing";
|
|
3
|
-
import { and, desc, eq } from "drizzle-orm";
|
|
3
|
+
import { and, asc, desc, eq, like } from "drizzle-orm";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
@@ -50,13 +50,18 @@ export default defineAction({
|
|
|
50
50
|
schema: z.object({
|
|
51
51
|
deckId: z.string().describe("Deck ID"),
|
|
52
52
|
limit: z.coerce.number().int().min(1).max(100).default(50),
|
|
53
|
+
threadId: z.string().min(1).optional(),
|
|
53
54
|
}),
|
|
54
55
|
http: { method: "GET" },
|
|
55
|
-
run: async ({ deckId, limit }) => {
|
|
56
|
+
run: async ({ deckId, limit, threadId }) => {
|
|
56
57
|
const access = await assertAccess("deck", deckId, "viewer");
|
|
57
58
|
const ownerEmail = access.resource.ownerEmail as string;
|
|
58
59
|
const db = getDb();
|
|
59
60
|
|
|
61
|
+
const where = and(
|
|
62
|
+
eq(schema.deckVersions.deckId, deckId),
|
|
63
|
+
eq(schema.deckVersions.ownerEmail, ownerEmail),
|
|
64
|
+
);
|
|
60
65
|
const versions = await db
|
|
61
66
|
.select({
|
|
62
67
|
id: schema.deckVersions.id,
|
|
@@ -68,19 +73,66 @@ export default defineAction({
|
|
|
68
73
|
createdAt: schema.deckVersions.createdAt,
|
|
69
74
|
})
|
|
70
75
|
.from(schema.deckVersions)
|
|
71
|
-
.where(
|
|
72
|
-
and(
|
|
73
|
-
eq(schema.deckVersions.deckId, deckId),
|
|
74
|
-
eq(schema.deckVersions.ownerEmail, ownerEmail),
|
|
75
|
-
),
|
|
76
|
-
)
|
|
76
|
+
.where(where)
|
|
77
77
|
.orderBy(desc(schema.deckVersions.createdAt))
|
|
78
78
|
.limit(limit);
|
|
79
|
+
const beginningVersions = await db
|
|
80
|
+
.select({
|
|
81
|
+
id: schema.deckVersions.id,
|
|
82
|
+
deckId: schema.deckVersions.deckId,
|
|
83
|
+
title: schema.deckVersions.title,
|
|
84
|
+
data: schema.deckVersions.data,
|
|
85
|
+
changeLabel: schema.deckVersions.changeLabel,
|
|
86
|
+
chatContext: schema.deckVersions.chatContext,
|
|
87
|
+
createdAt: schema.deckVersions.createdAt,
|
|
88
|
+
})
|
|
89
|
+
.from(schema.deckVersions)
|
|
90
|
+
.where(
|
|
91
|
+
threadId
|
|
92
|
+
? and(
|
|
93
|
+
where,
|
|
94
|
+
eq(schema.deckVersions.changeGroup, `start:thread:${threadId}`),
|
|
95
|
+
)
|
|
96
|
+
: and(
|
|
97
|
+
where,
|
|
98
|
+
like(schema.deckVersions.chatContext, '%"phase":"start"%'),
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
.orderBy(
|
|
102
|
+
threadId
|
|
103
|
+
? asc(schema.deckVersions.createdAt)
|
|
104
|
+
: desc(schema.deckVersions.createdAt),
|
|
105
|
+
)
|
|
106
|
+
.limit(threadId ? 1 : limit);
|
|
107
|
+
const versionsById = new Map(
|
|
108
|
+
[...beginningVersions, ...versions].map((version) => [
|
|
109
|
+
version.id,
|
|
110
|
+
version,
|
|
111
|
+
]),
|
|
112
|
+
);
|
|
113
|
+
const allVersions = [...versionsById.values()];
|
|
114
|
+
allVersions.sort(
|
|
115
|
+
(left, right) =>
|
|
116
|
+
new Date(right.createdAt).getTime() -
|
|
117
|
+
new Date(left.createdAt).getTime(),
|
|
118
|
+
);
|
|
119
|
+
const limitedVersions = allVersions.slice(0, limit);
|
|
120
|
+
const activeStart = threadId
|
|
121
|
+
? allVersions.find((version) => beginningVersions.includes(version))
|
|
122
|
+
: undefined;
|
|
123
|
+
if (activeStart && !limitedVersions.includes(activeStart)) {
|
|
124
|
+
limitedVersions[limitedVersions.length - 1] = activeStart;
|
|
125
|
+
limitedVersions.sort(
|
|
126
|
+
(left, right) =>
|
|
127
|
+
new Date(right.createdAt).getTime() -
|
|
128
|
+
new Date(left.createdAt).getTime(),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
79
131
|
|
|
80
132
|
return {
|
|
81
133
|
deckId,
|
|
82
|
-
count:
|
|
83
|
-
versions:
|
|
134
|
+
count: limitedVersions.length,
|
|
135
|
+
versions: limitedVersions.map((version) => {
|
|
84
136
|
let chatContext;
|
|
85
137
|
try {
|
|
86
138
|
chatContext = parseDeckVersionChatContext(version.chatContext);
|
|
@@ -21,6 +21,7 @@ import { IconMenu2 } from "@tabler/icons-react";
|
|
|
21
21
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
22
22
|
import { useLocation, useNavigate } from "react-router";
|
|
23
23
|
|
|
24
|
+
import { useDecks } from "@/context/DeckContext";
|
|
24
25
|
import { useSidebarCollapsed } from "@/hooks/use-sidebar-collapsed";
|
|
25
26
|
import {
|
|
26
27
|
buildSlidesAgentContext,
|
|
@@ -67,6 +68,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
67
68
|
const location = useLocation();
|
|
68
69
|
const navigate = useNavigate();
|
|
69
70
|
const t = useT();
|
|
71
|
+
const { flushDeckSave } = useDecks();
|
|
70
72
|
const creativeContextEnabled = useCreativeContextLab();
|
|
71
73
|
const isChatRoute =
|
|
72
74
|
location.pathname === "/chat" || location.pathname.startsWith("/chat/");
|
|
@@ -155,9 +157,14 @@ export function Layout({ children }: LayoutProps) {
|
|
|
155
157
|
if (!deckScope) return undefined;
|
|
156
158
|
const deckId = deckScope.id;
|
|
157
159
|
return {
|
|
160
|
+
beforeStart: () => flushDeckSave(deckId),
|
|
158
161
|
list: {
|
|
159
162
|
action: "list-deck-versions",
|
|
160
|
-
args:
|
|
163
|
+
args: (threadId) => ({
|
|
164
|
+
deckId,
|
|
165
|
+
limit: 100,
|
|
166
|
+
...(threadId ? { threadId } : {}),
|
|
167
|
+
}),
|
|
161
168
|
getVersions: (result: unknown) => {
|
|
162
169
|
const versions =
|
|
163
170
|
result && typeof result === "object"
|
|
@@ -174,9 +181,10 @@ export function Layout({ children }: LayoutProps) {
|
|
|
174
181
|
deckId,
|
|
175
182
|
versionId: version.id,
|
|
176
183
|
}),
|
|
184
|
+
beforeRestore: () => flushDeckSave(deckId),
|
|
177
185
|
},
|
|
178
186
|
};
|
|
179
|
-
}, [deckScope]);
|
|
187
|
+
}, [deckScope, flushDeckSave]);
|
|
180
188
|
|
|
181
189
|
useAgentChatHomeHandoffLinks({
|
|
182
190
|
storageKey: "slides",
|
|
@@ -13,12 +13,14 @@ export interface DeckVersionChatContext {
|
|
|
13
13
|
threadId?: string;
|
|
14
14
|
runId?: string;
|
|
15
15
|
turnId?: string;
|
|
16
|
+
phase?: "start" | "end";
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function contextFromFields(value: {
|
|
19
20
|
threadId?: unknown;
|
|
20
21
|
runId?: unknown;
|
|
21
22
|
turnId?: unknown;
|
|
23
|
+
phase?: unknown;
|
|
22
24
|
}): DeckVersionChatContext | undefined {
|
|
23
25
|
const context: DeckVersionChatContext = {};
|
|
24
26
|
for (const key of ["threadId", "runId", "turnId"] as const) {
|
|
@@ -26,6 +28,9 @@ function contextFromFields(value: {
|
|
|
26
28
|
context[key] = value[key];
|
|
27
29
|
}
|
|
28
30
|
}
|
|
31
|
+
if (value.phase === "start" || value.phase === "end") {
|
|
32
|
+
context.phase = value.phase;
|
|
33
|
+
}
|
|
29
34
|
return Object.keys(context).length > 0 ? context : undefined;
|
|
30
35
|
}
|
|
31
36
|
|
|
@@ -139,6 +144,7 @@ export async function createDeckVersionSnapshot(
|
|
|
139
144
|
.limit(1);
|
|
140
145
|
|
|
141
146
|
if (
|
|
147
|
+
options.chatContext?.phase !== "start" &&
|
|
142
148
|
latestVersion &&
|
|
143
149
|
latestVersion.title === source.title &&
|
|
144
150
|
deckContentSignature(latestVersion.data) ===
|
|
@@ -150,8 +156,12 @@ export async function createDeckVersionSnapshot(
|
|
|
150
156
|
const requestedChatContext = serializeDeckVersionChatContext(
|
|
151
157
|
options.chatContext,
|
|
152
158
|
);
|
|
153
|
-
const
|
|
159
|
+
const turnGroup =
|
|
154
160
|
options.chatContext?.turnId ?? options.chatContext?.runId ?? undefined;
|
|
161
|
+
const changeGroup =
|
|
162
|
+
options.chatContext?.phase === "start" && turnGroup
|
|
163
|
+
? `start:thread:${options.chatContext.threadId ?? turnGroup}`
|
|
164
|
+
: turnGroup;
|
|
155
165
|
if (
|
|
156
166
|
requestedChatContext &&
|
|
157
167
|
requestedChatContext === normalizedChatContext(latestVersion?.chatContext)
|
|
@@ -196,3 +206,28 @@ export async function createDeckVersionSnapshot(
|
|
|
196
206
|
|
|
197
207
|
return { created: true, id };
|
|
198
208
|
}
|
|
209
|
+
|
|
210
|
+
export async function createDeckChatBeginningSnapshot(
|
|
211
|
+
source: DeckSnapshotSource,
|
|
212
|
+
run: { threadId: string; runId: string },
|
|
213
|
+
): Promise<{ created: boolean; id?: string; reason?: string }> {
|
|
214
|
+
const rows = await getDb()
|
|
215
|
+
.select({ id: schema.deckVersions.id })
|
|
216
|
+
.from(schema.deckVersions)
|
|
217
|
+
.where(
|
|
218
|
+
and(
|
|
219
|
+
eq(schema.deckVersions.deckId, source.id),
|
|
220
|
+
eq(schema.deckVersions.ownerEmail, source.ownerEmail),
|
|
221
|
+
eq(schema.deckVersions.changeGroup, `start:thread:${run.threadId}`),
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
.limit(1);
|
|
225
|
+
if (rows.length) {
|
|
226
|
+
return { created: false, reason: "beginning-exists" };
|
|
227
|
+
}
|
|
228
|
+
return createDeckVersionSnapshot(source, {
|
|
229
|
+
force: true,
|
|
230
|
+
label: "Before chat",
|
|
231
|
+
chatContext: { ...run, phase: "start" },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -7,7 +7,10 @@ import { assertAccess } from "@agent-native/core/sharing";
|
|
|
7
7
|
import actionsRegistry from "../../.generated/actions-registry.js";
|
|
8
8
|
import { resolveSlidesRequestAuthContext } from "../handlers/request-auth-context.js";
|
|
9
9
|
import { prepareSlidesChatAttachments } from "../lib/chat-attachments.js";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createDeckChatBeginningSnapshot,
|
|
12
|
+
deckVersionChatContextFromRun,
|
|
13
|
+
} from "../lib/deck-versions.js";
|
|
11
14
|
import "../register-secrets.js";
|
|
12
15
|
|
|
13
16
|
const SLIDES_BACKGROUND_RUN_SOFT_TIMEOUT_MS = 13 * 60_000;
|
|
@@ -138,6 +141,7 @@ async function autosaveDeckAfterAgentTurn(
|
|
|
138
141
|
},
|
|
139
142
|
): Promise<void> {
|
|
140
143
|
if (scope.type !== "deck" || !hasDeckEdit(run, scope.id)) return;
|
|
144
|
+
if (!run.threadId || !run.runId) return;
|
|
141
145
|
|
|
142
146
|
const access = await assertAccess("deck", scope.id, "editor");
|
|
143
147
|
const deck = access.resource as {
|
|
@@ -147,15 +151,35 @@ async function autosaveDeckAfterAgentTurn(
|
|
|
147
151
|
ownerEmail: string;
|
|
148
152
|
};
|
|
149
153
|
const { createDeckVersionSnapshot } = await import("../lib/deck-versions.js");
|
|
154
|
+
const chatContext = deckVersionChatContextFromRun(run);
|
|
150
155
|
await createDeckVersionSnapshot(deck, {
|
|
151
156
|
force: true,
|
|
152
157
|
label: "Chat autosave",
|
|
153
|
-
chatContext:
|
|
158
|
+
chatContext: chatContext ? { ...chatContext, phase: "end" } : undefined,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function autosaveDeckBeforeAgentTurn(
|
|
163
|
+
scope: { type: string; id: string },
|
|
164
|
+
run: { threadId?: string; runId?: string },
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
if (scope.type !== "deck" || !run.threadId || !run.runId) return;
|
|
167
|
+
const access = await assertAccess("deck", scope.id, "editor");
|
|
168
|
+
const deck = access.resource as {
|
|
169
|
+
id: string;
|
|
170
|
+
title: string;
|
|
171
|
+
data: string;
|
|
172
|
+
ownerEmail: string;
|
|
173
|
+
};
|
|
174
|
+
await createDeckChatBeginningSnapshot(deck, {
|
|
175
|
+
threadId: run.threadId,
|
|
176
|
+
runId: run.runId,
|
|
154
177
|
});
|
|
155
178
|
}
|
|
156
179
|
|
|
157
180
|
export default createAgentChatPlugin({
|
|
158
181
|
appId: "slides",
|
|
182
|
+
onAgentTurnStart: autosaveDeckBeforeAgentTurn,
|
|
159
183
|
onAgentTurnComplete: autosaveDeckAfterAgentTurn,
|
|
160
184
|
actions: loadActionsFromStaticRegistry(actionsRegistry),
|
|
161
185
|
initialToolNames: INITIAL_TOOL_NAMES,
|
|
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
186
186
|
duplicate?: undefined;
|
|
187
187
|
eventId?: undefined;
|
|
188
188
|
} | {
|
|
189
|
-
error?: undefined;
|
|
190
189
|
accepted: boolean;
|
|
191
190
|
duplicate: boolean;
|
|
192
191
|
eventId: string;
|
|
192
|
+
error?: undefined;
|
|
193
193
|
}>;
|