@saadnvd1/agent-os 0.3.738 → 0.3.742

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.
@@ -0,0 +1,68 @@
1
+ import type { ChatContext, ChatItem } from "./events";
2
+ import { getSession, registry, type Listener } from "./registry";
3
+ import { releaseUnwatchedSoon, sendCapabilities } from "./settings";
4
+ import { recentTasks, settle } from "./store";
5
+ import { readPage, toolBody, type ItemPage } from "./page";
6
+ import { listQueue } from "./queued";
7
+ import { runningWorkers } from "./worker/client";
8
+
9
+ function savedContext(sessionId: string): ChatContext | null {
10
+ const raw = getSession(sessionId).chat_context;
11
+ if (!raw) return null;
12
+ try {
13
+ return JSON.parse(raw) as ChatContext;
14
+ } catch {
15
+ return null;
16
+ }
17
+ }
18
+
19
+ // What a watcher is sent of stored items: the live text of what's still
20
+ // streaming, or with no conversation running, nothing left running.
21
+ function present(sessionId: string, items: ChatItem[]): ChatItem[] {
22
+ const live = registry.live.get(sessionId);
23
+ if (live) return items.map((i) => live.streaming.get(i.id) ?? i);
24
+ return runningWorkers().includes(sessionId) ? items : settle(items);
25
+ }
26
+
27
+ // Snapshot then live updates. Returns the unsubscribe function. Every
28
+ // client gets the latest page and asks for older ones.
29
+ export function watchChat(sessionId: string, listener: Listener): () => void {
30
+ const live = registry.live.get(sessionId);
31
+ const page = readPage(sessionId);
32
+ const onPage = new Set(page.items.map((i) => i.id));
33
+ // The composer lists background work still going or just done, even when
34
+ // it started before the page.
35
+ const recent = Date.now() - 60 * 60 * 1000;
36
+ const tasks = recentTasks(sessionId, recent).filter((t) => !onPage.has(t.id));
37
+ listener({
38
+ type: "snapshot",
39
+ items: present(sessionId, page.items),
40
+ cursor: page.cursor,
41
+ hasMore: page.hasMore,
42
+ tasks: present(sessionId, tasks),
43
+ state: live?.state ?? "idle",
44
+ compacting: live?.compacting ?? null,
45
+ modelWait: live?.modelWait ?? null,
46
+ queue: listQueue(sessionId),
47
+ suggestion: getSession(sessionId).chat_suggestion ?? null,
48
+ });
49
+ listener({ type: "context", context: savedContext(sessionId) });
50
+ sendCapabilities(sessionId, listener).catch((error) =>
51
+ console.error(`[chat ${sessionId}] capabilities not sent:`, error)
52
+ );
53
+ let set = registry.listeners.get(sessionId);
54
+ if (!set) registry.listeners.set(sessionId, (set = new Set()));
55
+ set.add(listener);
56
+ return () => {
57
+ set?.delete(listener);
58
+ releaseUnwatchedSoon();
59
+ };
60
+ }
61
+
62
+ // The page before `before`, as the reader scrolls up.
63
+ export function chatHistory(sessionId: string, before: number): ItemPage {
64
+ const page = readPage(sessionId, before);
65
+ return { ...page, items: present(sessionId, page.items) };
66
+ }
67
+
68
+ export const chatToolBody = toolBody;
@@ -16,9 +16,9 @@ import { randomUUID } from "crypto";
16
16
  import os from "os";
17
17
  import { db, type Session } from "../db";
18
18
  import type { CompactFirst } from "./stale";
