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:
@ -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():
|
||||
|
||||
Reference in New Issue
Block a user