Feed panes in the multiplexer (vpane_* control commands, main-vertical layout, header row), src/orch (board, panel, formatter, transport, CAP adapter), Agent observer and cancel flag, supervision (idle warn/cancel, user close cancels, soft then hard stop), CAP answers collected through the spill path. Research archived under background-work-limits. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
7.9 KiB
cap (taicpp)
Port of the CAP section of ../tai.py (CAP_VERSION ... TAI_CAP_MANIFEST),
normative text in ../cap.md. Namespace detz::cap, sources in src/cap/,
one class per .h/.cpp pair. The C++ names are the Python names in
camelCase; behavior is verified by running both programs (see Verification).
Spec delta (this milestone)
- Real
TAI_CAP_MANIFESTreplaces the one-line placeholder (CapAgentManifest::TAI_CAP_MANIFEST,--cap-manifestprints it plus theprint()newline; byte-identical totai.py --cap-manifest). - All Cap classes ported: events, errors, bindings, manifest, registry,
session config, stream-json, speak session, the four drivers, answerer,
session, fleet, CLI. The old
Orchestratorgrant/check placeholder is gone. - Layer
wire_*(namespacedetz::wire) supplies what Python's stdlib gave tai.py:Json(json),WireToml(tomllib),WireRegex(re),WireProc/WirePty(subprocess, PtyProc),WireHttp(urllib, http.client, http.server),WireArgParse(argparse),WireShlex,WireDifflib,WireHash,WireText,WireOs.
Parity table (Python -> C++)
| Python | C++ |
|---|---|
CAP_VERSION CAP_MANIFEST_SCHEMA CAP_PROFILE_CODING CAP_MANIFEST_REVISION CAP_PROBE_TIMEOUT CAP_ERROR_CODES CAP_ERROR_STRINGS CAP_SESSION_CONFIG_ENV CAP_SPEAK_MAX_TURNS CAP_SPEAK_DIFF_MAX CAP_PTY_SETTLE_MS CAP_PTY_IDLE_MS CAP_PTY_SUBMIT_GAP_MS CAP_PTY_RESUBMITS_MAX CAP_PTY_SCROLLBACK CAP_PTY_INIT_TIMEOUT CAP_STREAM_LINE_MAX CAP_STREAM_STDERR_KEEP CAP_SESSION_POLL CAP_TURN_TIMEOUT CAP_WORKTREE_DIR CAP_ROUTE_QUEUE_MAX CAP_FLEET_DIFF_MAX CAP_MENTION_RE CAP_ACP_VERSION CAP_ACP_TERMINALS_MAX CAP_ACP_TERM_BUF CAP_ACP_TERM_WAIT_MAX CAP_ACP_FS_MAX CAP_A2A_TIMEOUT CAP_ANSWER_TIMEOUT CAP_ANSWER_PARALLEL CAP_ANSWER_MARKER CAP_ANSWER_RISKS |
cap_constants.h (same names) |
CAP_ERROR_SYMBOLS |
capErrorNumeric() in cap_error.h |
TAI_CAP_MANIFEST |
CapAgentManifest::TAI_CAP_MANIFEST() |
CapError |
cap_error.h |
CapEvent |
CapEvent (KINDS, DIRECTIONS, REQUIRED, ENUMS, make, validate, direction) |
CapBinding |
CapBinding (ORDER, UNSUPPORTED, declared, select) |
CapManifest |
CapManifest |
CapRegistry |
CapRegistry |
CapSessionConfig |
CapSessionConfig |
CapStreamJson |
CapStreamJson |
CapSpeakSession, cap_interrupt, cap_speak_main |
CapSpeakSession, CapInterrupt, CapSpeakMain |
CapDriver |
CapDriver (CapDriver::create in cap_driver_factory.cpp) |
CapPtyDriver |
CapPtyDriver over WirePty and CapVtScreen (port of TuiVtScreen, CapScreen seam) |
CapPipeReader, cap_proc_kill, cap_proc_reap, cap_stderr_drain |
CapPipeReader, CapProcUtil, CapStderrDrain |
CapStreamDriver CapAcpDriver CapA2aDriver |
same names |
CapAnswerer |
CapAnswerer |
CapTurnResult CapSession CapFleet |
same names |
cap_cli_main cap_cli_help cap_resolve_main cap_chat_main cap_run_main cap_fleet_plan cap_print_result cap_build_answerer cap_answer_flags |
CapCli static methods |
Event kinds
cap.session.ready, cap.text_chunk, cap.tool_call.start,
cap.tool_call.delta, cap.tool_call.end, cap.plan, cap.thought,
cap.ask_user, cap.permission.request, cap.usage, cap.error,
cap.artifact, cap.fs.change, cap.pty.raw_bytes,
cap.user_input.inject, cap.cancel, cap.session.config,
cap.ask_user.answer, cap.permission.response.
Errors
Symbols: cap_manifest_missing, cap_session_config_missing,
cap_unsupported_capability, cap_budget_exceeded, cap_invalid_answer,
cap_unknown_permission, cap_queued_input_unsupported,
cap_cancel_unsupported, cap_binding_unavailable,
cap_profile_unsupported, cap_scope_violation, cap_path_not_found,
cap_io_error, cap_terminal_unavailable, cap_terminal_killed.
String codes: rate_limit, auth_error, tool_timeout, context_overflow,
model_unavailable, pty_died, parse_failed, git_dirty, test_failed,
merge_conflict, language_server_down.
Seams (host-provided)
CapAgentPort / CapSpeakHost (the agent for --cap-speak), WireLog
(debug log sink), WireApp::setVersion (VERSION on the wire),
CapPtyDriver::setScreenFactory (swap the VT screen). Details and the exact
hooks: docs/wiring/capmcp.md.
Differences from Python (all declared)
CapAnswererruns[binary, --auto, prompt]directly; Python ran[sys.executable, tai.py, ...]. Default binary is/proc/self/exe.CapVtScreenparses SGR but does not store colors/attributes (text-only consumer) and keeps no scrollback history.- Regex: ECMAScript engine with Python syntax translation. Python-only syntax
beyond named groups,
\A,\Z,$, bare{/}, leading(?i)is not translated; compile error texts differ fromre.error. - argparse help is plain (no 3.14 color).
- Dates in TOML come back as strings.
- Terminal
terminal/outputkeeps theread(65536)blocking-chunk behavior. - Signals: SIGINT/SIGTERM set a flag polled by waits (
CapInterrupt).
Model catalog and @agent mentions
CapModelCatalog (src/cap/cap_model_catalog.*) lists agents from the *.toml manifests in the registry user and system dirs (cap CAP_AGENT_MAX 64) and checks each: manifest parses, binary on PATH. Status: ready, no_binary, bad_manifest, no_manifest. Entries are cached CAP_CATALOG_TTL (60) seconds; entries load in parallel.
Model protocol (tai-specific extension, absent from the Python app): [models] list = [...] and default in the manifest, plus an optional probe, [models] probe = ["bin", "--cap-models"] or capabilities.models = true for <binary> --cap-models. Probe output: JSON array of ids or {id|name|model} objects, {"models": [...], "default": "..."}, or one id per line. Bounds: CAP_MODEL_PROBE_TIMEOUT 5 s, CAP_MODEL_PROBE_MAX_BYTES 1 MiB, CAP_MODEL_MAX 500 per agent. A failed probe is recorded on the entry, never thrown. Tai's own manifest is unchanged and declares no models.
Mention resolution (after bots, providers, backends, known model ids, @auto, exact agent, and file paths; so a real file is never shadowed): the typed text is fuzzy-matched with FuzzyMatcher (fuzzy.md). Order: exact agent name or model id; head:tail splits at the first :, head fuzzy to a ready agent, tail fuzzy to that agent's models (no floor, the agent already disambiguates); otherwise the whole text is ranked against every ready agent name and every model. Cross-namespace picks need a score of CAP_FUZZY_FLOOR (0.3); the best score wins and equal scores resolve shorter id then list order, never an error. Unusable agents (no binary, bad manifest) are considered only when no ready candidate reaches the floor and then report why. An agent without a model list passes the model through with a note. When the pick differs from the typed text a note line shows the resolution and score. With a prompt the turn runs on that agent (CapMention, one cached CapSession per agent/model/cwd, model sent via startup.model_arg), in its own panel on the multiplexer screen, supervised (turn timeout, idle watchdog, closing the panel cancels it) and collected through the spill path (orchestration.md); without a prompt the agent is pinned (Agent::agent_pin) until @auto. A miss prints the closest candidate with its score and the full per-agent report (status, binary, manifest, binding, models, default, probe error); an unknown tag appends the same report.
Gaps: no TUI completion for agents yet; tai --cap-models is not implemented on tai's own side.
Verification
tests/capmcp_diff.py (JSON, TOML, str(), float repr, regex vs CPython),
tests/capmcp_cli_diff.py (--cap-manifest, --cap resolve|chat|run incl.
stream-json, PTY, ACP and A2A fake agents, argparse errors at four widths vs
tai.py), tests/capmcp_unit.cpp (tables and drift check).