@saadnvd1/agent-os 0.3.512 → 0.3.513

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.
@@ -3,7 +3,7 @@ import { formatTokens } from "./context";
3
3
  import { formatElapsed } from "./elapsed";
4
4
  import { registry, type Live } from "./registry";
5
5
  import { stallThresholdMs } from "./stall";
6
- import { listItems } from "./store";
6
+ import { lastItems, runningItems } from "./store";
7
7
  import type { WorkerEvent } from "./worker/protocol";
8
8
 
9
9
  const lastWhat = (i: ChatItem): string =>
@@ -55,23 +55,17 @@ export function track(live: Live, e: WorkerEvent): void {
55
55
 
56
56
  // After a reattach, what's still running comes from the saved items.
57
57
  export function restoreActivity(sessionId: string, state: ChatState) {
58
- const items = listItems(sessionId);
59
58
  const activity: Live["activity"] = { tools: new Map(), tasks: new Set() };
60
- if (state !== "idle")
61
- activity.turnStartedAt = items.findLast(
62
- (i) => i.kind === "user"
63
- )?.createdAt;
64
59
  if (state !== "idle") {
65
- const last = items.at(-1);
60
+ activity.turnStartedAt = lastItems(sessionId, 1, "user")[0]?.createdAt;
61
+ const last = lastItems(sessionId, 1)[0];
66
62
  activity.lastAt = last?.createdAt ?? activity.turnStartedAt;
67
63
  activity.lastWhat = last ? lastWhat(last) : undefined;
68
- }
69
- for (const i of items) {
70
- if (i.kind === "tool" && i.status === "running" && state !== "idle")
64
+ for (const i of runningItems(sessionId, "tool"))
71
65
  activity.tools.set(i.id, { label: i.title, since: i.createdAt });
72
- if (i.kind === "task" && i.status === "running" && !i.ambient)
73
- activity.tasks.add(i.taskId);
74
66
  }
67
+ for (const i of runningItems(sessionId, "task"))
68
+ if (!i.ambient) activity.tasks.add(i.taskId);
75
69
  return activity;
76
70
  }
77
71
 
@@ -311,7 +311,7 @@ export interface ToolBody {
311
311
  export type ChatServerMessage =
312
312
  | {
313
313
  type: "snapshot";
314
- // Every item, or the latest page for a client that asked (?paged=1).
314
+ // The latest page; older ones come with `history`.
315
315
  items: ChatItem[];
316
316
  // Where older items start, and whether there are any.
317
317
  cursor: number | null;
@@ -38,7 +38,6 @@ import {
38
38
  hasItem,
39
39
  itemsOfKind,
40
40
  recentTasks,
41
- listItems,
42
41
  saveItem,
43
42
  settle,
44
43
  } from "./store";
@@ -851,17 +850,11 @@ function present(sessionId: string, items: ChatItem[]): ChatItem[] {
851
850
  return runningWorkers().includes(sessionId) ? items : settle(items);
852
851
  }
853
852
 
854
- // Snapshot then live updates. Returns the unsubscribe function. `paged`
855
- // clients get the latest page and ask for the rest; others get everything.
856
- export function watchChat(
857
- sessionId: string,
858
- listener: Listener,
859
- paged = false
860
- ): () => void {
853
+ // Snapshot then live updates. Returns the unsubscribe function. Every
854
+ // client gets the latest page and asks for older ones.
855
+ export function watchChat(sessionId: string, listener: Listener): () => void {
861
856
  const live = registry.live.get(sessionId);
862
- const page = paged
863
- ? readPage(sessionId)
864
- : { items: listItems(sessionId), cursor: null, hasMore: false };
857
+ const page = readPage(sessionId);
865
858
  const onPage = new Set(page.items.map((i) => i.id));
866
859
  // The composer lists background work still going or just done, even when
867
860
  // it started before the page.
@@ -102,17 +102,16 @@ export function serveChatSocket(
102
102
  // A linked machine's session: its own AgentOS serves the chat.
103
103
  const link = demo ? null : peerChatLink(sessionId);
104
104
  if (link) {
105
- const paged = params.get("paged");
106
105
  return relayChatSocket(
107
106
  ws,
108
107
  link,
109
- { session: sessionId, ...(paged && { paged }) },
108
+ { session: sessionId, paged: "1" },
110
109
  send,
111
110
  (message) => reply(errorItem(message))
112
111
  );
113
112
  }
114
113
  try {
115
- unwatch = watchChat(sessionId, reply, params.get("paged") === "1");
114
+ unwatch = watchChat(sessionId, reply);
116
115
  } catch (err) {
117
116
  fail(err);
118
117
  if (!closed) ws.close();
package/lib/chat/store.ts CHANGED
@@ -198,22 +198,27 @@ export function recentTasks(
198
198
  .map((r) => JSON.parse(r.data));
199
199
  }
200
200
 
201
- // Tool calls saved as still running.
202
- export function runningTools(
203
- sessionId: string
204
- ): Extract<ChatItem, { kind: "tool" }>[] {
201
+ // Items of a kind saved as still running, read from the running index.
202
+ export function runningItems<K extends "tool" | "task">(
203
+ sessionId: string,
204
+ kind: K
205
+ ): Extract<ChatItem, { kind: K }>[] {
205
206
  flushItems();
206
207
  return (
207
208
  db
208
209
  .prepare(
209
210
  `SELECT data FROM chat_items WHERE session_id = ?
210
- AND kind = 'tool'
211
- AND json_extract(data, '$.status') = 'running'`
211
+ AND kind = ? AND json_extract(data, '$.status') = 'running'
212
+ ORDER BY seq`
212
213
  )
213
- .all(sessionId) as { data: string }[]
214
+ .all(sessionId, kind) as { data: string }[]
214
215
  ).map((r) => JSON.parse(r.data));
215
216
  }
216
217
 
218
+ // Tool calls saved as still running.
219
+ export const runningTools = (sessionId: string) =>
220
+ runningItems(sessionId, "tool");
221
+
217
222
  export function deleteItems(sessionId: string): void {
218
223
  pending.delete(sessionId);
219
224
  seqs.delete(sessionId);
@@ -1617,6 +1617,19 @@ const migrations: Migration[] = [
1617
1617
  `);
1618
1618
  },
1619
1619
  },
1620
+ {
1621
+ id: 70,
1622
+ name: "chat_items_running",
1623
+ up: (db) => {
1624
+ // What is still running (tools, background tasks) is read on every
1625
+ // attach; the index holds only those rows, so it's a lookup, not a
1626
+ // scan that parses every tool output of a long conversation.
1627
+ db.exec(
1628
+ `CREATE INDEX IF NOT EXISTS idx_chat_items_running ON chat_items(session_id, kind, seq)
1629
+ WHERE json_extract(data, '$.status') = 'running'`
1630
+ );
1631
+ },
1632
+ },
1620
1633
  ];
1621
1634
 
1622
1635
  // `upTo`: stop after this id (tests that start from an older database).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.512",
3
+ "version": "0.3.513",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"