fix(pty,subprocess,lsp): address review round on cancellation, lookup, and contracts

- pty-local: a pre-write inspection rejection no longer releases a canceled
  send while its foreground SIGINT is in flight; the interrupt path retains
  the slot and its post-signal tail resumes polling. Regression pins the
  failure-shaped cancellation and a close-during-write release.
- pty-local: SEND_ACTIVE names which provider operation is draining; README
  states the never-settling-provider outcome (slot retained, close recovers).
- subprocess-local: resolveExecutable rejects relative paths containing
  separators instead of expanding them per PATH entry with a misleading
  not-found error; seam JSDoc pins the rule for every provider.
- subprocess-local: LocalTerminalHandle documents why the seam's
  in-flight-join promise holds without operation tracking.
- lsp-local: the oversized-source diagnostic reports the observed byte lower
  bound; README documents that processId: null trades away server-side
  orphan cleanup after a hard-killed harness.
- pty/subprocess seams: cross-reference the twin five-member signal unions.
- pty-local: TODO markers for send-state consolidation and initialize-race
  relocation.
This commit is contained in:
Tianyi Cui
2026-08-02 14:11:57 +08:00
parent a662af94df
commit e385c11e8e
21 changed files with 155 additions and 33 deletions
@@ -10,7 +10,7 @@ Local implementation of the [`@deepseek-ai/dsh-subprocess`](../subprocess/README
- **Per-stream dispositions** — `'pipe'` hands the raw stream to the caller untouched (protocol framing stays consumer-owned); `'inherit'` passes the parent descriptor through; collect mode keeps the in-memory TAIL beyond its cap (errors and results cluster at the end — pi/OpenCode rationale) while the FULL stream is appended to a private temp file when a spill cap is configured — omitting `spill` keeps only the tail, the diagnostic shape. A stream larger than the spill cap discards its now-incomplete spill and returns only the marked truncated tail; spill fds are sealed at settlement, and a failed final close withholds the path rather than advertising an incomplete file. Spill files are `0600` with random names under a lazily-created `0700` per-process directory.
- **Credential scrub + explicit merge** — `process.env` minus credential-shaped vars (`*KEY*`/`*SECRET*`/`*TOKEN*`) and all ambient `DSH_*` names; the spec's explicit `env` merges after that scrub with no namespace validation, so a deliberately supplied credential or current `DSH_*` fact wins while stale nested-harness identity cannot leak in ambiently. Supplied stdin is written and closed; otherwise fd 0 is `/dev/null`. See the [stdin/env Agent Note](../../../.agents/notes/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md) and [managed environment Agent Note](../../../.agents/notes/implemented/feature/2026-07-10-agent-session-identity-and-log-location.md).
- **Offset-based reads** — collect-mode readers return deltas in whole-stream byte coordinates; the service never holds a cursor, so consumer-owned cursors (the bash background read path) and full-stream re-reads coexist, before and after settlement.
- **Executable lookup** — `resolveExecutable` checks absolute files or searches the scrubbed effective PATH with platform-aware executable extensions; relative PATH entries resolve from the host process cwd.
- **Executable lookup** — `resolveExecutable` checks absolute files or searches the scrubbed effective PATH with platform-aware executable extensions; relative paths containing separators are rejected at the seam, and relative PATH entries resolve from the host process cwd.
- **Terminal-process ownership** — `spawnTerminal` allocates `node-pty`, bridges UTF-8 terminal text, inspects and signals the current foreground process group, and exposes one awaited termination operation that sweeps descendants before and after terminating the top-level shell. Each foreground inspection retains exact identities from the rooted tree; Linux also enumerates the POSIX session after its leader exits. A previously observed macOS descendant and any same-session Linux member therefore remain fenced after reparenting, while pid/start identity prevents cleanup from following PID reuse. The higher PTY backend owns prompt readiness, buffers, and model-facing operations.
- **Terminate-and-join disposal** — the service retains live handles only so its own disposal can escalate every running tree and await its exit; settled and spawn-failed handles leave the live set on settlement.