@a-t-h-i/bot-lobby 0.6.13 → 0.6.15
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/README.md +70 -79
- package/package.json +4 -2
- package/prompts/master.md +3 -2
- package/prompts/qa.md +33 -11
- package/prompts/reviewer.md +5 -2
- package/prompts/worker.md +5 -2
- package/src/ask/relay.ts +1 -2
- package/src/ask/tool.ts +4 -6
- package/src/ask/types.ts +16 -0
- package/src/ask/web.ts +63 -0
- package/src/index.ts +7 -1
- package/src/lobby/ask.ts +4 -5
- package/src/lobby/blocks.ts +57 -0
- package/src/lobby/feed.ts +1 -1
- package/src/lobby/host.ts +178 -0
- package/src/lobby/keys.ts +10 -41
- package/src/lobby/prompt-hub.ts +30 -77
- package/src/lobby/prompts.ts +8 -12
- package/src/lobby/runtime.ts +49 -310
- package/src/lobby/service.ts +54 -134
- package/src/lobby/task-rows.ts +28 -0
- package/src/pi/commands.ts +14 -20
- package/src/pi/model-settings.ts +112 -0
- package/src/pi/start-task.ts +1 -1
- package/src/pi/tools.ts +6 -7
- package/src/pi/ui.ts +0 -9
- package/src/schemas/configuration.ts +9 -33
- package/src/state/attachments.ts +82 -0
- package/src/webui/api/index.ts +17 -15
- package/src/webui/api/lobby.ts +4 -3
- package/src/webui/api/planner.ts +6 -4
- package/src/webui/api/quickfix.ts +4 -3
- package/src/webui/api/sessions.ts +9 -7
- package/src/webui/api/settings.ts +20 -8
- package/src/webui/api/status.ts +4 -15
- package/src/webui/api/tasks.ts +11 -15
- package/src/webui/command.ts +35 -19
- package/src/webui/dev/fake-service.ts +1 -2
- package/src/webui/dev/fixtures.ts +2 -5
- package/src/webui/events.ts +1 -1
- package/src/webui/protocol.ts +22 -11
- package/src/webui/server.ts +42 -1
- package/src/webui/uploads.ts +68 -0
- package/webui/dist/assets/index-B1PnCyl2.css +2 -0
- package/webui/dist/assets/index-DI9Yq9IC.js +90 -0
- package/webui/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/webui/dist/build.json +142 -110
- package/webui/dist/index.html +4 -3
- package/src/ask/dialog.ts +0 -193
- package/src/ask/image.ts +0 -202
- package/src/ask/png.ts +0 -179
- package/src/ask/state.ts +0 -175
- package/src/ask/view.ts +0 -163
- package/src/lobby/layout.ts +0 -496
- package/src/lobby/markdown.ts +0 -121
- package/src/lobby/mini.ts +0 -167
- package/src/lobby/tabs/excalidraw.ts +0 -95
- package/src/lobby/tabs/git.ts +0 -162
- package/src/lobby/tabs/home.ts +0 -544
- package/src/lobby/tabs/issues.ts +0 -73
- package/src/lobby/tabs/knowledge.ts +0 -135
- package/src/lobby/tabs/metrics.ts +0 -300
- package/src/lobby/tabs/plan.ts +0 -262
- package/src/lobby/tabs/quickfix.ts +0 -136
- package/src/lobby/tabs/tasks.ts +0 -412
- package/src/lobby/theme.ts +0 -30
- package/src/lobby/view.ts +0 -3018
- package/src/pi/settings-ui.ts +0 -708
- package/src/width.ts +0 -102
- package/webui/dist/assets/index-Bk-lpAGm.css +0 -2
- package/webui/dist/assets/index-E6MPcbdC.js +0 -80
package/src/lobby/runtime.ts
CHANGED
|
@@ -1,49 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The lobby inside pi: one runtime per
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* records each Master turn in the metrics log.
|
|
2
|
+
* The lobby inside pi: one runtime per master session. It owns the lobby's
|
|
3
|
+
* state (the quick-fix queue, planner, issues, pulls, knowledge), narrates the
|
|
4
|
+
* Master's own turn into the feed, passes plan comments to the Master that
|
|
5
|
+
* owns a task and records each Master turn in the metrics log. The web server
|
|
6
|
+
* (src/webui) is its only surface.
|
|
8
7
|
*/
|
|
9
8
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import {
|
|
11
|
-
import { Key, type OverlayHandle, type TUI } from "@earendil-works/pi-tui";
|
|
12
|
-
import { detectProjectRoot, loadConfig, saveConfig } from "../state/project.ts";
|
|
9
|
+
import { detectProjectRoot, loadConfig } from "../state/project.ts";
|
|
13
10
|
import { appendMetrics, type MetricStatus } from "../state/metrics.ts";
|
|
14
11
|
import { describeToolCall } from "../pi/activity.ts";
|
|
15
|
-
import {
|
|
12
|
+
import { currentZenTask, onRunUpdates } from "../pi/ui.ts";
|
|
16
13
|
import type { WorkspaceInfo } from "../execution/workspace.ts";
|
|
17
14
|
import { basename } from "node:path";
|
|
18
15
|
import { isSubagentProcess } from "../pi/quiet.ts";
|
|
19
16
|
import { modelRef } from "../pi/model-support.ts";
|
|
20
17
|
import { modelLookup } from "../pi/tools.ts";
|
|
21
18
|
import { setQuickFixHandoff } from "../pi/route.ts";
|
|
22
|
-
import { currentWebServer } from "../webui/server.ts";
|
|
23
19
|
import type { Domain } from "../schemas/agent.ts";
|
|
24
|
-
import type { LobbyPanel } from "../schemas/configuration.ts";
|
|
25
20
|
import { chatFromEntries, lobbyFeed, narrateEvent, type AgentEventLike } from "./feed.ts";
|
|
26
21
|
import { classifier, effortFor, hintsFor } from "../classifier/instance.ts";
|
|
27
22
|
import { checkThinking } from "../pi/model-support.ts";
|
|
28
23
|
import { jobTitle, QuickFixQueue } from "./quickfix.ts";
|
|
29
|
-
import type { Asker } from "
|
|
30
|
-
import { miniLine, type MiniInput } from "./mini.ts";
|
|
24
|
+
import type { Asker } from "../ask/types.ts";
|
|
31
25
|
import type { PlanningSession } from "./planner.ts";
|
|
32
26
|
import { execCommand, IssuesState } from "./issues.ts";
|
|
33
27
|
import { PullsState } from "./pulls.ts";
|
|
34
28
|
import { PullReviews } from "./pr-review.ts";
|
|
35
29
|
import { KnowledgeBook } from "./knowledge.ts";
|
|
36
30
|
import { ExcalidrawBook } from "../excalidraw/sessions.ts";
|
|
37
|
-
import
|
|
38
|
-
import { createLobbyService,
|
|
31
|
+
import type { LobbyService } from "./host.ts";
|
|
32
|
+
import { createLobbyService, lobbyNotify, lobbyProfile, reviewProfile, seatProfile, setServiceState, savePlan as serviceSavePlan, answerPanel as serviceAnswerPanel } from "./service.ts";
|
|
39
33
|
import { lobbyTopics } from "./topics.ts";
|
|
40
|
-
|
|
41
|
-
export { backgroundSessions, FRAME_MS, setSessionLauncher } from "./service.ts";
|
|
42
|
-
import { lobbyTheme } from "./theme.ts";
|
|
34
|
+
export { backgroundSessions, setSessionLauncher } from "./service.ts";
|
|
43
35
|
import { deliverComments, onOwnerEvent } from "../pi/owner.ts";
|
|
44
|
-
import {
|
|
36
|
+
import { pushNotice } from "../webui/notices.ts";
|
|
45
37
|
|
|
46
|
-
export const ANCHOR_KEY = "bot-lobby-anchor";
|
|
47
38
|
export { deliverComments };
|
|
48
39
|
|
|
49
40
|
export interface Runtime {
|
|
@@ -51,12 +42,6 @@ export interface Runtime {
|
|
|
51
42
|
configDir: string;
|
|
52
43
|
ctx: ExtensionContext;
|
|
53
44
|
root: string;
|
|
54
|
-
tui?: TUI;
|
|
55
|
-
view?: LobbyView;
|
|
56
|
-
handle?: OverlayHandle;
|
|
57
|
-
visible: boolean;
|
|
58
|
-
/** Stepped aside while pi shows a dialog. */
|
|
59
|
-
asideForPrompt: boolean;
|
|
60
45
|
quickfix: QuickFixQueue;
|
|
61
46
|
planner?: PlanningSession;
|
|
62
47
|
issues: IssuesState;
|
|
@@ -65,19 +50,11 @@ export interface Runtime {
|
|
|
65
50
|
knowledge: KnowledgeBook;
|
|
66
51
|
excalidraw: ExcalidrawBook;
|
|
67
52
|
unsubscribeFeed?: () => void;
|
|
68
|
-
/**
|
|
69
|
-
unsubscribeTopics?: () => void;
|
|
70
|
-
/** The terminal's registration as the hub's only prompt surface. */
|
|
71
|
-
unregisterTerminalSurface?: () => void;
|
|
72
|
-
/** Puts the panel's questions to the user: the questionnaire unless a test sets another. */
|
|
53
|
+
/** Puts the panel's questions to the user: the web page's questionnaire unless a test sets another. */
|
|
73
54
|
asker?: Asker;
|
|
74
|
-
/** A questionnaire is
|
|
55
|
+
/** A questionnaire is open in the page. */
|
|
75
56
|
asking: boolean;
|
|
76
|
-
/** The
|
|
77
|
-
mouse: boolean;
|
|
78
|
-
/** Settings opened from the lobby are on screen; the lobby stays aside until they close. */
|
|
79
|
-
inSettings: boolean;
|
|
80
|
-
/** The repository (or folder) and branch the lobby's title shows, and whether git is being asked now. */
|
|
57
|
+
/** The repository (or folder) and branch the page's title shows, and whether git is being asked now. */
|
|
81
58
|
workspace: WorkspaceInfo;
|
|
82
59
|
readingWorkspace: boolean;
|
|
83
60
|
}
|
|
@@ -85,42 +62,11 @@ export interface Runtime {
|
|
|
85
62
|
let runtime: Runtime | undefined;
|
|
86
63
|
let lobbyService: LobbyService | undefined;
|
|
87
64
|
|
|
88
|
-
/** The lobby's backend, once its service has started
|
|
65
|
+
/** The lobby's backend, once its service has started. */
|
|
89
66
|
export function currentLobbyService(): LobbyService | undefined {
|
|
90
67
|
return lobbyService;
|
|
91
68
|
}
|
|
92
69
|
|
|
93
|
-
export function isLobbyVisible(): boolean {
|
|
94
|
-
return runtime?.visible === true;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** The lobby's view, once it has been shown (tests drive it through this). */
|
|
98
|
-
export function lobbyView(): LobbyView | undefined {
|
|
99
|
-
return runtime?.view;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Repaint through the lobby service's frame throttle (see service.ts). */
|
|
103
|
-
function rerender(): void {
|
|
104
|
-
repaintLobby();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** What the status line shows: the task, the planning round, the quick fix in hand. */
|
|
108
|
-
function miniInput(state: Runtime): MiniInput {
|
|
109
|
-
const snapshot = taskSnapshot();
|
|
110
|
-
const planner = state.planner;
|
|
111
|
-
const job = state.quickfix.running ?? state.quickfix.jobs.find((entry) => entry.status === "queued");
|
|
112
|
-
const queued = state.quickfix.jobs.filter((entry) => entry.status === "queued" && entry !== job).length;
|
|
113
|
-
const webLink = currentWebServer()?.link;
|
|
114
|
-
return {
|
|
115
|
-
...(snapshot.task ? { task: snapshot.task } : {}),
|
|
116
|
-
runs: snapshot.runs,
|
|
117
|
-
...(planner ? { planning: { busy: planner.busy, round: planner.turns, limit: loadConfig().lobby.maxPlanningRounds, questions: planner.awaitingAnswers ? planner.questions.length : 0, ready: planner.reply?.status === "ready", saved: Boolean(planner.saved) } } : {}),
|
|
118
|
-
...(job ? { quickfix: { title: jobTitle(job), running: job.status === "running", queued } } : {}),
|
|
119
|
-
...(webLink ? { webLink } : {}),
|
|
120
|
-
key: "Alt+L",
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
70
|
/** Save the plan as a pending task; the service holds the implementation. */
|
|
125
71
|
export async function savePlan(state: Runtime | undefined = runtime): Promise<string> {
|
|
126
72
|
return serviceSavePlan(state);
|
|
@@ -131,156 +77,6 @@ export async function answerPanel(state: Runtime | undefined = runtime): Promise
|
|
|
131
77
|
return serviceAnswerPanel(state);
|
|
132
78
|
}
|
|
133
79
|
|
|
134
|
-
function host(state: Runtime, tui: TUI): LobbyHost {
|
|
135
|
-
return {
|
|
136
|
-
...createLobbyService(state),
|
|
137
|
-
rows: () => tui.terminal.rows,
|
|
138
|
-
theme: () => lobbyTheme(state.ctx.ui.theme),
|
|
139
|
-
hide: () => hideLobby(),
|
|
140
|
-
panels: () => loadConfig().lobby.panels,
|
|
141
|
-
savePanels: (panels) => savePanels(panels),
|
|
142
|
-
keys: () => loadConfig().lobby.keys,
|
|
143
|
-
openSettings: (entry) => lobbySettings(state, entry),
|
|
144
|
-
editText: (title, text) => editText(state, title, text),
|
|
145
|
-
requestRender: () => tui.requestRender(),
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Run something pi draws itself (its settings menus, its multi-line editor)
|
|
151
|
-
* with the lobby stepped aside for the whole visit, not only for each dialog,
|
|
152
|
-
* so it does not flash between them; the lobby rereads the config after.
|
|
153
|
-
*/
|
|
154
|
-
async function whileAside<T>(state: Runtime, run: () => Promise<T>, failure: string): Promise<T | undefined> {
|
|
155
|
-
if (state.inSettings) return undefined;
|
|
156
|
-
state.inSettings = true;
|
|
157
|
-
setMouse(state, false);
|
|
158
|
-
state.handle?.setHidden(true);
|
|
159
|
-
try {
|
|
160
|
-
return await run();
|
|
161
|
-
} catch (error) {
|
|
162
|
-
state.ctx.ui.notify(`bot-lobby: ${failure} — ${(error as Error).message}`, "warning");
|
|
163
|
-
return undefined;
|
|
164
|
-
} finally {
|
|
165
|
-
state.inSettings = false;
|
|
166
|
-
state.asideForPrompt = false;
|
|
167
|
-
if (state.visible && state.handle) {
|
|
168
|
-
state.handle.setHidden(false);
|
|
169
|
-
state.handle.focus();
|
|
170
|
-
setMouse(state, true);
|
|
171
|
-
}
|
|
172
|
-
state.view?.reloadConfig();
|
|
173
|
-
rerender();
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** bot-lobby's settings (or one agent's entry) from inside the lobby. */
|
|
178
|
-
async function lobbySettings(state: Runtime, entry?: "quickfix" | "planner"): Promise<void> {
|
|
179
|
-
await whileAside(state, () => (entry ? openEntrySettings(state.pi, state.ctx, entry) : openSettings(state.pi, state.ctx)), "settings failed");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** A text edited in pi's multi-line editor (Enter saves, Shift+Enter is a new line); undefined when cancelled. */
|
|
183
|
-
async function editText(state: Runtime, title: string, text: string): Promise<string | undefined> {
|
|
184
|
-
return whileAside(state, () => state.ctx.ui.editor(title, text), "the editor failed");
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** Remember which panes show, keeping every other setting as the file has it now. */
|
|
188
|
-
function savePanels(panels: Record<LobbyPanel, boolean>): void {
|
|
189
|
-
try {
|
|
190
|
-
const config = loadConfig();
|
|
191
|
-
saveConfig({ ...config, lobby: { ...config.lobby, panels: { ...panels } } });
|
|
192
|
-
} catch {
|
|
193
|
-
// A read-only config only means the choice lasts for this session.
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** SGR mouse reporting: presses, releases and the wheel, in cell coordinates. */
|
|
198
|
-
const MOUSE_ON = "\x1b[?1000h\x1b[?1006h";
|
|
199
|
-
const MOUSE_OFF = "\x1b[?1006l\x1b[?1000l";
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* In pi's regular screen nothing reports the mouse, so the lobby turns
|
|
203
|
-
* reporting on while it is up (clicks pick tabs and draft lines, the wheel
|
|
204
|
-
* scrolls) and off whenever it steps aside. Full-screen pi reports the mouse
|
|
205
|
-
* itself and hands the events to the lobby.
|
|
206
|
-
*/
|
|
207
|
-
function setMouse(state: Runtime, on: boolean): void {
|
|
208
|
-
const tui = state.tui;
|
|
209
|
-
if (!tui || tui.mode === "fullscreen") return;
|
|
210
|
-
const want = on && loadConfig().lobby.mouse;
|
|
211
|
-
if (want === state.mouse) return;
|
|
212
|
-
state.mouse = want;
|
|
213
|
-
tui.terminal.write(want ? MOUSE_ON : MOUSE_OFF);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/** Show the lobby (optionally on `tab`); false when there is no interactive TUI. */
|
|
217
|
-
export function showLobby(tab?: TabId): boolean {
|
|
218
|
-
const state = runtime;
|
|
219
|
-
if (!state?.tui) return false;
|
|
220
|
-
if (isMinimized()) setMinimized(false);
|
|
221
|
-
if (!state.view || !state.handle) {
|
|
222
|
-
const theme = state.ctx.ui.theme;
|
|
223
|
-
state.view = new LobbyView(state.tui, host(state, state.tui), { borderColor: (text) => theme.fg("border", text), selectList: getSelectListTheme() });
|
|
224
|
-
state.handle = state.tui.showOverlay(state.view, { width: "100%", maxHeight: "100%", anchor: "top-left" });
|
|
225
|
-
} else {
|
|
226
|
-
state.handle.setHidden(false);
|
|
227
|
-
state.handle.focus();
|
|
228
|
-
}
|
|
229
|
-
state.visible = true;
|
|
230
|
-
state.asideForPrompt = false;
|
|
231
|
-
if (tab) state.view.setTab(tab);
|
|
232
|
-
state.view.start();
|
|
233
|
-
setMouse(state, true);
|
|
234
|
-
applyStatus(state.ctx, state.root, state.configDir);
|
|
235
|
-
state.tui.requestRender();
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function hideLobby(): void {
|
|
240
|
-
const state = runtime;
|
|
241
|
-
if (!state?.view || !state.visible) return;
|
|
242
|
-
state.visible = false;
|
|
243
|
-
state.asideForPrompt = false;
|
|
244
|
-
setMouse(state, false);
|
|
245
|
-
state.view.stop();
|
|
246
|
-
state.handle?.setHidden(true);
|
|
247
|
-
applyStatus(state.ctx, state.root, state.configDir);
|
|
248
|
-
state.tui?.requestRender();
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export function toggleLobby(): boolean {
|
|
252
|
-
if (isLobbyVisible()) {
|
|
253
|
-
hideLobby();
|
|
254
|
-
return true;
|
|
255
|
-
}
|
|
256
|
-
return showLobby();
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/** Open the lobby when this session drives a task and the config asks for it. */
|
|
260
|
-
export function autoOpenLobby(): void {
|
|
261
|
-
if (runtime && loadConfig().lobby.autoOpen && currentZenTask()) showLobby("lobby");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/** Step aside while pi shows a dialog (an approval, a question), and come back after. */
|
|
265
|
-
function promptStarted(): void {
|
|
266
|
-
lobbyTopics.bump("status");
|
|
267
|
-
const state = runtime;
|
|
268
|
-
if (!state?.visible || !state.handle || state.inSettings) return;
|
|
269
|
-
state.asideForPrompt = true;
|
|
270
|
-
setMouse(state, false);
|
|
271
|
-
state.handle.setHidden(true);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function promptEnded(): void {
|
|
275
|
-
lobbyTopics.bump("status");
|
|
276
|
-
const state = runtime;
|
|
277
|
-
if (!state?.asideForPrompt || !state.handle || state.inSettings) return;
|
|
278
|
-
state.asideForPrompt = false;
|
|
279
|
-
if (!state.visible) return;
|
|
280
|
-
state.handle.setHidden(false);
|
|
281
|
-
setMouse(state, true);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
80
|
function shutdown(): void {
|
|
285
81
|
const state = runtime;
|
|
286
82
|
if (!state) return;
|
|
@@ -288,23 +84,17 @@ function shutdown(): void {
|
|
|
288
84
|
lobbyService = undefined;
|
|
289
85
|
setServiceState(undefined);
|
|
290
86
|
setQuickFixHandoff(undefined);
|
|
291
|
-
setMouse(state, false);
|
|
292
87
|
state.unsubscribeFeed?.();
|
|
293
|
-
state.unsubscribeTopics?.();
|
|
294
|
-
state.unregisterTerminalSurface?.();
|
|
295
88
|
state.quickfix.cancelAll();
|
|
296
89
|
state.reviews.cancelAll();
|
|
297
90
|
state.planner?.cancel();
|
|
298
|
-
state.view?.dispose();
|
|
299
|
-
state.handle?.hide();
|
|
300
91
|
onRunUpdates(undefined);
|
|
301
|
-
onMinimizeChange(undefined);
|
|
302
92
|
}
|
|
303
93
|
|
|
304
94
|
/**
|
|
305
95
|
* The oracle routed a request to the quick-fix agent: queue it (no "looks like
|
|
306
96
|
* a task" hold, the oracle already decided), on its builder's settings when it
|
|
307
|
-
* is a quick feature, and
|
|
97
|
+
* is a quick feature, and tell the page where it went.
|
|
308
98
|
*/
|
|
309
99
|
function handToQuickFix(state: Runtime, request: string, builder: Domain | undefined, reason: string): string {
|
|
310
100
|
const quick = lobbyProfile(state, "quickfix");
|
|
@@ -314,24 +104,18 @@ function handToQuickFix(state: Runtime, request: string, builder: Domain | undef
|
|
|
314
104
|
...(profile ? { profile } : {}),
|
|
315
105
|
note: `The oracle sent your request here: ${reason.replace(/[.\s]+$/, "")}.${builder ? ` A quick feature: it runs on ${builder === "designer" ? "DESIGN" : "DEV"}'s model, thinking and time limit.` : ""}`,
|
|
316
106
|
});
|
|
317
|
-
showLobby("quickfix");
|
|
318
107
|
lobbyTopics.bump("quickfix");
|
|
319
|
-
|
|
108
|
+
pushNotice("The oracle sent your request to the quick-fix agent.", "info");
|
|
320
109
|
return job.id;
|
|
321
110
|
}
|
|
322
111
|
|
|
323
|
-
/** Whether the web server wants the lobby service without the terminal (a later step flips this on). */
|
|
324
|
-
function webServerOn(): boolean {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
112
|
/**
|
|
329
|
-
* Start the lobby's backend: state, service and background wiring. Runs
|
|
330
|
-
*
|
|
113
|
+
* Start the lobby's backend: state, service and background wiring. Runs for
|
|
114
|
+
* every interactive master session, not a subagent.
|
|
331
115
|
*/
|
|
332
116
|
export function startLobbyService(pi: ExtensionAPI, ctx: ExtensionContext, configDir: string): Runtime | undefined {
|
|
333
117
|
shutdown();
|
|
334
|
-
if (isSubagentProcess() || (ctx
|
|
118
|
+
if (isSubagentProcess() || !servesPage(ctx)) return undefined;
|
|
335
119
|
const root = detectProjectRoot(ctx.cwd, configDir);
|
|
336
120
|
const workflow = loadConfig().workflow;
|
|
337
121
|
const state: Runtime = {
|
|
@@ -339,22 +123,12 @@ export function startLobbyService(pi: ExtensionAPI, ctx: ExtensionContext, confi
|
|
|
339
123
|
configDir,
|
|
340
124
|
ctx,
|
|
341
125
|
root,
|
|
342
|
-
visible: false,
|
|
343
|
-
asideForPrompt: false,
|
|
344
126
|
asking: false,
|
|
345
|
-
mouse: false,
|
|
346
|
-
inSettings: false,
|
|
347
127
|
workspace: { name: basename(ctx.cwd) || ctx.cwd },
|
|
348
128
|
readingWorkspace: false,
|
|
349
129
|
quickfix: undefined as unknown as QuickFixQueue,
|
|
350
|
-
issues: new IssuesState(execCommand, ctx.cwd, () =>
|
|
351
|
-
|
|
352
|
-
rerender();
|
|
353
|
-
}),
|
|
354
|
-
pulls: new PullsState(execCommand, ctx.cwd, () => {
|
|
355
|
-
lobbyTopics.bump("git");
|
|
356
|
-
rerender();
|
|
357
|
-
}),
|
|
130
|
+
issues: new IssuesState(execCommand, ctx.cwd, () => lobbyTopics.bump("issues")),
|
|
131
|
+
pulls: new PullsState(execCommand, ctx.cwd, () => lobbyTopics.bump("git")),
|
|
358
132
|
reviews: undefined as unknown as PullReviews,
|
|
359
133
|
knowledge: new KnowledgeBook({
|
|
360
134
|
root,
|
|
@@ -374,14 +148,8 @@ export function startLobbyService(pi: ExtensionAPI, ctx: ExtensionContext, confi
|
|
|
374
148
|
stallTimeoutMs: workflow.stallTimeoutMs,
|
|
375
149
|
toolStallTimeoutMs: workflow.toolStallTimeoutMs,
|
|
376
150
|
feed: lobbyFeed,
|
|
377
|
-
onChange: () =>
|
|
378
|
-
|
|
379
|
-
rerender();
|
|
380
|
-
},
|
|
381
|
-
notify: (message, level) => {
|
|
382
|
-
lobbyTopics.bump("notices");
|
|
383
|
-
if (!state.visible) ctx.ui.notify(message, level);
|
|
384
|
-
},
|
|
151
|
+
onChange: () => lobbyTopics.bump("git"),
|
|
152
|
+
notify: (message, level) => lobbyNotify(state, message, level),
|
|
385
153
|
classifier: classifier(),
|
|
386
154
|
});
|
|
387
155
|
state.quickfix = new QuickFixQueue({
|
|
@@ -392,14 +160,8 @@ export function startLobbyService(pi: ExtensionAPI, ctx: ExtensionContext, confi
|
|
|
392
160
|
stallTimeoutMs: workflow.stallTimeoutMs,
|
|
393
161
|
toolStallTimeoutMs: workflow.toolStallTimeoutMs,
|
|
394
162
|
feed: lobbyFeed,
|
|
395
|
-
onChange: () =>
|
|
396
|
-
|
|
397
|
-
rerender();
|
|
398
|
-
},
|
|
399
|
-
notify: (message, level) => {
|
|
400
|
-
lobbyTopics.bump("notices");
|
|
401
|
-
if (!state.visible) ctx.ui.notify(message, level);
|
|
402
|
-
},
|
|
163
|
+
onChange: () => lobbyTopics.bump("quickfix"),
|
|
164
|
+
notify: (message, level) => lobbyNotify(state, message, level),
|
|
403
165
|
hints: hintsFor({ cwd: ctx.cwd, root, configDir }),
|
|
404
166
|
classifier: classifier(),
|
|
405
167
|
effort: effortFor((model, thinking) => checkThinking(modelLookup(ctx)(model), thinking).level),
|
|
@@ -407,30 +169,21 @@ export function startLobbyService(pi: ExtensionAPI, ctx: ExtensionContext, confi
|
|
|
407
169
|
runtime = state;
|
|
408
170
|
lobbyService = createLobbyService(state);
|
|
409
171
|
setServiceState(state);
|
|
410
|
-
// Topic routing (src/lobby/topics.ts):
|
|
411
|
-
//
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
// for tasks/plans/knowledge starts once a non-terminal listener attaches.
|
|
417
|
-
state.unsubscribeTopics = lobbyTopics.onChange(rerender);
|
|
418
|
-
state.unregisterTerminalSurface = promptHub.registerSurface({ name: "terminal", show() {}, withdraw() {} });
|
|
172
|
+
// Topic routing (src/lobby/topics.ts): sources bump a topic when their data
|
|
173
|
+
// changes and the web page rereads it. lobbyFeed (lobby), run updates (tasks),
|
|
174
|
+
// quick fix (quickfix), planner (planner), background sessions (sessions),
|
|
175
|
+
// metrics (metrics), pulls/reviews (git), issues (issues), the prompt hub
|
|
176
|
+
// (prompts), owner/notify events (notices), busy/workspace (status), link
|
|
177
|
+
// checks (excalidraw).
|
|
419
178
|
setQuickFixHandoff((request, builder, reason) => (runtime === state ? handToQuickFix(state, request, builder, reason) : undefined));
|
|
420
179
|
lobbyFeed.clear();
|
|
421
180
|
// Only the newest messages are kept; the feed learns whether earlier ones exist, and loads them when scrolled to.
|
|
422
181
|
lobbyFeed.seedChat(chatFromEntries(ctx.sessionManager.getBranch(), Number.POSITIVE_INFINITY));
|
|
423
|
-
state.unsubscribeFeed = lobbyFeed.onChange(() =>
|
|
424
|
-
lobbyTopics.bump("lobby");
|
|
425
|
-
rerender();
|
|
426
|
-
});
|
|
182
|
+
state.unsubscribeFeed = lobbyFeed.onChange(() => lobbyTopics.bump("lobby"));
|
|
427
183
|
onRunUpdates((runs) => {
|
|
428
184
|
lobbyFeed.runs(runs);
|
|
429
185
|
lobbyTopics.bump("tasks");
|
|
430
186
|
});
|
|
431
|
-
onMinimizeChange((value) => {
|
|
432
|
-
if (value) hideLobby();
|
|
433
|
-
});
|
|
434
187
|
// The owner's clock (pi/owner.ts) delivers comments and messages and drives auto mode; the lobby logs what it did.
|
|
435
188
|
onOwnerEvent((event) => {
|
|
436
189
|
lobbyTopics.bump("notices");
|
|
@@ -439,26 +192,17 @@ export function startLobbyService(pi: ExtensionAPI, ctx: ExtensionContext, confi
|
|
|
439
192
|
else if (event.kind === "auto") lobbyFeed.log("LOBBY", `auto mode ${event.on ? "on" : "off"} for ${event.taskId}`, event.on ? "success" : "info");
|
|
440
193
|
else if (event.kind === "nudge") lobbyFeed.log("LOBBY", `auto mode: keeping the oracle going on ${event.taskId}`, "info");
|
|
441
194
|
else lobbyFeed.log("LOBBY", `auto mode: no progress on ${event.taskId} — it needs you`, "warning");
|
|
442
|
-
rerender();
|
|
443
195
|
});
|
|
444
196
|
return state;
|
|
445
197
|
}
|
|
446
198
|
|
|
447
|
-
/**
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return { render: (width: number) => (miniShown(state) ? [miniLine(miniInput(state), width, lobbyTheme(state.ctx.ui.theme))] : []), invalidate: () => {} };
|
|
455
|
-
}, { placement: "belowEditor" });
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/** Create the session's lobby runtime (interactive master sessions only). */
|
|
459
|
-
export function initLobby(pi: ExtensionAPI, ctx: ExtensionContext, configDir: string): void {
|
|
460
|
-
const state = startLobbyService(pi, ctx, configDir);
|
|
461
|
-
if (state && ctx.mode === "tui") mountTerminalLobby(state);
|
|
199
|
+
/**
|
|
200
|
+
* Whether this pi process serves the web page: an interactive session. A
|
|
201
|
+
* one-shot run has nobody to answer, and the background sessions the lobby
|
|
202
|
+
* starts (RPC) are shown by the page of the window that started them.
|
|
203
|
+
*/
|
|
204
|
+
export function servesPage(ctx: ExtensionContext): boolean {
|
|
205
|
+
return ctx.mode === "tui";
|
|
462
206
|
}
|
|
463
207
|
|
|
464
208
|
interface MasterTurn {
|
|
@@ -481,28 +225,23 @@ function turnStatus(messages: readonly unknown[]): MetricStatus {
|
|
|
481
225
|
return "success";
|
|
482
226
|
}
|
|
483
227
|
|
|
484
|
-
/** Narrate the Master's own turn into the feed and record it as a metric
|
|
228
|
+
/** Narrate the Master's own turn into the feed and record it as a metric. */
|
|
485
229
|
export function registerLobbyEvents(pi: ExtensionAPI, configDir: string): void {
|
|
486
230
|
if (isSubagentProcess()) return;
|
|
487
231
|
let turn: MasterTurn | undefined;
|
|
488
232
|
const track = (ctx: ExtensionContext) => {
|
|
489
233
|
if (runtime) runtime.ctx = ctx;
|
|
490
234
|
};
|
|
491
|
-
pi.registerShortcut(Key.alt("l"), {
|
|
492
|
-
description: "bot-lobby: open or hide the lobby",
|
|
493
|
-
handler: (ctx) => {
|
|
494
|
-
if (!toggleLobby()) ctx.ui.notify("The lobby needs pi's interactive terminal UI.", "warning");
|
|
495
|
-
},
|
|
496
|
-
});
|
|
497
235
|
pi.on("session_start", (_event, ctx) => {
|
|
498
|
-
|
|
499
|
-
// A switch asked for from the lobby lands back in the lobby, on the session it switched to.
|
|
500
|
-
if (takeReopenAfterSwitch()) showLobby("lobby");
|
|
501
|
-
else autoOpenLobby();
|
|
236
|
+
startLobbyService(pi, ctx, configDir);
|
|
502
237
|
});
|
|
503
238
|
pi.on("session_shutdown", () => shutdown());
|
|
504
|
-
pi.on("ui_prompt_start", () =>
|
|
505
|
-
|
|
239
|
+
pi.on("ui_prompt_start", () => {
|
|
240
|
+
lobbyTopics.bump("status");
|
|
241
|
+
});
|
|
242
|
+
pi.on("ui_prompt_end", () => {
|
|
243
|
+
lobbyTopics.bump("status");
|
|
244
|
+
});
|
|
506
245
|
pi.on("agent_start", (_event, ctx) => {
|
|
507
246
|
track(ctx);
|
|
508
247
|
lobbyTopics.bump("status");
|