fix(launch-orchestrator): opencode uses plain TUI + ping, not run --attach

Same fix as the loops: opencode run --attach exits after one turn;
plain opencode TUI stays alive in tmux. Send startup prompt via
ping_session (Enter) after 8s init wait. Bootstrap points to
JOURNAL.md rather than sending the full prompt inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
autonomic-bot
2026-05-31 18:30:09 +00:00
parent 2aa3fbda8d
commit 6a6c17f526

View File

@ -78,6 +78,21 @@ def session_alive():
["tmux", "has-session", "-t", SESSION], capture_output=True
).returncode == 0
def _ping(session, msg):
"""Type a message into the tmux session and submit with Enter."""
import time
subprocess.run(["tmux", "send-keys", "-t", session, "-l", "--", msg], capture_output=True)
time.sleep(0.5)
for _ in range(5):
subprocess.run(["tmux", "send-keys", "-t", session, "Enter"], capture_output=True)
time.sleep(1)
r = subprocess.run(["tmux", "capture-pane", "-pt", session],
capture_output=True, text=True)
if msg[:28] not in r.stdout.splitlines()[-4:]:
return
subprocess.run(["tmux", "send-keys", "-t", session, "C-m"], capture_output=True)
time.sleep(0.5)
def resume_id():
sid = os.environ.get("ORCH_SESSION_ID")
if sid:
@ -118,16 +133,13 @@ def start(mode="resume"):
elif BACKEND == "opencode":
if not Path(OPENCODE_BIN).exists():
die(f"opencode not found at {OPENCODE_BIN}")
# No --resume equivalent in opencode; STARTUP_PROMPT orients the new session.
# The session title in the web UI identifies it as the orchestrator.
prompt = STARTUP_PROMPT or (
"You are the cc-ci orchestrator. Read /srv/cc-ci/AGENTS.md and "
"cc-ci-plan/JOURNAL.md for context, then resume supervising the loops."
)
# Plain `opencode` TUI stays alive in tmux and auto-connects to the shared server.
# (`opencode attach URL` exits without a real TTY; plain TUI works because tmux
# allocates a PTY for the pane.) The startup prompt is sent via ping_session after
# the TUI initialises. NO_COLOR=1 skips the first-run theme picker.
cmd = (
f"set -a; . /srv/cc-ci/.testenv; set +a; "
f"{OPENCODE_BIN} {model_flag} run --attach '{OPENCODE_SERVER}' "
f"--title '{SESSION}' '{prompt}'"
f"NO_COLOR=1 {OPENCODE_BIN} {model_flag}"
)
log(f"starting {SESSION} (backend=opencode, model={LOOP_MODEL or 'default'})")
log(f" visible at http://oc.commoninternet.net (tailnet only)")
@ -139,6 +151,22 @@ def start(mode="resume"):
f"cat >> '{LOG_DIR}/{SESSION}.log'"])
log(f"started. attach: tmux attach -t {SESSION}")
# opencode: send the startup prompt once the TUI has connected (~8s).
if BACKEND == "opencode":
import time
time.sleep(8)
prompt = STARTUP_PROMPT or (
"You are the cc-ci orchestrator. Read cc-ci-plan/JOURNAL.md for full "
"context on what's happened and what needs doing, then supervise the loops."
)
# Send a pointer to the journal rather than the full prompt (avoids length limits).
bootstrap = (
"You are the cc-ci orchestrator. Start by reading cc-ci-plan/JOURNAL.md "
"(`cat cc-ci-plan/JOURNAL.md`) for full context, then check loop status "
"(`cc-ci-plan/launch.sh status`) and resume supervising."
)
_ping(SESSION, bootstrap)
# ── main ──────────────────────────────────────────────────────────────────────
def main():