Skip to content

test(reference): pin Grok provenance and compatibility inventory - #213

Open
Blackman99 wants to merge 202 commits into
mainfrom
rewrite/grok-dsh-20260920
Open

Blackman99 wants to merge 202 commits into
mainfrom
rewrite/grok-dsh-20260920

Conversation

@Blackman99

Copy link
Copy Markdown
Owner

Implement #133 with isolated installed-reference probes, itemized story/ticket/acceptance mappings, raw pilot baselines, and scoped rewrite ADR supersession. Preserve legacy behavior and record unverified source/version and downstream parity work explicitly.## What

Why

Verification

  • Added a changeset (pnpm changeset) if this changes user-visible behavior

Implement #133 with isolated installed-reference probes, itemized story/ticket/acceptance mappings, raw pilot baselines, and scoped rewrite ADR supersession. Preserve legacy behavior and record unverified source/version and downstream parity work explicitly.
Resolve #133 review findings with complete compatibility syntax and source environment discovery, semantic acceptance mappings, independently resolved provenance, and strict headless format validation. Add real isolated FPS and hidden-option observations while preserving legacy behavior and the original pilot samples.
Address #133 review findings with source-seeded help traversal, independent portable source expectations, and real tutorial navigation and mode-refusal evidence. Assign remote workspace and tutorial contracts to their behavior owners while preserving existing runtime behavior and pilot samples.
Address #133 review findings with effect-based permission ownership and dedicated help/docs/debug/dock terminal scenarios. Record isolated palette, guide-reader and diagnostic probes while retaining explicit unverified dock availability and unchanged legacy behavior.
@changeset-bot

