@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.
- package/components/Chat/Activity.test.ts +2 -1
- package/components/Chat/Activity.tsx +102 -64
- package/components/Chat/ChatPanel.tsx +4 -0
- package/components/Chat/LiveOutput.test.ts +36 -0
- package/components/Chat/Tools.tsx +42 -5
- package/data/chat/useChat.ts +10 -1
- package/lib/chat/drivers/claude-mapper.ts +10 -0
- package/lib/chat/drivers/claude.ts +9 -1
- package/lib/chat/drivers/edit-guard.ts +311 -0
- package/lib/chat/drivers/opencode-config.ts +111 -0
- package/lib/chat/drivers/opencode-mapper.ts +125 -13
- package/lib/chat/drivers/opencode-plugin.ts +78 -0
- package/lib/chat/drivers/opencode-server.ts +7 -2
- package/lib/chat/drivers/opencode.ts +108 -9
- package/lib/chat/drivers/pi-edit-guard.ts +87 -0
- package/lib/chat/drivers/pi-mapper.ts +74 -1
- package/lib/chat/drivers/pi.ts +82 -3
- package/lib/chat/drivers/transient-retry.ts +87 -0
- package/lib/chat/drivers/turn-items.ts +74 -1
- package/lib/chat/drivers/turn-outcome.ts +48 -1
- package/lib/chat/events.ts +15 -1
- package/lib/chat/model-wait-live.ts +19 -0
- package/lib/chat/model-wait.ts +88 -0
- package/lib/chat/progress.ts +169 -0
- package/lib/chat/registry.ts +5 -0
- package/lib/chat/runner-watch.ts +68 -0
- package/lib/chat/runner.ts +4 -70
- package/lib/chat/tools.ts +14 -0
- package/package.json +1 -1
|
@@ -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;
|
package/lib/chat/runner.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|