ReviewOS

stacks/uplink

feat: let a text switch one thread between Claude and Codex

#10
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Part of #4. The per-installation half shipped in #7; this is the per-thread half.

A global setting alone cannot answer "use Codex for this one thread" from a phone, which is the point of the product. A per-text prefix is easy to forget and has to survive both the filter and detectWorkdir. The control-word vocabulary already solves this shape of problem.

What is there today

UPLINK_ENGINE and a settings.json field choose the engine for the whole installation, and the menubar offers a picker (app/Uplink/engines.ts, app/Uplink/desktop-agent.ts). Nothing reaches that from a phone, and every thread on a Mac uses the same engine.

parseControl (app/Uplink/commands.ts#L24) matches only a whole message, after stripping a leading slash and trailing punctuation. That is why stop cancels a run while "stop the dev server in ~/Code/x" still reaches the agent as a task. ALIASES (app/Uplink/commands.ts#L8) is the whole vocabulary and HELP_TEXT (#L29) is what help texts back.

A conversation stores one sessionId and one cwd (app/Uplink/store.ts), and begin() resumes when the session id, the cwd and the idle window all agree (app/Uplink/uplink.ts#L299).

Proposal

  1. Add claude and codex to ControlCommand (app/Uplink/commands.ts#L6) and ALIASES. A bare codex switches the thread and replies with confirmation; "codex keeps timing out on ~/Code/x" still reaches the agent, by the same rule that already protects stop.
  2. Add engine to conversations and to runs as defineModel attributes, schema.enum(['claude', 'codex']), default 'claude' so existing rows read correctly. Run buddy generate:migrations. Mirror both in the downloadable app's SCHEMA and RUN_COLUMNS (app/Uplink/sqlite-store.ts), which needs the migration path from the separate issue first.
  3. Switching engines must drop the stored session id. A Claude session id cannot be resumed by Codex and the reverse is equally true. The resume guard at app/Uplink/uplink.ts#L299 gains the engine to its list of things that must agree, and the control word clears sessionId the way new does at #L415.
  4. Pin the engine on the Job (app/Uplink/uplink.ts#L46) at enqueue time, so a queued task runs on the engine it was sent to rather than whatever the thread switched to while it waited.
  5. Add the two words to HELP_TEXT and to the homepage's list of words it always answers (resources/views/index.stx#L39).

Requirements

  • status says which engine is working. A thread that can switch needs to be able to say what it is on (app/Uplink/uplink.ts#L390).
  • Default stays Claude. A user who never texts codex sees no change at all.
  • An unavailable engine must say so when it is selected, not when the next text fails. If codex is texted and findCodex() found nothing, reply with how to install it.

Acceptance

  • A test texts codex, then a task, and asserts the run recorded engine = 'codex' and that no --resume or resume subcommand carried the Claude session id across the switch.
  • A test asserts a queued job keeps the engine it was enqueued with.
  • help lists both words.

Sign in to comment on this issue.