changeset-bot Bot commented Sep 20, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b78e28d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
codsh-cli Minor
codsh-bundle Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Implement ticket 02 (#134) with pinned licensed Rust UI reuse, separate Home/Profile, native candidate packaging, privacy observation and installed PTY verification. Preserve the legacy default and explicitly refuse execution until the dsh adapter is connected.
Resolve both Rust and legacy Home paths natively before writes, including existing ancestors of missing paths. Preserve symlink refusal and add installed-product PTY regressions for differently cased DSH_HOME and GROK_HOME roots, descendants, and reverse spelling. Addresses the independent ticket 02 isolation findings.
Track unresolved Home components and conservatively reject case-only potential overlap before any writes. Cover initially absent roots for both legacy Home variables through the installed native product, with distinct missing-sibling launch controls. Addresses the second independent ticket 02 isolation finding.
Distinguish dangling symlink components from missing directories before reconstructing Home paths. Refuse unresolved links before writes, retain native cycle errors, and verify explicit/default aliases, ancestors, chains and valid separate legacy links through the installed product.
Submit prompts from the isolated Rust client through released dsh ACP.
Show answers, provider thoughts, empty replies, and failures without
fabricating success. Keep legacy isolation when dsh cannot start.
Ask once before dsh write/edit tools run from the isolated Rust client.
The UI correlates ACP permission requests with tool-call ids, shows the
pending operation and dsh-supplied diff, allows that call once, and
rejects or ends input without writing. Missing files, tool errors, and
stale or duplicate replies are reported as failures.
Empty-draft Ctrl+C sends ACP session/cancel to released dsh; a non-empty
draft is cleared without cancelling. Esc never cancels a turn or pending
approval. Cancelled file tools cannot run from a late allow or reconnect,
and a new prompt still works after cancel.
Rename the in-tool cancel unit test to process teardown so it is not
read as resume coverage. Match --help to README: empty Ctrl+C quits
only before any turn exists.
Restore persisted dsh conversation after exit with --continue/--resume.
Interrupted tools show unknown and are not replayed. A second client is
refused while this process holds write ownership.
Convert leftover pending tools to unknown on crash recovery. Lock Windows
session owners with exclusive LockFileEx instead of succeeding open.
Reap the dsh child before releasing the owner lease, and pump the live
owner PTY while a rival is refused.
…tings

Add an official-compatible config.toml entry for the isolated Rust client,
map covered model/provider fields into generated dsh settings.yaml without
overwriting a competing user file, and expose CLI/env/overlay/file/default
origins through `codsh --rust inspect`. Invalid TOML is preserved. Missing
credentials stay local: no grok.com login, no default telemetry, and no
import of legacy ~/.dsh or ~/.grok credentials.
Write isolated dsh credentials at mode 0600 so dsh-credentials-local will load
config/overlay api_key. Inspect reports the telemetry values actually forced
off for dsh, stamps trace_upload origins, and keeps first-run base_url/env_key
visible. Document that env_key values are passed through and that ~/.dsh and
~/.grok files are not imported.
Disconnected empty Enter reloads config.toml and connects without submitting.
Credential writes patch a single refs entry and keep existing records.
…et 54 / #186)

Capture over the legacy memory store (MEMORY.md, sessions/, index.sqlite),
ported from grok-build a28ee2b with the reference prompts verbatim:

- session end saves a metadata summary to sessions/ (3 typed prompts of 50
  bytes, up to 5 topics, UTC, no model call; [memory.session] save_on_end);
- /flush and the idle flush ([compaction.memory_flush] idle_timeout_secs)
  send the last 20 messages (whole tool exchanges) to the session model or
  flush_model and append the answer to the daily session log, with the
  delta prompt after a first flush;
- /dream and the gated automatic Dream ([memory.dream] min_hours,
  min_sessions, check_interval_secs) merge the other session logs into the
  workspace MEMORY.md under the cross-process .dream-mutex lease; a failed
  or cancelled Dream leaves the gate open;
- --memory-flush flushes after a -p turn and fails unless a log is written.

The model call goes through dsh: the control plugin answers a
memory_model request without appending to the session and reports the
route, the messages and characters sent and the token usage; the cost is
not reported. /new, a session switch and quit cancel a running request.

Local deviations: an existing log is appended to, a MEMORY.md edited while
Dream ran is kept (nothing written), the previous MEMORY.md and
consolidated logs move to sessions/.archive/, and nothing-to-store is not
reported as flushed. /memory then s shows content-free diagnostics (cursor,
queue, pending age, gate, lease, last outcomes, archive) and y copies them.
GROK_MEMORY_LOG writes content-free event lines. [memory_v2] enabled = true
is refused: that store is not implemented. The pre-compaction flush is not
wired (dsh owns compaction) and semantic dedup needs embeddings.

Tests use the keyless mock model (DSH_CODE_CLI_MOCK_MEMORY and
_DELAY_MS): unit tests, the control spec, and the Linux/macOS PTY test
scripts/rust-memory-capture-pty-test.py (flush, delta, diagnostics,
session end, busy, conflict, merge, cancel, automatic Dream, idle flush,
cross-session injection, -p flush outcomes, memory_v2, GROK_MEMORY_LOG).
…with the terminal (#196)

The bundled Ship plugin now starts a per-session loopback server from its
hooks and prints one 'Ship graph · <summary> · <url>' line whose counts match
the graph the page renders. The graph is joined from the spec, answers.json,
and local tickets; the .ship.graph.json cache is only rebuilt, never read.
SessionStart/SessionEnd hooks restart and stop the server; it also exits when
the codsh host dies, the record changes, or the plugin data disappears.

Hook host: a command hook's systemMessage is shown as a note, and hooks get
CODSH_HOOK_HOST_PID. The e2e vt emulator now segments only a short head of
the unread stream (long sessions cost GBs before). New headless agent-browser
PTY check: scripts/rust-ship-web-pty-test.py.
Active, trusted plugins' .mcp.json / manifest mcpServers join mcp::discover
as Source::Plugin at the lowest priority, anchored in the plugin root with
GROK_PLUGIN_ROOT/DATA (and CLAUDE_PLUGIN_*). The plugin view, mcp list,
/mcps, and plugin list --json report the same states. Installing or
enabling grants nothing; disable/update/uninstall (or losing a name) forget
the servers' remembered allows, and live TUI/editor sessions are resumed in
a fresh dsh before the next prompt so withdrawn tools are gone.
… 65 / #197)

One ledger folded from the dsh session log (packages/cli/bin/rust-usage.mjs)
feeds every surface:

- /usage (alias /cost) and the /session-info usage line in the TUI;
- the status line command payload (context_window.session_input_tokens,
  session_output_tokens, session_usage, cost.total_api_duration_ms);
- headless json / streaming-json / streaming-messages-json (usage,
  modelUsage, num_turns, cost_status, usage_is_incomplete), sliced to the
  turns the prompt started;
- `codsh --rust usage <session-id> [turn]` (JSON, reference envelope).

A model call is one dsh attempt (step/start or a retry to its settle) with
the provider's reported usage: input with cache reads and writes, output
with reasoning, per model route. Subagent children fold into the parent turn
that spawned them. The whole log is folded, so a resume never double-counts;
a fork keeps inherited history as in the reference. A call with no reported
usage, an interrupted or failed turn, or a running or missing subagent log
marks the ledger incomplete instead of adding zeros. Auxiliary calls (title,
compaction summary, /btw, memory) are excluded. dsh reports no cost and no
price table exists in the pinned dsh or reference sources, so cost is shown
as not available (cost_status "unknown"); nothing is estimated, never $0.

The TUI notice area grows from 6 to 8 rows so /session-info keeps its
session id with the usage line.
…208)

The optional Ship extension now drives grill, to-spec and tickets (gates
auto-Confirmed, Mission Contract sealed at gate 1), a parallel landing wave in
worktree-isolated subagents with serial merge --no-ff and the legacy conflict
validation, the separate final verification turn, and Merge-back, all from
hook boundaries while dsh executes every agent. A failed or cancelled child is
never merged or ticked; /ship resumes any phase; a lost run state is rebuilt
from the files and a missing sealed contract stops the run.

Also: the Rust client keeps a Stop-continued model call's text in the running
turn, and the hook runner no longer crashes dsh on EPIPE from a hook that exits
early or is killed by a cancel.
…ulti-type content

Remote http/sse MCP servers run through codsh's own proxy (streamable HTTP
and legacy SSE, session and protocol headers, 401 refresh, 404 re-init,
refused redirects, no resend after a broken request). OAuth sign-in via
mcp login / /mcps auth / editor x.ai/mcp/auth_trigger: RFC 9728/8414
discovery, RFC 7591 registration, PKCE S256 with RFC 8707 resource, loopback
callback, owner-only token store, refresh, RFC 7009 revocation on logout.
Tool results keep image blocks for dsh. MCP elicitation (form and URL) is
answered on a TUI card or forwarded to editors as x.ai/mcp/elicit, with
schema re-checks; editors also get auth_status and read_resource.

