@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/dist/index.js CHANGED
@@ -286,7 +286,7 @@ async function openSSEStream(opts) {
286
286
  };
287
287
  }
288
288
 
289
- // src/hooks/useChat.ts
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/useMemory.ts
1110
- import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
1111
- function useMemory(autoFetch = true) {
1112
- const { fetchWithAuth } = usePersonaContext();
1113
- const [memory, setMemory] = useState2({
1114
- userFiles: [],
1115
- agentMemories: []
1116
- });
1117
- const [isLoading, setIsLoading] = useState2(false);
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 fetchMemory = useCallback2(async () => {
1120
- setIsLoading(true);
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
- try {
1123
- const res = await fetchWithAuth("/memory");
1124
- if (!res.ok) {
1125
- throw new Error(`Failed to fetch memory: ${res.statusText}`);
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, fetchMemory]
1246
+ [fetchWithAuth, chatLogger]
1178
1247
  );
1179
1248
  useEffect2(() => {
1180
- if (autoFetch) {
1181
- void fetchMemory();
1249
+ if (!effectiveThreadId || isStreaming) {
1250
+ chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
1251
+ return;
1182
1252
  }
1183
- }, [autoFetch, fetchMemory]);
1184
- return {
1185
- memory,
1186
- isLoading,
1187
- error,
1188
- refetch: fetchMemory,
1189
- getFile,
1190
- writeFile,
1191
- deleteFile
1192
- };
1193
- }
1194
-
1195
- // src/hooks/useThreads.ts
1196
- import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
1197
- function useThreads(autoFetch = true) {
1198
- const { fetchWithAuth, defaultAgentId } = usePersonaContext();
1199
- const [threads, setThreads] = useState3([]);
1200
- const [isLoading, setIsLoading] = useState3(false);
1201
- const [error, setError] = useState3(null);
1202
- const fetchThreads = useCallback3(async () => {
1203
- setIsLoading(true);
1204
- setError(null);
1205
- try {
1206
- const res = await fetchWithAuth("/threads");
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 = useCallback3(
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 = useCallback3(
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 = useCallback3(
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 = useCallback3(async () => {
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 = useCallback3(
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 = useCallback3(
1843
+ const renameThread = useCallback4(
1290
1844
  (threadId, title) => updateThread(threadId, { title }),
1291
1845
  [updateThread]
1292
1846
  );
1293
- const resetThread = useCallback3(
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 = useCallback3(
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
- useEffect3(() => {
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 useCallback4, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef2, useState as useState4 } from "react";
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 = useMemo3(() => logger.child("voice"), [logger]);
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] = useState4("idle");
1448
- const [isMuted, setIsMuted] = useState4(false);
1449
- const [transcript, setTranscript] = useState4(
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] = useState4(
2006
+ const [partial, setPartial] = useState5(
1453
2007
  null
1454
2008
  );
1455
- const [toolCalls, setToolCalls] = useState4([]);
1456
- const [error, setError] = useState4(null);
1457
- const [endReason, setEndReason] = useState4(
2009
+ const [toolCalls, setToolCalls] = useState5([]);
2010
+ const [error, setError] = useState5(null);
2011
+ const [endReason, setEndReason] = useState5(
1458
2012
  null
1459
2013
  );
1460
- const wsRef = useRef2(null);
1461
- const streamRef = useRef2(null);
1462
- const inputCtxRef = useRef2(null);
1463
- const outputCtxRef = useRef2(null);
1464
- const recorderNodeRef = useRef2(null);
1465
- const playerNodeRef = useRef2(null);
1466
- const acceptedTurnSeqRef = useRef2(0);
1467
- const mountedRef = useRef2(true);
1468
- const lastFinalTurnRef = useRef2(
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
- useEffect4(() => {
2025
+ useEffect5(() => {
1472
2026
  mountedRef.current = true;
1473
2027
  return () => {
1474
2028
  mountedRef.current = false;
1475
2029
  };
1476
2030
  }, []);
1477
- const teardownAudio = useCallback4(() => {
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 = useCallback4(() => {
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
- useEffect4(() => stop, [stop]);
1515
- const startCapture = useCallback4(async (inputSampleRate) => {
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 = useCallback4(async (outputSampleRate) => {
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 = useCallback4(
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 = useCallback4((value) => {
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 = useCallback4(
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 = useCallback4(
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 = useCallback4(async () => {
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 = useCallback4((muted) => {
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 = useCallback4((text) => {
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 = useCallback4((newContext) => {
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 useCallback5, useEffect as useEffect5, useState as useState5 } from "react";
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] = useState5([]);
1802
- const [isLoading, setIsLoading] = useState5(false);
1803
- const [isUploading, setIsUploading] = useState5(false);
1804
- const [error, setError] = useState5(null);
1805
- const fetchFiles = useCallback5(async () => {
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 = useCallback5(
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 = useCallback5(
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 = useCallback5(
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 = useCallback5((fileId) => {
2433
+ const getDownloadUrl = useCallback6((fileId) => {
1880
2434
  return `/files/${fileId}`;
1881
2435
  }, []);
1882
- useEffect5(() => {
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 useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
1902
- function useAgents(autoFetch = true) {
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] = useState6([]);
1905
- const [isLoading, setIsLoading] = useState6(false);
1906
- const [error, setError] = useState6(null);
1907
- const fetchAgents = useCallback6(async () => {
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 res = await fetchWithAuth("/agents");
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 || data?.agents || [];
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
- useEffect6(() => {
1926
- if (autoFetch) {
1927
- void fetchAgents();
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 useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
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] = useState7(null);
1943
- const [isConnected, setIsConnected] = useState7(false);
1944
- const [isLoading, setIsLoading] = useState7(false);
1945
- const checkHealth = useCallback7(async () => {
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
- useEffect7(() => {
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 useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
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] = useState8([]);
1984
- const [isLoading, setIsLoading] = useState8(false);
1985
- const [error, setError] = useState8(null);
1986
- const fetchConnections = useCallback8(async () => {
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
- useEffect8(() => {
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 useCallback9 } from "react";
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 = useCallback9(
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 = useCallback9(
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 useCallback10, useEffect as useEffect9, useState as useState9 } from "react";
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] = useState9([]);
2071
- const [pagination, setPagination] = useState9({
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] = useState9(false);
2078
- const [error, setError] = useState9(null);
2079
- const fetchWorkflows = useCallback10(async () => {
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
- totalPages: data.pagination.totalPages ?? 1
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 = useCallback10(
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
- useEffect9(() => {
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 useCallback11, useEffect as useEffect10, useState as useState10 } from "react";
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] = useState10(null);
2166
- const [versions, setVersions] = useState10([]);
2167
- const [mermaid, setMermaid] = useState10(null);
2168
- const [isLoading, setIsLoading] = useState10(false);
2169
- const [error, setError] = useState10(null);
2170
- const fetchWorkflow = useCallback11(async () => {
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 = useCallback11(async () => {
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 = useCallback11(async () => {
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 = useCallback11(
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 = useCallback11(
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 = useCallback11(async () => {
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
- useEffect10(() => {
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 useCallback12, useEffect as useEffect11, useRef as useRef3, useState as useState11 } from "react";
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] = useState11("idle");
2291
- const [runId, setRunId] = useState11(null);
2292
- const [activeNodeId, setActiveNodeId] = useState11(null);
2293
- const [nodeRuns, setNodeRuns] = useState11(
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] = useState11("");
2297
- const [output, setOutput] = useState11(null);
2298
- const [error, setError] = useState11(null);
2299
- const [events, setEvents] = useState11([]);
2300
- const abortControllerRef = useRef3(null);
2301
- const currentRunIdRef = useRef3(null);
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 = useRef3(options);
3366
+ const optionsRef = useRef4(options);
2304
3367
  optionsRef.current = options;
2305
- const cancel = useCallback12(async () => {
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 = useCallback12(() => {
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 = useCallback12(
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 = useCallback12(
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 = useCallback12(
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
- useEffect11(() => {
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 useCallback13, useEffect as useEffect12, useState as useState12 } from "react";
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] = useState12([]);
2570
- const [pagination, setPagination] = useState12({
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] = useState12(false);
2577
- const [error, setError] = useState12(null);
2578
- const fetchRuns = useCallback13(async () => {
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 = useCallback13(
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 = useCallback13(
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
- useEffect12(() => {
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.9.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,