agentpal 0.1.0

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.
@@ -0,0 +1,61 @@
1
+ mod codex;
2
+
3
+ use std::path::PathBuf;
4
+
5
+ use anyhow::Result;
6
+ use clap::{Args, Parser, Subcommand};
7
+
8
+ #[derive(Debug, Parser)]
9
+ #[command(name = "agentpal-host", version, about = "AgentPal desktop Host")]
10
+ struct Cli {
11
+ #[command(subcommand)]
12
+ command: Command,
13
+ }
14
+
15
+ #[derive(Debug, Subcommand)]
16
+ enum Command {
17
+ Codex(CodexCommand),
18
+ }
19
+
20
+ #[derive(Debug, Args)]
21
+ struct CodexCommand {
22
+ #[command(subcommand)]
23
+ command: CodexSubcommand,
24
+ }
25
+
26
+ #[derive(Debug, Subcommand)]
27
+ enum CodexSubcommand {
28
+ Probe(codex::CodexProbeArgs),
29
+ Pair(codex::CodexPairArgs),
30
+ Connect(codex::CodexConnectArgs),
31
+ }
32
+
33
+ #[tokio::main]
34
+ async fn main() -> Result<()> {
35
+ let cli = Cli::parse();
36
+
37
+ match cli.command {
38
+ Command::Codex(command) => match command.command {
39
+ CodexSubcommand::Probe(args) => {
40
+ let report = codex::probe(args).await;
41
+ println!("{}", serde_json::to_string_pretty(&report)?);
42
+ }
43
+ CodexSubcommand::Pair(args) => {
44
+ codex::pair(args)?;
45
+ }
46
+ CodexSubcommand::Connect(args) => {
47
+ codex::connect(args).await?;
48
+ }
49
+ },
50
+ }
51
+
52
+ Ok(())
53
+ }
54
+
55
+ fn normalize_workspace(path: PathBuf) -> Result<PathBuf> {
56
+ if path.is_absolute() {
57
+ Ok(path)
58
+ } else {
59
+ Ok(std::env::current_dir()?.join(path))
60
+ }
61
+ }
@@ -0,0 +1,11 @@
1
+ [package]
2
+ name = "agentpal-protocol"
3
+ edition.workspace = true
4
+ license.workspace = true
5
+ version.workspace = true
6
+
7
+ [dependencies]
8
+ serde.workspace = true
9
+ serde_json.workspace = true
10
+ time.workspace = true
11
+ uuid.workspace = true
@@ -0,0 +1,576 @@
1
+ use serde::{Deserialize, Serialize};
2
+ use serde_json::Value;
3
+ use time::OffsetDateTime;
4
+ use uuid::Uuid;
5
+
6
+ pub type HostId = String;
7
+ pub type SessionId = String;
8
+ pub type CommandId = String;
9
+ pub type ApprovalId = String;
10
+ pub type ClientId = String;
11
+ pub type DeviceId = String;
12
+ pub type DeviceToken = String;
13
+ pub type PairId = String;
14
+ pub type PairToken = String;
15
+ pub type WorkspaceId = String;
16
+
17
+ #[derive(Debug, Clone, Serialize, Deserialize)]
18
+ #[serde(rename_all = "camelCase")]
19
+ pub struct AgentPalEnvelope<T> {
20
+ pub id: Uuid,
21
+ pub host_id: HostId,
22
+ pub session_id: Option<SessionId>,
23
+ pub seq: u64,
24
+ #[serde(with = "time::serde::rfc3339")]
25
+ pub created_at: OffsetDateTime,
26
+ pub payload: T,
27
+ }
28
+
29
+ pub type SessionEventEnvelope = AgentPalEnvelope<SessionEvent>;
30
+
31
+ impl<T> AgentPalEnvelope<T> {
32
+ pub fn new(
33
+ host_id: impl Into<HostId>,
34
+ session_id: Option<SessionId>,
35
+ seq: u64,
36
+ payload: T,
37
+ ) -> Self {
38
+ Self {
39
+ id: Uuid::now_v7(),
40
+ host_id: host_id.into(),
41
+ session_id,
42
+ seq,
43
+ created_at: OffsetDateTime::now_utc(),
44
+ payload,
45
+ }
46
+ }
47
+ }
48
+
49
+ #[derive(Debug, Clone, Serialize, Deserialize)]
50
+ #[serde(rename_all = "camelCase")]
51
+ pub struct SessionSummary {
52
+ pub session_id: SessionId,
53
+ pub agent_kind: AgentKind,
54
+ pub workspace: String,
55
+ pub title: Option<String>,
56
+ pub state: SessionState,
57
+ pub pending_approvals: u32,
58
+ #[serde(with = "time::serde::rfc3339")]
59
+ pub updated_at: OffsetDateTime,
60
+ }
61
+
62
+ #[derive(Debug, Clone, Serialize, Deserialize)]
63
+ #[serde(rename_all = "kebab-case")]
64
+ pub enum AgentKind {
65
+ Codex,
66
+ ClaudeCode,
67
+ OpenCode,
68
+ OpenClaw,
69
+ Custom,
70
+ }
71
+
72
+ #[derive(Debug, Clone, Serialize, Deserialize)]
73
+ #[serde(rename_all = "kebab-case")]
74
+ pub enum SessionState {
75
+ Idle,
76
+ Thinking,
77
+ Running,
78
+ WaitingApproval,
79
+ Completed,
80
+ Failed,
81
+ Offline,
82
+ }
83
+
84
+ #[derive(Debug, Clone, Serialize, Deserialize)]
85
+ #[serde(tag = "type", rename_all = "kebab-case")]
86
+ pub enum SessionEvent {
87
+ SessionStarted {
88
+ summary: SessionSummary,
89
+ },
90
+ StateChanged {
91
+ state: SessionState,
92
+ },
93
+ UserMessage {
94
+ text: String,
95
+ },
96
+ AgentMessage {
97
+ text: String,
98
+ #[serde(default)]
99
+ complete: bool,
100
+ },
101
+ ToolStarted {
102
+ name: String,
103
+ input: Value,
104
+ },
105
+ ToolFinished {
106
+ name: String,
107
+ ok: bool,
108
+ summary: String,
109
+ },
110
+ CommandOutput {
111
+ command: String,
112
+ exit_code: Option<i32>,
113
+ summary: String,
114
+ },
115
+ DiffUpdated {
116
+ summary: DiffSummary,
117
+ },
118
+ ApprovalRequested {
119
+ request: ApprovalRequest,
120
+ },
121
+ ApprovalResolved {
122
+ approval_id: ApprovalId,
123
+ approved: bool,
124
+ },
125
+ Error {
126
+ message: String,
127
+ phase: Option<String>,
128
+ },
129
+ }
130
+
131
+ #[derive(Debug, Clone, Serialize, Deserialize)]
132
+ #[serde(rename_all = "camelCase")]
133
+ pub struct DiffSummary {
134
+ pub files_changed: u32,
135
+ pub additions: u32,
136
+ pub deletions: u32,
137
+ pub files: Vec<DiffFileSummary>,
138
+ }
139
+
140
+ #[derive(Debug, Clone, Serialize, Deserialize)]
141
+ #[serde(rename_all = "camelCase")]
142
+ pub struct DiffFileSummary {
143
+ pub path: String,
144
+ pub additions: u32,
145
+ pub deletions: u32,
146
+ pub risk: RiskLevel,
147
+ }
148
+
149
+ #[derive(Debug, Clone, Serialize, Deserialize)]
150
+ #[serde(rename_all = "camelCase")]
151
+ pub struct WorkspaceRequest {
152
+ pub request_id: String,
153
+ pub host_id: HostId,
154
+ pub session_id: Option<SessionId>,
155
+ pub workspace: Option<String>,
156
+ pub max_depth: u32,
157
+ pub max_entries: u32,
158
+ }
159
+
160
+ #[derive(Debug, Clone, Serialize, Deserialize)]
161
+ #[serde(rename_all = "camelCase")]
162
+ pub struct WorkspaceSnapshot {
163
+ pub request_id: String,
164
+ pub host_id: HostId,
165
+ pub workspace: String,
166
+ pub root_name: String,
167
+ #[serde(with = "time::serde::rfc3339")]
168
+ pub generated_at: OffsetDateTime,
169
+ pub tree: Vec<ProjectTreeEntry>,
170
+ pub tree_truncated: bool,
171
+ pub worktrees: Vec<WorktreeSummary>,
172
+ pub error: Option<String>,
173
+ }
174
+
175
+ #[derive(Debug, Clone, Serialize, Deserialize)]
176
+ #[serde(rename_all = "camelCase")]
177
+ pub struct FilePreviewRequest {
178
+ pub request_id: String,
179
+ pub host_id: HostId,
180
+ pub session_id: Option<SessionId>,
181
+ pub workspace: String,
182
+ pub path: String,
183
+ pub max_bytes: u32,
184
+ }
185
+
186
+ #[derive(Debug, Clone, Serialize, Deserialize)]
187
+ #[serde(rename_all = "camelCase")]
188
+ pub struct FilePreview {
189
+ pub request_id: String,
190
+ pub host_id: HostId,
191
+ pub workspace: String,
192
+ pub path: String,
193
+ pub name: String,
194
+ pub language: Option<String>,
195
+ pub size_bytes: u64,
196
+ pub truncated: bool,
197
+ pub content: Option<String>,
198
+ #[serde(with = "time::serde::rfc3339")]
199
+ pub generated_at: OffsetDateTime,
200
+ pub error: Option<String>,
201
+ }
202
+
203
+ #[derive(Debug, Clone, Serialize, Deserialize)]
204
+ #[serde(rename_all = "camelCase")]
205
+ pub struct ProjectTreeEntry {
206
+ pub path: String,
207
+ pub name: String,
208
+ pub kind: ProjectEntryKind,
209
+ pub depth: u32,
210
+ }
211
+
212
+ #[derive(Debug, Clone, Serialize, Deserialize)]
213
+ #[serde(rename_all = "kebab-case")]
214
+ pub enum ProjectEntryKind {
215
+ Directory,
216
+ File,
217
+ }
218
+
219
+ #[derive(Debug, Clone, Serialize, Deserialize)]
220
+ #[serde(rename_all = "camelCase")]
221
+ pub struct WorktreeSummary {
222
+ pub path: String,
223
+ pub branch: Option<String>,
224
+ pub head: Option<String>,
225
+ pub dirty: bool,
226
+ pub files_changed: u32,
227
+ pub additions: u32,
228
+ pub deletions: u32,
229
+ pub files: Vec<DiffFileSummary>,
230
+ pub diff_truncated: bool,
231
+ pub error: Option<String>,
232
+ }
233
+
234
+ #[derive(Debug, Clone, Serialize, Deserialize)]
235
+ #[serde(rename_all = "kebab-case")]
236
+ pub enum RiskLevel {
237
+ Low,
238
+ Medium,
239
+ High,
240
+ }
241
+
242
+ #[derive(Debug, Clone, Serialize, Deserialize)]
243
+ #[serde(rename_all = "camelCase")]
244
+ pub struct ApprovalRequest {
245
+ pub approval_id: ApprovalId,
246
+ pub source: AgentKind,
247
+ pub action: ApprovalAction,
248
+ pub title: String,
249
+ pub summary: String,
250
+ pub affected_files: Vec<String>,
251
+ pub risk: RiskLevel,
252
+ }
253
+
254
+ #[derive(Debug, Clone, Serialize, Deserialize)]
255
+ #[serde(rename_all = "kebab-case")]
256
+ pub enum ApprovalAction {
257
+ Command,
258
+ FileChange,
259
+ ToolCall,
260
+ Permission,
261
+ }
262
+
263
+ #[derive(Debug, Clone, Serialize, Deserialize)]
264
+ #[serde(rename_all = "camelCase")]
265
+ pub struct ClientCommand {
266
+ pub command_id: CommandId,
267
+ pub host_id: HostId,
268
+ pub session_id: SessionId,
269
+ pub kind: ClientCommandKind,
270
+ #[serde(with = "time::serde::rfc3339")]
271
+ pub created_at: OffsetDateTime,
272
+ pub payload: Value,
273
+ }
274
+
275
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
276
+ #[serde(rename_all = "kebab-case")]
277
+ pub enum ClientCommandKind {
278
+ InputSubmit,
279
+ SessionInterrupt,
280
+ SessionResume,
281
+ SessionStop,
282
+ ApprovalApprove,
283
+ ApprovalReject,
284
+ CommandInvoke,
285
+ PickerItemSelected,
286
+ }
287
+
288
+ #[derive(Debug, Clone, Serialize, Deserialize)]
289
+ #[serde(rename_all = "camelCase")]
290
+ pub struct PickerRegistryItem {
291
+ pub id: String,
292
+ pub trigger: PickerTrigger,
293
+ pub label: String,
294
+ pub kind: PickerItemKind,
295
+ pub source: AgentKind,
296
+ pub description: Option<String>,
297
+ pub insert_text: String,
298
+ pub execute_mode: PickerExecuteMode,
299
+ }
300
+
301
+ #[derive(Debug, Clone, Serialize, Deserialize)]
302
+ #[serde(rename_all = "camelCase")]
303
+ pub struct PickerRegistry {
304
+ pub host_id: HostId,
305
+ pub session_id: SessionId,
306
+ pub items: Vec<PickerRegistryItem>,
307
+ #[serde(with = "time::serde::rfc3339")]
308
+ pub updated_at: OffsetDateTime,
309
+ }
310
+
311
+ #[derive(Debug, Clone, Serialize, Deserialize)]
312
+ pub enum PickerTrigger {
313
+ #[serde(rename = "/")]
314
+ Slash,
315
+ #[serde(rename = "$")]
316
+ Dollar,
317
+ }
318
+
319
+ #[derive(Debug, Clone, Serialize, Deserialize)]
320
+ #[serde(rename_all = "kebab-case")]
321
+ pub enum PickerItemKind {
322
+ SlashCommand,
323
+ Skill,
324
+ Plugin,
325
+ Preset,
326
+ }
327
+
328
+ #[derive(Debug, Clone, Serialize, Deserialize)]
329
+ #[serde(rename_all = "kebab-case")]
330
+ pub enum PickerExecuteMode {
331
+ Insert,
332
+ Submit,
333
+ HostAction,
334
+ }
335
+
336
+ #[derive(Debug, Clone, Serialize, Deserialize)]
337
+ #[serde(
338
+ tag = "type",
339
+ rename_all = "kebab-case",
340
+ rename_all_fields = "camelCase"
341
+ )]
342
+ pub enum RelayClientMessage {
343
+ Register {
344
+ role: RelayClientRole,
345
+ client_id: ClientId,
346
+ #[serde(default)]
347
+ host_id: Option<HostId>,
348
+ #[serde(default)]
349
+ device_id: Option<DeviceId>,
350
+ #[serde(default)]
351
+ device_token: Option<DeviceToken>,
352
+ },
353
+ PairCreate {
354
+ request: PairCreateRequest,
355
+ },
356
+ PairClaim {
357
+ request: PairClaimRequest,
358
+ },
359
+ HostStatus {
360
+ status: HostStatus,
361
+ },
362
+ SessionEvent {
363
+ envelope: AgentPalEnvelope<SessionEvent>,
364
+ },
365
+ ClientCommand {
366
+ command: ClientCommand,
367
+ },
368
+ HistoryRequest {
369
+ request: HistoryRequest,
370
+ },
371
+ WorkspaceRequest {
372
+ request: WorkspaceRequest,
373
+ },
374
+ WorkspaceSnapshot {
375
+ snapshot: WorkspaceSnapshot,
376
+ },
377
+ FilePreviewRequest {
378
+ request: FilePreviewRequest,
379
+ },
380
+ FilePreview {
381
+ preview: FilePreview,
382
+ },
383
+ PickerRegistry {
384
+ registry: PickerRegistry,
385
+ },
386
+ }
387
+
388
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
389
+ #[serde(rename_all = "kebab-case")]
390
+ pub enum RelayClientRole {
391
+ Host,
392
+ Mobile,
393
+ }
394
+
395
+ #[derive(Debug, Clone, Serialize, Deserialize)]
396
+ #[serde(
397
+ tag = "type",
398
+ rename_all = "kebab-case",
399
+ rename_all_fields = "camelCase"
400
+ )]
401
+ pub enum RelayServerMessage {
402
+ Snapshot {
403
+ hosts: Vec<HostStatus>,
404
+ sessions: Vec<SessionSummary>,
405
+ picker_registries: Vec<PickerRegistry>,
406
+ workspace_snapshots: Vec<WorkspaceSnapshot>,
407
+ },
408
+ PairCreated {
409
+ pairing: PairingPayload,
410
+ },
411
+ PairClaimed {
412
+ claim: PairClaimAccepted,
413
+ },
414
+ HostStatus {
415
+ status: HostStatus,
416
+ },
417
+ SessionEvent {
418
+ envelope: AgentPalEnvelope<SessionEvent>,
419
+ },
420
+ ClientCommand {
421
+ command: ClientCommand,
422
+ },
423
+ HistoryRequest {
424
+ request: HistoryRequest,
425
+ },
426
+ WorkspaceRequest {
427
+ request: WorkspaceRequest,
428
+ },
429
+ WorkspaceSnapshot {
430
+ snapshot: WorkspaceSnapshot,
431
+ },
432
+ FilePreviewRequest {
433
+ request: FilePreviewRequest,
434
+ },
435
+ FilePreview {
436
+ preview: FilePreview,
437
+ },
438
+ HistoryPage {
439
+ page: HistoryPage,
440
+ },
441
+ PickerRegistry {
442
+ registry: PickerRegistry,
443
+ },
444
+ RelayNotice {
445
+ message: String,
446
+ },
447
+ Error {
448
+ message: String,
449
+ },
450
+ }
451
+
452
+ #[derive(Debug, Clone, Serialize, Deserialize)]
453
+ #[serde(rename_all = "camelCase")]
454
+ pub struct HistoryRequest {
455
+ pub request_id: String,
456
+ pub host_id: HostId,
457
+ pub session_id: SessionId,
458
+ pub before_seq: Option<u64>,
459
+ pub limit: u32,
460
+ }
461
+
462
+ #[derive(Debug, Clone, Serialize, Deserialize)]
463
+ #[serde(rename_all = "camelCase")]
464
+ pub struct HistoryPage {
465
+ pub request_id: String,
466
+ pub host_id: HostId,
467
+ pub session_id: SessionId,
468
+ pub events: Vec<SessionEventEnvelope>,
469
+ pub has_more: bool,
470
+ pub oldest_seq: Option<u64>,
471
+ pub newest_seq: Option<u64>,
472
+ }
473
+
474
+ #[derive(Debug, Clone, Serialize, Deserialize)]
475
+ #[serde(rename_all = "camelCase")]
476
+ pub struct PairingPayload {
477
+ pub version: u32,
478
+ pub relay_url: String,
479
+ #[serde(default)]
480
+ pub pair_id: Option<PairId>,
481
+ pub host_id: HostId,
482
+ pub host_name: String,
483
+ pub pair_token: PairToken,
484
+ #[serde(default)]
485
+ pub device_id: Option<DeviceId>,
486
+ #[serde(default)]
487
+ pub device_token: Option<DeviceToken>,
488
+ #[serde(default, with = "time::serde::rfc3339::option")]
489
+ pub expires_at: Option<OffsetDateTime>,
490
+ }
491
+
492
+ #[derive(Debug, Clone, Serialize, Deserialize)]
493
+ #[serde(rename_all = "camelCase")]
494
+ pub struct PairCreateRequest {
495
+ pub host_id: HostId,
496
+ pub host_name: String,
497
+ pub relay_url: String,
498
+ #[serde(default)]
499
+ pub pair_id: Option<PairId>,
500
+ #[serde(default)]
501
+ pub pair_token: Option<PairToken>,
502
+ #[serde(default)]
503
+ pub expires_in_seconds: Option<u64>,
504
+ }
505
+
506
+ #[derive(Debug, Clone, Serialize, Deserialize)]
507
+ #[serde(rename_all = "camelCase")]
508
+ pub struct PairClaimRequest {
509
+ pub pair_id: PairId,
510
+ pub pair_token: PairToken,
511
+ pub mobile_client_id: ClientId,
512
+ #[serde(default)]
513
+ pub device_id: Option<DeviceId>,
514
+ #[serde(default)]
515
+ pub device_name: Option<String>,
516
+ }
517
+
518
+ #[derive(Debug, Clone, Serialize, Deserialize)]
519
+ #[serde(rename_all = "camelCase")]
520
+ pub struct PairClaimAccepted {
521
+ pub pair_id: PairId,
522
+ pub host_id: HostId,
523
+ pub host_name: String,
524
+ pub mobile_client_id: ClientId,
525
+ pub device_id: DeviceId,
526
+ pub device_token: DeviceToken,
527
+ }
528
+
529
+ #[derive(Debug, Clone, Serialize, Deserialize)]
530
+ #[serde(rename_all = "camelCase")]
531
+ pub struct HostStatus {
532
+ pub host_id: HostId,
533
+ pub name: String,
534
+ pub online: bool,
535
+ pub agent_kinds: Vec<AgentKind>,
536
+ pub workspaces: Vec<String>,
537
+ pub active_sessions: u32,
538
+ #[serde(with = "time::serde::rfc3339")]
539
+ pub updated_at: OffsetDateTime,
540
+ }
541
+
542
+ impl HostStatus {
543
+ pub fn local_codex(
544
+ host_id: impl Into<HostId>,
545
+ name: impl Into<String>,
546
+ workspace: impl Into<String>,
547
+ ) -> Self {
548
+ Self {
549
+ host_id: host_id.into(),
550
+ name: name.into(),
551
+ online: true,
552
+ agent_kinds: vec![AgentKind::Codex],
553
+ workspaces: vec![workspace.into()],
554
+ active_sessions: 0,
555
+ updated_at: OffsetDateTime::now_utc(),
556
+ }
557
+ }
558
+ }
559
+
560
+ impl ClientCommand {
561
+ pub fn input_submit(
562
+ command_id: impl Into<CommandId>,
563
+ host_id: impl Into<HostId>,
564
+ session_id: impl Into<SessionId>,
565
+ text: impl Into<String>,
566
+ ) -> Self {
567
+ Self {
568
+ command_id: command_id.into(),
569
+ host_id: host_id.into(),
570
+ session_id: session_id.into(),
571
+ kind: ClientCommandKind::InputSubmit,
572
+ created_at: OffsetDateTime::now_utc(),
573
+ payload: serde_json::json!({ "text": text.into() }),
574
+ }
575
+ }
576
+ }
@@ -0,0 +1,22 @@
1
+ [package]
2
+ name = "agentpal-relay"
3
+ edition.workspace = true
4
+ license.workspace = true
5
+ version.workspace = true
6
+
7
+ [dependencies]
8
+ agentpal-protocol.workspace = true
9
+ anyhow.workspace = true
10
+ async-trait = "0.1.89"
11
+ axum.workspace = true
12
+ clap.workspace = true
13
+ futures-util.workspace = true
14
+ redis = { version = "1.2.2", features = ["tokio-comp", "connection-manager"] }
15
+ serde.workspace = true
16
+ serde_json.workspace = true
17
+ sha2 = "0.11.0"
18
+ time.workspace = true
19
+ tokio.workspace = true
20
+ tracing.workspace = true
21
+ tracing-subscriber.workspace = true
22
+ uuid.workspace = true