@a-t-h-i/bot-lobby 0.5.0 → 0.5.1
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 +146 -51
- package/package.json +5 -2
- package/prompts/panel.md +8 -3
- package/prompts/planner.md +9 -4
- package/src/lobby/ask.ts +226 -0
- package/src/lobby/keys.ts +51 -0
- package/src/lobby/layout.ts +192 -3
- package/src/lobby/markdown.ts +48 -0
- package/src/lobby/planner.ts +121 -14
- package/src/lobby/runtime.ts +158 -7
- package/src/lobby/tabs/home.ts +121 -43
- package/src/lobby/tabs/metrics.ts +155 -42
- package/src/lobby/tabs/plan.ts +117 -35
- package/src/lobby/tabs/quickfix.ts +40 -12
- package/src/lobby/tabs/tasks.ts +46 -12
- package/src/lobby/view.ts +868 -113
- package/src/pi/commands.ts +1 -1
- package/src/pi/settings-ui.ts +65 -1
- package/src/schemas/configuration.ts +38 -4
package/src/pi/commands.ts
CHANGED
|
@@ -26,7 +26,7 @@ import { describeRun, runFromLog } from "./run-summary.ts";
|
|
|
26
26
|
import { modelLookup } from "./tools.ts";
|
|
27
27
|
|
|
28
28
|
const HELP = [
|
|
29
|
-
"/bot-lobby Open the lobby: tasks, plan, quick fix,
|
|
29
|
+
"/bot-lobby Open the lobby: tasks, plan, quick fix, metrics (alt+l)",
|
|
30
30
|
"/bot-lobby <request> Start a task through the workflow",
|
|
31
31
|
"/bot-lobby status [taskId] Show the active task",
|
|
32
32
|
"/bot-lobby tasks List tasks",
|
package/src/pi/settings-ui.ts
CHANGED
|
@@ -9,10 +9,12 @@ import { Container, type Component, type Focusable, fuzzyFilter, getKeybindings,
|
|
|
9
9
|
import {
|
|
10
10
|
INHERIT_MODEL,
|
|
11
11
|
isThinkingLevel,
|
|
12
|
+
LOBBY_PANELS,
|
|
12
13
|
SCOUT_THINKING,
|
|
13
14
|
SUBAGENT_KINDS,
|
|
14
15
|
type AgentModelConfig,
|
|
15
16
|
type BotLobbyConfig,
|
|
17
|
+
type LobbyPanel,
|
|
16
18
|
type SubagentKind,
|
|
17
19
|
} from "../schemas/configuration.ts";
|
|
18
20
|
import { globalConfigPath, loadConfig, saveConfig } from "../state/project.ts";
|
|
@@ -314,6 +316,15 @@ async function editEntry(pi: ExtensionAPI, ctx: ExtensionContext, kind: Settings
|
|
|
314
316
|
}
|
|
315
317
|
}
|
|
316
318
|
|
|
319
|
+
/** One agent's settings on their own: the lobby's Quick fix and Plan tabs open their agent's entry directly. */
|
|
320
|
+
export async function openEntrySettings(pi: ExtensionAPI, ctx: ExtensionContext, kind: SettingsKind): Promise<void> {
|
|
321
|
+
if (ctx.mode !== "tui") {
|
|
322
|
+
ctx.ui.notify(`bot-lobby settings live in ${globalConfigPath()}; edit that file outside the TUI.`, "info");
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
await editEntry(pi, ctx, kind);
|
|
326
|
+
}
|
|
327
|
+
|
|
317
328
|
/**
|
|
318
329
|
* Subagents never inherit silently: any subagent whose model is still unset is
|
|
319
330
|
* pinned to the session's current model and saved, so settings always show
|
|
@@ -338,6 +349,57 @@ function entryDescription(kind: SettingsKind, view: EntryView): string {
|
|
|
338
349
|
return `${view.model} · ${thinking}${limit}${custom}`;
|
|
339
350
|
}
|
|
340
351
|
|
|
352
|
+
/** The lobby's on/off settings as the settings menu lists them; `panel:*` are the Lobby tab's panes. */
|
|
353
|
+
export type LobbySwitch = "autoOpen" | "autoAsk" | "mouse" | "issues" | `panel:${LobbyPanel}`;
|
|
354
|
+
|
|
355
|
+
const PANEL_SWITCH_LABELS: Record<LobbyPanel, string> = { scene: "Zen scene pane", conversation: "Conversation pane", activity: "Activity log pane", thinking: "Thinking pane" };
|
|
356
|
+
|
|
357
|
+
export const LOBBY_SWITCHES: ReadonlyArray<{ id: LobbySwitch; label: string; help: string }> = [
|
|
358
|
+
{ id: "autoOpen", label: "Open with a task", help: "open the lobby when this session starts or resumes a task" },
|
|
359
|
+
{ id: "autoAsk", label: "Ask at once", help: "put the panel's questions to you as soon as a round ends, while the Plan tab is open" },
|
|
360
|
+
{ id: "mouse", label: "Mouse", help: "click tabs and draft lines, scroll with the wheel (shift+drag still selects text)" },
|
|
361
|
+
{ id: "issues", label: "Issues tab", help: "the GitHub Issues tab" },
|
|
362
|
+
...LOBBY_PANELS.map((panel) => ({ id: `panel:${panel}` as const, label: PANEL_SWITCH_LABELS[panel], help: "shown on the Lobby tab; its key in the lobby toggles it too" })),
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
export function lobbySwitch(config: BotLobbyConfig, id: LobbySwitch): boolean {
|
|
366
|
+
if (id.startsWith("panel:")) return config.lobby.panels[id.slice("panel:".length) as LobbyPanel];
|
|
367
|
+
return config.lobby[id as Exclude<LobbySwitch, `panel:${string}`>];
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** The config with one lobby setting flipped. */
|
|
371
|
+
export function toggleLobbySwitch(config: BotLobbyConfig, id: LobbySwitch): BotLobbyConfig {
|
|
372
|
+
const value = !lobbySwitch(config, id);
|
|
373
|
+
if (id.startsWith("panel:")) {
|
|
374
|
+
const panel = id.slice("panel:".length) as LobbyPanel;
|
|
375
|
+
return { ...config, lobby: { ...config.lobby, panels: { ...config.lobby.panels, [panel]: value } } };
|
|
376
|
+
}
|
|
377
|
+
return { ...config, lobby: { ...config.lobby, [id]: value } };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** On/off settings for the lobby; enter flips one and saves it. Key rebinding stays in the file (lobby.keys). */
|
|
381
|
+
async function editLobby(ctx: ExtensionContext): Promise<void> {
|
|
382
|
+
for (;;) {
|
|
383
|
+
const config = loadConfig();
|
|
384
|
+
const items: SelectItem[] = LOBBY_SWITCHES.map((entry) => ({ value: entry.id, label: entry.label, description: `${lobbySwitch(config, entry.id) ? "on" : "off"} · ${entry.help}` }));
|
|
385
|
+
items.push({ value: "back", label: "Back", description: `keys: lobby.keys in ${globalConfigPath()}` });
|
|
386
|
+
const choice = await pick(ctx, "bot-lobby settings · Lobby", items);
|
|
387
|
+
if (!choice || choice === "back") return;
|
|
388
|
+
saveConfig(toggleLobbySwitch(config, choice as LobbySwitch));
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function lobbySummary(config: BotLobbyConfig): string {
|
|
393
|
+
const hidden = LOBBY_PANELS.filter((panel) => !config.lobby.panels[panel]);
|
|
394
|
+
return [
|
|
395
|
+
config.lobby.autoOpen ? "opens with a task" : "opens on alt+l",
|
|
396
|
+
config.lobby.autoAsk ? "asks at once" : "asks on enter",
|
|
397
|
+
config.lobby.mouse ? "mouse" : "no mouse",
|
|
398
|
+
...(config.lobby.issues ? ["issues tab"] : []),
|
|
399
|
+
...(hidden.length > 0 ? [`hidden: ${hidden.join(", ")}`] : []),
|
|
400
|
+
].join(" · ");
|
|
401
|
+
}
|
|
402
|
+
|
|
341
403
|
/** Open the per-agent settings editor; every change is written to the global config. */
|
|
342
404
|
export async function openSettings(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
|
|
343
405
|
if (ctx.mode !== "tui") {
|
|
@@ -352,9 +414,11 @@ export async function openSettings(pi: ExtensionAPI, ctx: ExtensionContext): Pro
|
|
|
352
414
|
for (;;) {
|
|
353
415
|
const config = loadConfig();
|
|
354
416
|
const items: SelectItem[] = SETTINGS_KINDS.map((kind) => ({ value: kind, label: kindLabel(kind), description: entryDescription(kind, entryView(config, kind)) }));
|
|
417
|
+
items.push({ value: "lobby", label: "Lobby", description: lobbySummary(config) });
|
|
355
418
|
items.push({ value: "close", label: "Close" });
|
|
356
419
|
const choice = await pick(ctx, "bot-lobby settings", items);
|
|
357
420
|
if (!choice || choice === "close") return;
|
|
358
|
-
|
|
421
|
+
if (choice === "lobby") await editLobby(ctx);
|
|
422
|
+
else await editEntry(pi, ctx, choice as SettingsKind);
|
|
359
423
|
}
|
|
360
424
|
}
|
|
@@ -83,12 +83,26 @@ export function isPanelMember(value: string): value is PanelMember {
|
|
|
83
83
|
return (PANEL_MEMBERS as readonly string[]).includes(value);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
/** Lobby panes that can be shown or hidden (the Lobby tab's scene, conversation, activity log and thinking). */
|
|
87
|
+
export const LOBBY_PANELS = ["scene", "conversation", "activity", "thinking"] as const;
|
|
88
|
+
export type LobbyPanel = (typeof LOBBY_PANELS)[number];
|
|
89
|
+
|
|
86
90
|
/** The full-screen lobby. */
|
|
87
91
|
export interface LobbyConfig {
|
|
88
92
|
/** Open by itself when this session starts or resumes a task. */
|
|
89
93
|
autoOpen: boolean;
|
|
90
94
|
/** Who sits on the planning panel next to the oracle, until toggled in the Plan tab. */
|
|
91
95
|
planningPanel: PanelMember[];
|
|
96
|
+
/** Put the panel's questions to the user as soon as a round ends, while the Plan tab is open. */
|
|
97
|
+
autoAsk: boolean;
|
|
98
|
+
/** Show the GitHub Issues tab (off for now). */
|
|
99
|
+
issues: boolean;
|
|
100
|
+
/** Which panes show; toggled with keys in the lobby and remembered here. */
|
|
101
|
+
panels: Record<LobbyPanel, boolean>;
|
|
102
|
+
/** Key overrides by action name, e.g. `{ "toggleThinking": "alt+t" }`. */
|
|
103
|
+
keys: Record<string, string>;
|
|
104
|
+
/** Clicks and the wheel work in the lobby (click a draft line to comment on it); shift+drag still selects text. */
|
|
105
|
+
mouse: boolean;
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
export interface BotLobbyConfig {
|
|
@@ -135,7 +149,15 @@ export const DEFAULT_CONFIG: BotLobbyConfig = {
|
|
|
135
149
|
scratchpadMaxParagraphs: 4,
|
|
136
150
|
scratchpadMaxChars: 2000,
|
|
137
151
|
},
|
|
138
|
-
lobby: {
|
|
152
|
+
lobby: {
|
|
153
|
+
autoOpen: true,
|
|
154
|
+
planningPanel: [...PANEL_MEMBERS],
|
|
155
|
+
autoAsk: true,
|
|
156
|
+
issues: false,
|
|
157
|
+
panels: { scene: true, conversation: true, activity: true, thinking: true },
|
|
158
|
+
keys: {},
|
|
159
|
+
mouse: true,
|
|
160
|
+
},
|
|
139
161
|
};
|
|
140
162
|
|
|
141
163
|
function positive(value: unknown): number | undefined {
|
|
@@ -159,14 +181,26 @@ function normalizeScout(override: Partial<ScoutConfig> | undefined): ScoutConfig
|
|
|
159
181
|
};
|
|
160
182
|
}
|
|
161
183
|
|
|
184
|
+
function flag(value: unknown, fallback: boolean): boolean {
|
|
185
|
+
return typeof value === "boolean" ? value : fallback;
|
|
186
|
+
}
|
|
187
|
+
|
|
162
188
|
function normalizeLobby(value: unknown): LobbyConfig {
|
|
163
|
-
const source = value as { autoOpen?: unknown; planningPanel?: unknown } | undefined;
|
|
189
|
+
const source = value as { autoOpen?: unknown; planningPanel?: unknown; autoAsk?: unknown; issues?: unknown; panels?: unknown; keys?: unknown; mouse?: unknown } | undefined;
|
|
190
|
+
const defaults = DEFAULT_CONFIG.lobby;
|
|
164
191
|
const panel = Array.isArray(source?.planningPanel)
|
|
165
192
|
? [...new Set(source.planningPanel.filter((entry): entry is PanelMember => typeof entry === "string" && isPanelMember(entry)))]
|
|
166
|
-
: [...
|
|
193
|
+
: [...defaults.planningPanel];
|
|
194
|
+
const panels = (source?.panels ?? {}) as Partial<Record<LobbyPanel, unknown>>;
|
|
195
|
+
const keys = source?.keys && typeof source.keys === "object" ? source.keys as Record<string, unknown> : {};
|
|
167
196
|
return {
|
|
168
|
-
autoOpen:
|
|
197
|
+
autoOpen: flag(source?.autoOpen, defaults.autoOpen),
|
|
169
198
|
planningPanel: PANEL_MEMBERS.filter((member) => panel.includes(member)),
|
|
199
|
+
autoAsk: flag(source?.autoAsk, defaults.autoAsk),
|
|
200
|
+
issues: flag(source?.issues, defaults.issues),
|
|
201
|
+
panels: Object.fromEntries(LOBBY_PANELS.map((name) => [name, flag(panels[name], defaults.panels[name])])) as Record<LobbyPanel, boolean>,
|
|
202
|
+
keys: Object.fromEntries(Object.entries(keys).filter((entry): entry is [string, string] => typeof entry[1] === "string" && entry[1].trim().length > 0)),
|
|
203
|
+
mouse: flag(source?.mouse, defaults.mouse),
|
|
170
204
|
};
|
|
171
205
|
}
|
|
172
206
|
|