@fitchmultz/pi-subagents 0.50.0 → 0.51.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/CHANGELOG.md +51 -2
- package/README.md +36 -5
- package/dist/agent/contracts.js +6 -24
- package/dist/agent/host.js +3 -16
- package/dist/agent/index.js +207 -173
- package/dist/agent/journal-tail.js +115 -156
- package/dist/agent/launch.js +20 -8
- package/dist/agent/legacy.js +65 -23
- package/dist/agent/notifications.js +138 -47
- package/dist/agent/observation-state.js +246 -0
- package/dist/agent/observation-view.js +258 -0
- package/dist/agent/observation.js +346 -0
- package/dist/agent/observe-card.js +243 -0
- package/dist/agent/observe-chrome.js +85 -0
- package/dist/agent/observe-client.js +285 -0
- package/dist/agent/observe-layout.js +65 -0
- package/dist/agent/observe-reading.js +84 -0
- package/dist/agent/observe-screen.js +400 -96
- package/dist/agent/observe-transcript.js +382 -0
- package/dist/agent/observe.js +250 -43
- package/dist/agent/process.js +198 -25
- package/dist/agent/receipts.js +92 -20
- package/dist/agent/runner.js +125 -20
- package/dist/agent/shell.js +2 -1
- package/dist/agent/storage.js +29 -12
- package/dist/agent/tool-schemas.js +20 -0
- package/dist/agent/typebox.js +10 -5
- package/dist/agent/usage.js +0 -115
- package/dist/agent/view.js +46 -16
- package/dist/agent/wake.js +99 -0
- package/dist/build-info.js +1 -1
- package/dist/intercom-client.d.ts +26 -0
- package/dist/intercom-client.js +45 -0
- package/dist/pi-intercom/agent-dir.d.ts +1 -0
- package/dist/pi-intercom/bridge-session.js +2 -1
- package/dist/pi-intercom/broker/broker.js +27 -21
- package/dist/pi-intercom/broker/client-last.d.ts +13 -0
- package/dist/pi-intercom/broker/client-last.js +17 -5
- package/dist/pi-intercom/broker/client.d.ts +60 -0
- package/dist/pi-intercom/broker/client.js +47 -16
- package/dist/pi-intercom/broker/connection.d.ts +29 -0
- package/dist/pi-intercom/broker/framing.d.ts +17 -0
- package/dist/pi-intercom/broker/framing.js +10 -7
- package/dist/pi-intercom/broker/guard.js +23 -0
- package/dist/pi-intercom/broker/last.d.ts +26 -0
- package/dist/pi-intercom/broker/last.js +57 -32
- package/dist/pi-intercom/broker/paths.d.ts +13 -0
- package/dist/pi-intercom/broker/paths.js +35 -12
- package/dist/pi-intercom/broker/pid.js +5 -1
- package/dist/pi-intercom/broker/server-connection.js +19 -0
- package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
- package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
- package/dist/pi-intercom/broker/targets.d.ts +9 -0
- package/dist/pi-intercom/connection.js +10 -2
- package/dist/pi-intercom/identity.js +6 -5
- package/dist/pi-intercom/inbound-delivery.js +16 -60
- package/dist/pi-intercom/inbound-journal.js +83 -6
- package/dist/pi-intercom/inbound-record.js +18 -1
- package/dist/pi-intercom/inbound.js +12 -38
- package/dist/pi-intercom/index.js +3 -0
- package/dist/pi-intercom/last-message.js +4 -16
- package/dist/pi-intercom/lifecycle.js +88 -3
- package/dist/pi-intercom/outbound.js +5 -15
- package/dist/pi-intercom/reply-tracker.js +7 -6
- package/dist/pi-intercom/reply-wait.js +2 -2
- package/dist/pi-intercom/session-targets.d.ts +24 -0
- package/dist/pi-intercom/tool-registration.js +4 -1
- package/dist/pi-intercom/tool-results.js +5 -7
- package/dist/pi-intercom/tool-schemas.js +12 -26
- package/dist/pi-intercom/types.d.ts +212 -0
- package/dist/pi-intercom/types.js +54 -3
- package/dist/pi-intercom/validation.d.ts +12 -0
- package/dist/pi-intercom/validation.js +3 -0
- package/dist/shared/bounded-json.js +151 -0
- package/dist/shared/compact-cards.js +66 -0
- package/dist/shared/journal-frames.js +3 -6
- package/dist/shared/json-projection.js +267 -129
- package/dist/shared/native-journal.js +123 -56
- package/dist/shared/post-exit-stdio-guard.js +120 -7
- package/dist/shared/tui-action.js +29 -0
- package/dist/shared/unknown.d.ts +9 -0
- package/docs/code-quality.md +2 -0
- package/docs/development.md +40 -1
- package/docs/intercom-bridge.md +9 -4
- package/docs/intercom.md +226 -6
- package/docs/performance.md +87 -11
- package/docs/reference.md +157 -20
- package/docs/retired-history-maintenance.md +252 -0
- package/package.json +28 -15
- package/scripts/archive-retired-history.mjs +464 -0
- package/scripts/build.mjs +48 -28
- package/scripts/measure-size.mjs +1 -1
- package/scripts/native-package-smoke.mjs +153 -12
- package/scripts/package-smoke.mjs +80 -11
- package/scripts/prepare.mjs +2 -2
- package/scripts/real-pi-live.mjs +377 -0
- package/scripts/real-pi-processes.mjs +6 -1
- package/scripts/real-pi-smoke.mjs +6 -282
- package/scripts/run-tests.mjs +4 -2
- package/scripts/typescript.mjs +35 -0
- package/skills/pi-intercom/SKILL.md +2 -1
- package/skills/pi-subagents/SKILL.md +23 -5
- package/src/agent/contracts.ts +8 -34
- package/src/agent/host.ts +19 -20
- package/src/agent/index.ts +224 -190
- package/src/agent/journal-tail.ts +141 -153
- package/src/agent/launch.ts +33 -9
- package/src/agent/legacy.ts +85 -31
- package/src/agent/notifications.ts +192 -67
- package/src/agent/observation-state.ts +277 -0
- package/src/agent/observation-view.ts +267 -0
- package/src/agent/observation.ts +405 -0
- package/src/agent/observe-card.ts +304 -0
- package/src/agent/observe-chrome.ts +170 -0
- package/src/agent/observe-client.ts +318 -0
- package/src/agent/observe-layout.ts +79 -0
- package/src/agent/observe-reading.ts +97 -0
- package/src/agent/observe-screen.ts +443 -129
- package/src/agent/observe-transcript.ts +434 -0
- package/src/agent/observe.ts +304 -71
- package/src/agent/process.ts +240 -34
- package/src/agent/receipts.ts +114 -24
- package/src/agent/runner.ts +125 -27
- package/src/agent/shell.ts +2 -1
- package/src/agent/storage.ts +27 -11
- package/src/agent/tool-schemas.ts +33 -0
- package/src/agent/typebox.ts +12 -7
- package/src/agent/usage.ts +0 -130
- package/src/agent/view.ts +70 -20
- package/src/agent/wake.ts +107 -0
- package/src/intercom-client.ts +108 -0
- package/src/pi-intercom/bridge-session.ts +2 -2
- package/src/pi-intercom/broker/broker.ts +27 -23
- package/src/pi-intercom/broker/client-last.ts +25 -12
- package/src/pi-intercom/broker/client.ts +55 -33
- package/src/pi-intercom/broker/framing.ts +11 -8
- package/src/pi-intercom/broker/guard.ts +33 -0
- package/src/pi-intercom/broker/last.ts +86 -33
- package/src/pi-intercom/broker/paths.ts +39 -12
- package/src/pi-intercom/broker/pid.ts +6 -1
- package/src/pi-intercom/broker/server-connection.ts +25 -0
- package/src/pi-intercom/connection.ts +21 -10
- package/src/pi-intercom/identity.ts +8 -5
- package/src/pi-intercom/inbound-delivery.ts +20 -66
- package/src/pi-intercom/inbound-journal.ts +103 -7
- package/src/pi-intercom/inbound-record.ts +21 -4
- package/src/pi-intercom/inbound.ts +18 -49
- package/src/pi-intercom/index.ts +3 -0
- package/src/pi-intercom/last-message.ts +10 -19
- package/src/pi-intercom/lifecycle.ts +117 -14
- package/src/pi-intercom/outbound.ts +7 -16
- package/src/pi-intercom/reply-tracker.ts +8 -6
- package/src/pi-intercom/reply-wait.ts +3 -2
- package/src/pi-intercom/runtime-types.ts +0 -1
- package/src/pi-intercom/tool-registration.ts +4 -1
- package/src/pi-intercom/tool-results.ts +10 -9
- package/src/pi-intercom/tool-schemas.ts +12 -44
- package/src/pi-intercom/types.ts +115 -4
- package/src/pi-intercom/validation.ts +4 -0
- package/src/shared/bounded-json.ts +162 -0
- package/src/shared/compact-cards.ts +118 -0
- package/src/shared/journal-frames.ts +3 -19
- package/src/shared/json-projection.ts +267 -147
- package/src/shared/native-journal.ts +157 -75
- package/src/shared/post-exit-stdio-guard.ts +165 -7
- package/src/shared/tui-action.ts +39 -0
- package/tsconfig.client.json +8 -0
- package/tsconfig.json +1 -0
- package/src/shared/stream-json.d.ts +0 -10
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import type { TUI } from "@earendil-works/pi-tui";
|
|
2
|
+
import { recordAt, errorMessage } from "../shared/unknown.ts";
|
|
3
|
+
import { actionText, actionHints } from "../shared/tui-action.ts";
|
|
4
|
+
import { ConversationJournal, plainText, type ConversationPage } from "./journal-tail.ts";
|
|
5
|
+
import type { NativeRecord } from "../shared/native-journal.ts";
|
|
6
|
+
import type { ObservationClient, ViewedTool } from "./observe-client.ts";
|
|
7
|
+
import { Card, type CardOptions } from "./observe-card.ts";
|
|
8
|
+
import { Container, Text } from "./observe-layout.ts";
|
|
9
|
+
|
|
10
|
+
export interface EntryAnchor {
|
|
11
|
+
id: string;
|
|
12
|
+
offset: number;
|
|
13
|
+
end?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PageRequest {
|
|
17
|
+
requested: number;
|
|
18
|
+
navigation?: -1 | 1;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type TranscriptChange =
|
|
22
|
+
| { kind: "history"; direction: -1 | 1 }
|
|
23
|
+
| { kind: "content"; id: string; direction: -1 | 1 }
|
|
24
|
+
| { kind: "pending" };
|
|
25
|
+
|
|
26
|
+
interface Hydration {
|
|
27
|
+
entry: NativeRecord;
|
|
28
|
+
card: Card;
|
|
29
|
+
request: PageRequest;
|
|
30
|
+
controller: AbortController;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** One mounted conversation page. Frozen cards are never mutated while the reader is above live. */
|
|
34
|
+
export class ObserverTranscript extends Container {
|
|
35
|
+
readonly journal: ConversationJournal;
|
|
36
|
+
readonly positions: Array<{ id: string; start: number; end: number }> = [];
|
|
37
|
+
private readonly cards = new Map<string, Card>();
|
|
38
|
+
private readonly pages = new Map<string, PageRequest>();
|
|
39
|
+
private readonly expanded = new Set<string>();
|
|
40
|
+
private before?: string;
|
|
41
|
+
private start = 0;
|
|
42
|
+
private stop = 0;
|
|
43
|
+
private entries: NativeRecord[] = [];
|
|
44
|
+
private selected?: string;
|
|
45
|
+
height = 0;
|
|
46
|
+
private mounted: Card[] = [];
|
|
47
|
+
waiting = false;
|
|
48
|
+
private revision = "";
|
|
49
|
+
private readonly display: CardOptions;
|
|
50
|
+
private readonly changed: (event: TranscriptChange) => void;
|
|
51
|
+
private readonly loaded: () => void;
|
|
52
|
+
private paused = false;
|
|
53
|
+
private hydration?: Promise<void>;
|
|
54
|
+
private active?: Hydration;
|
|
55
|
+
private readonly jobs = new Map<string, Hydration>();
|
|
56
|
+
private readonly pending = new Map<string, PageRequest>();
|
|
57
|
+
version = 0;
|
|
58
|
+
constructor(options: {
|
|
59
|
+
file: string;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
cwd: string;
|
|
62
|
+
tui: TUI;
|
|
63
|
+
hideThinking: boolean;
|
|
64
|
+
changed: (event: TranscriptChange) => void;
|
|
65
|
+
loaded: () => void;
|
|
66
|
+
}) {
|
|
67
|
+
super();
|
|
68
|
+
this.journal = new ConversationJournal(options.file, options.sessionId);
|
|
69
|
+
this.display = options;
|
|
70
|
+
this.changed = options.changed;
|
|
71
|
+
this.loaded = options.loaded;
|
|
72
|
+
}
|
|
73
|
+
observe(client?: ObservationClient): void {
|
|
74
|
+
const native = this.journal.native;
|
|
75
|
+
native.refresh();
|
|
76
|
+
this.waiting = !native.indexed;
|
|
77
|
+
if (client && !client.settle((id) => native.byId.has(id))) {
|
|
78
|
+
this.waiting = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
private refresh(client?: ObservationClient): boolean {
|
|
82
|
+
this.observe(client);
|
|
83
|
+
const revision = `${this.journal.native.records.length}:${client?.revision ?? -1}:${this.before ?? ""}`;
|
|
84
|
+
if (revision === this.revision) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
this.revision = revision;
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
sync(client?: ObservationClient): boolean {
|
|
91
|
+
if (!this.refresh(client)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const entries = this.journal.entries(client?.leaf);
|
|
95
|
+
this.entries = entries;
|
|
96
|
+
const stop = this.boundary(entries);
|
|
97
|
+
const start = Math.max(0, stop - 24);
|
|
98
|
+
this.start = start;
|
|
99
|
+
this.stop = stop;
|
|
100
|
+
const selected = entries.slice(start, stop);
|
|
101
|
+
this.clear();
|
|
102
|
+
if (start > 0) {
|
|
103
|
+
this.historyRow("↑ Load earlier activity", -1);
|
|
104
|
+
}
|
|
105
|
+
this.mounted = [];
|
|
106
|
+
for (const entry of selected.toReversed()) {
|
|
107
|
+
this.mounted.unshift(
|
|
108
|
+
this.card(entry, this.before === undefined && entry === selected.at(-1)),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
for (const card of this.mounted) {
|
|
112
|
+
this.addChild(card);
|
|
113
|
+
}
|
|
114
|
+
if (this.before === undefined) {
|
|
115
|
+
this.live(client);
|
|
116
|
+
}
|
|
117
|
+
if (this.before !== undefined) {
|
|
118
|
+
this.historyRow("↓ Load later activity", 1);
|
|
119
|
+
}
|
|
120
|
+
this.evict();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
pause(): void {
|
|
124
|
+
this.paused = true;
|
|
125
|
+
this.active?.controller.abort();
|
|
126
|
+
this.jobs.clear();
|
|
127
|
+
this.pending.clear();
|
|
128
|
+
}
|
|
129
|
+
resume(): void {
|
|
130
|
+
if (!this.paused) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.paused = false;
|
|
134
|
+
for (const card of this.mounted) {
|
|
135
|
+
const entry = this.journal.native.byId.get(card.id);
|
|
136
|
+
const request = this.pages.get(card.id) ?? { requested: 0 };
|
|
137
|
+
if (entry && (!card.ready || request.requested !== card.page)) {
|
|
138
|
+
this.load(entry, card, request);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
private card(entry: NativeRecord, latest: boolean): Card {
|
|
143
|
+
const id = entry.value.id;
|
|
144
|
+
let card = this.cards.get(id);
|
|
145
|
+
if (!card) {
|
|
146
|
+
const call = recordAt(entry.value, "message")?.toolCallId;
|
|
147
|
+
const live = typeof call === "string" ? `tool:${call}` : "";
|
|
148
|
+
card = this.cards.get(live) ?? new Card(id);
|
|
149
|
+
this.cards.delete(live);
|
|
150
|
+
card.id = id;
|
|
151
|
+
if (!card.tool && !card.assistant) {
|
|
152
|
+
card.content.addChild(new Text("Loading published content…", 0, 0));
|
|
153
|
+
}
|
|
154
|
+
this.cards.set(id, card);
|
|
155
|
+
}
|
|
156
|
+
if (!card.ready) {
|
|
157
|
+
this.load(entry, card, this.pages.get(id) ?? { requested: latest ? -1 : 0 });
|
|
158
|
+
}
|
|
159
|
+
return card;
|
|
160
|
+
}
|
|
161
|
+
private current({ card, request, controller }: Hydration): boolean {
|
|
162
|
+
return (
|
|
163
|
+
!controller.signal.aborted &&
|
|
164
|
+
this.cards.get(card.id) === card &&
|
|
165
|
+
this.pending.get(card.id) === request
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
private load(entry: NativeRecord, card: Card, request: PageRequest): void {
|
|
169
|
+
if (this.paused || this.pending.get(card.id) === request) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
this.pending.set(card.id, request);
|
|
173
|
+
this.pages.set(card.id, request);
|
|
174
|
+
this.jobs.set(card.id, { entry, card, request, controller: new AbortController() });
|
|
175
|
+
if (this.active?.card === card) {
|
|
176
|
+
this.active.controller.abort();
|
|
177
|
+
}
|
|
178
|
+
this.drain();
|
|
179
|
+
}
|
|
180
|
+
private drain(): void {
|
|
181
|
+
const job = this.jobs.values().next().value;
|
|
182
|
+
if (this.hydration || this.paused || !job) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
this.jobs.delete(job.card.id);
|
|
186
|
+
const { card } = job;
|
|
187
|
+
this.active = job;
|
|
188
|
+
this.hydration = this.hydrate(job)
|
|
189
|
+
.catch((error: unknown) => {
|
|
190
|
+
if (!this.current(job)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
this.pending.delete(card.id);
|
|
194
|
+
card.footer.clear();
|
|
195
|
+
card.footer.addChild(
|
|
196
|
+
new Text(`Content unavailable: ${plainText(errorMessage(error))}`, 0, 0),
|
|
197
|
+
);
|
|
198
|
+
card.invalidate();
|
|
199
|
+
this.version++;
|
|
200
|
+
this.loaded();
|
|
201
|
+
})
|
|
202
|
+
.finally(() => {
|
|
203
|
+
this.hydration = undefined;
|
|
204
|
+
this.active = undefined;
|
|
205
|
+
this.drain();
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
private async hydrate(job: Hydration): Promise<void> {
|
|
209
|
+
if (!this.current(job)) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const { entry, card, request, controller } = job;
|
|
213
|
+
const options = { signal: controller.signal, hideThinking: this.display.hideThinking };
|
|
214
|
+
let page = await this.journal.page(entry, Math.max(0, request.requested), options);
|
|
215
|
+
if (request.requested < 0 && page.pages > 1) {
|
|
216
|
+
page = await this.journal.page(entry, page.pages - 1, options);
|
|
217
|
+
}
|
|
218
|
+
const call =
|
|
219
|
+
typeof page.message.toolCallId === "string" && !card.tool
|
|
220
|
+
? await this.journal.call(page.message.toolCallId, card.id, controller.signal)
|
|
221
|
+
: undefined;
|
|
222
|
+
if (!this.current(job)) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
this.durable(card.id, page, call?.arguments);
|
|
226
|
+
this.pages.set(card.id, { requested: page.page });
|
|
227
|
+
this.pending.delete(card.id);
|
|
228
|
+
this.version++;
|
|
229
|
+
if (request.navigation === undefined) {
|
|
230
|
+
this.loaded();
|
|
231
|
+
} else {
|
|
232
|
+
this.changed({ kind: "content", id: card.id, direction: request.navigation });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
private evict(): void {
|
|
236
|
+
const mounted = new Set(this.mounted.map((card) => card.id));
|
|
237
|
+
for (const id of this.cards.keys()) {
|
|
238
|
+
if (!mounted.has(id)) {
|
|
239
|
+
this.cards.delete(id);
|
|
240
|
+
this.jobs.delete(id);
|
|
241
|
+
this.pending.delete(id);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (this.active && !mounted.has(this.active.card.id)) {
|
|
245
|
+
this.active.controller.abort();
|
|
246
|
+
}
|
|
247
|
+
for (const choices of [this.pages, this.expanded]) {
|
|
248
|
+
for (const id of choices.keys()) {
|
|
249
|
+
if (choices.size > 48 && !mounted.has(id)) {
|
|
250
|
+
choices.delete(id);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
private durable(id: string, page: ConversationPage, args: unknown): void {
|
|
256
|
+
const card = this.cards.get(id);
|
|
257
|
+
if (!card) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
card.expanded ||= this.expanded.has(id);
|
|
261
|
+
card.publish(page, args, this.display);
|
|
262
|
+
if (page.pages > 1) {
|
|
263
|
+
card.footer.addChild(new Text(`Content page ${page.page + 1}/${page.pages}`, 0, 0));
|
|
264
|
+
card.footer.addChild(
|
|
265
|
+
actionHints(
|
|
266
|
+
([-1, 1] as const).map((direction) => ({
|
|
267
|
+
text: direction < 0 ? "Previous page" : "Next page",
|
|
268
|
+
action: () => {
|
|
269
|
+
this.changePage(id, direction);
|
|
270
|
+
},
|
|
271
|
+
})),
|
|
272
|
+
),
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
card.invalidate();
|
|
276
|
+
}
|
|
277
|
+
private changePage(id: string, direction: -1 | 1): boolean {
|
|
278
|
+
const card = this.cards.get(id);
|
|
279
|
+
const entry = this.journal.native.byId.get(id);
|
|
280
|
+
if (!card || !entry) {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
const page = Math.max(0, Math.min(card.pages - 1, card.page + direction));
|
|
284
|
+
if (page === card.page) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
const request = { requested: page, navigation: direction };
|
|
288
|
+
this.pages.set(id, request);
|
|
289
|
+
this.changed({ kind: "pending" });
|
|
290
|
+
this.load(entry, card, request);
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
contentPage(line: number, height: number, direction: -1 | 1): boolean {
|
|
294
|
+
const positions = direction < 0 ? this.positions.toReversed() : this.positions;
|
|
295
|
+
return positions.some((item) => {
|
|
296
|
+
const visible = item.end > line && item.start < line + height;
|
|
297
|
+
const boundary = direction < 0 ? line <= item.start : line + height >= item.end;
|
|
298
|
+
return visible && boundary && this.changePage(item.id, direction);
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
private live(client?: ObservationClient): void {
|
|
302
|
+
if (!client) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const message = client.message;
|
|
306
|
+
if (message) {
|
|
307
|
+
const id = `live:${message.key}`;
|
|
308
|
+
let card = this.cards.get(id);
|
|
309
|
+
if (!card) {
|
|
310
|
+
card = new Card(id);
|
|
311
|
+
this.cards.set(id, card);
|
|
312
|
+
}
|
|
313
|
+
card.stream(message, this.display.hideThinking);
|
|
314
|
+
this.mounted.push(card);
|
|
315
|
+
this.addChild(card);
|
|
316
|
+
}
|
|
317
|
+
this.pendingCalls(client);
|
|
318
|
+
for (const value of client.tools.values()) {
|
|
319
|
+
this.liveTool(value);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
private pendingCalls(client: ObservationClient): void {
|
|
323
|
+
for (const block of client.message?.blocks.filter(Boolean) ?? []) {
|
|
324
|
+
if (
|
|
325
|
+
block.type === "toolCall" &&
|
|
326
|
+
block.id !== undefined &&
|
|
327
|
+
block.name !== undefined &&
|
|
328
|
+
!client.tools.has(block.id)
|
|
329
|
+
) {
|
|
330
|
+
this.liveTool({ id: block.id, name: block.name, args: block.args }, true);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
private liveTool(value: ViewedTool, preparing = false): void {
|
|
335
|
+
const id = `tool:${value.id}`;
|
|
336
|
+
let card = this.cards.get(id);
|
|
337
|
+
if (!card) {
|
|
338
|
+
card = new Card(id);
|
|
339
|
+
card.expanded = this.expanded.has(id);
|
|
340
|
+
this.cards.set(id, card);
|
|
341
|
+
}
|
|
342
|
+
card.updateTool(value, this.display, preparing);
|
|
343
|
+
this.mounted.push(card);
|
|
344
|
+
this.addChild(card);
|
|
345
|
+
}
|
|
346
|
+
private boundary(entries: NativeRecord[]): number {
|
|
347
|
+
const found = entries.findIndex((entry) => entry.value.id === this.before);
|
|
348
|
+
this.before = found < 0 ? undefined : this.before;
|
|
349
|
+
return this.before === undefined || found < 0 ? entries.length : found;
|
|
350
|
+
}
|
|
351
|
+
private historyRow(label: string, direction: -1 | 1): void {
|
|
352
|
+
this.addChild(
|
|
353
|
+
actionText(label, () => {
|
|
354
|
+
this.changed({ kind: "history", direction });
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
history(direction: -1 | 1): string | undefined {
|
|
359
|
+
if ((direction < 0 && this.start === 0) || (direction > 0 && this.before === undefined)) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const anchor =
|
|
363
|
+
direction < 0
|
|
364
|
+
? this.mounted.at(0)
|
|
365
|
+
: this.mounted.findLast((card) => this.journal.native.byId.has(card.id));
|
|
366
|
+
const stop = direction < 0 ? this.start + 1 : this.stop + 23;
|
|
367
|
+
this.before = this.entries[stop]?.value.id;
|
|
368
|
+
return anchor?.id;
|
|
369
|
+
}
|
|
370
|
+
latest(): void {
|
|
371
|
+
this.before = undefined;
|
|
372
|
+
const entry = this.entries.at(-1);
|
|
373
|
+
if (!entry) {
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const card = this.cards.get(entry.value.id);
|
|
377
|
+
if (card?.ready === true && card.page === card.pages - 1) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const request = { requested: -1 };
|
|
381
|
+
this.pages.set(entry.value.id, request);
|
|
382
|
+
if (card) {
|
|
383
|
+
this.load(entry, card, request);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
select(line: number, height: number): boolean {
|
|
387
|
+
const position = this.positions.find(
|
|
388
|
+
(item) =>
|
|
389
|
+
item.end > line &&
|
|
390
|
+
item.start < line + height &&
|
|
391
|
+
this.cards.get(item.id)?.tool !== undefined,
|
|
392
|
+
);
|
|
393
|
+
this.selected = position?.id;
|
|
394
|
+
let changed = false;
|
|
395
|
+
for (const card of this.mounted) {
|
|
396
|
+
const selected = card.id === this.selected;
|
|
397
|
+
if (card.selected !== selected) {
|
|
398
|
+
card.selected = selected;
|
|
399
|
+
card.invalidate();
|
|
400
|
+
changed = true;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return changed;
|
|
404
|
+
}
|
|
405
|
+
toggle(): void {
|
|
406
|
+
this.cards.get(this.selected ?? "")?.toggle();
|
|
407
|
+
}
|
|
408
|
+
anchor(line: number): EntryAnchor | undefined {
|
|
409
|
+
const position = this.positions.find((item) => item.end > line);
|
|
410
|
+
return position && { id: position.id, offset: Math.max(0, line - position.start) };
|
|
411
|
+
}
|
|
412
|
+
line(anchor: EntryAnchor): number {
|
|
413
|
+
const position = this.positions.find((item) => item.id === anchor.id);
|
|
414
|
+
return (anchor.end === true ? (position?.end ?? 0) : (position?.start ?? 0)) + anchor.offset;
|
|
415
|
+
}
|
|
416
|
+
override render(width: number): string[] {
|
|
417
|
+
this.positions.length = 0;
|
|
418
|
+
let start = 0;
|
|
419
|
+
for (const child of this.children) {
|
|
420
|
+
const height = child.render(width).length;
|
|
421
|
+
if (child instanceof Card) {
|
|
422
|
+
this.positions.push({ id: child.id, start, end: start + height });
|
|
423
|
+
if (child.expanded) {
|
|
424
|
+
this.expanded.add(child.id);
|
|
425
|
+
} else {
|
|
426
|
+
this.expanded.delete(child.id);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
start += height;
|
|
430
|
+
}
|
|
431
|
+
this.height = start;
|
|
432
|
+
return super.render(width);
|
|
433
|
+
}
|
|
434
|
+
}
|