Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions examples/codex-cli-bootstrap-message-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path
Expand Down Expand Up @@ -51,9 +52,15 @@
"--bootstrap",
"--codex-app",
"Do not spend quota for a setup-only turn",
"python3 -m pip install --upgrade loopx",
"workflow-skills --install",
"loopx-project.github.io/loopx/install.sh",
*(
("loopx doctor",)
if os.name == "nt"
else (
"python3 -m pip install --upgrade loopx",
"workflow-skills --install",
"loopx-project.github.io/loopx/install.sh",
)
),
)


Expand All @@ -63,9 +70,17 @@ def assert_message_contract(payload: dict[str, object]) -> None:
assert payload["invocation_mode"] == "codex_cli_setup_then_goal_mode", payload
assert payload["goal_id"] == GOAL_ID, payload
assert payload["agent_id"] == AGENT_ID, payload
assert "python3 -m pip install --upgrade loopx" in str(payload["install_repair_command"]), payload
assert "workflow-skills --install" in str(payload["install_repair_command"]), payload
assert "loopx-project.github.io/loopx/install.sh" in str(payload["install_repair_command"]), payload
repair = str(payload["install_repair_command"])
assert "loopx doctor" in repair, payload
if os.name == "nt":
# Native Windows has no python3 or Bash; the owner prints the checkout
# installer or the running interpreter's pip repair instead.
assert "| bash" not in repair, payload
assert "install-windows.ps1" in repair or "-m pip install --upgrade loopx" in repair, payload
else:
assert "python3 -m pip install --upgrade loopx" in repair, payload
assert "workflow-skills --install" in repair, payload
assert "loopx-project.github.io/loopx/install.sh" in repair, payload
assert payload["existing_goal_probe_command"] == payload["quota_guard_command"], payload
assert "heartbeat-prompt --thin" in str(payload["heartbeat_prompt_command"]), payload
assert "heartbeat-prompt --thin" in str(payload["heartbeat_prompt_json_command"]), payload
Expand Down Expand Up @@ -229,8 +244,12 @@ def main() -> int:
assert "Fresh Repo Install Repair" in cli_markdown, cli_markdown
assert "Post-Bootstrap Loop Loader" in cli_markdown, cli_markdown
assert "Transcript-Free Validation Checklist" in cli_markdown, cli_markdown
assert "loopx-project.github.io/loopx/install.sh" in cli_markdown, cli_markdown
assert "python3 -m pip install --upgrade loopx" in cli_markdown, cli_markdown
if os.name == "nt":
assert "loopx doctor" in cli_markdown, cli_markdown
assert "| bash" not in cli_markdown, cli_markdown
else:
assert "loopx-project.github.io/loopx/install.sh" in cli_markdown, cli_markdown
assert "python3 -m pip install --upgrade loopx" in cli_markdown, cli_markdown

