Skip to content

Add the dasHerd detachable terminal foundation#3528

Merged
borisbat merged 20 commits into
masterfrom
codex/herder-worktree-plan
Jul 21, 2026
Merged

Add the dasHerd detachable terminal foundation#3528
borisbat merged 20 commits into
masterfrom
codex/herder-worktree-plan

Conversation

@borisbat

Copy link
Copy Markdown
Collaborator

Summary

  • establish the detachable terminal host as dasHerd's foundational execution primitive, with measured Git-command overhead and a staged worktree/task-terminal plan
  • add a persistent daScript watcher that owns multiple PTYs, durable raw/event logs, controller leases, heartbeats, timeouts, crash/hang/drain handling, terminal snapshots, and authenticated HTTP/WebSocket control
  • add an offline xterm.js browser fallback plus a docked native rich client that renders raw watcher bytes and returns keyboard/paste input through the same terminal path
  • refactor the watcher into an explicit GC-safe lifecycle and preserve binary WebSocket frames, including embedded zero bytes
  • fix attach-time geometry races and background-color erase semantics found by exercising PowerShell and OpenCode at full-screen sizes
  • extend the pinned xterm oracle corpus with the reduced OpenCode background-erase stream

Companion renderer changes: borisbat/dasImgui#229

Validation

  • watcher core tests pass in interpreter and JIT modes (2/2 each)
  • dasHV WebSocket tests pass (9/9), including A\0B binary payload preservation
  • terminal semantic tests pass
  • pinned @xterm/headless oracle is current and app compatibility tests pass (13 scenarios)
  • rich client and embedded-terminal compile-only checks pass
  • live PowerShell stress: 300 output lines followed by editing/backspace remained on the prompt with matching 290x67 PTY/client geometry
  • live OpenCode replay produced correct Unicode, cursor coordinates, and full-cell background state; companion batching reduced 19,430 background cells to 91 spans at about 69 FPS

Notes

This is intentionally a substantial work-in-progress foundation PR. The watcher/browser/native-client path is usable, while higher-level worktree and agent herding UI remains follow-up work.

Copilot AI review requested due to automatic review settings July 21, 2026 00:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR lays down the foundation for dasHerd’s detachable terminal host (a persistent watcher that owns PTYs + durable logs, controllable over authenticated HTTP/WebSocket), along with benchmarking probes, an offline xterm.js browser control panel, and a native ImGui client. It also extends the dashv WebSocket client API to preserve binary frames (including embedded \0) and adjusts terminal erase behavior to better match real-terminal expectations.

Changes:

  • Add the dasHerd watcher (core session lifecycle + HTTP/WS control server), plus a browser control panel and an ImGui “rich client”.
  • Add benchmark “chunk” probes measuring Git-through-terminal overhead and process-per-session overhead.
  • Extend dasHV WebSocket client callbacks to a lossless onMessageFrame API (opcode + authoritative byte count), add binary-frame tests/examples, and adjust terminal erase semantics + oracle corpus.

Reviewed changes

