feat: forward agent display identity to prompts
This commit is contained in:
@@ -107,6 +107,7 @@ class AgentProvisionPlan:
|
|||||||
instance_name: str
|
instance_name: str
|
||||||
prompt_file: Path
|
prompt_file: Path
|
||||||
guest_env: dict[str, str]
|
guest_env: dict[str, str]
|
||||||
|
has_prompt: bool = False
|
||||||
env_vars: dict[str, str] = field(default_factory=dict)
|
env_vars: dict[str, str] = field(default_factory=dict)
|
||||||
dirs: tuple[AgentProvisionDir, ...] = ()
|
dirs: tuple[AgentProvisionDir, ...] = ()
|
||||||
files: tuple[AgentProvisionFile, ...] = ()
|
files: tuple[AgentProvisionFile, ...] = ()
|
||||||
|
|||||||
@@ -38,6 +38,27 @@ def _skills_dir(guest_home: str) -> str:
|
|||||||
def _prompt_path(guest_home: str) -> str:
|
def _prompt_path(guest_home: str) -> str:
|
||||||
return f"{guest_home}/.bot-bottle-prompt.txt"
|
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(
|
_RUNTIME = AgentProviderRuntime(
|
||||||
template="claude",
|
template="claude",
|
||||||
command="claude",
|
command="claude",
|
||||||
@@ -106,6 +127,7 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
env_vars["CLAUDE_CODE_OAUTH_TOKEN"] = "egress-placeholder"
|
env_vars["CLAUDE_CODE_OAUTH_TOKEN"] = "egress-placeholder"
|
||||||
hidden_env_names = frozenset({"CLAUDE_CODE_OAUTH_TOKEN"})
|
hidden_env_names = frozenset({"CLAUDE_CODE_OAUTH_TOKEN"})
|
||||||
|
|
||||||
|
has_prompt = _prepend_display_identity(prompt_file, label, color)
|
||||||
return AgentProvisionPlan(
|
return AgentProvisionPlan(
|
||||||
template=_RUNTIME.template,
|
template=_RUNTIME.template,
|
||||||
command=_RUNTIME.command,
|
command=_RUNTIME.command,
|
||||||
@@ -117,6 +139,7 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
prompt_file=prompt_file,
|
prompt_file=prompt_file,
|
||||||
env_vars=env_vars,
|
env_vars=env_vars,
|
||||||
guest_env=resolved_guest_env,
|
guest_env=resolved_guest_env,
|
||||||
|
has_prompt=has_prompt,
|
||||||
files=files,
|
files=files,
|
||||||
egress_routes=egress_routes,
|
egress_routes=egress_routes,
|
||||||
hidden_env_names=hidden_env_names,
|
hidden_env_names=hidden_env_names,
|
||||||
@@ -158,7 +181,7 @@ class ClaudeAgentProvider(AgentProvider):
|
|||||||
user="root",
|
user="root",
|
||||||
)
|
)
|
||||||
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
||||||
return prompt_path if agent.prompt else None
|
return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None
|
||||||
|
|
||||||
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
||||||
"""Apply the claude-side declarative provision steps from
|
"""Apply the claude-side declarative provision steps from
|
||||||
|
|||||||
@@ -46,6 +46,27 @@ def _skills_dir(guest_home: str) -> str:
|
|||||||
def _prompt_path(guest_home: str) -> str:
|
def _prompt_path(guest_home: str) -> str:
|
||||||
return f"{guest_home}/.bot-bottle-prompt.txt"
|
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(
|
_RUNTIME = AgentProviderRuntime(
|
||||||
template="codex",
|
template="codex",
|
||||||
command="codex",
|
command="codex",
|
||||||
@@ -77,7 +98,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
label: str = "",
|
label: str = "",
|
||||||
color: str = "",
|
color: str = "",
|
||||||
) -> AgentProvisionPlan:
|
) -> AgentProvisionPlan:
|
||||||
del auth_token, label, color # Claude-only knobs
|
del auth_token # Claude-only knob
|
||||||
resolved_guest_env = dict(guest_env or {})
|
resolved_guest_env = dict(guest_env or {})
|
||||||
guest_home = self.guest_home
|
guest_home = self.guest_home
|
||||||
trusted_path = trusted_project_path or guest_home
|
trusted_path = trusted_project_path or guest_home
|
||||||
@@ -143,6 +164,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
"guest, but Codex did not accept it"
|
"guest, but Codex did not accept it"
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
has_prompt = _prepend_display_identity(prompt_file, label, color)
|
||||||
return AgentProvisionPlan(
|
return AgentProvisionPlan(
|
||||||
template=_RUNTIME.template,
|
template=_RUNTIME.template,
|
||||||
command=_RUNTIME.command,
|
command=_RUNTIME.command,
|
||||||
@@ -154,6 +176,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
prompt_file=prompt_file,
|
prompt_file=prompt_file,
|
||||||
env_vars=env_vars,
|
env_vars=env_vars,
|
||||||
guest_env=resolved_guest_env,
|
guest_env=resolved_guest_env,
|
||||||
|
has_prompt=has_prompt,
|
||||||
dirs=tuple(dirs),
|
dirs=tuple(dirs),
|
||||||
files=tuple(files),
|
files=tuple(files),
|
||||||
pre_copy=tuple(pre_copy),
|
pre_copy=tuple(pre_copy),
|
||||||
@@ -198,7 +221,7 @@ class CodexAgentProvider(AgentProvider):
|
|||||||
user="root",
|
user="root",
|
||||||
)
|
)
|
||||||
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
agent = plan.spec.manifest.agents[plan.spec.agent_name]
|
||||||
return prompt_path if agent.prompt else None
|
return prompt_path if plan.agent_provision.has_prompt or agent.prompt else None
|
||||||
|
|
||||||
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
def provision(self, plan: "BottlePlan", bottle: "Bottle") -> None:
|
||||||
"""Apply the codex-side declarative provision steps from
|
"""Apply the codex-side declarative provision steps from
|
||||||
|
|||||||
@@ -62,6 +62,26 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
config = Path(tmp, "codex-config.toml").read_text()
|
config = Path(tmp, "codex-config.toml").read_text()
|
||||||
self.assertIn('[projects."/home/node/workspace"]', config)
|
self.assertIn('[projects."/home/node/workspace"]', config)
|
||||||
|
|
||||||
|
def test_codex_injects_name_and_color_into_prompt(self):
|
||||||
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
|
prompt_file = Path(tmp) / "prompt.txt"
|
||||||
|
prompt_file.write_text("Existing instructions.\n")
|
||||||
|
plan = build_agent_provision_plan(
|
||||||
|
template="codex",
|
||||||
|
dockerfile="",
|
||||||
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=prompt_file,
|
||||||
|
label="review-api",
|
||||||
|
color="bright-cyan",
|
||||||
|
)
|
||||||
|
prompt = prompt_file.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"))
|
||||||
|
|
||||||
def test_codex_forward_host_credentials_adds_auth_and_verify(self):
|
def test_codex_forward_host_credentials_adds_auth_and_verify(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
home = Path(tmp) / "host-codex"
|
home = Path(tmp) / "host-codex"
|
||||||
@@ -126,6 +146,26 @@ class TestAgentProviderRuntime(unittest.TestCase):
|
|||||||
self.assertIn("/home/node", config["projects"])
|
self.assertIn("/home/node", config["projects"])
|
||||||
self.assertIn("/home/node/workspace", config["projects"])
|
self.assertIn("/home/node/workspace", config["projects"])
|
||||||
|
|
||||||
|
def test_claude_injects_name_and_color_into_prompt(self):
|
||||||
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
|
prompt_file = Path(tmp) / "prompt.txt"
|
||||||
|
prompt_file.write_text("Existing instructions.\n")
|
||||||
|
plan = build_agent_provision_plan(
|
||||||
|
template="claude",
|
||||||
|
dockerfile="",
|
||||||
|
state_dir=Path(tmp),
|
||||||
|
instance_name="bot-bottle-test",
|
||||||
|
prompt_file=prompt_file,
|
||||||
|
label="research-ui",
|
||||||
|
color="green",
|
||||||
|
)
|
||||||
|
prompt = prompt_file.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"))
|
||||||
|
|
||||||
def test_codex_forward_host_credentials_populates_egress_routes(self):
|
def test_codex_forward_host_credentials_populates_egress_routes(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
with tempfile.TemporaryDirectory(prefix="bb-provider.") as tmp:
|
||||||
home = Path(tmp) / "host-codex"
|
home = Path(tmp) / "host-codex"
|
||||||
|
|||||||
@@ -127,6 +127,21 @@ class TestClaudeProvisionPrompt(unittest.TestCase):
|
|||||||
self.assertIsNone(r)
|
self.assertIsNone(r)
|
||||||
bottle.cp_in.assert_called_once()
|
bottle.cp_in.assert_called_once()
|
||||||
|
|
||||||
|
def test_returns_path_when_provider_prompt_has_identity(self):
|
||||||
|
bottle = _make_bottle()
|
||||||
|
provision = AgentProvisionPlan(
|
||||||
|
template="claude", command="claude", prompt_mode="append_file",
|
||||||
|
image="", dockerfile="", guest_home="/home/node",
|
||||||
|
instance_name="bot-bottle-demo-abc12",
|
||||||
|
prompt_file=Path("/tmp/prompt.txt"),
|
||||||
|
guest_env={},
|
||||||
|
has_prompt=True,
|
||||||
|
)
|
||||||
|
r = ClaudeAgentProvider().provision_prompt(
|
||||||
|
_plan(agent_prompt="", agent_provision=provision), bottle,
|
||||||
|
)
|
||||||
|
self.assertEqual("/home/node/.bot-bottle-prompt.txt", r)
|
||||||
|
|
||||||
def test_chowns_to_node_after_copy(self):
|
def test_chowns_to_node_after_copy(self):
|
||||||
bottle = _make_bottle()
|
bottle = _make_bottle()
|
||||||
ClaudeAgentProvider().provision_prompt(_plan(), bottle)
|
ClaudeAgentProvider().provision_prompt(_plan(), bottle)
|
||||||
|
|||||||
@@ -130,6 +130,22 @@ class TestCodexProvisionPrompt(unittest.TestCase):
|
|||||||
self.assertIsNone(r)
|
self.assertIsNone(r)
|
||||||
bottle.cp_in.assert_called_once()
|
bottle.cp_in.assert_called_once()
|
||||||
|
|
||||||
|
def test_returns_path_when_provider_prompt_has_identity(self):
|
||||||
|
bottle = _make_bottle()
|
||||||
|
provision = AgentProvisionPlan(
|
||||||
|
template="codex", command="codex",
|
||||||
|
prompt_mode="read_prompt_file",
|
||||||
|
image="", dockerfile="", guest_home="/home/node",
|
||||||
|
instance_name="bot-bottle-demo-abc12",
|
||||||
|
prompt_file=Path("/tmp/prompt.txt"),
|
||||||
|
guest_env={},
|
||||||
|
has_prompt=True,
|
||||||
|
)
|
||||||
|
r = CodexAgentProvider().provision_prompt(
|
||||||
|
_plan(agent_prompt="", agent_provision=provision), bottle,
|
||||||
|
)
|
||||||
|
self.assertEqual("/home/node/.bot-bottle-prompt.txt", r)
|
||||||
|
|
||||||
|
|
||||||
class TestCodexProvisionSkills(unittest.TestCase):
|
class TestCodexProvisionSkills(unittest.TestCase):
|
||||||
def test_noop_when_agent_has_no_skills(self):
|
def test_noop_when_agent_has_no_skills(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user