Refs #168
Add configurable image services to `codsh --rust`. `[models] image_gen`
(and optionally `image_edit`) names a `[model.<id>]` marked
supports_image_generation / supports_image_edit, speaking the reference
`xai` JSON body or the `openai` Images shape (for example a local
stable-diffusion.cpp sd-server). With none configured neither tool exists;
official hosts are a configuration error and a chat model is never used as
an image service or picked as the default chat model from an image entry.

dsh runs the model's `image_gen` / `image_edit` tools (rust-acp-image.mjs,
registered only when the client enables them) and every call asks through
the dsh approval path: the card names the prompt, the host, the number of
reference images sent and that codsh does not know the service's price.
`[Image #N]` chips, absolute paths, file:// and data: URLs are references;
Read deny rules refuse a path. `/imagine <description>` sends the reference
instruction verbatim. The native `image run --json` job checks replies
(refusal, URL-only, malformed bytes, HTTP errors, timeout, cancel save
nothing) and saves atomically to `<session>/images/<n>.<ext>`. The row shows
elapsed time, Ctrl+C kills the request, `/images [open [N]]` lists and opens
results, and resume keeps titles, `/imagine` text and the saved files.
`codsh --rust image generate|edit|list` and `inspect` use the same config.

Tests use the keyless mock model and a loopback fake image service (unit,
spec, and the Linux/macOS PTY test scripts/rust-image-gen-pty-test.py);
the openai shape was also run against a real local sd-server (SD-Turbo).
Add a configurable video service to `codsh --rust`. `[models] video_gen`
names a `[model.<id>]` marked supports_video_generation, speaking the
reference `xai` async API (POST /videos/generations, poll /videos/{id},
download video.url) or the native job API of a local stable-diffusion.cpp
sd-server (`protocol = "sdcpp"`). With none configured no video tool exists;
official hosts are a configuration error and a chat model is never used as a
video service.