cli_message_only = run_cli(
"codex-cli-bootstrap-message",
Expand Down
13 changes: 11 additions & 2 deletions examples/codex-cli-first-run-rehearsal-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from __future__ import annotations

import os
import subprocess
import sys
from pathlib import Path
Expand Down Expand Up @@ -95,8 +96,16 @@ def assert_cli_surfaces_align() -> None:
assert not message.startswith("/goal "), message
assert "setup/bootstrap instruction" in normalized, message
assert "/goal <loader task_body>" in normalized, message
assert "python3 -m pip install --upgrade loopx" in normalized, message
assert "workflow-skills --install" in normalized, message
assert "loopx doctor" in normalized, message
if os.name == "nt":
assert "| bash" not in normalized, message
assert (
"install-windows.ps1" in normalized
or "-m pip install --upgrade loopx" in normalized
), message
else:
assert "python3 -m pip install --upgrade loopx" in normalized, message
assert "workflow-skills --install" in normalized, message
assert "Codex CLI TUI" in normalized, message
assert "quota should-run" in normalized, message
assert "settlement_plan.ordered_steps" in normalized, message
Expand Down
21 changes: 16 additions & 5 deletions examples/codex-cli-tui-bootstrap-smoke-bundle-smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,18 @@ def main() -> None:
assert bundle["agent_id"] == AGENT_ID, bundle
assert "--message-only" in bundle["message_only_command"], bundle
assert str(fresh_repo) in bundle["message_only_command"], bundle
assert "loopx-project.github.io/loopx/install.sh" in bundle["install_repair_command"], bundle
assert "python3 -m pip install --upgrade loopx" in bundle["install_repair_command"], bundle
assert "workflow-skills --install" in bundle["install_repair_command"], bundle
repair = bundle["install_repair_command"]
assert "loopx doctor" in repair, bundle
if os.name == "nt":
assert "| bash" not in repair, bundle
assert (
"install-windows.ps1" in repair
or "-m pip install --upgrade loopx" in repair
), bundle
else:
assert "loopx-project.github.io/loopx/install.sh" in repair, bundle
assert "python3 -m pip install --upgrade loopx" in repair, bundle
assert "workflow-skills --install" in repair, bundle
assert "quota should-run" in bundle["quota_guard_command"], bundle
assert "--agent-id codex-side-bypass" in bundle["quota_guard_command"], bundle
assert "refresh-state --goal-id public-fresh-codex-cli-goal" in bundle["refresh_command"], bundle
Expand Down Expand Up @@ -183,8 +192,10 @@ def main() -> None:
assert not message_only.startswith("/goal "), message_only
assert "# Codex CLI LoopX Bootstrap Message" not in message_only, message_only
assert "Fresh Repo Install Repair" not in message_only, message_only
assert "loopx-project.github.io/loopx/install.sh" in message_only, message_only
assert "python3 -m pip install --upgrade loopx" in message_only, message_only
assert "loopx doctor" in message_only, message_only
if os.name != "nt":
assert "loopx-project.github.io/loopx/install.sh" in message_only, message_only
assert "python3 -m pip install --upgrade loopx" in message_only, message_only
assert "/goal <loader task_body>" in message_only, message_only
assert "quota should-run" in message_only, message_only
assert "refresh-state --goal-id public-fresh-codex-cli-goal" in message_only, message_only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,16 @@ def assert_installed_release(
assert not message.startswith("/goal "), message
assert "setup/bootstrap instruction" in normalized_message, message
assert "/goal <loader task_body>" in normalized_message, message
assert "python3 -m pip install --upgrade loopx" in normalized_message, message
assert "workflow-skills --install" in normalized_message, message
assert "loopx doctor" in normalized_message, message
if os.name == "nt":
assert "| bash" not in normalized_message, message
assert (
"install-windows.ps1" in normalized_message
or "-m pip install --upgrade loopx" in normalized_message
), message
else:
assert "python3 -m pip install --upgrade loopx" in normalized_message, message
assert "workflow-skills --install" in normalized_message, message
assert "Codex CLI TUI" in normalized_message, message
assert "quota should-run" in normalized_message, message
assert "settlement_plan.ordered_steps" in normalized_message, message
Expand Down
62 changes: 32 additions & 30 deletions loopx/agent_onboarding.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@
SKILLS_ROOT_LABEL as KIRO_CLI_SKILLS_ROOT_LABEL,
)
from .project_prompt import (
host_command,
render_available_capability_args,
render_codex_cli_install_preflight,
render_quota_guard_command,
shell_arg,
shell_arg_for_host,
)
from .registry import registry_goals
from .skill_install_readback import (
Expand All @@ -46,31 +48,31 @@

def _surface_install_command(agent_type: str, cli_bin: str, project: str) -> str | None:
if agent_type in {"codex-app", "codex-app-ssh", "codex-ide-plugin", "codex-cli"}:
return f"{shell_arg(cli_bin)} slash-commands --install --surface codex"
return f"{host_command(cli_bin)} slash-commands --install --surface codex"
if agent_type == "claude-code":
return f"{shell_arg(cli_bin)} slash-commands --install --surface claude-code"
return f"{host_command(cli_bin)} slash-commands --install --surface claude-code"
if agent_type == "opencode":
return (
f"{shell_arg(cli_bin)} slash-commands --install --surface opencode "
f"{host_command(cli_bin)} slash-commands --install --surface opencode "
"--with-goal-bridge"
)
if agent_type == "gemini-cli":
return f"{shell_arg(cli_bin)} slash-commands --install --surface gemini"
return f"{host_command(cli_bin)} slash-commands --install --surface gemini"
if agent_type == "cursor-agent":
return f"{shell_arg(cli_bin)} slash-commands --install --surface cursor"
return f"{host_command(cli_bin)} slash-commands --install --surface cursor"
if agent_type == "zcode":
return f"{shell_arg(cli_bin)} slash-commands --install --surface zcode"
return f"{host_command(cli_bin)} slash-commands --install --surface zcode"
if agent_type == "agy":
return f"{shell_arg(cli_bin)} slash-commands --install --surface agy"
return f"{host_command(cli_bin)} slash-commands --install --surface agy"
if agent_type == "kiro-cli":
return f"{shell_arg(cli_bin)} slash-commands --install --surface kiro-cli"
return f"{host_command(cli_bin)} slash-commands --install --surface kiro-cli"
if agent_type == "pi":
# The slash-commands installer resolves the Pi extension target through
# --pi-project; pass the resolved project so the command stays correct
# when agent-onboard runs from any cwd.
return (
f"{shell_arg(cli_bin)} slash-commands --install --surface pi "
f"--pi-project {shell_arg(project)}"
f"{host_command(cli_bin)} slash-commands --install --surface pi "
f"--pi-project {shell_arg_for_host(project)}"
)
return None

Expand Down Expand Up @@ -102,15 +104,15 @@ def _project_skill_command(
execute: bool = False,
) -> str:
parts = [
shell_arg(cli_bin),
host_command(cli_bin),
"project-skill",
command,
"--project",
shell_arg(project),
shell_arg_for_host(project),
"--skill",
shell_arg(skill_id),
shell_arg_for_host(skill_id),
"--surface",
shell_arg(surface),
shell_arg_for_host(surface),
]
if execute:
parts.append("--execute")
Expand Down Expand Up @@ -303,21 +305,21 @@ def _bootstrap_pack_command(
"other-agent": "other-agent",
}
parts = [
shell_arg(cli_bin),
host_command(cli_bin),
"bootstrap-command-pack",
"--project",
shell_arg(project),
shell_arg_for_host(project),
"--goal-id",
shell_arg(goal_id),
shell_arg_for_host(goal_id),
"--host-surface",
shell_arg(surface_by_type[agent_type]),
shell_arg_for_host(surface_by_type[agent_type]),
]
if agent_id:
parts.extend(["--agent-id", shell_arg(agent_id)])
parts.extend(["--agent-id", shell_arg_for_host(agent_id)])
if task_text:
parts.extend(["--goal-text", shell_arg(task_text)])
parts.extend(["--goal-text", shell_arg_for_host(task_text)])
for capability in available_capabilities or []:
parts.extend(["--available-capability", shell_arg(capability)])
parts.extend(["--available-capability", shell_arg_for_host(capability)])
return " ".join(parts)


Expand Down Expand Up @@ -514,12 +516,12 @@ def build_agent_onboarding_packet(
else None
),
"agent_onboard_recheck": (
f"{shell_arg(cli_bin)} agent-onboard "
f"--agent-type {shell_arg(canonical_agent_type)} "
f"--project {shell_arg(resolved_project)} "
f"--goal-id {shell_arg(resolved_goal_id)}"
f"{host_command(cli_bin)} agent-onboard "
f"--agent-type {shell_arg_for_host(canonical_agent_type)} "
f"--project {shell_arg_for_host(resolved_project)} "
f"--goal-id {shell_arg_for_host(resolved_goal_id)}"
+ (
f" --agent-id {shell_arg(str(selected_agent_id))}"
f" --agent-id {shell_arg_for_host(str(selected_agent_id))}"
if selected_agent_id
else ""
)
Expand All @@ -530,11 +532,11 @@ def build_agent_onboarding_packet(
commands["install_command_facade"] = install_command
if canonical_agent_type == "codex-cli":
commands["codex_cli_bootstrap_message"] = (
f"{shell_arg(cli_bin)} codex-cli-bootstrap-message "
f"--project {shell_arg(resolved_project)} "
f"--goal-id {shell_arg(resolved_goal_id)}"
f"{host_command(cli_bin)} codex-cli-bootstrap-message "
f"--project {shell_arg_for_host(resolved_project)} "
f"--goal-id {shell_arg_for_host(resolved_goal_id)}"
+ (
f" --agent-id {shell_arg(str(selected_agent_id))}"
f" --agent-id {shell_arg_for_host(str(selected_agent_id))}"
if selected_agent_id
else ""
)
Expand Down
22 changes: 14 additions & 8 deletions loopx/control_plane/heartbeat/task_body.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from typing import Any

from ...project_prompt import render_turn_bootstrap
from .rules import (
CODEX_NATIVE_GOAL_UNCHANGED_WAIT_RULE,
DEFAULT_MATERIAL_QUEUE_RULE,
Expand Down Expand Up @@ -53,10 +54,15 @@ def bind_exact_turn_settlement_task_body(
) -> str:
if turn_instance_id is None:
return task_body
bound = task_body.replace(
bound = task_body
for placeholder in (
"LOOPX_TURN=<current_time_iso>",
f"LOOPX_TURN={turn_instance_id}",
)
'$LOOPX_TURN = "<current_time_iso>"',
):
bound = bound.replace(
placeholder,
render_turn_bootstrap(value=turn_instance_id),
)
plan_path = ("writeback.settlement_plan.ordered_steps" if captured_envelope
else "interaction_contract.cli_channel.settlement_plan.ordered_steps")
return (
Expand Down Expand Up @@ -103,7 +109,7 @@ def render_heartbeat_task_body(

```bash
{cli_preflight}
LOOPX_TURN=<current_time_iso>
{render_turn_bootstrap()}
{pr_review_pre_quota_block}{quota_guard_command}
```

Expand Down Expand Up @@ -273,7 +279,7 @@ def render_brief_heartbeat_task_body(

```bash
{cli_preflight}
LOOPX_TURN=<current_time_iso>
{render_turn_bootstrap()}
{pr_review_pre_quota_block}{quota_guard_command}
```

Expand Down Expand Up @@ -341,7 +347,7 @@ def render_compact_heartbeat_task_body(

```bash
{cli_preflight}
LOOPX_TURN=<current_time_iso>
{render_turn_bootstrap()}
{pr_review_pre_quota_block}{quota_guard_command}
```

Expand Down Expand Up @@ -638,7 +644,7 @@ def render_thin_heartbeat_task_body(
After a workspace or state change, re-enter the guard before continuing work.

```sh
LOOPX_TURN=<current_time_iso>
{render_turn_bootstrap()}
{pr_review_pre_quota_instruction}{quota_guard_command}
```
{SCOPE_BOUNDED_WORK_RULE}
Expand All @@ -665,7 +671,7 @@ def render_thin_heartbeat_task_body(
{HOST_LOOP_QUOTA_DISPATCH_RULE}
{HEARTBEAT_TURN_BOOTSTRAP_RULE}
```sh
LOOPX_TURN=<current_time_iso>
{render_turn_bootstrap()}
{pr_review_pre_quota_instruction}{quota_guard_command}
```
{HEARTBEAT_NOTIFICATION_RULE_SHORT}
Expand Down
Loading
Loading