feat: forward agent style via native CLI config and terminal title
Replace prompt-injection for display identity with native UI wiring: - Claude: writes a statusline shell script + custom theme JSON, wired up via settings.json so label/color show in the status bar and theme - Codex: writes [tui] block into codex-config.toml (status_line, terminal_title, dark-ansi theme) - Both backends set the terminal title via ANSI OSC 0 escape before exec-ing the agent when a label is present Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import shlex
|
||||
from typing import Callable
|
||||
|
||||
from typing import cast
|
||||
@@ -22,12 +23,14 @@ class DockerBottle(Bottle):
|
||||
*,
|
||||
agent_command: str = "claude",
|
||||
agent_prompt_mode: PromptMode = "append_file",
|
||||
terminal_title: str = "",
|
||||
):
|
||||
self.name = container
|
||||
self._teardown = teardown
|
||||
self.prompt_path = prompt_path_in_container
|
||||
self._agent_prompt_mode = agent_prompt_mode
|
||||
self.agent_command = agent_command
|
||||
self.terminal_title = terminal_title
|
||||
self.agent_provider_template = (
|
||||
"codex" if agent_command == "codex" else "claude"
|
||||
)
|
||||
@@ -47,9 +50,14 @@ class DockerBottle(Bottle):
|
||||
return cmd
|
||||
|
||||
def exec_agent(self, argv: list[str], *, tty: bool = True) -> int:
|
||||
return subprocess.run(
|
||||
self.agent_argv(argv, tty=tty), check=False,
|
||||
).returncode
|
||||
agent_argv = self.agent_argv(argv, tty=tty)
|
||||
if self.terminal_title and tty:
|
||||
shell_script = (
|
||||
f"printf '\\033]0;%s\\007' {shlex.quote(self.terminal_title)}; "
|
||||
f"exec {shlex.join(agent_argv)}"
|
||||
)
|
||||
return subprocess.run(["sh", "-lc", shell_script], check=False).returncode
|
||||
return subprocess.run(agent_argv, check=False).returncode
|
||||
|
||||
def exec(self, script: str, *, user: str = "node") -> ExecResult:
|
||||
# Pipe via stdin to `sh -s` so the caller never has to worry
|
||||
|
||||
@@ -175,6 +175,7 @@ def launch(
|
||||
None,
|
||||
agent_command=plan.agent_command,
|
||||
agent_prompt_mode=plan.agent_prompt_mode,
|
||||
terminal_title=plan.spec.label or plan.spec.agent_name,
|
||||
)
|
||||
bottle.prompt_path = provision(plan, bottle)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ from __future__ import annotations
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import shlex
|
||||
from typing import Mapping, cast
|
||||
|
||||
from ...agent_provider import PromptMode, prompt_args
|
||||
@@ -68,6 +69,7 @@ class SmolmachinesBottle(Bottle):
|
||||
guest_env: Mapping[str, str] | None = None,
|
||||
agent_command: str = "claude",
|
||||
agent_prompt_mode: PromptMode = "append_file",
|
||||
terminal_title: str = "",
|
||||
) -> None:
|
||||
self.name = machine_name
|
||||
# In-VM path to the agent's prompt file. None when the
|
||||
@@ -81,6 +83,7 @@ class SmolmachinesBottle(Bottle):
|
||||
self._guest_env = dict(guest_env or {})
|
||||
self._agent_prompt_mode = agent_prompt_mode
|
||||
self.agent_command = agent_command
|
||||
self.terminal_title = terminal_title
|
||||
self.agent_provider_template = (
|
||||
"codex" if agent_command == "codex" else "claude"
|
||||
)
|
||||
@@ -128,9 +131,14 @@ class SmolmachinesBottle(Bottle):
|
||||
UID switches via `runuser -u node --` (not `-l`) so we
|
||||
avoid login-shell wiring. HOME / USER come from `smolvm
|
||||
-e` instead, which sets them on the process env."""
|
||||
return subprocess.run(
|
||||
self.agent_argv(argv, tty=tty), check=False,
|
||||
).returncode
|
||||
agent_argv = self.agent_argv(argv, tty=tty)
|
||||
if self.terminal_title and tty:
|
||||
shell_script = (
|
||||
f"printf '\\033]0;%s\\007' {shlex.quote(self.terminal_title)}; "
|
||||
f"exec {shlex.join(agent_argv)}"
|
||||
)
|
||||
return subprocess.run(["sh", "-lc", shell_script], check=False).returncode
|
||||
return subprocess.run(agent_argv, check=False).returncode
|
||||
|
||||
# smolvm/libkrun can SIGKILL an otherwise-normal exec during
|
||||
# early-VM provisioning. Retry once after a short settle so
|
||||
|
||||
@@ -103,6 +103,7 @@ def launch(
|
||||
guest_env=plan.guest_env,
|
||||
agent_command=plan.agent_command,
|
||||
agent_prompt_mode=plan.agent_prompt_mode,
|
||||
terminal_title=plan.spec.label or plan.spec.agent_name,
|
||||
)
|
||||
bottle.prompt_path = provision(plan, bottle)
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@ from typing import TYPE_CHECKING
|
||||
from ...agent_provider import (
|
||||
AgentProvider,
|
||||
AgentProviderRuntime,
|
||||
AgentProvisionDir,
|
||||
AgentProvisionFile,
|
||||
AgentProvisionPlan,
|
||||
)
|
||||
from ...backend.docker import util as docker_mod
|
||||
from ...egress import EgressRoute
|
||||
from ...log import die, info, warn
|
||||
|
||||
@@ -39,24 +41,68 @@ def _prompt_path(guest_home: str) -> str:
|
||||
return f"{guest_home}/.bot-bottle-prompt.txt"
|
||||
|
||||
|
||||
def _display_identity_prompt(label: str, color: str) -> str:
|
||||
lines: list[str] = []
|
||||
if label:
|
||||
lines.append(f"Name: {label}")
|
||||
if color:
|
||||
lines.append(f"Color: {color}")
|
||||
if not lines:
|
||||
return ""
|
||||
return "Bot-bottle agent display identity:\n" + "\n".join(lines)
|
||||
_STATUS_LINE_COLORS = {
|
||||
"black": "\033[30m",
|
||||
"red": "\033[31m",
|
||||
"green": "\033[32m",
|
||||
"yellow": "\033[33m",
|
||||
"blue": "\033[34m",
|
||||
"magenta": "\033[35m",
|
||||
"cyan": "\033[36m",
|
||||
"white": "\033[37m",
|
||||
"bright-black": "\033[90m",
|
||||
"bright-red": "\033[91m",
|
||||
"bright-green": "\033[92m",
|
||||
"bright-yellow": "\033[93m",
|
||||
"bright-blue": "\033[94m",
|
||||
"bright-magenta": "\033[95m",
|
||||
"bright-cyan": "\033[96m",
|
||||
"bright-white": "\033[97m",
|
||||
}
|
||||
|
||||
_CLAUDE_THEME_COLORS = {
|
||||
"black": "black",
|
||||
"red": "red",
|
||||
"green": "green",
|
||||
"yellow": "yellow",
|
||||
"blue": "blue",
|
||||
"magenta": "magenta",
|
||||
"cyan": "cyan",
|
||||
"white": "white",
|
||||
"bright-black": "blackBright",
|
||||
"bright-red": "redBright",
|
||||
"bright-green": "greenBright",
|
||||
"bright-yellow": "yellowBright",
|
||||
"bright-blue": "blueBright",
|
||||
"bright-magenta": "magentaBright",
|
||||
"bright-cyan": "cyanBright",
|
||||
"bright-white": "whiteBright",
|
||||
}
|
||||
|
||||
|
||||
def _prepend_display_identity(prompt_file: Path, label: str, color: str) -> bool:
|
||||
identity = _display_identity_prompt(label, color)
|
||||
original = prompt_file.read_text() if prompt_file.exists() else ""
|
||||
if not identity:
|
||||
return bool(original)
|
||||
prompt_file.write_text(f"{identity}\n\n{original}" if original else f"{identity}\n")
|
||||
return True
|
||||
def _status_line_script(label: str, color: str) -> str:
|
||||
if not label:
|
||||
return "#!/bin/sh\nprintf '\\n'\n"
|
||||
label_q = shlex.quote(label)
|
||||
if color and color in _STATUS_LINE_COLORS:
|
||||
return (
|
||||
"#!/bin/sh\n"
|
||||
f"printf '%b%s%b\\n' '{_STATUS_LINE_COLORS[color]}' {label_q} '\\033[0m'\n"
|
||||
)
|
||||
return f"#!/bin/sh\nprintf '%s\\n' {label_q}\n"
|
||||
|
||||
|
||||
def _custom_theme_payload(color: str) -> dict[str, object] | None:
|
||||
theme_color = _CLAUDE_THEME_COLORS.get(color)
|
||||
if not theme_color:
|
||||
return None
|
||||
return {
|
||||
"name": f"Bot-bottle {color}",
|
||||
"base": "dark",
|
||||
"overrides": {
|
||||
"claude": f"ansi:{theme_color}",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
_RUNTIME = AgentProviderRuntime(
|
||||
@@ -99,6 +145,10 @@ class ClaudeAgentProvider(AgentProvider):
|
||||
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
||||
"DISABLE_ERROR_REPORTING": "1",
|
||||
}
|
||||
dirs = (
|
||||
AgentProvisionDir(f"{guest_home}/.claude"),
|
||||
AgentProvisionDir(f"{guest_home}/.claude/themes"),
|
||||
)
|
||||
claude_config = state_dir / "claude.json"
|
||||
claude_projects = {guest_home: {"hasTrustDialogAccepted": True}}
|
||||
claude_projects[trusted_path] = {"hasTrustDialogAccepted": True}
|
||||
@@ -108,15 +158,45 @@ class ClaudeAgentProvider(AgentProvider):
|
||||
"bypassPermissionsModeAccepted": True,
|
||||
"projects": claude_projects,
|
||||
}
|
||||
if label:
|
||||
payload["name"] = label
|
||||
if color:
|
||||
payload["color"] = color
|
||||
claude_config.write_text(json.dumps(payload, indent=2) + "\n")
|
||||
claude_config.chmod(0o600)
|
||||
files = (
|
||||
files = [
|
||||
AgentProvisionFile(claude_config, f"{guest_home}/.claude.json"),
|
||||
)
|
||||
]
|
||||
|
||||
claude_settings = state_dir / "claude-settings.json"
|
||||
claude_settings_payload: dict[str, object] = {}
|
||||
if label or color:
|
||||
statusline_script = state_dir / "claude-statusline.sh"
|
||||
statusline_script.write_text(_status_line_script(label, color))
|
||||
statusline_script.chmod(0o755)
|
||||
files.append(AgentProvisionFile(
|
||||
statusline_script,
|
||||
f"{guest_home}/.claude/statusline.sh",
|
||||
mode="755",
|
||||
))
|
||||
claude_settings_payload["statusLine"] = {
|
||||
"type": "command",
|
||||
"command": "~/.claude/statusline.sh",
|
||||
}
|
||||
theme_payload = _custom_theme_payload(color)
|
||||
if theme_payload is not None:
|
||||
theme_name = f"bot-bottle-{docker_mod.slugify(label or color)}"
|
||||
theme_file = state_dir / f"{theme_name}.json"
|
||||
theme_file.write_text(json.dumps(theme_payload, indent=2) + "\n")
|
||||
theme_file.chmod(0o644)
|
||||
files.append(AgentProvisionFile(
|
||||
theme_file,
|
||||
f"{guest_home}/.claude/themes/{theme_name}.json",
|
||||
))
|
||||
claude_settings_payload["theme"] = f"custom:{theme_name}"
|
||||
if claude_settings_payload:
|
||||
claude_settings.write_text(json.dumps(claude_settings_payload, indent=2) + "\n")
|
||||
claude_settings.chmod(0o600)
|
||||
files.append(AgentProvisionFile(
|
||||
claude_settings,
|
||||
f"{guest_home}/.claude/settings.json",
|
||||
))
|
||||
egress_routes = (EgressRoute(
|
||||
host="api.anthropic.com",
|
||||
auth_scheme="Bearer" if auth_token else "",
|
||||
@@ -127,7 +207,7 @@ class ClaudeAgentProvider(AgentProvider):
|
||||
env_vars["CLAUDE_CODE_OAUTH_TOKEN"] = "egress-placeholder"
|
||||
hidden_env_names = frozenset({"CLAUDE_CODE_OAUTH_TOKEN"})
|
||||
|
||||
has_prompt = _prepend_display_identity(prompt_file, label, color)
|
||||
has_prompt = prompt_file.exists() and bool(prompt_file.read_text())
|
||||
return AgentProvisionPlan(
|
||||
template=_RUNTIME.template,
|
||||
command=_RUNTIME.command,
|
||||
@@ -140,7 +220,8 @@ class ClaudeAgentProvider(AgentProvider):
|
||||
env_vars=env_vars,
|
||||
guest_env=resolved_guest_env,
|
||||
has_prompt=has_prompt,
|
||||
files=files,
|
||||
dirs=dirs,
|
||||
files=tuple(files),
|
||||
egress_routes=egress_routes,
|
||||
hidden_env_names=hidden_env_names,
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@ from ...agent_provider import (
|
||||
CODEX_HOST_CREDENTIAL_HOSTS,
|
||||
AgentProvider,
|
||||
AgentProviderRuntime,
|
||||
AgentProvisionCommand,
|
||||
AgentProvisionDir,
|
||||
AgentProvisionCommand,
|
||||
AgentProvisionFile,
|
||||
AgentProvisionPlan,
|
||||
)
|
||||
@@ -47,26 +47,6 @@ def _prompt_path(guest_home: str) -> str:
|
||||
return f"{guest_home}/.bot-bottle-prompt.txt"
|
||||
|
||||
|
||||
def _display_identity_prompt(label: str, color: str) -> str:
|
||||
lines: list[str] = []
|
||||
if label:
|
||||
lines.append(f"Name: {label}")
|
||||
if color:
|
||||
lines.append(f"Color: {color}")
|
||||
if not lines:
|
||||
return ""
|
||||
return "Bot-bottle agent display identity:\n" + "\n".join(lines)
|
||||
|
||||
|
||||
def _prepend_display_identity(prompt_file: Path, label: str, color: str) -> bool:
|
||||
identity = _display_identity_prompt(label, color)
|
||||
original = prompt_file.read_text() if prompt_file.exists() else ""
|
||||
if not identity:
|
||||
return bool(original)
|
||||
prompt_file.write_text(f"{identity}\n\n{original}" if original else f"{identity}\n")
|
||||
return True
|
||||
|
||||
|
||||
_RUNTIME = AgentProviderRuntime(
|
||||
template="codex",
|
||||
command="codex",
|
||||
@@ -98,7 +78,7 @@ class CodexAgentProvider(AgentProvider):
|
||||
label: str = "",
|
||||
color: str = "",
|
||||
) -> AgentProvisionPlan:
|
||||
del auth_token # Claude-only knob
|
||||
del auth_token, label, color # Claude-only / title-only knobs
|
||||
resolved_guest_env = dict(guest_env or {})
|
||||
guest_home = self.guest_home
|
||||
trusted_path = trusted_project_path or guest_home
|
||||
@@ -122,6 +102,11 @@ class CodexAgentProvider(AgentProvider):
|
||||
config_file.write_text(
|
||||
f'[projects."{toml_path}"]\n'
|
||||
'trust_level = "trusted"\n'
|
||||
"\n"
|
||||
"[tui]\n"
|
||||
'status_line = ["model", "cwd"]\n'
|
||||
'terminal_title = ["spinner", "project"]\n'
|
||||
'theme = "dark-ansi"\n'
|
||||
)
|
||||
config_file.chmod(0o600)
|
||||
files.append(AgentProvisionFile(config_file, config_path))
|
||||
@@ -164,7 +149,7 @@ class CodexAgentProvider(AgentProvider):
|
||||
"guest, but Codex did not accept it"
|
||||
)))
|
||||
|
||||
has_prompt = _prepend_display_identity(prompt_file, label, color)
|
||||
has_prompt = prompt_file.exists() and bool(prompt_file.read_text())
|
||||
return AgentProvisionPlan(
|
||||
template=_RUNTIME.template,
|
||||
command=_RUNTIME.command,
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
config = Path(tmp, "codex-config.toml").read_text()
|
||||
self.assertIn('[projects."/home/node/workspace"]', config)
|
||||
|
||||
def test_codex_injects_name_and_color_into_prompt(self):
|
||||
def test_codex_writes_tui_settings_without_mutating_prompt(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||
prompt_file = Path(tmp) / "prompt.txt"
|
||||
prompt_file.write_text("Existing instructions.\n")
|
||||
@@ -76,11 +76,12 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
color="bright-cyan",
|
||||
)
|
||||
prompt = prompt_file.read_text()
|
||||
config = Path(tmp, "codex-config.toml").read_text()
|
||||
self.assertTrue(plan.has_prompt)
|
||||
self.assertIn("Bot-bottle agent display identity:", prompt)
|
||||
self.assertIn("Name: review-api", prompt)
|
||||
self.assertIn("Color: bright-cyan", prompt)
|
||||
self.assertTrue(prompt.endswith("Existing instructions.\n"))
|
||||
self.assertEqual("Existing instructions.\n", prompt)
|
||||
self.assertIn("[tui]", config)
|
||||
self.assertIn('status_line = ["model", "cwd"]', config)
|
||||
self.assertIn('terminal_title = ["spinner", "project"]', config)
|
||||
|
||||
def test_codex_forward_host_credentials_adds_auth_and_verify(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||
@@ -146,7 +147,7 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
self.assertIn("/home/node", config["projects"])
|
||||
self.assertIn("/home/node/workspace", config["projects"])
|
||||
|
||||
def test_claude_injects_name_and_color_into_prompt(self):
|
||||
def test_claude_writes_statusline_and_theme_without_mutating_prompt(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||
prompt_file = Path(tmp) / "prompt.txt"
|
||||
prompt_file.write_text("Existing instructions.\n")
|
||||
@@ -160,11 +161,11 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
||||
color="green",
|
||||
)
|
||||
prompt = prompt_file.read_text()
|
||||
settings = json.loads(Path(tmp, "claude-settings.json").read_text())
|
||||
self.assertTrue(plan.has_prompt)
|
||||
self.assertIn("Bot-bottle agent display identity:", prompt)
|
||||
self.assertIn("Name: research-ui", prompt)
|
||||
self.assertIn("Color: green", prompt)
|
||||
self.assertTrue(prompt.endswith("Existing instructions.\n"))
|
||||
self.assertEqual("Existing instructions.\n", prompt)
|
||||
self.assertEqual("~/.claude/statusline.sh", settings["statusLine"]["command"])
|
||||
self.assertEqual("custom:bot-bottle-research-ui", settings["theme"])
|
||||
|
||||
def test_codex_forward_host_credentials_populates_egress_routes(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||
|
||||
@@ -8,6 +8,8 @@ either side are expected to diverge the implementations."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
@@ -127,7 +129,7 @@ class TestClaudeProvisionPrompt(unittest.TestCase):
|
||||
self.assertIsNone(r)
|
||||
bottle.cp_in.assert_called_once()
|
||||
|
||||
def test_returns_path_when_provider_prompt_has_identity(self):
|
||||
def test_returns_path_when_provider_prompt_exists(self):
|
||||
bottle = _make_bottle()
|
||||
provision = AgentProvisionPlan(
|
||||
template="claude", command="claude", prompt_mode="append_file",
|
||||
@@ -261,6 +263,35 @@ class TestClaudeProvision(unittest.TestCase):
|
||||
_plan(agent_provision=provision), bottle,
|
||||
)
|
||||
|
||||
|
||||
class TestClaudeUiProvision(unittest.TestCase):
|
||||
def test_writes_statusline_and_custom_theme_files(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-claude-ui.") as tmp:
|
||||
state_dir = Path(tmp)
|
||||
prompt_file = state_dir / "prompt.txt"
|
||||
prompt_file.write_text("Existing instructions.\n")
|
||||
plan = ClaudeAgentProvider().provision_plan(
|
||||
dockerfile="",
|
||||
state_dir=state_dir,
|
||||
instance_name="bot-bottle-demo-abc12",
|
||||
prompt_file=prompt_file,
|
||||
label="research-ui",
|
||||
color="bright-cyan",
|
||||
)
|
||||
settings = json.loads((state_dir / "claude-settings.json").read_text())
|
||||
statusline = (state_dir / "claude-statusline.sh").read_text()
|
||||
theme = json.loads((state_dir / "bot-bottle-research-ui.json").read_text())
|
||||
prompt_text = prompt_file.read_text()
|
||||
self.assertTrue(plan.has_prompt)
|
||||
self.assertEqual("Existing instructions.\n", prompt_text)
|
||||
self.assertEqual("command", settings["statusLine"]["type"])
|
||||
self.assertEqual("~/.claude/statusline.sh", settings["statusLine"]["command"])
|
||||
self.assertEqual("custom:bot-bottle-research-ui", settings["theme"])
|
||||
self.assertIn("research-ui", statusline)
|
||||
self.assertIn("\x1b[96m", statusline)
|
||||
self.assertEqual("dark", theme["base"])
|
||||
self.assertEqual("ansi:cyanBright", theme["overrides"]["claude"])
|
||||
|
||||
def test_runs_verify_commands(self):
|
||||
provision = AgentProvisionPlan(
|
||||
template="claude", command="claude", prompt_mode="append_file",
|
||||
|
||||
@@ -9,6 +9,7 @@ no claude equivalent."""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@@ -130,7 +131,7 @@ class TestCodexProvisionPrompt(unittest.TestCase):
|
||||
self.assertIsNone(r)
|
||||
bottle.cp_in.assert_called_once()
|
||||
|
||||
def test_returns_path_when_provider_prompt_has_identity(self):
|
||||
def test_returns_path_when_provider_prompt_exists(self):
|
||||
bottle = _make_bottle()
|
||||
provision = AgentProvisionPlan(
|
||||
template="codex", command="codex",
|
||||
@@ -146,6 +147,28 @@ class TestCodexProvisionPrompt(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual("/home/node/.bot-bottle-prompt.txt", r)
|
||||
|
||||
def test_writes_tui_settings_into_codex_config(self):
|
||||
with tempfile.TemporaryDirectory(prefix="bb-codex-ui.") as tmp:
|
||||
state_dir = Path(tmp)
|
||||
prompt_file = state_dir / "prompt.txt"
|
||||
prompt_file.write_text("Existing instructions.\n")
|
||||
plan = CodexAgentProvider().provision_plan(
|
||||
dockerfile="",
|
||||
state_dir=state_dir,
|
||||
instance_name="bot-bottle-demo-abc12",
|
||||
prompt_file=prompt_file,
|
||||
label="research-ui",
|
||||
color="bright-cyan",
|
||||
)
|
||||
config = (state_dir / "codex-config.toml").read_text()
|
||||
prompt_text = prompt_file.read_text()
|
||||
self.assertTrue(plan.has_prompt)
|
||||
self.assertEqual("Existing instructions.\n", prompt_text)
|
||||
self.assertIn("[tui]", config)
|
||||
self.assertIn('status_line = ["model", "cwd"]', config)
|
||||
self.assertIn('terminal_title = ["spinner", "project"]', config)
|
||||
self.assertIn('theme = "dark-ansi"', config)
|
||||
|
||||
|
||||
class TestCodexProvisionSkills(unittest.TestCase):
|
||||
def test_noop_when_agent_has_no_skills(self):
|
||||
|
||||
Reference in New Issue
Block a user