@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
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Codex CLI: one `codex exec --json` run per step. The prompt goes over
3
+ * stdin, Bot Lobby's instructions as `developer_instructions`, and the JSONL
4
+ * events (`thread.started`, `item.*`, `turn.completed`) become the same
5
+ * stream events a pi subagent sends.
6
+ *
7
+ * The flag set follows T3 Code's `apps/server/src/textGeneration/CodexTextGeneration.ts`
8
+ * (`exec`, `--skip-git-repo-check`, `-s`, `--model`, `model_reasoning_effort`)
9
+ * and `apps/server/src/provider/codexLaunchArgs.ts`.
10
+ */
11
+ import type { HarnessDriver, HarnessIO } from "./process.ts";
12
+ import type { HarnessAccess, HarnessCollector, HarnessPlan } from "./runner.ts";
13
+
14
+ /** Longest instructions passed on the command line; longer ones lead the prompt instead. */
15
+ const MAX_ARG_BYTES = 100_000;
16
+ /** "Never" for Codex's compaction threshold, which cannot be switched off. */
17
+ const NO_COMPACTION = 1_000_000_000_000;
18
+ const LOCAL_PROVIDERS = new Set(["ollama", "lmstudio"]);
19
+
20
+ /** A TOML basic string: JSON's escapes are valid TOML. */
21
+ export function tomlString(value: string): string {
22
+ return JSON.stringify(value);
23
+ }
24
+
25
+ export function codexArgs(plan: HarnessPlan, access: HarnessAccess, launchArgs: string[]): { args: string[]; promptPrefix: string } {
26
+ const { settings } = plan.harness;
27
+ const args = ["exec", ...launchArgs, "--json", "--skip-git-repo-check", "-s", access.writes ? "workspace-write" : "read-only", "-c", 'approval_policy="never"'];
28
+ if (plan.model) args.push("--model", plan.model);
29
+ if (plan.effort) args.push("-c", `model_reasoning_effort=${tomlString(plan.effort)}`);
30
+ const provider = settings.modelProvider;
31
+ if (LOCAL_PROVIDERS.has(provider)) args.push("--oss", "--local-provider", provider);
32
+ else if (provider) args.push("-c", `model_provider=${tomlString(provider)}`);
33
+ if (plan.autoCompact === false) args.push("-c", `model_auto_compact_token_limit=${NO_COMPACTION}`);
34
+ else if (settings.compactAt > 0) args.push("-c", `model_auto_compact_token_limit=${settings.compactAt}`);
35
+ let promptPrefix = "";
36
+ if (plan.instructions) {
37
+ if (Buffer.byteLength(plan.instructions) <= MAX_ARG_BYTES) args.push("-c", `developer_instructions=${tomlString(plan.instructions)}`);
38
+ else promptPrefix = plan.instructions;
39
+ }
40
+ args.push("-");
41
+ return { args, promptPrefix };
42
+ }
43
+
44
+ interface CodexItem {
45
+ id?: string;
46
+ type?: string;
47
+ text?: string;
48
+ command?: string;
49
+ exit_code?: number | null;
50
+ status?: string;
51
+ changes?: Array<{ path?: string; kind?: string }>;
52
+ server?: string;
53
+ tool?: string;
54
+ query?: string;
55
+ message?: string;
56
+ }
57
+
58
+ interface CodexEvent {
59
+ type?: string;
60
+ thread_id?: string;
61
+ item?: CodexItem;
62
+ usage?: { input_tokens?: number; cached_input_tokens?: number; output_tokens?: number };
63
+ error?: { message?: string };
64
+ message?: string;
65
+ }
66
+
67
+ function toolOf(item: CodexItem): { name: string; args: Record<string, unknown> } | undefined {
68
+ switch (item.type) {
69
+ case "command_execution":
70
+ return { name: "bash", args: { command: item.command ?? "" } };
71
+ case "mcp_tool_call":
72
+ return { name: `mcp__${item.server ?? "server"}__${item.tool ?? "tool"}`, args: {} };
73
+ case "web_search":
74
+ return { name: "web_search", args: { query: item.query ?? "" } };
75
+ default:
76
+ return undefined;
77
+ }
78
+ }
79
+
80
+ export function codexDriver(io: HarnessIO, plan: HarnessPlan, prompt: string, collector: HarnessCollector): HarnessDriver {
81
+ const started = new Set<string>();
82
+ const onItem = (phase: "started" | "updated" | "completed", item: CodexItem) => {
83
+ if (item.type === "agent_message" && phase === "completed") {
84
+ io.emit({ type: "writing" });
85
+ if (item.text?.trim()) collector.text = item.text;
86
+ return;
87
+ }
88
+ if (item.type === "reasoning" && phase === "completed") {
89
+ if (item.text?.trim()) io.emit({ type: "thought", text: item.text.trim().slice(0, 1500) });
90
+ return;
91
+ }
92
+ if (item.type === "file_change" && phase === "completed") {
93
+ for (const change of item.changes ?? []) {
94
+ if (!change.path) continue;
95
+ const name = change.kind === "add" ? "write" : "edit";
96
+ io.emit({ type: "tool_execution_start", toolName: name, args: { path: change.path } });
97
+ io.emit({ type: "tool_execution_end", toolName: name, isError: item.status === "failed" });
98
+ }
99
+ return;
100
+ }
101
+ if (item.type === "error" && item.message) {
102
+ collector.warning = item.message;
103
+ return;
104
+ }
105
+ const tool = toolOf(item);
106
+ if (!tool) return;
107
+ const id = item.id ?? `${tool.name}:${started.size}`;
108
+ if (!started.has(id)) {
109
+ started.add(id);
110
+ io.emit({ type: "tool_execution_start", toolName: tool.name, args: tool.args });
111
+ if (phase !== "completed") return;
112
+ }
113
+ if (phase === "completed") io.emit({ type: "tool_execution_end", toolName: tool.name, isError: item.status === "failed" || (typeof item.exit_code === "number" && item.exit_code !== 0) });
114
+ };
115
+ return {
116
+ start() {
117
+ io.write(prompt);
118
+ io.closeInput();
119
+ },
120
+ line(text) {
121
+ let event: CodexEvent;
122
+ try {
123
+ event = JSON.parse(text) as CodexEvent;
124
+ } catch {
125
+ return;
126
+ }
127
+ switch (event.type) {
128
+ case "thread.started":
129
+ collector.session = event.thread_id;
130
+ return;
131
+ case "turn.started":
132
+ io.emit({ type: "turn_start" });
133
+ return;
134
+ case "item.started":
135
+ case "item.updated":
136
+ case "item.completed":
137
+ if (event.item) onItem(event.type.slice("item.".length) as "started" | "updated" | "completed", event.item);
138
+ return;
139
+ case "turn.completed": {
140
+ const input = (event.usage?.input_tokens ?? 0);
141
+ const output = event.usage?.output_tokens ?? 0;
142
+ collector.addUsage(input, output, 0, plan.model || undefined);
143
+ io.emit({ type: "usage", input, output, cost: 0, ...(plan.model ? { model: plan.model } : {}) });
144
+ return;
145
+ }
146
+ case "turn.failed":
147
+ collector.error = event.error?.message ?? "Codex reported a failed turn";
148
+ return;
149
+ case "error":
150
+ if (event.message) collector.warning = event.message;
151
+ return;
152
+ }
153
+ },
154
+ };
155
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * How Bot Lobby's prompts reach a third-party harness.
3
+ *
4
+ * Codex takes them as developer instructions and Claude Code appends them to
5
+ * its own system prompt; ACP has no system prompt field, so they ride in the
6
+ * first prompt, wrapped so they cannot be mistaken for the user's words.
7
+ * Adapted from T3 Code, `packages/provider-core/src/server/runtimeInstructions.ts`
8
+ * (`buildRuntimeInstructions`) and `orchestrationInstructions.ts`
9
+ * (`t3AcpPromptWithInstructions`).
10
+ */
11
+ import type { HarnessRun } from "../schemas/harness.ts";
12
+ import { HARNESSES } from "./catalog.ts";
13
+
14
+ /** Pi's tool names in the role prompts, and what stands in for them on another harness. */
15
+ const HARNESS_NOTE = `## Running on another harness
16
+
17
+ You run in Bot Lobby through a third-party harness, not pi. Where these instructions name pi's tools (read, edit, write, bash, grep, find, ls), use your own equivalents. Bot Lobby's own tools (ask_user_question, ask_expert, the file desk and task board tools, web_search and fetch_content) are not available here: work from the brief, use your own web tools if you have them, and put any open question or blocker in your report instead of asking.`;
18
+
19
+ function toSingleLine(value: string): string {
20
+ return value.replaceAll(/\s+/g, " ").trim();
21
+ }
22
+
23
+ /** Who and what the agent runs as, in case it is asked. */
24
+ export function runtimeInstructions(harness: HarnessRun, effort?: string): string {
25
+ const model = toSingleLine(harness.model);
26
+ const modelInfo = model ? `, as ${model}` : "";
27
+ const effortInfo = effort ? ` with ${toSingleLine(effort)} reasoning effort` : "";
28
+ return `<runtime_info>In case you're asked: you are running in Bot Lobby through the ${HARNESSES[harness.id].label} harness${modelInfo}${effortInfo}. No need to mention this otherwise.</runtime_info>`;
29
+ }
30
+
31
+ /**
32
+ * The instructions a harness run gets: the compiled role prompt and the
33
+ * harness note when prompts are injected, else only the report format the
34
+ * engine parses (`contract`), which a run cannot do without.
35
+ */
36
+ export function harnessInstructions(input: { harness: HarnessRun; systemPrompt?: string; contract?: string; effort?: string }): string {
37
+ const body = input.harness.settings.injectPrompts ? [input.systemPrompt ?? "", HARNESS_NOTE] : [input.contract ?? ""];
38
+ return [...body, runtimeInstructions(input.harness, input.effort)].filter((part) => part.trim().length > 0).join("\n\n---\n\n");
39
+ }
40
+
41
+ /** ACP: the instructions go in front of the first prompt, wrapped; a native slash command stays first. */
42
+ export function acpPromptWithInstructions(prompt: string, instructions: string): string {
43
+ if (prompt.trimStart().startsWith("/") || !instructions.trim()) return prompt;
44
+ return `<bot_lobby_instructions>\n${instructions}\n</bot_lobby_instructions>\n\n<user_request>\n${prompt}\n</user_request>`;
45
+ }