agentpal 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +2362 -2155
- package/Cargo.toml +3 -2
- package/README.md +116 -12
- package/crates/host/Cargo.toml +1 -0
- package/crates/host/src/codex.rs +85 -43
- package/crates/host/src/main.rs +2 -0
- package/package.json +1 -1
package/Cargo.toml
CHANGED
|
@@ -9,7 +9,7 @@ members = [
|
|
|
9
9
|
[workspace.package]
|
|
10
10
|
edition = "2024"
|
|
11
11
|
license = "MIT"
|
|
12
|
-
version = "0.1.
|
|
12
|
+
version = "0.1.1"
|
|
13
13
|
|
|
14
14
|
[workspace.dependencies]
|
|
15
15
|
agentpal-protocol = { path = "crates/protocol" }
|
|
@@ -21,9 +21,10 @@ local-ip-address = "0.6.13"
|
|
|
21
21
|
qrcode = { version = "0.14.1", default-features = false }
|
|
22
22
|
serde = { version = "1.0.228", features = ["derive"] }
|
|
23
23
|
serde_json = "1.0.145"
|
|
24
|
+
rustls = { version = "0.23.37", default-features = false, features = ["ring", "std", "tls12"] }
|
|
24
25
|
time = { version = "0.3.44", features = ["serde", "formatting", "macros", "parsing"] }
|
|
25
26
|
tokio = { version = "1.48.0", features = ["macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
|
26
|
-
tokio-tungstenite = "0.29.0"
|
|
27
|
+
tokio-tungstenite = { version = "0.29.0", features = ["rustls-tls-webpki-roots"] }
|
|
27
28
|
tracing = "0.1.41"
|
|
28
29
|
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
|
|
29
30
|
url = "2.5.7"
|
package/README.md
CHANGED
|
@@ -1,43 +1,147 @@
|
|
|
1
1
|
# AgentPal
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Your coding agents keep working. You keep control.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
AgentPal is a pocket control surface for coding agents running on your own machine. Pair your phone with a local Codex host, step away from the desk, and stay close enough to review progress, catch blockers, and send the next instruction.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npx agentpal@latest pair
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
AgentPal is not a remote desktop, not a phone terminal, and not another AI chat box. Your computer remains the place where code is read, edited, tested, and committed. Your phone becomes the place where you keep the work visible and steer it when attention is needed.
|
|
12
|
+
|
|
13
|
+
## Why
|
|
14
|
+
|
|
15
|
+
Long-running coding agents are useful because they can keep moving while you are not staring at the terminal. They are also useful only if you can stay in the loop:
|
|
16
|
+
|
|
17
|
+
- a command needs approval;
|
|
18
|
+
- a diff needs a quick review;
|
|
19
|
+
- a task gets blocked and needs a nudge;
|
|
20
|
+
- a session finishes while you are away;
|
|
21
|
+
- you want to send one short follow-up without reopening your laptop.
|
|
8
22
|
|
|
9
|
-
|
|
23
|
+
AgentPal is built for that gap between "the agent is running" and "I still need to be responsible for what it does."
|
|
24
|
+
|
|
25
|
+
## Start Here
|
|
26
|
+
|
|
27
|
+
Run this from the project directory on the computer where Codex is available:
|
|
10
28
|
|
|
11
29
|
```bash
|
|
12
30
|
npx agentpal@latest pair
|
|
13
31
|
```
|
|
14
32
|
|
|
15
|
-
|
|
33
|
+
The command starts the AgentPal host, creates a Cloud Relay pairing, and prints a pairing URL plus QR code for the mobile app.
|
|
34
|
+
|
|
35
|
+
You can also install the CLI globally:
|
|
16
36
|
|
|
17
37
|
```bash
|
|
18
38
|
npm install -g agentpal
|
|
19
39
|
agentpal pair
|
|
20
40
|
```
|
|
21
41
|
|
|
42
|
+
To pair a different workspace:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
agentpal pair --workspace /path/to/project
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## What Works Today
|
|
49
|
+
|
|
50
|
+
AgentPal `0.1.x` is the first public release line of the desktop/CLI side:
|
|
51
|
+
|
|
52
|
+
- public npm package: `agentpal`;
|
|
53
|
+
- one-command pairing entry: `npx agentpal@latest pair`;
|
|
54
|
+
- Cloud Relay pairing flow;
|
|
55
|
+
- local Codex host bridge;
|
|
56
|
+
- QR/link pairing payloads;
|
|
57
|
+
- workspace-aware host startup;
|
|
58
|
+
- local relay command for development and advanced setups.
|
|
59
|
+
|
|
60
|
+
The mobile app and broader agent adapters are still evolving. This release is intended to make the public pairing path installable and testable.
|
|
61
|
+
|
|
62
|
+
## How It Works
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
Phone app <-> Cloud Relay <-> AgentPal Host <-> Codex CLI
|
|
66
|
+
|
|
|
67
|
+
v
|
|
68
|
+
your workspace
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The host runs on your computer and talks to the local coding-agent process. The relay helps your phone and host find each other when they are not on the same network. Your repository stays on your machine; the phone is a control and review surface, not the execution environment.
|
|
72
|
+
|
|
22
73
|
## Commands
|
|
23
74
|
|
|
24
75
|
```bash
|
|
25
76
|
agentpal pair
|
|
26
77
|
agentpal pair --workspace .
|
|
78
|
+
agentpal pair --workspace . --relay-url ws://127.0.0.1:8790/ws
|
|
79
|
+
|
|
27
80
|
agentpal relay --host 0.0.0.0 --port 8790
|
|
28
81
|
agentpal host codex connect --workspace .
|
|
29
82
|
```
|
|
30
83
|
|
|
31
|
-
`agentpal pair` uses the current directory as the default workspace and the public Cloud Relay
|
|
84
|
+
`agentpal pair` uses the current directory as the default workspace and the public Cloud Relay unless you pass `--relay-url` or set `AGENTPAL_RELAY_URL`.
|
|
85
|
+
|
|
86
|
+
## Requirements
|
|
87
|
+
|
|
88
|
+
The current npm package is source-based. It installs a Node.js wrapper and runs the packaged Rust host/relay through `cargo run`.
|
|
89
|
+
|
|
90
|
+
You need:
|
|
91
|
+
|
|
92
|
+
- Node.js 18 or newer;
|
|
93
|
+
- Rust toolchain with `cargo`;
|
|
94
|
+
- Codex CLI available as `codex` for live host sessions.
|
|
95
|
+
|
|
96
|
+
Prebuilt binaries are planned, but they are not part of the `0.1.x` release line.
|
|
97
|
+
|
|
98
|
+
## Security Model
|
|
99
|
+
|
|
100
|
+
AgentPal is designed around local execution:
|
|
101
|
+
|
|
102
|
+
- your code stays in your workspace on your computer;
|
|
103
|
+
- the host launches and supervises the local coding agent;
|
|
104
|
+
- the relay forwards pairing and realtime messages;
|
|
105
|
+
- the phone sends control intent and receives structured session state.
|
|
106
|
+
|
|
107
|
+
Do not treat the current public relay as a private enterprise deployment boundary. For sensitive work, run your own relay or wait for hardened deployment guidance.
|
|
108
|
+
|
|
109
|
+
## Development
|
|
110
|
+
|
|
111
|
+
Run the CLI from source:
|
|
32
112
|
|
|
33
113
|
```bash
|
|
34
|
-
agentpal
|
|
114
|
+
npm run agentpal -- --help
|
|
115
|
+
npm run agentpal -- pair --workspace .
|
|
35
116
|
```
|
|
36
117
|
|
|
37
|
-
|
|
118
|
+
Run a local relay:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
cargo run -p agentpal-relay -- --host 0.0.0.0 --port 8790
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Check the Rust workspace:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
cargo fmt --check
|
|
128
|
+
cargo check --workspace
|
|
129
|
+
cargo test -p agentpal-relay
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Mobile app code lives under `apps/mobile`. Host, relay, and protocol crates live under `crates/`.
|
|
133
|
+
|
|
134
|
+
## Roadmap
|
|
135
|
+
|
|
136
|
+
Near-term work:
|
|
137
|
+
|
|
138
|
+
- mobile pairing and session UX hardening;
|
|
139
|
+
- prebuilt CLI binaries so users do not need Rust;
|
|
140
|
+
- more complete Codex session controls;
|
|
141
|
+
- Claude Code and other adapter paths;
|
|
142
|
+
- self-hosted relay deployment guide;
|
|
143
|
+
- stronger auth, device trust, and production relay hardening.
|
|
38
144
|
|
|
39
|
-
|
|
40
|
-
- Rust toolchain with `cargo`
|
|
41
|
-
- Codex CLI available as `codex` for live host sessions
|
|
145
|
+
## Status
|
|
42
146
|
|
|
43
|
-
The npm package
|
|
147
|
+
AgentPal is early. The public npm package is live, but the product surface is still being shaped. Expect sharp edges, fast iteration, and honest limits.
|
package/crates/host/Cargo.toml
CHANGED
package/crates/host/src/codex.rs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
use std::{
|
|
2
|
-
collections::{HashMap, HashSet},
|
|
3
|
-
env,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
collections::{HashMap, HashSet},
|
|
3
|
+
env,
|
|
4
|
+
io::{self, Write},
|
|
5
|
+
path::{Path, PathBuf},
|
|
6
|
+
process::Stdio,
|
|
7
|
+
sync::Arc,
|
|
7
8
|
time::{Duration, Instant},
|
|
8
9
|
};
|
|
9
10
|
|
|
@@ -15,7 +16,10 @@ use agentpal_protocol::{
|
|
|
15
16
|
RiskLevel, SessionEvent, SessionState, SessionSummary, WorkspaceSnapshot, WorktreeSummary,
|
|
16
17
|
};
|
|
17
18
|
use clap::Args;
|
|
18
|
-
use futures_util::{
|
|
19
|
+
use futures_util::{
|
|
20
|
+
SinkExt, StreamExt,
|
|
21
|
+
stream::{SplitSink, SplitStream},
|
|
22
|
+
};
|
|
19
23
|
use local_ip_address::local_ip;
|
|
20
24
|
use qrcode::{QrCode, render::unicode};
|
|
21
25
|
use serde::{Deserialize, Serialize};
|
|
@@ -396,26 +400,17 @@ async fn run_connect_loop(
|
|
|
396
400
|
codex_version: Option<String>,
|
|
397
401
|
codex_websocket_url: &str,
|
|
398
402
|
) -> anyhow::Result<()> {
|
|
399
|
-
tokio::time::sleep(Duration::from_millis(600)).await;
|
|
400
|
-
let relay_url = normalize_ws_url(&args.relay_url);
|
|
401
|
-
let (relay_socket, _) = connect_async(relay_url.as_str()).await?;
|
|
402
|
-
let (
|
|
403
|
-
let
|
|
404
|
-
let
|
|
405
|
-
let
|
|
406
|
-
let
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
let workspace_owned = workspace.to_owned();
|
|
411
|
-
let thread_ids = Arc::new(Mutex::new(HashMap::<String, String>::new()));
|
|
412
|
-
let pending_thread_starts = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
|
|
413
|
-
let pending_thread_loads = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
|
|
414
|
-
let picker_items = Arc::new(Mutex::new(default_picker_items()));
|
|
415
|
-
let seq = Arc::new(Mutex::new(0_u64));
|
|
416
|
-
|
|
417
|
-
relay_send(
|
|
418
|
-
&relay_write,
|
|
403
|
+
tokio::time::sleep(Duration::from_millis(600)).await;
|
|
404
|
+
let relay_url = normalize_ws_url(&args.relay_url);
|
|
405
|
+
let (relay_socket, _) = connect_async(relay_url.as_str()).await?;
|
|
406
|
+
let (relay_write, mut relay_read) = relay_socket.split();
|
|
407
|
+
let relay_write = Arc::new(Mutex::new(relay_write));
|
|
408
|
+
let host_id = args.host_id.clone().unwrap_or_else(default_host_id);
|
|
409
|
+
let session_id = args.session_id.clone();
|
|
410
|
+
let workspace_owned = workspace.to_owned();
|
|
411
|
+
|
|
412
|
+
relay_send(
|
|
413
|
+
&relay_write,
|
|
419
414
|
&RelayClientMessage::Register {
|
|
420
415
|
role: RelayClientRole::Host,
|
|
421
416
|
client_id: format!("{host_id}-host"),
|
|
@@ -442,11 +437,23 @@ async fn run_connect_loop(
|
|
|
442
437
|
pair_token: None,
|
|
443
438
|
expires_in_seconds,
|
|
444
439
|
},
|
|
445
|
-
},
|
|
446
|
-
)
|
|
447
|
-
.await?;
|
|
448
|
-
|
|
449
|
-
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
.await?;
|
|
443
|
+
print_pairing_response(&mut relay_read, &host_id, args.no_qr).await?;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let (codex_socket, _) = connect_async(codex_websocket_url).await?;
|
|
447
|
+
let (codex_write, codex_read) = codex_socket.split();
|
|
448
|
+
let codex_write = Arc::new(Mutex::new(codex_write));
|
|
449
|
+
let (codex_tx, mut codex_rx) = mpsc::unbounded_channel::<Value>();
|
|
450
|
+
let thread_ids = Arc::new(Mutex::new(HashMap::<String, String>::new()));
|
|
451
|
+
let pending_thread_starts = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
|
|
452
|
+
let pending_thread_loads = Arc::new(Mutex::new(HashMap::<u64, String>::new()));
|
|
453
|
+
let picker_items = Arc::new(Mutex::new(default_picker_items()));
|
|
454
|
+
let seq = Arc::new(Mutex::new(0_u64));
|
|
455
|
+
|
|
456
|
+
publish_host_status(&relay_write, &host_id, host_name, workspace, 1).await?;
|
|
450
457
|
|
|
451
458
|
send_codex_initialize(&codex_write).await?;
|
|
452
459
|
request_codex_picker_sources(&codex_write, &seq, workspace).await?;
|
|
@@ -726,12 +733,45 @@ async fn run_connect_loop(
|
|
|
726
733
|
if let Some(version) = codex_version {
|
|
727
734
|
eprintln!("Codex connected through AgentPal Host ({version})");
|
|
728
735
|
}
|
|
729
|
-
Ok(())
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
fn
|
|
733
|
-
|
|
734
|
-
|
|
736
|
+
Ok(())
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async fn print_pairing_response(
|
|
740
|
+
relay_read: &mut SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
|
741
|
+
host_id: &str,
|
|
742
|
+
no_qr: bool,
|
|
743
|
+
) -> anyhow::Result<()> {
|
|
744
|
+
while let Some(message) = relay_read.next().await {
|
|
745
|
+
let message = message?;
|
|
746
|
+
match message {
|
|
747
|
+
Message::Text(text) => {
|
|
748
|
+
let server_message: RelayServerMessage = serde_json::from_str(&text)?;
|
|
749
|
+
match server_message {
|
|
750
|
+
RelayServerMessage::PairCreated { pairing } => {
|
|
751
|
+
if pairing.host_id == host_id {
|
|
752
|
+
print_pairing_payload(&pairing, no_qr)?;
|
|
753
|
+
return Ok(());
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
RelayServerMessage::Error { message } => {
|
|
757
|
+
anyhow::bail!("AgentPal Relay error: {message}");
|
|
758
|
+
}
|
|
759
|
+
_ => {}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
Message::Close(frame) => {
|
|
763
|
+
anyhow::bail!("relay websocket closed before pair response: {frame:?}");
|
|
764
|
+
}
|
|
765
|
+
Message::Binary(_) | Message::Ping(_) | Message::Pong(_) | Message::Frame(_) => {}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
anyhow::bail!("relay websocket ended before pair response");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
fn relay_task_result(
|
|
773
|
+
result: Result<anyhow::Result<()>, tokio::task::JoinError>,
|
|
774
|
+
) -> anyhow::Result<()> {
|
|
735
775
|
match result {
|
|
736
776
|
Ok(Ok(())) => Ok(()),
|
|
737
777
|
Ok(Err(error)) => Err(error),
|
|
@@ -2327,12 +2367,14 @@ fn print_pairing_payload(payload: &PairingPayload, no_qr: bool) -> anyhow::Resul
|
|
|
2327
2367
|
.quiet_zone(true)
|
|
2328
2368
|
.module_dimensions(2, 1)
|
|
2329
2369
|
.build();
|
|
2330
|
-
println!();
|
|
2331
|
-
println!("{qr}");
|
|
2332
|
-
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2370
|
+
println!();
|
|
2371
|
+
println!("{qr}");
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
io::stdout().flush()?;
|
|
2375
|
+
|
|
2376
|
+
Ok(())
|
|
2377
|
+
}
|
|
2336
2378
|
|
|
2337
2379
|
fn pair_url(payload: &PairingPayload) -> String {
|
|
2338
2380
|
let mut url = url::Url::parse("agentpal://pair").expect("static pair url parses");
|
package/crates/host/src/main.rs
CHANGED