claudeck 1.3.1 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudeck",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "type": "module",
5
5
  "description": "A browser-based UI for Claude Code — chat, run workflows, manage MCP servers, track costs, and orchestrate autonomous agents from a local web interface. Installable as a PWA.",
6
6
  "main": "server.js",
@@ -11,6 +11,7 @@
11
11
  "cli.js",
12
12
  "server.js",
13
13
  "db.js",
14
+ "db/",
14
15
  "server/",
15
16
  "public/",
16
17
  "config/",
@@ -3,63 +3,63 @@ import { listTodos, createTodo, updateTodo, archiveTodo, deleteTodo, createBrag,
3
3
 
4
4
  const router = Router();
5
5
 
6
- router.get("/", (req, res) => {
6
+ router.get("/", async (req, res) => {
7
7
  try {
8
8
  const archived = req.query.archived === "1";
9
- const todos = listTodos(archived);
9
+ const todos = await listTodos(archived);
10
10
  res.json(todos);
11
11
  } catch (err) {
12
12
  res.status(500).json({ error: err.message });
13
13
  }
14
14
  });
15
15
 
16
- router.get("/counts", (req, res) => {
16
+ router.get("/counts", async (req, res) => {
17
17
  try {
18
- res.json(getTodoCounts());
18
+ res.json(await getTodoCounts());
19
19
  } catch (err) {
20
20
  res.status(500).json({ error: err.message });
21
21
  }
22
22
  });
23
23
 
24
- router.post("/", (req, res) => {
24
+ router.post("/", async (req, res) => {
25
25
  try {
26
26
  const { text } = req.body;
27
27
  if (!text || typeof text !== "string") {
28
28
  return res.status(400).json({ error: "text is required" });
29
29
  }
30
- const info = createTodo(text.trim());
30
+ const info = await createTodo(text.trim());
31
31
  res.json({ id: info.lastInsertRowid });
32
32
  } catch (err) {
33
33
  res.status(500).json({ error: err.message });
34
34
  }
35
35
  });
36
36
 
37
- router.put("/:id", (req, res) => {
37
+ router.put("/:id", async (req, res) => {
38
38
  try {
39
39
  const id = Number(req.params.id);
40
40
  const { text, done, priority } = req.body;
41
- updateTodo(id, text ?? null, done ?? null, priority ?? null);
41
+ await updateTodo(id, text ?? null, done ?? null, priority ?? null);
42
42
  res.json({ ok: true });
43
43
  } catch (err) {
44
44
  res.status(500).json({ error: err.message });
45
45
  }
46
46
  });
47
47
 
48
- router.put("/:id/archive", (req, res) => {
48
+ router.put("/:id/archive", async (req, res) => {
49
49
  try {
50
50
  const id = Number(req.params.id);
51
51
  const { archived } = req.body;
52
- archiveTodo(id, archived ?? true);
52
+ await archiveTodo(id, archived ?? true);
53
53
  res.json({ ok: true });
54
54
  } catch (err) {
55
55
  res.status(500).json({ error: err.message });
56
56
  }
57
57
  });
58
58
 
59
- router.delete("/:id", (req, res) => {
59
+ router.delete("/:id", async (req, res) => {
60
60
  try {
61
61
  const id = Number(req.params.id);
62
- deleteTodo(id);
62
+ await deleteTodo(id);
63
63
  res.json({ ok: true });
64
64
  } catch (err) {
65
65
  res.status(500).json({ error: err.message });
@@ -67,15 +67,15 @@ router.delete("/:id", (req, res) => {
67
67
  });
68
68
 
69
69
  // ── Brags ──────────────────────────────────────────────
70
- router.get("/brags", (req, res) => {
70
+ router.get("/brags", async (req, res) => {
71
71
  try {
72
- res.json(listBrags());
72
+ res.json(await listBrags());
73
73
  } catch (err) {
74
74
  res.status(500).json({ error: err.message });
75
75
  }
76
76
  });
77
77
 
78
- router.post("/:id/brag", (req, res) => {
78
+ router.post("/:id/brag", async (req, res) => {
79
79
  try {
80
80
  const todoId = Number(req.params.id);
81
81
  const { summary } = req.body;
@@ -86,27 +86,27 @@ router.post("/:id/brag", (req, res) => {
86
86
  return res.status(400).json({ error: "summary must be 500 chars or less" });
87
87
  }
88
88
  // Get the todo text before archiving
89
- const todos = listTodos(false);
89
+ const todos = await listTodos(false);
90
90
  const todo = todos.find(t => t.id === todoId);
91
- const archivedTodos = listTodos(true);
91
+ const archivedTodos = await listTodos(true);
92
92
  const archivedTodo = archivedTodos.find(t => t.id === todoId);
93
93
  const foundTodo = todo || archivedTodo;
94
94
  if (!foundTodo) {
95
95
  return res.status(404).json({ error: "Todo not found" });
96
96
  }
97
- const info = createBrag(todoId, foundTodo.text, summary.trim());
97
+ const info = await createBrag(todoId, foundTodo.text, summary.trim());
98
98
  // Archive the todo
99
- archiveTodo(todoId, true);
99
+ await archiveTodo(todoId, true);
100
100
  res.json({ id: info.lastInsertRowid });
101
101
  } catch (err) {
102
102
  res.status(500).json({ error: err.message });
103
103
  }
104
104
  });
105
105
 
106
- router.delete("/brags/:id", (req, res) => {
106
+ router.delete("/brags/:id", async (req, res) => {
107
107
  try {
108
108
  const id = Number(req.params.id);
109
- deleteBrag(id);
109
+ await deleteBrag(id);
110
110
  res.json({ ok: true });
111
111
  } catch (err) {
112
112
  res.status(500).json({ error: err.message });
@@ -25,6 +25,31 @@
25
25
  gap: 18px;
26
26
  }
27
27
 
28
+ /* ── Load-more indicator (lazy loading) ─────────────── */
29
+ .load-more-indicator {
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: center;
33
+ gap: 8px;
34
+ padding: 10px 0;
35
+ color: var(--text-muted);
36
+ font-size: 0.8rem;
37
+ }
38
+
39
+ .load-more-spinner {
40
+ display: inline-block;
41
+ width: 14px;
42
+ height: 14px;
43
+ border: 2px solid var(--border);
44
+ border-top-color: var(--accent);
45
+ border-radius: 50%;
46
+ animation: load-more-spin 0.6s linear infinite;
47
+ }
48
+
49
+ @keyframes load-more-spin {
50
+ to { transform: rotate(360deg); }
51
+ }
52
+
28
53
  .messages:empty::after {
29
54
  content: "";
30
55
  display: none;
@@ -36,18 +36,35 @@ export async function fetchActiveSessionIds() {
36
36
  return data.activeSessionIds || [];
37
37
  }
38
38
 
39
- export async function fetchMessages(sessionId) {
40
- const res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/messages`);
39
+ function _appendPaginationParams(url, { limit, before } = {}) {
40
+ const params = new URLSearchParams();
41
+ if (limit) params.set("limit", limit);
42
+ if (before) params.set("before", before);
43
+ const qs = params.toString();
44
+ return qs ? `${url}?${qs}` : url;
45
+ }
46
+
47
+ export async function fetchMessages(sessionId, opts) {
48
+ const url = _appendPaginationParams(
49
+ `/api/sessions/${encodeURIComponent(sessionId)}/messages`, opts
50
+ );
51
+ const res = await fetch(url);
41
52
  return res.json();
42
53
  }
43
54
 
44
- export async function fetchMessagesByChatId(sessionId, chatId) {
45
- const res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/messages/${encodeURIComponent(chatId)}`);
55
+ export async function fetchMessagesByChatId(sessionId, chatId, opts) {
56
+ const url = _appendPaginationParams(
57
+ `/api/sessions/${encodeURIComponent(sessionId)}/messages/${encodeURIComponent(chatId)}`, opts
58
+ );
59
+ const res = await fetch(url);
46
60
  return res.json();
47
61
  }
48
62
 
49
- export async function fetchSingleMessages(sessionId) {
50
- const res = await fetch(`/api/sessions/${encodeURIComponent(sessionId)}/messages-single`);
63
+ export async function fetchSingleMessages(sessionId, opts) {
64
+ const url = _appendPaginationParams(
65
+ `/api/sessions/${encodeURIComponent(sessionId)}/messages-single`, opts
66
+ );
67
+ const res = await fetch(url);
51
68
  return res.json();
52
69
  }
53
70
 
@@ -3,6 +3,12 @@ import { $ } from './dom.js';
3
3
  import { getState, setState } from './store.js';
4
4
  import { emit } from './events.js';
5
5
 
6
+ export function subscribeToSession(sessionId) {
7
+ const ws = getState("ws");
8
+ if (!ws || ws.readyState !== 1 || !sessionId) return;
9
+ ws.send(JSON.stringify({ type: "subscribe", sessionId }));
10
+ }
11
+
6
12
  let backoffAttempt = 0;
7
13
  let hasConnectedBefore = false;
8
14
 
@@ -37,6 +43,12 @@ export function connectWebSocket() {
37
43
  hasConnectedBefore = true;
38
44
  emit("ws:connected");
39
45
  }
46
+
47
+ // Subscribe to current session for multi-client broadcast
48
+ const currentSession = getState("sessionId");
49
+ if (currentSession) {
50
+ ws.send(JSON.stringify({ type: "subscribe", sessionId: currentSession }));
51
+ }
40
52
  };
41
53
 
42
54
  ws.onmessage = (event) => {
@@ -399,6 +399,10 @@ function handleServerMessage(msg) {
399
399
  showThinking("Thinking...", pane);
400
400
  break;
401
401
 
402
+ case "user_message":
403
+ addUserMessage(msg.text, pane);
404
+ break;
405
+
402
406
  case "text":
403
407
  appendAssistantText(msg.text, pane);
404
408
  break;
@@ -5,8 +5,12 @@ import { CHAT_IDS } from '../core/constants.js';
5
5
  import { escapeHtml } from '../core/utils.js';
6
6
  import * as api from '../core/api.js';
7
7
  import { panes, enterParallelMode, exitParallelMode } from '../ui/parallel.js';
8
- import { renderMessagesIntoPane, showWhalyPlaceholder } from '../ui/messages.js';
8
+ import { renderMessagesIntoPane, prependOlderMessages, showWhalyPlaceholder, showLoadingIndicator, hideLoadingIndicator } from '../ui/messages.js';
9
9
  import { loadContextGauge } from '../ui/context-gauge.js';
10
+ import { subscribeToSession } from '../core/ws.js';
11
+
12
+ const MESSAGE_PAGE_SIZE = 30;
13
+ const SCROLL_LOAD_THRESHOLD = 150; // px from top to trigger load more
10
14
 
11
15
  const SESSION_STORAGE_KEY = "claudeck-session-id";
12
16
 
@@ -14,6 +18,7 @@ const SESSION_STORAGE_KEY = "claudeck-session-id";
14
18
  onState("sessionId", (val) => {
15
19
  if (val) {
16
20
  localStorage.setItem(SESSION_STORAGE_KEY, val);
21
+ subscribeToSession(val);
17
22
  } else {
18
23
  localStorage.removeItem(SESSION_STORAGE_KEY);
19
24
  }
@@ -228,16 +233,16 @@ export async function deleteSession(id) {
228
233
 
229
234
  export async function loadMessages(sid) {
230
235
  if (getState("parallelMode")) {
231
- for (const chatId of CHAT_IDS) {
232
- loadPaneMessages(sid, chatId);
233
- }
236
+ // Load all panes concurrently instead of sequentially
237
+ await Promise.all(CHAT_IDS.map(chatId => loadPaneMessages(sid, chatId)));
234
238
  return;
235
239
  }
236
240
 
237
241
  const pane = panes.get(null);
238
242
  try {
239
- const messages = await api.fetchSingleMessages(sid);
243
+ const messages = await api.fetchSingleMessages(sid, { limit: MESSAGE_PAGE_SIZE });
240
244
  renderMessagesIntoPane(messages, pane);
245
+ _initPanePagination(pane, messages, "single");
241
246
  loadContextGauge(sid);
242
247
  } catch (err) {
243
248
  console.error("Failed to load messages:", err);
@@ -248,22 +253,109 @@ export async function loadPaneMessages(sid, chatId) {
248
253
  const pane = panes.get(chatId);
249
254
  if (!pane) return;
250
255
  try {
251
- let messages = await api.fetchMessagesByChatId(sid, chatId);
252
-
256
+ let messages;
253
257
  // For Chat 1 (chat-0): also load single-mode messages as fallback
254
258
  if (chatId === CHAT_IDS[0]) {
255
- const singleMessages = await api.fetchSingleMessages(sid);
256
- if (singleMessages.length > 0) {
257
- messages = [...singleMessages, ...messages].sort((a, b) => a.id - b.id);
259
+ const [chatMsgs, singleMsgs] = await Promise.all([
260
+ api.fetchMessagesByChatId(sid, chatId, { limit: MESSAGE_PAGE_SIZE }),
261
+ api.fetchSingleMessages(sid, { limit: MESSAGE_PAGE_SIZE }),
262
+ ]);
263
+ if (singleMsgs.length > 0) {
264
+ messages = [...singleMsgs, ...chatMsgs].sort((a, b) => a.id - b.id);
265
+ } else {
266
+ messages = chatMsgs;
258
267
  }
268
+ } else {
269
+ messages = await api.fetchMessagesByChatId(sid, chatId, { limit: MESSAGE_PAGE_SIZE });
259
270
  }
260
271
 
261
272
  renderMessagesIntoPane(messages, pane);
273
+ _initPanePagination(pane, messages, chatId === CHAT_IDS[0] ? "chat0" : "chat");
262
274
  } catch (err) {
263
275
  console.error(`Failed to load messages for ${chatId}:`, err);
264
276
  }
265
277
  }
266
278
 
279
+ // ── Lazy-load pagination ────────────────────────────────
280
+
281
+ function _initPanePagination(pane, messages, mode) {
282
+ pane._hasMore = messages.length >= MESSAGE_PAGE_SIZE;
283
+ pane._oldestMessageId = messages.length > 0 ? messages[0].id : null;
284
+ pane._loadingMore = false;
285
+ pane._paginationMode = mode; // "single" | "chat" | "chat0"
286
+
287
+ // Remove any existing scroll listener
288
+ if (pane._scrollHandler) {
289
+ pane.messagesDiv.removeEventListener("scroll", pane._scrollHandler);
290
+ }
291
+
292
+ if (pane._hasMore) {
293
+ pane._scrollHandler = () => _onPaneScroll(pane);
294
+ pane.messagesDiv.addEventListener("scroll", pane._scrollHandler, { passive: true });
295
+ }
296
+ }
297
+
298
+ function _onPaneScroll(pane) {
299
+ if (
300
+ pane.messagesDiv.scrollTop < SCROLL_LOAD_THRESHOLD &&
301
+ pane._hasMore &&
302
+ !pane._loadingMore
303
+ ) {
304
+ _loadMoreMessages(pane);
305
+ }
306
+ }
307
+
308
+ async function _loadMoreMessages(pane) {
309
+ pane._loadingMore = true;
310
+ showLoadingIndicator(pane);
311
+
312
+ const sid = getState("sessionId");
313
+ const before = pane._oldestMessageId;
314
+ const opts = { limit: MESSAGE_PAGE_SIZE, before };
315
+
316
+ try {
317
+ let olderMessages;
318
+
319
+ switch (pane._paginationMode) {
320
+ case "single":
321
+ olderMessages = await api.fetchSingleMessages(sid, opts);
322
+ break;
323
+ case "chat0": {
324
+ // Chat 1 merges chatId + single messages
325
+ const [chatMsgs, singleMsgs] = await Promise.all([
326
+ api.fetchMessagesByChatId(sid, pane.chatId, opts),
327
+ api.fetchSingleMessages(sid, opts),
328
+ ]);
329
+ olderMessages = singleMsgs.length > 0
330
+ ? [...singleMsgs, ...chatMsgs].sort((a, b) => a.id - b.id)
331
+ : chatMsgs;
332
+ break;
333
+ }
334
+ default:
335
+ olderMessages = await api.fetchMessagesByChatId(sid, pane.chatId, opts);
336
+ }
337
+
338
+ if (olderMessages.length === 0) {
339
+ pane._hasMore = false;
340
+ } else {
341
+ pane._oldestMessageId = olderMessages[0].id;
342
+ pane._hasMore = olderMessages.length >= MESSAGE_PAGE_SIZE;
343
+ prependOlderMessages(olderMessages, pane);
344
+ }
345
+ } catch (err) {
346
+ console.error("Failed to load more messages:", err);
347
+ } finally {
348
+ hideLoadingIndicator(pane);
349
+ pane._loadingMore = false;
350
+
351
+ // Detach scroll listener if no more messages
352
+ if (!pane._hasMore && pane._scrollHandler) {
353
+ pane.messagesDiv.removeEventListener("scroll", pane._scrollHandler);
354
+ pane._scrollHandler = null;
355
+ }
356
+ }
357
+ }
358
+
267
359
  // ── Session Context Menu ────────────────────────────────
268
360
  let sessionCtxMenu = null;
269
361
 
@@ -443,3 +443,45 @@ function addForkButton(msgEl, messageId) {
443
443
  btn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="6" y1="3" x2="6" y2="15"/><circle cx="18" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg>`;
444
444
  msgEl.appendChild(btn);
445
445
  }
446
+
447
+ // ── Lazy-loading helpers ────────────────────────────────
448
+
449
+ export function prependOlderMessages(messages, pane) {
450
+ if (!messages || messages.length === 0) return;
451
+
452
+ // Render older messages into a detached container using the same rendering logic
453
+ const tempContainer = document.createElement("div");
454
+ const tempPane = { messagesDiv: tempContainer, currentAssistantMsg: null };
455
+ renderMessagesIntoPane(messages, tempPane);
456
+
457
+ // Capture scroll position before DOM mutation
458
+ const scrollHeightBefore = pane.messagesDiv.scrollHeight;
459
+
460
+ // Move all rendered nodes into the real pane
461
+ const fragment = document.createDocumentFragment();
462
+ while (tempContainer.firstChild) {
463
+ fragment.appendChild(tempContainer.firstChild);
464
+ }
465
+
466
+ // Insert after loading indicator (if present) or at the top
467
+ const indicator = pane.messagesDiv.querySelector(".load-more-indicator");
468
+ const insertRef = indicator ? indicator.nextSibling : pane.messagesDiv.firstChild;
469
+ pane.messagesDiv.insertBefore(fragment, insertRef);
470
+
471
+ // Restore scroll position so the user's view doesn't jump
472
+ const scrollHeightAfter = pane.messagesDiv.scrollHeight;
473
+ pane.messagesDiv.scrollTop += (scrollHeightAfter - scrollHeightBefore);
474
+ }
475
+
476
+ export function showLoadingIndicator(pane) {
477
+ if (pane.messagesDiv.querySelector(".load-more-indicator")) return;
478
+ const el = document.createElement("div");
479
+ el.className = "load-more-indicator";
480
+ el.innerHTML = '<span class="load-more-spinner"></span> Loading older messages\u2026';
481
+ pane.messagesDiv.prepend(el);
482
+ }
483
+
484
+ export function hideLoadingIndicator(pane) {
485
+ const el = pane.messagesDiv.querySelector(".load-more-indicator");
486
+ if (el) el.remove();
487
+ }
@@ -138,11 +138,9 @@ export function enterParallelMode() {
138
138
 
139
139
  const sessionId = getState("sessionId");
140
140
  if (sessionId) {
141
- // Lazy import to avoid circular dependency
141
+ // Lazy import to avoid circular dependency — load all panes concurrently
142
142
  import('../features/sessions.js').then(({ loadPaneMessages }) => {
143
- for (const chatId of CHAT_IDS) {
144
- loadPaneMessages(sessionId, chatId);
145
- }
143
+ Promise.all(CHAT_IDS.map(chatId => loadPaneMessages(sessionId, chatId)));
146
144
  });
147
145
  }
148
146
  }
@@ -40,7 +40,7 @@ import { logNotification } from "./notification-logger.js";
40
40
  * Build the agent system prompt that instructs Claude to work autonomously
41
41
  * toward the given goal.
42
42
  */
43
- function buildAgentPrompt(agentDef, userContext, sharedContext, cwd) {
43
+ async function buildAgentPrompt(agentDef, userContext, sharedContext, cwd) {
44
44
  let prompt = `You are an autonomous AI agent. Work toward the following goal step by step, using any tools available to you.\n\n`;
45
45
  prompt += `## Goal\n${agentDef.goal}\n\n`;
46
46
  if (userContext) {
@@ -48,7 +48,7 @@ function buildAgentPrompt(agentDef, userContext, sharedContext, cwd) {
48
48
  }
49
49
  // Inject persistent memories from previous sessions
50
50
  if (cwd) {
51
- const memoryPrompt = buildAgentMemoryPrompt(cwd, 8);
51
+ const memoryPrompt = await buildAgentMemoryPrompt(cwd, 8);
52
52
  if (memoryPrompt) {
53
53
  prompt += memoryPrompt + '\n\n';
54
54
  console.log(`\n══════ AGENT MEMORY INJECTION ══════`);
@@ -105,7 +105,7 @@ export async function runAgent({
105
105
  const monitorRunId = runId || `single-${Date.now()}`;
106
106
  const effectiveRunType = runType || 'single';
107
107
  try {
108
- recordAgentRunStart(monitorRunId, agentId, agentDef.title, effectiveRunType, parentRunId);
108
+ await recordAgentRunStart(monitorRunId, agentId, agentDef.title, effectiveRunType, parentRunId);
109
109
  } catch (e) { /* ignore duplicates */ }
110
110
 
111
111
  function agentSend(payload) {
@@ -161,8 +161,8 @@ export async function runAgent({
161
161
  if (resumeId) opts.resume = resumeId;
162
162
 
163
163
  // Load shared context from previous agents in this run
164
- const sharedContext = runId ? getAllAgentContext(runId) : [];
165
- const prompt = buildAgentPrompt(agentDef, userContext, sharedContext, cwd);
164
+ const sharedContext = runId ? await getAllAgentContext(runId) : [];
165
+ const prompt = await buildAgentPrompt(agentDef, userContext, sharedContext, cwd);
166
166
  let resolvedSid = clientSid;
167
167
  let claudeSessionId = null;
168
168
  let sessionModel = null;
@@ -185,15 +185,15 @@ export async function runAgent({
185
185
 
186
186
  sessionIds.set(ourSid, claudeSessionId);
187
187
 
188
- if (!getSession(ourSid)) {
189
- createSession(ourSid, claudeSessionId, projectName || "Agent Session", cwd || "");
190
- updateSessionTitle(ourSid, `Agent: ${agentDef.title}`);
188
+ if (!await getSession(ourSid)) {
189
+ await createSession(ourSid, claudeSessionId, projectName || "Agent Session", cwd || "");
190
+ await updateSessionTitle(ourSid, `Agent: ${agentDef.title}`);
191
191
  } else {
192
- updateClaudeSessionId(ourSid, claudeSessionId);
192
+ await updateClaudeSessionId(ourSid, claudeSessionId);
193
193
  }
194
194
 
195
195
  agentSend({ type: "session", sessionId: ourSid });
196
- addMessage(resolvedSid, "user", JSON.stringify({ text: `[Agent: ${agentDef.title}] ${agentDef.goal}` }), null);
196
+ await addMessage(resolvedSid, "user", JSON.stringify({ text: `[Agent: ${agentDef.title}] ${agentDef.goal}` }), null);
197
197
  continue;
198
198
  }
199
199
 
@@ -204,7 +204,7 @@ export async function runAgent({
204
204
  lastAssistantText += (lastAssistantText ? "\n\n" : "") + block.text;
205
205
  agentSend({ type: "text", text: block.text });
206
206
  if (resolvedSid) {
207
- addMessage(resolvedSid, "assistant", JSON.stringify({ text: block.text }), null);
207
+ await addMessage(resolvedSid, "assistant", JSON.stringify({ text: block.text }), null);
208
208
  }
209
209
  } else if (block.type === "tool_use") {
210
210
  turnCount++;
@@ -220,7 +220,7 @@ export async function runAgent({
220
220
  : "",
221
221
  });
222
222
  if (resolvedSid) {
223
- addMessage(resolvedSid, "tool", JSON.stringify({ id: block.id, name: block.name, input: block.input }), null);
223
+ await addMessage(resolvedSid, "tool", JSON.stringify({ id: block.id, name: block.name, input: block.input }), null);
224
224
  }
225
225
  }
226
226
  }
@@ -242,7 +242,7 @@ export async function runAgent({
242
242
  isError: block.is_error || false,
243
243
  });
244
244
  if (resolvedSid) {
245
- addMessage(resolvedSid, "tool_result", JSON.stringify({
245
+ await addMessage(resolvedSid, "tool_result", JSON.stringify({
246
246
  toolUseId: block.tool_use_id,
247
247
  content: text.slice(0, 10000),
248
248
  isError: block.is_error || false,
@@ -266,7 +266,7 @@ export async function runAgent({
266
266
  const resultModel = Object.keys(sdkMsg.modelUsage || {})[0] || sessionModel;
267
267
 
268
268
  if (resolvedSid) {
269
- addCost(resolvedSid, costUsd, durationMs, numTurns, inputTokens, outputTokens, {
269
+ await addCost(resolvedSid, costUsd, durationMs, numTurns, inputTokens, outputTokens, {
270
270
  model: resultModel,
271
271
  stopReason: sdkMsg.subtype,
272
272
  isError: 0,
@@ -280,7 +280,7 @@ export async function runAgent({
280
280
  duration_ms: durationMs,
281
281
  num_turns: numTurns,
282
282
  cost_usd: costUsd,
283
- totalCost: getTotalCost(),
283
+ totalCost: await getTotalCost(),
284
284
  input_tokens: inputTokens,
285
285
  output_tokens: outputTokens,
286
286
  cache_read_tokens: cacheReadTokens,
@@ -301,11 +301,11 @@ export async function runAgent({
301
301
 
302
302
  // Record completion for monitoring
303
303
  try {
304
- recordAgentRunComplete(monitorRunId, agentId, 'completed', numTurns, costUsd, durationMs, inputTokens, outputTokens);
304
+ await recordAgentRunComplete(monitorRunId, agentId, 'completed', numTurns, costUsd, durationMs, inputTokens, outputTokens);
305
305
  } catch (e) { /* ignore */ }
306
306
 
307
307
  // Log notification
308
- logNotification('agent', `Agent "${agentDef.title}" completed`,
308
+ await logNotification('agent', `Agent "${agentDef.title}" completed`,
309
309
  `${numTurns} turns · $${costUsd.toFixed(4)} · ${(durationMs / 1000).toFixed(1)}s`,
310
310
  JSON.stringify({ costUsd, durationMs, inputTokens, outputTokens, turns: numTurns }),
311
311
  resolvedSid, agentId);
@@ -315,7 +315,7 @@ export async function runAgent({
315
315
  const summary = lastAssistantText.length > 4000
316
316
  ? lastAssistantText.slice(0, 4000) + "\n\n[truncated]"
317
317
  : lastAssistantText;
318
- setAgentContext(runId, agentId, "output", summary);
318
+ await setAgentContext(runId, agentId, "output", summary);
319
319
  }
320
320
  } else if (sdkMsg.subtype?.startsWith("error")) {
321
321
  const errMsg = sdkMsg.errors?.join(", ") || "Unknown error";
@@ -329,14 +329,14 @@ export async function runAgent({
329
329
  const resultModel = Object.keys(sdkMsg.modelUsage || {})[0] || sessionModel;
330
330
 
331
331
  if (resolvedSid) {
332
- addCost(resolvedSid, costUsd, durationMs, numTurns, inputTokens, outputTokens, {
332
+ await addCost(resolvedSid, costUsd, durationMs, numTurns, inputTokens, outputTokens, {
333
333
  model: resultModel,
334
334
  stopReason: sdkMsg.subtype,
335
335
  isError: 1,
336
336
  cacheReadTokens,
337
337
  cacheCreationTokens,
338
338
  });
339
- addMessage(resolvedSid, "error", JSON.stringify({ error: errMsg, subtype: sdkMsg.subtype }), null);
339
+ await addMessage(resolvedSid, "error", JSON.stringify({ error: errMsg, subtype: sdkMsg.subtype }), null);
340
340
  }
341
341
 
342
342
  lastAgentMetrics = { durationMs, costUsd, inputTokens, outputTokens, model: resultModel, turns: numTurns, isError: true, error: errMsg };
@@ -345,11 +345,11 @@ export async function runAgent({
345
345
 
346
346
  // Record error for monitoring
347
347
  try {
348
- recordAgentRunComplete(monitorRunId, agentId, 'error', numTurns, costUsd, durationMs, inputTokens, outputTokens, errMsg);
348
+ await recordAgentRunComplete(monitorRunId, agentId, 'error', numTurns, costUsd, durationMs, inputTokens, outputTokens, errMsg);
349
349
  } catch (e) { /* ignore */ }
350
350
 
351
351
  // Log error notification
352
- logNotification('error', `Agent "${agentDef.title}" failed`,
352
+ await logNotification('error', `Agent "${agentDef.title}" failed`,
353
353
  errMsg.slice(0, 200),
354
354
  JSON.stringify({ costUsd, durationMs, error: errMsg }),
355
355
  resolvedSid, agentId);
@@ -361,11 +361,11 @@ export async function runAgent({
361
361
  if (err.name === "AbortError") {
362
362
  agentSend({ type: "agent_aborted", agentId, turn: turnCount });
363
363
  agentSend({ type: "aborted" });
364
- try { recordAgentRunComplete(monitorRunId, agentId, 'aborted', turnCount, 0, 0, 0, 0, 'Aborted'); } catch (e) { /* ignore */ }
364
+ try { await recordAgentRunComplete(monitorRunId, agentId, 'aborted', turnCount, 0, 0, 0, 0, 'Aborted'); } catch (e) { /* ignore */ }
365
365
  } else {
366
366
  agentSend({ type: "agent_error", agentId, error: err.message, turn: turnCount });
367
367
  agentSend({ type: "error", error: err.message });
368
- try { recordAgentRunComplete(monitorRunId, agentId, 'error', turnCount, 0, 0, 0, 0, err.message); } catch (e) { /* ignore */ }
368
+ try { await recordAgentRunComplete(monitorRunId, agentId, 'error', turnCount, 0, 0, 0, 0, err.message); } catch (e) { /* ignore */ }
369
369
  }
370
370
  throw err; // Re-throw so callers (chains, DAGs) know the agent failed
371
371
  } finally {
@@ -413,8 +413,8 @@ export async function runAgent({
413
413
  // Auto-capture memories from agent output
414
414
  if (cwd && lastAssistantText) {
415
415
  try {
416
- const explicitCount = saveExplicitMemories(cwd, lastAssistantText, resolvedSid);
417
- const autoCount = captureMemories(cwd, lastAssistantText, resolvedSid, agentId);
416
+ const explicitCount = await saveExplicitMemories(cwd, lastAssistantText, resolvedSid);
417
+ const autoCount = await captureMemories(cwd, lastAssistantText, resolvedSid, agentId);
418
418
  const totalCaptured = explicitCount + autoCount;
419
419
  if (totalCaptured > 0) {
420
420
  console.log(`Captured ${totalCaptured} memories (${explicitCount} explicit, ${autoCount} auto) from agent ${agentId}`);