Commit Graph
12 Commits
Author SHA1 Message Date
Brennan Benson d4cfee76be Add audit-only daemon incarnation evidence (#11606)
* feat(daemon): add audit incarnation evidence

* fix(daemon): isolate audit evidence observers
2026-07-30 16:02:08 -07:00
NeilandOrca aab112933e Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai>
2026-07-23 18:35:31 -07:00
Neil 8f40ddf328 fix(memory): bound OOM-prone accumulators (#10179) 2026-07-23 06:22:56 -07:00
Jinwoo HongandOrca 7adda25b0a fix(daemon): retire empty current-generation daemons (#9277)
* fix(daemon): retire empty current-generation daemons

Co-authored-by: Orca <help@stably.ai>

* fix(daemon): retire empty daemons on disconnect

Co-authored-by: Orca <help@stably.ai>

* test(daemon): authenticate Windows lifecycle harness

Co-authored-by: Orca <help@stably.ai>

* test(daemon): assert remaining shutdown budget

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-07-19 19:38:44 -07:00
NeilandOrca 46646d7ff1 chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules

Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.

Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):

error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol        (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse            (19: copy-then-reverse -> toReversed)

warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type  (aliasing footgun guard)
- typescript/no-unsafe-function-type         (bans bare Function type)
- unicorn/prefer-array-flat-map              (map().flat() -> flatMap())
- unicorn/prefer-regexp-test                 (.match() in bool ctx -> .test())

mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.

Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.

* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse

mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).

Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-29 22:38:29 -07:00
Neil 220d5cb7fe Prevent and recover from degraded terminal daemon spawns
Adds daemon PTY-spawn health classification, POSIX daemon cwd repair before terminal spawn, and degraded-daemon fallback routing so fresh terminals keep opening without killing preserved sessions.
2026-06-29 20:17:46 -07:00
Neil a426f36fcb Fix packaged daemon stale bundle detection 2026-05-31 17:43:02 -07:00
Jinjing 68d42040f4 fix: isolate e2e daemon and onboarding state (#1606) 2026-05-08 17:45:02 -07:00
Jinwoo HongandOrca daaf2aa73d feat(settings): manage sessions panel + pid-recycling daemon guard (#1343)
* fix(daemon): guard killStaleDaemon against pid recycling

The daemon's pid file carried only a bare integer, so killStaleDaemon had
no way to verify the current owner of that pid was still the process it
forked. Unix pids recycle — on macOS the default ceiling is ~2048 and
wraps in minutes under load — so blind SIGTERM/SIGKILL on a recycled pid
could hit an unrelated user process (editor, language server, background
task) silently.

Three moving pieces:

1. Pid file now carries startedAtMs. daemon-spawner exports DaemonPidFile
   and serializeDaemonPidFile; daemon-init fills startedAtMs from
   getProcessStartedAtMs(child.pid) right after the daemon signals ready.
   parseDaemonPidFile tries JSON first and falls back to bare-integer for
   backward compatibility (startedAtMs: null on legacy files).

2. isDaemonProcess takes expected startedAtMs. In addition to the cmdline
   match, it consults getProcessStartedAtMs(pid) and compares with a
   ±1.5s tolerance. Null on either side is fail-open so the guard never
   strengthens existing behavior negatively (Windows, legacy pid files,
   kernel-thread /proc failures).

3. SIGKILL re-check. The SIGTERM-then-wait window is up to 3s — long
   enough for the pid to be recycled if the original daemon dies during
   the wait. Before escalating to SIGKILL, isDaemonProcess runs again;
   on mismatch we log reason=pid_recycled and skip SIGKILL.

Carried forward from #1323 as a standalone safety fix — independent of
that PR's router/drain machinery, which is not shipping.

Co-authored-by: Orca <help@stably.ai>

* test(daemon): cover pid-recycling guard surface in daemon-health

Adds unit coverage for the new Phase 0 surface:

- parseDaemonPidFile: JSON round-trip, JSON without startedAtMs,
  bare-integer fallback for legacy pid files, malformed-input rejection.
- startTimeMatches: null-expected fail-open, null-actual fail-open,
  within-tolerance match, outside-tolerance rejection.
- killStaleDaemon: with a mismatched startedAtMs in the pid file,
  assert that no SIGTERM/SIGKILL is sent even though the liveness probe
  (process.kill(pid, 0)) runs.

startTimeMatches was promoted from module-private to exported so it can
be exercised directly — it's a pure function with no internal state.

Co-authored-by: Orca <help@stably.ai>

* feat(settings): manage sessions panel for daemon staleness UX

Add a Manage Sessions settings panel with list/kill-all/kill-one/restart
backed by a new pty:management IPC surface. Rows are hover-highlighted and
click to reveal the corresponding terminal pane, mirroring the bottom
status-bar sessions popover. Kill-all and restart-daemon are icon buttons
(Trash2, RotateCw) with tooltips so the restart action doesn't collide with
the row-refresh RefreshCw icon.

Co-authored-by: Orca <help@stably.ai>

* fix(settings): honest killAll counts + suppress post-kill spawn toast

killAll now snapshots the initial session IDs and polls listSessions every
100ms for up to 6.5s — past the daemon's 5s SIGTERM→SIGKILL ladder — so
well-behaved shells hosting long-running agents finish their SIGTERM
handlers before we classify them as "refused to exit." Shutdowns fire once
per initial session (no retry spam), and fresh session IDs that appear
mid-poll (renderer remounts) don't inflate remainingCount.

pty-transport's connect() catch now detects the adapter's
TerminalKilledError tombstone rejection ("...was explicitly killed") and
suppresses the red "file an issue" toast. After Kill All, a pane remount
would call pty:spawn on the dead session ID; surfacing the tombstone as
a scary error misrepresented an intentional user action. The pane still
renders "Process exited" via the normal lifecycle.

Co-authored-by: Orca <help@stably.ai>

* feat(daemon): foundation for pty:management IPC surface

Adds the plumbing the Manage Sessions settings panel depends on:

- daemon-init exports getDaemonProvider / replaceDaemonProvider /
  restartDaemon / cleanupDaemonForProtocol so the pty:management
  handlers can access the current provider and coordinate a clean
  restart without importing window-services internals.
- daemon-pty-router exports getAllAdapters so the killAll / listSessions
  handlers can fan across the current adapter plus any legacy-protocol
  adapters still attached for in-flight sessions.
- daemon-pty-adapter gains a listSessions RPC and readonly
  protocolVersion so the handlers can annotate each session with the
  adapter it belongs to and route killOne back to the right adapter.
- types.ts exports DaemonSessionInfo (SessionInfo + protocolVersion)
  as the shared shape the preload API surface mirrors.
- ipc/pty.ts, attach-main-window-services, TerminalPane and
  terminal-search pick up the small bindings required to wire the
  router through existing code paths without regressions.

Co-authored-by: Orca <help@stably.ai>

* fix(settings): remove high-session-count warning banner

The banner nagged at 20 sessions, which is well within normal use for
users with many open worktrees. Count is already visible in the header
bar, and the table supports per-row and bulk kills, so the banner added
noise without actionable value.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-05-02 23:05:44 -07:00
Jinwoo Hong a73b4a6c1f Fix daemon shell-ready wrapper persistence (#1025) 2026-04-24 13:01:27 -07:00
Jinwoo Hong 22ae50e0ae fix(terminal): auto-respawn PTY daemon when it dies mid-session (#881) 2026-04-20 19:49:27 -07:00
Jinwoo Hong fd4f986c59 feat: terminal persistence via out-of-process daemon (#729) 2026-04-17 01:42:41 -04:00