The service's capabilities are explicit (video_durations, video_resolutions,
video_tools; sdcpp video_fps / video_output_format / video_strength), carried
into the tool schemas, and checked before any request: an unsupported
duration, resolution or input fails with the reason instead of being dropped.

dsh runs the model's `image_to_video` / `reference_to_video` tools
(rust-acp-video.mjs, registered only when the client enables them) and every
call asks through the dsh approval path with a card naming length,
resolution, prompt, host, image count and the unknown price. `/imagine-video`
sends the reference instruction verbatim with the attached images. The
native `video run --json` job records each job in `<session>/video-jobs/`
before the start request and maps remote states to the live row (queued with
position, generating, downloading, elapsed time). Failed, expired, refused,
malformed or foreign-host results, timeouts and Ctrl+C are distinct and save
nothing; Ctrl+C asks an sdcpp service to cancel and records its answer.
Finished videos are checked and saved atomically as
`<session>/videos/<n>.<ext>`. `/videos [open|status|cancel]` lists, opens,
queries a job nobody follows (after a timeout, a crash or `--resume`, which
names unfinished jobs) and cancels; `codsh --rust video ...` does the same
outside a session.

Tests: 15 Rust unit tests against loopback fakes of both protocols,
scripts/rust-video-gen.spec.mjs (18) and scripts/rust-video-gen-pty-test.py
(added to test:rust:pty). The sdcpp protocol was also run against a real
local sd-server (SD 1.5 + AnimateDiff mm_sd15_v3).
…#217 #218)

Found by running #186 against a local Qwen3-4B (llama.cpp) with the real
launcher; each fix has a unit test and a PTY check in
scripts/rust-memory-capture-pty-test.py that fails on 95070ba.

- #215: the first /flush after a restart or --resume was a full flush and
  re-captured everything. The previous flush lived only in memory (as in
  the reference memory_state.rs); it is now read back from the session's
  newest flush log (sessions/ or sessions/.archive/, the segment after the
  last `<!-- flush … -->`), so the delta prompt is used. Recorded as a
  local change in rust/upstream/MODIFICATIONS.
- #216: first-turn recall joined every prompt word with AND, so a
  conversational question found no session log before Dream. It now
  follows the reference index.rs: extract_keywords (copied verbatim with
  its stop words and tests into memory_keywords.rs) joined with OR,
  ranked by BM25. search_notes keeps AND.
- #217: [memory_v2] is a known top-level key, so inspect shows only the
  "not implemented" refusal, not an unknown security/policy field warning.
- #218: a /flush or /dream busy refusal is cleared by the next memory
  notice, so "Dream is already running" no longer stays under
  "Dream completed". Other errors are kept.

The memory-capture PTY test now also runs in test:rust:pty.
)

A session whose first turn carried local memory was titled
`<local-memory> Local memory notes the`: codsh folds memory, rules, agent
definitions, and an expanded skill/command body into the same text block
as the user's words, and dsh's fallback title, the picker prompts, and the
resume projection all read that whole block. On resume such a first turn
was dropped (isDirectUser saw the leading `<`).

Follow the reference title order (rename, generated, first typed prompt):
- assets::typed_prompt / helper typedPrompt strip the leading
  <local-memory>, <human_rules>, <agent-definitions> blocks and unwrap a
  skill/command invocation to the typed `/name args`.
- Catalog prompts, sessions list, /resume, dashboard, /rename --auto,
  remote replay, resumed turns, rewind points, and export use it.
- dsh's stored fallback title is never shown; provider and user titles are.
- Picker prompts list only direct user messages (no plugin
  `<system-reminder>` or runtime snapshot).