Copilot reviewed 28 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
utils/dasHerd/WORKTREES_AND_TASK_TERMINALS.md Design draft describing worktree + task-terminal + detachable host direction and staged plan.
utils/dasHerd/watcher/watcher_server.das HTTP + WebSocket control server for the watcher (auth, session ops, snapshots, raw replay).
utils/dasHerd/watcher/watcher_core.das Core session lifecycle (launch, poll, drain, timeout, controller lease, raw history + log writing).
utils/dasHerd/watcher/vendor/xterm/xterm.css Vendored xterm.css asset for offline browser terminal UI.
utils/dasHerd/watcher/vendor/xterm/README.md Notes and versioning for vendored xterm.js assets.
utils/dasHerd/watcher/vendor/xterm/LICENSE.xterm.txt License for vendored xterm.js.
utils/dasHerd/watcher/vendor/xterm/LICENSE.addon-web-links.txt License for vendored web-links addon.
utils/dasHerd/watcher/vendor/xterm/LICENSE.addon-unicode11.txt License for vendored unicode11 addon.
utils/dasHerd/watcher/vendor/xterm/LICENSE.addon-fit.txt License for vendored fit addon.
utils/dasHerd/watcher/vendor/xterm/addon-web-links.js Vendored addon-web-links.js bundle.
utils/dasHerd/watcher/vendor/xterm/addon-unicode11.js Vendored addon-unicode11.js bundle.
utils/dasHerd/watcher/vendor/xterm/addon-fit.js Vendored addon-fit.js bundle.
utils/dasHerd/watcher/tests/test_watcher_core.das Watcher core tests covering lifecycle, drain/exit races, timeouts, controller arbitration.
utils/dasHerd/watcher/rich_client.das Native ImGui client that renders raw watcher bytes via the daScript terminal emulator and sends input back.
utils/dasHerd/watcher/README.md Run/usage docs for watcher + clients and their responsibilities/logs.
utils/dasHerd/watcher/main.das Watcher process lifecycle host (init/update/shutdown, loopback binding, GC scheduling).
utils/dasHerd/watcher/control.html Offline browser control panel using xterm.js, authenticated via token query param.
utils/dasHerd/chunk1/session_host_worker.das Worker for “fresh process per session” benchmark lane.
utils/dasHerd/chunk1/README.md Documentation + first measurement results for chunk1 overhead probe.
utils/dasHerd/chunk1/benchmark_session_host.das Benchmark harness comparing direct vs in-process terminal vs fresh worker-host lanes.
utils/dasHerd/chunk0/README.md Documentation + first measurement results for chunk0 terminal overhead probe.
utils/dasHerd/chunk0/git_terminal_window.das Visible terminal probe UI (ImGui) for manual inspection + logging.
utils/dasHerd/chunk0/git_terminal_probe.das Shared probe implementation for direct capture vs terminal capture lanes.
utils/dasHerd/chunk0/benchmark_git_terminal.das Headless benchmark runner for chunk0 probes.
tutorials/dasHV/06_websockets.das Tutorial code updated to the new onMessageFrame-based client callback.
tests/dasHV/test_websockets.das Adds coverage for binary WebSocket frames preserving opcode/length/embedded zero bytes.
modules/dasTerminal/tests/terminal_semantics.das Adds an assertion for wide grapheme handling (“中”).
modules/dasTerminal/tests/app_compat/scenarios.json Adds an OpenCode background-color erase scenario to the oracle corpus.
modules/dasTerminal/tests/app_compat/scenario-source.mjs Adds source definition for the new OpenCode scenario.
modules/dasTerminal/tests/app_compat/oracle-snapshots.json Adds expected oracle snapshot for the new OpenCode scenario.
modules/dasTerminal/daslib/terminal.das Changes erase/blanking semantics to respect the active pen background/attributes.
modules/dasHV/src/dasHV.h Adds C++ API declarations for channel close + server bind host selection.
modules/dasHV/src/dasHV.cpp Routes WebSocket client delivery through opcode-aware onMessageFrame; adds close/bind-host externs.
modules/dasHV/src/dashv_gen.inc Updates generated adapter offsets for the new onMessageFrame method.
modules/dasHV/src/aot_hv.h Adds AOT declarations for the new close/bind-host externs.
modules/dasHV/dashv/dashv_boost.das Exposes onMessageFrame + set_bind_host at the daScript wrapper level.
examples/hv/ws_chat_client.das Example updated to handle the new onMessageFrame callback.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread utils/dasHerd/watcher/watcher_core.das
Comment thread utils/dasHerd/watcher/watcher_server.das
Comment thread utils/dasHerd/watcher/tests/test_watcher_core.das
Comment thread tutorials/dasHV/06_websockets.das
Comment thread modules/dasHV/dashv/dashv_boost.das Outdated
Copilot AI review requested due to automatic review settings July 21, 2026 00:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 39 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 21, 2026 01:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 41 changed files in this pull request and generated 2 comments.

Comment on lines +263 to +273
def private observe_exit(var session : WatcherSession?; now_ms : int64) {
if (session.exit_seen_ms >= 0l || !terminal_process_exited(session.terminal)) return
session.exit_seen_ms = now_ms
session.exit_code = terminal_exit_code(session.terminal)
session.state = "exited"
session.status_revision++
if (session.last_data_ms == 0l) {
session.last_data_ms = now_ms
}
append_event(session, "process_exited", "exit_code={session.exit_code}")
}
Comment on lines +197 to 202
void onMessageFrame ( const string & msg, ws_opcode opcode ) {
if ( auto fnOnMessageFrame = get_onMessageFrame(classPtr) ) {
invoke_onMessageFrame(context, fnOnMessageFrame, classPtr,
(char *)msg.data(), static_cast<int32_t>(msg.size()), opcode);
}
}
Copilot AI review requested due to automatic review settings July 21, 2026 01:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 42 changed files in this pull request and generated 4 comments.

Comment on lines +208 to +210
let error = watcher_input(input.session_id, input.text)
if (!empty(error)) return self.respond_error(resp, http_status.CONFLICT, error)
return resp |> JSON(sprint_json(WatcherOk(), false), http_status.OK)
Comment on lines +221 to +223
let error = watcher_resize(resize.session_id, resize.columns, resize.rows, 0)
if (!empty(error)) return self.respond_error(resp, http_status.CONFLICT, error)
return resp |> JSON(sprint_json(WatcherOk(), false), http_status.OK)
Comment on lines +360 to +366
def private input_session(var session : WatcherSession?; text : string; client_id : int) : string {
if (session.controller_id != 0 && session.controller_id != client_id) return "controller lease is held by another client"
if (session.state != "running" || session.drained) return "session is not running"
if (!terminal_write(session.terminal, text)) return terminal_transport_error(session.terminal)
session.input_bytes += int64(length(text))
return ""
}
Comment on lines +375 to +380
def private resize_session(var session : WatcherSession?; columns, rows, client_id : int) : string {
if (session.controller_id != 0 && session.controller_id != client_id) return "controller lease is held by another client"
terminal_resize(session.terminal, clamp(columns, 20, 400), clamp(rows, 5, 200))
append_event(session, "resized", "{session.terminal.columns}x{session.terminal.rows}")
return ""
}
Copilot AI review requested due to automatic review settings July 21, 2026 01:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 42 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

utils/dasHerd/watcher/watcher_core.das:268

  • observe_exit overwrites session.state to "exited" for any session once terminal_process_exited() becomes true. That can hide real failure states (e.g. transport_error sets state="failed" and drained=true; the next tick’s drained-path calls observe_exit and will flip it to "exited"). Preserve "failed" while still recording exit_code/exit_seen_ms.

@borisbat
borisbat merged commit e57d954 into master Jul 21, 2026
35 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants