Skip to content

feat(pair): open the editor and launch an agent - #207

Merged
jpopesculian merged 1 commit into
mainfrom
jpop/pair
Aug 3, 2026
Merged

feat(pair): open the editor and launch an agent#207
jpopesculian merged 1 commit into
mainfrom
jpop/pair

Conversation

@jpopesculian

@jpopesculian jpopesculian commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added a pair command for connecting workspace or dataset notebooks to supported coding agents.
    • Supports Claude Code, Codex, and opencode with automatic or explicit agent selection.
    • Added options to select a notebook, suppress browser opening, or display the pairing prompt only.
    • Supports pinned, draft, and versioned workspace and dataset targets.
    • Waits for the notebook session to become ready and provides clear setup errors and timeout feedback.
    • Securely provides authentication details to the selected agent.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e3708af2-66d0-4c68-9488-69085ea470dc

📥 Commits

Reviewing files that changed from the base of the PR and between 09edc23 and 68f25ab.

📒 Files selected for processing (10)
  • src/commands/mod.rs
  • src/commands/pair/agent.rs
  • src/commands/pair/mod.rs
  • src/commands/pair/prompt.rs
  • src/commands/pair/session.rs
  • src/commands/pair/target.rs
  • src/graphql/dataset_pair_editor.graphql
  • src/graphql/dataset_version_pair_editor.graphql
  • src/graphql/workspace_pair_editor.graphql
  • src/graphql/workspace_version_pair_editor.graphql
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/graphql/workspace_version_pair_editor.graphql
  • src/graphql/dataset_version_pair_editor.graphql
  • src/commands/pair/mod.rs
  • src/graphql/dataset_pair_editor.graphql
  • src/commands/mod.rs
  • src/commands/pair/session.rs
  • src/graphql/workspace_pair_editor.graphql
  • src/commands/pair/agent.rs
  • src/commands/pair/target.rs

📝 Walkthrough

Walkthrough

Adds a pair CLI command that resolves workspace or dataset editors, prepares secure notebook access, selects Claude Code, Codex, or opencode, waits for session readiness, and launches the agent or prints the generated prompt.

Changes

Pair command

Layer / File(s) Summary
Resolve pairing targets
src/commands/pair/target.rs, src/graphql/*pair_editor.graphql
Parses workspace and dataset targets, resolves pinned or draft editors through GraphQL, and extracts runner URLs and tokens.
Select and prepare agents
src/commands/pair/agent.rs
Detects agent binaries and skills, selects an available agent, and builds agent-specific commands.
Prepare prompt and session
src/commands/pair/prompt.rs, src/commands/pair/session.rs
Writes tokens to restricted temporary files, builds pairing prompts, authenticates session requests, and polls for readiness.
Wire and run pair command
src/commands/mod.rs, src/commands/pair/mod.rs
Registers CLI options and executes target resolution, session preparation, prompt output, or agent launch. Tests cover parsing and command behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PairCommand
  participant GraphQLClient
  participant Sessions
  participant Agent
  User->>PairCommand: run pair target
  PairCommand->>GraphQLClient: resolve workspace or dataset editor
  GraphQLClient-->>PairCommand: editor URL and token
  PairCommand->>Sessions: wait for authenticated session
  Sessions-->>PairCommand: session ready
  PairCommand->>Agent: launch with pairing prompt
  Agent-->>User: pairing session
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.59% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: opening the editor and launching an agent.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jpop/pair

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/commands/pair/session.rs (1)

65-81: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

wait sleeps once more after the final failed check.

The loop checks readiness, then always sleeps, even when the deadline has passed. The command therefore waits up to TIMEOUT + POLL_INTERVAL before it reports the error. Move the sleep behind a remaining-time check.

🤖 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/commands/pair/session.rs` around lines 65 - 81, Update Session::wait so
it checks the remaining time after each failed is_ready call and only sleeps
when the deadline has not been reached, preventing an extra POLL_INTERVAL delay
before returning the existing timeout error.
🤖 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 `@src/commands/pair/mod.rs`:
- Line 99: Update the process-command construction in the pair execution flow
around agent.command(...).spawn() to enable kill-on-drop for the spawned
interactive agent, ensuring it is terminated when Cli::do_run aborts pair during
shutdown. Preserve the existing wait and error propagation behavior.
- Around line 97-100: Update the Some(agent) branch in the pair command so the
ExitStatus returned by agent.command(&prompt).spawn()?.wait().await? is checked
and a non-zero status is propagated as a command error, ensuring aqora pair
reports agent failures to scripts and CI.

In `@src/commands/pair/prompt.rs`:
- Around line 37-48: Update the token-file lifecycle in write_token and its
caller pair so the temporary directory is removed when the pairing agent exits,
while keeping the private directory and file permissions unchanged. Ensure the
returned path remains usable for the active session and cleanup occurs after
that session completes rather than leaving accumulated token files behind.

In `@src/commands/pair/session.rs`:
- Around line 30-32: Update the reqwest client construction in the session setup
around Client::builder to configure a per-request timeout shorter than
POLL_INTERVAL plus the total wait budget, while preserving the existing
invalid-certificate behavior and error propagation.

In `@src/commands/pair/target.rs`:
- Around line 154-171: Update the missing-workspace error advice in the version
lookup around workspace.version to interpolate the actual owner and workspace
values in the aqora pair command, instead of displaying literal
{owner}/{workspace} placeholders; preserve the existing guidance and error
behavior.

---

Nitpick comments:
In `@src/commands/pair/session.rs`:
- Around line 65-81: Update Session::wait so it checks the remaining time after
each failed is_ready call and only sleeps when the deadline has not been
reached, preventing an extra POLL_INTERVAL delay before returning the existing
timeout error.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b0ce4913-8791-443d-9a8e-1b99ea185566

📥 Commits

Reviewing files that changed from the base of the PR and between d7fa459 and 1d4bfea.

📒 Files selected for processing (8)
  • src/commands/mod.rs
  • src/commands/pair/agent.rs
  • src/commands/pair/mod.rs
  • src/commands/pair/prompt.rs
  • src/commands/pair/session.rs
  • src/commands/pair/target.rs
  • src/graphql/workspace_pair_editor.graphql
  • src/graphql/workspace_version_pair_editor.graphql

Comment thread src/commands/pair/mod.rs
Comment thread src/commands/pair/mod.rs
match agent {
Some(agent) => {
pb.finish_with_message(format!("Launching {}", agent.display_name()));
agent.command(&prompt).spawn()?.wait().await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the shutdown handler and any existing kill_on_drop usage.
rg -n -C 8 'fn shutdown_signal' src
rg -n 'kill_on_drop|process::Command' src

Repository: aqora-io/cli

Length of output: 1289


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files involving pair, shutdown, run commands:\n'
git ls-files | rg '(^src/commands/pair/|src/shutdown\.rs|src/commands/run\.rs|src/commands/prompt|src/main\.rs|Cargo\.toml)'

printf '\n-- src/commands/pair/mod.rs outline/contents --\n'
wc -l src/commands/pair/mod.rs
sed -n '1,160p' src/commands/pair/mod.rs

printf '\n-- src/shutdown.rs --\n'
sed -n '1,120p' src/shutdown.rs

printf '\n-- pair agent command implementation --\n'
sed -n '1,140p' src/commands/pair/agent.rs

printf '\n-- select usages --\n'
rg -n -C 3 'tokio::select!|shutdown_signal|ctrl_c|tokio::process::Command' src/commands src/main.rs app 2>/dev/null || true

printf '\n-- Cargo deps tokio signal/process --\n'
if [ -f Cargo.toml ]; then
  sed -n '1,220p' Cargo.toml
fi

Repository: aqora-io/cli

Length of output: 16606


Avoid leaving the interactive agent running after shutdown.

Cli::do_run runs pair inside tokio::select! { run, shutdown_signal() }. When Ctrl-C selects the shutdown branch, pair is aborted and dropped, but tokio::process::Command::spawn() does not kill the agent on drop unless kill_on_drop(true) is set. Use kill_on_drop(true) or omit the shutdown race from pair so the client cannot exit while the agent still owns the terminal.

🤖 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/commands/pair/mod.rs` at line 99, Update the process-command construction
in the pair execution flow around agent.command(...).spawn() to enable
kill-on-drop for the spawned interactive agent, ensuring it is terminated when
Cli::do_run aborts pair during shutdown. Preserve the existing wait and error
propagation behavior.

Comment thread src/commands/pair/prompt.rs Outdated
Comment thread src/commands/pair/session.rs
Comment thread src/commands/pair/target.rs
@jpopesculian
jpopesculian force-pushed the jpop/pair branch 3 times, most recently from 6b51754 to 09edc23 Compare August 3, 2026 12:08
@cimandef

cimandef commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What do you think about putting that behind a flag? That way, if I want, I can compile the CLI without the agent-related features (as a default=true option). I think this would be useful for people who don't want to work with AI

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/commands/pair/mod.rs (1)

17-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Gate the agent-related CLI surface behind a compile-time feature, per PR discussion.

The claude, codex, and opencode flags, along with the agent module dependency, are always compiled into the pair command. A reviewer on this PR requested that agent-related features sit behind a compile-time flag, enabled by default, so the CLI can build without them for users who do not want AI integration. This file is the wiring point where such a #[cfg(feature = "agent")] gate (or similar) would need to apply to the claude/codex/opencode args, the agent() method, and the mod agent; import.

🤖 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/commands/pair/mod.rs` around lines 17 - 45, Gate the agent integration
behind the default-enabled "agent" compile-time feature: conditionally compile
the claude, codex, and opencode fields in Pair, the Pair::agent() method, and
the mod agent; declaration using the same feature. Ensure the pair command still
builds and exposes its non-agent behavior when the feature is disabled,
including updating prompt_only’s agent conflict configuration as needed.
🤖 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 `@src/commands/pair/prompt.rs`:
- Around line 11-31: Update build_prompt to POSIX-shell-quote both
editor.base_url and token_path before inserting them into the generated
execute-code.sh commands, including the URL’s single-quoted argument. Ensure
embedded quotes and shell metacharacters cannot alter command parsing, and use
cat -- when reading the quoted token path.

In `@src/commands/pair/target.rs`:
- Around line 288-296: Update the pinned dataset resolution flow around
DatasetVersionPairEditor to preserve semver prerelease/build metadata when the
schema supports it; otherwise reject targets whose version contains pre or build
with a user-facing error. Replace direct major/minor/patch as i64 casts with
i64::try_from, propagating a user error for out-of-range components instead of
allowing wrapping.

In `@src/graphql/workspace_pair_editor.graphql`:
- Line 4: The draft queries rely on unspecified version ordering and may select
the wrong draft. Update src/graphql/workspace_pair_editor.graphql:4-4 and
src/graphql/dataset_pair_editor.graphql:4-4 to request newest-first ordering; if
the schema cannot order these connections, update resolve_draft at
src/commands/pair/target.rs:232-239 and resolve_dataset_draft at
src/commands/pair/target.rs:346-351 to select the returned node with the highest
version instead of the first.

---

Nitpick comments:
In `@src/commands/pair/mod.rs`:
- Around line 17-45: Gate the agent integration behind the default-enabled
"agent" compile-time feature: conditionally compile the claude, codex, and
opencode fields in Pair, the Pair::agent() method, and the mod agent;
declaration using the same feature. Ensure the pair command still builds and
exposes its non-agent behavior when the feature is disabled, including updating
prompt_only’s agent conflict configuration as needed.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f5a70573-6cae-45a4-b5f1-1f5a223611e9

📥 Commits

Reviewing files that changed from the base of the PR and between 1d4bfea and 09edc23.

📒 Files selected for processing (10)
  • src/commands/mod.rs
  • src/commands/pair/agent.rs
  • src/commands/pair/mod.rs
  • src/commands/pair/prompt.rs
  • src/commands/pair/session.rs
  • src/commands/pair/target.rs
  • src/graphql/dataset_pair_editor.graphql
  • src/graphql/dataset_version_pair_editor.graphql
  • src/graphql/workspace_pair_editor.graphql
  • src/graphql/workspace_version_pair_editor.graphql
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/graphql/workspace_version_pair_editor.graphql
  • src/commands/mod.rs
  • src/commands/pair/session.rs
  • src/commands/pair/agent.rs

Comment on lines +11 to +31
pub fn build_prompt(editor: &PairEditor, token_path: &Path, editor_page: &Url) -> String {
format!(
"Use the /marimo-pair skill to pair-program on a running marimo notebook.

Connect to the notebook at: {base_url}

Use `execute-code.sh --url {base_url}` from the marimo-pair skill to execute code in the \
notebook.

An auth token is stored at {token_path}. Pass it via `execute-code.sh --url '{base_url}' \
--token \"$(cat '{token_path}')\"`.

The notebook must be open in a browser for a session to exist. If the server reports no \
active sessions, ask the user to open {editor_page} and then try again.

Once you are connected, send a fun toast (mo.status.toast(...)) to the user inside marimo \
letting them know you're ready to pair.",
base_url = editor.base_url,
token_path = token_path.display(),
editor_page = editor_page,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Escape values before inserting them into shell syntax.

Line 17 inserts editor.base_url into a single-quoted shell argument without escaping embedded single quotes. A URL path can contain ' and command-substitution characters. This closes the argument quote. An agent that follows the generated command can execute injected shell code.

Use POSIX-safe quoting for both the URL and token path. Use cat -- for the token path.

Proposed fix
+fn shell_quote(value: &str) -> String {
+    format!("'{}'", value.replace('\'', "'\"'\"'"))
+}
+
 pub fn build_prompt(editor: &PairEditor, token_path: &Path, editor_page: &Url) -> String {
+    let base_url = editor.base_url.as_str();
+    let token_path = token_path.display().to_string();
+    let shell_base_url = shell_quote(base_url);
+    let shell_token_path = shell_quote(&token_path);
+
     format!(
         "Use the /marimo-pair skill to pair-program on a running marimo notebook.
 ...
-Use `execute-code.sh --url {base_url}` from the marimo-pair skill to execute code in the \
+Use `execute-code.sh --url {shell_base_url}` from the marimo-pair skill to execute code in the \
 notebook.
 ...
-An auth token is stored at {token_path}. Pass it via `execute-code.sh --url '{base_url}' \
---token \"$(cat '{token_path}')\"`.
+An auth token is stored at {token_path}. Pass it via `execute-code.sh --url {shell_base_url} \
+--token \"$(cat -- {shell_token_path})\"`.
 ...
-        base_url = editor.base_url,
-        token_path = token_path.display(),
+        base_url,
+        token_path,
+        shell_base_url,
+        shell_token_path,
         editor_page = editor_page,
     )
 }
🤖 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/commands/pair/prompt.rs` around lines 11 - 31, Update build_prompt to
POSIX-shell-quote both editor.base_url and token_path before inserting them into
the generated execute-code.sh commands, including the URL’s single-quoted
argument. Ensure embedded quotes and shell metacharacters cannot alter command
parsing, and use cat -- when reading the quoted token path.

Comment thread src/commands/pair/target.rs
query WorkspacePairEditor($owner: String!, $slug: String!, $notebook: String) {
workspaceBySlug(owner: $owner, slug: $slug) {
id
versions(first: 1, filters: { published: false }) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Draft selection depends on an unstated server ordering. Both draft queries request versions(first: 1, filters: { published: false }) without an order argument, and both Rust resolvers treat the single returned node as the newest draft. If the server default order is not newest-first, pairing opens the wrong draft version.

  • src/graphql/workspace_pair_editor.graphql#L4-L4: add an explicit newest-first order argument to the versions connection, or raise first so the caller can choose.
  • src/graphql/dataset_pair_editor.graphql#L4-L4: apply the same explicit ordering to the dataset versions connection.
  • src/commands/pair/target.rs#L232-L239: if the schema cannot order the connection, select the highest version from the returned nodes in resolve_draft instead of taking the first node.
  • src/commands/pair/target.rs#L346-L351: apply the same selection rule in resolve_dataset_draft.
📍 Affects 3 files
  • src/graphql/workspace_pair_editor.graphql#L4-L4 (this comment)
  • src/graphql/dataset_pair_editor.graphql#L4-L4
  • src/commands/pair/target.rs#L232-L239
  • src/commands/pair/target.rs#L346-L351
🤖 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/graphql/workspace_pair_editor.graphql` at line 4, The draft queries rely
on unspecified version ordering and may select the wrong draft. Update
src/graphql/workspace_pair_editor.graphql:4-4 and
src/graphql/dataset_pair_editor.graphql:4-4 to request newest-first ordering; if
the schema cannot order these connections, update resolve_draft at
src/commands/pair/target.rs:232-239 and resolve_dataset_draft at
src/commands/pair/target.rs:346-351 to select the returned node with the highest
version instead of the first.

Copy link
Copy Markdown
Contributor Author

I mean this is a fairly light feature (no extra deps, a few lines of code) so putting it behind a flag seems excessive. If you don't want to use it, you don't have to but I don't see the reasoning behind feature gating it

cimandef commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

I was just being very conservative with the Agents feature, but you're right it's really lightweight, so it's fine!!

@jpopesculian
jpopesculian merged commit 24e0217 into main Aug 3, 2026
17 checks passed
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