Tests: Rust unit (typed_prompt, catalog titles/projection, memory block
round trip), scripts/rust-session-title.spec.mjs, and a PTY step in
rust-memory-capture-pty-test.py (sessions list, /resume, resumed turn);
all fail on 9b6eb6c.
…ess /deep-research (ticket 74 / #206)

Real runs with a local Qwen3-4B and a local SearXNG showed three gaps in
the integrated deep-research workflow (5b300a8):

Web tools follow the effective settings. The launcher decided the dsh
`tool-web` row from CODSH_WEB_SEARCH / CODSH_WEB_FETCH before the Rust
client had read config.toml, so a setup configured only in config.toml
registered neither tool: researchers (and #171's in-session search) had no
web_search/web_fetch. rust.mjs now writes a fail-closed placeholder and
`config::apply_to_dsh` (and the test-seam patch) appends `web_tools_yaml`,
the effective `web.search/fetch.enabled` (config.toml, env overrides such
as GROK_WEB_FETCH / GROK_DISABLE_WEB_SEARCH, requirements,
--disable-web-search), after the overlay; dsh applies the later row. A
stray CODSH_WEB_* value is no longer a setting.

A partial result is not shown as a plain success. The completion notice
reads `workflow deep-research [complete (result: partial)]`, and the
plugin's `workflow` event carries `result`, so the tasks row and the
workflow block title say `complete (result: partial)` as /workflow runs
already did.

Headless `-p "/deep-research <query>"` runs the built-in command instead
of sending the text to the model, as the reference's slash_exec does: the
client sends `workflow_launch`, the plugin waits in the foreground
(CODSH_WORKFLOW_FOREGROUND), and the result block is the answer (exit 0
when the run completes, partial or verified; 1 when it is stopped, blocked,
budget-limited or fails; a signal stops the run). No query prints the
usage. The streaming-messages-json init line is emitted when the launch is
sent and lists the session's visible tools (new control `tools` request)
and `deep-research` in slash_commands.

Docs: README/README.zh say that workflow children cannot answer an
approval, so in `ask` mode their web_fetch needs an allow rule, a domain
grant or always-approve, and record the local Qwen3-4B + SearXNG run.

Tests: config unit test without CODSH_WEB_* (config-only, env override,
--disable-web-search, stray env); control/headless/subagents/plain unit
tests; statusWords and the partial `workflow` event in
rust-workflow.spec.mjs; new scripts/rust-web-config-pty-test.py (packed
install, no CODSH_ACP_PATCH, no CODSH_WEB_*; added to test:rust:pty);
rust-workflow-pty-test.py now generates its overlay with web off so only
config.toml turns the tools on, and checks the partial notice and tasks row
and headless /deep-research; rust-plain-pipe-test.py no longer expects
web_search from a stray CODSH_WEB_SEARCH.
A workflow child or subagent asked through the same approval/request
waterfall as the main session, but dsh-acp only answers for the ACP
sessions it owns, so a child's ask fell through to "unavailable" and was
refused ("permission mode ask"). In the default ask mode deep-research
researchers could never web_fetch without an allow rule.

rust-acp-child-approval.mjs (wired in rust-acp-control.mjs) now takes a
child's request when the terminal UI owns its root session and the control
channel is up. It sends child_approval with the child's label, type,
workflow and phase (from a new subagent lineage lookup), the tool and its
input, and waits for child_approval_answer. The child's aborted call, its
disposal or the root's settle the request as cancelled and close the card;
a closed channel settles it as unavailable; headless and agent stdio keep
the old refusal. file-approval passes the call's input and abort signal.

The Rust client queues the requests (child_approval.rs) behind the main
session's own prompt. The status line names who asks, the tool and the
target; with an empty prompt y allows once, a records the grant exactly as
the main prompt's a does (same grants and policy files), n returns the
refusal to the child. Enter does not answer a child's request. A pending
child approval outranks the question card like a main approval.

Reference (grok-build a28ee2b acp_handler/permissions.rs) also routes a
subagent's request into the owning session's permission queue; it cancels
the child's turn on a reject, while codsh refuses only that call.

Tests: unit tests for routing, attribution, queueing, deny, abort, stale
answers, dispose/close, headless, allow rules and always-approve (JS), the
queue, parsing, event handling and grant recording (Rust); a new installed
PTY test (rust-child-approval-pty-test.py) with a workflow child's web_fetch
in ask mode: y fetches, n refuses, a remembers the domain, /workflow stop
closes a waiting request, -p keeps the refusal.
The Rust client now installs, updates and rolls back on macOS from the
packed release product alone (no Rust toolchain), with evidence from
GitHub-hosted macOS runners.

- Version: the native reports the launcher's package version (0.24.0),
  not the internal Cargo 0.1.0; build:rust passes CODSH_PACKAGE_VERSION.
- Update: packages/cli/bin/installer.mjs picks the installer that owns
  the running package (npm/pnpm/yarn/bun global prefix, then an
  inherited npm_config_user_agent) and is shared by `codsh update` and
  the new `codsh --rust update [--check] [--to <v>] [--json]`
  (schema codsh.rust-update.v1). A broken native prints a recovery line.
- dsh: codsh.testedDsh pins the dsh this release was tested with
  (0.1.5-rc.3). Missing/old-dsh messages, install-check (dsh.tested,
  dsh.note) and the README name `@deepseek-ai/dsh@<tested>`; the registry
  latest 0.1.7-rc.2 fails the first turn ("format v4 message requires a
  producer-owned source kind").
- Startup: session/new is retried for up to 15s while dsh is still
  registering the provider adapter ("no adapter registered for
  provider" / "ACP session persistence flush failed"), a race seen on
  slow macOS and Linux runners; ACP error details are shown.
- macOS build fixes: openpty pointer mutability; install test resolves
  /tmp -> /private/tmp; py3.10-compatible screen harness.
- CI: reusable rust-native.yml builds darwin-arm64/x64 and linux-x64
  natives; rust-platforms.yml packs all natives and runs the clean
  install/update/rollback, PTY, isolation/network audit and first-phase
  flows on macOS arm64, Intel and x64 Node under Rosetta; release.yml
  builds the natives and runs check:rust-package before publishing.
Linux users install the prebuilt Rust client from the packed product and
complete a dsh turn, tool approval, cancel and resume, with evidence from
clean GitHub-hosted systems.

- Natives: linux-x64 and new linux-arm64, OpenSSL compiled in (no
  libssl at run time); floor recorded as glibc 2.35.
- Runtime check before anything starts (and before a Rust Home is
  created): an ELF reader finds the native's libc and needed libraries;
  musl (Alpine), an older glibc or a missing shared library is refused
  with what to install and that plain `codsh` keeps working
  (codes libc / glibc / library). install-check has a `runtime` section
  and check:rust-package verifies it.
- The turn/permission/cancel/resume PTY tests run on Linux too.
- scripts/rust-platform-test.py: a reusable installed-product platform
  run (install-check, install/update/rollback, turn, approval, cancel,
  resume) that writes platform-report.json with the environment and an
  untested-capability list.
- CI: Linux jobs on Ubuntu 22.04/24.04 x64 and 22.04 arm64 hosts, and
  clean containers (Debian 12/13, Fedora 41 run; Debian 11 and Alpine
  must refuse), all with the registry dsh at the tested version.
Build, package and run the Rust client natively on Windows x64.

- rust-native.yml builds x86_64-pc-windows-msvc on windows-2022 with
  +crt-static; release.yml requires win32-x64 in check:rust-package.
- Paths: dunce canonical paths (no \\?\ verbatim prefixes reach dsh or the
  Homes); drive-aware approval paths.
- The ACP control channel listens on a loopback TCP port with the same
  one-time token on Windows (Unix socket elsewhere); process trees are ended
  with taskkill /T /F.
- The session-owner lock on Windows locks one byte far past the record, so
  the mandatory byte-range lock no longer makes the owner file unreadable
  (resume and the scheduler failed with "no longer owned").
- Windows reports a key release for every key: releases now reach the
  composer only for the voice chord (every character was typed twice), and a
  release with no press before it (an Alt code, or a ConPTY character not on
  the keyboard layout such as a check mark) is typed as the character.
- No kitty keyboard push/pop on Windows.
- dsh's Windows shell tool is `pwsh`: it renders and is policed as a shell
  command; the keyless mock issues pwsh commands on win32.
- scripts/rust-windows-pty-test.py drives the installed product through a
  real ConPTY (pywinpty): install-check, headless and interactive turns
  (CJK, Alt-code characters), file approval, pwsh, cancel of the process
  tree, resume, the refused sandbox profile, and install/update/refusal/
  rollback with the Rust Home kept and legacy Homes untouched. The windows
  job of rust-platforms.yml runs it on windows-2022 and windows-2025 without
  a Rust toolchain.
…#202)

Measure the installed client against pinned Grok 1.0.34 on macos-15 with
thresholds frozen before any candidate process starts
(scripts/rust-perf-bench.py, docs/rewrite/perf/).

Client fixes that moved the numbers:
- ACP unread counter and short wait_for_input slices
- remembered artifact verification in the launcher
- hangup watchdog so a closed terminal ends the client, dsh and the launcher
- first connect() on a worker thread; Ctrl+Q during connect exits at once and
  kills the dsh group; keys typed during connect are kept
- large paste stops resolving each line as a workspace drop; typing frames coalesce
- auto-compaction session read runs off the event loop
- synchronous ACP requests return when answered (session/close at quit included)

Documented remaining regressions needing an explicit decision: dsh turn-start
latency / no ACP token streaming (first-visible), process-tree RSS of the Node
launcher + dsh, and warm-start first-output p95.
Add scripts/rust-capability-matrix.py and the capability-matrix job
(ubuntu-22.04, macos-15, windows-2022) that install the packed product and
record OS / terminal versions and the real effect of keys, mouse, shell,
cancel, screen modes, terminal restore / hangup / early quit, the real
clipboard, voice doctor without fixtures, sandbox, loopback SSH, a real
tmux server and the Windows ConPTY harness. Every cell is ok, refused or
unavailable; nothing passes silently.

Product fixes found by the matrix:
- Linux: xclip / wl-paste that cannot reach the display is reported as
  such, not as an empty clipboard.
- Windows: /copy feeds clip.exe UTF-16LE with a BOM so CJK survives.
- macOS: /copy forces a UTF-8 locale for pbcopy (CJK became MacRoman
  when LANG was unset).
- macOS: a closed terminal is detected without POLLHUP (stdin readable
  with FIONREAD 0 for three checks) so the client exits 129 and dsh ends.

New harnesses: rust-clipboard-pty-test.py, rust-tmux-pty-test.py; the
Unix PTY harnesses now also run on Linux; the Windows harness adds
keys/history, clipboard and voice doctor steps. Docs in
docs/rewrite/platform-capabilities.md, README, CONTRIBUTING; changeset.
Document the three remaining macOS interaction regressions Kara accepted
on 2026-09-28 so #202 can close, with measured after-f numbers and causes:
first-visible (dsh ACP turn start / no token streaming), process-tree RSS
(Node launcher + dsh), and warm start:first-output. Thresholds were not
loosened. Point the README perf paragraph at the same decision.
…n reference (#209)

Stress bench and CI evidence for ticket 209 (part of #132):

- scripts/rust-stress-bench.py: concurrent sessions, busy-turn quit,
  long sessions, workflow pause/resume, and RSS/fd/process growth,
  measured against the frozen reference; per-metric validity, a metric
  that cannot be judged fails the run; --fetch-reference DIR.
- CI: `stress` job in rust-platforms.yml (ubuntu-22.04, macos-15,
  windows-2022) on ci/** branches containing "stress"; evidence under
  evidence/stress. Thresholds and reports from run 36465419485 in
  docs/rewrite/perf/stress/{linux-x64,macos-arm64,windows-x64}.
- acp: create an empty 0600 $DSH_HOME/.credentials.yaml when absent so
  dsh quit no longer waits ~1.1 s on the chokidar throttle timer; logout
  counts only a non-empty file as a stored credential.
- A JS dsh runs with --max-semi-space-size=16 (Node 24 young-generation
  RSS growth of ~40-55 MiB over long sessions).

Hard requirements (no leaks, no orphaned processes, no crashes) pass on
all three platforms. Remaining gaps are tracked in #221: quit under load
(Linux, macOS, Windows), Linux workflow resume, the Windows
/workflow pause control-channel failure, and Windows late-session turn
time. Thresholds were fixed before the runs and were not loosened.

This branch has not been deployed

No deployments
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.

1 participant