agent-message 0.1.3 → 0.1.4
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/README.md +97 -22
- package/codex-message/Cargo.lock +1867 -0
- package/codex-message/Cargo.toml +17 -0
- package/codex-message/README.md +29 -0
- package/codex-message/src/agent_message.rs +205 -0
- package/codex-message/src/app.rs +857 -0
- package/codex-message/src/codex.rs +278 -0
- package/codex-message/src/main.rs +101 -0
- package/codex-message/src/render.rs +125 -0
- package/npm/bin/agent-message.mjs +245 -13
- package/npm/bin/codex-message.mjs +41 -0
- package/npm/runtime/agent_gateway.mjs +5 -2
- package/npm/runtime/bin/agent-message-cli-darwin-amd64 +0 -0
- package/npm/runtime/bin/agent-message-cli-darwin-arm64 +0 -0
- package/npm/runtime/bin/agent-message-server-darwin-amd64 +0 -0
- package/npm/runtime/bin/agent-message-server-darwin-arm64 +0 -0
- package/npm/runtime/web-dist/assets/index-AfnEJAni.css +1 -0
- package/npm/runtime/web-dist/assets/index-QUUfdfoN.js +182 -0
- package/npm/runtime/web-dist/index.html +2 -2
- package/npm/runtime/web-dist/sw.js +2 -1
- package/package.json +4 -2
- package/npm/runtime/web-dist/assets/index-4VmoBZF3.js +0 -182
- package/npm/runtime/web-dist/assets/index-D_RPU5JN.css +0 -1
- package/npm/runtime/web-dist/workbox-8c29f6e4.js +0 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "codex-message"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
edition = "2024"
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
anyhow = "1.0.100"
|
|
8
|
+
clap = { version = "4.5.53", features = ["derive", "env"] }
|
|
9
|
+
futures-util = "0.3.31"
|
|
10
|
+
rand = "0.9.2"
|
|
11
|
+
reqwest = { version = "0.12.24", default-features = false, features = ["json", "rustls-tls", "stream"] }
|
|
12
|
+
serde = { version = "1.0.228", features = ["derive"] }
|
|
13
|
+
serde_json = "1.0.145"
|
|
14
|
+
tokio = { version = "1.48.0", features = ["full"] }
|
|
15
|
+
tokio-util = { version = "0.7.17", features = ["io"] }
|
|
16
|
+
url = { version = "2.5.7", features = ["serde"] }
|
|
17
|
+
uuid = { version = "1.18.1", features = ["v4"] }
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# codex-message
|
|
2
|
+
|
|
3
|
+
`codex-message` is a separate Rust wrapper around `codex app-server` that uses
|
|
4
|
+
the `agent-message` binary as its transport layer and reuses the default
|
|
5
|
+
`agent-message` config/profile store.
|
|
6
|
+
|
|
7
|
+
Behavior:
|
|
8
|
+
|
|
9
|
+
1. Starts a fresh `agent-{chatId}` account with a random numeric PIN.
|
|
10
|
+
2. Sends the `--to` user a startup message with the generated credentials.
|
|
11
|
+
3. Reuses one Codex app-server thread for the DM session.
|
|
12
|
+
4. Polls `agent-message read <user>` for new plain-text requests and relays them into `turn/start`.
|
|
13
|
+
5. For approval and input requests, sends readable `json_render` prompts back to Jay and waits for a text reply.
|
|
14
|
+
6. Sends final Codex results back as `json_render` reports.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd codex-message
|
|
20
|
+
cargo run -- --to jay --model gpt-5.4
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Useful flags:
|
|
24
|
+
|
|
25
|
+
- `--to jay`
|
|
26
|
+
- `--cwd /path/to/worktree`
|
|
27
|
+
- `--approval-policy on-request`
|
|
28
|
+
- `--sandbox workspace-write`
|
|
29
|
+
- `--network-access`
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
use std::process::Stdio;
|
|
3
|
+
use std::time::Duration;
|
|
4
|
+
|
|
5
|
+
use anyhow::{Context, Result, anyhow, bail};
|
|
6
|
+
use serde_json::Value;
|
|
7
|
+
use tokio::process::Command;
|
|
8
|
+
|
|
9
|
+
#[derive(Debug, Clone)]
|
|
10
|
+
pub(crate) struct AgentMessageClient {
|
|
11
|
+
binary: PathBuf,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
impl AgentMessageClient {
|
|
15
|
+
pub(crate) fn new(binary: PathBuf) -> Self {
|
|
16
|
+
Self { binary }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub(crate) async fn register(&self, username: &str, pin: &str) -> Result<()> {
|
|
20
|
+
let output = self
|
|
21
|
+
.run(["register", username, pin])
|
|
22
|
+
.await
|
|
23
|
+
.context("run `agent-message register`")?;
|
|
24
|
+
if !output.contains(&format!("registered {username}")) {
|
|
25
|
+
bail!("unexpected register output: {output}");
|
|
26
|
+
}
|
|
27
|
+
Ok(())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub(crate) async fn send_text_message(&self, username: &str, text: &str) -> Result<String> {
|
|
31
|
+
let output = self
|
|
32
|
+
.run(["send", username, text])
|
|
33
|
+
.await
|
|
34
|
+
.context("run `agent-message send`")?;
|
|
35
|
+
parse_sent_message_id(&output)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub(crate) async fn send_json_render_message(
|
|
39
|
+
&self,
|
|
40
|
+
username: &str,
|
|
41
|
+
spec: Value,
|
|
42
|
+
) -> Result<String> {
|
|
43
|
+
let payload = serde_json::to_string(&spec).context("encode json_render spec")?;
|
|
44
|
+
let output = self
|
|
45
|
+
.run(["send", username, &payload, "--kind", "json_render"])
|
|
46
|
+
.await
|
|
47
|
+
.context("run `agent-message send --kind json_render`")?;
|
|
48
|
+
parse_sent_message_id(&output)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub(crate) async fn read_messages(&self, username: &str, limit: usize) -> Result<Vec<Message>> {
|
|
52
|
+
let limit_string = limit.to_string();
|
|
53
|
+
let output = self
|
|
54
|
+
.run(["read", username, "-n", &limit_string])
|
|
55
|
+
.await
|
|
56
|
+
.context("run `agent-message read`")?;
|
|
57
|
+
parse_read_output(&output)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async fn run<const N: usize>(&self, args: [&str; N]) -> Result<String> {
|
|
61
|
+
let mut command = Command::new(&self.binary);
|
|
62
|
+
command
|
|
63
|
+
.args(args)
|
|
64
|
+
.stdin(Stdio::null())
|
|
65
|
+
.stdout(Stdio::piped())
|
|
66
|
+
.stderr(Stdio::piped());
|
|
67
|
+
|
|
68
|
+
let child = command
|
|
69
|
+
.spawn()
|
|
70
|
+
.with_context(|| format!("spawn `{}`", self.binary.display()))?;
|
|
71
|
+
let output = tokio::time::timeout(Duration::from_secs(30), child.wait_with_output())
|
|
72
|
+
.await
|
|
73
|
+
.context("agent-message command timed out")?
|
|
74
|
+
.context("wait for agent-message command")?;
|
|
75
|
+
|
|
76
|
+
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
|
77
|
+
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
|
|
78
|
+
if !output.status.success() {
|
|
79
|
+
let detail = if stderr.is_empty() {
|
|
80
|
+
stdout.clone()
|
|
81
|
+
} else {
|
|
82
|
+
stderr.clone()
|
|
83
|
+
};
|
|
84
|
+
return Err(anyhow!("agent-message command failed: {detail}"));
|
|
85
|
+
}
|
|
86
|
+
Ok(stdout)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
91
|
+
pub(crate) struct Message {
|
|
92
|
+
pub(crate) id: String,
|
|
93
|
+
pub(crate) sender_username: String,
|
|
94
|
+
pub(crate) text: String,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn parse_sent_message_id(output: &str) -> Result<String> {
|
|
98
|
+
let trimmed = output.trim();
|
|
99
|
+
let Some(rest) = trimmed.strip_prefix("sent ") else {
|
|
100
|
+
bail!("unexpected send output: {trimmed}");
|
|
101
|
+
};
|
|
102
|
+
let id = rest.trim();
|
|
103
|
+
if id.is_empty() {
|
|
104
|
+
bail!("send output did not contain a message id");
|
|
105
|
+
}
|
|
106
|
+
Ok(id.to_string())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn parse_read_output(output: &str) -> Result<Vec<Message>> {
|
|
110
|
+
let mut messages = Vec::new();
|
|
111
|
+
let mut current: Option<Message> = None;
|
|
112
|
+
|
|
113
|
+
for raw_line in output.lines() {
|
|
114
|
+
let line = raw_line.trim_end();
|
|
115
|
+
if line.trim().is_empty() {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if line.starts_with('[') {
|
|
120
|
+
if let Some(message) = current.take() {
|
|
121
|
+
messages.push(message);
|
|
122
|
+
}
|
|
123
|
+
current = Some(parse_read_line(line)?);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let Some(message) = current.as_mut() else {
|
|
128
|
+
bail!("unexpected read line: {line}");
|
|
129
|
+
};
|
|
130
|
+
message.text.push('\n');
|
|
131
|
+
message.text.push_str(line);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if let Some(message) = current {
|
|
135
|
+
messages.push(message);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
Ok(messages)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fn parse_read_line(line: &str) -> Result<Message> {
|
|
142
|
+
let Some(after_index) = line.split_once("] ").map(|(_, rest)| rest) else {
|
|
143
|
+
bail!("unexpected read line: {line}");
|
|
144
|
+
};
|
|
145
|
+
let Some((message_id, rest)) = after_index.split_once(' ') else {
|
|
146
|
+
bail!("unexpected read line missing message id: {line}");
|
|
147
|
+
};
|
|
148
|
+
let Some((sender, text)) = rest.split_once(": ") else {
|
|
149
|
+
bail!("unexpected read line missing sender/text separator: {line}");
|
|
150
|
+
};
|
|
151
|
+
if message_id.trim().is_empty() || sender.trim().is_empty() {
|
|
152
|
+
bail!("unexpected read line with empty fields: {line}");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Ok(Message {
|
|
156
|
+
id: message_id.trim().to_string(),
|
|
157
|
+
sender_username: sender.trim().to_string(),
|
|
158
|
+
text: text.to_string(),
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[cfg(test)]
|
|
163
|
+
mod tests {
|
|
164
|
+
use super::*;
|
|
165
|
+
|
|
166
|
+
#[test]
|
|
167
|
+
fn parses_read_line() {
|
|
168
|
+
let parsed = parse_read_line("[1] m-123 jay: hello world").expect("parse line");
|
|
169
|
+
assert_eq!(
|
|
170
|
+
parsed,
|
|
171
|
+
Message {
|
|
172
|
+
id: "m-123".to_string(),
|
|
173
|
+
sender_username: "jay".to_string(),
|
|
174
|
+
text: "hello world".to_string(),
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#[test]
|
|
180
|
+
fn parses_send_output() {
|
|
181
|
+
assert_eq!(
|
|
182
|
+
parse_sent_message_id("sent m-42").expect("parse send output"),
|
|
183
|
+
"m-42"
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#[test]
|
|
188
|
+
fn parses_multiline_read_output() {
|
|
189
|
+
let output = "\
|
|
190
|
+
[1] m-123 jay: first line
|
|
191
|
+
chat_id: abc123
|
|
192
|
+
pin: 654321
|
|
193
|
+
|
|
194
|
+
[2] m-124 jay: second message";
|
|
195
|
+
|
|
196
|
+
let messages = parse_read_output(output).expect("parse output");
|
|
197
|
+
|
|
198
|
+
assert_eq!(messages.len(), 2);
|
|
199
|
+
assert_eq!(messages[0].id, "m-123");
|
|
200
|
+
assert_eq!(messages[0].sender_username, "jay");
|
|
201
|
+
assert_eq!(messages[0].text, "first line\nchat_id: abc123\npin: 654321");
|
|
202
|
+
assert_eq!(messages[1].id, "m-124");
|
|
203
|
+
assert_eq!(messages[1].text, "second message");
|
|
204
|
+
}
|
|
205
|
+
}
|