A Session with listen = true (the default) now listens on 127.0.0.1 with
the ALPN crafter-debug/1 and writes its discovery record (port, token,
protocol, source id, client path), which goes first when it ends. One
request per stream the client opens: the request and a FIN in, zero or
more stream lines and then the reply and a FIN out, one JSON object per
line.
It holds no thread while nothing arrives. Crafter.Network delivers a
new stream on the pool, its bytes on msquic's worker through OnRecieve,
and replies leave through SendAsync, which never waits. The handler,
which takes the session's mutex, runs on a pool task rather than inside
the stream's callback, so no msquic worker ever waits on the session,
and the session's threads only send, which Network allows under any
lock. Stream wrappers are let go outside every lock their callbacks
take. The rules head the implementation.
Limits: a request over 1 MiB is answered with an error, not buffered,
and a log follower more than 8 MiB behind is reset. quit --force exits
200 ms after its reply was taken, from a thread of its own, since
"sent" means msquic has the bytes, not that they left. A client killed
without closing is let go at the 10 s idle timeout.
The Session refuses a pool without a worker: Network hands it new
streams there, and with Start(1) nothing would run them. The partition
exports nothing, so an app's view of the module is unchanged and it
does not see Crafter.Network's types.
Crafter.Network comes from forgejo, unpinned, with the args unchanged;
--local takes ../Crafter.Network, and $CRAFTER_DEBUG_NETWORK any
project.cpp, for the branch the callback API is on until it lands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quit raised the flag and then replied. A main loop that saw the flag in
between could end the Session, and with it the connections, before the
reply was given, and the agent then saw the app vanish (exit 3) instead
of its quit succeed. The reply now goes first: the transport delivers a
reply given before its connections close. quit --force raises the flag
first as before; it exits from the reply's own thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
debug.log handed `last` to the timeline query, where 0 means every
entry, so `log --follow --last 0` streamed the whole ring of up to 8192
entries before the new ones, where an agent reads it as tail -n 0 -f:
nothing old, only what comes next. 0 now means no backlog, followed or
not, and the guide says that --follow starts with the last 50.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Session::State has module linkage, and six of its members take Job or
Waiter, which lived in an anonymous namespace: clang warned on each
build that a TU-local entity was exposed (-WTU-local-entity-exposure).
They move to a named namespace, SessionImpl, which this file alone uses
and nothing exports. No behaviour changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app's end of the connection, independent of the transport, which
reaches it through Detail::Exchange: one request's way back, safe from
any thread and never blocking.
- What needs only the session's own state (hello, status, list, log,
pause, resume) is answered on the transport thread. What touches the
app (calls, get, set, trace, screenshot) queues for the next Pump and
runs on the app's thread between frames. Waits and steps reply from
the Pump that sees them satisfied, events from the thread that emits
them. No request ever holds a thread while it waits.
- A request whose timeout passes before Pump is answered Timeout and
is guaranteed not to have run, so an agent never wonders whether a
command went through; an Async result arrives on the Pump it is
ready; requests left when the Session ends are answered ShuttingDown.
- Advance() for frame loops and Paused()/TakeSteps() for fixed-step
loops hold what the app gates on them; pausing an app that gates
nothing is refused instead of silently doing nothing. pause --when
stops the frame its condition holds; pause --on-check stops before
the next Advance in the frame a check fails, optionally raising
SIGTRAP for an attached debugger.
- A wait that times out says what its condition's paths were; a log
follower gets matching entries in order, from the backlog on, and is
dropped when it cannot keep up.
- Request parameters go through Codec, so a mistyped request gets the
same did-you-mean replies a mistyped command does. The session sets
PR_SET_PTRACER_ANY so a debugger the agent starts can attach.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>