* fix(pid): treat EPERM as alive in isPidAlive
Match isPidDefinitelyDead: process.kill(pid, 0) throwing EPERM means the
PID exists but cannot be signaled, so it should not look dead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(pid): check Linux zombies after EPERM existence probe
EPERM means the PID exists but cannot be signaled; still run the zombie check so Linux zombies are not reported as alive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(pid): isolate EPERM liveness probe
Co-authored-by: stantheman0128 <stanshih888@gmail.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
A plugin manifest configSchema declaring a __proto__ property polluted
Object.prototype process-wide during ordinary plugin config validation.
applyObjectPropertyDefaults iterated schema-controlled property keys and
wrote into value[key] with no blocked-key guard; because manifests are
parsed JSON, __proto__ is an own key and value["__proto__"] dereferences
the inherited Object.prototype, onto which nested defaults were written.
Skip __proto__, prototype, and constructor at each write site in
applyObjectPropertyDefaults, reusing the existing isBlockedObjectKey
helper already used at other untrusted object-write boundaries.
The talk.speak synthesis path passed raw agent text to providers, so talk
mode could read fenced code, links, and tables aloud. Speech normalization
now happens once at the shared synthesis boundary (speech-mode stripMarkdown:
label-only links, decorative bullet/glyph cleanup, punctuation collapsing).
Code-heavy replies (>=50% fenced content, CommonMark-aware scanner incl.
blockquote/list containers and unclosed fences) get routed by surface:
talk.speak speaks a short on-screen fallback line, channel auto-TTS skips
the voice note since the visible text already carries the content, and
explicit conversions (tts.convert, tagged hidden TTS, chat Listen) always
speak stripped best-effort content.
* fix(shared): reject HTTP status codes outside the 100-599 range
extractHttpStatusMatch only checked Number.isFinite, so 3-digit
sequences such as "000", "099", "600", and "999" were accepted as
valid HTTP statuses. Downstream code then surfaced them as "HTTP 0"
in user-visible messages and fed them into retry/classification
branches (e.g. isCloudflareOrHtmlErrorPage checks `code < 500` and
`code < 600`). Restrict the extracted code to the IANA HTTP status
code range (100-599).
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
* fix(shared): validate all parsed HTTP status prefixes
---------
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: recognize provider-wrapped HTTP status for session retry
* fix(llm): scope wrapped status parsing to retry
Preserve the leading-status contract for rate-window and formatting callers while letting retry classification recognize built-in provider wrappers.
Co-authored-by: wuqingxuan <wu.qingxuan@xydigit.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(deadcode): trim auto-reply and CLI exports
* refactor(deadcode): trim cron and task exports
* refactor(deadcode): trim fleet and process exports
* test(deadcode): exercise live task and process seams
* test(fleet): cover stream redaction through owner module
* refactor(security): trim dead internal exports
* refactor(secrets): trim dead internal exports
* refactor(deadcode): trim remaining src exports
* refactor(deadcode): remove test-only runtime exports
* refactor(deadcode): trim pairing test exports
* refactor(deadcode): reconcile refreshed baseline
* test(auto-reply): deduplicate queue state imports
* feat(nodes): route alerts by active computer
* fix(ci): allowlist node presence mobile coverage
* test(prompts): refresh node presence snapshots
* fix(macos): await presence reporter actor hop
* fix(macos): type presence test delivery state
* docs(nodes): add active presence guide
* docs(nodes): format presence troubleshooting
* fix(core): make indexed access explicit in auto-reply, infra, and config
Part 1/3 of the src NUIA phase-3b burn-down (#104600): iteration and
destructuring over index reads, boundary guards on parsed input, and
named invariants. Config path walkers bind the path head once; SQLite
migration key handling is hoisted without query-shape changes.
* fix(core): make indexed access explicit in cli, gateway, commands, security, shared
Part 2/3: argv/token selection restructured, gateway event/attachment
invariants named, security parsers stay fail-closed (invariant
violations throw), edit-distance matrices access checked entries.
* fix(core): make indexed access explicit across remaining src surfaces
Part 3/3: channels, plugins, process, cron, plugin-sdk, media, logging,
tui, hooks, daemon, and small directories. Latent bug fixed: a tailnet
resolver could leak undefined through a string|null contract and now
fails with a descriptive local error.
* fix(core): keep optional boundaries optional after per-commit review
Review findings: expectDefined misused where absence is a legitimate
state. CLI --profile/route-args missing next tokens take their existing
miss paths; help normalization compares --help against the last
positional again; first-time plugin install spreads absent cfg.plugins;
denylist scan iterates manifest dependency entries instead of throwing
on omitted sections; tailnet resolver returns a guaranteed string at
the source instead of a caller-side undefined throw.
* refactor(core): closed-key provider labels and honest optional passthroughs
PROVIDER_LABELS becomes a satisfies-typed closed record (static reads
provably defined; dynamic lookups go through providerUsageLabel with
honest string|undefined). Status-scan overview passes its optional
params through unchanged instead of asserting them.
* fix(channels): make getChatChannelMeta honestly optional
The original signature claimed ChatChannelMeta while leaking undefined
on bundled channel id metadata drift; three of four callers already
handled absence. The return type now says so, and the one assuming
caller falls back to the raw channel label.
* fix(core): index-safety for post-rebase main drift
Covers the sqlite-sessions flip and auth-source-plan code that landed
mid-phase, plus the channel-validation test consuming the now honestly
optional getChatChannelMeta.
* refactor(channels): split chat-meta accessors along the SDK contract
getChatChannelMeta keeps its shipped plugin-SDK signature (defined for
bundled ids, fail-loud on impossible misses); new findChatChannelMeta
carries the drift-tolerant optional contract for core auto-enable and
formatting paths.
* fix(qa-channel): own channel metadata instead of a guaranteed-undefined catalog lookup
qa-channel spread getChatChannelMeta over an id that is never in the
bundled catalog, shipping an empty setup meta by accident; the fail-loud
SDK accessor exposed it. The channel now declares its metadata once.
* fix(gateway): heartbeat projection lookahead is optional at the transcript tail
expectDefined wrapped messages[i + 1] whose absence on the final message
is the normal case; the adjacent ternary already handled it. Restores
the plain optional read with an explicit guard in the pair condition.
* fix(plugin-sdk): channel plugin factory tolerates non-bundled channel ids again
createChannelPluginBase spreads bundled catalog meta for ANY channel id,
where absence is the normal case for external plugins; the resolver is
honestly optional again while the exported bundled-id accessor keeps the
fail-loud contract.
* fix(core): spreads of optional config sections stay optional
Fresh-setup and first-install paths (crestodian setup inference, hook
installs, agent config base, target agent models) legitimately lack the
section being rebuilt; spreading undefined is the shipped {} semantics.
Removes the remaining gratuitous assertion wraps found by tree audit.