@wizardingcode/shibaox-tui 0.1.3 → 0.1.4
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/dist/main.js +8188 -0
- package/dist/main.js.map +67 -0
- package/package.json +8 -12
- package/src/app.tsx +0 -300
- package/src/component/dialogs/confirm.tsx +0 -29
- package/src/component/dialogs/diff.tsx +0 -143
- package/src/component/dialogs/help.tsx +0 -99
- package/src/component/dialogs/keys.tsx +0 -201
- package/src/component/dialogs/palette.tsx +0 -95
- package/src/component/dialogs/runs.tsx +0 -115
- package/src/component/dialogs/tiers.tsx +0 -240
- package/src/component/footer.tsx +0 -66
- package/src/component/logo.tsx +0 -29
- package/src/component/prompt/autocomplete.tsx +0 -44
- package/src/component/prompt/index.tsx +0 -213
- package/src/component/reconnecting.tsx +0 -44
- package/src/component/sidebar.tsx +0 -235
- package/src/component/tabs.tsx +0 -116
- package/src/component/too-small.tsx +0 -15
- package/src/context/client.tsx +0 -54
- package/src/context/commands.tsx +0 -44
- package/src/context/config.tsx +0 -22
- package/src/context/data-state.ts +0 -41
- package/src/context/data.tsx +0 -308
- package/src/context/exit.tsx +0 -13
- package/src/context/keys.tsx +0 -70
- package/src/context/poller.ts +0 -352
- package/src/context/prefs.tsx +0 -54
- package/src/context/route.tsx +0 -23
- package/src/main.tsx +0 -55
- package/src/model/format.ts +0 -38
- package/src/model/prompt-commands.ts +0 -315
- package/src/model/status.ts +0 -55
- package/src/model/stream.ts +0 -464
- package/src/motion/animation.ts +0 -235
- package/src/motion/config.tsx +0 -15
- package/src/motion/fade-in-text.tsx +0 -93
- package/src/motion/masked-text.ts +0 -62
- package/src/motion/one-cell-motion.ts +0 -100
- package/src/motion/pulse.ts +0 -38
- package/src/motion/shimmer-text.tsx +0 -78
- package/src/motion/spinner-renderable.ts +0 -171
- package/src/motion/spinner.tsx +0 -100
- package/src/motion/subcell.ts +0 -40
- package/src/routes/home.tsx +0 -365
- package/src/routes/session/approval-bar.tsx +0 -136
- package/src/routes/session/cards.tsx +0 -495
- package/src/routes/session/context-line.tsx +0 -129
- package/src/routes/session/continue.tsx +0 -104
- package/src/routes/session/index.tsx +0 -340
- package/src/routes/session/request.tsx +0 -87
- package/src/routes/session/timeline.tsx +0 -313
- package/src/testing/fake-client.ts +0 -249
- package/src/theme/context.tsx +0 -44
- package/src/theme/resolve.ts +0 -196
- package/src/theme/shibaox.json +0 -159
- package/src/ui/border.ts +0 -20
- package/src/ui/delayed-presence.ts +0 -23
- package/src/ui/dialog.tsx +0 -123
- package/src/ui/layout.ts +0 -23
- package/src/ui/marquee.ts +0 -75
- package/src/ui/pane-resize.ts +0 -77
- package/src/ui/toast.tsx +0 -203
- package/tsconfig.json +0 -19
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import type { Renderable, ScrollBoxRenderable } from '@opentui/core';
|
|
2
|
-
import { useTerminalDimensions } from '@opentui/solid';
|
|
3
|
-
import {
|
|
4
|
-
type Accessor,
|
|
5
|
-
createEffect,
|
|
6
|
-
createMemo,
|
|
7
|
-
createSignal,
|
|
8
|
-
For,
|
|
9
|
-
getOwner,
|
|
10
|
-
type JSX,
|
|
11
|
-
Match,
|
|
12
|
-
on,
|
|
13
|
-
onCleanup,
|
|
14
|
-
runWithOwner,
|
|
15
|
-
Switch,
|
|
16
|
-
} from 'solid-js';
|
|
17
|
-
import { useData } from '../../context/data.js';
|
|
18
|
-
import { useKeys } from '../../context/keys.js';
|
|
19
|
-
import { type Card, isChatRun } from '../../model/stream.js';
|
|
20
|
-
import { useTheme } from '../../theme/context.js';
|
|
21
|
-
import {
|
|
22
|
-
DecideCard,
|
|
23
|
-
EarlierCard,
|
|
24
|
-
ErrorCard,
|
|
25
|
-
GateCard,
|
|
26
|
-
HumanCard,
|
|
27
|
-
NodeCard,
|
|
28
|
-
SummaryCard,
|
|
29
|
-
} from './cards.js';
|
|
30
|
-
import { RequestBlock } from './request.js';
|
|
31
|
-
|
|
32
|
-
/** How many cards the screen mounts per run (the model keeps up to 5000). */
|
|
33
|
-
export const RENDER_LIMIT = 300;
|
|
34
|
-
|
|
35
|
-
/** Ids of the rows the cursor can land on: card headers and tool lines, in reading order. */
|
|
36
|
-
export function selectableRows(cards: Card[]): string[] {
|
|
37
|
-
const rows: string[] = [];
|
|
38
|
-
for (const c of cards) {
|
|
39
|
-
if (c.kind === 'error' || c.kind === 'earlier') continue;
|
|
40
|
-
if (c.kind === 'summary') {
|
|
41
|
-
rows.push('card:summary');
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
rows.push(`card:${c.nodeId}`);
|
|
45
|
-
if (c.kind === 'node')
|
|
46
|
-
for (const b of c.blocks) if (b.kind === 'tool') rows.push(`tool:${c.nodeId}:${b.id}`);
|
|
47
|
-
}
|
|
48
|
-
return rows;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface TimelineApi {
|
|
52
|
-
scrollBy(lines: number): void;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The conversation of a tab: every run of its thread (request block, then cards), in a
|
|
57
|
-
* scrollbox that follows the newest content until the user scrolls up, with a cursor over card
|
|
58
|
-
* headers and tool lines (enter expands or collapses).
|
|
59
|
-
*/
|
|
60
|
-
export function Timeline(props: {
|
|
61
|
-
runId: string;
|
|
62
|
-
focused: boolean;
|
|
63
|
-
onToggle?: (id: string) => void;
|
|
64
|
-
api?: (api: TimelineApi) => void;
|
|
65
|
-
}): JSX.Element {
|
|
66
|
-
const data = useData();
|
|
67
|
-
const theme = useTheme();
|
|
68
|
-
const dimensions = useTerminalDimensions();
|
|
69
|
-
const runs = createMemo(() => data.threadOf(props.runId));
|
|
70
|
-
const owner = getOwner();
|
|
71
|
-
const memos = new Map<string, Accessor<Card[]>>();
|
|
72
|
-
// per-run memos live under the timeline itself, never under whichever computation asks first
|
|
73
|
-
const cardsOf = (run: string): Accessor<Card[]> => {
|
|
74
|
-
let m = memos.get(run);
|
|
75
|
-
if (!m) {
|
|
76
|
-
const all = data.timeline(run);
|
|
77
|
-
// only the newest cards are mounted: every card costs native renderables (spinner,
|
|
78
|
-
// fade-in, markdown); older ones fold into the "earlier" line
|
|
79
|
-
const limited = () => {
|
|
80
|
-
const list = all();
|
|
81
|
-
if (list.length <= RENDER_LIMIT) return list;
|
|
82
|
-
const dropped = list.slice(0, list.length - RENDER_LIMIT);
|
|
83
|
-
const earlier = dropped.reduce((n, c) => n + (c.kind === 'earlier' ? c.count : 1), 0);
|
|
84
|
-
return [
|
|
85
|
-
{ kind: 'earlier', key: 'earlier', count: earlier } as Card,
|
|
86
|
-
...list.slice(-RENDER_LIMIT),
|
|
87
|
-
];
|
|
88
|
-
};
|
|
89
|
-
m = runWithOwner(owner, () => createMemo(limited)) ?? limited;
|
|
90
|
-
memos.set(run, m);
|
|
91
|
-
}
|
|
92
|
-
return m;
|
|
93
|
-
};
|
|
94
|
-
const rows = createMemo(() =>
|
|
95
|
-
runs().flatMap((run) => selectableRows(cardsOf(run)()).map((id) => `${run}|${id}`)),
|
|
96
|
-
);
|
|
97
|
-
const version = createMemo(() => runs().reduce((n, run) => n + cardsOf(run)().length, 0));
|
|
98
|
-
const [cursor, setCursor] = createSignal(0);
|
|
99
|
-
const [expanded, setExpanded] = createSignal(new Set<string>(), { equals: false });
|
|
100
|
-
const [now, setNow] = createSignal(Date.now());
|
|
101
|
-
// true until the user scrolls up: the view then follows the newest content
|
|
102
|
-
const [following, setFollowing] = createSignal(true);
|
|
103
|
-
const refs = new Map<string, Renderable>();
|
|
104
|
-
let scroll: ScrollBoxRenderable | undefined;
|
|
105
|
-
const tick = setInterval(() => setNow(Date.now()), 1000);
|
|
106
|
-
onCleanup(() => clearInterval(tick));
|
|
107
|
-
|
|
108
|
-
// OpenTUI's stickyScroll re-sticks after a programmatic scrollTo(0), so following is ours alone
|
|
109
|
-
const follow = () => {
|
|
110
|
-
if (!following() || !scroll) return;
|
|
111
|
-
scroll.scrollTo(scroll.scrollHeight);
|
|
112
|
-
};
|
|
113
|
-
createEffect(
|
|
114
|
-
on([version, dimensions], () => {
|
|
115
|
-
setTimeout(follow, 0);
|
|
116
|
-
setTimeout(follow, 120);
|
|
117
|
-
}),
|
|
118
|
-
);
|
|
119
|
-
props.api?.({ scrollBy: (n) => scroll?.scrollBy(n) });
|
|
120
|
-
|
|
121
|
-
const selectedRow = () => rows()[Math.min(cursor(), rows().length - 1)];
|
|
122
|
-
/** The selected row id inside `run`, or undefined when the cursor is in another run. */
|
|
123
|
-
const selectedIn = (run: string) => {
|
|
124
|
-
const s = selectedRow();
|
|
125
|
-
return props.focused && s?.startsWith(`${run}|`) ? s.slice(run.length + 1) : undefined;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const ensureVisible = () => {
|
|
129
|
-
const id = selectedRow();
|
|
130
|
-
const el = id ? refs.get(id) : undefined;
|
|
131
|
-
if (!el || !scroll) return;
|
|
132
|
-
const top = el.y - scroll.viewport.y + scroll.scrollTop;
|
|
133
|
-
if (top < scroll.scrollTop) scroll.scrollTo(top);
|
|
134
|
-
else if (top + el.height > scroll.scrollTop + scroll.viewport.height)
|
|
135
|
-
scroll.scrollTo(top + el.height - scroll.viewport.height);
|
|
136
|
-
};
|
|
137
|
-
// only after a one-row move: right after scrollTo(0)/scrollTo(end) the row positions are stale
|
|
138
|
-
const move = (delta: number) => {
|
|
139
|
-
setCursor((c) => c + delta);
|
|
140
|
-
setTimeout(ensureVisible, 0);
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const toggle = () => {
|
|
144
|
-
const id = selectedRow();
|
|
145
|
-
if (!id) return;
|
|
146
|
-
setExpanded((s) => {
|
|
147
|
-
if (s.has(id)) s.delete(id);
|
|
148
|
-
else s.add(id);
|
|
149
|
-
return s;
|
|
150
|
-
});
|
|
151
|
-
props.onToggle?.(id);
|
|
152
|
-
};
|
|
153
|
-
/** The expanded set as the cards see it: ids of this run only. */
|
|
154
|
-
const expandedIn = (run: string) =>
|
|
155
|
-
new Set(
|
|
156
|
-
[...expanded()]
|
|
157
|
-
.filter((id) => id.startsWith(`${run}|`))
|
|
158
|
-
.map((id) => id.slice(run.length + 1)),
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
useKeys('pane', (key) => {
|
|
162
|
-
if (!props.focused || key.ctrl || key.meta) return false;
|
|
163
|
-
const n = rows().length;
|
|
164
|
-
switch (key.name) {
|
|
165
|
-
// j/k walk the cursor; the arrows (and a wheel the terminal turns into arrows) scroll the view
|
|
166
|
-
case 'j':
|
|
167
|
-
if (cursor() < n - 1) move(1);
|
|
168
|
-
else scroll?.scrollBy(1);
|
|
169
|
-
return true;
|
|
170
|
-
case 'k':
|
|
171
|
-
setFollowing(false);
|
|
172
|
-
if (cursor() > 0) move(-1);
|
|
173
|
-
else scroll?.scrollBy(-1);
|
|
174
|
-
return true;
|
|
175
|
-
case 'down':
|
|
176
|
-
scroll?.scrollBy(2);
|
|
177
|
-
return true;
|
|
178
|
-
case 'up':
|
|
179
|
-
setFollowing(false);
|
|
180
|
-
scroll?.scrollBy(-2);
|
|
181
|
-
return true;
|
|
182
|
-
case 'pagedown':
|
|
183
|
-
scroll?.scrollBy(scroll.viewport.height);
|
|
184
|
-
return true;
|
|
185
|
-
case 'pageup':
|
|
186
|
-
setFollowing(false);
|
|
187
|
-
scroll?.scrollBy(-scroll.viewport.height);
|
|
188
|
-
return true;
|
|
189
|
-
case 'g':
|
|
190
|
-
if (key.shift) {
|
|
191
|
-
setFollowing(true);
|
|
192
|
-
setCursor(Math.max(0, n - 1));
|
|
193
|
-
follow();
|
|
194
|
-
} else {
|
|
195
|
-
setFollowing(false);
|
|
196
|
-
setCursor(0);
|
|
197
|
-
scroll?.scrollTo(0);
|
|
198
|
-
}
|
|
199
|
-
return true;
|
|
200
|
-
case 'return':
|
|
201
|
-
toggle();
|
|
202
|
-
return true;
|
|
203
|
-
default:
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const rowRefFor = (run: string) => (id: string, el: unknown) => {
|
|
209
|
-
const key = `${run}|${id}`;
|
|
210
|
-
if (el) refs.set(key, el as Renderable);
|
|
211
|
-
else refs.delete(key);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
return (
|
|
215
|
-
<scrollbox
|
|
216
|
-
ref={(r: ScrollBoxRenderable) => {
|
|
217
|
-
scroll = r;
|
|
218
|
-
}}
|
|
219
|
-
flexGrow={1}
|
|
220
|
-
width="100%"
|
|
221
|
-
verticalScrollbarOptions={{
|
|
222
|
-
visible: true,
|
|
223
|
-
trackOptions: {
|
|
224
|
-
backgroundColor: theme.background.base,
|
|
225
|
-
foregroundColor: theme.scrollbar.base,
|
|
226
|
-
},
|
|
227
|
-
}}
|
|
228
|
-
horizontalScrollbarOptions={{ visible: false }}
|
|
229
|
-
contentOptions={{ flexDirection: 'column', paddingTop: 1, paddingLeft: 2, paddingRight: 2 }}
|
|
230
|
-
>
|
|
231
|
-
<For each={runs()}>
|
|
232
|
-
{(run) => {
|
|
233
|
-
const cards = cardsOf(run);
|
|
234
|
-
const rowRef = rowRefFor(run);
|
|
235
|
-
const selected = createMemo(() => selectedIn(run));
|
|
236
|
-
const expandedHere = createMemo(() => expandedIn(run));
|
|
237
|
-
const nodes = createMemo(
|
|
238
|
-
() =>
|
|
239
|
-
cards().filter(
|
|
240
|
-
(c) => c.kind !== 'error' && c.kind !== 'earlier' && c.kind !== 'summary',
|
|
241
|
-
).length,
|
|
242
|
-
);
|
|
243
|
-
return (
|
|
244
|
-
<>
|
|
245
|
-
<RequestBlock runId={run} />
|
|
246
|
-
<For each={cards()}>
|
|
247
|
-
{(card) => (
|
|
248
|
-
<Switch>
|
|
249
|
-
<Match when={card.kind === 'node' && card}>
|
|
250
|
-
{(c) => (
|
|
251
|
-
<NodeCard
|
|
252
|
-
card={c()}
|
|
253
|
-
now={now()}
|
|
254
|
-
selectedRow={selected()}
|
|
255
|
-
expanded={expandedHere()}
|
|
256
|
-
rowRef={rowRef}
|
|
257
|
-
chat={isChatRun(data.state.states[run])}
|
|
258
|
-
/>
|
|
259
|
-
)}
|
|
260
|
-
</Match>
|
|
261
|
-
<Match when={card.kind === 'gate' && card}>
|
|
262
|
-
{(c) => (
|
|
263
|
-
<GateCard
|
|
264
|
-
card={c()}
|
|
265
|
-
selected={selected() === `card:${c().nodeId}`}
|
|
266
|
-
rowRef={rowRef}
|
|
267
|
-
/>
|
|
268
|
-
)}
|
|
269
|
-
</Match>
|
|
270
|
-
<Match when={card.kind === 'decide' && card}>
|
|
271
|
-
{(c) => (
|
|
272
|
-
<DecideCard
|
|
273
|
-
card={c()}
|
|
274
|
-
selected={selected() === `card:${c().nodeId}`}
|
|
275
|
-
rowRef={rowRef}
|
|
276
|
-
/>
|
|
277
|
-
)}
|
|
278
|
-
</Match>
|
|
279
|
-
<Match when={card.kind === 'human' && card}>
|
|
280
|
-
{(c) => (
|
|
281
|
-
<HumanCard
|
|
282
|
-
card={c()}
|
|
283
|
-
selected={selected() === `card:${c().nodeId}`}
|
|
284
|
-
rowRef={rowRef}
|
|
285
|
-
/>
|
|
286
|
-
)}
|
|
287
|
-
</Match>
|
|
288
|
-
<Match when={card.kind === 'error' && card}>
|
|
289
|
-
{(c) => <ErrorCard card={c()} />}
|
|
290
|
-
</Match>
|
|
291
|
-
<Match when={card.kind === 'earlier' && card}>
|
|
292
|
-
{(c) => <EarlierCard card={c()} />}
|
|
293
|
-
</Match>
|
|
294
|
-
<Match when={card.kind === 'summary' && card}>
|
|
295
|
-
{(c) => (
|
|
296
|
-
<SummaryCard
|
|
297
|
-
card={c()}
|
|
298
|
-
nodes={nodes()}
|
|
299
|
-
selected={selected() === 'card:summary'}
|
|
300
|
-
rowRef={rowRef}
|
|
301
|
-
/>
|
|
302
|
-
)}
|
|
303
|
-
</Match>
|
|
304
|
-
</Switch>
|
|
305
|
-
)}
|
|
306
|
-
</For>
|
|
307
|
-
</>
|
|
308
|
-
);
|
|
309
|
-
}}
|
|
310
|
-
</For>
|
|
311
|
-
</scrollbox>
|
|
312
|
-
);
|
|
313
|
-
}
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import type { ProjectProfile, RunState } from '@wizardingcode/shibaox-core';
|
|
2
|
-
import type {
|
|
3
|
-
DiffResult,
|
|
4
|
-
Envelope,
|
|
5
|
-
Health,
|
|
6
|
-
InboxItem,
|
|
7
|
-
KeyRow,
|
|
8
|
-
ModelChoice,
|
|
9
|
-
OrgConfig,
|
|
10
|
-
OrgConfigPatch,
|
|
11
|
-
RunSummaryPlus,
|
|
12
|
-
SubmitRequest,
|
|
13
|
-
} from '@wizardingcode/shibaox-daemon';
|
|
14
|
-
import { DaemonHttpError } from '@wizardingcode/shibaox-daemon/client';
|
|
15
|
-
import type { DaemonClientLike } from '../context/client.js';
|
|
16
|
-
|
|
17
|
-
interface Stream {
|
|
18
|
-
queue: Envelope[];
|
|
19
|
-
wake?: () => void;
|
|
20
|
-
closed: boolean;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** An in-memory daemon: lists, states and SSE frames the tests push in; every call is recorded. */
|
|
24
|
-
export class FakeDaemonClient implements DaemonClientLike {
|
|
25
|
-
runs: RunSummaryPlus[] = [];
|
|
26
|
-
states = new Map<string, RunState>();
|
|
27
|
-
inboxItems: InboxItem[] = [];
|
|
28
|
-
healthValue: Health = {
|
|
29
|
-
version: '0.0.1',
|
|
30
|
-
uptimeSeconds: 1,
|
|
31
|
-
runs: { running: 0, queued: 0, waiting: 0 },
|
|
32
|
-
channels: [],
|
|
33
|
-
};
|
|
34
|
-
calls: { method: string; args: unknown[] }[] = [];
|
|
35
|
-
/** When true every call rejects with `Error('down')` (the daemon is unreachable). */
|
|
36
|
-
failing = false;
|
|
37
|
-
/** When set, `answer()` throws this as a DaemonHttpError. */
|
|
38
|
-
answerError?: { status: number; code: string; message: string };
|
|
39
|
-
submitResult: { runId: string; warnings: string[] } = { runId: 'new-run', warnings: [] };
|
|
40
|
-
/** Frames every new `events()` of that run starts with (the daemon's history replay). */
|
|
41
|
-
history = new Map<string, Envelope[]>();
|
|
42
|
-
/** Diff results by run id; a missing entry is a 404 `no_workspace`. */
|
|
43
|
-
diffs = new Map<string, DiffResult>();
|
|
44
|
-
/** Project profiles by path; a missing entry is a 404 `not_found`. */
|
|
45
|
-
profiles = new Map<string, ProjectProfile>();
|
|
46
|
-
/** What `/model` offers. */
|
|
47
|
-
modelChoices: ModelChoice[] = [];
|
|
48
|
-
/** What `/tiers` shows, by org root. */
|
|
49
|
-
orgConfigs = new Map<string, OrgConfig>();
|
|
50
|
-
/** What `/keys` shows. */
|
|
51
|
-
keyRows: KeyRow[] = [];
|
|
52
|
-
/** What `GET /orgs/default` answers. */
|
|
53
|
-
defaultOrgRoot = '/o';
|
|
54
|
-
/** How many times `defaultOrg()` still fails before answering (a daemon that is settling). */
|
|
55
|
-
defaultOrgFailures = 0;
|
|
56
|
-
private readonly streams = new Map<string, Stream>();
|
|
57
|
-
|
|
58
|
-
private record(method: string, args: unknown[]): void {
|
|
59
|
-
this.calls.push({ method, args });
|
|
60
|
-
if (this.failing) throw new Error('down');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async health(): Promise<Health> {
|
|
64
|
-
this.record('health', []);
|
|
65
|
-
return this.healthValue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async listRuns(q?: { status?: string; org?: string }): Promise<RunSummaryPlus[]> {
|
|
69
|
-
this.record('listRuns', [q]);
|
|
70
|
-
return this.runs;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async getRun(id: string): Promise<RunState> {
|
|
74
|
-
this.record('getRun', [id]);
|
|
75
|
-
const s = this.states.get(id);
|
|
76
|
-
if (!s) throw new DaemonHttpError(404, 'not_found', `run ${id} not found`);
|
|
77
|
-
return s;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async inbox(): Promise<InboxItem[]> {
|
|
81
|
-
this.record('inbox', []);
|
|
82
|
-
return this.inboxItems;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async answer(
|
|
86
|
-
id: string,
|
|
87
|
-
a: { approved: boolean; note?: string; via?: 'cli' | 'api' },
|
|
88
|
-
): Promise<{ runId: string; kind: 'human' | 'approval' }> {
|
|
89
|
-
this.record('answer', [id, a]);
|
|
90
|
-
if (this.answerError)
|
|
91
|
-
throw new DaemonHttpError(
|
|
92
|
-
this.answerError.status,
|
|
93
|
-
this.answerError.code,
|
|
94
|
-
this.answerError.message,
|
|
95
|
-
);
|
|
96
|
-
const item = this.inboxItems.find((i) => i.id === id);
|
|
97
|
-
return { runId: item?.runId ?? 'r', kind: item?.kind ?? 'human' };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async cancel(id: string): Promise<RunState> {
|
|
101
|
-
this.record('cancel', [id]);
|
|
102
|
-
const s = this.states.get(id);
|
|
103
|
-
if (!s) throw new DaemonHttpError(404, 'not_found', `run ${id} not found`);
|
|
104
|
-
return { ...s, status: 'cancelled' };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async resume(id: string, o?: { budgetUsd?: number }): Promise<RunState> {
|
|
108
|
-
this.record('resume', [id, o]);
|
|
109
|
-
const s = this.states.get(id);
|
|
110
|
-
if (!s) throw new DaemonHttpError(404, 'not_found', `run ${id} not found`);
|
|
111
|
-
return s;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async submitRun(req: SubmitRequest): Promise<{ runId: string; warnings: string[] }> {
|
|
115
|
-
this.record('submitRun', [req]);
|
|
116
|
-
return this.submitResult;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async diff(id: string): Promise<DiffResult> {
|
|
120
|
-
this.record('diff', [id]);
|
|
121
|
-
const d = this.diffs.get(id);
|
|
122
|
-
if (!d) throw new DaemonHttpError(404, 'no_workspace', 'The run workspace is gone');
|
|
123
|
-
return d;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async defaultOrg(): Promise<{ root: string; created: boolean }> {
|
|
127
|
-
this.record('defaultOrg', []);
|
|
128
|
-
if (this.defaultOrgFailures > 0) {
|
|
129
|
-
this.defaultOrgFailures--;
|
|
130
|
-
throw new Error('daemon still starting');
|
|
131
|
-
}
|
|
132
|
-
return { root: this.defaultOrgRoot, created: false };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
async keys(): Promise<KeyRow[]> {
|
|
136
|
-
this.record('keys', []);
|
|
137
|
-
return this.keyRows;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async setKey(name: string, value: string): Promise<{ name: string; set: true }> {
|
|
141
|
-
this.record('setKey', [name, value]);
|
|
142
|
-
return { name, set: true };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async unsetKey(name: string): Promise<{ name: string; removed: boolean }> {
|
|
146
|
-
this.record('unsetKey', [name]);
|
|
147
|
-
return { name, removed: true };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async orgConfig(root: string): Promise<OrgConfig> {
|
|
151
|
-
this.record('orgConfig', [root]);
|
|
152
|
-
const c = this.orgConfigs.get(root);
|
|
153
|
-
if (!c) throw new DaemonHttpError(404, 'bad_request', `org.yaml: file not found (${root})`);
|
|
154
|
-
return c;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
async setOrgConfig(root: string, patch: OrgConfigPatch): Promise<OrgConfig> {
|
|
158
|
-
this.record('setOrgConfig', [root, patch]);
|
|
159
|
-
const c = await this.orgConfig(root);
|
|
160
|
-
const tiers = { ...c.tiers };
|
|
161
|
-
for (const [k, v] of Object.entries(patch.tiers ?? {}))
|
|
162
|
-
if (v === null) delete tiers[k as keyof typeof tiers];
|
|
163
|
-
else if (v !== undefined) tiers[k as keyof typeof tiers] = v;
|
|
164
|
-
const next: OrgConfig = {
|
|
165
|
-
...c,
|
|
166
|
-
tiers,
|
|
167
|
-
judge: patch.judge === null ? undefined : (patch.judge ?? c.judge),
|
|
168
|
-
adapter: patch.adapter === null ? undefined : (patch.adapter ?? c.adapter),
|
|
169
|
-
per_run_usd: patch.per_run_usd === null ? undefined : (patch.per_run_usd ?? c.per_run_usd),
|
|
170
|
-
};
|
|
171
|
-
this.orgConfigs.set(root, next);
|
|
172
|
-
return next;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async models(): Promise<ModelChoice[]> {
|
|
176
|
-
this.record('models', []);
|
|
177
|
-
return this.modelChoices;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async projectProfile(path: string, orgRoot?: string): Promise<ProjectProfile> {
|
|
181
|
-
this.record('projectProfile', [path, orgRoot]);
|
|
182
|
-
const p = this.profiles.get(path);
|
|
183
|
-
if (!p) throw new DaemonHttpError(404, 'not_found', `project path not found: ${path}`);
|
|
184
|
-
return p;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
events(
|
|
188
|
-
id: string,
|
|
189
|
-
o: { since?: string; signal?: AbortSignal; historyOnly?: boolean } = {},
|
|
190
|
-
): AsyncIterable<Envelope> {
|
|
191
|
-
this.calls.push({ method: 'events', args: [id, o] });
|
|
192
|
-
const failing = this.failing;
|
|
193
|
-
const stream: Stream = { queue: [...(this.history.get(id) ?? [])], closed: false };
|
|
194
|
-
this.streams.set(id, stream);
|
|
195
|
-
const close = () => {
|
|
196
|
-
stream.closed = true;
|
|
197
|
-
if (this.streams.get(id) === stream) this.streams.delete(id);
|
|
198
|
-
stream.wake?.();
|
|
199
|
-
};
|
|
200
|
-
o.signal?.addEventListener('abort', close, { once: true });
|
|
201
|
-
async function* gen(): AsyncGenerator<Envelope> {
|
|
202
|
-
if (failing) {
|
|
203
|
-
close();
|
|
204
|
-
throw new Error('down');
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
while (true) {
|
|
208
|
-
while (stream.queue.length > 0) {
|
|
209
|
-
const env = stream.queue.shift() as Envelope;
|
|
210
|
-
yield env;
|
|
211
|
-
if (env.kind === 'end') return;
|
|
212
|
-
// one frame per macrotask, like frames arriving on a socket
|
|
213
|
-
await new Promise((r) => setImmediate(r));
|
|
214
|
-
}
|
|
215
|
-
if (stream.closed) return;
|
|
216
|
-
await new Promise<void>((r) => {
|
|
217
|
-
stream.wake = r;
|
|
218
|
-
});
|
|
219
|
-
stream.wake = undefined;
|
|
220
|
-
}
|
|
221
|
-
} finally {
|
|
222
|
-
close();
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return gen();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/** Delivers a frame to the live `events()` iterator of `runId` (dropped when there is none). */
|
|
229
|
-
pushFrame(runId: string, env: Envelope): void {
|
|
230
|
-
const s = this.streams.get(runId);
|
|
231
|
-
if (!s || s.closed) return;
|
|
232
|
-
s.queue.push(env);
|
|
233
|
-
s.wake?.();
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/** Ends the live `events()` iterator of `runId` without an `end` frame (a dropped connection). */
|
|
237
|
-
closeStream(runId: string): void {
|
|
238
|
-
const s = this.streams.get(runId);
|
|
239
|
-
if (!s) return;
|
|
240
|
-
s.closed = true;
|
|
241
|
-
this.streams.delete(runId);
|
|
242
|
-
s.wake?.();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** Run ids with a live `events()` iterator. */
|
|
246
|
-
openStreams(): string[] {
|
|
247
|
-
return [...this.streams.entries()].filter(([, s]) => !s.closed).map(([id]) => id);
|
|
248
|
-
}
|
|
249
|
-
}
|
package/src/theme/context.tsx
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { SyntaxStyle } from '@opentui/core';
|
|
2
|
-
import {
|
|
3
|
-
createContext,
|
|
4
|
-
createMemo,
|
|
5
|
-
type JSX,
|
|
6
|
-
onCleanup,
|
|
7
|
-
type ParentProps,
|
|
8
|
-
useContext,
|
|
9
|
-
} from 'solid-js';
|
|
10
|
-
import { type ResolvedTheme, resolveTheme, syntaxStyles } from './resolve.js';
|
|
11
|
-
import shibaox from './shibaox.json' with { type: 'json' };
|
|
12
|
-
|
|
13
|
-
let cached: ResolvedTheme | undefined;
|
|
14
|
-
/** The built-in shibaox theme, resolved once. */
|
|
15
|
-
export function defaultTheme(): ResolvedTheme {
|
|
16
|
-
cached ??= resolveTheme(shibaox);
|
|
17
|
-
return cached;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const Context = createContext<{ theme: ResolvedTheme; syntax: () => SyntaxStyle }>();
|
|
21
|
-
|
|
22
|
-
export function ThemeProvider(props: ParentProps<{ theme?: ResolvedTheme }>): JSX.Element {
|
|
23
|
-
const theme = props.theme ?? defaultTheme();
|
|
24
|
-
const syntax = createMemo(() => SyntaxStyle.fromStyles(syntaxStyles(theme)));
|
|
25
|
-
// a native handle: released a little after the provider goes, once no renderable draws with it
|
|
26
|
-
onCleanup(() => {
|
|
27
|
-
const style = syntax();
|
|
28
|
-
setTimeout(() => style.destroy(), 500);
|
|
29
|
-
});
|
|
30
|
-
return <Context.Provider value={{ theme, syntax }}>{props.children}</Context.Provider>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function useTheme(): ResolvedTheme {
|
|
34
|
-
const c = useContext(Context);
|
|
35
|
-
if (!c) throw new Error('useTheme outside ThemeProvider');
|
|
36
|
-
return c.theme;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The syntax style for `<markdown>`, `<code>` and `<diff>`, memoized per theme. */
|
|
40
|
-
export function useSyntax(): SyntaxStyle {
|
|
41
|
-
const c = useContext(Context);
|
|
42
|
-
if (!c) throw new Error('useSyntax outside ThemeProvider');
|
|
43
|
-
return c.syntax();
|
|
44
|
-
}
|