1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! The documented HTTP API an orchestrating agent drives sessions with.
//!
//! The web viewer's own `/api/...` routes exist for the browser: they are
//! undocumented, cookie-only, and shaped around what a phone renders. These
//! `/api/v1/...` routes are the stable surface instead. They authenticate with
//! a bearer token from a file the same user can read, answer with a version
//! header so a client can tell which contract it reached, and — the point of
//! the whole module — let a caller block until one specific prompt finishes and
//! read a structured outcome for it.
//!
//! Everything that needs the daemon's live session actors or its SQLite store
//! reaches them through [`SubagentBackend`]. The daemon implements it in
//! `server_runtime::api`; the route tests implement it with a hand-written fake,
//! so the HTTP contract is tested without a running daemon.
//!
//! # The artifact routes are contract
//!
//! A caller that runs work somewhere other than this machine cannot read the
//! working tree to decide whether a change is safe to publish, so four routes
//! are part of the stable contract rather than conveniences. Changing any of
//! their media types, response bodies, or status meanings is a change to this
//! API:
//!
//! - `GET /sessions/{session_id}/diff` answers a unified diff as
//! `text/x-diff; charset=utf-8`, comparing from the revision the caller names
//! in `base` when it names one. With `json=true` it answers
//! `application/json` carrying `mj_checkpoint::archive::SessionDiff` instead,
//! a type that lives in the checkpoint crate, so changing its fields is also
//! a change here.
//! - `POST /sessions/{session_id}/export` answers the work in the form the
//! caller asks for: `kind: "patch"` as `text/x-diff`, `kind: "branch"` as
//! `{"branch", "remote"}`, and `kind: "bundle"` as
//! `application/octet-stream` with the bundle named in an attachment
//! filename.
//! - `GET` and `PUT /sessions/{session_id}/files` read one file as
//! `application/octet-stream` and inject one, answering `{"path", "bytes"}`.
//! Injection requires a live, idle session, because a write into a running
//! turn has no meaning.
//! - `GET /sessions/{session_id}/transcript` answers a page of the transcript,
//! with `next_after_seq` as the cursor to continue from and `latest_seq` to
//! tell whether the page reached the end.
//!
//! Where a route can refuse because of the session's own state — an export with
//! nothing to export, an injection into a session that is neither live nor idle
//! — it answers 409 with a sentence the caller can act on. A failure that is not
//! the caller's to fix is a 5xx. Keeping those apart is part of the contract,
//! because one is a decision for a person and the other is not.
use ;
use Arc;
use Duration;
use ;
use ;
use ;
use ;
use Next;
use ;
use ;
use ;
use ;
use ;
use ;
use ;
use ;
/// Response header naming the contract version this server speaks. A client
/// that understands only version 1 can refuse anything else without parsing a
/// body it may not recognize.
pub const API_VERSION_HEADER: &str = "mj-api-version";
pub const API_VERSION: &str = "1";
/// How long a wait blocks when the caller names no timeout, and the ceiling it
/// may ask for. Both are generous: a turn routinely runs for minutes, and the
/// caller is a program that reconnects rather than a person holding a page.
pub const DEFAULT_WAIT_SECS: u64 = 600;
pub use MAX_WAIT_SECONDS as MAX_WAIT_SECS;
/// How often a wait re-reads durable state for a session with no live actor.
const STOPPED_POLL_INTERVAL: Duration = from_millis;
const API_TOKEN_FILE: &str = "api-token";
const API_TOKEN_BYTES: usize = 32;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
pub use *;
use *;
use *;
pub use *;
pub use *;
pub use *;
use *;
use *;