daemon: D2 — download.probe, genuinely async on both transports

download.probe was a stub (-32603). It needs an HTTP round trip on the engine's probe
pool (up to the schema's 30s x-deadlineMs), which cannot fit VeloxDispatcher's
synchronous on_download_probe -> HandlerResult<T> return without blocking the RPC
loop for the duration — a hard no per CLAUDE.md ("never block the RPC loop") and
AGENT-DAEMON.md build step 1.

uds_server.cpp and ws_server.cpp special-case "download.probe" before the generic
dispatch(), exactly the way they already special-case session.hello/session.subscribe:
parse the params, call the port, and queue the reply whenever the callback fires
(dropped silently if the connection is gone by then).

rpc::TaskActionPort gains probe_now(DownloadProbeParams, callback) — kept in proto/std
terms, no vdm::net::* in the signature, so veloxd_rpc never needs core/include's vdm
headers just to declare this. sched::Scheduler::probe_now is the implementation:
builds a vdm::net::ProbeRequest, runs it on the engine's probe pool, marshals the
engine-thread callback back onto the loop (deps_.post_to_loop, same as every other
engine callback here), maps a probe failure to -32013 ProbeFailed (data.httpStatus set
when there was an HTTP response), and fills suggestedCategoryId/suggestedSaveDir with a
plain extension match against the categories table — not the real rules engine, which
is still D3; noted in a comment.

Verified against real veloxd + tools/testserver, not just unit tests: a real probe
answers in ~5ms with size/resumable/etag/redirect chain; a bad host maps to -32013;
and — the actual point of the async design — a connection running a 10s slow-loris
probe does not block a second connection's download.list, which answers in ~1ms while
the probe is still outstanding.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GRDjHGgpYmMoPE2UFbe7pP
This commit is contained in:
2026-09-11 17:30:41 +04:00
co-authored by Claude Sonnet 5
parent 55f0c6099d
commit 4f6fb1029d
10 changed files with 255 additions and 18 deletions
+2 -2
View File
@@ -161,7 +161,7 @@ int main() {
});
}
velox::daemon::rpc::UdsServer uds(loop, dispatcher, hub, rt.socket_path());
velox::daemon::rpc::UdsServer uds(loop, dispatcher, hub, rt.socket_path(), &scheduler);
if (const auto ec = uds.start()) {
std::cerr << "veloxd: cannot listen on " << rt.socket_path() << ": " << ec.message()
<< "\n";
@@ -175,7 +175,7 @@ int main() {
// TODO(build step 7): replace EnvAutoApprover with a GUI-dialog / desktop-notification
// approver. Until then pairing needs VELOX_PAIR_AUTO=1.
velox::daemon::rpc::EnvAutoApprover approver;
velox::daemon::rpc::WsServer ws(loop, dispatcher, *db, approver, hub, rt);
velox::daemon::rpc::WsServer ws(loop, dispatcher, *db, approver, hub, rt, &scheduler);
if (const auto ec = ws.start()) {
std::cerr << "veloxd: WebSocket transport unavailable (" << ec.message()
<< "); the extension fallback will not work this run\n";