feat(daemon): Subscribe command stream + CompleteCommand — the vendor loop closes
The HCACK-4 contract, implemented end to end. For every YAML-declared command the service registers a forwarding handler (new HostCommandRegistry names()/spec() accessors): with a subscribed tool client the command is queued onto the Subscribe stream (id + name + params via from_item) and the host is answered S2F42 HCACK=4 immediately — never blocking the io thread or the T3 window; with NO subscriber the command takes its declarative YAML ack (the honest pre-daemon behaviour). Settled + documented in the proto: v1 is a firehose with no buffering/replay. CompleteCommand correlates the pending id (audit; unknown id => PARAMETER_INVALID). Side effects stay suppressed on HCACK-4 (router applies them only on Accept), so the completion event the TOOL fires is the host's real signal — exactly E30's intent. Tests (daemon suite 101 -> 124 assertions): a real S2F41 dispatched through the full default-handler router ON the io thread under run_async — HCACK 4 with subscriber + params on the stream, declarative Accept without, CompleteCommand known/unknown, fallback restored after unsubscribe. Interop (now 20 checks, all green): the complete conformant loop against the secsgem-py reference host — S2F41 START -> S2F42 HCACK=4 -> tool receives Command(name=START, id=1) -> CompleteCommand -> FireEvent -> host receives S6F11. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -71,6 +71,21 @@ class HostCommandRegistry {
|
||||
}
|
||||
bool has(const std::string& rcmd) const { return by_rcmd_.count(rcmd) > 0; }
|
||||
bool has_handler(const std::string& rcmd) const { return handlers_.count(rcmd) > 0; }
|
||||
|
||||
// Enumeration + spec lookup, so a front-end (e.g. the gRPC daemon) can
|
||||
// attach behaviour to every declared command and fall back to the
|
||||
// declarative ack when its tool client isn't connected.
|
||||
std::vector<std::string> names() const {
|
||||
std::vector<std::string> out;
|
||||
out.reserve(by_rcmd_.size());
|
||||
for (const auto& [rcmd, _] : by_rcmd_) out.push_back(rcmd);
|
||||
return out;
|
||||
}
|
||||
std::optional<Spec> spec(const std::string& rcmd) const {
|
||||
auto it = by_rcmd_.find(rcmd);
|
||||
if (it == by_rcmd_.end()) return std::nullopt;
|
||||
return it->second;
|
||||
}
|
||||
Result dispatch(const std::string& rcmd,
|
||||
const std::vector<CommandParameter>& params) const {
|
||||
auto it = by_rcmd_.find(rcmd);
|
||||
|
||||
Reference in New Issue
Block a user