@personaai/react 0.9.0 → 0.11.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/README.md +210 -187
- package/dist/docs/.nojekyll +1 -0
- package/dist/index.cjs +1324 -253
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +456 -13
- package/dist/index.d.ts +456 -13
- package/dist/index.js +1321 -254
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -286,7 +286,7 @@ async function openSSEStream(opts) {
|
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
// src/hooks/
|
|
289
|
+
// src/hooks/streamEventHelpers.ts
|
|
290
290
|
function isErrorToolContent(content) {
|
|
291
291
|
if (typeof content !== "string" || !content.trim().startsWith("{"))
|
|
292
292
|
return false;
|
|
@@ -332,6 +332,38 @@ function parsePresentedFile(content) {
|
|
|
332
332
|
return null;
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
|
+
function normalizeWorkspaceFiles(raw) {
|
|
336
|
+
const normalized = {};
|
|
337
|
+
for (const [path, file] of Object.entries(raw || {})) {
|
|
338
|
+
normalized[path] = {
|
|
339
|
+
content: file.content,
|
|
340
|
+
size: file.size,
|
|
341
|
+
createdAt: file.created_at,
|
|
342
|
+
modifiedAt: file.modified_at
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return normalized;
|
|
346
|
+
}
|
|
347
|
+
function normalizePendingInterrupt(pending) {
|
|
348
|
+
if (!pending || typeof pending !== "object") return null;
|
|
349
|
+
const p = pending;
|
|
350
|
+
if (p.kind === "hitl") {
|
|
351
|
+
return {
|
|
352
|
+
kind: "hitl",
|
|
353
|
+
actionRequests: p.value?.actionRequests ?? [],
|
|
354
|
+
reviewConfigs: p.value?.reviewConfigs ?? []
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (p.kind === "clarification") {
|
|
358
|
+
return {
|
|
359
|
+
kind: "clarification",
|
|
360
|
+
questions: p.value?.questions ?? []
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/hooks/useChat.ts
|
|
335
367
|
function parseSandboxCommand(tc) {
|
|
336
368
|
let command = tc.args ?? "";
|
|
337
369
|
try {
|
|
@@ -363,36 +395,6 @@ function parseSandboxCommand(tc) {
|
|
|
363
395
|
seq: tc.seq
|
|
364
396
|
};
|
|
365
397
|
}
|
|
366
|
-
function normalizeWorkspaceFiles(raw) {
|
|
367
|
-
const normalized = {};
|
|
368
|
-
for (const [path, file] of Object.entries(raw || {})) {
|
|
369
|
-
normalized[path] = {
|
|
370
|
-
content: file.content,
|
|
371
|
-
size: file.size,
|
|
372
|
-
createdAt: file.created_at,
|
|
373
|
-
modifiedAt: file.modified_at
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
return normalized;
|
|
377
|
-
}
|
|
378
|
-
function normalizePendingInterrupt(pending) {
|
|
379
|
-
if (!pending || typeof pending !== "object") return null;
|
|
380
|
-
const p = pending;
|
|
381
|
-
if (p.kind === "hitl") {
|
|
382
|
-
return {
|
|
383
|
-
kind: "hitl",
|
|
384
|
-
actionRequests: p.value?.actionRequests ?? [],
|
|
385
|
-
reviewConfigs: p.value?.reviewConfigs ?? []
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
if (p.kind === "clarification") {
|
|
389
|
-
return {
|
|
390
|
-
kind: "clarification",
|
|
391
|
-
questions: p.value?.questions ?? []
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
return null;
|
|
395
|
-
}
|
|
396
398
|
function useChat(options = {}) {
|
|
397
399
|
const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
|
|
398
400
|
const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
|
|
@@ -1106,104 +1108,656 @@ function useChat(options = {}) {
|
|
|
1106
1108
|
};
|
|
1107
1109
|
}
|
|
1108
1110
|
|
|
1109
|
-
// src/hooks/
|
|
1110
|
-
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
const
|
|
1111
|
+
// src/hooks/useArchitectChat.ts
|
|
1112
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
1113
|
+
var ARCHITECT_AGENT_ID = "000000000000000000000002";
|
|
1114
|
+
function useArchitectChat(options = {}) {
|
|
1115
|
+
const { fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
|
|
1116
|
+
const chatLogger = useMemo3(() => logger.child("architect"), [logger]);
|
|
1117
|
+
const threadId = options.threadId;
|
|
1118
|
+
const [internalThreadId, setInternalThreadId] = useState2(void 0);
|
|
1119
|
+
const effectiveThreadId = threadId ?? internalThreadId;
|
|
1120
|
+
useEffect2(() => {
|
|
1121
|
+
if (threadId !== void 0) setInternalThreadId(threadId);
|
|
1122
|
+
}, [threadId]);
|
|
1123
|
+
const didLogInitRef = useRef2(false);
|
|
1124
|
+
if (!didLogInitRef.current) {
|
|
1125
|
+
didLogInitRef.current = true;
|
|
1126
|
+
chatLogger.debug("useArchitectChat init", {
|
|
1127
|
+
threadId,
|
|
1128
|
+
hasInitialMessages: !!options.initialMessages?.length
|
|
1129
|
+
});
|
|
1130
|
+
chatLogger.trace("useArchitectChat options", {
|
|
1131
|
+
threadId,
|
|
1132
|
+
initialMessageCount: options.initialMessages?.length ?? 0
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
const [messages, setMessages] = useState2(
|
|
1136
|
+
options.initialMessages || []
|
|
1137
|
+
);
|
|
1138
|
+
const [input, setInput] = useState2("");
|
|
1139
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
1140
|
+
const [isLoadingHistory, setIsLoadingHistory] = useState2(false);
|
|
1118
1141
|
const [error, setError] = useState2(null);
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1142
|
+
const [interrupt, setInterrupt] = useState2(null);
|
|
1143
|
+
const [files, setFiles] = useState2({});
|
|
1144
|
+
const [todos, setTodos] = useState2([]);
|
|
1145
|
+
const [presentedFile, setPresentedFile] = useState2(null);
|
|
1146
|
+
const abortControllerRef = useRef2(null);
|
|
1147
|
+
const loadedThreadIdRef = useRef2(void 0);
|
|
1148
|
+
const stop = useCallback2(() => {
|
|
1149
|
+
if (abortControllerRef.current) {
|
|
1150
|
+
chatLogger.info("stop streaming", {});
|
|
1151
|
+
chatLogger.debug("abort controller", {
|
|
1152
|
+
hasController: !!abortControllerRef.current
|
|
1153
|
+
});
|
|
1154
|
+
abortControllerRef.current.abort();
|
|
1155
|
+
abortControllerRef.current = null;
|
|
1156
|
+
setIsStreaming(false);
|
|
1157
|
+
chatLogger.debug("streaming stopped");
|
|
1158
|
+
} else {
|
|
1159
|
+
chatLogger.trace("stop called \u2014 no active stream");
|
|
1160
|
+
}
|
|
1161
|
+
}, [chatLogger]);
|
|
1162
|
+
const clear = useCallback2(() => {
|
|
1163
|
+
chatLogger.info("clear chat", { messageCount: messages.length });
|
|
1164
|
+
chatLogger.debug("clear", { messageCount: messages.length });
|
|
1165
|
+
stop();
|
|
1166
|
+
setMessages([]);
|
|
1121
1167
|
setError(null);
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1168
|
+
setInterrupt(null);
|
|
1169
|
+
setFiles({});
|
|
1170
|
+
setTodos([]);
|
|
1171
|
+
setPresentedFile(null);
|
|
1172
|
+
loadedThreadIdRef.current = void 0;
|
|
1173
|
+
}, [stop, chatLogger, messages.length]);
|
|
1174
|
+
const startNewChat = useCallback2(() => {
|
|
1175
|
+
chatLogger.info("startNewChat \u2014 entering ephemeral mode");
|
|
1176
|
+
stop();
|
|
1177
|
+
setMessages([]);
|
|
1178
|
+
setError(null);
|
|
1179
|
+
setInterrupt(null);
|
|
1180
|
+
setFiles({});
|
|
1181
|
+
setTodos([]);
|
|
1182
|
+
setPresentedFile(null);
|
|
1183
|
+
setInput("");
|
|
1184
|
+
loadedThreadIdRef.current = void 0;
|
|
1185
|
+
setInternalThreadId(void 0);
|
|
1186
|
+
}, [stop, chatLogger]);
|
|
1187
|
+
const loadThreadMessages = useCallback2(
|
|
1188
|
+
async (id) => {
|
|
1189
|
+
chatLogger.debug("loadThreadMessages start", { threadId: id });
|
|
1190
|
+
chatLogger.trace("loadThreadMessages", { threadId: id });
|
|
1191
|
+
setIsLoadingHistory(true);
|
|
1192
|
+
setError(null);
|
|
1193
|
+
try {
|
|
1194
|
+
const res = await fetchWithAuth(`/threads/${id}/messages`);
|
|
1195
|
+
if (!res.ok)
|
|
1196
|
+
throw new Error(`Failed to load thread history: ${res.statusText}`);
|
|
1197
|
+
const body = await res.json();
|
|
1198
|
+
const data = body?.data ?? body;
|
|
1199
|
+
const raw = data?.messages ?? [];
|
|
1200
|
+
const subagentTraces = data?.subagentTraces ?? {};
|
|
1201
|
+
const loaded = raw.map((m, i) => ({
|
|
1202
|
+
id: m.id || `history-${id}-${i}`,
|
|
1203
|
+
role: m.role,
|
|
1204
|
+
content: m.content,
|
|
1205
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1206
|
+
toolCalls: m.toolCalls?.map((tc) => {
|
|
1207
|
+
const trace = subagentTraces[tc.toolCallId];
|
|
1208
|
+
return Array.isArray(trace) && trace.length > 0 ? {
|
|
1209
|
+
...tc,
|
|
1210
|
+
subagentActivity: persistedTraceToActivityEntries(trace)
|
|
1211
|
+
} : tc;
|
|
1212
|
+
})
|
|
1213
|
+
}));
|
|
1214
|
+
setMessages(loaded);
|
|
1215
|
+
setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
|
|
1216
|
+
setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
|
|
1217
|
+
setTodos(data?.state?.todos ?? []);
|
|
1218
|
+
loadedThreadIdRef.current = id;
|
|
1219
|
+
chatLogger.info("loadThreadMessages succeeded", {
|
|
1220
|
+
threadId: id,
|
|
1221
|
+
messageCount: loaded.length
|
|
1222
|
+
});
|
|
1223
|
+
chatLogger.debug("loadThreadMessages completed", {
|
|
1224
|
+
threadId: id,
|
|
1225
|
+
messageCount: loaded.length
|
|
1226
|
+
});
|
|
1227
|
+
return loaded;
|
|
1228
|
+
} catch (err) {
|
|
1229
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1230
|
+
chatLogger.warn("loadThreadMessages failed", {
|
|
1231
|
+
threadId: id,
|
|
1232
|
+
error: errorObj.message
|
|
1233
|
+
});
|
|
1234
|
+
chatLogger.error("loadThreadMessages error", {
|
|
1235
|
+
threadId: id,
|
|
1236
|
+
error: errorObj.message
|
|
1237
|
+
});
|
|
1238
|
+
setError(errorObj);
|
|
1239
|
+
if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
|
|
1240
|
+
return [];
|
|
1241
|
+
} finally {
|
|
1242
|
+
setIsLoadingHistory(false);
|
|
1243
|
+
chatLogger.trace("loadThreadMessages end", { threadId: id });
|
|
1126
1244
|
}
|
|
1127
|
-
const data = await res.json();
|
|
1128
|
-
setMemory(data);
|
|
1129
|
-
return data;
|
|
1130
|
-
} catch (err) {
|
|
1131
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1132
|
-
setError(errorObj);
|
|
1133
|
-
return { userFiles: [], agentMemories: [] };
|
|
1134
|
-
} finally {
|
|
1135
|
-
setIsLoading(false);
|
|
1136
|
-
}
|
|
1137
|
-
}, [fetchWithAuth]);
|
|
1138
|
-
const getFile = useCallback2(
|
|
1139
|
-
async (params) => {
|
|
1140
|
-
const query = new URLSearchParams({ path: params.path });
|
|
1141
|
-
if (params.scope) query.set("scope", params.scope);
|
|
1142
|
-
if (params.agentId) query.set("agentId", params.agentId);
|
|
1143
|
-
const res = await fetchWithAuth(`/memory/file?${query.toString()}`);
|
|
1144
|
-
if (!res.ok)
|
|
1145
|
-
throw new Error(`Failed to get memory file: ${res.statusText}`);
|
|
1146
|
-
return await res.json();
|
|
1147
|
-
},
|
|
1148
|
-
[fetchWithAuth]
|
|
1149
|
-
);
|
|
1150
|
-
const writeFile = useCallback2(
|
|
1151
|
-
async (params) => {
|
|
1152
|
-
const res = await fetchWithAuth("/memory/file", {
|
|
1153
|
-
method: "PUT",
|
|
1154
|
-
headers: { "Content-Type": "application/json" },
|
|
1155
|
-
body: JSON.stringify(params)
|
|
1156
|
-
});
|
|
1157
|
-
if (!res.ok)
|
|
1158
|
-
throw new Error(`Failed to write memory file: ${res.statusText}`);
|
|
1159
|
-
const updated = await res.json();
|
|
1160
|
-
void fetchMemory();
|
|
1161
|
-
return updated;
|
|
1162
|
-
},
|
|
1163
|
-
[fetchWithAuth, fetchMemory]
|
|
1164
|
-
);
|
|
1165
|
-
const deleteFile = useCallback2(
|
|
1166
|
-
async (params) => {
|
|
1167
|
-
const query = new URLSearchParams({ path: params.path });
|
|
1168
|
-
if (params.scope) query.set("scope", params.scope);
|
|
1169
|
-
if (params.agentId) query.set("agentId", params.agentId);
|
|
1170
|
-
const res = await fetchWithAuth(`/memory/file?${query.toString()}`, {
|
|
1171
|
-
method: "DELETE"
|
|
1172
|
-
});
|
|
1173
|
-
if (!res.ok)
|
|
1174
|
-
throw new Error(`Failed to delete memory file: ${res.statusText}`);
|
|
1175
|
-
void fetchMemory();
|
|
1176
1245
|
},
|
|
1177
|
-
[fetchWithAuth,
|
|
1246
|
+
[fetchWithAuth, chatLogger]
|
|
1178
1247
|
);
|
|
1179
1248
|
useEffect2(() => {
|
|
1180
|
-
if (
|
|
1181
|
-
|
|
1249
|
+
if (!effectiveThreadId || isStreaming) {
|
|
1250
|
+
chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
|
|
1251
|
+
return;
|
|
1182
1252
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1253
|
+
if (loadedThreadIdRef.current === effectiveThreadId) {
|
|
1254
|
+
chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
if (messages.length > 0) {
|
|
1258
|
+
chatLogger.trace("auto-load has messages", {
|
|
1259
|
+
threadId: effectiveThreadId,
|
|
1260
|
+
count: messages.length
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
chatLogger.info("auto-load thread history", { threadId: effectiveThreadId });
|
|
1265
|
+
chatLogger.debug("loadThreadMessages trigger", { threadId: effectiveThreadId });
|
|
1266
|
+
void loadThreadMessages(effectiveThreadId);
|
|
1267
|
+
}, [effectiveThreadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
1268
|
+
const doSend = useCallback2(
|
|
1269
|
+
async (prompt, baseMessages, overrideOptions) => {
|
|
1270
|
+
chatLogger.debug("sendMessage start", {
|
|
1271
|
+
promptPreview: prompt.slice(0, 100),
|
|
1272
|
+
threadId: effectiveThreadId ?? overrideOptions?.threadId,
|
|
1273
|
+
hasResume: !!overrideOptions?.resume,
|
|
1274
|
+
messageCount: baseMessages.length
|
|
1275
|
+
});
|
|
1276
|
+
chatLogger.trace("sendMessage details", {
|
|
1277
|
+
promptPreview: prompt.slice(0, 200),
|
|
1278
|
+
hasResume: !!overrideOptions?.resume
|
|
1279
|
+
});
|
|
1280
|
+
const userMessage = {
|
|
1281
|
+
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
1282
|
+
role: "user",
|
|
1283
|
+
content: prompt,
|
|
1284
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1285
|
+
};
|
|
1286
|
+
const assistantMessageId = `assistant-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1287
|
+
const placeholderAssistant = {
|
|
1288
|
+
id: assistantMessageId,
|
|
1289
|
+
role: "assistant",
|
|
1290
|
+
content: "",
|
|
1291
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1292
|
+
isStreaming: true,
|
|
1293
|
+
toolCalls: []
|
|
1294
|
+
};
|
|
1295
|
+
const nextMessages = [...baseMessages, userMessage];
|
|
1296
|
+
setMessages([...nextMessages, placeholderAssistant]);
|
|
1297
|
+
setInput("");
|
|
1298
|
+
setIsStreaming(true);
|
|
1299
|
+
setError(null);
|
|
1300
|
+
setInterrupt(null);
|
|
1301
|
+
const controller = new AbortController();
|
|
1302
|
+
abortControllerRef.current = controller;
|
|
1303
|
+
const finalizeReasoning = () => {
|
|
1304
|
+
setMessages(
|
|
1305
|
+
(prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
|
|
1306
|
+
);
|
|
1307
|
+
};
|
|
1308
|
+
try {
|
|
1309
|
+
const payloadMessages = nextMessages.filter((m) => m.role !== "reasoning").map((m) => ({
|
|
1310
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
1311
|
+
content: m.content
|
|
1312
|
+
}));
|
|
1313
|
+
let resolvedThreadId = await overrideOptions?.threadId ?? effectiveThreadId;
|
|
1314
|
+
if (!resolvedThreadId) {
|
|
1315
|
+
chatLogger.info("minting real thread for ephemeral architect chat", {});
|
|
1316
|
+
const createRes = await fetchWithAuth("/threads", {
|
|
1317
|
+
method: "POST",
|
|
1318
|
+
headers: { "Content-Type": "application/json" },
|
|
1319
|
+
body: JSON.stringify({ agentId: ARCHITECT_AGENT_ID })
|
|
1320
|
+
});
|
|
1321
|
+
if (!createRes.ok) throw new Error(`Failed to create thread: ${createRes.statusText}`);
|
|
1322
|
+
const createBody = await createRes.json();
|
|
1323
|
+
const created = createBody?.data ?? createBody;
|
|
1324
|
+
resolvedThreadId = created._id ?? created.id;
|
|
1325
|
+
if (!resolvedThreadId) throw new Error("Thread creation returned no id");
|
|
1326
|
+
setInternalThreadId(resolvedThreadId);
|
|
1327
|
+
loadedThreadIdRef.current = resolvedThreadId;
|
|
1328
|
+
options.onThreadCreated?.(resolvedThreadId);
|
|
1329
|
+
chatLogger.info("ephemeral architect chat minted", { threadId: resolvedThreadId });
|
|
1330
|
+
}
|
|
1331
|
+
chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
|
|
1332
|
+
const token = getAuthToken ? await getAuthToken() : null;
|
|
1333
|
+
chatLogger.debug("opening SSE stream", {
|
|
1334
|
+
hasToken: !!token,
|
|
1335
|
+
hasThreadId: !!resolvedThreadId
|
|
1336
|
+
});
|
|
1337
|
+
const stream = await openSSEStream({
|
|
1338
|
+
url: `${baseUrl}/architect`,
|
|
1339
|
+
headers: {
|
|
1340
|
+
"Content-Type": "application/json",
|
|
1341
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
1342
|
+
},
|
|
1343
|
+
body: JSON.stringify({
|
|
1344
|
+
messages: payloadMessages,
|
|
1345
|
+
threadId: resolvedThreadId,
|
|
1346
|
+
resume: overrideOptions?.resume
|
|
1347
|
+
}),
|
|
1348
|
+
signal: controller.signal
|
|
1349
|
+
});
|
|
1350
|
+
if (!stream.ok) {
|
|
1351
|
+
chatLogger.warn("SSE stream not ok", { status: stream.status, errorText: stream.errorText });
|
|
1352
|
+
chatLogger.error("architect stream failed", { status: stream.status, error: stream.errorText });
|
|
1353
|
+
throw new Error(`Architect error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
1354
|
+
}
|
|
1355
|
+
chatLogger.debug("SSE stream opened", { status: stream.status });
|
|
1356
|
+
chatLogger.info("architect stream started", { threadId: resolvedThreadId });
|
|
1357
|
+
const reader = stream.reader;
|
|
1358
|
+
let buffer = "";
|
|
1359
|
+
let accumulatedText = "";
|
|
1360
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
1361
|
+
let streamSeq = 0;
|
|
1362
|
+
let activeReasoningId = null;
|
|
1363
|
+
const reasoningById = /* @__PURE__ */ new Map();
|
|
1364
|
+
const patchAssistant = (patch) => {
|
|
1365
|
+
setMessages(
|
|
1366
|
+
(prev) => prev.map(
|
|
1367
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
|
|
1368
|
+
)
|
|
1369
|
+
);
|
|
1370
|
+
};
|
|
1371
|
+
const insertReasoningMessage = (id, seq) => {
|
|
1372
|
+
const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
|
|
1373
|
+
setMessages((prev) => {
|
|
1374
|
+
const idx = prev.findIndex((m) => m.id === assistantMessageId);
|
|
1375
|
+
if (idx === -1) return [...prev, msg];
|
|
1376
|
+
const next = [...prev];
|
|
1377
|
+
next.splice(idx, 0, msg);
|
|
1378
|
+
return next;
|
|
1379
|
+
});
|
|
1380
|
+
};
|
|
1381
|
+
while (true) {
|
|
1382
|
+
const { done, value } = await reader.read();
|
|
1383
|
+
if (done) break;
|
|
1384
|
+
buffer += value ?? "";
|
|
1385
|
+
const lines = buffer.split("\n");
|
|
1386
|
+
buffer = lines.pop() ?? "";
|
|
1387
|
+
for (const line of lines) {
|
|
1388
|
+
const trimmed = line.trim();
|
|
1389
|
+
if (!trimmed || !trimmed.startsWith("data:")) continue;
|
|
1390
|
+
const raw = trimmed.replace(/^data:\s*/, "");
|
|
1391
|
+
if (raw === "[DONE]") break;
|
|
1392
|
+
try {
|
|
1393
|
+
const event = JSON.parse(raw);
|
|
1394
|
+
chatLogger.trace("stream event", { type: event.type, event });
|
|
1395
|
+
options.onEvent?.(event);
|
|
1396
|
+
if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
|
|
1397
|
+
chatLogger.debug("text chunk", { deltaLength: event.delta.length });
|
|
1398
|
+
accumulatedText += event.delta;
|
|
1399
|
+
patchAssistant({ content: accumulatedText, isStreaming: true });
|
|
1400
|
+
} else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
|
|
1401
|
+
chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
|
|
1402
|
+
const existing = toolCallsMap.get(event.toolCallId);
|
|
1403
|
+
if (existing) {
|
|
1404
|
+
existing.args = (existing.args || "") + (event.delta || "");
|
|
1405
|
+
} else {
|
|
1406
|
+
toolCallsMap.set(event.toolCallId, {
|
|
1407
|
+
toolCallId: event.toolCallId,
|
|
1408
|
+
toolName: event.toolCallName || "",
|
|
1409
|
+
args: event.delta || "",
|
|
1410
|
+
seq: streamSeq++
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
patchAssistant({});
|
|
1414
|
+
} else if (event.type === "TOOL_CALL_RESULT") {
|
|
1415
|
+
chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
|
|
1416
|
+
const existing = toolCallsMap.get(event.toolCallId);
|
|
1417
|
+
if (existing) {
|
|
1418
|
+
existing.result = event.content;
|
|
1419
|
+
existing.isError = isErrorToolContent(event.content);
|
|
1420
|
+
if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
|
|
1421
|
+
if (existing.toolName === "present_file" && !existing.isError) {
|
|
1422
|
+
const presented = parsePresentedFile(event.content);
|
|
1423
|
+
if (presented) {
|
|
1424
|
+
chatLogger.info("present_file", { path: presented.path });
|
|
1425
|
+
setPresentedFile(presented);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
patchAssistant({});
|
|
1429
|
+
}
|
|
1430
|
+
} else if (event.type === "STATE_SNAPSHOT") {
|
|
1431
|
+
chatLogger.debug("state snapshot", {
|
|
1432
|
+
fileCount: Object.keys(event.snapshot.files ?? {}).length,
|
|
1433
|
+
todoCount: event.snapshot.todos?.length ?? 0
|
|
1434
|
+
});
|
|
1435
|
+
setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
|
|
1436
|
+
setTodos(event.snapshot.todos ?? []);
|
|
1437
|
+
} else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
|
|
1438
|
+
chatLogger.debug("reasoning start", { messageId: event.messageId });
|
|
1439
|
+
activeReasoningId = event.messageId;
|
|
1440
|
+
reasoningById.set(event.messageId, { content: "" });
|
|
1441
|
+
insertReasoningMessage(event.messageId, streamSeq++);
|
|
1442
|
+
} else if (event.type === "REASONING_MESSAGE_CONTENT") {
|
|
1443
|
+
let rid = event.messageId || activeReasoningId || "";
|
|
1444
|
+
if (!rid || !reasoningById.has(rid)) {
|
|
1445
|
+
rid = rid || `reasoning-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
1446
|
+
activeReasoningId = rid;
|
|
1447
|
+
reasoningById.set(rid, { content: "" });
|
|
1448
|
+
insertReasoningMessage(rid, streamSeq++);
|
|
1449
|
+
chatLogger.debug("reasoning lazy start", { messageId: rid });
|
|
1450
|
+
}
|
|
1451
|
+
const entry = reasoningById.get(rid);
|
|
1452
|
+
entry.content += event.delta;
|
|
1453
|
+
chatLogger.trace("reasoning content", { messageId: rid, deltaLength: event.delta?.length ?? 0 });
|
|
1454
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m));
|
|
1455
|
+
} else if (event.type === "REASONING_END") {
|
|
1456
|
+
chatLogger.debug("reasoning end", { messageId: activeReasoningId });
|
|
1457
|
+
const rid = activeReasoningId;
|
|
1458
|
+
activeReasoningId = null;
|
|
1459
|
+
if (rid) {
|
|
1460
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
|
|
1461
|
+
}
|
|
1462
|
+
} else if (event.type === "CUSTOM") {
|
|
1463
|
+
chatLogger.debug("custom event", { name: event.name });
|
|
1464
|
+
if (event.name === "hitl_request") {
|
|
1465
|
+
const value2 = event.value;
|
|
1466
|
+
chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
|
|
1467
|
+
setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
|
|
1468
|
+
} else if (event.name === "clarification_request") {
|
|
1469
|
+
const value2 = event.value;
|
|
1470
|
+
chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
|
|
1471
|
+
setInterrupt({ kind: "clarification", questions: value2.questions });
|
|
1472
|
+
} else if (event.name === "subagent_activity") {
|
|
1473
|
+
const { toolCallId, ...entry } = event.value;
|
|
1474
|
+
chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
|
|
1475
|
+
const existing = toolCallsMap.get(toolCallId);
|
|
1476
|
+
if (existing) {
|
|
1477
|
+
existing.subagentActivity = [...existing.subagentActivity || [], entry];
|
|
1478
|
+
patchAssistant({});
|
|
1479
|
+
}
|
|
1480
|
+
} else if (event.name === "mcp_app") {
|
|
1481
|
+
const val = event.value;
|
|
1482
|
+
if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
|
|
1483
|
+
const existing = toolCallsMap.get(val.toolCallId);
|
|
1484
|
+
if (existing) {
|
|
1485
|
+
existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
|
|
1486
|
+
patchAssistant({});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
} else if (event.type === "title") {
|
|
1491
|
+
const title = event.title;
|
|
1492
|
+
if (typeof title === "string" && title.trim()) {
|
|
1493
|
+
chatLogger.info("auto title", { title });
|
|
1494
|
+
options.onTitle?.(title);
|
|
1495
|
+
}
|
|
1496
|
+
} else if (event.type === "RUN_ERROR") {
|
|
1497
|
+
chatLogger.warn("run error", { message: event.message, code: event.code });
|
|
1498
|
+
chatLogger.error("stream run error", { code: event.code, message: event.message });
|
|
1499
|
+
throw new Error(event.message || "Stream error from agent");
|
|
1500
|
+
} else {
|
|
1501
|
+
chatLogger.trace("unhandled event", { type: event.type });
|
|
1502
|
+
}
|
|
1503
|
+
} catch (e) {
|
|
1504
|
+
if (e instanceof Error && e.message.startsWith("Stream error")) throw e;
|
|
1505
|
+
chatLogger.warn("event parse error", { raw: raw.slice(0, 200), error: e instanceof Error ? e.message : String(e) });
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
const finalMessage = {
|
|
1510
|
+
id: assistantMessageId,
|
|
1511
|
+
role: "assistant",
|
|
1512
|
+
content: accumulatedText,
|
|
1513
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1514
|
+
isStreaming: false,
|
|
1515
|
+
toolCalls: Array.from(toolCallsMap.values())
|
|
1516
|
+
};
|
|
1517
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
|
|
1518
|
+
chatLogger.info("sendMessage succeeded", { textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
|
|
1519
|
+
chatLogger.debug("sendMessage completed", { textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
|
|
1520
|
+
options.onFinish?.(finalMessage);
|
|
1521
|
+
return true;
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
if (controller.signal.aborted) {
|
|
1524
|
+
chatLogger.info("sendMessage aborted", {});
|
|
1525
|
+
chatLogger.debug("stream aborted", {});
|
|
1526
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
1527
|
+
return false;
|
|
1528
|
+
}
|
|
1529
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1530
|
+
chatLogger.warn("sendMessage failed", { error: errorObj.message });
|
|
1531
|
+
chatLogger.error("sendMessage error", { error: errorObj.message });
|
|
1532
|
+
setError(errorObj);
|
|
1533
|
+
options.onError?.(errorObj);
|
|
1534
|
+
setMessages(
|
|
1535
|
+
(prev) => prev.map(
|
|
1536
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
|
|
1537
|
+
)
|
|
1538
|
+
);
|
|
1539
|
+
return false;
|
|
1540
|
+
} finally {
|
|
1541
|
+
setIsStreaming(false);
|
|
1542
|
+
abortControllerRef.current = null;
|
|
1543
|
+
finalizeReasoning();
|
|
1544
|
+
chatLogger.debug("sendMessage finally", { isStreaming: false });
|
|
1545
|
+
chatLogger.trace("sendMessage end", {});
|
|
1546
|
+
}
|
|
1547
|
+
},
|
|
1548
|
+
[baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
|
|
1549
|
+
);
|
|
1550
|
+
const sendMessage = useCallback2(
|
|
1551
|
+
async (contentToSend, overrideOptions) => {
|
|
1552
|
+
const prompt = (contentToSend ?? input).trim();
|
|
1553
|
+
if (!prompt || isStreaming) {
|
|
1554
|
+
chatLogger.trace("sendMessage skipped", {
|
|
1555
|
+
hasPrompt: !!prompt,
|
|
1556
|
+
isStreaming
|
|
1557
|
+
});
|
|
1558
|
+
if (isStreaming) {
|
|
1559
|
+
chatLogger.warn("sendMessage dropped while streaming \u2014 caller should queue or disable send", {});
|
|
1560
|
+
}
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
return doSend(prompt, messages, overrideOptions);
|
|
1564
|
+
},
|
|
1565
|
+
[input, isStreaming, messages, doSend, chatLogger]
|
|
1566
|
+
);
|
|
1567
|
+
const handleInputChange = useCallback2(
|
|
1568
|
+
(e) => {
|
|
1569
|
+
chatLogger.trace("handleInputChange", { length: e.target.value.length });
|
|
1570
|
+
setInput(e.target.value);
|
|
1571
|
+
},
|
|
1572
|
+
[chatLogger]
|
|
1573
|
+
);
|
|
1574
|
+
const handleSubmit = useCallback2(
|
|
1575
|
+
(e) => {
|
|
1576
|
+
if (e) e.preventDefault();
|
|
1577
|
+
chatLogger.debug("handleSubmit", {});
|
|
1578
|
+
void sendMessage();
|
|
1579
|
+
},
|
|
1580
|
+
[sendMessage, chatLogger]
|
|
1581
|
+
);
|
|
1582
|
+
const reload = useCallback2(async () => {
|
|
1583
|
+
if (messages.length === 0 || isStreaming) {
|
|
1584
|
+
chatLogger.trace("reload skipped", {
|
|
1585
|
+
messageCount: messages.length,
|
|
1586
|
+
isStreaming
|
|
1587
|
+
});
|
|
1588
|
+
return false;
|
|
1589
|
+
}
|
|
1590
|
+
let lastUserIndex = -1;
|
|
1591
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1592
|
+
if (messages[i].role === "user") {
|
|
1593
|
+
lastUserIndex = i;
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
if (lastUserIndex === -1) {
|
|
1598
|
+
chatLogger.warn("reload no user message", {});
|
|
1599
|
+
return false;
|
|
1600
|
+
}
|
|
1601
|
+
const lastUserMessage = messages[lastUserIndex];
|
|
1602
|
+
const truncated = messages.slice(0, lastUserIndex);
|
|
1603
|
+
chatLogger.info("reload", { messageId: lastUserMessage.id });
|
|
1604
|
+
chatLogger.debug("reload last user", {
|
|
1605
|
+
preview: lastUserMessage.content.slice(0, 100)
|
|
1606
|
+
});
|
|
1607
|
+
return doSend(lastUserMessage.content, truncated);
|
|
1608
|
+
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1609
|
+
const resumeInterrupt = useCallback2(
|
|
1610
|
+
(resume, displayContent) => {
|
|
1611
|
+
chatLogger.info("resumeInterrupt", {
|
|
1612
|
+
kind: resume.decisions ? "hitl" : "clarification",
|
|
1613
|
+
preview: displayContent.slice(0, 50)
|
|
1614
|
+
});
|
|
1615
|
+
return sendMessage(displayContent, { resume });
|
|
1616
|
+
},
|
|
1617
|
+
[sendMessage, chatLogger]
|
|
1618
|
+
);
|
|
1619
|
+
const dismissPresentedFile = useCallback2(() => {
|
|
1620
|
+
chatLogger.debug("dismissPresentedFile", {});
|
|
1621
|
+
setPresentedFile(null);
|
|
1622
|
+
}, [chatLogger]);
|
|
1623
|
+
const openWorkspaceFile = useCallback2(
|
|
1624
|
+
(path) => {
|
|
1625
|
+
chatLogger.debug("openWorkspaceFile", { path });
|
|
1626
|
+
setPresentedFile({
|
|
1627
|
+
path,
|
|
1628
|
+
title: path.split("/").pop() || path,
|
|
1629
|
+
description: ""
|
|
1630
|
+
});
|
|
1631
|
+
},
|
|
1632
|
+
[chatLogger]
|
|
1633
|
+
);
|
|
1634
|
+
return {
|
|
1635
|
+
messages,
|
|
1636
|
+
input,
|
|
1637
|
+
setInput,
|
|
1638
|
+
handleInputChange,
|
|
1639
|
+
handleSubmit,
|
|
1640
|
+
sendMessage,
|
|
1641
|
+
isStreaming,
|
|
1642
|
+
isLoading: isStreaming,
|
|
1643
|
+
isLoadingHistory,
|
|
1644
|
+
error,
|
|
1645
|
+
interrupt,
|
|
1646
|
+
resumeInterrupt,
|
|
1647
|
+
files,
|
|
1648
|
+
todos,
|
|
1649
|
+
presentedFile,
|
|
1650
|
+
dismissPresentedFile,
|
|
1651
|
+
openWorkspaceFile,
|
|
1652
|
+
stop,
|
|
1653
|
+
reload,
|
|
1654
|
+
clear,
|
|
1655
|
+
startNewChat,
|
|
1656
|
+
currentThreadId: effectiveThreadId,
|
|
1657
|
+
isEphemeral: !effectiveThreadId,
|
|
1658
|
+
setMessages,
|
|
1659
|
+
loadThreadMessages
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
// src/hooks/useMemory.ts
|
|
1664
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
|
|
1665
|
+
function useMemory(autoFetch = true) {
|
|
1666
|
+
const { fetchWithAuth } = usePersonaContext();
|
|
1667
|
+
const [memory, setMemory] = useState3({
|
|
1668
|
+
userFiles: [],
|
|
1669
|
+
agentMemories: []
|
|
1670
|
+
});
|
|
1671
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1672
|
+
const [error, setError] = useState3(null);
|
|
1673
|
+
const fetchMemory = useCallback3(async () => {
|
|
1674
|
+
setIsLoading(true);
|
|
1675
|
+
setError(null);
|
|
1676
|
+
try {
|
|
1677
|
+
const res = await fetchWithAuth("/memory");
|
|
1678
|
+
if (!res.ok) {
|
|
1679
|
+
throw new Error(`Failed to fetch memory: ${res.statusText}`);
|
|
1680
|
+
}
|
|
1681
|
+
const data = await res.json();
|
|
1682
|
+
setMemory(data);
|
|
1683
|
+
return data;
|
|
1684
|
+
} catch (err) {
|
|
1685
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
1686
|
+
setError(errorObj);
|
|
1687
|
+
return { userFiles: [], agentMemories: [] };
|
|
1688
|
+
} finally {
|
|
1689
|
+
setIsLoading(false);
|
|
1690
|
+
}
|
|
1691
|
+
}, [fetchWithAuth]);
|
|
1692
|
+
const getFile = useCallback3(
|
|
1693
|
+
async (params) => {
|
|
1694
|
+
const query = new URLSearchParams({ path: params.path });
|
|
1695
|
+
if (params.scope) query.set("scope", params.scope);
|
|
1696
|
+
if (params.agentId) query.set("agentId", params.agentId);
|
|
1697
|
+
const res = await fetchWithAuth(`/memory/file?${query.toString()}`);
|
|
1698
|
+
if (!res.ok)
|
|
1699
|
+
throw new Error(`Failed to get memory file: ${res.statusText}`);
|
|
1700
|
+
return await res.json();
|
|
1701
|
+
},
|
|
1702
|
+
[fetchWithAuth]
|
|
1703
|
+
);
|
|
1704
|
+
const writeFile = useCallback3(
|
|
1705
|
+
async (params) => {
|
|
1706
|
+
const res = await fetchWithAuth("/memory/file", {
|
|
1707
|
+
method: "PUT",
|
|
1708
|
+
headers: { "Content-Type": "application/json" },
|
|
1709
|
+
body: JSON.stringify(params)
|
|
1710
|
+
});
|
|
1711
|
+
if (!res.ok)
|
|
1712
|
+
throw new Error(`Failed to write memory file: ${res.statusText}`);
|
|
1713
|
+
const updated = await res.json();
|
|
1714
|
+
void fetchMemory();
|
|
1715
|
+
return updated;
|
|
1716
|
+
},
|
|
1717
|
+
[fetchWithAuth, fetchMemory]
|
|
1718
|
+
);
|
|
1719
|
+
const deleteFile = useCallback3(
|
|
1720
|
+
async (params) => {
|
|
1721
|
+
const query = new URLSearchParams({ path: params.path });
|
|
1722
|
+
if (params.scope) query.set("scope", params.scope);
|
|
1723
|
+
if (params.agentId) query.set("agentId", params.agentId);
|
|
1724
|
+
const res = await fetchWithAuth(`/memory/file?${query.toString()}`, {
|
|
1725
|
+
method: "DELETE"
|
|
1726
|
+
});
|
|
1727
|
+
if (!res.ok)
|
|
1728
|
+
throw new Error(`Failed to delete memory file: ${res.statusText}`);
|
|
1729
|
+
void fetchMemory();
|
|
1730
|
+
},
|
|
1731
|
+
[fetchWithAuth, fetchMemory]
|
|
1732
|
+
);
|
|
1733
|
+
useEffect3(() => {
|
|
1734
|
+
if (autoFetch) {
|
|
1735
|
+
void fetchMemory();
|
|
1736
|
+
}
|
|
1737
|
+
}, [autoFetch, fetchMemory]);
|
|
1738
|
+
return {
|
|
1739
|
+
memory,
|
|
1740
|
+
isLoading,
|
|
1741
|
+
error,
|
|
1742
|
+
refetch: fetchMemory,
|
|
1743
|
+
getFile,
|
|
1744
|
+
writeFile,
|
|
1745
|
+
deleteFile
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// src/hooks/useThreads.ts
|
|
1750
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
|
|
1751
|
+
function useThreads(autoFetch = true) {
|
|
1752
|
+
const { fetchWithAuth, defaultAgentId } = usePersonaContext();
|
|
1753
|
+
const [threads, setThreads] = useState4([]);
|
|
1754
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1755
|
+
const [error, setError] = useState4(null);
|
|
1756
|
+
const fetchThreads = useCallback4(async () => {
|
|
1757
|
+
setIsLoading(true);
|
|
1758
|
+
setError(null);
|
|
1759
|
+
try {
|
|
1760
|
+
const res = await fetchWithAuth("/threads");
|
|
1207
1761
|
if (!res.ok) throw new Error(`Failed to list threads: ${res.statusText}`);
|
|
1208
1762
|
const data = await res.json();
|
|
1209
1763
|
const items = Array.isArray(data) ? data : data?.items || data?.threads || (Array.isArray(data?.data) ? data.data : void 0) || [];
|
|
@@ -1217,7 +1771,7 @@ function useThreads(autoFetch = true) {
|
|
|
1217
1771
|
setIsLoading(false);
|
|
1218
1772
|
}
|
|
1219
1773
|
}, [fetchWithAuth]);
|
|
1220
|
-
const createThread =
|
|
1774
|
+
const createThread = useCallback4(
|
|
1221
1775
|
async (agentId) => {
|
|
1222
1776
|
const targetAgentId = agentId || defaultAgentId;
|
|
1223
1777
|
const res = await fetchWithAuth("/threads", {
|
|
@@ -1234,7 +1788,7 @@ function useThreads(autoFetch = true) {
|
|
|
1234
1788
|
},
|
|
1235
1789
|
[fetchWithAuth, defaultAgentId, fetchThreads]
|
|
1236
1790
|
);
|
|
1237
|
-
const deleteThread =
|
|
1791
|
+
const deleteThread = useCallback4(
|
|
1238
1792
|
async (threadId) => {
|
|
1239
1793
|
const res = await fetchWithAuth(`/threads/${threadId}`, {
|
|
1240
1794
|
method: "DELETE"
|
|
@@ -1245,7 +1799,7 @@ function useThreads(autoFetch = true) {
|
|
|
1245
1799
|
},
|
|
1246
1800
|
[fetchWithAuth]
|
|
1247
1801
|
);
|
|
1248
|
-
const bulkDeleteThreads =
|
|
1802
|
+
const bulkDeleteThreads = useCallback4(
|
|
1249
1803
|
async (threadIds) => {
|
|
1250
1804
|
const res = await fetchWithAuth("/threads/bulk-delete", {
|
|
1251
1805
|
method: "POST",
|
|
@@ -1262,13 +1816,13 @@ function useThreads(autoFetch = true) {
|
|
|
1262
1816
|
},
|
|
1263
1817
|
[fetchWithAuth]
|
|
1264
1818
|
);
|
|
1265
|
-
const deleteAllThreads =
|
|
1819
|
+
const deleteAllThreads = useCallback4(async () => {
|
|
1266
1820
|
const ids = threads.map((t) => t._id);
|
|
1267
1821
|
for (let i = 0; i < ids.length; i += 100) {
|
|
1268
1822
|
await bulkDeleteThreads(ids.slice(i, i + 100));
|
|
1269
1823
|
}
|
|
1270
1824
|
}, [threads, bulkDeleteThreads]);
|
|
1271
|
-
const updateThread =
|
|
1825
|
+
const updateThread = useCallback4(
|
|
1272
1826
|
async (threadId, input) => {
|
|
1273
1827
|
const res = await fetchWithAuth(`/threads/${threadId}`, {
|
|
1274
1828
|
method: "PATCH",
|
|
@@ -1286,11 +1840,11 @@ function useThreads(autoFetch = true) {
|
|
|
1286
1840
|
},
|
|
1287
1841
|
[fetchWithAuth]
|
|
1288
1842
|
);
|
|
1289
|
-
const renameThread =
|
|
1843
|
+
const renameThread = useCallback4(
|
|
1290
1844
|
(threadId, title) => updateThread(threadId, { title }),
|
|
1291
1845
|
[updateThread]
|
|
1292
1846
|
);
|
|
1293
|
-
const resetThread =
|
|
1847
|
+
const resetThread = useCallback4(
|
|
1294
1848
|
async (threadId) => {
|
|
1295
1849
|
const res = await fetchWithAuth(`/threads/${threadId}/reset`, {
|
|
1296
1850
|
method: "POST"
|
|
@@ -1305,7 +1859,7 @@ function useThreads(autoFetch = true) {
|
|
|
1305
1859
|
},
|
|
1306
1860
|
[fetchWithAuth]
|
|
1307
1861
|
);
|
|
1308
|
-
const getThread =
|
|
1862
|
+
const getThread = useCallback4(
|
|
1309
1863
|
async (threadId) => {
|
|
1310
1864
|
const res = await fetchWithAuth(`/threads/${threadId}`);
|
|
1311
1865
|
if (!res.ok) throw new Error(`Failed to fetch thread: ${res.statusText}`);
|
|
@@ -1314,7 +1868,7 @@ function useThreads(autoFetch = true) {
|
|
|
1314
1868
|
},
|
|
1315
1869
|
[fetchWithAuth]
|
|
1316
1870
|
);
|
|
1317
|
-
|
|
1871
|
+
useEffect4(() => {
|
|
1318
1872
|
if (autoFetch) {
|
|
1319
1873
|
void fetchThreads();
|
|
1320
1874
|
}
|
|
@@ -1336,7 +1890,7 @@ function useThreads(autoFetch = true) {
|
|
|
1336
1890
|
}
|
|
1337
1891
|
|
|
1338
1892
|
// src/hooks/useVoice.ts
|
|
1339
|
-
import { useCallback as
|
|
1893
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo4, useRef as useRef3, useState as useState5 } from "react";
|
|
1340
1894
|
|
|
1341
1895
|
// src/hooks/voiceWorklets.ts
|
|
1342
1896
|
var RECORDER_WORKLET_SOURCE = `
|
|
@@ -1439,42 +1993,42 @@ function mergeTranscriptText(prev, next) {
|
|
|
1439
1993
|
}
|
|
1440
1994
|
function useVoice(options = {}) {
|
|
1441
1995
|
const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
|
|
1442
|
-
const voiceLogger =
|
|
1996
|
+
const voiceLogger = useMemo4(() => logger.child("voice"), [logger]);
|
|
1443
1997
|
const agentId = options.agentId || defaultAgentId;
|
|
1444
1998
|
const threadId = options.threadId;
|
|
1445
1999
|
const contextOverride = options.contextOverride;
|
|
1446
2000
|
const context = options.context;
|
|
1447
|
-
const [state, setState] =
|
|
1448
|
-
const [isMuted, setIsMuted] =
|
|
1449
|
-
const [transcript, setTranscript] =
|
|
2001
|
+
const [state, setState] = useState5("idle");
|
|
2002
|
+
const [isMuted, setIsMuted] = useState5(false);
|
|
2003
|
+
const [transcript, setTranscript] = useState5(
|
|
1450
2004
|
[]
|
|
1451
2005
|
);
|
|
1452
|
-
const [partial, setPartial] =
|
|
2006
|
+
const [partial, setPartial] = useState5(
|
|
1453
2007
|
null
|
|
1454
2008
|
);
|
|
1455
|
-
const [toolCalls, setToolCalls] =
|
|
1456
|
-
const [error, setError] =
|
|
1457
|
-
const [endReason, setEndReason] =
|
|
2009
|
+
const [toolCalls, setToolCalls] = useState5([]);
|
|
2010
|
+
const [error, setError] = useState5(null);
|
|
2011
|
+
const [endReason, setEndReason] = useState5(
|
|
1458
2012
|
null
|
|
1459
2013
|
);
|
|
1460
|
-
const wsRef =
|
|
1461
|
-
const streamRef =
|
|
1462
|
-
const inputCtxRef =
|
|
1463
|
-
const outputCtxRef =
|
|
1464
|
-
const recorderNodeRef =
|
|
1465
|
-
const playerNodeRef =
|
|
1466
|
-
const acceptedTurnSeqRef =
|
|
1467
|
-
const mountedRef =
|
|
1468
|
-
const lastFinalTurnRef =
|
|
2014
|
+
const wsRef = useRef3(null);
|
|
2015
|
+
const streamRef = useRef3(null);
|
|
2016
|
+
const inputCtxRef = useRef3(null);
|
|
2017
|
+
const outputCtxRef = useRef3(null);
|
|
2018
|
+
const recorderNodeRef = useRef3(null);
|
|
2019
|
+
const playerNodeRef = useRef3(null);
|
|
2020
|
+
const acceptedTurnSeqRef = useRef3(0);
|
|
2021
|
+
const mountedRef = useRef3(true);
|
|
2022
|
+
const lastFinalTurnRef = useRef3(
|
|
1469
2023
|
null
|
|
1470
2024
|
);
|
|
1471
|
-
|
|
2025
|
+
useEffect5(() => {
|
|
1472
2026
|
mountedRef.current = true;
|
|
1473
2027
|
return () => {
|
|
1474
2028
|
mountedRef.current = false;
|
|
1475
2029
|
};
|
|
1476
2030
|
}, []);
|
|
1477
|
-
const teardownAudio =
|
|
2031
|
+
const teardownAudio = useCallback5(() => {
|
|
1478
2032
|
try {
|
|
1479
2033
|
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
1480
2034
|
} catch {
|
|
@@ -1501,7 +2055,7 @@ function useVoice(options = {}) {
|
|
|
1501
2055
|
}
|
|
1502
2056
|
outputCtxRef.current = null;
|
|
1503
2057
|
}, []);
|
|
1504
|
-
const stop =
|
|
2058
|
+
const stop = useCallback5(() => {
|
|
1505
2059
|
voiceLogger.debug("stop() called");
|
|
1506
2060
|
try {
|
|
1507
2061
|
wsRef.current?.close(1e3, "client_stop");
|
|
@@ -1511,8 +2065,8 @@ function useVoice(options = {}) {
|
|
|
1511
2065
|
teardownAudio();
|
|
1512
2066
|
if (mountedRef.current) setState("idle");
|
|
1513
2067
|
}, [teardownAudio, voiceLogger]);
|
|
1514
|
-
|
|
1515
|
-
const startCapture =
|
|
2068
|
+
useEffect5(() => stop, [stop]);
|
|
2069
|
+
const startCapture = useCallback5(async (inputSampleRate) => {
|
|
1516
2070
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1517
2071
|
audio: {
|
|
1518
2072
|
echoCancellation: true,
|
|
@@ -1538,7 +2092,7 @@ function useVoice(options = {}) {
|
|
|
1538
2092
|
recorderNodeRef.current = recorderNode;
|
|
1539
2093
|
source.connect(recorderNode);
|
|
1540
2094
|
}, []);
|
|
1541
|
-
const setupPlayback =
|
|
2095
|
+
const setupPlayback = useCallback5(async (outputSampleRate) => {
|
|
1542
2096
|
const outputCtx = new AudioContext({ sampleRate: outputSampleRate });
|
|
1543
2097
|
outputCtxRef.current = outputCtx;
|
|
1544
2098
|
await outputCtx.audioWorklet.addModule(playerWorkletUrl());
|
|
@@ -1546,7 +2100,7 @@ function useVoice(options = {}) {
|
|
|
1546
2100
|
playerNode.connect(outputCtx.destination);
|
|
1547
2101
|
playerNodeRef.current = playerNode;
|
|
1548
2102
|
}, []);
|
|
1549
|
-
const upsertToolCall =
|
|
2103
|
+
const upsertToolCall = useCallback5(
|
|
1550
2104
|
(id, patch) => {
|
|
1551
2105
|
setToolCalls((prev) => {
|
|
1552
2106
|
const idx = prev.findIndex((t) => t.id === id);
|
|
@@ -1558,7 +2112,7 @@ function useVoice(options = {}) {
|
|
|
1558
2112
|
},
|
|
1559
2113
|
[]
|
|
1560
2114
|
);
|
|
1561
|
-
const handleTranscript =
|
|
2115
|
+
const handleTranscript = useCallback5((value) => {
|
|
1562
2116
|
const { speaker, text, isFinal, turnSeq } = value;
|
|
1563
2117
|
if (!isFinal) {
|
|
1564
2118
|
setPartial({ id: "partial", speaker, text });
|
|
@@ -1581,7 +2135,7 @@ function useVoice(options = {}) {
|
|
|
1581
2135
|
});
|
|
1582
2136
|
lastFinalTurnRef.current = { speaker, turnSeq };
|
|
1583
2137
|
}, []);
|
|
1584
|
-
const handleCustomEvent =
|
|
2138
|
+
const handleCustomEvent = useCallback5(
|
|
1585
2139
|
(name, value) => {
|
|
1586
2140
|
switch (name) {
|
|
1587
2141
|
case "voice_session_ready":
|
|
@@ -1639,7 +2193,7 @@ function useVoice(options = {}) {
|
|
|
1639
2193
|
},
|
|
1640
2194
|
[handleTranscript, setupPlayback, startCapture, teardownAudio, voiceLogger]
|
|
1641
2195
|
);
|
|
1642
|
-
const handleMessage =
|
|
2196
|
+
const handleMessage = useCallback5(
|
|
1643
2197
|
(event) => {
|
|
1644
2198
|
if (typeof event.data !== "string") {
|
|
1645
2199
|
const buf = event.data;
|
|
@@ -1697,7 +2251,7 @@ function useVoice(options = {}) {
|
|
|
1697
2251
|
},
|
|
1698
2252
|
[handleCustomEvent, upsertToolCall, voiceLogger]
|
|
1699
2253
|
);
|
|
1700
|
-
const start =
|
|
2254
|
+
const start = useCallback5(async () => {
|
|
1701
2255
|
if (!agentId) {
|
|
1702
2256
|
const err = new Error(
|
|
1703
2257
|
"useVoice: no agentId provided and no defaultAgentId set on PersonaProvider"
|
|
@@ -1764,17 +2318,17 @@ function useVoice(options = {}) {
|
|
|
1764
2318
|
teardownAudio();
|
|
1765
2319
|
}
|
|
1766
2320
|
}, [agentId, threadId, contextOverride, context, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
|
|
1767
|
-
const mute =
|
|
2321
|
+
const mute = useCallback5((muted) => {
|
|
1768
2322
|
setIsMuted(muted);
|
|
1769
2323
|
streamRef.current?.getAudioTracks().forEach((track) => {
|
|
1770
2324
|
track.enabled = !muted;
|
|
1771
2325
|
});
|
|
1772
2326
|
}, []);
|
|
1773
|
-
const sendText =
|
|
2327
|
+
const sendText = useCallback5((text) => {
|
|
1774
2328
|
if (!text?.trim() || wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1775
2329
|
wsRef.current.send(JSON.stringify({ type: "voice.text", text }));
|
|
1776
2330
|
}, []);
|
|
1777
|
-
const updateContext =
|
|
2331
|
+
const updateContext = useCallback5((newContext) => {
|
|
1778
2332
|
if (wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1779
2333
|
wsRef.current.send(JSON.stringify({ type: "voice.context", context: newContext }));
|
|
1780
2334
|
}, []);
|
|
@@ -1795,14 +2349,14 @@ function useVoice(options = {}) {
|
|
|
1795
2349
|
}
|
|
1796
2350
|
|
|
1797
2351
|
// src/hooks/useFiles.ts
|
|
1798
|
-
import { useCallback as
|
|
2352
|
+
import { useCallback as useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
|
|
1799
2353
|
function useFiles(autoFetch = true) {
|
|
1800
2354
|
const { fetchWithAuth } = usePersonaContext();
|
|
1801
|
-
const [files, setFiles] =
|
|
1802
|
-
const [isLoading, setIsLoading] =
|
|
1803
|
-
const [isUploading, setIsUploading] =
|
|
1804
|
-
const [error, setError] =
|
|
1805
|
-
const fetchFiles =
|
|
2355
|
+
const [files, setFiles] = useState6([]);
|
|
2356
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
2357
|
+
const [isUploading, setIsUploading] = useState6(false);
|
|
2358
|
+
const [error, setError] = useState6(null);
|
|
2359
|
+
const fetchFiles = useCallback6(async () => {
|
|
1806
2360
|
setIsLoading(true);
|
|
1807
2361
|
setError(null);
|
|
1808
2362
|
try {
|
|
@@ -1820,7 +2374,7 @@ function useFiles(autoFetch = true) {
|
|
|
1820
2374
|
setIsLoading(false);
|
|
1821
2375
|
}
|
|
1822
2376
|
}, [fetchWithAuth]);
|
|
1823
|
-
const uploadFile =
|
|
2377
|
+
const uploadFile = useCallback6(
|
|
1824
2378
|
async (fileOrFormData) => {
|
|
1825
2379
|
setIsUploading(true);
|
|
1826
2380
|
setError(null);
|
|
@@ -1853,7 +2407,7 @@ function useFiles(autoFetch = true) {
|
|
|
1853
2407
|
},
|
|
1854
2408
|
[fetchWithAuth, fetchFiles]
|
|
1855
2409
|
);
|
|
1856
|
-
const deleteFile =
|
|
2410
|
+
const deleteFile = useCallback6(
|
|
1857
2411
|
async (fileId) => {
|
|
1858
2412
|
const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
|
|
1859
2413
|
if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
|
|
@@ -1861,7 +2415,7 @@ function useFiles(autoFetch = true) {
|
|
|
1861
2415
|
},
|
|
1862
2416
|
[fetchWithAuth]
|
|
1863
2417
|
);
|
|
1864
|
-
const bulkDeleteFiles =
|
|
2418
|
+
const bulkDeleteFiles = useCallback6(
|
|
1865
2419
|
async (fileIds) => {
|
|
1866
2420
|
const res = await fetchWithAuth("/files/bulk-delete", {
|
|
1867
2421
|
method: "POST",
|
|
@@ -1876,10 +2430,10 @@ function useFiles(autoFetch = true) {
|
|
|
1876
2430
|
},
|
|
1877
2431
|
[fetchWithAuth]
|
|
1878
2432
|
);
|
|
1879
|
-
const getDownloadUrl =
|
|
2433
|
+
const getDownloadUrl = useCallback6((fileId) => {
|
|
1880
2434
|
return `/files/${fileId}`;
|
|
1881
2435
|
}, []);
|
|
1882
|
-
|
|
2436
|
+
useEffect6(() => {
|
|
1883
2437
|
if (autoFetch) {
|
|
1884
2438
|
void fetchFiles();
|
|
1885
2439
|
}
|
|
@@ -1898,21 +2452,39 @@ function useFiles(autoFetch = true) {
|
|
|
1898
2452
|
}
|
|
1899
2453
|
|
|
1900
2454
|
// src/hooks/useAgents.ts
|
|
1901
|
-
import { useCallback as
|
|
1902
|
-
function useAgents(
|
|
2455
|
+
import { useCallback as useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
|
|
2456
|
+
function useAgents(options = true) {
|
|
2457
|
+
const opts = typeof options === "boolean" ? { autoFetch: options } : options;
|
|
2458
|
+
const { autoFetch = true, page, limit, search, category, scope } = opts;
|
|
1903
2459
|
const { fetchWithAuth } = usePersonaContext();
|
|
1904
|
-
const [agents, setAgents] =
|
|
1905
|
-
const [
|
|
1906
|
-
|
|
1907
|
-
|
|
2460
|
+
const [agents, setAgents] = useState7([]);
|
|
2461
|
+
const [pagination, setPagination] = useState7({
|
|
2462
|
+
total: 0,
|
|
2463
|
+
page: page ?? 1,
|
|
2464
|
+
limit: limit ?? 20,
|
|
2465
|
+
pages: 0
|
|
2466
|
+
});
|
|
2467
|
+
const [isLoading, setIsLoading] = useState7(false);
|
|
2468
|
+
const [error, setError] = useState7(null);
|
|
2469
|
+
const fetchAgents = useCallback7(async () => {
|
|
1908
2470
|
setIsLoading(true);
|
|
1909
2471
|
setError(null);
|
|
1910
2472
|
try {
|
|
1911
|
-
const
|
|
2473
|
+
const query = new URLSearchParams();
|
|
2474
|
+
if (page) query.set("page", String(page));
|
|
2475
|
+
if (limit) query.set("limit", String(limit));
|
|
2476
|
+
if (search) query.set("search", search);
|
|
2477
|
+
if (category) query.set("category", category);
|
|
2478
|
+
if (scope) query.set("scope", scope);
|
|
2479
|
+
const queryStr = query.toString() ? `?${query.toString()}` : "";
|
|
2480
|
+
const res = await fetchWithAuth(`/agents${queryStr}`);
|
|
1912
2481
|
if (!res.ok) throw new Error(`Failed to list agents: ${res.statusText}`);
|
|
1913
2482
|
const data = await res.json();
|
|
1914
|
-
const items = Array.isArray(data) ? data : data?.items
|
|
2483
|
+
const items = Array.isArray(data) ? data : data?.items ?? [];
|
|
1915
2484
|
setAgents(items);
|
|
2485
|
+
setPagination(
|
|
2486
|
+
data?.pagination ?? { total: items.length, page: 1, limit: items.length, pages: 1 }
|
|
2487
|
+
);
|
|
1916
2488
|
return items;
|
|
1917
2489
|
} catch (err) {
|
|
1918
2490
|
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
@@ -1921,28 +2493,131 @@ function useAgents(autoFetch = true) {
|
|
|
1921
2493
|
} finally {
|
|
1922
2494
|
setIsLoading(false);
|
|
1923
2495
|
}
|
|
1924
|
-
}, [fetchWithAuth]);
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2496
|
+
}, [fetchWithAuth, page, limit, search, category, scope]);
|
|
2497
|
+
const getAgent = useCallback7(
|
|
2498
|
+
async (agentId) => {
|
|
2499
|
+
const res = await fetchWithAuth(`/agents/${agentId}`);
|
|
2500
|
+
if (!res.ok) throw new Error(`Failed to fetch agent: ${res.statusText}`);
|
|
2501
|
+
return res.json();
|
|
2502
|
+
},
|
|
2503
|
+
[fetchWithAuth]
|
|
2504
|
+
);
|
|
2505
|
+
const createAgent = useCallback7(
|
|
2506
|
+
async (input) => {
|
|
2507
|
+
setError(null);
|
|
2508
|
+
try {
|
|
2509
|
+
const res = await fetchWithAuth("/agents", {
|
|
2510
|
+
method: "POST",
|
|
2511
|
+
headers: { "Content-Type": "application/json" },
|
|
2512
|
+
body: JSON.stringify(input)
|
|
2513
|
+
});
|
|
2514
|
+
if (!res.ok) {
|
|
2515
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2516
|
+
throw new Error(`Failed to create agent (${res.status}): ${errText}`);
|
|
2517
|
+
}
|
|
2518
|
+
const created = await res.json();
|
|
2519
|
+
setAgents((prev) => [created, ...prev]);
|
|
2520
|
+
return created;
|
|
2521
|
+
} catch (err) {
|
|
2522
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2523
|
+
setError(errorObj);
|
|
2524
|
+
throw errorObj;
|
|
2525
|
+
}
|
|
2526
|
+
},
|
|
2527
|
+
[fetchWithAuth]
|
|
2528
|
+
);
|
|
2529
|
+
const updateAgent = useCallback7(
|
|
2530
|
+
async (agentId, input) => {
|
|
2531
|
+
setError(null);
|
|
2532
|
+
try {
|
|
2533
|
+
const res = await fetchWithAuth(`/agents/${agentId}`, {
|
|
2534
|
+
method: "PATCH",
|
|
2535
|
+
headers: { "Content-Type": "application/json" },
|
|
2536
|
+
body: JSON.stringify(input)
|
|
2537
|
+
});
|
|
2538
|
+
if (!res.ok) {
|
|
2539
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2540
|
+
throw new Error(`Failed to update agent (${res.status}): ${errText}`);
|
|
2541
|
+
}
|
|
2542
|
+
const updated = await res.json();
|
|
2543
|
+
setAgents((prev) => prev.map((a) => a._id === agentId ? updated : a));
|
|
2544
|
+
return updated;
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2547
|
+
setError(errorObj);
|
|
2548
|
+
throw errorObj;
|
|
2549
|
+
}
|
|
2550
|
+
},
|
|
2551
|
+
[fetchWithAuth]
|
|
2552
|
+
);
|
|
2553
|
+
const deleteAgent = useCallback7(
|
|
2554
|
+
async (agentId) => {
|
|
2555
|
+
setError(null);
|
|
2556
|
+
try {
|
|
2557
|
+
const res = await fetchWithAuth(`/agents/${agentId}`, { method: "DELETE" });
|
|
2558
|
+
if (!res.ok) {
|
|
2559
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2560
|
+
throw new Error(`Failed to delete agent (${res.status}): ${errText}`);
|
|
2561
|
+
}
|
|
2562
|
+
setAgents((prev) => prev.filter((a) => a._id !== agentId));
|
|
2563
|
+
} catch (err) {
|
|
2564
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2565
|
+
setError(errorObj);
|
|
2566
|
+
throw errorObj;
|
|
2567
|
+
}
|
|
2568
|
+
},
|
|
2569
|
+
[fetchWithAuth]
|
|
2570
|
+
);
|
|
2571
|
+
const bulkDeleteAgents = useCallback7(
|
|
2572
|
+
async (ids) => {
|
|
2573
|
+
setError(null);
|
|
2574
|
+
try {
|
|
2575
|
+
const res = await fetchWithAuth("/agents/bulk-delete", {
|
|
2576
|
+
method: "POST",
|
|
2577
|
+
headers: { "Content-Type": "application/json" },
|
|
2578
|
+
body: JSON.stringify({ ids })
|
|
2579
|
+
});
|
|
2580
|
+
if (!res.ok) {
|
|
2581
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2582
|
+
throw new Error(`Failed to bulk-delete agents (${res.status}): ${errText}`);
|
|
2583
|
+
}
|
|
2584
|
+
const result = await res.json();
|
|
2585
|
+
const deletedSet = new Set(result.deleted);
|
|
2586
|
+
setAgents((prev) => prev.filter((a) => !deletedSet.has(a._id)));
|
|
2587
|
+
return result;
|
|
2588
|
+
} catch (err) {
|
|
2589
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2590
|
+
setError(errorObj);
|
|
2591
|
+
throw errorObj;
|
|
2592
|
+
}
|
|
2593
|
+
},
|
|
2594
|
+
[fetchWithAuth]
|
|
2595
|
+
);
|
|
2596
|
+
useEffect7(() => {
|
|
2597
|
+
if (autoFetch) void fetchAgents();
|
|
1929
2598
|
}, [autoFetch, fetchAgents]);
|
|
1930
2599
|
return {
|
|
1931
2600
|
agents,
|
|
2601
|
+
pagination,
|
|
1932
2602
|
isLoading,
|
|
1933
2603
|
error,
|
|
1934
|
-
refetch: fetchAgents
|
|
2604
|
+
refetch: fetchAgents,
|
|
2605
|
+
getAgent,
|
|
2606
|
+
createAgent,
|
|
2607
|
+
updateAgent,
|
|
2608
|
+
deleteAgent,
|
|
2609
|
+
bulkDeleteAgents
|
|
1935
2610
|
};
|
|
1936
2611
|
}
|
|
1937
2612
|
|
|
1938
2613
|
// src/hooks/useConnection.ts
|
|
1939
|
-
import { useCallback as
|
|
2614
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
|
|
1940
2615
|
function useConnection(autoCheck = true) {
|
|
1941
2616
|
const { fetchWithAuth } = usePersonaContext();
|
|
1942
|
-
const [health, setHealth] =
|
|
1943
|
-
const [isConnected, setIsConnected] =
|
|
1944
|
-
const [isLoading, setIsLoading] =
|
|
1945
|
-
const checkHealth =
|
|
2617
|
+
const [health, setHealth] = useState8(null);
|
|
2618
|
+
const [isConnected, setIsConnected] = useState8(false);
|
|
2619
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
2620
|
+
const checkHealth = useCallback8(async () => {
|
|
1946
2621
|
setIsLoading(true);
|
|
1947
2622
|
try {
|
|
1948
2623
|
const res = await fetchWithAuth("/health");
|
|
@@ -1961,7 +2636,7 @@ function useConnection(autoCheck = true) {
|
|
|
1961
2636
|
setIsLoading(false);
|
|
1962
2637
|
}
|
|
1963
2638
|
}, [fetchWithAuth]);
|
|
1964
|
-
|
|
2639
|
+
useEffect8(() => {
|
|
1965
2640
|
if (autoCheck) {
|
|
1966
2641
|
void checkHealth();
|
|
1967
2642
|
}
|
|
@@ -1975,15 +2650,15 @@ function useConnection(autoCheck = true) {
|
|
|
1975
2650
|
}
|
|
1976
2651
|
|
|
1977
2652
|
// src/hooks/useMcpConnections.ts
|
|
1978
|
-
import { useCallback as
|
|
2653
|
+
import { useCallback as useCallback9, useEffect as useEffect9, useState as useState9 } from "react";
|
|
1979
2654
|
function useMcpConnections(options = {}) {
|
|
1980
2655
|
const { defaultAgentId, fetchWithAuth } = usePersonaContext();
|
|
1981
2656
|
const agentId = options.agentId ?? defaultAgentId;
|
|
1982
2657
|
const autoFetch = options.autoFetch ?? true;
|
|
1983
|
-
const [connections, setConnections] =
|
|
1984
|
-
const [isLoading, setIsLoading] =
|
|
1985
|
-
const [error, setError] =
|
|
1986
|
-
const fetchConnections =
|
|
2658
|
+
const [connections, setConnections] = useState9([]);
|
|
2659
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
2660
|
+
const [error, setError] = useState9(null);
|
|
2661
|
+
const fetchConnections = useCallback9(async () => {
|
|
1987
2662
|
if (!agentId) return [];
|
|
1988
2663
|
setIsLoading(true);
|
|
1989
2664
|
setError(null);
|
|
@@ -2007,7 +2682,34 @@ function useMcpConnections(options = {}) {
|
|
|
2007
2682
|
setIsLoading(false);
|
|
2008
2683
|
}
|
|
2009
2684
|
}, [agentId, fetchWithAuth, options.returnTo]);
|
|
2010
|
-
|
|
2685
|
+
const [isDisconnecting, setIsDisconnecting] = useState9(false);
|
|
2686
|
+
const disconnect = useCallback9(
|
|
2687
|
+
async (mcpId) => {
|
|
2688
|
+
setIsDisconnecting(true);
|
|
2689
|
+
setError(null);
|
|
2690
|
+
try {
|
|
2691
|
+
const res = await fetchWithAuth(`/mcps/${mcpId}/oauth/user/connection`, {
|
|
2692
|
+
method: "DELETE"
|
|
2693
|
+
});
|
|
2694
|
+
if (!res.ok)
|
|
2695
|
+
throw new Error(`Failed to disconnect MCP: ${res.statusText}`);
|
|
2696
|
+
setConnections(
|
|
2697
|
+
(prev) => prev.map(
|
|
2698
|
+
(c) => c.mcpId === mcpId ? { ...c, connected: false, authorizeUrl: null } : c
|
|
2699
|
+
)
|
|
2700
|
+
);
|
|
2701
|
+
void fetchConnections();
|
|
2702
|
+
} catch (err) {
|
|
2703
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2704
|
+
setError(errorObj);
|
|
2705
|
+
throw errorObj;
|
|
2706
|
+
} finally {
|
|
2707
|
+
setIsDisconnecting(false);
|
|
2708
|
+
}
|
|
2709
|
+
},
|
|
2710
|
+
[fetchWithAuth, fetchConnections]
|
|
2711
|
+
);
|
|
2712
|
+
useEffect9(() => {
|
|
2011
2713
|
if (autoFetch) void fetchConnections();
|
|
2012
2714
|
}, [autoFetch, fetchConnections]);
|
|
2013
2715
|
return {
|
|
@@ -2016,16 +2718,18 @@ function useMcpConnections(options = {}) {
|
|
|
2016
2718
|
unconnected: connections.filter((c) => !c.connected),
|
|
2017
2719
|
isLoading,
|
|
2018
2720
|
error,
|
|
2019
|
-
refetch: fetchConnections
|
|
2721
|
+
refetch: fetchConnections,
|
|
2722
|
+
disconnect,
|
|
2723
|
+
isDisconnecting
|
|
2020
2724
|
};
|
|
2021
2725
|
}
|
|
2022
2726
|
|
|
2023
2727
|
// src/hooks/useMcp.ts
|
|
2024
|
-
import { useCallback as
|
|
2728
|
+
import { useCallback as useCallback10 } from "react";
|
|
2025
2729
|
function useMcp(options = {}) {
|
|
2026
2730
|
const { fetchWithAuth } = usePersonaContext();
|
|
2027
2731
|
const defaultMcpId = options.mcpId;
|
|
2028
|
-
const readResource =
|
|
2732
|
+
const readResource = useCallback10(
|
|
2029
2733
|
async (uri, mcpId) => {
|
|
2030
2734
|
const id = mcpId ?? defaultMcpId;
|
|
2031
2735
|
if (!id) throw new Error("MCP server ID is required to read resource");
|
|
@@ -2039,7 +2743,7 @@ function useMcp(options = {}) {
|
|
|
2039
2743
|
},
|
|
2040
2744
|
[fetchWithAuth, defaultMcpId]
|
|
2041
2745
|
);
|
|
2042
|
-
const callTool =
|
|
2746
|
+
const callTool = useCallback10(
|
|
2043
2747
|
async (name, args, mcpId) => {
|
|
2044
2748
|
const id = mcpId ?? defaultMcpId;
|
|
2045
2749
|
if (!id) throw new Error("MCP server ID is required to call tool");
|
|
@@ -2061,22 +2765,378 @@ function useMcp(options = {}) {
|
|
|
2061
2765
|
};
|
|
2062
2766
|
}
|
|
2063
2767
|
|
|
2768
|
+
// src/hooks/useSkills.ts
|
|
2769
|
+
import { useCallback as useCallback11, useEffect as useEffect10, useState as useState10 } from "react";
|
|
2770
|
+
function useSkills(options = {}) {
|
|
2771
|
+
const { autoFetch = true, page, limit, search, scope } = options;
|
|
2772
|
+
const { fetchWithAuth } = usePersonaContext();
|
|
2773
|
+
const [skills, setSkills] = useState10([]);
|
|
2774
|
+
const [pagination, setPagination] = useState10({
|
|
2775
|
+
total: 0,
|
|
2776
|
+
page: page ?? 1,
|
|
2777
|
+
limit: limit ?? 20,
|
|
2778
|
+
pages: 0
|
|
2779
|
+
});
|
|
2780
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
2781
|
+
const [error, setError] = useState10(null);
|
|
2782
|
+
const fetchSkills = useCallback11(async () => {
|
|
2783
|
+
setIsLoading(true);
|
|
2784
|
+
setError(null);
|
|
2785
|
+
try {
|
|
2786
|
+
const query = new URLSearchParams();
|
|
2787
|
+
if (page) query.set("page", String(page));
|
|
2788
|
+
if (limit) query.set("limit", String(limit));
|
|
2789
|
+
if (search) query.set("search", search);
|
|
2790
|
+
if (scope) query.set("scope", scope);
|
|
2791
|
+
const queryStr = query.toString() ? `?${query.toString()}` : "";
|
|
2792
|
+
const res = await fetchWithAuth(`/skills${queryStr}`);
|
|
2793
|
+
if (!res.ok) throw new Error(`Failed to list skills: ${res.statusText}`);
|
|
2794
|
+
const data = await res.json();
|
|
2795
|
+
const items = Array.isArray(data) ? data : data?.items ?? [];
|
|
2796
|
+
setSkills(items);
|
|
2797
|
+
setPagination(
|
|
2798
|
+
data?.pagination ?? { total: items.length, page: 1, limit: items.length, pages: 1 }
|
|
2799
|
+
);
|
|
2800
|
+
return items;
|
|
2801
|
+
} catch (err) {
|
|
2802
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2803
|
+
setError(errorObj);
|
|
2804
|
+
return [];
|
|
2805
|
+
} finally {
|
|
2806
|
+
setIsLoading(false);
|
|
2807
|
+
}
|
|
2808
|
+
}, [fetchWithAuth, page, limit, search, scope]);
|
|
2809
|
+
const getSkill = useCallback11(
|
|
2810
|
+
async (skillId) => {
|
|
2811
|
+
const res = await fetchWithAuth(`/skills/${skillId}`);
|
|
2812
|
+
if (!res.ok) throw new Error(`Failed to fetch skill: ${res.statusText}`);
|
|
2813
|
+
return res.json();
|
|
2814
|
+
},
|
|
2815
|
+
[fetchWithAuth]
|
|
2816
|
+
);
|
|
2817
|
+
const createSkill = useCallback11(
|
|
2818
|
+
async (input) => {
|
|
2819
|
+
setError(null);
|
|
2820
|
+
try {
|
|
2821
|
+
const res = await fetchWithAuth("/skills", {
|
|
2822
|
+
method: "POST",
|
|
2823
|
+
headers: { "Content-Type": "application/json" },
|
|
2824
|
+
body: JSON.stringify(input)
|
|
2825
|
+
});
|
|
2826
|
+
if (!res.ok) {
|
|
2827
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2828
|
+
throw new Error(`Failed to create skill (${res.status}): ${errText}`);
|
|
2829
|
+
}
|
|
2830
|
+
const created = await res.json();
|
|
2831
|
+
setSkills((prev) => [created, ...prev]);
|
|
2832
|
+
return created;
|
|
2833
|
+
} catch (err) {
|
|
2834
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2835
|
+
setError(errorObj);
|
|
2836
|
+
throw errorObj;
|
|
2837
|
+
}
|
|
2838
|
+
},
|
|
2839
|
+
[fetchWithAuth]
|
|
2840
|
+
);
|
|
2841
|
+
const updateSkill = useCallback11(
|
|
2842
|
+
async (skillId, input) => {
|
|
2843
|
+
setError(null);
|
|
2844
|
+
try {
|
|
2845
|
+
const res = await fetchWithAuth(`/skills/${skillId}`, {
|
|
2846
|
+
method: "PATCH",
|
|
2847
|
+
headers: { "Content-Type": "application/json" },
|
|
2848
|
+
body: JSON.stringify(input)
|
|
2849
|
+
});
|
|
2850
|
+
if (!res.ok) {
|
|
2851
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2852
|
+
throw new Error(`Failed to update skill (${res.status}): ${errText}`);
|
|
2853
|
+
}
|
|
2854
|
+
const updated = await res.json();
|
|
2855
|
+
setSkills((prev) => prev.map((s) => s._id === skillId ? updated : s));
|
|
2856
|
+
return updated;
|
|
2857
|
+
} catch (err) {
|
|
2858
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2859
|
+
setError(errorObj);
|
|
2860
|
+
throw errorObj;
|
|
2861
|
+
}
|
|
2862
|
+
},
|
|
2863
|
+
[fetchWithAuth]
|
|
2864
|
+
);
|
|
2865
|
+
const deleteSkill = useCallback11(
|
|
2866
|
+
async (skillId) => {
|
|
2867
|
+
setError(null);
|
|
2868
|
+
try {
|
|
2869
|
+
const res = await fetchWithAuth(`/skills/${skillId}`, { method: "DELETE" });
|
|
2870
|
+
if (!res.ok) {
|
|
2871
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2872
|
+
throw new Error(`Failed to delete skill (${res.status}): ${errText}`);
|
|
2873
|
+
}
|
|
2874
|
+
setSkills((prev) => prev.filter((s) => s._id !== skillId));
|
|
2875
|
+
} catch (err) {
|
|
2876
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2877
|
+
setError(errorObj);
|
|
2878
|
+
throw errorObj;
|
|
2879
|
+
}
|
|
2880
|
+
},
|
|
2881
|
+
[fetchWithAuth]
|
|
2882
|
+
);
|
|
2883
|
+
const bulkDeleteSkills = useCallback11(
|
|
2884
|
+
async (ids) => {
|
|
2885
|
+
setError(null);
|
|
2886
|
+
try {
|
|
2887
|
+
const res = await fetchWithAuth("/skills/bulk-delete", {
|
|
2888
|
+
method: "POST",
|
|
2889
|
+
headers: { "Content-Type": "application/json" },
|
|
2890
|
+
body: JSON.stringify({ ids })
|
|
2891
|
+
});
|
|
2892
|
+
if (!res.ok) {
|
|
2893
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2894
|
+
throw new Error(`Failed to bulk-delete skills (${res.status}): ${errText}`);
|
|
2895
|
+
}
|
|
2896
|
+
const result = await res.json();
|
|
2897
|
+
const deletedSet = new Set(result.deleted);
|
|
2898
|
+
setSkills((prev) => prev.filter((s) => !deletedSet.has(s._id)));
|
|
2899
|
+
return result;
|
|
2900
|
+
} catch (err) {
|
|
2901
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2902
|
+
setError(errorObj);
|
|
2903
|
+
throw errorObj;
|
|
2904
|
+
}
|
|
2905
|
+
},
|
|
2906
|
+
[fetchWithAuth]
|
|
2907
|
+
);
|
|
2908
|
+
const getSkillUsage = useCallback11(
|
|
2909
|
+
async (skillId) => {
|
|
2910
|
+
const res = await fetchWithAuth(`/skills/${skillId}/usage`);
|
|
2911
|
+
if (!res.ok) throw new Error(`Failed to fetch skill usage: ${res.statusText}`);
|
|
2912
|
+
return res.json();
|
|
2913
|
+
},
|
|
2914
|
+
[fetchWithAuth]
|
|
2915
|
+
);
|
|
2916
|
+
useEffect10(() => {
|
|
2917
|
+
if (autoFetch) void fetchSkills();
|
|
2918
|
+
}, [autoFetch, fetchSkills]);
|
|
2919
|
+
return {
|
|
2920
|
+
skills,
|
|
2921
|
+
pagination,
|
|
2922
|
+
isLoading,
|
|
2923
|
+
error,
|
|
2924
|
+
refetch: fetchSkills,
|
|
2925
|
+
getSkill,
|
|
2926
|
+
createSkill,
|
|
2927
|
+
updateSkill,
|
|
2928
|
+
deleteSkill,
|
|
2929
|
+
bulkDeleteSkills,
|
|
2930
|
+
getSkillUsage
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
// src/hooks/useRcpSources.ts
|
|
2935
|
+
import { useCallback as useCallback12, useEffect as useEffect11, useState as useState11 } from "react";
|
|
2936
|
+
function useRcpSources(options = {}) {
|
|
2937
|
+
const { autoFetch = true, page, limit, search } = options;
|
|
2938
|
+
const { fetchWithAuth } = usePersonaContext();
|
|
2939
|
+
const [rcpSources, setRcpSources] = useState11([]);
|
|
2940
|
+
const [pagination, setPagination] = useState11({
|
|
2941
|
+
total: 0,
|
|
2942
|
+
page: page ?? 1,
|
|
2943
|
+
limit: limit ?? 20,
|
|
2944
|
+
pages: 0
|
|
2945
|
+
});
|
|
2946
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
2947
|
+
const [error, setError] = useState11(null);
|
|
2948
|
+
const fetchRcpSources = useCallback12(async () => {
|
|
2949
|
+
setIsLoading(true);
|
|
2950
|
+
setError(null);
|
|
2951
|
+
try {
|
|
2952
|
+
const query = new URLSearchParams();
|
|
2953
|
+
if (page) query.set("page", String(page));
|
|
2954
|
+
if (limit) query.set("limit", String(limit));
|
|
2955
|
+
if (search) query.set("search", search);
|
|
2956
|
+
const queryStr = query.toString() ? `?${query.toString()}` : "";
|
|
2957
|
+
const res = await fetchWithAuth(`/rcp-sources${queryStr}`);
|
|
2958
|
+
if (!res.ok) throw new Error(`Failed to list RCP sources: ${res.statusText}`);
|
|
2959
|
+
const data = await res.json();
|
|
2960
|
+
const items = Array.isArray(data) ? data : data?.items ?? [];
|
|
2961
|
+
setRcpSources(items);
|
|
2962
|
+
setPagination(
|
|
2963
|
+
data?.pagination ?? { total: items.length, page: 1, limit: items.length, pages: 1 }
|
|
2964
|
+
);
|
|
2965
|
+
return items;
|
|
2966
|
+
} catch (err) {
|
|
2967
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
2968
|
+
setError(errorObj);
|
|
2969
|
+
return [];
|
|
2970
|
+
} finally {
|
|
2971
|
+
setIsLoading(false);
|
|
2972
|
+
}
|
|
2973
|
+
}, [fetchWithAuth, page, limit, search]);
|
|
2974
|
+
const getRcpSource = useCallback12(
|
|
2975
|
+
async (sourceId) => {
|
|
2976
|
+
const res = await fetchWithAuth(`/rcp-sources/${sourceId}`);
|
|
2977
|
+
if (!res.ok) throw new Error(`Failed to fetch RCP source: ${res.statusText}`);
|
|
2978
|
+
return res.json();
|
|
2979
|
+
},
|
|
2980
|
+
[fetchWithAuth]
|
|
2981
|
+
);
|
|
2982
|
+
const createRcpSource = useCallback12(
|
|
2983
|
+
async (input) => {
|
|
2984
|
+
setError(null);
|
|
2985
|
+
try {
|
|
2986
|
+
const res = await fetchWithAuth("/rcp-sources", {
|
|
2987
|
+
method: "POST",
|
|
2988
|
+
headers: { "Content-Type": "application/json" },
|
|
2989
|
+
body: JSON.stringify(input)
|
|
2990
|
+
});
|
|
2991
|
+
if (!res.ok) {
|
|
2992
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
2993
|
+
throw new Error(`Failed to create RCP source (${res.status}): ${errText}`);
|
|
2994
|
+
}
|
|
2995
|
+
const created = await res.json();
|
|
2996
|
+
setRcpSources((prev) => [created, ...prev]);
|
|
2997
|
+
return created;
|
|
2998
|
+
} catch (err) {
|
|
2999
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3000
|
+
setError(errorObj);
|
|
3001
|
+
throw errorObj;
|
|
3002
|
+
}
|
|
3003
|
+
},
|
|
3004
|
+
[fetchWithAuth]
|
|
3005
|
+
);
|
|
3006
|
+
const updateRcpSource = useCallback12(
|
|
3007
|
+
async (sourceId, input) => {
|
|
3008
|
+
setError(null);
|
|
3009
|
+
try {
|
|
3010
|
+
const res = await fetchWithAuth(`/rcp-sources/${sourceId}`, {
|
|
3011
|
+
method: "PATCH",
|
|
3012
|
+
headers: { "Content-Type": "application/json" },
|
|
3013
|
+
body: JSON.stringify(input)
|
|
3014
|
+
});
|
|
3015
|
+
if (!res.ok) {
|
|
3016
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
3017
|
+
throw new Error(`Failed to update RCP source (${res.status}): ${errText}`);
|
|
3018
|
+
}
|
|
3019
|
+
const updated = await res.json();
|
|
3020
|
+
setRcpSources((prev) => prev.map((s) => s._id === sourceId ? updated : s));
|
|
3021
|
+
return updated;
|
|
3022
|
+
} catch (err) {
|
|
3023
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3024
|
+
setError(errorObj);
|
|
3025
|
+
throw errorObj;
|
|
3026
|
+
}
|
|
3027
|
+
},
|
|
3028
|
+
[fetchWithAuth]
|
|
3029
|
+
);
|
|
3030
|
+
const deleteRcpSource = useCallback12(
|
|
3031
|
+
async (sourceId) => {
|
|
3032
|
+
setError(null);
|
|
3033
|
+
try {
|
|
3034
|
+
const res = await fetchWithAuth(`/rcp-sources/${sourceId}`, { method: "DELETE" });
|
|
3035
|
+
if (!res.ok) {
|
|
3036
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
3037
|
+
throw new Error(`Failed to delete RCP source (${res.status}): ${errText}`);
|
|
3038
|
+
}
|
|
3039
|
+
setRcpSources((prev) => prev.filter((s) => s._id !== sourceId));
|
|
3040
|
+
} catch (err) {
|
|
3041
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3042
|
+
setError(errorObj);
|
|
3043
|
+
throw errorObj;
|
|
3044
|
+
}
|
|
3045
|
+
},
|
|
3046
|
+
[fetchWithAuth]
|
|
3047
|
+
);
|
|
3048
|
+
const bulkDeleteRcpSources = useCallback12(
|
|
3049
|
+
async (ids) => {
|
|
3050
|
+
setError(null);
|
|
3051
|
+
try {
|
|
3052
|
+
const res = await fetchWithAuth("/rcp-sources/bulk-delete", {
|
|
3053
|
+
method: "POST",
|
|
3054
|
+
headers: { "Content-Type": "application/json" },
|
|
3055
|
+
body: JSON.stringify({ ids })
|
|
3056
|
+
});
|
|
3057
|
+
if (!res.ok) {
|
|
3058
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
3059
|
+
throw new Error(`Failed to bulk-delete RCP sources (${res.status}): ${errText}`);
|
|
3060
|
+
}
|
|
3061
|
+
const result = await res.json();
|
|
3062
|
+
const deletedSet = new Set(result.deleted);
|
|
3063
|
+
setRcpSources((prev) => prev.filter((s) => !deletedSet.has(s._id)));
|
|
3064
|
+
return result;
|
|
3065
|
+
} catch (err) {
|
|
3066
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3067
|
+
setError(errorObj);
|
|
3068
|
+
throw errorObj;
|
|
3069
|
+
}
|
|
3070
|
+
},
|
|
3071
|
+
[fetchWithAuth]
|
|
3072
|
+
);
|
|
3073
|
+
const getRcpSourceUsage = useCallback12(
|
|
3074
|
+
async (sourceId) => {
|
|
3075
|
+
const res = await fetchWithAuth(`/rcp-sources/${sourceId}/usage`);
|
|
3076
|
+
if (!res.ok) throw new Error(`Failed to fetch RCP source usage: ${res.statusText}`);
|
|
3077
|
+
return res.json();
|
|
3078
|
+
},
|
|
3079
|
+
[fetchWithAuth]
|
|
3080
|
+
);
|
|
3081
|
+
const testRcpSourceConnection = useCallback12(
|
|
3082
|
+
async (sourceId) => {
|
|
3083
|
+
setError(null);
|
|
3084
|
+
try {
|
|
3085
|
+
const res = await fetchWithAuth(`/rcp-sources/${sourceId}/test`, { method: "POST" });
|
|
3086
|
+
if (!res.ok) {
|
|
3087
|
+
const errText = await res.text().catch(() => res.statusText);
|
|
3088
|
+
throw new Error(`Failed to test RCP source connection (${res.status}): ${errText}`);
|
|
3089
|
+
}
|
|
3090
|
+
const result = await res.json();
|
|
3091
|
+
setRcpSources(
|
|
3092
|
+
(prev) => prev.map(
|
|
3093
|
+
(s) => s._id === sourceId ? { ...s, tools: result.tools, lastTestedAt: (/* @__PURE__ */ new Date()).toISOString() } : s
|
|
3094
|
+
)
|
|
3095
|
+
);
|
|
3096
|
+
return result;
|
|
3097
|
+
} catch (err) {
|
|
3098
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
3099
|
+
setError(errorObj);
|
|
3100
|
+
throw errorObj;
|
|
3101
|
+
}
|
|
3102
|
+
},
|
|
3103
|
+
[fetchWithAuth]
|
|
3104
|
+
);
|
|
3105
|
+
useEffect11(() => {
|
|
3106
|
+
if (autoFetch) void fetchRcpSources();
|
|
3107
|
+
}, [autoFetch, fetchRcpSources]);
|
|
3108
|
+
return {
|
|
3109
|
+
rcpSources,
|
|
3110
|
+
pagination,
|
|
3111
|
+
isLoading,
|
|
3112
|
+
error,
|
|
3113
|
+
refetch: fetchRcpSources,
|
|
3114
|
+
getRcpSource,
|
|
3115
|
+
createRcpSource,
|
|
3116
|
+
updateRcpSource,
|
|
3117
|
+
deleteRcpSource,
|
|
3118
|
+
bulkDeleteRcpSources,
|
|
3119
|
+
getRcpSourceUsage,
|
|
3120
|
+
testRcpSourceConnection
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
|
|
2064
3124
|
// src/hooks/useWorkflows.ts
|
|
2065
|
-
import { useCallback as
|
|
3125
|
+
import { useCallback as useCallback13, useEffect as useEffect12, useState as useState12 } from "react";
|
|
2066
3126
|
function useWorkflows(options) {
|
|
2067
3127
|
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2068
3128
|
const { autoFetch = true, search, status, page, limit } = opts;
|
|
2069
3129
|
const { fetchWithAuth } = usePersonaContext();
|
|
2070
|
-
const [workflows, setWorkflows] =
|
|
2071
|
-
const [pagination, setPagination] =
|
|
3130
|
+
const [workflows, setWorkflows] = useState12([]);
|
|
3131
|
+
const [pagination, setPagination] = useState12({
|
|
2072
3132
|
page: page ?? 1,
|
|
2073
3133
|
limit: limit ?? 20,
|
|
2074
3134
|
total: 0,
|
|
2075
3135
|
totalPages: 0
|
|
2076
3136
|
});
|
|
2077
|
-
const [isLoading, setIsLoading] =
|
|
2078
|
-
const [error, setError] =
|
|
2079
|
-
const fetchWorkflows =
|
|
3137
|
+
const [isLoading, setIsLoading] = useState12(false);
|
|
3138
|
+
const [error, setError] = useState12(null);
|
|
3139
|
+
const fetchWorkflows = useCallback13(async () => {
|
|
2080
3140
|
setIsLoading(true);
|
|
2081
3141
|
setError(null);
|
|
2082
3142
|
try {
|
|
@@ -2098,7 +3158,10 @@ function useWorkflows(options) {
|
|
|
2098
3158
|
page: data.pagination.page ?? 1,
|
|
2099
3159
|
limit: data.pagination.limit ?? items.length,
|
|
2100
3160
|
total: data.pagination.total ?? items.length,
|
|
2101
|
-
|
|
3161
|
+
// Backend's paginationEnvelope() names this field `pages`, not
|
|
3162
|
+
// `totalPages` — this used to always fall through to the `?? 1`
|
|
3163
|
+
// default.
|
|
3164
|
+
totalPages: data.pagination.pages ?? 1
|
|
2102
3165
|
});
|
|
2103
3166
|
} else {
|
|
2104
3167
|
setPagination({
|
|
@@ -2117,7 +3180,7 @@ function useWorkflows(options) {
|
|
|
2117
3180
|
setIsLoading(false);
|
|
2118
3181
|
}
|
|
2119
3182
|
}, [fetchWithAuth, search, status, page, limit]);
|
|
2120
|
-
const createWorkflow =
|
|
3183
|
+
const createWorkflow = useCallback13(
|
|
2121
3184
|
async (input) => {
|
|
2122
3185
|
setError(null);
|
|
2123
3186
|
try {
|
|
@@ -2141,7 +3204,7 @@ function useWorkflows(options) {
|
|
|
2141
3204
|
},
|
|
2142
3205
|
[fetchWithAuth]
|
|
2143
3206
|
);
|
|
2144
|
-
|
|
3207
|
+
useEffect12(() => {
|
|
2145
3208
|
if (autoFetch) {
|
|
2146
3209
|
void fetchWorkflows();
|
|
2147
3210
|
}
|
|
@@ -2157,17 +3220,17 @@ function useWorkflows(options) {
|
|
|
2157
3220
|
}
|
|
2158
3221
|
|
|
2159
3222
|
// src/hooks/useWorkflow.ts
|
|
2160
|
-
import { useCallback as
|
|
3223
|
+
import { useCallback as useCallback14, useEffect as useEffect13, useState as useState13 } from "react";
|
|
2161
3224
|
function useWorkflow(workflowId, options) {
|
|
2162
3225
|
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2163
3226
|
const { autoFetch = true } = opts;
|
|
2164
3227
|
const { fetchWithAuth } = usePersonaContext();
|
|
2165
|
-
const [workflow, setWorkflow] =
|
|
2166
|
-
const [versions, setVersions] =
|
|
2167
|
-
const [mermaid, setMermaid] =
|
|
2168
|
-
const [isLoading, setIsLoading] =
|
|
2169
|
-
const [error, setError] =
|
|
2170
|
-
const fetchWorkflow =
|
|
3228
|
+
const [workflow, setWorkflow] = useState13(null);
|
|
3229
|
+
const [versions, setVersions] = useState13([]);
|
|
3230
|
+
const [mermaid, setMermaid] = useState13(null);
|
|
3231
|
+
const [isLoading, setIsLoading] = useState13(false);
|
|
3232
|
+
const [error, setError] = useState13(null);
|
|
3233
|
+
const fetchWorkflow = useCallback14(async () => {
|
|
2171
3234
|
if (!workflowId) return null;
|
|
2172
3235
|
setIsLoading(true);
|
|
2173
3236
|
setError(null);
|
|
@@ -2187,7 +3250,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2187
3250
|
setIsLoading(false);
|
|
2188
3251
|
}
|
|
2189
3252
|
}, [fetchWithAuth, workflowId]);
|
|
2190
|
-
const fetchVersions =
|
|
3253
|
+
const fetchVersions = useCallback14(async () => {
|
|
2191
3254
|
if (!workflowId) return [];
|
|
2192
3255
|
try {
|
|
2193
3256
|
const res = await fetchWithAuth(`/workflows/${workflowId}/versions`);
|
|
@@ -2200,7 +3263,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2200
3263
|
return [];
|
|
2201
3264
|
}
|
|
2202
3265
|
}, [fetchWithAuth, workflowId]);
|
|
2203
|
-
const fetchMermaid =
|
|
3266
|
+
const fetchMermaid = useCallback14(async () => {
|
|
2204
3267
|
if (!workflowId) return "";
|
|
2205
3268
|
try {
|
|
2206
3269
|
const res = await fetchWithAuth(`/workflows/${workflowId}/mermaid`);
|
|
@@ -2213,7 +3276,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2213
3276
|
return "";
|
|
2214
3277
|
}
|
|
2215
3278
|
}, [fetchWithAuth, workflowId]);
|
|
2216
|
-
const saveDraft =
|
|
3279
|
+
const saveDraft = useCallback14(
|
|
2217
3280
|
async (draft) => {
|
|
2218
3281
|
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2219
3282
|
setError(null);
|
|
@@ -2232,7 +3295,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2232
3295
|
},
|
|
2233
3296
|
[fetchWithAuth, workflowId]
|
|
2234
3297
|
);
|
|
2235
|
-
const publish =
|
|
3298
|
+
const publish = useCallback14(
|
|
2236
3299
|
async (summary) => {
|
|
2237
3300
|
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2238
3301
|
setError(null);
|
|
@@ -2252,7 +3315,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2252
3315
|
},
|
|
2253
3316
|
[fetchWithAuth, workflowId, fetchWorkflow, fetchVersions]
|
|
2254
3317
|
);
|
|
2255
|
-
const deleteWorkflow =
|
|
3318
|
+
const deleteWorkflow = useCallback14(async () => {
|
|
2256
3319
|
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2257
3320
|
setError(null);
|
|
2258
3321
|
const res = await fetchWithAuth(`/workflows/${workflowId}`, {
|
|
@@ -2263,7 +3326,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2263
3326
|
}
|
|
2264
3327
|
setWorkflow(null);
|
|
2265
3328
|
}, [fetchWithAuth, workflowId]);
|
|
2266
|
-
|
|
3329
|
+
useEffect13(() => {
|
|
2267
3330
|
if (autoFetch && workflowId) {
|
|
2268
3331
|
void fetchWorkflow();
|
|
2269
3332
|
}
|
|
@@ -2284,25 +3347,25 @@ function useWorkflow(workflowId, options) {
|
|
|
2284
3347
|
}
|
|
2285
3348
|
|
|
2286
3349
|
// src/hooks/useWorkflowStream.ts
|
|
2287
|
-
import { useCallback as
|
|
3350
|
+
import { useCallback as useCallback15, useEffect as useEffect14, useRef as useRef4, useState as useState14 } from "react";
|
|
2288
3351
|
function useWorkflowStream(workflowId, options) {
|
|
2289
3352
|
const { baseUrl, getAuthToken, fetchWithAuth, logger } = usePersonaContext();
|
|
2290
|
-
const [status, setStatus] =
|
|
2291
|
-
const [runId, setRunId] =
|
|
2292
|
-
const [activeNodeId, setActiveNodeId] =
|
|
2293
|
-
const [nodeRuns, setNodeRuns] =
|
|
3353
|
+
const [status, setStatus] = useState14("idle");
|
|
3354
|
+
const [runId, setRunId] = useState14(null);
|
|
3355
|
+
const [activeNodeId, setActiveNodeId] = useState14(null);
|
|
3356
|
+
const [nodeRuns, setNodeRuns] = useState14(
|
|
2294
3357
|
{}
|
|
2295
3358
|
);
|
|
2296
|
-
const [text, setText] =
|
|
2297
|
-
const [output, setOutput] =
|
|
2298
|
-
const [error, setError] =
|
|
2299
|
-
const [events, setEvents] =
|
|
2300
|
-
const abortControllerRef =
|
|
2301
|
-
const currentRunIdRef =
|
|
3359
|
+
const [text, setText] = useState14("");
|
|
3360
|
+
const [output, setOutput] = useState14(null);
|
|
3361
|
+
const [error, setError] = useState14(null);
|
|
3362
|
+
const [events, setEvents] = useState14([]);
|
|
3363
|
+
const abortControllerRef = useRef4(null);
|
|
3364
|
+
const currentRunIdRef = useRef4(null);
|
|
2302
3365
|
currentRunIdRef.current = runId;
|
|
2303
|
-
const optionsRef =
|
|
3366
|
+
const optionsRef = useRef4(options);
|
|
2304
3367
|
optionsRef.current = options;
|
|
2305
|
-
const cancel =
|
|
3368
|
+
const cancel = useCallback15(async () => {
|
|
2306
3369
|
if (abortControllerRef.current) {
|
|
2307
3370
|
abortControllerRef.current.abort();
|
|
2308
3371
|
abortControllerRef.current = null;
|
|
@@ -2323,7 +3386,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2323
3386
|
setStatus("cancelled");
|
|
2324
3387
|
setActiveNodeId(null);
|
|
2325
3388
|
}, [fetchWithAuth, logger]);
|
|
2326
|
-
const reset =
|
|
3389
|
+
const reset = useCallback15(() => {
|
|
2327
3390
|
if (abortControllerRef.current) {
|
|
2328
3391
|
abortControllerRef.current.abort();
|
|
2329
3392
|
abortControllerRef.current = null;
|
|
@@ -2337,7 +3400,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2337
3400
|
setError(null);
|
|
2338
3401
|
setEvents([]);
|
|
2339
3402
|
}, []);
|
|
2340
|
-
const processStream =
|
|
3403
|
+
const processStream = useCallback15(
|
|
2341
3404
|
async (url, method, body, targetRunId) => {
|
|
2342
3405
|
if (abortControllerRef.current) {
|
|
2343
3406
|
abortControllerRef.current.abort();
|
|
@@ -2502,7 +3565,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2502
3565
|
},
|
|
2503
3566
|
[getAuthToken]
|
|
2504
3567
|
);
|
|
2505
|
-
const start =
|
|
3568
|
+
const start = useCallback15(
|
|
2506
3569
|
async (input, opts) => {
|
|
2507
3570
|
const targetWfId = opts?.workflowId || workflowId;
|
|
2508
3571
|
if (!targetWfId) {
|
|
@@ -2523,7 +3586,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2523
3586
|
},
|
|
2524
3587
|
[workflowId, baseUrl, processStream]
|
|
2525
3588
|
);
|
|
2526
|
-
const resume =
|
|
3589
|
+
const resume = useCallback15(
|
|
2527
3590
|
async (targetRunId, sinceSeq) => {
|
|
2528
3591
|
if (!targetRunId) {
|
|
2529
3592
|
throw new Error("Run ID is required to resume stream");
|
|
@@ -2536,7 +3599,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2536
3599
|
},
|
|
2537
3600
|
[baseUrl, processStream]
|
|
2538
3601
|
);
|
|
2539
|
-
|
|
3602
|
+
useEffect14(() => {
|
|
2540
3603
|
return () => {
|
|
2541
3604
|
if (abortControllerRef.current) {
|
|
2542
3605
|
abortControllerRef.current.abort();
|
|
@@ -2561,21 +3624,21 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2561
3624
|
}
|
|
2562
3625
|
|
|
2563
3626
|
// src/hooks/useWorkflowRuns.ts
|
|
2564
|
-
import { useCallback as
|
|
3627
|
+
import { useCallback as useCallback16, useEffect as useEffect15, useState as useState15 } from "react";
|
|
2565
3628
|
function useWorkflowRuns(workflowId, options) {
|
|
2566
3629
|
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2567
3630
|
const { autoFetch = true, page = 1, limit = 20 } = opts;
|
|
2568
3631
|
const { fetchWithAuth } = usePersonaContext();
|
|
2569
|
-
const [runs, setRuns] =
|
|
2570
|
-
const [pagination, setPagination] =
|
|
3632
|
+
const [runs, setRuns] = useState15([]);
|
|
3633
|
+
const [pagination, setPagination] = useState15({
|
|
2571
3634
|
page,
|
|
2572
3635
|
limit,
|
|
2573
3636
|
total: 0,
|
|
2574
3637
|
totalPages: 0
|
|
2575
3638
|
});
|
|
2576
|
-
const [isLoading, setIsLoading] =
|
|
2577
|
-
const [error, setError] =
|
|
2578
|
-
const fetchRuns =
|
|
3639
|
+
const [isLoading, setIsLoading] = useState15(false);
|
|
3640
|
+
const [error, setError] = useState15(null);
|
|
3641
|
+
const fetchRuns = useCallback16(async () => {
|
|
2579
3642
|
if (!workflowId) return [];
|
|
2580
3643
|
setIsLoading(true);
|
|
2581
3644
|
setError(null);
|
|
@@ -2605,7 +3668,7 @@ function useWorkflowRuns(workflowId, options) {
|
|
|
2605
3668
|
setIsLoading(false);
|
|
2606
3669
|
}
|
|
2607
3670
|
}, [fetchWithAuth, workflowId, page, limit]);
|
|
2608
|
-
const getRun =
|
|
3671
|
+
const getRun = useCallback16(
|
|
2609
3672
|
async (runId) => {
|
|
2610
3673
|
if (!runId) throw new Error("Run ID is required");
|
|
2611
3674
|
const res = await fetchWithAuth(`/workflows/runs/${runId}`);
|
|
@@ -2616,7 +3679,7 @@ function useWorkflowRuns(workflowId, options) {
|
|
|
2616
3679
|
},
|
|
2617
3680
|
[fetchWithAuth]
|
|
2618
3681
|
);
|
|
2619
|
-
const cancelRun =
|
|
3682
|
+
const cancelRun = useCallback16(
|
|
2620
3683
|
async (runId) => {
|
|
2621
3684
|
if (!runId) throw new Error("Run ID is required");
|
|
2622
3685
|
const res = await fetchWithAuth(`/workflows/runs/${runId}/cancel`, {
|
|
@@ -2631,7 +3694,7 @@ function useWorkflowRuns(workflowId, options) {
|
|
|
2631
3694
|
},
|
|
2632
3695
|
[fetchWithAuth]
|
|
2633
3696
|
);
|
|
2634
|
-
|
|
3697
|
+
useEffect15(() => {
|
|
2635
3698
|
if (autoFetch && workflowId) {
|
|
2636
3699
|
void fetchRuns();
|
|
2637
3700
|
}
|
|
@@ -2655,8 +3718,9 @@ import {
|
|
|
2655
3718
|
getLogLevel,
|
|
2656
3719
|
isLevelEnabled
|
|
2657
3720
|
} from "@personaai/logger";
|
|
2658
|
-
var VERSION = "0.
|
|
3721
|
+
var VERSION = "0.10.0";
|
|
2659
3722
|
export {
|
|
3723
|
+
ARCHITECT_AGENT_ID,
|
|
2660
3724
|
PersonaProvider,
|
|
2661
3725
|
VERSION,
|
|
2662
3726
|
createLogger2 as createLogger,
|
|
@@ -2667,6 +3731,7 @@ export {
|
|
|
2667
3731
|
setLogLevel,
|
|
2668
3732
|
supportsStreamingFetch,
|
|
2669
3733
|
useAgents,
|
|
3734
|
+
useArchitectChat,
|
|
2670
3735
|
useChat,
|
|
2671
3736
|
useConnection,
|
|
2672
3737
|
useFiles,
|
|
@@ -2674,6 +3739,8 @@ export {
|
|
|
2674
3739
|
useMcpConnections,
|
|
2675
3740
|
useMemory,
|
|
2676
3741
|
usePersonaContext,
|
|
3742
|
+
useRcpSources,
|
|
3743
|
+
useSkills,
|
|
2677
3744
|
useThreads,
|
|
2678
3745
|
useVoice,
|
|
2679
3746
|
useWorkflow,
|