19
+ import { trackModelWait } from "./model-wait-live";
19
20
  import type {
20
21
  ApprovalDecision,
21
- ChatContext,
22
22
  ChatFile,
23
23
  ChatImage,
24
24
  ChatServerMessage,
@@ -34,26 +34,17 @@ import {
34
34
  type Listener,
35
35
  type Live,
36
36
  } from "./registry";
37
- import {
38
- capsKey,
39
- emitCapabilities,
40
- releaseUnwatchedSoon,
41
- sendCapabilities,
42
- setChatPlan,
43
- } from "./settings";
37
+ import { capsKey, emitCapabilities, setChatPlan } from "./settings";
44
38
  import {
45
39
  endOrphanedTasks,
46
40
  endRunningTasks,
47
41
  getItem,
48
42
  hasItem,
49
43
  itemsOfKind,
50
- recentTasks,
51
44
  sessionsWithRunningTasks,
52
45
  saveItem,
53
- settle,
54
46
  } from "./store";
55
47
  import { noteToolResult } from "../health/env/recordHit";
56
- import { readPage, toolBody, type ItemPage } from "./page";
57
48
  import {
58
49
  deleteQueued,
59
50
  editQueued,
@@ -101,16 +92,6 @@ export {
101
92
  setChatPlan,
102
93
  } from "./settings";
103
94
 
104
- function savedContext(sessionId: string): ChatContext | null {
105
- const raw = getSession(sessionId).chat_context;
106
- if (!raw) return null;
107
- try {
108
- return JSON.parse(raw) as ChatContext;
109
- } catch {
110
- return null;
111
- }
112
- }
113
-
114
95
  const CARRY_OUT = "Carry out the plan.";
115
96
 
116
97
  // Leaves plan mode and starts the plan. The message's id comes from the
@@ -192,6 +173,7 @@ function emitEnded(
192
173
 
193
174
  function onWorkerEvent(sessionId: string, live: Live, e: WorkerEvent): void {
194
175
  track(live, e);
176
+ if (trackModelWait(sessionId, live, e)) return;
195
177
  if (e.type === "item" && e.item.kind === "tool")
196
178
  noteToolResult(sessionId, e.item);
197
179
  if (
@@ -968,55 +950,7 @@ export function chatTaskOutput(
968
950
  return taskOutputTail(taskId, task.outputFile);
969
951
  }
970
952
 
971
- // What a watcher is sent of stored items: the live text of what's still
972
- // streaming, or with no conversation running, nothing left running.
973
- function present(sessionId: string, items: ChatItem[]): ChatItem[] {
974
- const live = registry.live.get(sessionId);
975
- if (live) return items.map((i) => live.streaming.get(i.id) ?? i);
976
- return runningWorkers().includes(sessionId) ? items : settle(items);
977
- }
978
-
979
- // Snapshot then live updates. Returns the unsubscribe function. Every
980
- // client gets the latest page and asks for older ones.
981
- export function watchChat(sessionId: string, listener: Listener): () => void {
982
- const live = registry.live.get(sessionId);
983
- const page = readPage(sessionId);
984
- const onPage = new Set(page.items.map((i) => i.id));
985
- // The composer lists background work still going or just done, even when
986
- // it started before the page.
987
- const recent = Date.now() - 60 * 60 * 1000;
988
- const tasks = recentTasks(sessionId, recent).filter((t) => !onPage.has(t.id));
989
- listener({
990
- type: "snapshot",
991
- items: present(sessionId, page.items),
992
- cursor: page.cursor,
993
- hasMore: page.hasMore,
994
- tasks: present(sessionId, tasks),
995
- state: live?.state ?? "idle",
996
- compacting: live?.compacting ?? null,
997
- queue: listQueue(sessionId),
998
- suggestion: getSession(sessionId).chat_suggestion ?? null,
999
- });
1000
- listener({ type: "context", context: savedContext(sessionId) });
1001
- sendCapabilities(sessionId, listener).catch((error) =>
1002
- console.error(`[chat ${sessionId}] capabilities not sent:`, error)
1003
- );
1004
- let set = registry.listeners.get(sessionId);
1005
- if (!set) registry.listeners.set(sessionId, (set = new Set()));
1006
- set.add(listener);
1007
- return () => {
1008
- set?.delete(listener);
1009
- releaseUnwatchedSoon();
1010
- };
1011
- }
1012
-
1013
- // The page before `before`, as the reader scrolls up.
1014
- export function chatHistory(sessionId: string, before: number): ItemPage {
1015
- const page = readPage(sessionId, before);
1016
- return { ...page, items: present(sessionId, page.items) };
1017
- }
1018
-
1019
- export const chatToolBody = toolBody;
953
+ export { chatHistory, chatToolBody, watchChat } from "./runner-watch";
1020
954
 
1021
955
  // After a restart: reconnect to every worker still running a conversation,
1022
956
  // and send the queues of conversations whose worker is gone.
package/lib/chat/tools.ts CHANGED
@@ -81,3 +81,17 @@ export function clipOutput(text: string): string {
81
81
  ? `${text.slice(0, MAX_TOOL_OUTPUT)}\n… (${text.length - MAX_TOOL_OUTPUT} more characters)`
82
82
  : text;
83
83
  }
84
+
85
+ export const LIVE_OUTPUT_LINES = 200;
86
+
87
+ // A running step's output so far: the last lines, never more than a
88
+ // finished step keeps. The end of a long build is what the reader wants.
89
+ export function tailOutput(text: string): string {
90
+ const lines = text.split("\n");
91
+ let tail =
92
+ lines.length > LIVE_OUTPUT_LINES
93
+ ? lines.slice(-LIVE_OUTPUT_LINES).join("\n")
94
+ : text;
95
+ if (tail.length > MAX_TOOL_OUTPUT) tail = tail.slice(-MAX_TOOL_OUTPUT);
96
+ return tail === text ? tail : `…\n${tail}`;
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saadnvd1/agent-os",
3
- "version": "0.3.738",
3
+ "version": "0.3.742",
4
4
  "description": "Self-hosted web UI for managing Claude Code sessions",
5
5
  "bin": {
6
6
  "agent-os": "scripts/agent-os"