@agent-native/core 0.14.5 → 0.14.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/thread-data-builder.d.ts +11 -1
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +120 -48
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/chat-threads/store.d.ts +6 -1
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +109 -40
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +8 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +72 -5
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +2 -2
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +6 -0
- package/dist/server/onboarding-html.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getDbExec, intType } from "../db/client.js";
|
|
2
|
+
import { mergeThreadDataForClientSave, normalizeThreadRepository, } from "../agent/thread-data-builder.js";
|
|
2
3
|
import { emitChatThreadChange } from "./emitter.js";
|
|
3
4
|
let _initPromise;
|
|
4
5
|
/**
|
|
@@ -11,10 +12,9 @@ let _initPromise;
|
|
|
11
12
|
* while leaving straight reads and other thread-data-unrelated updates
|
|
12
13
|
* untouched.
|
|
13
14
|
*
|
|
14
|
-
* Cross-process races
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* run owned by that user, which run in one place at a time.
|
|
15
|
+
* Cross-process races are handled by `updateThreadData`, which performs a
|
|
16
|
+
* compare-and-swap on `updated_at`, rereads the latest row on conflict, and
|
|
17
|
+
* remerges message history before retrying.
|
|
18
18
|
*/
|
|
19
19
|
const _threadDataLocks = new Map();
|
|
20
20
|
export function withThreadDataLock(threadId, fn) {
|
|
@@ -56,6 +56,48 @@ async function ensureTable() {
|
|
|
56
56
|
function generateId() {
|
|
57
57
|
return `thread-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
58
58
|
}
|
|
59
|
+
function deriveMessageCount(threadData, fallback) {
|
|
60
|
+
if (typeof threadData !== "string" || !threadData.trim())
|
|
61
|
+
return fallback;
|
|
62
|
+
try {
|
|
63
|
+
const repo = normalizeThreadRepository(JSON.parse(threadData));
|
|
64
|
+
if (Array.isArray(repo.messages))
|
|
65
|
+
return repo.messages.length;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Keep the stored count if the JSON blob is malformed.
|
|
69
|
+
}
|
|
70
|
+
return fallback;
|
|
71
|
+
}
|
|
72
|
+
function rowToThread(r) {
|
|
73
|
+
const threadData = r.thread_data ?? "{}";
|
|
74
|
+
const storedCount = Number(r.message_count);
|
|
75
|
+
return {
|
|
76
|
+
id: r.id,
|
|
77
|
+
ownerEmail: r.owner_email,
|
|
78
|
+
title: r.title,
|
|
79
|
+
preview: r.preview,
|
|
80
|
+
threadData,
|
|
81
|
+
messageCount: deriveMessageCount(threadData, storedCount),
|
|
82
|
+
createdAt: Number(r.created_at),
|
|
83
|
+
updatedAt: Number(r.updated_at),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function rowToSummary(r) {
|
|
87
|
+
const threadData = r.thread_data;
|
|
88
|
+
const storedCount = Number(r.message_count);
|
|
89
|
+
const messageCount = deriveMessageCount(threadData, storedCount);
|
|
90
|
+
if (messageCount <= 0)
|
|
91
|
+
return null;
|
|
92
|
+
return {
|
|
93
|
+
id: r.id,
|
|
94
|
+
title: r.title,
|
|
95
|
+
preview: r.preview,
|
|
96
|
+
messageCount,
|
|
97
|
+
createdAt: Number(r.created_at),
|
|
98
|
+
updatedAt: Number(r.updated_at),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
59
101
|
export async function createThread(ownerEmail, opts) {
|
|
60
102
|
await ensureTable();
|
|
61
103
|
const client = getDbExec();
|
|
@@ -86,17 +128,7 @@ export async function getThread(id) {
|
|
|
86
128
|
});
|
|
87
129
|
if (rows.length === 0)
|
|
88
130
|
return null;
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
id: r.id,
|
|
92
|
-
ownerEmail: r.owner_email,
|
|
93
|
-
title: r.title,
|
|
94
|
-
preview: r.preview,
|
|
95
|
-
threadData: r.thread_data,
|
|
96
|
-
messageCount: Number(r.message_count),
|
|
97
|
-
createdAt: Number(r.created_at),
|
|
98
|
-
updatedAt: Number(r.updated_at),
|
|
99
|
-
};
|
|
131
|
+
return rowToThread(rows[0]);
|
|
100
132
|
}
|
|
101
133
|
export async function forkThread(sourceId, ownerEmail, opts) {
|
|
102
134
|
const source = await getThread(sourceId);
|
|
@@ -134,17 +166,12 @@ export async function listThreads(ownerEmail, limit = 50, offset = 0) {
|
|
|
134
166
|
await ensureTable();
|
|
135
167
|
const client = getDbExec();
|
|
136
168
|
const { rows } = await client.execute({
|
|
137
|
-
sql: `SELECT id, title, preview, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?`,
|
|
169
|
+
sql: `SELECT id, title, preview, thread_data, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? AND (message_count > 0 OR thread_data LIKE '%"messages"%') ORDER BY updated_at DESC LIMIT ? OFFSET ?`,
|
|
138
170
|
args: [ownerEmail, limit, offset],
|
|
139
171
|
});
|
|
140
|
-
return rows
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
preview: r.preview,
|
|
144
|
-
messageCount: Number(r.message_count),
|
|
145
|
-
createdAt: Number(r.created_at),
|
|
146
|
-
updatedAt: Number(r.updated_at),
|
|
147
|
-
}));
|
|
172
|
+
return rows
|
|
173
|
+
.map((r) => rowToSummary(r))
|
|
174
|
+
.filter((r) => r !== null);
|
|
148
175
|
}
|
|
149
176
|
function escapeLike(s) {
|
|
150
177
|
return s.replace(/([\\%_])/g, "\\$1");
|
|
@@ -154,26 +181,68 @@ export async function searchThreads(ownerEmail, query, limit = 50) {
|
|
|
154
181
|
const client = getDbExec();
|
|
155
182
|
const pattern = `%${escapeLike(query)}%`;
|
|
156
183
|
const { rows } = await client.execute({
|
|
157
|
-
sql: `SELECT id, title, preview, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? AND (title LIKE ? OR preview LIKE ? OR thread_data LIKE ?) ORDER BY updated_at DESC LIMIT ?`,
|
|
184
|
+
sql: `SELECT id, title, preview, thread_data, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? AND (message_count > 0 OR thread_data LIKE '%"messages"%') AND (title LIKE ? OR preview LIKE ? OR thread_data LIKE ?) ORDER BY updated_at DESC LIMIT ?`,
|
|
158
185
|
args: [ownerEmail, pattern, pattern, pattern, limit],
|
|
159
186
|
});
|
|
160
|
-
return rows
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
preview: r.preview,
|
|
164
|
-
messageCount: Number(r.message_count),
|
|
165
|
-
createdAt: Number(r.created_at),
|
|
166
|
-
updatedAt: Number(r.updated_at),
|
|
167
|
-
}));
|
|
187
|
+
return rows
|
|
188
|
+
.map((r) => rowToSummary(r))
|
|
189
|
+
.filter((r) => r !== null);
|
|
168
190
|
}
|
|
169
|
-
|
|
191
|
+
function parseThreadData(value) {
|
|
192
|
+
try {
|
|
193
|
+
return JSON.parse(value || "{}");
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export async function updateThreadData(id, threadData, title, preview, messageCount, options = {}) {
|
|
170
200
|
await ensureTable();
|
|
171
201
|
const client = getDbExec();
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
202
|
+
const maxAttempts = options.maxAttempts ?? 5;
|
|
203
|
+
let lastConflict = false;
|
|
204
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
205
|
+
const current = await getThread(id);
|
|
206
|
+
if (!current)
|
|
207
|
+
return;
|
|
208
|
+
let nextThreadData = threadData;
|
|
209
|
+
let nextMessageCount = messageCount;
|
|
210
|
+
try {
|
|
211
|
+
const merged = mergeThreadDataForClientSave(parseThreadData(current.threadData), parseThreadData(threadData), {
|
|
212
|
+
preserveExistingQueuedMessages: options.preserveExistingQueuedMessages ?? true,
|
|
213
|
+
preserveExistingTopLevelKeys: options.preserveExistingTopLevelKeys ?? true,
|
|
214
|
+
});
|
|
215
|
+
nextThreadData = JSON.stringify(merged);
|
|
216
|
+
if (Array.isArray(merged.messages)) {
|
|
217
|
+
nextMessageCount = merged.messages.length;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Keep the caller's serialized value if either JSON blob is malformed.
|
|
222
|
+
}
|
|
223
|
+
const nextUpdatedAt = Math.max(Date.now(), current.updatedAt + 1);
|
|
224
|
+
const result = await client.execute({
|
|
225
|
+
sql: `UPDATE chat_threads SET thread_data = ?, title = ?, preview = ?, message_count = ?, updated_at = ? WHERE id = ? AND updated_at = ?`,
|
|
226
|
+
args: [
|
|
227
|
+
nextThreadData,
|
|
228
|
+
title,
|
|
229
|
+
preview,
|
|
230
|
+
nextMessageCount,
|
|
231
|
+
nextUpdatedAt,
|
|
232
|
+
id,
|
|
233
|
+
current.updatedAt,
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
if (result.rowsAffected > 0) {
|
|
237
|
+
emitChatThreadChange(id);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
lastConflict = true;
|
|
241
|
+
await new Promise((resolve) => setTimeout(resolve, 10 * (attempt + 1)));
|
|
242
|
+
}
|
|
243
|
+
if (lastConflict) {
|
|
244
|
+
throw new Error(`Failed to update chat thread ${id} after concurrent write conflicts.`);
|
|
245
|
+
}
|
|
177
246
|
}
|
|
178
247
|
/**
|
|
179
248
|
* Read the engine pinned to a thread (stored in thread_data JSON).
|
|
@@ -230,7 +299,7 @@ export async function setThreadQueuedMessages(threadId, queuedMessages) {
|
|
|
230
299
|
else {
|
|
231
300
|
data.queuedMessages = queuedMessages;
|
|
232
301
|
}
|
|
233
|
-
await updateThreadData(threadId, JSON.stringify(data), thread.title, thread.preview, thread.messageCount);
|
|
302
|
+
await updateThreadData(threadId, JSON.stringify(data), thread.title, thread.preview, thread.messageCount, { preserveExistingQueuedMessages: false });
|
|
234
303
|
});
|
|
235
304
|
}
|
|
236
305
|
export async function deleteThread(id) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/chat-threads/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec, isPostgres, intType } from \"../db/client.js\";\nimport { emitChatThreadChange } from \"./emitter.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Per-thread async mutex. Read-modify-write on the `thread_data` JSON blob\n * is not atomic at the DB level — two concurrent callers (e.g. the UI\n * persisting queued messages while `onRunComplete` appends agent output)\n * would both read the same row, each mutate it independently, and the\n * second write clobbers the first. Serializing on thread id inside this\n * process eliminates the race for the usual single-process deployment\n * while leaving straight reads and other thread-data-unrelated updates\n * untouched.\n *\n * Cross-process races (multiple Node replicas writing the same thread at\n * the same instant) are not fixed here — acceptable for `thread_data`\n * today because writes come from either the user's own tab or an agent\n * run owned by that user, which run in one place at a time.\n */\nconst _threadDataLocks = new Map<string, Promise<unknown>>();\n\nexport function withThreadDataLock<T>(\n threadId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const prev = _threadDataLocks.get(threadId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n _threadDataLocks.set(threadId, next);\n // Use `.then(cleanup, cleanup)` (not `.finally`) so the rejection is\n // observed on this chained promise — otherwise any failure inside `fn`\n // triggers `unhandledRejection` on the discarded `finally()` return.\n // The caller still sees the rejection via `next`.\n const cleanup = () => {\n if (_threadDataLocks.get(threadId) === next) {\n _threadDataLocks.delete(threadId);\n }\n };\n next.then(cleanup, cleanup);\n return next as Promise<T>;\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS chat_threads (\n id TEXT PRIMARY KEY,\n owner_email TEXT NOT NULL,\n title TEXT NOT NULL DEFAULT '',\n preview TEXT NOT NULL DEFAULT '',\n thread_data TEXT NOT NULL DEFAULT '{}',\n message_count ${intType()} NOT NULL DEFAULT 0,\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL\n )\n `);\n })();\n }\n return _initPromise;\n}\n\nfunction generateId(): string {\n return `thread-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport interface ChatThread {\n id: string;\n ownerEmail: string;\n title: string;\n preview: string;\n threadData: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface ChatThreadSummary {\n id: string;\n title: string;\n preview: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport async function createThread(\n ownerEmail: string,\n opts?: { id?: string; title?: string },\n): Promise<ChatThread> {\n await ensureTable();\n const client = getDbExec();\n const id = opts?.id ?? generateId();\n const now = Date.now();\n const title = opts?.title ?? \"\";\n\n await client.execute({\n sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at) VALUES (?, ?, ?, '', '{}', 0, ?, ?)`,\n args: [id, ownerEmail, title, now, now],\n });\n\n return {\n id,\n ownerEmail,\n title,\n preview: \"\",\n threadData: \"{}\",\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n}\n\nexport async function getThread(id: string): Promise<ChatThread | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, owner_email, title, preview, thread_data, message_count, created_at, updated_at FROM chat_threads WHERE id = ?`,\n args: [id],\n });\n if (rows.length === 0) return null;\n const r = rows[0];\n return {\n id: r.id as string,\n ownerEmail: r.owner_email as string,\n title: r.title as string,\n preview: r.preview as string,\n threadData: r.thread_data as string,\n messageCount: Number(r.message_count),\n createdAt: Number(r.created_at),\n updatedAt: Number(r.updated_at),\n };\n}\n\nexport async function forkThread(\n sourceId: string,\n ownerEmail: string,\n opts?: { id?: string },\n): Promise<ChatThread | null> {\n const source = await getThread(sourceId);\n if (!source || source.ownerEmail !== ownerEmail) return null;\n const id = opts?.id ?? generateId();\n const now = Date.now();\n const title = source.title ? `${source.title} (fork)` : \"\";\n const client = getDbExec();\n await client.execute({\n sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n ownerEmail,\n title,\n source.preview,\n source.threadData,\n source.messageCount,\n now,\n now,\n ],\n });\n return {\n id,\n ownerEmail,\n title,\n preview: source.preview,\n threadData: source.threadData,\n messageCount: source.messageCount,\n createdAt: now,\n updatedAt: now,\n };\n}\n\nexport async function listThreads(\n ownerEmail: string,\n limit = 50,\n offset = 0,\n): Promise<ChatThreadSummary[]> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, title, preview, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? ORDER BY updated_at DESC LIMIT ? OFFSET ?`,\n args: [ownerEmail, limit, offset],\n });\n return rows.map((r) => ({\n id: r.id as string,\n title: r.title as string,\n preview: r.preview as string,\n messageCount: Number(r.message_count),\n createdAt: Number(r.created_at),\n updatedAt: Number(r.updated_at),\n }));\n}\n\nfunction escapeLike(s: string): string {\n return s.replace(/([\\\\%_])/g, \"\\\\$1\");\n}\n\nexport async function searchThreads(\n ownerEmail: string,\n query: string,\n limit = 50,\n): Promise<ChatThreadSummary[]> {\n await ensureTable();\n const client = getDbExec();\n const pattern = `%${escapeLike(query)}%`;\n const { rows } = await client.execute({\n sql: `SELECT id, title, preview, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? AND (title LIKE ? OR preview LIKE ? OR thread_data LIKE ?) ORDER BY updated_at DESC LIMIT ?`,\n args: [ownerEmail, pattern, pattern, pattern, limit],\n });\n return rows.map((r) => ({\n id: r.id as string,\n title: r.title as string,\n preview: r.preview as string,\n messageCount: Number(r.message_count),\n createdAt: Number(r.created_at),\n updatedAt: Number(r.updated_at),\n }));\n}\n\nexport async function updateThreadData(\n id: string,\n threadData: string,\n title: string,\n preview: string,\n messageCount: number,\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n await client.execute({\n sql: `UPDATE chat_threads SET thread_data = ?, title = ?, preview = ?, message_count = ?, updated_at = ? WHERE id = ?`,\n args: [threadData, title, preview, messageCount, Date.now(), id],\n });\n emitChatThreadChange(id);\n}\n\nexport interface ThreadEngineMeta {\n engineName: string;\n model: string;\n}\n\n/**\n * Read the engine pinned to a thread (stored in thread_data JSON).\n * Returns null if no engine is pinned.\n */\nexport async function getThreadEngineMeta(\n threadId: string,\n): Promise<ThreadEngineMeta | null> {\n const thread = await getThread(threadId);\n if (!thread?.threadData) return null;\n try {\n const data = JSON.parse(thread.threadData);\n if (data.engineMeta?.engineName) return data.engineMeta as ThreadEngineMeta;\n } catch {}\n return null;\n}\n\n/**\n * Pin an engine to a thread by storing engineMeta in thread_data JSON.\n * Does not change messages, title, or preview.\n */\nexport async function setThreadEngineMeta(\n threadId: string,\n meta: ThreadEngineMeta,\n): Promise<void> {\n return withThreadDataLock(threadId, async () => {\n const thread = await getThread(threadId);\n if (!thread) return;\n let data: Record<string, unknown> = {};\n try {\n data = JSON.parse(thread.threadData);\n } catch {}\n data.engineMeta = meta;\n await updateThreadData(\n threadId,\n JSON.stringify(data),\n thread.title,\n thread.preview,\n thread.messageCount,\n );\n });\n}\n\nexport interface QueuedMessage {\n id: string;\n text: string;\n images?: string[];\n references?: unknown[];\n}\n\n/**\n * Persist the user's queued (not-yet-sent) messages onto the thread.\n * Stored in thread_data JSON so it survives reloads without a schema\n * change. Safe to call often — the frontend debounces writes.\n */\nexport async function setThreadQueuedMessages(\n threadId: string,\n queuedMessages: QueuedMessage[],\n): Promise<void> {\n return withThreadDataLock(threadId, async () => {\n const thread = await getThread(threadId);\n if (!thread) return;\n let data: Record<string, unknown> = {};\n try {\n data = JSON.parse(thread.threadData);\n } catch {}\n if (queuedMessages.length === 0) {\n delete data.queuedMessages;\n } else {\n data.queuedMessages = queuedMessages;\n }\n await updateThreadData(\n threadId,\n JSON.stringify(data),\n thread.title,\n thread.preview,\n thread.messageCount,\n );\n });\n}\n\nexport async function deleteThread(id: string): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `DELETE FROM chat_threads WHERE id = ?`,\n args: [id],\n });\n if (result.rowsAffected > 0) {\n emitChatThreadChange(id);\n return true;\n }\n return false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/chat-threads/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { getDbExec, intType } from \"../db/client.js\";\nimport {\n mergeThreadDataForClientSave,\n normalizeThreadRepository,\n} from \"../agent/thread-data-builder.js\";\nimport { emitChatThreadChange } from \"./emitter.js\";\n\nlet _initPromise: Promise<void> | undefined;\n\n/**\n * Per-thread async mutex. Read-modify-write on the `thread_data` JSON blob\n * is not atomic at the DB level — two concurrent callers (e.g. the UI\n * persisting queued messages while `onRunComplete` appends agent output)\n * would both read the same row, each mutate it independently, and the\n * second write clobbers the first. Serializing on thread id inside this\n * process eliminates the race for the usual single-process deployment\n * while leaving straight reads and other thread-data-unrelated updates\n * untouched.\n *\n * Cross-process races are handled by `updateThreadData`, which performs a\n * compare-and-swap on `updated_at`, rereads the latest row on conflict, and\n * remerges message history before retrying.\n */\nconst _threadDataLocks = new Map<string, Promise<unknown>>();\n\nexport function withThreadDataLock<T>(\n threadId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const prev = _threadDataLocks.get(threadId) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n _threadDataLocks.set(threadId, next);\n // Use `.then(cleanup, cleanup)` (not `.finally`) so the rejection is\n // observed on this chained promise — otherwise any failure inside `fn`\n // triggers `unhandledRejection` on the discarded `finally()` return.\n // The caller still sees the rejection via `next`.\n const cleanup = () => {\n if (_threadDataLocks.get(threadId) === next) {\n _threadDataLocks.delete(threadId);\n }\n };\n next.then(cleanup, cleanup);\n return next as Promise<T>;\n}\n\nasync function ensureTable(): Promise<void> {\n if (!_initPromise) {\n _initPromise = (async () => {\n const client = getDbExec();\n await client.execute(`\n CREATE TABLE IF NOT EXISTS chat_threads (\n id TEXT PRIMARY KEY,\n owner_email TEXT NOT NULL,\n title TEXT NOT NULL DEFAULT '',\n preview TEXT NOT NULL DEFAULT '',\n thread_data TEXT NOT NULL DEFAULT '{}',\n message_count ${intType()} NOT NULL DEFAULT 0,\n created_at ${intType()} NOT NULL,\n updated_at ${intType()} NOT NULL\n )\n `);\n })();\n }\n return _initPromise;\n}\n\nfunction generateId(): string {\n return `thread-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport interface ChatThread {\n id: string;\n ownerEmail: string;\n title: string;\n preview: string;\n threadData: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface ChatThreadSummary {\n id: string;\n title: string;\n preview: string;\n messageCount: number;\n createdAt: number;\n updatedAt: number;\n}\n\nfunction deriveMessageCount(threadData: unknown, fallback: number): number {\n if (typeof threadData !== \"string\" || !threadData.trim()) return fallback;\n try {\n const repo = normalizeThreadRepository(JSON.parse(threadData));\n if (Array.isArray(repo.messages)) return repo.messages.length;\n } catch {\n // Keep the stored count if the JSON blob is malformed.\n }\n return fallback;\n}\n\nfunction rowToThread(r: Record<string, unknown>): ChatThread {\n const threadData = (r.thread_data as string) ?? \"{}\";\n const storedCount = Number(r.message_count);\n return {\n id: r.id as string,\n ownerEmail: r.owner_email as string,\n title: r.title as string,\n preview: r.preview as string,\n threadData,\n messageCount: deriveMessageCount(threadData, storedCount),\n createdAt: Number(r.created_at),\n updatedAt: Number(r.updated_at),\n };\n}\n\nfunction rowToSummary(r: Record<string, unknown>): ChatThreadSummary | null {\n const threadData = r.thread_data as string | undefined;\n const storedCount = Number(r.message_count);\n const messageCount = deriveMessageCount(threadData, storedCount);\n if (messageCount <= 0) return null;\n return {\n id: r.id as string,\n title: r.title as string,\n preview: r.preview as string,\n messageCount,\n createdAt: Number(r.created_at),\n updatedAt: Number(r.updated_at),\n };\n}\n\nexport async function createThread(\n ownerEmail: string,\n opts?: { id?: string; title?: string },\n): Promise<ChatThread> {\n await ensureTable();\n const client = getDbExec();\n const id = opts?.id ?? generateId();\n const now = Date.now();\n const title = opts?.title ?? \"\";\n\n await client.execute({\n sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at) VALUES (?, ?, ?, '', '{}', 0, ?, ?)`,\n args: [id, ownerEmail, title, now, now],\n });\n\n return {\n id,\n ownerEmail,\n title,\n preview: \"\",\n threadData: \"{}\",\n messageCount: 0,\n createdAt: now,\n updatedAt: now,\n };\n}\n\nexport async function getThread(id: string): Promise<ChatThread | null> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, owner_email, title, preview, thread_data, message_count, created_at, updated_at FROM chat_threads WHERE id = ?`,\n args: [id],\n });\n if (rows.length === 0) return null;\n return rowToThread(rows[0]);\n}\n\nexport async function forkThread(\n sourceId: string,\n ownerEmail: string,\n opts?: { id?: string },\n): Promise<ChatThread | null> {\n const source = await getThread(sourceId);\n if (!source || source.ownerEmail !== ownerEmail) return null;\n const id = opts?.id ?? generateId();\n const now = Date.now();\n const title = source.title ? `${source.title} (fork)` : \"\";\n const client = getDbExec();\n await client.execute({\n sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n args: [\n id,\n ownerEmail,\n title,\n source.preview,\n source.threadData,\n source.messageCount,\n now,\n now,\n ],\n });\n return {\n id,\n ownerEmail,\n title,\n preview: source.preview,\n threadData: source.threadData,\n messageCount: source.messageCount,\n createdAt: now,\n updatedAt: now,\n };\n}\n\nexport async function listThreads(\n ownerEmail: string,\n limit = 50,\n offset = 0,\n): Promise<ChatThreadSummary[]> {\n await ensureTable();\n const client = getDbExec();\n const { rows } = await client.execute({\n sql: `SELECT id, title, preview, thread_data, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? AND (message_count > 0 OR thread_data LIKE '%\"messages\"%') ORDER BY updated_at DESC LIMIT ? OFFSET ?`,\n args: [ownerEmail, limit, offset],\n });\n return rows\n .map((r) => rowToSummary(r))\n .filter((r): r is ChatThreadSummary => r !== null);\n}\n\nfunction escapeLike(s: string): string {\n return s.replace(/([\\\\%_])/g, \"\\\\$1\");\n}\n\nexport async function searchThreads(\n ownerEmail: string,\n query: string,\n limit = 50,\n): Promise<ChatThreadSummary[]> {\n await ensureTable();\n const client = getDbExec();\n const pattern = `%${escapeLike(query)}%`;\n const { rows } = await client.execute({\n sql: `SELECT id, title, preview, thread_data, message_count, created_at, updated_at FROM chat_threads WHERE owner_email = ? AND (message_count > 0 OR thread_data LIKE '%\"messages\"%') AND (title LIKE ? OR preview LIKE ? OR thread_data LIKE ?) ORDER BY updated_at DESC LIMIT ?`,\n args: [ownerEmail, pattern, pattern, pattern, limit],\n });\n return rows\n .map((r) => rowToSummary(r))\n .filter((r): r is ChatThreadSummary => r !== null);\n}\n\nexport interface UpdateThreadDataOptions {\n preserveExistingQueuedMessages?: boolean;\n preserveExistingTopLevelKeys?: boolean;\n maxAttempts?: number;\n}\n\nfunction parseThreadData(value: string): any {\n try {\n return JSON.parse(value || \"{}\");\n } catch {\n return {};\n }\n}\n\nexport async function updateThreadData(\n id: string,\n threadData: string,\n title: string,\n preview: string,\n messageCount: number,\n options: UpdateThreadDataOptions = {},\n): Promise<void> {\n await ensureTable();\n const client = getDbExec();\n const maxAttempts = options.maxAttempts ?? 5;\n let lastConflict = false;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const current = await getThread(id);\n if (!current) return;\n\n let nextThreadData = threadData;\n let nextMessageCount = messageCount;\n try {\n const merged = mergeThreadDataForClientSave(\n parseThreadData(current.threadData),\n parseThreadData(threadData),\n {\n preserveExistingQueuedMessages:\n options.preserveExistingQueuedMessages ?? true,\n preserveExistingTopLevelKeys:\n options.preserveExistingTopLevelKeys ?? true,\n },\n );\n nextThreadData = JSON.stringify(merged);\n if (Array.isArray(merged.messages)) {\n nextMessageCount = merged.messages.length;\n }\n } catch {\n // Keep the caller's serialized value if either JSON blob is malformed.\n }\n\n const nextUpdatedAt = Math.max(Date.now(), current.updatedAt + 1);\n const result = await client.execute({\n sql: `UPDATE chat_threads SET thread_data = ?, title = ?, preview = ?, message_count = ?, updated_at = ? WHERE id = ? AND updated_at = ?`,\n args: [\n nextThreadData,\n title,\n preview,\n nextMessageCount,\n nextUpdatedAt,\n id,\n current.updatedAt,\n ],\n });\n\n if (result.rowsAffected > 0) {\n emitChatThreadChange(id);\n return;\n }\n\n lastConflict = true;\n await new Promise((resolve) => setTimeout(resolve, 10 * (attempt + 1)));\n }\n\n if (lastConflict) {\n throw new Error(\n `Failed to update chat thread ${id} after concurrent write conflicts.`,\n );\n }\n}\n\nexport interface ThreadEngineMeta {\n engineName: string;\n model: string;\n}\n\n/**\n * Read the engine pinned to a thread (stored in thread_data JSON).\n * Returns null if no engine is pinned.\n */\nexport async function getThreadEngineMeta(\n threadId: string,\n): Promise<ThreadEngineMeta | null> {\n const thread = await getThread(threadId);\n if (!thread?.threadData) return null;\n try {\n const data = JSON.parse(thread.threadData);\n if (data.engineMeta?.engineName) return data.engineMeta as ThreadEngineMeta;\n } catch {}\n return null;\n}\n\n/**\n * Pin an engine to a thread by storing engineMeta in thread_data JSON.\n * Does not change messages, title, or preview.\n */\nexport async function setThreadEngineMeta(\n threadId: string,\n meta: ThreadEngineMeta,\n): Promise<void> {\n return withThreadDataLock(threadId, async () => {\n const thread = await getThread(threadId);\n if (!thread) return;\n let data: Record<string, unknown> = {};\n try {\n data = JSON.parse(thread.threadData);\n } catch {}\n data.engineMeta = meta;\n await updateThreadData(\n threadId,\n JSON.stringify(data),\n thread.title,\n thread.preview,\n thread.messageCount,\n );\n });\n}\n\nexport interface QueuedMessage {\n id: string;\n text: string;\n images?: string[];\n references?: unknown[];\n}\n\n/**\n * Persist the user's queued (not-yet-sent) messages onto the thread.\n * Stored in thread_data JSON so it survives reloads without a schema\n * change. Safe to call often — the frontend debounces writes.\n */\nexport async function setThreadQueuedMessages(\n threadId: string,\n queuedMessages: QueuedMessage[],\n): Promise<void> {\n return withThreadDataLock(threadId, async () => {\n const thread = await getThread(threadId);\n if (!thread) return;\n let data: Record<string, unknown> = {};\n try {\n data = JSON.parse(thread.threadData);\n } catch {}\n if (queuedMessages.length === 0) {\n delete data.queuedMessages;\n } else {\n data.queuedMessages = queuedMessages;\n }\n await updateThreadData(\n threadId,\n JSON.stringify(data),\n thread.title,\n thread.preview,\n thread.messageCount,\n { preserveExistingQueuedMessages: false },\n );\n });\n}\n\nexport async function deleteThread(id: string): Promise<boolean> {\n await ensureTable();\n const client = getDbExec();\n const result = await client.execute({\n sql: `DELETE FROM chat_threads WHERE id = ?`,\n args: [id],\n });\n if (result.rowsAffected > 0) {\n emitChatThreadChange(id);\n return true;\n }\n return false;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentPanel.d.ts","sourceRoot":"","sources":["../../src/client/AgentPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KASN,MAAM,OAAO,CAAC;AAwCf,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AA6J7D,MAAM,WAAW,oBAAoB;IACnC,mGAAmG;IACnG,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,mEAAmE;IACnE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,wEAAwE;IACxE,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACzC;AAuBD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,kBAAkB,EAClB,eAAe,CAChB;IACC,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6GAA6G;IAC7G,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6HAA6H;IAC7H,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,yEAAyE;IACzE,UAAU,CAAC,EAAE,oBAAoB,CAAC;CACnC;
|
|
1
|
+
{"version":3,"file":"AgentPanel.d.ts","sourceRoot":"","sources":["../../src/client/AgentPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KASN,MAAM,OAAO,CAAC;AAwCf,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AA6J7D,MAAM,WAAW,oBAAoB;IACnC,mGAAmG;IACnG,OAAO,EAAE,OAAO,CAAC;IACjB,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,4EAA4E;IAC5E,4BAA4B,CAAC,EAAE,MAAM,CAAC;IACtC,mEAAmE;IACnE,2BAA2B,CAAC,EAAE,MAAM,CAAC;IACrC,wEAAwE;IACxE,8BAA8B,CAAC,EAAE,MAAM,CAAC;CACzC;AAuBD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,kBAAkB,EAClB,eAAe,CAChB;IACC,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC7B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6GAA6G;IAC7G,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,iFAAiF;IACjF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6HAA6H;IAC7H,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC7B,yEAAyE;IACzE,UAAU,CAAC,EAAE,oBAAoB,CAAC;CACnC;AAs1CD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,2CAgBhD;AAID,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,gDAAgD;IAChD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gDAAgD;IAChD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB;yDACqD;IACrD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5B,sDAAsD;IACtD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oEAAoE;IACpE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,cAAsC,EACtC,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,QAAkB,EAClB,WAAmB,EACnB,aAAqB,GACtB,EAAE,iBAAiB,2CAiWnB;AAED;;;GAGG;AACH,wBAAgB,cAAc,SAgB7B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,2CAuBtE"}
|
|
@@ -309,7 +309,14 @@ function AgentPanelInner({ defaultMode = "chat", className, apiUrl, emptyStateTe
|
|
|
309
309
|
const codeUnavailableSecondaryCtaLabel = codeAccess?.unavailableSecondaryCtaLabel ?? "Use Builder";
|
|
310
310
|
const codeUnavailableSecondaryCtaHref = codeAccess?.unavailableSecondaryCtaHref;
|
|
311
311
|
const canUseCodeTools = isDevMode && codeAccessEnabled;
|
|
312
|
-
|
|
312
|
+
// Hide the CLI tab when embedded in the Builder.io frame — code editing
|
|
313
|
+
// there happens via Builder, and the CLI panel only offers a Download
|
|
314
|
+
// Desktop CTA, which adds clutter without value.
|
|
315
|
+
const showCliMode = (isDevMode || !codeAccessEnabled) && !isInFrame();
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (mode === "cli" && !showCliMode)
|
|
318
|
+
switchMode("chat");
|
|
319
|
+
}, [mode, showCliMode, switchMode]);
|
|
313
320
|
// Notify frame when dev mode changes — use both a local CustomEvent (for
|
|
314
321
|
// when AgentPanel is rendered directly in the frame) AND postMessage (for
|
|
315
322
|
// when AgentPanel is inside the iframe and needs to cross the boundary).
|