Skip to content

feat(cli): agent-relay drive <name> interactive take-over client (#864 sub-3)#887

Merged
willwashburn merged 5 commits into
mainfrom
feat/864-drive-client
May 18, 2026
Merged

feat(cli): agent-relay drive <name> interactive take-over client (#864 sub-3)#887
willwashburn merged 5 commits into
mainfrom
feat/864-drive-client

Conversation

@willwashburn
Copy link
Copy Markdown
Member

Summary

Adds the read-write attach verb from the #864 verb taxonomy. agent-relay drive <name> attaches to a running agent, flips it into human session mode so the broker parks new inbound relay messages in a per-worker FIFO queue, forwards your keystrokes to the worker's PTY, lets you drain the queue on demand, and detaches cleanly without killing the agent. Built on top of the four session-mode routes added in #884 and the snapshot-on-attach helper from #880.

Sequence on attach

  1. Discover the broker connection (--broker-urlRELAY_BROKER_URL.agent-relay/connection.json).
  2. GET /api/spawned/{name}/mode — remember the previous mode so we can restore on detach.
  3. PUT /api/spawned/{name}/mode with {"mode":"human"} — start queueing inbound relay messages.
  4. captureAndRenderSnapshot — repaint the agent's current visible screen so the user isn't staring at a blank terminal.
  5. GET /api/spawned/{name}/pending — seed the status-line counter.
  6. Open /ws, subscribe to worker_stream / delivery_queued / agent_pending_drained events for this worker.
  7. Switch local stdin to raw mode; forward bytes to POST /api/input/{name}.

On clean or abnormal detach, best-effort PUT /api/spawned/{name}/mode restores the worker's prior session mode so the queue doesn't fill up indefinitely. Raw mode is always restored in try/finally style via a teardownStdin helper.

Keybinds

Keys Action
Ctrl+G Flush the pending queue (POST /api/spawned/{name}/flush).
Ctrl+B then D (or d or Ctrl+D) Detach cleanly. Restores prior session mode, closes WS, exits 0.
Ctrl+B then ? Toggle a help hint in the status line.
Ctrl+C Safety alias for detach — drive never kills the agent.
Ctrl+B then any other byte Forwarded to the agent unchanged so TUI apps using Ctrl+B aren't deprived.

Status line is painted on the bottom row using ANSI save/restore-cursor escapes so the agent's output isn't disturbed:

[drive Alice | mode=human | pending=3 | Ctrl+G flush | Ctrl+B D detach]

What's in this PR

  • src/cli/commands/drive.ts — new command. Includes the session runner, HTTP helpers, KeybindParser state machine, renderStatusLine helper, and classifyWsEvent filter — all exported and unit-tested in isolation.
  • src/cli/commands/drive.test.ts — 33 tests covering: classify filter, keybind parser (single-byte + Ctrl+B prefix + pass-through), status-line ANSI, happy-path attach + detach, mode-flip failure, snapshot not_found / no_pty / unavailable, pending-count tracking, keystroke forwarding, flush keybind, abnormal-close mode restore, re-attach-when-already-human, SIGINT handling, and missing-broker.
  • src/cli/lib/broker-connection.ts — new. Factors the --broker-url → env → connection.json discovery chain that view was carrying inline. drive reuses it; future relay/new/run verbs can too.
  • src/cli/bootstrap.ts — register drive.
  • src/cli/bootstrap.test.ts — add 'drive' to expectedLeafCommands (per the regression feat(cli): agent-relay view <name> read-only stream client (#864 sub-1) #880 hit on first try).
  • web/content/docs/reference-cli.mdx — new ### drive section with flag table, keybinds table, status line example, and worked example.

What's out (deliberately)

Test plan

  • npx vitest run src/cli/commands/drive.test.ts src/cli/commands/view.test.ts src/cli/lib/attach.test.ts72 passed.
  • npx vitest run src/cli/bootstrap.test.ts3 passed (the new drive leaf is in the hardcoded list).
  • npx tsc --noEmit clean for touched scope.
  • npm run lint — 0 errors. One complexity warning (19) on runDriveSession, consistent with the patterns in attach.ts (18) and lots of other CLI commands (20–68).
  • Manual smoke test: spawn a TUI agent, agent-relay drive <name>, type into it, send relay messages from another window, watch pending count go up, hit Ctrl+G, watch it drain, hit Ctrl+B D, confirm the agent's session mode is back to relay.

Part of #864.

🤖 Generated with Claude Code

 sub-3)

Adds the read-write attach verb from issue #864's verb taxonomy. `drive`
flips the worker into `human` session mode so the broker parks new
inbound relay messages in a per-worker FIFO queue, captures the agent's
current screen, opens the WS event stream, and forwards your keystrokes
to the worker's PTY via `POST /api/input/{name}`. Keybinds: Ctrl+G to
flush the queue, Ctrl+B D (or Ctrl+C) to detach. On any exit path the
client best-effort restores the worker's prior session mode so the queue
doesn't fill up indefinitely.

Factors broker-connection discovery (flag → env → connection.json) into
`src/cli/lib/broker-connection.ts` so `drive`, `view`, and future
`relay`/`new`/`run` verbs share a single resolver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwashburn willwashburn requested a review from khaliqgant as a code owner May 18, 2026 04:02
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b4ff68b-658d-4b0d-bad4-c8fbb1e30857

📥 Commits

Reviewing files that changed from the base of the PR and between 0aeb71c and 209e5d4.

📒 Files selected for processing (2)
  • .trajectories/completed/2026-05/traj_ryf5sstno6p3.json
  • .trajectories/index.json

📝 Walkthrough

Walkthrough

Adds an interactive CLI verb agent-relay drive <name> with broker-connection discovery, session-mode HTTP helpers, PTY resize forwarding, WebSocket streaming and event classification, keybind parsing, ANSI status-line rendering, comprehensive Vitest tests, CLI bootstrap wiring, docs, and trajectory metadata updates.

Changes

Drive CLI Command

Layer / File(s) Summary
Broker connection discovery
src/cli/lib/broker-connection.ts, src/cli/lib/broker-connection.test.ts
New shared module for resolving broker URL and API key from CLI flags, environment, or <stateDir>/connection.json, plus defaultStateDir() and toWsUrl() utilities and tests.
Drive command implementation
src/cli/commands/drive.ts
Dependency types and defaults, HTTP helpers (getSessionMode, setSessionMode, getPendingCount, flushPending, sendInput, resizeWorker), WS event classifier, KeybindParser, renderStatusLine, and runDriveSession orchestration with PTY resize sync and cleanup; CLI registration via registerDriveCommands.
Drive command test suite
src/cli/commands/drive.test.ts
Full Vitest harness and tests: unit tests for classification/keybinds/status, E2E session tests (mode flip/restore, snapshot handling, pending/flush, stdin forwarding, SIGINT/re-attach/abnormal close), resize-forwarding tests, and CLI registration checks.
CLI bootstrap wiring
src/cli/bootstrap.ts, src/cli/bootstrap.test.ts
Imports/registers drive commands in createProgram() and updates bootstrap tests to expect the drive leaf command.
User documentation
web/content/docs/reference-cli.mdx
Documents drive attach/detach flow, flags, keybindings, status-line behavior, PTY resize behavior, and example session.
Trajectory metadata
.trajectories/active/*.json, .trajectories/index.json, .trajectories/completed/*
Populates an active trajectory JSON (agents/chapters/events), updates index timestamp/path, and normalizes a completed trajectory projectId placeholder.

Sequence Diagram

sequenceDiagram
  participant User
  participant CLI as drive command
  participant Broker as Broker HTTP API
  participant Agent as Agent WebSocket

  User->>CLI: agent-relay drive <name>
  CLI->>Broker: GET /api/spawned/{name}/mode
  CLI->>Broker: PUT /api/spawned/{name}/mode = human
  CLI->>Broker: GET /api/spawned/{name}/snapshot
  CLI->>Broker: GET /api/spawned/{name}/pending
  CLI->>Agent: WebSocket /ws
  Agent-->>CLI: stream chunks/events
  CLI-->>User: render + status bar
  User->>CLI: keystrokes (raw)
  CLI->>Broker: POST /api/input/{name}
  CLI->>Broker: POST /api/spawned/{name}/flush (Ctrl+G)
  CLI->>Broker: POST /api/resize/{name} (on attach/resize)
  User->>CLI: Ctrl+C (detach)
  CLI->>Broker: PUT /api/spawned/{name}/mode = prior
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • khaliqgant

Poem

🐰 I hopped to the keyboard, eager to guide,
Switched to human, watched the stream collide,
Ctrl+G flushed the queue, Ctrl+C set me free,
A tiny rabbit driver, steering gently.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a new interactive drive command for agent takeover. It is specific, follows the feat(scope) convention, and accurately reflects the primary purpose of the changeset.
Description check ✅ Passed The PR description comprehensively covers the summary and rationale, includes detailed test plan results showing tests passed, covers the implementation scope, and explicitly marks one manual test as pending rather than omitted.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/864-drive-client

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.trajectories/active/traj_v87cyrs8dke9.json:
- Line 26: Locate the JSON objects with the "content" field that contain a
duplicated sentence separated by a colon (e.g., "Factored broker-connection
discovery into src/cli/lib/broker-connection.ts: Factored broker-connection
discovery into src/cli/lib/broker-connection.ts") and replace the value with the
single non-duplicated sentence (remove the trailing ": <same text>" repetition).
Apply the same fix to the other occurrence noted (the second "content" entry at
the other occurrence) so each "content" string reads once without the "A: A"
duplication.

In @.trajectories/index.json:
- Line 417: The entry uses a machine-specific absolute path value for the JSON
"path" key ("/Users/will/.../traj_v87cyrs8dke9.json"); replace it with a
repo-relative path (e.g. ".trajectories/active/traj_v87cyrs8dke9.json") in
.trajectories/index.json so it matches the other entries. Locate the JSON object
containing the "path" key referencing traj_v87cyrs8dke9.json, edit the value to
the relative path, then stage and recommit the change (or amend the commit) so
no local filesystem/user info remains in the repo.

In `@web/content/docs/reference-cli.mdx`:
- Line 107: Update the detach key documentation entry to list all supported
variants: replace the current "`Ctrl+B` then `D` | Detach cleanly..." line with
a description that includes "D (or d or Ctrl+D)" so it reads something like
"`Ctrl+B` then `D` (or `d` or `Ctrl+D`) | Detach cleanly. Restores the worker's
prior session mode, closes the WebSocket, exits 0." Ensure the table row in
web/content/docs/reference-cli.mdx reflecting the detach behavior is updated
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 09151df4-bb12-406f-b5fd-4e03d19c05f9

📥 Commits

Reviewing files that changed from the base of the PR and between 09240e3 and b73642e.

📒 Files selected for processing (8)
  • .trajectories/active/traj_v87cyrs8dke9.json
  • .trajectories/index.json
  • src/cli/bootstrap.test.ts
  • src/cli/bootstrap.ts
  • src/cli/commands/drive.test.ts
  • src/cli/commands/drive.ts
  • src/cli/lib/broker-connection.ts
  • web/content/docs/reference-cli.mdx

Comment thread .trajectories/active/traj_v87cyrs8dke9.json Outdated
Comment thread .trajectories/index.json Outdated
Comment thread web/content/docs/reference-cli.mdx Outdated
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment thread src/cli/commands/drive.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b73642ef0c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/cli/commands/drive.ts Outdated
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/cli/lib/broker-connection.ts">

<violation number="1" location="src/cli/lib/broker-connection.ts:81">
P2: Using `??` here treats blank trimmed values as valid and prevents fallback to lower-priority URL sources.</violation>

<violation number="2" location="src/cli/lib/broker-connection.ts:87">
P2: `??` allows empty trimmed API key values to override valid fallback keys from lower-priority sources.</violation>
</file>

<file name="src/cli/commands/drive.ts">

<violation number="1" location="src/cli/commands/drive.ts:537">
P2: `stdin` bytes are encoded with `'binary'` before `/api/input`, which can corrupt non-ASCII keystrokes. Use UTF-8 decoding so terminal input round-trips correctly through the JSON API.</violation>
</file>

<file name=".trajectories/index.json">

<violation number="1" location=".trajectories/index.json:417">
P2: Store trajectory paths as repository-relative paths instead of absolute local paths to avoid leaking machine-specific filesystem details and breaking portability.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Fix all with cubic | Re-trigger cubic

Comment thread src/cli/lib/broker-connection.ts
Comment thread src/cli/lib/broker-connection.ts
Comment thread src/cli/commands/drive.ts Outdated
Comment thread .trajectories/index.json Outdated
Reviewer pushback on the original sub-PR 3 punt: terminal resize
forwarding isn't a nice-to-have for a take-over UX, it's table
stakes. Every comparable tool (tmux, screen, ssh, mosh) forwards
resizes; without it, a TUI in the agent renders into whatever
24×80 box the PTY was spawned with and ignores the human's actual
viewport. Bad enough that the previous "out of scope, follow-up
issue" answer doesn't hold up.

Wires resize forwarding into the existing drive session lifecycle:

  - New `DriveTerminal` dep: `getSize()` reads local rows/cols
    from `process.stdout` (or `null` when stdout isn't a TTY);
    `onResize(handler)` translates Node's automatic
    `process.stdout` `'resize'` event (which Node fires on
    SIGWINCH for TTY stdouts) into a subscription with an
    unsubscribe handle.
  - New `resizeWorker()` HTTP helper: `POST /api/resize/{name}`
    body `{rows, cols}`. Matches the shape of `sendInput` /
    `setSessionMode` so it slots into the existing pattern.
  - On attach: after snapshot + before WS open, sync the agent's
    PTY to the local terminal. Best-effort — a failure logs but
    doesn't abort (the human can still type, output just renders
    at the old size). Skipped when stdout isn't a TTY.
  - On the same `socket.on('open')` callback that takes over
    stdin: subscribe to local resize events. Forwards every
    change to the broker and repaints the status line at the new
    bottom-row index.
  - On teardown (clean detach, signal, abnormal close): unsub
    the resize handler alongside the stdin handler. Same
    lifecycle = same teardown path.
  - Status-line row source switched from `snapshot.rows` (the
    agent's PTY rows at snapshot time) to the local terminal's
    current rows. Falls back to snapshot value if local size is
    unavailable, then to the renderer's 24-row default. Without
    this, the status line would land in the wrong place after
    the first resize.

Tests (5 new, 38 total in drive.test.ts):

  - Initial-attach size forwarded with correct body
  - Subsequent SIGWINCH events forward every change in order
  - Detach unsubscribes the resize listener (`listenerCount() == 0`)
  - Non-TTY stdout skips resize forwarding entirely (0 calls)
  - Initial resize failure logs a warning but lets the session continue

New `FakeTerminal` helper in the test file models the DI surface
the same way `FakeStdin` / `FakeWebSocket` do — fully under test
control, never touches a real TTY.

Docs (`web/content/docs/reference-cli.mdx`) updated: added a 7th
step to the on-attach sequence describing the resize sync, and
mentions SIGWINCH forwarding alongside it.

Touched-suite test status: 80 passing across drive / view / attach
/ bootstrap (was 75 before; +5 new resize tests).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/cli/commands/drive.ts (1)

756-758: 💤 Low value

WebSocket message data handling may not correctly convert ArrayBuffer or Buffer[].

The WebSocket.RawData type includes ArrayBuffer | Buffer[] in addition to Buffer. For these types, String(data) produces "[object ArrayBuffer]" or similar, which fails JSON parsing and returns { kind: 'other' }. This is safe but could log confusing "other" events if the broker ever sends non-Buffer data.

If this matters for future compatibility:

Proposed fix for broader message type support
 socket.on('message', (data) => {
-  const text =
-    typeof data === 'string' ? data : Buffer.isBuffer(data) ? data.toString('utf-8') : String(data);
+  let text: string;
+  if (typeof data === 'string') {
+    text = data;
+  } else if (Buffer.isBuffer(data)) {
+    text = data.toString('utf-8');
+  } else if (Array.isArray(data)) {
+    text = Buffer.concat(data).toString('utf-8');
+  } else if (data instanceof ArrayBuffer) {
+    text = Buffer.from(data).toString('utf-8');
+  } else {
+    text = String(data);
+  }
   const event = classifyWsEvent(text, agentName);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cli/commands/drive.ts` around lines 756 - 758, The WebSocket message
handler in the socket.on('message', ...) callback currently only handles string
and Buffer, causing ArrayBuffer or Buffer[] (WebSocket.RawData) to stringify as
"[object ArrayBuffer]" and break JSON parsing; update the conversion for the
message payload (the local variable text) to detect ArrayBuffer (convert via new
Uint8Array(data) then text decoder or Buffer.from) and Buffer[] (concat into a
single Buffer) so all RawData variants produce a proper UTF-8 string before
downstream JSON.parse or event handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/cli/commands/drive.ts`:
- Around line 756-758: The WebSocket message handler in the socket.on('message',
...) callback currently only handles string and Buffer, causing ArrayBuffer or
Buffer[] (WebSocket.RawData) to stringify as "[object ArrayBuffer]" and break
JSON parsing; update the conversion for the message payload (the local variable
text) to detect ArrayBuffer (convert via new Uint8Array(data) then text decoder
or Buffer.from) and Buffer[] (concat into a single Buffer) so all RawData
variants produce a proper UTF-8 string before downstream JSON.parse or event
handling.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b58bcb0-639b-4374-95c5-26a40adac722

📥 Commits

Reviewing files that changed from the base of the PR and between b73642e and 47a77c8.

📒 Files selected for processing (3)
  • src/cli/commands/drive.test.ts
  • src/cli/commands/drive.ts
  • web/content/docs/reference-cli.mdx
✅ Files skipped from review due to trivial changes (1)
  • web/content/docs/reference-cli.mdx

Review feedback on PR #887:

(1) UTF-8 stdin encoding (devin / codex / cubic P2)

`outcome.forward.toString('binary')` encoded raw stdin bytes as
latin1 before JSON transport. The broker deserializes `data` as a
Rust UTF-8 String, so bytes ≥ 0x80 got re-encoded — a typed `é`
(0xC3 0xA9) arrived at the agent as `é` (4 bytes). Switching to
`'utf-8'` matches Node's default and round-trips correctly.

(2) Empty-trim falls through in broker-connection (cubic P2 ×2)

`?.trim() ?? envValue` is wrong: `''.trim()` is `''`, not nullish,
so a blank `--broker-url` flag would silently override a real
`RELAY_BROKER_URL`. New `trimOrUndefined()` helper filters empty
results to undefined so `??` chains correctly fall through to
lower-priority sources. Four regression tests in the new
`broker-connection.test.ts` lock the behaviour for both url and
api-key, across both override layers.

(3) Trajectory absolute paths leak local user (coderabbit Major / cubic P2)

`.trajectories/index.json:417` stored
`/Users/will/Projects/AgentWorkforce/relay/.claude/worktrees/...`
— the trail tool didn't normalize to repo-relative when it ran in
a worktree. Replaced with the repo-relative form
`.trajectories/active/traj_v87cyrs8dke9.json` matching every
other entry.

(4) Duplicated decision content (coderabbit Minor)

`.trajectories/active/traj_v87cyrs8dke9.json` had two entries
shaped like `"X: X"` (formatter bug — duplicated the headline
around a colon). De-duped both.

(5) Docs underdocumented detach (coderabbit Minor)

The `KeybindParser` accepts `D`, `d`, or `Ctrl+D` after the
`Ctrl+B` prefix. Docs only listed `D`. Updated the keybinds row.

Touched-suite test status: 87 passing across drive / view /
attach / bootstrap / broker-connection (was 80; +7 from the new
broker-connection test file).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwashburn
Copy link
Copy Markdown
Member Author

Pushed 0aeb71cb addressing every inline finding. Recap by reviewer:

@devin-ai-integration (🟡) / @chatgpt-codex-connector (P2) / @cubic-dev-ai (P2)outcome.forward.toString('binary') corrupts non-ASCII keystrokes (latin1 → JSON → broker UTF-8 String → double-encoded on the agent's PTY).

  • Fixed in src/cli/commands/drive.ts:653. Switched to 'utf-8' to match Node's default and the broker's String deserialization. Inline comment explains the double-encoding pitfall.

@cubic-dev-ai (P2 ×2)?.trim() ?? envValue is wrong: ''.trim() is '' which is not nullish, so a blank --broker-url / --api-key flag would silently override a real env var or connection.json value.

  • Fixed in src/cli/lib/broker-connection.ts. New trimOrUndefined() helper filters empty results so ?? chains correctly fall through. Four regression tests in the new src/cli/lib/broker-connection.test.ts lock the behaviour for both url and apiKey, across both override layers.

@coderabbitai (🟠 Major) / @cubic-dev-ai (P2).trajectories/index.json:417 stored an absolute local path leaking /Users/will/... filesystem info.

  • Fixed. Normalized to repo-relative .trajectories/active/traj_v87cyrs8dke9.json matching every other entry. (Trail tool didn't normalize when it ran in a worktree.)

@coderabbitai (🟡 Minor).trajectories/active/traj_v87cyrs8dke9.json had two decision entries shaped like "X: X" (formatter bug duplicating the headline around a colon).

  • Fixed. De-duped both content strings.

@coderabbitai (🟡 Minor) — Docs only listed D for detach; the KeybindParser accepts D / d / Ctrl+D after the Ctrl+B prefix.

  • Fixed in web/content/docs/reference-cli.mdx. Keybinds row now shows all three variants.

Test status: 87 passing across drive / view / attach / bootstrap / the new broker-connection (was 80; +7 — 4 trim-fallthrough regression tests + 3 reorganization). cargo-side untouched in this commit.

All 8 inline review threads marked as resolved on the GitHub UI.

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 18, 2026

@willwashburn — clean sweep, every finding addressed correctly. A few highlights worth calling out:

  • trimOrUndefined() helper is the right approach — the ?.trim() ?? fallback trap is subtle and easy to re-introduce; having the helper name make the intent explicit plus the four regression tests locking the priority chain is exactly the right fix.
  • toString('utf-8') with the inline comment explaining the double-encoding pitfall is the kind of defensive documentation that saves the next person from re-introducing the bug.
  • Absolute path normalization — good to have that cleaned up; the worktree tool not normalizing paths is a useful thing to know.

Test count at 87 with the new regression coverage looks solid. Happy to approve.

(´• ω •`) 🐇✨


✏️ Learnings added
Learnt from: willwashburn
URL: https://github.com/AgentWorkforce/relay/pull/887

Timestamp: 2026-05-18T05:23:54.723Z
Learning: In `src/cli/lib/broker-connection.ts`, empty string inputs from CLI flags (`--broker-url`, `--api-key`) must be treated as absent using a `trimOrUndefined()` helper before `??` chaining. `''.trim()` returns `''` which is not nullish, so a blank flag would silently override env vars or `connection.json` values without the helper.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Actions performed

Comments resolved. Approval is disabled; enable reviews.request_changes_workflow to allow explicit top-level @coderabbitai resolve or @coderabbitai approve commands.

@willwashburn
Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Copilot AI and others added 2 commits May 18, 2026 05:30
Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Co-authored-by: willwashburn <957608+willwashburn@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 18, 2026

@copilot resolve the merge conflicts in this pull request

Resolved. I merged origin/main into this branch, fixed the conflict in .trajectories/index.json, and pushed the conflict-resolution commit efcb1ed2 (plus follow-up metadata normalization in 209e5d4b).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants