@personaai/react 0.8.1 → 0.10.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
@@ -133,7 +133,8 @@ function fetchReader(response, controller) {
133
133
  function xhrStream(opts) {
134
134
  return new Promise((resolveStream, rejectStream) => {
135
135
  const xhr = new XMLHttpRequest();
136
- xhr.open("POST", opts.url);
136
+ const method = opts.method ?? "POST";
137
+ xhr.open(method, opts.url);
137
138
  for (const [key, val] of Object.entries(opts.headers))
138
139
  xhr.setRequestHeader(key, val);
139
140
  let consumed = 0;
@@ -232,7 +233,7 @@ function xhrStream(opts) {
232
233
  });
233
234
  }
234
235
  }
235
- xhr.send(opts.body);
236
+ xhr.send(method === "GET" ? null : opts.body ?? null);
236
237
  });
237
238
  }
238
239
  async function openSSEStream(opts) {
@@ -244,10 +245,11 @@ async function openSSEStream(opts) {
244
245
  if (opts.signal.aborted) controller.abort();
245
246
  else opts.signal.addEventListener("abort", () => controller.abort());
246
247
  }
248
+ const method = opts.method ?? "POST";
247
249
  const response = await fetch(opts.url, {
248
- method: "POST",
250
+ method,
249
251
  headers: opts.headers,
250
- body: opts.body,
252
+ body: method === "GET" ? void 0 : opts.body,
251
253
  signal: controller.signal
252
254
  });
253
255
  if (!response.ok) {
@@ -284,7 +286,7 @@ async function openSSEStream(opts) {
284
286
  };
285
287
  }
286
288
 
287
- // src/hooks/useChat.ts
289
+ // src/hooks/streamEventHelpers.ts
288
290
  function isErrorToolContent(content) {
289
291
  if (typeof content !== "string" || !content.trim().startsWith("{"))
290
292
  return false;
@@ -330,6 +332,38 @@ function parsePresentedFile(content) {
330
332
  return null;
331
333
  }
332
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
333
367
  function parseSandboxCommand(tc) {
334
368
  let command = tc.args ?? "";
335
369
  try {
@@ -361,36 +395,6 @@ function parseSandboxCommand(tc) {
361
395
  seq: tc.seq
362
396
  };
363
397
  }
364
- function normalizeWorkspaceFiles(raw) {
365
- const normalized = {};
366
- for (const [path, file] of Object.entries(raw || {})) {
367
- normalized[path] = {
368
- content: file.content,
369
- size: file.size,
370
- createdAt: file.created_at,
371
- modifiedAt: file.modified_at
372
- };
373
- }
374
- return normalized;
375
- }
376
- function normalizePendingInterrupt(pending) {
377
- if (!pending || typeof pending !== "object") return null;
378
- const p = pending;
379
- if (p.kind === "hitl") {
380
- return {
381
- kind: "hitl",
382
- actionRequests: p.value?.actionRequests ?? [],
383
- reviewConfigs: p.value?.reviewConfigs ?? []
384
- };
385
- }
386
- if (p.kind === "clarification") {
387
- return {
388
- kind: "clarification",
389
- questions: p.value?.questions ?? []
390
- };
391
- }
392
- return null;
393
- }
394
398
  function useChat(options = {}) {
395
399
  const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
396
400
  const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
@@ -1022,7 +1026,560 @@ function useChat(options = {}) {
1022
1026
  },
1023
1027
  [sendMessage, chatLogger]
1024
1028
  );
1025
- const reload = useCallback(async () => {
1029
+ const reload = useCallback(async () => {
1030
+ if (messages.length === 0 || isStreaming) {
1031
+ chatLogger.trace("reload skipped", {
1032
+ messageCount: messages.length,
1033
+ isStreaming
1034
+ });
1035
+ return false;
1036
+ }
1037
+ let lastUserIndex = -1;
1038
+ for (let i = messages.length - 1; i >= 0; i--) {
1039
+ if (messages[i].role === "user") {
1040
+ lastUserIndex = i;
1041
+ break;
1042
+ }
1043
+ }
1044
+ if (lastUserIndex === -1) {
1045
+ chatLogger.warn("reload no user message", {});
1046
+ return false;
1047
+ }
1048
+ const lastUserMessage = messages[lastUserIndex];
1049
+ const truncated = messages.slice(0, lastUserIndex);
1050
+ chatLogger.info("reload", { messageId: lastUserMessage.id });
1051
+ chatLogger.debug("reload last user", {
1052
+ preview: lastUserMessage.content.slice(0, 100)
1053
+ });
1054
+ return doSend(lastUserMessage.content, truncated);
1055
+ }, [isStreaming, messages, doSend, chatLogger]);
1056
+ const resumeInterrupt = useCallback(
1057
+ (resume, displayContent) => {
1058
+ chatLogger.info("resumeInterrupt", {
1059
+ kind: resume.decisions ? "hitl" : "clarification",
1060
+ preview: displayContent.slice(0, 50)
1061
+ });
1062
+ return sendMessage(displayContent, { resume });
1063
+ },
1064
+ [sendMessage, chatLogger]
1065
+ );
1066
+ const dismissPresentedFile = useCallback(() => {
1067
+ chatLogger.debug("dismissPresentedFile", {});
1068
+ setPresentedFile(null);
1069
+ }, [chatLogger]);
1070
+ const openWorkspaceFile = useCallback(
1071
+ (path) => {
1072
+ chatLogger.debug("openWorkspaceFile", { path });
1073
+ setPresentedFile({
1074
+ path,
1075
+ title: path.split("/").pop() || path,
1076
+ description: ""
1077
+ });
1078
+ },
1079
+ [chatLogger]
1080
+ );
1081
+ return {
1082
+ messages,
1083
+ input,
1084
+ setInput,
1085
+ handleInputChange,
1086
+ handleSubmit,
1087
+ sendMessage,
1088
+ isStreaming,
1089
+ isLoading: isStreaming,
1090
+ isLoadingHistory,
1091
+ error,
1092
+ interrupt,
1093
+ resumeInterrupt,
1094
+ files,
1095
+ todos,
1096
+ presentedFile,
1097
+ dismissPresentedFile,
1098
+ openWorkspaceFile,
1099
+ sandboxCommands,
1100
+ stop,
1101
+ reload,
1102
+ clear,
1103
+ startNewChat,
1104
+ currentThreadId: effectiveThreadId,
1105
+ isEphemeral: !effectiveThreadId,
1106
+ setMessages,
1107
+ loadThreadMessages
1108
+ };
1109
+ }
1110
+
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);
1141
+ const [error, setError] = useState2(null);
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([]);
1167
+ setError(null);
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 });
1244
+ }
1245
+ },
1246
+ [fetchWithAuth, chatLogger]
1247
+ );
1248
+ useEffect2(() => {
1249
+ if (!effectiveThreadId || isStreaming) {
1250
+ chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
1251
+ return;
1252
+ }
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 () => {
1026
1583
  if (messages.length === 0 || isStreaming) {
1027
1584
  chatLogger.trace("reload skipped", {
1028
1585
  messageCount: messages.length,
@@ -1049,7 +1606,7 @@ function useChat(options = {}) {
1049
1606
  });
1050
1607
  return doSend(lastUserMessage.content, truncated);
1051
1608
  }, [isStreaming, messages, doSend, chatLogger]);
1052
- const resumeInterrupt = useCallback(
1609
+ const resumeInterrupt = useCallback2(
1053
1610
  (resume, displayContent) => {
1054
1611
  chatLogger.info("resumeInterrupt", {
1055
1612
  kind: resume.decisions ? "hitl" : "clarification",
@@ -1059,11 +1616,11 @@ function useChat(options = {}) {
1059
1616
  },
1060
1617
  [sendMessage, chatLogger]
1061
1618
  );
1062
- const dismissPresentedFile = useCallback(() => {
1619
+ const dismissPresentedFile = useCallback2(() => {
1063
1620
  chatLogger.debug("dismissPresentedFile", {});
1064
1621
  setPresentedFile(null);
1065
1622
  }, [chatLogger]);
1066
- const openWorkspaceFile = useCallback(
1623
+ const openWorkspaceFile = useCallback2(
1067
1624
  (path) => {
1068
1625
  chatLogger.debug("openWorkspaceFile", { path });
1069
1626
  setPresentedFile({
@@ -1092,7 +1649,6 @@ function useChat(options = {}) {
1092
1649
  presentedFile,
1093
1650
  dismissPresentedFile,
1094
1651
  openWorkspaceFile,
1095
- sandboxCommands,
1096
1652
  stop,
1097
1653
  reload,
1098
1654
  clear,
@@ -1105,16 +1661,16 @@ function useChat(options = {}) {
1105
1661
  }
1106
1662
 
1107
1663
  // src/hooks/useMemory.ts
1108
- import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
1664
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
1109
1665
  function useMemory(autoFetch = true) {
1110
1666
  const { fetchWithAuth } = usePersonaContext();
1111
- const [memory, setMemory] = useState2({
1667
+ const [memory, setMemory] = useState3({
1112
1668
  userFiles: [],
1113
1669
  agentMemories: []
1114
1670
  });
1115
- const [isLoading, setIsLoading] = useState2(false);
1116
- const [error, setError] = useState2(null);
1117
- const fetchMemory = useCallback2(async () => {
1671
+ const [isLoading, setIsLoading] = useState3(false);
1672
+ const [error, setError] = useState3(null);
1673
+ const fetchMemory = useCallback3(async () => {
1118
1674
  setIsLoading(true);
1119
1675
  setError(null);
1120
1676
  try {
@@ -1133,7 +1689,7 @@ function useMemory(autoFetch = true) {
1133
1689
  setIsLoading(false);
1134
1690
  }
1135
1691
  }, [fetchWithAuth]);
1136
- const getFile = useCallback2(
1692
+ const getFile = useCallback3(
1137
1693
  async (params) => {
1138
1694
  const query = new URLSearchParams({ path: params.path });
1139
1695
  if (params.scope) query.set("scope", params.scope);
@@ -1145,7 +1701,7 @@ function useMemory(autoFetch = true) {
1145
1701
  },
1146
1702
  [fetchWithAuth]
1147
1703
  );
1148
- const writeFile = useCallback2(
1704
+ const writeFile = useCallback3(
1149
1705
  async (params) => {
1150
1706
  const res = await fetchWithAuth("/memory/file", {
1151
1707
  method: "PUT",
@@ -1160,7 +1716,7 @@ function useMemory(autoFetch = true) {
1160
1716
  },
1161
1717
  [fetchWithAuth, fetchMemory]
1162
1718
  );
1163
- const deleteFile = useCallback2(
1719
+ const deleteFile = useCallback3(
1164
1720
  async (params) => {
1165
1721
  const query = new URLSearchParams({ path: params.path });
1166
1722
  if (params.scope) query.set("scope", params.scope);
@@ -1174,7 +1730,7 @@ function useMemory(autoFetch = true) {
1174
1730
  },
1175
1731
  [fetchWithAuth, fetchMemory]
1176
1732
  );
1177
- useEffect2(() => {
1733
+ useEffect3(() => {
1178
1734
  if (autoFetch) {
1179
1735
  void fetchMemory();
1180
1736
  }
@@ -1191,13 +1747,13 @@ function useMemory(autoFetch = true) {
1191
1747
  }
1192
1748
 
1193
1749
  // src/hooks/useThreads.ts
1194
- import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
1750
+ import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
1195
1751
  function useThreads(autoFetch = true) {
1196
1752
  const { fetchWithAuth, defaultAgentId } = usePersonaContext();
1197
- const [threads, setThreads] = useState3([]);
1198
- const [isLoading, setIsLoading] = useState3(false);
1199
- const [error, setError] = useState3(null);
1200
- const fetchThreads = useCallback3(async () => {
1753
+ const [threads, setThreads] = useState4([]);
1754
+ const [isLoading, setIsLoading] = useState4(false);
1755
+ const [error, setError] = useState4(null);
1756
+ const fetchThreads = useCallback4(async () => {
1201
1757
  setIsLoading(true);
1202
1758
  setError(null);
1203
1759
  try {
@@ -1215,7 +1771,7 @@ function useThreads(autoFetch = true) {
1215
1771
  setIsLoading(false);
1216
1772
  }
1217
1773
  }, [fetchWithAuth]);
1218
- const createThread = useCallback3(
1774
+ const createThread = useCallback4(
1219
1775
  async (agentId) => {
1220
1776
  const targetAgentId = agentId || defaultAgentId;
1221
1777
  const res = await fetchWithAuth("/threads", {
@@ -1232,7 +1788,7 @@ function useThreads(autoFetch = true) {
1232
1788
  },
1233
1789
  [fetchWithAuth, defaultAgentId, fetchThreads]
1234
1790
  );
1235
- const deleteThread = useCallback3(
1791
+ const deleteThread = useCallback4(
1236
1792
  async (threadId) => {
1237
1793
  const res = await fetchWithAuth(`/threads/${threadId}`, {
1238
1794
  method: "DELETE"
@@ -1243,7 +1799,7 @@ function useThreads(autoFetch = true) {
1243
1799
  },
1244
1800
  [fetchWithAuth]
1245
1801
  );
1246
- const bulkDeleteThreads = useCallback3(
1802
+ const bulkDeleteThreads = useCallback4(
1247
1803
  async (threadIds) => {
1248
1804
  const res = await fetchWithAuth("/threads/bulk-delete", {
1249
1805
  method: "POST",
@@ -1260,13 +1816,13 @@ function useThreads(autoFetch = true) {
1260
1816
  },
1261
1817
  [fetchWithAuth]
1262
1818
  );
1263
- const deleteAllThreads = useCallback3(async () => {
1819
+ const deleteAllThreads = useCallback4(async () => {
1264
1820
  const ids = threads.map((t) => t._id);
1265
1821
  for (let i = 0; i < ids.length; i += 100) {
1266
1822
  await bulkDeleteThreads(ids.slice(i, i + 100));
1267
1823
  }
1268
1824
  }, [threads, bulkDeleteThreads]);
1269
- const updateThread = useCallback3(
1825
+ const updateThread = useCallback4(
1270
1826
  async (threadId, input) => {
1271
1827
  const res = await fetchWithAuth(`/threads/${threadId}`, {
1272
1828
  method: "PATCH",
@@ -1284,11 +1840,11 @@ function useThreads(autoFetch = true) {
1284
1840
  },
1285
1841
  [fetchWithAuth]
1286
1842
  );
1287
- const renameThread = useCallback3(
1843
+ const renameThread = useCallback4(
1288
1844
  (threadId, title) => updateThread(threadId, { title }),
1289
1845
  [updateThread]
1290
1846
  );
1291
- const resetThread = useCallback3(
1847
+ const resetThread = useCallback4(
1292
1848
  async (threadId) => {
1293
1849
  const res = await fetchWithAuth(`/threads/${threadId}/reset`, {
1294
1850
  method: "POST"
@@ -1303,7 +1859,7 @@ function useThreads(autoFetch = true) {
1303
1859
  },
1304
1860
  [fetchWithAuth]
1305
1861
  );
1306
- const getThread = useCallback3(
1862
+ const getThread = useCallback4(
1307
1863
  async (threadId) => {
1308
1864
  const res = await fetchWithAuth(`/threads/${threadId}`);
1309
1865
  if (!res.ok) throw new Error(`Failed to fetch thread: ${res.statusText}`);
@@ -1312,7 +1868,7 @@ function useThreads(autoFetch = true) {
1312
1868
  },
1313
1869
  [fetchWithAuth]
1314
1870
  );
1315
- useEffect3(() => {
1871
+ useEffect4(() => {
1316
1872
  if (autoFetch) {
1317
1873
  void fetchThreads();
1318
1874
  }
@@ -1334,7 +1890,7 @@ function useThreads(autoFetch = true) {
1334
1890
  }
1335
1891
 
1336
1892
  // src/hooks/useVoice.ts
1337
- 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";
1338
1894
 
1339
1895
  // src/hooks/voiceWorklets.ts
1340
1896
  var RECORDER_WORKLET_SOURCE = `
@@ -1437,42 +1993,42 @@ function mergeTranscriptText(prev, next) {
1437
1993
  }
1438
1994
  function useVoice(options = {}) {
1439
1995
  const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
1440
- const voiceLogger = useMemo3(() => logger.child("voice"), [logger]);
1996
+ const voiceLogger = useMemo4(() => logger.child("voice"), [logger]);
1441
1997
  const agentId = options.agentId || defaultAgentId;
1442
1998
  const threadId = options.threadId;
1443
1999
  const contextOverride = options.contextOverride;
1444
2000
  const context = options.context;
1445
- const [state, setState] = useState4("idle");
1446
- const [isMuted, setIsMuted] = useState4(false);
1447
- const [transcript, setTranscript] = useState4(
2001
+ const [state, setState] = useState5("idle");
2002
+ const [isMuted, setIsMuted] = useState5(false);
2003
+ const [transcript, setTranscript] = useState5(
1448
2004
  []
1449
2005
  );
1450
- const [partial, setPartial] = useState4(
2006
+ const [partial, setPartial] = useState5(
1451
2007
  null
1452
2008
  );
1453
- const [toolCalls, setToolCalls] = useState4([]);
1454
- const [error, setError] = useState4(null);
1455
- const [endReason, setEndReason] = useState4(
2009
+ const [toolCalls, setToolCalls] = useState5([]);
2010
+ const [error, setError] = useState5(null);
2011
+ const [endReason, setEndReason] = useState5(
1456
2012
  null
1457
2013
  );
1458
- const wsRef = useRef2(null);
1459
- const streamRef = useRef2(null);
1460
- const inputCtxRef = useRef2(null);
1461
- const outputCtxRef = useRef2(null);
1462
- const recorderNodeRef = useRef2(null);
1463
- const playerNodeRef = useRef2(null);
1464
- const acceptedTurnSeqRef = useRef2(0);
1465
- const mountedRef = useRef2(true);
1466
- 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(
1467
2023
  null
1468
2024
  );
1469
- useEffect4(() => {
2025
+ useEffect5(() => {
1470
2026
  mountedRef.current = true;
1471
2027
  return () => {
1472
2028
  mountedRef.current = false;
1473
2029
  };
1474
2030
  }, []);
1475
- const teardownAudio = useCallback4(() => {
2031
+ const teardownAudio = useCallback5(() => {
1476
2032
  try {
1477
2033
  streamRef.current?.getTracks().forEach((track) => track.stop());
1478
2034
  } catch {
@@ -1499,7 +2055,7 @@ function useVoice(options = {}) {
1499
2055
  }
1500
2056
  outputCtxRef.current = null;
1501
2057
  }, []);
1502
- const stop = useCallback4(() => {
2058
+ const stop = useCallback5(() => {
1503
2059
  voiceLogger.debug("stop() called");
1504
2060
  try {
1505
2061
  wsRef.current?.close(1e3, "client_stop");
@@ -1509,8 +2065,8 @@ function useVoice(options = {}) {
1509
2065
  teardownAudio();
1510
2066
  if (mountedRef.current) setState("idle");
1511
2067
  }, [teardownAudio, voiceLogger]);
1512
- useEffect4(() => stop, [stop]);
1513
- const startCapture = useCallback4(async (inputSampleRate) => {
2068
+ useEffect5(() => stop, [stop]);
2069
+ const startCapture = useCallback5(async (inputSampleRate) => {
1514
2070
  const stream = await navigator.mediaDevices.getUserMedia({
1515
2071
  audio: {
1516
2072
  echoCancellation: true,
@@ -1536,7 +2092,7 @@ function useVoice(options = {}) {
1536
2092
  recorderNodeRef.current = recorderNode;
1537
2093
  source.connect(recorderNode);
1538
2094
  }, []);
1539
- const setupPlayback = useCallback4(async (outputSampleRate) => {
2095
+ const setupPlayback = useCallback5(async (outputSampleRate) => {
1540
2096
  const outputCtx = new AudioContext({ sampleRate: outputSampleRate });
1541
2097
  outputCtxRef.current = outputCtx;
1542
2098
  await outputCtx.audioWorklet.addModule(playerWorkletUrl());
@@ -1544,7 +2100,7 @@ function useVoice(options = {}) {
1544
2100
  playerNode.connect(outputCtx.destination);
1545
2101
  playerNodeRef.current = playerNode;
1546
2102
  }, []);
1547
- const upsertToolCall = useCallback4(
2103
+ const upsertToolCall = useCallback5(
1548
2104
  (id, patch) => {
1549
2105
  setToolCalls((prev) => {
1550
2106
  const idx = prev.findIndex((t) => t.id === id);
@@ -1556,7 +2112,7 @@ function useVoice(options = {}) {
1556
2112
  },
1557
2113
  []
1558
2114
  );
1559
- const handleTranscript = useCallback4((value) => {
2115
+ const handleTranscript = useCallback5((value) => {
1560
2116
  const { speaker, text, isFinal, turnSeq } = value;
1561
2117
  if (!isFinal) {
1562
2118
  setPartial({ id: "partial", speaker, text });
@@ -1579,7 +2135,7 @@ function useVoice(options = {}) {
1579
2135
  });
1580
2136
  lastFinalTurnRef.current = { speaker, turnSeq };
1581
2137
  }, []);
1582
- const handleCustomEvent = useCallback4(
2138
+ const handleCustomEvent = useCallback5(
1583
2139
  (name, value) => {
1584
2140
  switch (name) {
1585
2141
  case "voice_session_ready":
@@ -1637,7 +2193,7 @@ function useVoice(options = {}) {
1637
2193
  },
1638
2194
  [handleTranscript, setupPlayback, startCapture, teardownAudio, voiceLogger]
1639
2195
  );
1640
- const handleMessage = useCallback4(
2196
+ const handleMessage = useCallback5(
1641
2197
  (event) => {
1642
2198
  if (typeof event.data !== "string") {
1643
2199
  const buf = event.data;
@@ -1695,7 +2251,7 @@ function useVoice(options = {}) {
1695
2251
  },
1696
2252
  [handleCustomEvent, upsertToolCall, voiceLogger]
1697
2253
  );
1698
- const start = useCallback4(async () => {
2254
+ const start = useCallback5(async () => {
1699
2255
  if (!agentId) {
1700
2256
  const err = new Error(
1701
2257
  "useVoice: no agentId provided and no defaultAgentId set on PersonaProvider"
@@ -1762,17 +2318,17 @@ function useVoice(options = {}) {
1762
2318
  teardownAudio();
1763
2319
  }
1764
2320
  }, [agentId, threadId, contextOverride, context, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
1765
- const mute = useCallback4((muted) => {
2321
+ const mute = useCallback5((muted) => {
1766
2322
  setIsMuted(muted);
1767
2323
  streamRef.current?.getAudioTracks().forEach((track) => {
1768
2324
  track.enabled = !muted;
1769
2325
  });
1770
2326
  }, []);
1771
- const sendText = useCallback4((text) => {
2327
+ const sendText = useCallback5((text) => {
1772
2328
  if (!text?.trim() || wsRef.current?.readyState !== WebSocket.OPEN) return;
1773
2329
  wsRef.current.send(JSON.stringify({ type: "voice.text", text }));
1774
2330
  }, []);
1775
- const updateContext = useCallback4((newContext) => {
2331
+ const updateContext = useCallback5((newContext) => {
1776
2332
  if (wsRef.current?.readyState !== WebSocket.OPEN) return;
1777
2333
  wsRef.current.send(JSON.stringify({ type: "voice.context", context: newContext }));
1778
2334
  }, []);
@@ -1793,14 +2349,14 @@ function useVoice(options = {}) {
1793
2349
  }
1794
2350
 
1795
2351
  // src/hooks/useFiles.ts
1796
- 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";
1797
2353
  function useFiles(autoFetch = true) {
1798
2354
  const { fetchWithAuth } = usePersonaContext();
1799
- const [files, setFiles] = useState5([]);
1800
- const [isLoading, setIsLoading] = useState5(false);
1801
- const [isUploading, setIsUploading] = useState5(false);
1802
- const [error, setError] = useState5(null);
1803
- 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 () => {
1804
2360
  setIsLoading(true);
1805
2361
  setError(null);
1806
2362
  try {
@@ -1818,7 +2374,7 @@ function useFiles(autoFetch = true) {
1818
2374
  setIsLoading(false);
1819
2375
  }
1820
2376
  }, [fetchWithAuth]);
1821
- const uploadFile = useCallback5(
2377
+ const uploadFile = useCallback6(
1822
2378
  async (fileOrFormData) => {
1823
2379
  setIsUploading(true);
1824
2380
  setError(null);
@@ -1851,7 +2407,7 @@ function useFiles(autoFetch = true) {
1851
2407
  },
1852
2408
  [fetchWithAuth, fetchFiles]
1853
2409
  );
1854
- const deleteFile = useCallback5(
2410
+ const deleteFile = useCallback6(
1855
2411
  async (fileId) => {
1856
2412
  const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
1857
2413
  if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
@@ -1859,7 +2415,7 @@ function useFiles(autoFetch = true) {
1859
2415
  },
1860
2416
  [fetchWithAuth]
1861
2417
  );
1862
- const bulkDeleteFiles = useCallback5(
2418
+ const bulkDeleteFiles = useCallback6(
1863
2419
  async (fileIds) => {
1864
2420
  const res = await fetchWithAuth("/files/bulk-delete", {
1865
2421
  method: "POST",
@@ -1874,10 +2430,10 @@ function useFiles(autoFetch = true) {
1874
2430
  },
1875
2431
  [fetchWithAuth]
1876
2432
  );
1877
- const getDownloadUrl = useCallback5((fileId) => {
2433
+ const getDownloadUrl = useCallback6((fileId) => {
1878
2434
  return `/files/${fileId}`;
1879
2435
  }, []);
1880
- useEffect5(() => {
2436
+ useEffect6(() => {
1881
2437
  if (autoFetch) {
1882
2438
  void fetchFiles();
1883
2439
  }
@@ -1896,13 +2452,13 @@ function useFiles(autoFetch = true) {
1896
2452
  }
1897
2453
 
1898
2454
  // src/hooks/useAgents.ts
1899
- import { useCallback as useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
2455
+ import { useCallback as useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
1900
2456
  function useAgents(autoFetch = true) {
1901
2457
  const { fetchWithAuth } = usePersonaContext();
1902
- const [agents, setAgents] = useState6([]);
1903
- const [isLoading, setIsLoading] = useState6(false);
1904
- const [error, setError] = useState6(null);
1905
- const fetchAgents = useCallback6(async () => {
2458
+ const [agents, setAgents] = useState7([]);
2459
+ const [isLoading, setIsLoading] = useState7(false);
2460
+ const [error, setError] = useState7(null);
2461
+ const fetchAgents = useCallback7(async () => {
1906
2462
  setIsLoading(true);
1907
2463
  setError(null);
1908
2464
  try {
@@ -1920,7 +2476,7 @@ function useAgents(autoFetch = true) {
1920
2476
  setIsLoading(false);
1921
2477
  }
1922
2478
  }, [fetchWithAuth]);
1923
- useEffect6(() => {
2479
+ useEffect7(() => {
1924
2480
  if (autoFetch) {
1925
2481
  void fetchAgents();
1926
2482
  }
@@ -1934,13 +2490,13 @@ function useAgents(autoFetch = true) {
1934
2490
  }
1935
2491
 
1936
2492
  // src/hooks/useConnection.ts
1937
- import { useCallback as useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
2493
+ import { useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
1938
2494
  function useConnection(autoCheck = true) {
1939
2495
  const { fetchWithAuth } = usePersonaContext();
1940
- const [health, setHealth] = useState7(null);
1941
- const [isConnected, setIsConnected] = useState7(false);
1942
- const [isLoading, setIsLoading] = useState7(false);
1943
- const checkHealth = useCallback7(async () => {
2496
+ const [health, setHealth] = useState8(null);
2497
+ const [isConnected, setIsConnected] = useState8(false);
2498
+ const [isLoading, setIsLoading] = useState8(false);
2499
+ const checkHealth = useCallback8(async () => {
1944
2500
  setIsLoading(true);
1945
2501
  try {
1946
2502
  const res = await fetchWithAuth("/health");
@@ -1959,7 +2515,7 @@ function useConnection(autoCheck = true) {
1959
2515
  setIsLoading(false);
1960
2516
  }
1961
2517
  }, [fetchWithAuth]);
1962
- useEffect7(() => {
2518
+ useEffect8(() => {
1963
2519
  if (autoCheck) {
1964
2520
  void checkHealth();
1965
2521
  }
@@ -1973,15 +2529,15 @@ function useConnection(autoCheck = true) {
1973
2529
  }
1974
2530
 
1975
2531
  // src/hooks/useMcpConnections.ts
1976
- import { useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
2532
+ import { useCallback as useCallback9, useEffect as useEffect9, useState as useState9 } from "react";
1977
2533
  function useMcpConnections(options = {}) {
1978
2534
  const { defaultAgentId, fetchWithAuth } = usePersonaContext();
1979
2535
  const agentId = options.agentId ?? defaultAgentId;
1980
2536
  const autoFetch = options.autoFetch ?? true;
1981
- const [connections, setConnections] = useState8([]);
1982
- const [isLoading, setIsLoading] = useState8(false);
1983
- const [error, setError] = useState8(null);
1984
- const fetchConnections = useCallback8(async () => {
2537
+ const [connections, setConnections] = useState9([]);
2538
+ const [isLoading, setIsLoading] = useState9(false);
2539
+ const [error, setError] = useState9(null);
2540
+ const fetchConnections = useCallback9(async () => {
1985
2541
  if (!agentId) return [];
1986
2542
  setIsLoading(true);
1987
2543
  setError(null);
@@ -2005,7 +2561,7 @@ function useMcpConnections(options = {}) {
2005
2561
  setIsLoading(false);
2006
2562
  }
2007
2563
  }, [agentId, fetchWithAuth, options.returnTo]);
2008
- useEffect8(() => {
2564
+ useEffect9(() => {
2009
2565
  if (autoFetch) void fetchConnections();
2010
2566
  }, [autoFetch, fetchConnections]);
2011
2567
  return {
@@ -2019,11 +2575,11 @@ function useMcpConnections(options = {}) {
2019
2575
  }
2020
2576
 
2021
2577
  // src/hooks/useMcp.ts
2022
- import { useCallback as useCallback9 } from "react";
2578
+ import { useCallback as useCallback10 } from "react";
2023
2579
  function useMcp(options = {}) {
2024
2580
  const { fetchWithAuth } = usePersonaContext();
2025
2581
  const defaultMcpId = options.mcpId;
2026
- const readResource = useCallback9(
2582
+ const readResource = useCallback10(
2027
2583
  async (uri, mcpId) => {
2028
2584
  const id = mcpId ?? defaultMcpId;
2029
2585
  if (!id) throw new Error("MCP server ID is required to read resource");
@@ -2037,7 +2593,7 @@ function useMcp(options = {}) {
2037
2593
  },
2038
2594
  [fetchWithAuth, defaultMcpId]
2039
2595
  );
2040
- const callTool = useCallback9(
2596
+ const callTool = useCallback10(
2041
2597
  async (name, args, mcpId) => {
2042
2598
  const id = mcpId ?? defaultMcpId;
2043
2599
  if (!id) throw new Error("MCP server ID is required to call tool");
@@ -2059,6 +2615,592 @@ function useMcp(options = {}) {
2059
2615
  };
2060
2616
  }
2061
2617
 
2618
+ // src/hooks/useWorkflows.ts
2619
+ import { useCallback as useCallback11, useEffect as useEffect10, useState as useState10 } from "react";
2620
+ function useWorkflows(options) {
2621
+ const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
2622
+ const { autoFetch = true, search, status, page, limit } = opts;
2623
+ const { fetchWithAuth } = usePersonaContext();
2624
+ const [workflows, setWorkflows] = useState10([]);
2625
+ const [pagination, setPagination] = useState10({
2626
+ page: page ?? 1,
2627
+ limit: limit ?? 20,
2628
+ total: 0,
2629
+ totalPages: 0
2630
+ });
2631
+ const [isLoading, setIsLoading] = useState10(false);
2632
+ const [error, setError] = useState10(null);
2633
+ const fetchWorkflows = useCallback11(async () => {
2634
+ setIsLoading(true);
2635
+ setError(null);
2636
+ try {
2637
+ const params = new URLSearchParams();
2638
+ if (search) params.set("search", search);
2639
+ if (status) params.set("status", status);
2640
+ if (page) params.set("page", String(page));
2641
+ if (limit) params.set("limit", String(limit));
2642
+ const queryStr = params.toString() ? `?${params.toString()}` : "";
2643
+ const res = await fetchWithAuth(`/workflows${queryStr}`);
2644
+ if (!res.ok) {
2645
+ throw new Error(`Failed to list workflows: ${res.statusText}`);
2646
+ }
2647
+ const data = await res.json();
2648
+ const items = Array.isArray(data) ? data : data?.items || data?.workflows || [];
2649
+ setWorkflows(items);
2650
+ if (data?.pagination) {
2651
+ setPagination({
2652
+ page: data.pagination.page ?? 1,
2653
+ limit: data.pagination.limit ?? items.length,
2654
+ total: data.pagination.total ?? items.length,
2655
+ totalPages: data.pagination.totalPages ?? 1
2656
+ });
2657
+ } else {
2658
+ setPagination({
2659
+ page: 1,
2660
+ limit: items.length,
2661
+ total: items.length,
2662
+ totalPages: 1
2663
+ });
2664
+ }
2665
+ return items;
2666
+ } catch (err) {
2667
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2668
+ setError(errorObj);
2669
+ return [];
2670
+ } finally {
2671
+ setIsLoading(false);
2672
+ }
2673
+ }, [fetchWithAuth, search, status, page, limit]);
2674
+ const createWorkflow = useCallback11(
2675
+ async (input) => {
2676
+ setError(null);
2677
+ try {
2678
+ const res = await fetchWithAuth("/workflows", {
2679
+ method: "POST",
2680
+ headers: { "Content-Type": "application/json" },
2681
+ body: JSON.stringify(input)
2682
+ });
2683
+ if (!res.ok) {
2684
+ const errText = await res.text().catch(() => res.statusText);
2685
+ throw new Error(`Failed to create workflow (${res.status}): ${errText}`);
2686
+ }
2687
+ const created = await res.json();
2688
+ setWorkflows((prev) => [created, ...prev]);
2689
+ return created;
2690
+ } catch (err) {
2691
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2692
+ setError(errorObj);
2693
+ throw errorObj;
2694
+ }
2695
+ },
2696
+ [fetchWithAuth]
2697
+ );
2698
+ useEffect10(() => {
2699
+ if (autoFetch) {
2700
+ void fetchWorkflows();
2701
+ }
2702
+ }, [autoFetch, fetchWorkflows]);
2703
+ return {
2704
+ workflows,
2705
+ pagination,
2706
+ isLoading,
2707
+ error,
2708
+ refetch: fetchWorkflows,
2709
+ createWorkflow
2710
+ };
2711
+ }
2712
+
2713
+ // src/hooks/useWorkflow.ts
2714
+ import { useCallback as useCallback12, useEffect as useEffect11, useState as useState11 } from "react";
2715
+ function useWorkflow(workflowId, options) {
2716
+ const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
2717
+ const { autoFetch = true } = opts;
2718
+ const { fetchWithAuth } = usePersonaContext();
2719
+ const [workflow, setWorkflow] = useState11(null);
2720
+ const [versions, setVersions] = useState11([]);
2721
+ const [mermaid, setMermaid] = useState11(null);
2722
+ const [isLoading, setIsLoading] = useState11(false);
2723
+ const [error, setError] = useState11(null);
2724
+ const fetchWorkflow = useCallback12(async () => {
2725
+ if (!workflowId) return null;
2726
+ setIsLoading(true);
2727
+ setError(null);
2728
+ try {
2729
+ const res = await fetchWithAuth(`/workflows/${workflowId}`);
2730
+ if (!res.ok) {
2731
+ throw new Error(`Failed to fetch workflow (${res.status}): ${res.statusText}`);
2732
+ }
2733
+ const data = await res.json();
2734
+ setWorkflow(data);
2735
+ return data;
2736
+ } catch (err) {
2737
+ const errorObj = err instanceof Error ? err : new Error(String(err));
2738
+ setError(errorObj);
2739
+ return null;
2740
+ } finally {
2741
+ setIsLoading(false);
2742
+ }
2743
+ }, [fetchWithAuth, workflowId]);
2744
+ const fetchVersions = useCallback12(async () => {
2745
+ if (!workflowId) return [];
2746
+ try {
2747
+ const res = await fetchWithAuth(`/workflows/${workflowId}/versions`);
2748
+ if (!res.ok) return [];
2749
+ const data = await res.json();
2750
+ const items = Array.isArray(data) ? data : data?.items || [];
2751
+ setVersions(items);
2752
+ return items;
2753
+ } catch {
2754
+ return [];
2755
+ }
2756
+ }, [fetchWithAuth, workflowId]);
2757
+ const fetchMermaid = useCallback12(async () => {
2758
+ if (!workflowId) return "";
2759
+ try {
2760
+ const res = await fetchWithAuth(`/workflows/${workflowId}/mermaid`);
2761
+ if (!res.ok) return "";
2762
+ const data = await res.json();
2763
+ const code = typeof data?.mermaid === "string" ? data.mermaid : String(data ?? "");
2764
+ setMermaid(code);
2765
+ return code;
2766
+ } catch {
2767
+ return "";
2768
+ }
2769
+ }, [fetchWithAuth, workflowId]);
2770
+ const saveDraft = useCallback12(
2771
+ async (draft) => {
2772
+ if (!workflowId) throw new Error("Workflow ID is required");
2773
+ setError(null);
2774
+ const res = await fetchWithAuth(`/workflows/${workflowId}/draft`, {
2775
+ method: "POST",
2776
+ headers: { "Content-Type": "application/json" },
2777
+ body: JSON.stringify({ draft })
2778
+ });
2779
+ if (!res.ok) {
2780
+ const errText = await res.text().catch(() => res.statusText);
2781
+ throw new Error(`Failed to save draft (${res.status}): ${errText}`);
2782
+ }
2783
+ const updated = await res.json();
2784
+ setWorkflow(updated);
2785
+ return updated;
2786
+ },
2787
+ [fetchWithAuth, workflowId]
2788
+ );
2789
+ const publish = useCallback12(
2790
+ async (summary) => {
2791
+ if (!workflowId) throw new Error("Workflow ID is required");
2792
+ setError(null);
2793
+ const res = await fetchWithAuth(`/workflows/${workflowId}/publish`, {
2794
+ method: "POST",
2795
+ headers: { "Content-Type": "application/json" },
2796
+ body: JSON.stringify(summary ? { summary } : {})
2797
+ });
2798
+ if (!res.ok) {
2799
+ const errText = await res.text().catch(() => res.statusText);
2800
+ throw new Error(`Failed to publish workflow (${res.status}): ${errText}`);
2801
+ }
2802
+ const versionResult = await res.json();
2803
+ void fetchWorkflow();
2804
+ void fetchVersions();
2805
+ return versionResult;
2806
+ },
2807
+ [fetchWithAuth, workflowId, fetchWorkflow, fetchVersions]
2808
+ );
2809
+ const deleteWorkflow = useCallback12(async () => {
2810
+ if (!workflowId) throw new Error("Workflow ID is required");
2811
+ setError(null);
2812
+ const res = await fetchWithAuth(`/workflows/${workflowId}`, {
2813
+ method: "DELETE"
2814
+ });
2815
+ if (!res.ok) {
2816
+ throw new Error(`Failed to delete workflow (${res.status}): ${res.statusText}`);
2817
+ }
2818
+ setWorkflow(null);
2819
+ }, [fetchWithAuth, workflowId]);
2820
+ useEffect11(() => {
2821
+ if (autoFetch && workflowId) {
2822
+ void fetchWorkflow();
2823
+ }
2824
+ }, [autoFetch, workflowId, fetchWorkflow]);
2825
+ return {
2826
+ workflow,
2827
+ versions,
2828
+ mermaid,
2829
+ isLoading,
2830
+ error,
2831
+ refetch: fetchWorkflow,
2832
+ fetchVersions,
2833
+ fetchMermaid,
2834
+ saveDraft,
2835
+ publish,
2836
+ deleteWorkflow
2837
+ };
2838
+ }
2839
+
2840
+ // src/hooks/useWorkflowStream.ts
2841
+ import { useCallback as useCallback13, useEffect as useEffect12, useRef as useRef4, useState as useState12 } from "react";
2842
+ function useWorkflowStream(workflowId, options) {
2843
+ const { baseUrl, getAuthToken, fetchWithAuth, logger } = usePersonaContext();
2844
+ const [status, setStatus] = useState12("idle");
2845
+ const [runId, setRunId] = useState12(null);
2846
+ const [activeNodeId, setActiveNodeId] = useState12(null);
2847
+ const [nodeRuns, setNodeRuns] = useState12(
2848
+ {}
2849
+ );
2850
+ const [text, setText] = useState12("");
2851
+ const [output, setOutput] = useState12(null);
2852
+ const [error, setError] = useState12(null);
2853
+ const [events, setEvents] = useState12([]);
2854
+ const abortControllerRef = useRef4(null);
2855
+ const currentRunIdRef = useRef4(null);
2856
+ currentRunIdRef.current = runId;
2857
+ const optionsRef = useRef4(options);
2858
+ optionsRef.current = options;
2859
+ const cancel = useCallback13(async () => {
2860
+ if (abortControllerRef.current) {
2861
+ abortControllerRef.current.abort();
2862
+ abortControllerRef.current = null;
2863
+ }
2864
+ const currentRun = currentRunIdRef.current;
2865
+ if (currentRun) {
2866
+ try {
2867
+ await fetchWithAuth(`/workflows/runs/${currentRun}/cancel`, {
2868
+ method: "POST"
2869
+ });
2870
+ } catch (err) {
2871
+ logger.warn("Failed to send cancel request to workflow run", {
2872
+ runId: currentRun,
2873
+ error: String(err)
2874
+ });
2875
+ }
2876
+ }
2877
+ setStatus("cancelled");
2878
+ setActiveNodeId(null);
2879
+ }, [fetchWithAuth, logger]);
2880
+ const reset = useCallback13(() => {
2881
+ if (abortControllerRef.current) {
2882
+ abortControllerRef.current.abort();
2883
+ abortControllerRef.current = null;
2884
+ }
2885
+ setStatus("idle");
2886
+ setRunId(null);
2887
+ setActiveNodeId(null);
2888
+ setNodeRuns({});
2889
+ setText("");
2890
+ setOutput(null);
2891
+ setError(null);
2892
+ setEvents([]);
2893
+ }, []);
2894
+ const processStream = useCallback13(
2895
+ async (url, method, body, targetRunId) => {
2896
+ if (abortControllerRef.current) {
2897
+ abortControllerRef.current.abort();
2898
+ }
2899
+ const controller = new AbortController();
2900
+ abortControllerRef.current = controller;
2901
+ setStatus("running");
2902
+ setError(null);
2903
+ const token = getAuthToken ? await getAuthToken() : null;
2904
+ const headers = {
2905
+ Accept: "text/event-stream"
2906
+ };
2907
+ if (token) {
2908
+ headers["Authorization"] = `Bearer ${token}`;
2909
+ }
2910
+ if (method === "POST") {
2911
+ headers["Content-Type"] = "application/json";
2912
+ }
2913
+ let assignedRunId = targetRunId ?? null;
2914
+ let accumulatedText = "";
2915
+ let finalOutput = null;
2916
+ try {
2917
+ const stream = await openSSEStream({
2918
+ url,
2919
+ method,
2920
+ headers,
2921
+ body,
2922
+ signal: controller.signal
2923
+ });
2924
+ if (!stream.ok) {
2925
+ throw new Error(
2926
+ stream.errorText || `Stream request failed with status ${stream.status}`
2927
+ );
2928
+ }
2929
+ const runIdHeader = stream.getHeader("x-persona-run-id");
2930
+ if (runIdHeader) {
2931
+ assignedRunId = runIdHeader;
2932
+ setRunId(runIdHeader);
2933
+ }
2934
+ let buffer = "";
2935
+ while (true) {
2936
+ const { done, value } = await stream.reader.read();
2937
+ if (done) break;
2938
+ if (!value) continue;
2939
+ buffer += value;
2940
+ const lines = buffer.split("\n");
2941
+ buffer = lines.pop() ?? "";
2942
+ for (const line of lines) {
2943
+ const trimmed = line.trim();
2944
+ if (!trimmed || trimmed.startsWith(":")) continue;
2945
+ if (trimmed.startsWith("data:")) {
2946
+ const rawData = trimmed.slice(5).trim();
2947
+ if (!rawData || rawData === "[DONE]") continue;
2948
+ try {
2949
+ const event = JSON.parse(rawData);
2950
+ setEvents((prev) => [...prev, event]);
2951
+ optionsRef.current?.onEvent?.(event);
2952
+ if ("runId" in event && typeof event.runId === "string" && !assignedRunId) {
2953
+ assignedRunId = event.runId;
2954
+ setRunId(event.runId);
2955
+ }
2956
+ if (event.type === "TEXT_MESSAGE_CHUNK") {
2957
+ accumulatedText += event.delta;
2958
+ setText((prev) => prev + event.delta);
2959
+ } else if (event.type === "CUSTOM") {
2960
+ if (event.name === "workflow_node_started") {
2961
+ const val = event.value;
2962
+ setActiveNodeId(val.nodeId);
2963
+ setNodeRuns((prev) => ({
2964
+ ...prev,
2965
+ [val.nodeId]: {
2966
+ nodeId: val.nodeId,
2967
+ nodeType: val.nodeType,
2968
+ nodeLabel: val.nodeLabel,
2969
+ status: "running",
2970
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2971
+ input: val.input
2972
+ }
2973
+ }));
2974
+ optionsRef.current?.onNodeStarted?.(val);
2975
+ } else if (event.name === "workflow_node_completed") {
2976
+ const val = event.value;
2977
+ setNodeRuns((prev) => {
2978
+ const existing = prev[val.nodeId];
2979
+ return {
2980
+ ...prev,
2981
+ [val.nodeId]: {
2982
+ nodeId: val.nodeId,
2983
+ nodeType: existing?.nodeType ?? "unknown",
2984
+ nodeLabel: existing?.nodeLabel ?? val.nodeId,
2985
+ status: "completed",
2986
+ startedAt: existing?.startedAt,
2987
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
2988
+ input: existing?.input,
2989
+ output: val.output
2990
+ }
2991
+ };
2992
+ });
2993
+ setActiveNodeId((curr) => curr === val.nodeId ? null : curr);
2994
+ finalOutput = val.output;
2995
+ setOutput(val.output);
2996
+ optionsRef.current?.onNodeCompleted?.(val);
2997
+ } else if (event.name === "workflow_node_failed") {
2998
+ const val = event.value;
2999
+ setNodeRuns((prev) => {
3000
+ const existing = prev[val.nodeId];
3001
+ return {
3002
+ ...prev,
3003
+ [val.nodeId]: {
3004
+ nodeId: val.nodeId,
3005
+ nodeType: existing?.nodeType ?? "unknown",
3006
+ nodeLabel: existing?.nodeLabel ?? val.nodeId,
3007
+ status: "failed",
3008
+ startedAt: existing?.startedAt,
3009
+ finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
3010
+ input: existing?.input,
3011
+ error: val.error
3012
+ }
3013
+ };
3014
+ });
3015
+ setActiveNodeId((curr) => curr === val.nodeId ? null : curr);
3016
+ optionsRef.current?.onNodeFailed?.(val);
3017
+ }
3018
+ } else if (event.type === "RUN_FINISHED") {
3019
+ setStatus("completed");
3020
+ setActiveNodeId(null);
3021
+ optionsRef.current?.onFinish?.({
3022
+ runId: assignedRunId || "",
3023
+ output: finalOutput,
3024
+ text: accumulatedText
3025
+ });
3026
+ } else if (event.type === "RUN_ERROR") {
3027
+ const runErr = new Error(event.message || event.code || "Workflow run error");
3028
+ setStatus("failed");
3029
+ setError(runErr);
3030
+ setActiveNodeId(null);
3031
+ optionsRef.current?.onError?.(runErr);
3032
+ }
3033
+ } catch {
3034
+ }
3035
+ }
3036
+ }
3037
+ }
3038
+ setStatus((prev) => prev === "running" ? "completed" : prev);
3039
+ return finalOutput;
3040
+ } catch (err) {
3041
+ if (controller.signal.aborted) {
3042
+ setStatus("cancelled");
3043
+ return null;
3044
+ }
3045
+ const errorObj = err instanceof Error ? err : new Error(String(err));
3046
+ setStatus("failed");
3047
+ setError(errorObj);
3048
+ setActiveNodeId(null);
3049
+ optionsRef.current?.onError?.(errorObj);
3050
+ throw errorObj;
3051
+ } finally {
3052
+ if (abortControllerRef.current === controller) {
3053
+ abortControllerRef.current = null;
3054
+ }
3055
+ }
3056
+ },
3057
+ [getAuthToken]
3058
+ );
3059
+ const start = useCallback13(
3060
+ async (input, opts) => {
3061
+ const targetWfId = opts?.workflowId || workflowId;
3062
+ if (!targetWfId) {
3063
+ throw new Error("Workflow ID must be provided to start execution");
3064
+ }
3065
+ setNodeRuns({});
3066
+ setText("");
3067
+ setOutput(null);
3068
+ setEvents([]);
3069
+ setActiveNodeId(null);
3070
+ const cleanBase = baseUrl.replace(/\/+$/, "");
3071
+ const url = `${cleanBase}/workflows/${targetWfId}/stream`;
3072
+ const body = JSON.stringify({
3073
+ input: input ?? {},
3074
+ dryRun: opts?.dryRun ?? false
3075
+ });
3076
+ return processStream(url, "POST", body);
3077
+ },
3078
+ [workflowId, baseUrl, processStream]
3079
+ );
3080
+ const resume = useCallback13(
3081
+ async (targetRunId, sinceSeq) => {
3082
+ if (!targetRunId) {
3083
+ throw new Error("Run ID is required to resume stream");
3084
+ }
3085
+ setRunId(targetRunId);
3086
+ const cleanBase = baseUrl.replace(/\/+$/, "");
3087
+ const query = sinceSeq !== void 0 ? `?since=${sinceSeq}` : "";
3088
+ const url = `${cleanBase}/workflows/runs/${targetRunId}/resume${query}`;
3089
+ await processStream(url, "GET", void 0, targetRunId);
3090
+ },
3091
+ [baseUrl, processStream]
3092
+ );
3093
+ useEffect12(() => {
3094
+ return () => {
3095
+ if (abortControllerRef.current) {
3096
+ abortControllerRef.current.abort();
3097
+ }
3098
+ };
3099
+ }, []);
3100
+ return {
3101
+ runId,
3102
+ status,
3103
+ isRunning: status === "running",
3104
+ activeNodeId,
3105
+ nodeRuns,
3106
+ text,
3107
+ output,
3108
+ error,
3109
+ events,
3110
+ start,
3111
+ cancel,
3112
+ resume,
3113
+ reset
3114
+ };
3115
+ }
3116
+
3117
+ // src/hooks/useWorkflowRuns.ts
3118
+ import { useCallback as useCallback14, useEffect as useEffect13, useState as useState13 } from "react";
3119
+ function useWorkflowRuns(workflowId, options) {
3120
+ const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
3121
+ const { autoFetch = true, page = 1, limit = 20 } = opts;
3122
+ const { fetchWithAuth } = usePersonaContext();
3123
+ const [runs, setRuns] = useState13([]);
3124
+ const [pagination, setPagination] = useState13({
3125
+ page,
3126
+ limit,
3127
+ total: 0,
3128
+ totalPages: 0
3129
+ });
3130
+ const [isLoading, setIsLoading] = useState13(false);
3131
+ const [error, setError] = useState13(null);
3132
+ const fetchRuns = useCallback14(async () => {
3133
+ if (!workflowId) return [];
3134
+ setIsLoading(true);
3135
+ setError(null);
3136
+ try {
3137
+ const query = `?page=${page}&limit=${limit}`;
3138
+ const res = await fetchWithAuth(`/workflows/${workflowId}/runs${query}`);
3139
+ if (!res.ok) {
3140
+ throw new Error(`Failed to list workflow runs: ${res.statusText}`);
3141
+ }
3142
+ const data = await res.json();
3143
+ const items = Array.isArray(data) ? data : data?.items || data?.runs || [];
3144
+ setRuns(items);
3145
+ if (data?.pagination) {
3146
+ setPagination({
3147
+ page: data.pagination.page ?? page,
3148
+ limit: data.pagination.limit ?? limit,
3149
+ total: data.pagination.total ?? items.length,
3150
+ totalPages: data.pagination.totalPages ?? 1
3151
+ });
3152
+ }
3153
+ return items;
3154
+ } catch (err) {
3155
+ const errorObj = err instanceof Error ? err : new Error(String(err));
3156
+ setError(errorObj);
3157
+ return [];
3158
+ } finally {
3159
+ setIsLoading(false);
3160
+ }
3161
+ }, [fetchWithAuth, workflowId, page, limit]);
3162
+ const getRun = useCallback14(
3163
+ async (runId) => {
3164
+ if (!runId) throw new Error("Run ID is required");
3165
+ const res = await fetchWithAuth(`/workflows/runs/${runId}`);
3166
+ if (!res.ok) {
3167
+ throw new Error(`Failed to fetch workflow run (${res.status}): ${res.statusText}`);
3168
+ }
3169
+ return await res.json();
3170
+ },
3171
+ [fetchWithAuth]
3172
+ );
3173
+ const cancelRun = useCallback14(
3174
+ async (runId) => {
3175
+ if (!runId) throw new Error("Run ID is required");
3176
+ const res = await fetchWithAuth(`/workflows/runs/${runId}/cancel`, {
3177
+ method: "POST"
3178
+ });
3179
+ if (!res.ok) {
3180
+ throw new Error(`Failed to cancel workflow run (${res.status}): ${res.statusText}`);
3181
+ }
3182
+ setRuns(
3183
+ (prev) => prev.map((r) => r._id === runId ? { ...r, status: "cancelled" } : r)
3184
+ );
3185
+ },
3186
+ [fetchWithAuth]
3187
+ );
3188
+ useEffect13(() => {
3189
+ if (autoFetch && workflowId) {
3190
+ void fetchRuns();
3191
+ }
3192
+ }, [autoFetch, workflowId, fetchRuns]);
3193
+ return {
3194
+ runs,
3195
+ pagination,
3196
+ isLoading,
3197
+ error,
3198
+ refetch: fetchRuns,
3199
+ getRun,
3200
+ cancelRun
3201
+ };
3202
+ }
3203
+
2062
3204
  // src/index.ts
2063
3205
  import {
2064
3206
  createLogger as createLogger2,
@@ -2067,8 +3209,9 @@ import {
2067
3209
  getLogLevel,
2068
3210
  isLevelEnabled
2069
3211
  } from "@personaai/logger";
2070
- var VERSION = "0.8.1";
3212
+ var VERSION = "0.10.0";
2071
3213
  export {
3214
+ ARCHITECT_AGENT_ID,
2072
3215
  PersonaProvider,
2073
3216
  VERSION,
2074
3217
  createLogger2 as createLogger,
@@ -2079,6 +3222,7 @@ export {
2079
3222
  setLogLevel,
2080
3223
  supportsStreamingFetch,
2081
3224
  useAgents,
3225
+ useArchitectChat,
2082
3226
  useChat,
2083
3227
  useConnection,
2084
3228
  useFiles,
@@ -2087,6 +3231,10 @@ export {
2087
3231
  useMemory,
2088
3232
  usePersonaContext,
2089
3233
  useThreads,
2090
- useVoice
3234
+ useVoice,
3235
+ useWorkflow,
3236
+ useWorkflowRuns,
3237
+ useWorkflowStream,
3238
+ useWorkflows
2091
3239
  };
2092
3240
  //# sourceMappingURL=index.js.map