@a-t-h-i/bot-lobby 0.6.55 → 0.6.57

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.
Files changed (41) hide show
  1. package/THIRD_PARTY_NOTICES.md +12 -1
  2. package/package.json +4 -1
  3. package/src/execution/agent-runner.ts +7 -0
  4. package/src/execution/delegation.ts +3 -1
  5. package/src/execution/pi-runner.ts +14 -6
  6. package/src/harness/acp.ts +272 -0
  7. package/src/harness/catalog.ts +257 -0
  8. package/src/harness/claude-sdk.ts +169 -0
  9. package/src/harness/claude.ts +188 -0
  10. package/src/harness/cli-args.ts +62 -0
  11. package/src/harness/codex.ts +155 -0
  12. package/src/harness/instructions.ts +45 -0
  13. package/src/harness/model-manifest.json +829 -0
  14. package/src/harness/process.ts +175 -0
  15. package/src/harness/runner.ts +188 -0
  16. package/src/harness/status.ts +115 -0
  17. package/src/lobby/host.ts +2 -0
  18. package/src/lobby/planner.ts +3 -2
  19. package/src/lobby/pr-review.ts +2 -1
  20. package/src/lobby/quickfix.ts +6 -2
  21. package/src/master/goal-check.ts +1 -0
  22. package/src/master/master.ts +3 -3
  23. package/src/master/research.ts +2 -1
  24. package/src/pi/auto-compaction.ts +23 -5
  25. package/src/pi/delegation.ts +5 -3
  26. package/src/pi/grants.ts +10 -0
  27. package/src/pi/model-support.ts +6 -2
  28. package/src/pi/quiet.ts +2 -0
  29. package/src/schemas/configuration.ts +67 -23
  30. package/src/schemas/harness.ts +122 -0
  31. package/src/webui/api/index.ts +2 -1
  32. package/src/webui/api/settings.ts +7 -1
  33. package/src/webui/dev/fake-service.ts +13 -0
  34. package/src/webui/protocol.ts +9 -0
  35. package/webui/dist/assets/{TerminalView-CIfjkb5H.js → TerminalView-C8VC7KXy.js} +1 -1
  36. package/webui/dist/assets/index-Bs1_77_1.js +114 -0
  37. package/webui/dist/assets/index-DOLQ4FDd.css +2 -0
  38. package/webui/dist/build.json +16 -12
  39. package/webui/dist/index.html +2 -2
  40. package/webui/dist/assets/index-BkXhtB6A.css +0 -2
  41. package/webui/dist/assets/index-DIM2-FUQ.js +0 -114
