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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! Live-streaming accumulation for in-flight steps.
//!
//! Per ARCH §2.3 / §3.5 / §4.4: the harness writes
//! `<conv-repo>/steps/<conv-id>/<NNN>/response.json` as a JSONL stream
//! of §4.4 events as the model's adapter emits them, then closes the fd
//! at completion. The frontend tails this file from disk on every tick
//! (§3.5: re-read, no in-memory accumulator that could drift).
//!
//! **One read, one value** (§5.1 #10, #28b). The fold yields a [`Stream`]:
//! the accumulated answer text, the accumulated reasoning text, and the
//! **kind of the last content delta** that landed. The last is what splits a
//! live model call into the three things it can be doing — nothing back yet,
//! thinking, answering — for the per-agent `Doing` derivation the §11 live
//! mark paints. Reading the file twice would cost a second syscall per agent
//! per tick and could catch two different mid-write states of one file, so the
//! facts come off one pass or they are not one file's answer at all — which is
//! why they travel as one value ([`Agent::stream`](super::Agent::stream))
//! rather than as fields that could be filled from two reads.
//!
//! The fold is **resumable** ([`Stream::absorb`], §7.2 live tail): folding the
//! bytes appended since the last read and absorbing the result is the same
//! `Stream` as folding the file whole, so the frame-cadence follower
//! (`app::live`) reuses this parser on a growing suffix instead of growing a
//! second one.
//!
//! Functions here are pure over an on-disk `<conv-repo>/steps/` tree.
//! Events outside the `content_delta` seam (`message_start`, tool-argument
//! deltas, etc.) are ignored at this layer — pulsing tool indicators (bl-23d9)
//! and branch-state badges (bl-de6b) read their own signals.
//!
//! Deltas are read from brazen's `v=1` `content_delta` (§4.4). The v0.6 legacy
//! vocabulary is retired (bl-56ee).
use Path;
use STEPS_DIR;
/// The fold's own JSON spelling (REMOTE §3, bl-73e7) — the follow lane's frame
/// body, beside the type rather than at the boundary that carries it.
/// Width of the zero-padded step sequence in on-disk paths
/// (`steps/<conv-id>/001`, `…/002`, ...). Mirrors
/// `src/prompt/step::STEP_SEQ_WIDTH` — duplicated here to keep the UI
/// crate free of a dep on the harness binary.
const STEP_SEQ_WIDTH: usize = 3;
/// The live tail the harness appends stream events to and closes at
/// completion. `pub(super)` so the §11 recency derivation
/// (`enumerate::last_action_from_disk`) names the same file this folds.
pub const RESPONSE_FILE: &str = "response.json";
/// The kind of one `content_delta` — the brazen `v=1` `Delta` arms yog reads
/// (§4.4). `json_delta` (tool arguments) is not one of them: it is the model
/// composing a tool call, which the §5.1 #10 tool records already say better.
/// What one read of the latest step's `response.json` says (§5.1 #10, #28b) —
/// every fact off one pass, so they cannot describe two different mid-write
/// states of the same file.
/// Accrete `more` onto an accumulator that may not exist yet. Absent stays
/// absent — a stream that has said nothing has said nothing, and an empty
/// `Some("")` would read as "it spoke" to every seat downstream.
/// Read the latest step's live stream from disk.
///
/// `pub` because it completes the view-model boundary: [`Transcript::with_live`]
/// (crate::transcript::Transcript::with_live) takes a [`Stream`], and a public
/// surface that consumes one owes a public way to obtain one off a workspace.
///
/// "Latest step" is the highest `<NNN>` directory under
/// `<conv-repo>/steps/<conv-id>/`. Re-derived on every call from the
/// directory listing so the view-model has no in-memory state to drift
/// out of sync with disk (§3.5). An absent conversation, an absent file and
/// an unreadable one all read as the default [`Stream`] — nothing has come
/// back — which is the general path with empty input, not a case.
/// The latest step's open response file, or `None` when no step has opened
/// one — the path the §7.2 live-tail follower holds its offset into.
/// `pub(crate)` because the follower is the only caller that wants the *file*
/// rather than the fold.
pub
/// Find the highest-numbered `<NNN>/` directory under `conv_steps`.
/// Entries that don't match the zero-padded step shape are ignored —
/// `tools/` lives one level deeper, so it's structurally not at risk
/// here, but staying strict keeps us robust to stray files.
pub
/// Fold a JSONL `response.json` payload into its [`Stream`]. Each line is a
/// §4.4 stream event; text fragments accumulate in stream order across all
/// block indices, and every recognised delta moves `last_delta` — so the field
/// ends on the newest one, which is what the operator is watching happen.
/// Lines that fail to parse, and events outside the delta seam, are skipped —
/// partial-write tolerance is structural (the harness may be mid-line on disk).
/// `pub(crate)` so the §7.2 follower folds its appended suffix through this one
/// parser and absorbs the result ([`Stream::absorb`]).
pub
/// An accumulator as the fact it is: nothing said reads as absent, never as an
/// empty utterance — every seat downstream branches on `Some`.
/// The brazen `v=1` delta seam:
/// `{"type":"content_delta","delta":{"<arm>":"…"}}` — the externally-tagged
/// `Delta`'s two arms yog reads, with their payloads. A `json_delta` (tool
/// arguments) or any other event yields `None`.