@a-t-h-i/bot-lobby 0.6.55 → 0.6.56
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/THIRD_PARTY_NOTICES.md +12 -1
- package/package.json +4 -1
- package/src/execution/agent-runner.ts +4 -0
- package/src/execution/pi-runner.ts +9 -4
- package/src/harness/acp.ts +272 -0
- package/src/harness/catalog.ts +257 -0
- package/src/harness/claude-sdk.ts +169 -0
- package/src/harness/claude.ts +188 -0
- package/src/harness/cli-args.ts +62 -0
- package/src/harness/codex.ts +155 -0
- package/src/harness/instructions.ts +45 -0
- package/src/harness/model-manifest.json +829 -0
- package/src/harness/process.ts +175 -0
- package/src/harness/runner.ts +188 -0
- package/src/harness/status.ts +115 -0
- package/src/lobby/host.ts +2 -0
- package/src/lobby/planner.ts +2 -1
- package/src/lobby/pr-review.ts +1 -0
- package/src/lobby/quickfix.ts +4 -1
- package/src/master/master.ts +3 -3
- package/src/master/research.ts +1 -0
- package/src/pi/model-support.ts +6 -2
- package/src/schemas/configuration.ts +33 -13
- package/src/schemas/harness.ts +122 -0
- package/src/webui/api/index.ts +2 -1
- package/src/webui/api/settings.ts +7 -1
- package/src/webui/dev/fake-service.ts +13 -0
- package/src/webui/protocol.ts +9 -0
- package/webui/dist/assets/{TerminalView-CIfjkb5H.js → TerminalView-BVehQ7yB.js} +1 -1
- package/webui/dist/assets/index-BKiKxUUG.js +114 -0
- package/webui/dist/assets/index-DOLQ4FDd.css +2 -0
- package/webui/dist/build.json +15 -11
- package/webui/dist/index.html +2 -2
- package/webui/dist/assets/index-BkXhtB6A.css +0 -2
- package/webui/dist/assets/index-DIM2-FUQ.js +0 -114
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Domain, Role } from "./agent.ts";
|
|
2
|
+
import { defaultHarnesses, harnessChoiceFields, harnessRunOf, normalizeHarnesses, type HarnessChoice, type HarnessesConfig, type HarnessRun } from "./harness.ts";
|
|
2
3
|
|
|
3
4
|
export type ModelRef = "inherit" | string;
|
|
4
5
|
|
|
@@ -22,7 +23,7 @@ export function isThinkingLevel(value: string): value is ThinkingLevelName {
|
|
|
22
23
|
return (THINKING_LEVELS as readonly string[]).includes(value);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export interface AgentModelConfig {
|
|
26
|
+
export interface AgentModelConfig extends HarnessChoice {
|
|
26
27
|
model: ModelRef;
|
|
27
28
|
thinking: string;
|
|
28
29
|
/** Free-form instructions layered on top of this agent's built-in prompt. */
|
|
@@ -36,7 +37,7 @@ export interface AgentModelConfig {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/** Scouts pick a model only; their thinking is fixed at `SCOUT_THINKING`. */
|
|
39
|
-
export interface ScoutConfig {
|
|
40
|
+
export interface ScoutConfig extends HarnessChoice {
|
|
40
41
|
mcpTools?: string[];
|
|
41
42
|
/** See AgentModelConfig.skills. */
|
|
42
43
|
skills?: string[];
|
|
@@ -271,6 +272,8 @@ export interface BotLobbyConfig {
|
|
|
271
272
|
knowledge: KnowledgeConfig;
|
|
272
273
|
lobby: LobbyConfig;
|
|
273
274
|
classifier: ClassifierConfig;
|
|
275
|
+
/** Third-party harnesses a subagent can run on instead of pi, each with its own settings. */
|
|
276
|
+
harnesses: HarnessesConfig;
|
|
274
277
|
}
|
|
275
278
|
|
|
276
279
|
export const DEFAULT_CONFIG: BotLobbyConfig = {
|
|
@@ -349,6 +352,7 @@ export const DEFAULT_CONFIG: BotLobbyConfig = {
|
|
|
349
352
|
effort: { cheapModel: INHERIT_MODEL },
|
|
350
353
|
exclude: [],
|
|
351
354
|
},
|
|
355
|
+
harnesses: defaultHarnesses(),
|
|
352
356
|
};
|
|
353
357
|
|
|
354
358
|
/** Choices the settings menu cycles through for the planning round limit; 0 = unlimited. */
|
|
@@ -376,12 +380,18 @@ export function normalizeMcpTools(value: unknown): string[] {
|
|
|
376
380
|
return Array.isArray(value) ? [...new Set(value.filter((tool): tool is string => typeof tool === "string" && /^mcp__[A-Za-z0-9_]+__[A-Za-z0-9_]+$/.test(tool)))] : [];
|
|
377
381
|
}
|
|
378
382
|
|
|
379
|
-
/**
|
|
380
|
-
|
|
383
|
+
/**
|
|
384
|
+
* Merge one agent's override over its default; legacy `inherit` or unknown
|
|
385
|
+
* thinking falls back to the default level. Only subagents may name a
|
|
386
|
+
* harness (`pi` or an unknown one drops the field): the oracle and the
|
|
387
|
+
* planner live in the pi session.
|
|
388
|
+
*/
|
|
389
|
+
function normalizeAgent(base: AgentModelConfig, override: Partial<AgentModelConfig> | undefined, harness = true): AgentModelConfig {
|
|
381
390
|
const merged = { ...base, ...(override ?? {}) };
|
|
382
391
|
const thinking = isThinkingLevel(merged.thinking) ? merged.thinking : base.thinking;
|
|
383
392
|
const mcpTools = normalizeMcpTools(merged.mcpTools);
|
|
384
|
-
|
|
393
|
+
const choice = harness ? harnessChoiceFields(merged) : {};
|
|
394
|
+
return { model: merged.model, thinking, ...(mcpTools.length ? { mcpTools } : {}), ...skillsField(merged.skills), ...(typeof merged.instructions === "string" ? { instructions: merged.instructions } : {}), ...(merged.autoCompact === false ? { autoCompact: false } : {}), ...choice };
|
|
385
395
|
}
|
|
386
396
|
|
|
387
397
|
/** Scouts keep a model and a tool-call cap; any thinking value in the file is dropped. */
|
|
@@ -394,6 +404,7 @@ function normalizeScout(override: Partial<ScoutConfig> | undefined): ScoutConfig
|
|
|
394
404
|
...(mcpTools.length ? { mcpTools } : {}),
|
|
395
405
|
...skillsField(override?.skills),
|
|
396
406
|
...(override?.autoCompact === false ? { autoCompact: false } : {}),
|
|
407
|
+
...harnessChoiceFields(override ?? {}),
|
|
397
408
|
};
|
|
398
409
|
}
|
|
399
410
|
|
|
@@ -517,7 +528,7 @@ export function resolveConfig(partial: unknown): BotLobbyConfig {
|
|
|
517
528
|
const knowledge = { ...DEFAULT_CONFIG.knowledge, ...(src.knowledge as Partial<KnowledgeConfig> | undefined) };
|
|
518
529
|
const srcAgents = (src.agents ?? {}) as Partial<BotLobbyConfig["agents"]>;
|
|
519
530
|
return {
|
|
520
|
-
master: normalizeAgent(DEFAULT_CONFIG.master, src.master as Partial<AgentModelConfig> | undefined),
|
|
531
|
+
master: normalizeAgent(DEFAULT_CONFIG.master, src.master as Partial<AgentModelConfig> | undefined, false),
|
|
521
532
|
agents: {
|
|
522
533
|
designer: normalizeAgent(DEFAULT_CONFIG.agents.designer, srcAgents.designer),
|
|
523
534
|
backend: normalizeAgent(DEFAULT_CONFIG.agents.backend, srcAgents.backend),
|
|
@@ -526,12 +537,13 @@ export function resolveConfig(partial: unknown): BotLobbyConfig {
|
|
|
526
537
|
scout: normalizeScout(src.scout as Partial<ScoutConfig> | undefined),
|
|
527
538
|
researcher: normalizeAgent(DEFAULT_CONFIG.researcher, src.researcher as Partial<AgentModelConfig> | undefined),
|
|
528
539
|
quickFix: normalizeAgent(DEFAULT_CONFIG.quickFix, src.quickFix as Partial<AgentModelConfig> | undefined),
|
|
529
|
-
planner: normalizeAgent(DEFAULT_CONFIG.planner, src.planner as Partial<AgentModelConfig> | undefined),
|
|
540
|
+
planner: normalizeAgent(DEFAULT_CONFIG.planner, src.planner as Partial<AgentModelConfig> | undefined, false),
|
|
530
541
|
workflow,
|
|
531
542
|
lint: normalizeLint(src.lint),
|
|
532
543
|
knowledge,
|
|
533
544
|
lobby: normalizeLobby(src.lobby),
|
|
534
545
|
classifier: normalizeClassifier(src.classifier),
|
|
546
|
+
harnesses: normalizeHarnesses(src.harnesses),
|
|
535
547
|
};
|
|
536
548
|
}
|
|
537
549
|
|
|
@@ -553,6 +565,14 @@ export interface AgentProfile {
|
|
|
553
565
|
skills?: string[];
|
|
554
566
|
/** False when its settings turn auto-compaction off. */
|
|
555
567
|
autoCompact?: boolean;
|
|
568
|
+
/** The third-party harness it runs on; absent runs it on pi. */
|
|
569
|
+
harness?: HarnessRun;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** `{ harness }` when an entry runs on a third-party harness, else nothing. */
|
|
573
|
+
function harnessField(config: BotLobbyConfig, entry: HarnessChoice): { harness?: HarnessRun } {
|
|
574
|
+
const harness = harnessRunOf(entry, config.harnesses);
|
|
575
|
+
return harness ? { harness } : {};
|
|
556
576
|
}
|
|
557
577
|
|
|
558
578
|
/** `{ autoCompact: false }` when an entry turns auto-compaction off, else nothing. */
|
|
@@ -586,10 +606,10 @@ export function agentProfile(config: BotLobbyConfig, domain: Domain, role: Role)
|
|
|
586
606
|
const kind = profileKind(domain, role);
|
|
587
607
|
const instructions = config.agents[domain].instructions;
|
|
588
608
|
if (kind === "scout") {
|
|
589
|
-
return { kind, model: modelOf(config.scout.model), thinking: SCOUT_THINKING, instructions, ...grantFields(config.scout), ...compactField(config.scout) };
|
|
609
|
+
return { kind, model: modelOf(config.scout.model), thinking: SCOUT_THINKING, instructions, ...grantFields(config.scout), ...compactField(config.scout), ...harnessField(config, config.scout) };
|
|
590
610
|
}
|
|
591
611
|
const entry = kind === "researcher" ? config.researcher : config.agents[kind];
|
|
592
|
-
return { kind, model: modelOf(entry.model), thinking: entry.thinking, instructions, ...grantFields(entry), ...compactField(entry) };
|
|
612
|
+
return { kind, model: modelOf(entry.model), thinking: entry.thinking, instructions, ...grantFields(entry), ...compactField(entry), ...harnessField(config, entry) };
|
|
593
613
|
}
|
|
594
614
|
|
|
595
615
|
/** The settings entry of a lobby agent (quick fix or planner). */
|
|
@@ -598,18 +618,18 @@ export function lobbyAgentConfig(config: BotLobbyConfig, kind: LobbyAgentKind):
|
|
|
598
618
|
}
|
|
599
619
|
|
|
600
620
|
/** Profile for a lobby agent run, from settings alone; `model` is undefined while unset. */
|
|
601
|
-
export function lobbyAgentProfile(config: BotLobbyConfig, kind: LobbyAgentKind): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean } {
|
|
621
|
+
export function lobbyAgentProfile(config: BotLobbyConfig, kind: LobbyAgentKind): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean; harness?: HarnessRun } {
|
|
602
622
|
const entry = lobbyAgentConfig(config, kind);
|
|
603
|
-
return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry) };
|
|
623
|
+
return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry), ...harnessField(config, entry) };
|
|
604
624
|
}
|
|
605
625
|
|
|
606
626
|
/**
|
|
607
627
|
* A planning panel seat's profile: the domain's (or the researcher's) model,
|
|
608
628
|
* thinking, custom instructions and grants.
|
|
609
629
|
*/
|
|
610
|
-
export function panelMemberProfile(config: BotLobbyConfig, member: PanelMember): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean } {
|
|
630
|
+
export function panelMemberProfile(config: BotLobbyConfig, member: PanelMember): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean; harness?: HarnessRun } {
|
|
611
631
|
const entry = member === "researcher" ? config.researcher : config.agents[member];
|
|
612
|
-
return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry) };
|
|
632
|
+
return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry), ...harnessField(config, entry) };
|
|
613
633
|
}
|
|
614
634
|
|
|
615
635
|
/** The model and thinking a worker's `ask_expert` consults: the configured expert, or the oracle's model. */
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Third-party coding harnesses (Codex CLI, Claude Code, ACP agents such as
|
|
3
|
+
* Antigravity, Gemini CLI and OpenCode) that a subagent can run on instead of
|
|
4
|
+
* pi. The oracle always stays on pi: Bot Lobby is a pi extension, so the
|
|
5
|
+
* workflow, knowledge and compaction live there. A harness only replaces the
|
|
6
|
+
* process one subagent step runs in, with our role prompt injected into it.
|
|
7
|
+
*
|
|
8
|
+
* The per-harness fields follow T3 Code's provider settings
|
|
9
|
+
* (`packages/contracts/src/settings.ts`: binary path, home path, launch
|
|
10
|
+
* arguments, custom models, auto-compact window).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Harnesses a subagent can run on; `pi` (absent in the config) is the built-in default. */
|
|
14
|
+
export const HARNESS_IDS = ["codex", "claude", "antigravity", "gemini", "opencode", "acp"] as const;
|
|
15
|
+
export type HarnessId = (typeof HARNESS_IDS)[number];
|
|
16
|
+
|
|
17
|
+
export function isHarnessId(value: unknown): value is HarnessId {
|
|
18
|
+
return typeof value === "string" && (HARNESS_IDS as readonly string[]).includes(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** How Bot Lobby talks to a harness. */
|
|
22
|
+
export type HarnessProtocol = "codex-exec" | "claude-stream" | "acp";
|
|
23
|
+
|
|
24
|
+
export interface HarnessSettings {
|
|
25
|
+
/** The executable; empty uses the harness's usual command on PATH. */
|
|
26
|
+
binaryPath: string;
|
|
27
|
+
/** Extra arguments, shell-quoted, passed on every run (for a custom ACP agent: the arguments that start it). */
|
|
28
|
+
launchArgs: string;
|
|
29
|
+
/** Codex's CODEX_HOME or Claude Code's CLAUDE_CONFIG_DIR; empty keeps the harness's own. */
|
|
30
|
+
homePath: string;
|
|
31
|
+
/** Who serves the models: Codex's `model_provider` (or `ollama`/`lmstudio` for local models), Claude Code's `anthropic`/`bedrock`/`vertex`; empty keeps the harness's own. */
|
|
32
|
+
modelProvider: string;
|
|
33
|
+
/** Compact the agent's context after this many tokens; 0 keeps the harness's own threshold. */
|
|
34
|
+
compactAt: number;
|
|
35
|
+
/** Put Bot Lobby's role prompt (the agent's instructions, knowledge and report format) into the harness; off sends the task alone. */
|
|
36
|
+
injectPrompts: boolean;
|
|
37
|
+
/** Model ids typed in by hand, listed after the harness's own. */
|
|
38
|
+
customModels: string[];
|
|
39
|
+
/** Antigravity's `localharness_external` next to its ACP server (ANTIGRAVITY_HARNESS_PATH); empty when the server finds it itself. */
|
|
40
|
+
harnessPath: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const DEFAULT_HARNESS_SETTINGS: HarnessSettings = {
|
|
44
|
+
binaryPath: "",
|
|
45
|
+
launchArgs: "",
|
|
46
|
+
homePath: "",
|
|
47
|
+
modelProvider: "",
|
|
48
|
+
compactAt: 0,
|
|
49
|
+
injectPrompts: true,
|
|
50
|
+
customModels: [],
|
|
51
|
+
harnessPath: "",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type HarnessesConfig = Record<HarnessId, HarnessSettings>;
|
|
55
|
+
|
|
56
|
+
export function defaultHarnesses(): HarnessesConfig {
|
|
57
|
+
return Object.fromEntries(HARNESS_IDS.map((id) => [id, { ...DEFAULT_HARNESS_SETTINGS, customModels: [] as string[] }])) as unknown as HarnessesConfig;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Smallest and largest compaction threshold a harness accepts, in tokens. */
|
|
61
|
+
export const HARNESS_COMPACT_MIN = 10_000;
|
|
62
|
+
export const HARNESS_COMPACT_MAX = 2_000_000;
|
|
63
|
+
|
|
64
|
+
function text(value: unknown, fallback: string): string {
|
|
65
|
+
return typeof value === "string" ? value.trim() : fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function compactAt(value: unknown): number {
|
|
69
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return 0;
|
|
70
|
+
return Math.min(HARNESS_COMPACT_MAX, Math.max(HARNESS_COMPACT_MIN, Math.round(value)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function modelIds(value: unknown): string[] {
|
|
74
|
+
if (!Array.isArray(value)) return [];
|
|
75
|
+
return [...new Set(value.filter((entry): entry is string => typeof entry === "string").map((entry) => entry.trim()).filter((entry) => entry.length > 0 && entry.length <= 200 && !/\s/.test(entry)))];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeHarness(value: unknown): HarnessSettings {
|
|
79
|
+
const source = (value && typeof value === "object" ? value : {}) as Record<string, unknown>;
|
|
80
|
+
const base = DEFAULT_HARNESS_SETTINGS;
|
|
81
|
+
return {
|
|
82
|
+
binaryPath: text(source.binaryPath, base.binaryPath),
|
|
83
|
+
launchArgs: text(source.launchArgs, base.launchArgs),
|
|
84
|
+
homePath: text(source.homePath, base.homePath),
|
|
85
|
+
modelProvider: text(source.modelProvider, base.modelProvider),
|
|
86
|
+
compactAt: compactAt(source.compactAt),
|
|
87
|
+
injectPrompts: source.injectPrompts !== false,
|
|
88
|
+
customModels: modelIds(source.customModels),
|
|
89
|
+
harnessPath: text(source.harnessPath, base.harnessPath),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function normalizeHarnesses(value: unknown): HarnessesConfig {
|
|
94
|
+
const source = (value && typeof value === "object" ? value : {}) as Record<string, unknown>;
|
|
95
|
+
return Object.fromEntries(HARNESS_IDS.map((id) => [id, normalizeHarness(source[id])])) as HarnessesConfig;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** An agent entry's harness fields: absent for pi, else the harness and its model (empty: the harness's default). */
|
|
99
|
+
export interface HarnessChoice {
|
|
100
|
+
harness?: HarnessId;
|
|
101
|
+
harnessModel?: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function harnessChoiceFields(entry: { harness?: unknown; harnessModel?: unknown }): HarnessChoice {
|
|
105
|
+
if (!isHarnessId(entry.harness)) return {};
|
|
106
|
+
const model = text(entry.harnessModel, "");
|
|
107
|
+
return { harness: entry.harness, ...(model ? { harnessModel: model } : {}) };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Everything one run on a harness needs from the settings. */
|
|
111
|
+
export interface HarnessRun {
|
|
112
|
+
id: HarnessId;
|
|
113
|
+
/** Empty: the harness's own default model. */
|
|
114
|
+
model: string;
|
|
115
|
+
settings: HarnessSettings;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The harness an agent entry runs on, with that harness's settings; undefined for pi. */
|
|
119
|
+
export function harnessRunOf(entry: HarnessChoice, harnesses: HarnessesConfig): HarnessRun | undefined {
|
|
120
|
+
if (!entry.harness) return undefined;
|
|
121
|
+
return { id: entry.harness, model: entry.harnessModel ?? "", settings: harnesses[entry.harness] };
|
|
122
|
+
}
|
package/src/webui/api/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { plansDiscard, plansGet, plansStart } from "./plans.ts";
|
|
|
21
21
|
import { gitCancelReview, gitJev, gitPull, gitPulls, gitReview } from "./git.ts";
|
|
22
22
|
import { issuesCreate, issuesGet, issuesList } from "./issues.ts";
|
|
23
23
|
import { sessionsAnswer, sessionsArchive, sessionsChat, sessionsList, sessionsMessage, sessionsRename, sessionsStart, sessionsStop, sessionsSwitch } from "./sessions.ts";
|
|
24
|
-
import { settingsGet, settingsGrants, settingsLinters, settingsSet } from "./settings.ts";
|
|
24
|
+
import { settingsGet, settingsGrants, settingsHarnesses, settingsLinters, settingsSet } from "./settings.ts";
|
|
25
25
|
import { terminalClose, terminalInput, terminalList, terminalOpen, terminalResize } from "./terminal.ts";
|
|
26
26
|
import { MAX_INPUT_CHARS } from "../terminal.ts";
|
|
27
27
|
import { themesChoose, themesGet, themesRemove, themesRename, themesSave } from "./themes.ts";
|
|
@@ -386,6 +386,7 @@ function buildRoutes(): Record<string, Route> {
|
|
|
386
386
|
};
|
|
387
387
|
routes["settings.linters"] = { schema: Empty, run: (_body, ctx) => settingsLinters(ctx) };
|
|
388
388
|
routes["settings.grants"] = { schema: Empty, run: (_body, ctx) => settingsGrants(ctx) };
|
|
389
|
+
routes["settings.harnesses"] = { schema: Empty, run: (_body, ctx) => settingsHarnesses(ctx) };
|
|
389
390
|
const ThemeId = Type.String({ minLength: 1, maxLength: 40 });
|
|
390
391
|
const ThemeName = Type.String({ minLength: 1, maxLength: 200 });
|
|
391
392
|
const ThemeVars = Type.Optional(Type.Record(Type.String({ maxLength: 60 }), Type.String({ maxLength: 400 })));
|
|
@@ -10,7 +10,8 @@ import { lobbyTopics } from "../../lobby/topics.ts";
|
|
|
10
10
|
import { prefillModels } from "../../pi/model-settings.ts";
|
|
11
11
|
import { kindLabel } from "../../pi/model-support.ts";
|
|
12
12
|
import { pushNotice } from "../notices.ts";
|
|
13
|
-
import type { SettingsGrants, SettingsInfo, SettingsLinter } from "../protocol.ts";
|
|
13
|
+
import type { SettingsGrants, SettingsHarnesses, SettingsInfo, SettingsLinter } from "../protocol.ts";
|
|
14
|
+
import { harnessStatuses } from "../../harness/status.ts";
|
|
14
15
|
import type { ApiContext } from "./index.ts";
|
|
15
16
|
import { fail } from "./index.ts";
|
|
16
17
|
|
|
@@ -88,6 +89,11 @@ export function settingsGrants(ctx: ApiContext): SettingsGrants {
|
|
|
88
89
|
return ctx.service.grants?.() ?? { mcpTools: [], skills: [] };
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
/** Each harness, found on PATH or not, with its models; the dev server's fake service answers for itself. */
|
|
93
|
+
export async function settingsHarnesses(ctx: ApiContext): Promise<SettingsHarnesses> {
|
|
94
|
+
return { harnesses: ctx.service.harnesses?.() ?? await harnessStatuses(readConfig(ctx).harnesses) };
|
|
95
|
+
}
|
|
96
|
+
|
|
91
97
|
/** Merge, normalise and save the patch; the lobby rereads the config. */
|
|
92
98
|
export function settingsSet(body: { patch: Record<string, unknown> }, ctx: ApiContext): { config: BotLobbyConfig } {
|
|
93
99
|
checkPort(body.patch);
|
|
@@ -13,6 +13,8 @@ import { cleanSessionName } from "../../lobby/sessions.ts";
|
|
|
13
13
|
import type { PlanComment } from "../../state/comments.ts";
|
|
14
14
|
import type { LobbyService } from "../../lobby/host.ts";
|
|
15
15
|
import { loadScenario, type ScenarioFixture } from "./fixtures.ts";
|
|
16
|
+
import { HARNESSES, harnessCommand, harnessModels } from "../../harness/catalog.ts";
|
|
17
|
+
import { HARNESS_IDS } from "../../schemas/harness.ts";
|
|
16
18
|
import type { PlanRecord, PlanSummary } from "../../state/plan-history.ts";
|
|
17
19
|
|
|
18
20
|
interface StreamState {
|
|
@@ -408,6 +410,17 @@ export function createFixtureService(name: string, feed = new LobbyFeed()): Lobb
|
|
|
408
410
|
},
|
|
409
411
|
configChanged: () => {},
|
|
410
412
|
models: () => models.map((model) => ({ id: model.id, label: model.label, thinkingLevels: [...model.thinkingLevels] })),
|
|
413
|
+
// A fixed machine for screenshots and browser tests: Codex and Claude Code installed, the rest not.
|
|
414
|
+
harnesses: () => HARNESS_IDS.map((id) => {
|
|
415
|
+
const info = HARNESSES[id];
|
|
416
|
+
const installed: Partial<Record<string, string>> = { codex: "0.163.0", claude: "2.1.306" };
|
|
417
|
+
const command = harnessCommand(id, config.harnesses[id]).command;
|
|
418
|
+
return {
|
|
419
|
+
id, label: info.label, blurb: info.blurb, protocol: info.protocol, command, providers: info.providers, effort: info.effort,
|
|
420
|
+
...(installed[id] ? { path: `/usr/local/bin/${command}`, version: installed[id] } : {}),
|
|
421
|
+
models: harnessModels(id, config.harnesses[id]),
|
|
422
|
+
};
|
|
423
|
+
}),
|
|
411
424
|
classifierModels: () => [{ id: "opencode/jev-1.13-free", label: "Jev 1.13 (free)" }, { id: "openrouter/liquid/d1", label: "Liquid D1" }],
|
|
412
425
|
grants: () => ({
|
|
413
426
|
mcpTools: [
|
package/src/webui/protocol.ts
CHANGED
|
@@ -15,6 +15,8 @@ import type { BotLobbyConfig, PanelMember } from "../schemas/configuration.ts";
|
|
|
15
15
|
import type { QaRiskAssessment } from "../schemas/task.ts";
|
|
16
16
|
import type { MemberState, PanelNote, PanelQuestion, PlannerMessage } from "../lobby/planner.ts";
|
|
17
17
|
import type { QuickFixJob } from "../lobby/quickfix.ts";
|
|
18
|
+
import type { HarnessStatus } from "../harness/status.ts";
|
|
19
|
+
import type { HarnessModel } from "../harness/catalog.ts";
|
|
18
20
|
|
|
19
21
|
export interface ProjectInfo { id: string; name: string; cwd: string; port: number }
|
|
20
22
|
export interface FolderListing { path: string; parent?: string; folders: Array<{ name: string; path: string }>; truncated: boolean }
|
|
@@ -23,6 +25,7 @@ export type { WebPrompt };
|
|
|
23
25
|
export type { LobbyTopic };
|
|
24
26
|
export type { DialogAnswer, SessionDialog, TaskRow, PlanComment };
|
|
25
27
|
export type { LiveSession };
|
|
28
|
+
export type { HarnessStatus, HarnessModel };
|
|
26
29
|
export type { PanelMember, MemberState, PanelNote, PanelQuestion, PlannerMessage, QuickFixJob, BotLobbyConfig };
|
|
27
30
|
|
|
28
31
|
/** Every API answer: the result, or why not. */
|
|
@@ -232,6 +235,11 @@ export interface SettingsGrants {
|
|
|
232
235
|
skills: SettingsSkill[];
|
|
233
236
|
}
|
|
234
237
|
|
|
238
|
+
/** `settings.harnesses`: each third-party harness, whether it is installed, and the models it offers. */
|
|
239
|
+
export interface SettingsHarnesses {
|
|
240
|
+
harnesses: HarnessStatus[];
|
|
241
|
+
}
|
|
242
|
+
|
|
235
243
|
/** One message on the event stream (`GET /api/events`, server-sent events). */
|
|
236
244
|
export type StreamEvent =
|
|
237
245
|
| { type: "hello"; versions: Partial<Record<LobbyTopic, number>> }
|
|
@@ -629,6 +637,7 @@ export interface Api {
|
|
|
629
637
|
"settings.set": { request: { patch: Record<string, unknown> }; result: { config: BotLobbyConfig } };
|
|
630
638
|
"settings.linters": { request: Record<string, never>; result: { linters: SettingsLinter[] } };
|
|
631
639
|
"settings.grants": { request: Record<string, never>; result: SettingsGrants };
|
|
640
|
+
"settings.harnesses": { request: Record<string, never>; result: SettingsHarnesses };
|
|
632
641
|
"themes.get": { request: Record<string, never>; result: ThemeStoreInfo };
|
|
633
642
|
"themes.save": { request: { name: string; light?: Record<string, string>; dark?: Record<string, string> }; result: ThemeStoreInfo & { id: string } };
|
|
634
643
|
"themes.rename": { request: { id: string; name: string }; result: ThemeStoreInfo };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e,i as t,n,o as r,r as i,t as a}from"./index-
|
|
1
|
+
import{a as e,i as t,n,o as r,r as i,t as a}from"./index-BKiKxUUG.js";var o=r(e(),1),s=Object.defineProperty,c=Object.getOwnPropertyDescriptor,l=(e,t)=>{for(var n in t)s(e,n,{get:t[n],enumerable:!0})},u=(e,t,n,r)=>{for(var i=r>1?void 0:r?c(t,n):t,a=e.length-1,o;a>=0;a--)(o=e[a])&&(i=(r?o(t,n,i):o(i))||i);return r&&i&&s(t,n,i),i},d=(e,t)=>(n,r)=>t(n,r,e),f=`Terminal input`,p={get:()=>f,set:e=>f=e},m=`Too much output to announce, navigate to rows manually to read`,h={get:()=>m,set:e=>m=e};function g(e){return e.replace(/\r?\n/g,`\r`)}function _(e,t){return t?`\x1B[200~`+e+`\x1B[201~`:e}function ee(e,t){e.clipboardData&&e.clipboardData.setData(`text/plain`,t.selectionText),e.preventDefault()}function te(e,t,n,r){e.stopPropagation(),e.clipboardData&&ne(e.clipboardData.getData(`text/plain`),t,n,r)}function ne(e,t,n,r){e=g(e),e=_(e,n.decPrivateModes.bracketedPasteMode&&r.rawOptions.ignoreBracketedPasteMode!==!0),n.triggerDataEvent(e,!0),t.value=``}function re(e,t,n){let r=n.getBoundingClientRect(),i=e.clientX-r.left-10,a=e.clientY-r.top-10;t.style.width=`20px`,t.style.height=`20px`,t.style.left=`${i}px`,t.style.top=`${a}px`,t.style.zIndex=`1000`,t.focus()}function ie(e,t,n,r,i){re(e,t,n),i&&r.rightClickSelect(e),t.value=r.selectionText,t.select()}function ae(e){return e>65535?(e-=65536,String.fromCharCode((e>>10)+55296)+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)}function oe(e,t=0,n=e.length){let r=``;for(let i=t;i<n;++i){let t=e[i];t>65535?(t-=65536,r+=String.fromCharCode((t>>10)+55296)+String.fromCharCode(t%1024+56320)):r+=String.fromCharCode(t)}return r}var se=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){let n=e.length;if(!n)return 0;let r=0,i=0;if(this._interim){let n=e.charCodeAt(i++);56320<=n&&n<=57343?t[r++]=(this._interim-55296)*1024+n-56320+65536:(t[r++]=this._interim,t[r++]=n),this._interim=0}for(let a=i;a<n;++a){let i=e.charCodeAt(a);if(55296<=i&&i<=56319){if(++a>=n)return this._interim=i,r;let o=e.charCodeAt(a);56320<=o&&o<=57343?t[r++]=(i-55296)*1024+o-56320+65536:(t[r++]=i,t[r++]=o);continue}i!==65279&&(t[r++]=i)}return r}},ce=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){let n=e.length;if(!n)return 0;let r=0,i,a,o,s,c=0,l=0;if(this.interim[0]){let i=!1,a=this.interim[0];a&=(a&224)==192?31:(a&240)==224?15:7;let o=0,s;for(;(s=this.interim[++o]&63)&&o<4;)a<<=6,a|=s;let c=(this.interim[0]&224)==192?2:(this.interim[0]&240)==224?3:4,u=c-o;for(;l<u;){if(l>=n)return 0;if(s=e[l++],(s&192)!=128){l--,i=!0;break}this.interim[o++]=s,a<<=6,a|=s&63}i||(c===2?a<128?l--:t[r++]=a:c===3?a<2048||a>=55296&&a<=57343||a===65279||(t[r++]=a):a<65536||a>1114111||(t[r++]=a)),this.interim.fill(0)}let u=n-4,d=l;for(;d<n;){for(;d<u&&!((i=e[d])&128)&&!((a=e[d+1])&128)&&!((o=e[d+2])&128)&&!((s=e[d+3])&128);)t[r++]=i,t[r++]=a,t[r++]=o,t[r++]=s,d+=4;if(i=e[d++],i<128)t[r++]=i;else if((i&224)==192){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(c=(i&31)<<6|a&63,c<128){d--;continue}t[r++]=c}else if((i&240)==224){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,r;if(o=e[d++],(o&192)!=128){d--;continue}if(c=(i&15)<<12|(a&63)<<6|o&63,c<2048||c>=55296&&c<=57343||c===65279)continue;t[r++]=c}else if((i&248)==240){if(d>=n)return this.interim[0]=i,r;if(a=e[d++],(a&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,r;if(o=e[d++],(o&192)!=128){d--;continue}if(d>=n)return this.interim[0]=i,this.interim[1]=a,this.interim[2]=o,r;if(s=e[d++],(s&192)!=128){d--;continue}if(c=(i&7)<<18|(a&63)<<12|(o&63)<<6|s&63,c<65536||c>1114111)continue;t[r++]=c}}return r}},v=``,le=` `,ue=class e{constructor(){this.fg=0,this.bg=0,this.extended=new de}static toColorRGB(e){return[e>>>16&255,e>>>8&255,e&255]}static fromColorRGB(e){return(e[0]&255)<<16|(e[1]&255)<<8|e[2]&255}clone(){let t=new e;return t.fg=this.fg,t.bg=this.bg,t.extended=this.extended.clone(),t}isInverse(){return this.fg&67108864}isBold(){return this.fg&134217728}isUnderline(){return this.hasExtendedAttrs()&&this.extended.underlineStyle!==0?1:this.fg&268435456}isBlink(){return this.fg&536870912}isInvisible(){return this.fg&1073741824}isItalic(){return this.bg&67108864}isDim(){return this.bg&134217728}isStrikethrough(){return this.fg&2147483648}isProtected(){return this.bg&536870912}isOverline(){return this.bg&1073741824}getFgColorMode(){return this.fg&50331648}getBgColorMode(){return this.bg&50331648}isFgRGB(){return(this.fg&50331648)==50331648}isBgRGB(){return(this.bg&50331648)==50331648}isFgPalette(){return(this.fg&50331648)==16777216||(this.fg&50331648)==33554432}isBgPalette(){return(this.bg&50331648)==16777216||(this.bg&50331648)==33554432}isFgDefault(){return!(this.fg&50331648)}isBgDefault(){return!(this.bg&50331648)}isAttributeDefault(){return this.fg===0&&this.bg===0}getFgColor(){switch(this.fg&50331648){case 16777216:case 33554432:return this.fg&255;case 50331648:return this.fg&16777215;default:return-1}}getBgColor(){switch(this.bg&50331648){case 16777216:case 33554432:return this.bg&255;case 50331648:return this.bg&16777215;default:return-1}}hasExtendedAttrs(){return this.bg&268435456}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(this.bg&268435456&&~this.extended.underlineColor)switch(this.extended.underlineColor&50331648){case 16777216:case 33554432:return this.extended.underlineColor&255;case 50331648:return this.extended.underlineColor&16777215;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return this.bg&268435456&&~this.extended.underlineColor?this.extended.underlineColor&50331648:this.getFgColorMode()}isUnderlineColorRGB(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==50331648:this.isFgRGB()}isUnderlineColorPalette(){return this.bg&268435456&&~this.extended.underlineColor?(this.extended.underlineColor&50331648)==16777216||(this.extended.underlineColor&50331648)==33554432:this.isFgPalette()}isUnderlineColorDefault(){return this.bg&268435456&&~this.extended.underlineColor?!(this.extended.underlineColor&50331648):this.isFgDefault()}getUnderlineStyle(){return this.fg&268435456?this.bg&268435456?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}},de=class e{constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}get ext(){return this._urlId?this._ext&-469762049|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(this._ext&469762048)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return this._ext&67108863}set underlineColor(e){this._ext&=-67108864,this._ext|=e&67108863}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){let e=(this._ext&3758096384)>>29;return e<0?e^4294967288:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}clone(){return new e(this._ext,this._urlId)}isEmpty(){return this.underlineStyle===0&&this._urlId===0}},y=class e extends ue{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new de,this.combinedData=``}static fromCharData(t){let n=new e;return n.setFromCharData(t),n}isCombined(){return this.content&2097152}getWidth(){return this.content>>22}getChars(){return this.content&2097152?this.combinedData:this.content&2097151?ae(this.content&2097151):``}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):this.content&2097151}setFromCharData(e){this.fg=e[0],this.bg=0;let t=!1;if(e[1].length>2)t=!0;else if(e[1].length===2){let n=e[1].charCodeAt(0);if(55296<=n&&n<=56319){let r=e[1].charCodeAt(1);56320<=r&&r<=57343?this.content=(n-55296)*1024+r-56320+65536|e[2]<<22:t=!0}else t=!0}else this.content=e[1].charCodeAt(0)|e[2]<<22;t&&(this.combinedData=e[1],this.content=2097152|e[2]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}},fe=`di$target`,pe=`di$dependencies`,me=new Map;function b(e){return e[pe]||[]}function x(e){if(me.has(e))return me.get(e);let t=function(e,n,r){if(arguments.length!==3)throw Error(`@IServiceName-decorator can only be used to decorate a parameter`);he(t,e,r)};return t._id=e,me.set(e,t),t}function he(e,t,n){t[fe]===t?t[pe].push({id:e,index:n}):(t[pe]=[{id:e,index:n}],t[fe]=t)}var S=x(`BufferService`),ge=x(`CoreMouseService`),_e=x(`CoreService`),C=x(`CharsetService`),ve=x(`InstantiationService`),w=x(`LogService`),T=x(`OptionsService`),ye=x(`OscLinkService`),be=x(`UnicodeService`),xe=x(`DecorationService`),Se=class{constructor(e,t,n){this._bufferService=e,this._optionsService=t,this._oscLinkService=n}provideLinks(e,t){let n=this._bufferService.buffer.lines.get(e-1);if(!n){t(void 0);return}let r=[],i=this._optionsService.rawOptions.linkHandler,a=new y,o=n.getTrimmedLength(),s=-1,c=-1,l=!1;for(let t=0;t<o;t++)if(c!==-1||n.hasContent(t)){if(n.loadCell(t,a),a.hasExtendedAttrs()&&a.extended.urlId){if(c===-1){c=t,s=a.extended.urlId;continue}l=a.extended.urlId!==s}else c!==-1&&(l=!0);if(l||c!==-1&&t===o-1){let n=this._oscLinkService.getLinkData(s)?.uri;if(n){let a={start:{x:c+1,y:e},end:{x:t+ +(!l&&t===o-1),y:e}},s=!1;if(!i?.allowNonHttpProtocols)try{let e=new URL(n);[`http:`,`https:`].includes(e.protocol)||(s=!0)}catch{s=!0}s||r.push({text:n,range:a,activate:(e,t)=>i?i.activate(e,t,a):Ce(e,t),hover:(e,t)=>i?.hover?.(e,t,a),leave:(e,t)=>i?.leave?.(e,t,a)})}l=!1,a.hasExtendedAttrs()&&a.extended.urlId?(c=t,s=a.extended.urlId):(c=-1,s=-1)}}t(r)}};Se=u([d(0,S),d(1,T),d(2,ye)],Se);function Ce(e,t){if(confirm(`Do you want to navigate to ${t}?
|
|
2
2
|
|
|
3
3
|
WARNING: This link could potentially be dangerous`)){let e=window.open();if(e){try{e.opener=null}catch{}e.location.href=t}else console.warn(`Opening link blocked as opener could not be cleared`)}}var we=x(`CharSizeService`),Te=x(`CoreBrowserService`),Ee=x(`MouseService`),De=x(`RenderService`),Oe=x(`SelectionService`),ke=x(`CharacterJoinerService`),Ae=x(`ThemeService`),je=x(`LinkProviderService`),Me=new class{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout(()=>{throw e.stack?Re.isErrorNoTelemetry(e)?new Re(e.message+`
|
|
4
4
|
|