@@ -27,8 +27,9 @@ export function registerAgentPermissions(pi: ExtensionAPI): void {
27
27
  if (!info) return { block: true, reason: `Unknown tool ${event.toolName}.` };
28
28
  if (["list_mcp_resources", "list_mcp_resource_templates", "read_mcp_resource"].includes(event.toolName)) {
29
29
  const server = (event.input as { server?: unknown }).server;
30
- const grants = policy?.mcpTools ?? loadConfig().master.mcpTools ?? [];
31
- if (typeof server === "string" && grants.some((tool) => tool.startsWith(`mcp__${server.replace(/[^A-Za-z0-9_]/g, "_")}__`)) && info?.exposure !== "hidden") return;
30
+ // The oracle with no list in its settings may use every connected server.
31
+ const grants = policy?.mcpTools ?? loadConfig().master.mcpTools;
32
+ if (typeof server === "string" && (!grants || grants.some((tool) => tool.startsWith(`mcp__${server.replace(/[^A-Za-z0-9_]/g, "_")}__`))) && info?.exposure !== "hidden") return;
32
33
  return { block: true, reason: "Specify an MCP server with an explicit tool grant to read its resources." };
33
34
  }
34
35
  if (policy) {
@@ -36,7 +37,8 @@ export function registerAgentPermissions(pi: ExtensionAPI): void {
36
37
  return { block: true, reason: `This agent is not granted ${event.toolName}.` };
37
38
  }
38
39
  if (event.toolName.startsWith("mcp__")) {
39
- if ((loadConfig().master.mcpTools ?? []).includes(event.toolName) && info?.exposure !== "hidden") return;
40
+ const granted = loadConfig().master.mcpTools;
41
+ if ((!granted || granted.includes(event.toolName)) && info?.exposure !== "hidden") return;
40
42
  return { block: true, reason: `Grant ${event.toolName} to the master in bot-lobby settings first.` };
41
43
  }
42
44
  if (event.parentToolCallId && !pi.getActiveTools().includes(event.toolName)) return { block: true, reason: `${event.toolName} is not active for this agent.` };
package/src/pi/grants.ts CHANGED
@@ -55,6 +55,16 @@ export function grantableTools(pi: ExtensionAPI | undefined = source): Grantable
55
55
  .sort((a, b) => a.server.localeCompare(b.server) || a.name.localeCompare(b.name));
56
56
  }
57
57
 
58
+ /**
59
+ * The MCP tools a run may call: its settings' list as given, or, when the
60
+ * settings name none, every tool pi has connected now (only the read-only ones
61
+ * for an agent that must not change anything).
62
+ */
63
+ export function mcpGrant(granted: readonly string[] | undefined, readOnly: boolean, pi: ExtensionAPI | undefined = source): string[] {
64
+ if (granted) return [...granted];
65
+ return grantableTools(pi).filter((tool) => !readOnly || tool.readOnly).map((tool) => tool.name);
66
+ }
67
+
58
68
  /**
59
69
  * The skills an agent working in `cwd` would find: the session's own (its
60
70
  * skill commands, which include packages and settings paths) and pi's default
@@ -13,6 +13,7 @@ import {
13
13
  type SubagentKind,
14
14
  type ThinkingLevelName,
15
15
  } from "../schemas/configuration.ts";
16
+ import type { HarnessRun } from "../schemas/harness.ts";
16
17
 
17
18
  /** Find a model by `provider/id` (or bare id); undefined when the registry does not know it. */
18
19
  export type ModelLookup = (ref: string) => Model<Api> | undefined;
@@ -78,6 +79,8 @@ const warned = new Set<string>();
78
79
  export function createProfileResolver(config: BotLobbyConfig, options: ResolverOptions): ProfileResolver {
79
80
  return (domain, role) => {
80
81
  const profile = agentProfile(config, domain, role);
82
+ // A harness run picks its own model and effort; pi's model and its thinking clamp do not apply.
83
+ if (profile.harness) return profile;
81
84
  const model = profile.model ?? options.sessionModel;
82
85
  const check = checkThinking(model ? options.lookup(model) : undefined, profile.thinking);
83
86
  if (check.warning) {
@@ -91,10 +94,11 @@ export function createProfileResolver(config: BotLobbyConfig, options: ResolverO
91
94
  };
92
95
  }
93
96
 
94
- type RunProfile = { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[] };
97
+ type RunProfile = { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean; compactAt?: number; harness?: HarnessRun };
95
98
 
96
99
  /** Unset models run on the session's; thinking is clamped to the model, with a one-time warning. */
97
100
  function resolveRunProfile(profile: RunProfile, label: string, options: ResolverOptions): RunProfile {
101
+ if (profile.harness) return profile;
98
102
  const model = profile.model ?? options.sessionModel;
99
103
  const check = checkThinking(model ? options.lookup(model) : undefined, profile.thinking);
100
104
  if (check.warning) {
@@ -129,7 +133,7 @@ export function resolvePanelProfile(config: BotLobbyConfig, member: PanelMember,
129
133
  */
130
134
  export function resolveReviewProfile(config: BotLobbyConfig, options: ResolverOptions): RunProfile {
131
135
  const profile = agentProfile(config, "qa", "reviewer");
132
- return resolveRunProfile({ model: profile.model, thinking: profile.thinking, ...(profile.instructions ? { instructions: profile.instructions } : {}), ...(profile.mcpTools ? { mcpTools: profile.mcpTools } : {}), ...(profile.skills ? { skills: profile.skills } : {}) }, kindLabel("qa"), options);
136
+ return resolveRunProfile({ model: profile.model, thinking: profile.thinking, ...(profile.instructions ? { instructions: profile.instructions } : {}), ...(profile.mcpTools ? { mcpTools: profile.mcpTools } : {}), ...(profile.skills ? { skills: profile.skills } : {}), ...(profile.autoCompact === false ? { autoCompact: false } : {}), ...(profile.compactAt ? { compactAt: profile.compactAt } : {}), ...(profile.harness ? { harness: profile.harness } : {}) }, kindLabel("qa"), options);
133
137
  }
134
138
 
135
139
  /** Every configured subagent whose thinking level its model does not support, for `/bot-lobby config`. */
package/src/pi/quiet.ts CHANGED
@@ -60,6 +60,8 @@ export function webToolsFor(active: string[], taskActive: boolean, hidden: reado
60
60
 
61
61
  /** Set to "0" in a subagent's environment when its settings turn auto-compaction off. */
62
62
  export const AUTO_COMPACT_ENV = "BOT_LOBBY_AUTO_COMPACT";
63
+ /** The token count at which a subagent compacts, from its settings. */
64
+ export const COMPACT_AT_ENV = "BOT_LOBBY_COMPACT_AT";
63
65
 
64
66
  /** True inside a bot-lobby subagent process (marked in `spawnPiProcess`). */
65
67
  export function isSubagentProcess(): boolean {
@@ -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,21 +23,23 @@ 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. */
29
30
  instructions?: string;
30
- /** Exact native MCP tool names this profile may call; empty grants none. */
31
+ /** Exact native MCP tool names this profile may call; absent gives it every tool pi has connected (read-only ones for read-only agents), empty gives it none. */
31
32
  mcpTools?: string[];
32
33
  /** Names of the skills this agent may load; absent gives it every skill pi finds, empty gives it none. */
33
34
  skills?: string[];
34
35
  /** Compact this agent's context on its own as it fills (on unless false); the oracle's also updates the knowledge base. */
35
36
  autoCompact?: boolean;
37
+ /** Context size, in tokens, at which auto-compaction starts; absent uses the built-in threshold. */
38
+ compactAt?: number;
36
39
  }
37
40
 
38
41
  /** Scouts pick a model only; their thinking is fixed at `SCOUT_THINKING`. */
39
- export interface ScoutConfig {
42
+ export interface ScoutConfig extends HarnessChoice {
40
43
  mcpTools?: string[];
41
44
  /** See AgentModelConfig.skills. */
42
45
  skills?: string[];
@@ -45,6 +48,8 @@ export interface ScoutConfig {
45
48
  maxToolCalls: number;
46
49
  /** Compact a scout's context on its own as it fills (on unless false). */
47
50
  autoCompact?: boolean;
51
+ /** See AgentModelConfig.compactAt. */
52
+ compactAt?: number;
48
53
  }
49
54
 
50
55
  /** Subagent kinds with their own settings entry. */
@@ -271,6 +276,8 @@ export interface BotLobbyConfig {
271
276
  knowledge: KnowledgeConfig;
272
277
  lobby: LobbyConfig;
273
278
  classifier: ClassifierConfig;
279
+ /** Third-party harnesses a subagent can run on instead of pi, each with its own settings. */
280
+ harnesses: HarnessesConfig;
274
281
  }
275
282
 
276
283
  export const DEFAULT_CONFIG: BotLobbyConfig = {
@@ -349,6 +356,7 @@ export const DEFAULT_CONFIG: BotLobbyConfig = {
349
356
  effort: { cheapModel: INHERIT_MODEL },
350
357
  exclude: [],
351
358
  },
359
+ harnesses: defaultHarnesses(),
352
360
  };
353
361
 
354
362
  /** Choices the settings menu cycles through for the planning round limit; 0 = unlimited. */
@@ -376,24 +384,49 @@ export function normalizeMcpTools(value: unknown): string[] {
376
384
  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
385
  }
378
386
 
379
- /** Merge one agent's override over its default; legacy `inherit` or unknown thinking falls back to the default level. */
380
- function normalizeAgent(base: AgentModelConfig, override: Partial<AgentModelConfig> | undefined): AgentModelConfig {
387
+ /** `{ mcpTools }` when an entry lists its tools (even none), else nothing, so the agent keeps every connected tool. */
388
+ function mcpToolsField(value: unknown): { mcpTools?: string[] } {
389
+ return Array.isArray(value) ? { mcpTools: normalizeMcpTools(value) } : {};
390
+ }
391
+
392
+ /** The smallest and largest auto-compaction thresholds Settings accepts, in tokens. */
393
+ export const COMPACT_AT_MIN = 4_000;
394
+ export const COMPACT_AT_MAX = 2_000_000;
395
+
396
+ /** A whole number of tokens within the accepted range, or absent for the built-in threshold. */
397
+ export function normalizeCompactAt(value: unknown): number | undefined {
398
+ return typeof value === "number" && Number.isFinite(value) && value >= COMPACT_AT_MIN && value <= COMPACT_AT_MAX ? Math.round(value) : undefined;
399
+ }
400
+
401
+ /** Auto-compaction off and its threshold, when an entry sets them. */
402
+ function compactFields(entry: { autoCompact?: unknown; compactAt?: unknown } | undefined): { autoCompact?: false; compactAt?: number } {
403
+ const compactAt = normalizeCompactAt(entry?.compactAt);
404
+ return { ...(entry?.autoCompact === false ? { autoCompact: false } : {}), ...(compactAt ? { compactAt } : {}) };
405
+ }
406
+
407
+ /**
408
+ * Merge one agent's override over its default; legacy `inherit` or unknown
409
+ * thinking falls back to the default level. Only subagents may name a
410
+ * harness (`pi` or an unknown one drops the field): the oracle and the
411
+ * planner live in the pi session.
412
+ */
413
+ function normalizeAgent(base: AgentModelConfig, override: Partial<AgentModelConfig> | undefined, harness = true): AgentModelConfig {
381
414
  const merged = { ...base, ...(override ?? {}) };
382
415
  const thinking = isThinkingLevel(merged.thinking) ? merged.thinking : base.thinking;
383
- const mcpTools = normalizeMcpTools(merged.mcpTools);
384
- return { model: merged.model, thinking, ...(mcpTools.length ? { mcpTools } : {}), ...skillsField(merged.skills), ...(typeof merged.instructions === "string" ? { instructions: merged.instructions } : {}), ...(merged.autoCompact === false ? { autoCompact: false } : {}) };
416
+ const choice = harness ? harnessChoiceFields(merged) : {};
417
+ return { model: merged.model, thinking, ...mcpToolsField(merged.mcpTools), ...skillsField(merged.skills), ...(typeof merged.instructions === "string" ? { instructions: merged.instructions } : {}), ...compactFields(merged), ...choice };
385
418
  }
386
419
 
387
420
  /** Scouts keep a model and a tool-call cap; any thinking value in the file is dropped. */
388
421
  function normalizeScout(override: Partial<ScoutConfig> | undefined): ScoutConfig {
389
422
  const base = DEFAULT_CONFIG.scout;
390
- const mcpTools = normalizeMcpTools(override?.mcpTools);
391
423
  return {
392
424
  model: typeof override?.model === "string" && override.model.trim() ? override.model : base.model,
393
425
  maxToolCalls: typeof override?.maxToolCalls === "number" && Number.isFinite(override.maxToolCalls) && override.maxToolCalls >= 0 ? Math.round(override.maxToolCalls) : base.maxToolCalls,
394
- ...(mcpTools.length ? { mcpTools } : {}),
426
+ ...mcpToolsField(override?.mcpTools),
395
427
  ...skillsField(override?.skills),
396
- ...(override?.autoCompact === false ? { autoCompact: false } : {}),
428
+ ...compactFields(override),
429
+ ...harnessChoiceFields(override ?? {}),
397
430
  };
398
431
  }
399
432
 
@@ -517,7 +550,7 @@ export function resolveConfig(partial: unknown): BotLobbyConfig {
517
550
  const knowledge = { ...DEFAULT_CONFIG.knowledge, ...(src.knowledge as Partial<KnowledgeConfig> | undefined) };
518
551
  const srcAgents = (src.agents ?? {}) as Partial<BotLobbyConfig["agents"]>;
519
552
  return {
520
- master: normalizeAgent(DEFAULT_CONFIG.master, src.master as Partial<AgentModelConfig> | undefined),
553
+ master: normalizeAgent(DEFAULT_CONFIG.master, src.master as Partial<AgentModelConfig> | undefined, false),
521
554
  agents: {
522
555
  designer: normalizeAgent(DEFAULT_CONFIG.agents.designer, srcAgents.designer),
523
556
  backend: normalizeAgent(DEFAULT_CONFIG.agents.backend, srcAgents.backend),
@@ -526,12 +559,13 @@ export function resolveConfig(partial: unknown): BotLobbyConfig {
526
559
  scout: normalizeScout(src.scout as Partial<ScoutConfig> | undefined),
527
560
  researcher: normalizeAgent(DEFAULT_CONFIG.researcher, src.researcher as Partial<AgentModelConfig> | undefined),
528
561
  quickFix: normalizeAgent(DEFAULT_CONFIG.quickFix, src.quickFix as Partial<AgentModelConfig> | undefined),
529
- planner: normalizeAgent(DEFAULT_CONFIG.planner, src.planner as Partial<AgentModelConfig> | undefined),
562
+ planner: normalizeAgent(DEFAULT_CONFIG.planner, src.planner as Partial<AgentModelConfig> | undefined, false),
530
563
  workflow,
531
564
  lint: normalizeLint(src.lint),
532
565
  knowledge,
533
566
  lobby: normalizeLobby(src.lobby),
534
567
  classifier: normalizeClassifier(src.classifier),
568
+ harnesses: normalizeHarnesses(src.harnesses),
535
569
  };
536
570
  }
537
571
 
@@ -547,22 +581,32 @@ export interface AgentProfile {
547
581
  model?: string;
548
582
  thinking: string;
549
583
  instructions?: string;
550
- /** Exact native MCP tool names this profile may call; empty grants none. */
584
+ /** Exact native MCP tool names this profile may call; absent keeps every connected tool, empty grants none. */
551
585
  mcpTools?: string[];
552
586
  /** Skill names it may load; absent keeps every skill pi finds. */
553
587
  skills?: string[];
554
588
  /** False when its settings turn auto-compaction off. */
555
589
  autoCompact?: boolean;
590
+ /** Tokens at which auto-compaction starts, when set. */
591
+ compactAt?: number;
592
+ /** The third-party harness it runs on; absent runs it on pi. */
593
+ harness?: HarnessRun;
594
+ }
595
+
596
+ /** `{ harness }` when an entry runs on a third-party harness, else nothing. */
597
+ function harnessField(config: BotLobbyConfig, entry: HarnessChoice): { harness?: HarnessRun } {
598
+ const harness = harnessRunOf(entry, config.harnesses);
599
+ return harness ? { harness } : {};
556
600
  }
557
601
 
558
- /** `{ autoCompact: false }` when an entry turns auto-compaction off, else nothing. */
559
- function compactField(entry: { autoCompact?: boolean }): { autoCompact?: false } {
560
- return entry.autoCompact === false ? { autoCompact: false } : {};
602
+ /** Auto-compaction off and its threshold, as an entry sets them. */
603
+ function compactField(entry: { autoCompact?: boolean; compactAt?: number }): { autoCompact?: false; compactAt?: number } {
604
+ return { ...(entry.autoCompact === false ? { autoCompact: false } : {}), ...(entry.compactAt ? { compactAt: entry.compactAt } : {}) };
561
605
  }
562
606
 
563
607
  /** The MCP tools and skills an entry grants, leaving out what it does not name. */
564
608
  function grantFields(entry: { mcpTools?: string[]; skills?: string[] }): { mcpTools?: string[]; skills?: string[] } {
565
- return { ...(entry.mcpTools?.length ? { mcpTools: entry.mcpTools } : {}), ...(entry.skills ? { skills: entry.skills } : {}) };
609
+ return { ...(entry.mcpTools ? { mcpTools: entry.mcpTools } : {}), ...(entry.skills ? { skills: entry.skills } : {}) };
566
610
  }
567
611
 
568
612
  /** The settings entry a domain/role run draws from. */
@@ -586,10 +630,10 @@ export function agentProfile(config: BotLobbyConfig, domain: Domain, role: Role)
586
630
  const kind = profileKind(domain, role);
587
631
  const instructions = config.agents[domain].instructions;
588
632
  if (kind === "scout") {
589
- return { kind, model: modelOf(config.scout.model), thinking: SCOUT_THINKING, instructions, ...grantFields(config.scout), ...compactField(config.scout) };
633
+ return { kind, model: modelOf(config.scout.model), thinking: SCOUT_THINKING, instructions, ...grantFields(config.scout), ...compactField(config.scout), ...harnessField(config, config.scout) };
590
634
  }
591
635
  const entry = kind === "researcher" ? config.researcher : config.agents[kind];
592
- return { kind, model: modelOf(entry.model), thinking: entry.thinking, instructions, ...grantFields(entry), ...compactField(entry) };
636
+ return { kind, model: modelOf(entry.model), thinking: entry.thinking, instructions, ...grantFields(entry), ...compactField(entry), ...harnessField(config, entry) };
593
637
  }
594
638
 
595
639
  /** The settings entry of a lobby agent (quick fix or planner). */
@@ -598,18 +642,18 @@ export function lobbyAgentConfig(config: BotLobbyConfig, kind: LobbyAgentKind):
598
642
  }
599
643
 
600
644
  /** 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 } {
645
+ export function lobbyAgentProfile(config: BotLobbyConfig, kind: LobbyAgentKind): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean; compactAt?: number; harness?: HarnessRun } {
602
646
  const entry = lobbyAgentConfig(config, kind);
603
- return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry) };
647
+ return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry), ...harnessField(config, entry) };
604
648
  }
605
649
 
606
650
  /**
607
651
  * A planning panel seat's profile: the domain's (or the researcher's) model,
608
652
  * thinking, custom instructions and grants.
609
653
  */
610
- export function panelMemberProfile(config: BotLobbyConfig, member: PanelMember): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean } {
654
+ export function panelMemberProfile(config: BotLobbyConfig, member: PanelMember): { model?: string; thinking: string; instructions?: string; mcpTools?: string[]; skills?: string[]; autoCompact?: boolean; compactAt?: number; harness?: HarnessRun } {
611
655
  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) };
656
+ return { model: modelOf(entry.model), thinking: entry.thinking, instructions: entry.instructions, ...grantFields(entry), ...compactField(entry), ...harnessField(config, entry) };
613
657
  }
614
658
 
615
659
  /** 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
+ }
@@ -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: [
@@ -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-DIM2-FUQ.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}?
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"./index-Bs1_77_1.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