feat(process): persistent-process tier SDK surface (JS/TS)#14
Conversation
Mirror the Rust SDK + host astrid:[email protected] persistent tier. Bump contracts to the #12 commit (1a06cf4, process-package-only — events bundle / contracts.ts untouched, so the open hook PR is unaffected); ComponentizeJS reads the persistent host WIT live from contracts/host/. wit-imports.d.ts: persistent types + free-fn declarations. process.ts: spawnPersistent() + persistent knobs, PersistentProcess class (status/readLogs/readSince/writeStdin/closeStdin/signal/wait/stop/release), process.{attach,listProcesses,statusMany}, logCursorStart(), types. ProcessSignal gains stop/cont. attach is an id-wrapper (reattach without the host's deferred attach fn). Verified: tsc clean; example capsule componentizes (169 host imports).
There was a problem hiding this comment.
Code Review
This pull request adds a persistent-process tier to the SDK, introducing spawnPersistent, PersistentProcess, and associated management functions to support background processes that survive instance resets. The review feedback points out potential RangeError crashes when converting non-finite numbers (like Infinity or NaN) to BigInt in several configuration and timing parameters. To address this, the reviewer suggests implementing a safe conversion helper toSafeBigInt and adding input validation to the wait method.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
Adds a new persistent-process tier to the JS/TS SDK process module, mirroring the host astrid:[email protected] persistent tier and the Rust SDK so capsule authors can manage background processes that outlive pooled instance resets.
Changes:
- Extended the host ABI type surface (
wit-imports.d.ts) with persistent-process types and id-keyed free functions. - Added an ergonomic persistent API layer in
process.ts(spawnPersistent,PersistentProcess,attach,listProcesses,statusMany, cursor-based log reads). - Re-exported new process-related types from the SDK entrypoint and documented the feature in the changelog.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| packages/astrid-sdk/src/wit-imports.d.ts | Adds persistent-tier types and free-function host imports for id-keyed process operations. |
| packages/astrid-sdk/src/process.ts | Implements the JS/TS ergonomic persistent-process layer and request shaping for new WIT fields. |
| packages/astrid-sdk/src/index.ts | Re-exports newly introduced process types for ergonomic consumption. |
| CHANGELOG.md | Documents the new persistent-process tier API surface and behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…16) ## What Adds `capabilities.enumerate()` to `@unicity-astrid/sdk` — the ergonomic wrapper for `astrid:sys/host.enumerate-capabilities` (astrid-runtime/wit#13, merged), mirroring the Rust SDK `capabilities::enumerate`. It is the infallible list dual of `capabilities.check`: returns the calling capsule's own held capability names — the categories declared in its `[capabilities]` manifest block (`host_process`, `net_connect`, `fs_read`, …), not the scoped arguments within them. Argument-free, returns `string[]` with no error path (an empty array is the valid "no capabilities" answer). Hand-adds the ambient `enumerateCapabilities(): string[]` host declaration in `wit-imports.d.ts` and bumps the `contracts` submodule to the merged `9742f80` so `astrid-build`'s `CANONICAL_WIT_DIR` (`contracts/host`) declares the import for downstream componentization. The `astrid:contracts` events bundle is unchanged. ## Stacking Stacked on `feat/process-persistent-sdk` (#14) — base is that branch, so the diff shows only the enumerate delta. Rebase onto `main` and retarget once #14 lands. ## Verify `tsc -b` typecheck green; `capabilities.enumerate` is exported via `export * as capabilities`.
… RangeError BigInt(Infinity) / BigInt(NaN) throws a cryptic RangeError. A capsule author passing Infinity to mean no limit hit that on the persistent-process knobs. Adds a toSafeBigInt helper that maps non-finite input to undefined (= use the host default/ceiling, which already clamps) for the soft knobs — TTLs, resource limits, and stop grace. wait(timeoutMs) is bounded by design, so it validates finiteness and throws a clear SysError.api instead, mirroring net.toHostTimeout.
Regenerates astrid-contracts.wit (and the generated contracts.ts) via scripts/sync-contracts-wit.sh so sync-contracts-wit.sh --check passes. The mirror was missing astrid:hook's hook-event-request record present in contracts/interfaces at the pinned 9742f80. Only the bundle is touched — the contracts submodule pin is untouched.
Summary
Mirrors the Rust SDK and the host
astrid:[email protected]persistent-process tier (host: astrid-runtime/astrid#867; Rust SDK: astrid-runtime/sdk-rust#53; contract: astrid-runtime/wit#12), so JS/TS capsule authors can spawn a background child that outlives the pooled, stateless instance that started it — unlikeprocess.BackgroundProcessHandle, whose kernel resource is reaped on instance reset.Changes
contractssubmodule → the mergedastrid:[email protected]persistent tier (wit commit1a06cf4, the feat(contracts): regenerate bundle with hook-event-request #12 squash). Deliberately the feat(contracts): regenerate bundle with hook-event-request #12-only commit (predates fix(build): resolve SDK runtime + host WIT for registry installs (0.1.1) #11), so theastrid:contractsevents bundle (astrid-contracts.wit/ generatedcontracts.ts) is untouched (sync-contracts-wit.sh --checkstays green) and unrelated open SDK PRs are unaffected. Onmain,astrid-buildreads the host WIT live fromcontracts/host/(CANONICAL_WIT_DIR), so the bump is all ComponentizeJS needs.wit-imports.d.ts(hand-written host ABI types): persistent types + free-function declarations onastrid:process/[email protected]—spawnPersistent,attach,listProcesses,status,statusMany,readLogs,readSince,writeStdin,closeStdin,signal,wait,stop,releaseProcess,watch,unwatch;ProcessInfo,ProcessPhase,LogStream,LogCursor,LogChunk,OverflowPolicy,ResourceLimits;ErrorCodegainsno-such-process/registry-full/persist-unsupported;ProcessSignalgainsstop/cont;SpawnRequestgains the 8 persistent fields.process.tsergonomic layer:spawnPersistent(cmd, args, options)+ the persistentSpawnPersistentOptionsknobs;PersistentProcessclass (status/readLogs(drain) /readSince(non-draining cursor → byte-faithfulLogChunkResult;logCursorStart()) /writeStdin/closeStdin/signal/wait(bounded) /stop(consumes — SIGTERM→grace→SIGKILL) /release); module fnsattach(id)(id-wrapper reattach — works without the host's deferredattachresource fn),listProcesses,statusMany; ergonomic types.buildSpawnRequestupdated for the newSpawnRequestfields (required).index.ts: top-level re-exports for the new public types.Not exposed (host-deferred)
watch/unwatch(open publish-authority RFC question — poll viastatus+ boundedwait) and resource-limit enforcement (limitsis plumbed through but the host doesn't enforce it yet).Test Plan
npm run build(tsc) green across the workspaceexamples/test-capsulecomponentizes against the persistent WIT — ComponentizeJS produced a 12.96 MB WASM with 169 host imports, proving the new host functions wire through the JS build pathscripts/sync-contracts-wit.sh --checkpasses (events bundle untouched)contracts.tsdrift