Commit graph

48 commits

Author SHA1 Message Date
Armin Ronacher
032656b6c6 fix(env): kill what a process program left running when it exits
When a program started by the process operation exits on its own, the
daemon now ends its process group (Unix) or its own job object (Windows)
before reaping it, so a wrapper like npx does not leave its server
running. The pid leaves the tracked groups before it is reaped, so a
racing close cannot signal a reused pid.
2026-11-03 14:17:21 +01:00
Armin Ronacher
9c9ce8547e feat(env): run programs with plain pipes through the daemon
The daemon's process operation runs a long-lived program with piped stdin, stdout and stderr, for programs that speak a protocol on stdin and stdout such as MCP servers. processWrite and processCloseStdin feed stdin in arrival order; output arrives as raw bytes per stream; cancelling or a lost connection kills the process group. hello reports it in features, and openProcess() is the client.

On Windows, readers and writers of child pipes now run their own overlapped operations and look at stop every 100 ms. The standard library opens these pipes for overlapped I/O, so CancelSynchronousIo never reached a blocked read: exec's readers stayed blocked while a descendant held a pipe open.

Spawning, reaping, argv parsing and target lookup are shared by exec, pty and process.
2026-11-03 00:53:07 +01:00
Mario Zechner
e9c0ff4d84 feat(chord): Chord v2 is the package's root
src/next is src (the main export), src/next-node is src/node with short subpaths (/http, /workers, /plugins,
/plugins/runtime, /app, /surfaces, /web, /react, /tui, /tui/launcher); the foundations get /json and /watch;
Chord v1 moves to src/old (/old, /old/node) until p2 runs on v2; tests and docs move likewise (test/, docs/,
test/old, docs/old). Durable and env import the foundations from their subpaths.
2026-11-02 22:36:55 +00:00
Armin Ronacher
db6c084455 Add [Unreleased] section for next cycle 2026-11-02 11:38:21 +01:00
Armin Ronacher
f9a2636556 Release v1.5.0 2026-11-02 11:38:21 +01:00
Armin Ronacher
b4c2e92acb fix: remove vulnerable dependency chains
Replace build-time shell utilities with Node filesystem operations and retire the native sandbox example in favor of existing isolation options. Upgrade glob matching and worker-test dependencies and refresh the lockfiles.
2026-10-31 17:54:29 +01:00
robopipi[bot]
102d128713
fix(env): report watch overflow when a change list exceeds the frame limit (#11605)
After a rescan, the env daemon's file watcher sent every changed path in
a single `change` event. When a big batch of changes made that event
larger than the 16 MiB frame limit, `write_frame` rejected it. The
daemon's only output writer then shut down. The daemon process kept
running but sent no more replies or pings. Requests hung until the
client's 30 s silence limit dropped the connection and reconnected.

Now the watcher builds the event with `change_event`. If the serialized
path list would not fit in a frame (`MAX_PAYLOAD`), it sends the
existing `{ kind: "change", overflow: true }` event in its place. The
client already maps that to an `overflow` file change, so the wire
format does not change and the connection keeps working.

Testing:
- Added Rust unit tests in `watch.rs`. One checks that a small path list
is sent as `paths`. The other checks that a list larger than
`MAX_PAYLOAD` becomes `overflow`.
- `cargo fmt --check`, `cargo clippy --locked --all-targets -- -D
warnings` and `cargo test --locked` pass in `packages/env/daemon`.
- The `packages/env` vitest suite passes.
- Reproduced with the real daemon: I watched a directory and renamed in
a directory of 6,000 files with long paths, about 20 MB of path JSON.
Before the change, `fileInfo` requests timed out from the flush until
the client reconnected about 30 s later. After the change, the watcher
reports `overflow` right away and `fileInfo` keeps working. With 3,000
files, which fits in a frame, the paths are still reported as before.

Fixes #10516

Requested by @badlogic

Co-authored-by: triagent <triagent@localhost>
2026-10-30 12:15:09 +01:00
Petr Houška
a87b742ff4 docs(env): Windows commands' descendants survive a terminated daemon
The job object uses silent breakaway, as libuv does, so it only kills each
command's own process.
2026-10-28 21:09:58 +01:00
Petr Houška
b6d5c2ecdc fix(env): kill the daemon's commands when it is terminated
Connection.close() ends stdin and sends SIGTERM at once. The daemon did not
handle SIGTERM, so it died before its end-of-input kill_all, and running
commands were reparented to init. The daemon now blocks SIGTERM, SIGINT and
SIGHUP before starting any thread and waits for them on its own thread, which
kills every process group it started and exits. On Windows the job object
already kills commands with the daemon.
2026-10-28 21:09:58 +01:00
robopipi[bot]
92c1da1f62
fix(env): fail exec and pty with spawn_error when a helper thread cannot start (#11540)
The pi-env daemon started an `exec`'s stdout and stderr readers and its
waiter with `thread::spawn`. `thread::spawn` panics when no thread can
be created, for example when a cgroup `TasksMax` or `RLIMIT_NPROC` limit
is reached. When that happened after the child was already running:
- only the request thread unwound, so the exec never got a reply;
- its control channel was dropped, so `cancel` and `kill` reached
nobody;
- the child kept running, even after the daemon exited.

`pty` started its reader, input writer and waiter threads the same way.

## Changes
- `exec.rs`: `spawn_reader` now uses `thread::Builder::spawn` and
returns an `io::Result`. If a reader or the waiter cannot start, `run`
sets `stop`, interrupts any readers already started, kills the process
group, reaps the child, removes it from `groups`, and returns
`spawn_error`.
- The waiter thread now gets the `Child` through a channel once it is
running. If the waiter cannot start, the child stays with the caller,
which can still reap it. Before, the failed thread's closure dropped the
child without waiting for it.
- `pty.rs`: the same handling for the reader, writer and waiter threads.
On failure the terminal is also removed from `terminals`.

## Testing
- Manual reproduction with a script that runs the daemon as `prlimit
--nproc=N -- pi-env`. It starts 30 `sleep 30` execs 200 ms apart, aborts
them all, then closes the connection. N ran from 37 to 70.
- Before: for most N, one or two execs stayed pending forever after
abort, and a `sleep 30` kept running after the daemon was closed.
- After: every exec settles, either `aborted` or `spawn_error`, and no
`sleep` is left running.
- `cargo build`, `cargo clippy --all-targets`, `cargo fmt --check` and
`cargo test` all pass. The `packages/env` vitest suite passes (78
passed), and `npm run check` passes.
- There is no automated regression test. A thread-creation failure
cannot be triggered reliably in a test: `RLIMIT_NPROC` counts all of the
user's tasks and does not apply to root, and the daemon has no
fault-injection hook.
- The Windows build was not compiled. The `exec.rs` change uses only
existing cross-platform calls, and the `pty.rs` change is Unix-only.

Fixes #11433

Requested by @petrroll

---------

Co-authored-by: triagent <triagent@localhost>
Co-authored-by: Petr Houška <houskape@gmail.com>
2026-10-28 20:44:25 +01:00
Mario Zechner
5f312dac4c feat(chord,durable): failures are watch calls; one vocabulary for watches, observers and reports
Chord's shared watch: listeners take a tagged call, { kind: "value", value, ops, retired } or
{ kind: "failed", error }; a recoverable failure keeps the watch open and the next value arrives as
[["r", value]]; WatchEnd is tagged by kind, and an aborted context ends a watch "aborted".

Durable: observers take the watch's shapes (advance(call, ctx), end(end)); onReport(report) with
report.kind and report.error, session_failed is storage_failed; subscribeCommits is observeCommits;
SessionEnd and ReportFrom are tagged by kind; FileChange is tagged and file watchers stop with
stop(ctx); CompactionFailure and CompactionStatus name their trigger (pi.live version 2 with a
migrate); the task_outdated report uses the scheduler's reasons; TaskInspection's ready is pending;
DocumentCommitChange is tagged. Stored formats are unchanged.
2026-10-27 20:38:08 +00:00
Michael Yu
479d6e5e29
fix(env): include stderr in pi-env startup errors (#10716)
When the daemon exits before `hello`, the error has only the exit code.
For example, with `loginShell: true` and a profile that aborts on a
missing file:

```
pi-env exited with code 3 before it was ready
```

`sshConnection`'s probe and deploy steps already report ssh's stderr, so
this is about the launch itself: a login profile, a daemon that can't
execute, or a `Connection` created with its own command, which has no
probe or deploy step, so auth and network errors also end up here.

This keeps a short stderr tail until `hello` and adds it to startup
errors:

```
pi-env exited with code 3 before it was ready
remote stderr:
profile: /opt/toolchain/env.sh: No such file or directory
```

The tail is capped at 20 lines / 2000 chars, with terminal escapes and
control characters stripped. Ready sessions are unchanged.

I hit it while testing `loginShell` against an sshd in Docker. Tests are
in `test/connection.test.ts`, and `npm run check` and `./test.sh` pass.
2026-10-27 16:16:49 +01:00
Mario Zechner
8b0caefc22 feat(durable,chord): publish commits at adoption, address-following watches, synchronous observers
Durable publishes a stored commit inside Storage.commit's adopt: the
telemetry events, its own observers (document watches, conversation
views, task graph, scheduler, waits), and a dispatch of host
subscribeCommits listeners one microtask later, before every wait and
abort the commit triggers. A commit the storage rejects after adoption is
published before the Session fails; a throwing telemetry adapter is
reported as host_error "telemetry" instead of rejecting a durable commit.

Chord's watch listeners take one change object, { value, ops, retired }
({ value } for native watches); retirement is a flag, not an end.
Durable's document watches follow their address, and a task document's
watch ends after its retirement call. New synchronous observers for
adapters: observeDoc, observeConversation, observeTaskGraph; the watches
are built on them. The env file watcher's change type is renamed
FileChange.
2026-10-26 20:44:34 +00:00
Armin Ronacher
2685de7a17 fix: update deps for security advisories
Bump vitest to 4.1.12 (vite 8.3.4) and the gondolin example extension to 0.14.0, which drops node-forge. Gondolin's undici moves to 6.29.1.
2026-10-22 11:24:50 +02:00
Armin Ronacher
58c0b02ce5 Add [Unreleased] section for next cycle 2026-10-22 00:29:24 +02:00
Armin Ronacher
4a080252f0 Release v1.4.0 2026-10-22 00:29:13 +02:00
Mario Zechner
14ff455a0a fix(env): make the read/bash differential test deterministic and meaningful
The fake tool API lacked retainedOutput, so every bash case threw on both sides
and the test only compared the error text. Its stdout+stderr case also depended
on the order of two concurrently read pipes and failed intermittently in CI.
The API now provides retainedOutput, each bash case writes to one stream, and
bash cases must return a result.
2026-10-16 19:58:24 +00:00
Armin Ronacher
ae7b16d43b Add [Unreleased] section for next cycle 2026-10-15 20:57:35 +02:00
Armin Ronacher
71023aec15 Release v1.3.0 2026-10-15 20:57:27 +02:00
Armin Ronacher
f13c00fb3a fix: pin internal dependencies to exact versions
Update release synchronization and dependency checks to enforce exact versions. Refresh both lockfiles and add regression coverage.

closes #10865
2026-10-13 14:28:15 +02:00
Armin Ronacher
d6370b72d0 fix(env): make the listing cancel and platform tests hold in CI
- Give the listing cancel test 30 s: creating 10,000 files takes over 5 s
  on Windows CI.
- Accept an x64 daemon on arm64 macOS: the darwin-x64 release daemon runs
  through Rosetta in CI.
2026-10-13 01:23:33 +02:00
Mario Zechner
de022efd61 test(env): wait for the foreground job's name, not just any foreground process
The shell forks before it execs, so a poll could catch the forked shell (named sh) and fail the name check.
2026-10-12 23:19:48 +00:00
Armin Ronacher
d547949184 Add [Unreleased] section for next cycle 2026-10-13 00:47:08 +02:00
Armin Ronacher
06a4669355 Release v1.2.0 2026-10-13 00:46:55 +02:00
Armin Ronacher
0d79b74db1 docs: audit changelogs and add new features for next release 2026-10-13 00:36:49 +02:00
Mario Zechner
55df12bf62 docs(durable,env): changelog entries for listFiles 2026-10-12 15:22:48 +00:00
Mario Zechner
b6fda579f4 feat(env): listFiles for remote and local environments, nodePlatform
RemoteExecutionEnv.listFiles resolves the path and lists through the
daemon; a daemon without the listFiles feature yields not_supported.
LocalExecutionEnv is NodeExecutionEnv plus listFiles through a daemon on
the same machine, so local and SSH sessions list with the same code.
nodePlatform maps hello's Rust names to Node's (windows -> win32,
macos -> darwin, x86_64 -> x64, aarch64 -> arm64).
2026-10-12 15:22:14 +00:00
Mario Zechner
aa499c2426 feat(env): listFiles operation in the pi-env daemon
Lists a directory tree like fd --hidden --exclude .git without following
symbolic links, with the ignore crate of fd and ripgrep for git's ignore
rules. Records travel as payload (kind byte, relative / path, NUL); the
listing stops at maxEntries, maxBytes or timeoutMs with truncated, and a
cancel stops it between entries. hello reports features: ["listFiles"]
so clients can tell older daemons apart.
2026-10-12 15:22:14 +00:00
Armin Ronacher
74613de659 feat(env): report a terminal's foreground job with ptyForeground
The process group in front of the pty when it is not the program's own (a job its shell runs), with the leader's name on macOS and Linux. Exposed as RemoteTerminal.foreground().
2026-10-11 16:35:08 +00:00
Armin Ronacher
367b2bcf59 feat(env): run programs in pseudo-terminals for interactive terminals
The daemon gets pty, ptyWrite and ptyResize: raw terminal output as events, input in arrival order, SIGWINCH on resize, and the process group killed on cancel or a lost connection. openTerminal() is the client, localDaemon() the daemon for this machine. Unix only; Windows answers ENOTSUP.
2026-10-11 16:35:08 +00:00
Armin Ronacher
75a99721d1 Add [Unreleased] section for next cycle 2026-10-07 23:33:49 +02:00
Armin Ronacher
abe508e1b8 Release v1.1.0 2026-10-07 23:33:33 +02:00
Mario Zechner
70759f48be fix: address actionable npm audit findings 2026-10-07 23:00:59 +02:00
Armin Ronacher
28dcce2ba4 Add [Unreleased] section for next cycle 2026-10-05 21:23:02 +02:00
Armin Ronacher
7c10bd4337 Release v1.0.4 2026-10-05 21:22:49 +02:00
Armin Ronacher
5b31896472 fix(env): login shell test works when the account shell is zsh 2026-10-05 21:17:23 +02:00
Mario Zechner
031b24aa64 feat(env): lazy SSH connections 2026-10-05 20:39:50 +02:00
Mario Zechner
b7dfc049e9 fix(env): upload to Windows as base64 lines through PowerShell's input 2026-10-05 17:32:22 +02:00
Mario Zechner
9a193ff7e2 fix(env): upload to Windows without relying on the end of stdin 2026-10-05 17:32:22 +02:00
Mario Zechner
6fa21f2a37 fix(env): time out stuck ssh calls and daemon starts 2026-10-05 17:32:22 +02:00
Mario Zechner
4bf5a6bc5c fix(env): detect Windows when its shell garbles the POSIX probe 2026-10-05 17:32:22 +02:00
Mario Zechner
97a600395e fix(env): Windows watcher identity from file indexes 2026-10-05 17:32:22 +02:00
Mario Zechner
956e81504c fix(env): ENOTDIR for listing a file on Windows, pinned NDK install, protocol docs 2026-10-05 17:32:22 +02:00
Mario Zechner
ed94330a20 feat(env): hardened SSH bootstrap and release daemon tests 2026-10-05 17:32:22 +02:00
Mario Zechner
46d0ff9365 fix(env): daemon robustness, output windows, native watching and Node read semantics 2026-10-05 17:32:22 +02:00
Mario Zechner
cd60a5b990 fix(durable): watcher coverage, growing reads, output BOM and progress defaults 2026-10-05 17:32:22 +02:00
Mario Zechner
b78e6a9085 feat(env): SSH bootstrap and daemons in the npm package
- connectSsh detects the remote system, deploys the packaged daemon
  verified by SHA-256 before it runs, and connects over ssh with strict
  host keys under a fixed alias; scanHostKey and acceptHostKey
- Windows detection and deployment through PowerShell
- release builds the daemon for every supported remote system and
  ships them in @earendil-works/pi-env's bin/
- end-to-end tests against a disposable sshd
2026-10-05 13:06:08 +02:00
Mario Zechner
ba03e03f2d feat(env): add @earendil-works/pi-env, remote execution environments over SSH
- RemoteExecutionEnv: a Durable ExecutionEnv on another machine whose
  results match NodeExecutionEnv there; Connection to the pi-env daemon
- packages/env/daemon: the Rust daemon for Linux, macOS, Android and
  Windows, following Node and libuv semantics
- env.yml runs Durable's env tests, the daemon's checks and pi-env's
  conformance and differential tests on Linux, macOS and Windows
2026-10-05 12:50:48 +02:00