* Improve mobile terminal streaming performance
Co-authored-by: Orca <help@stably.ai>
* Add mobile clear terminal action
Co-authored-by: Orca <help@stably.ai>
* Fix terminal connection test mock
Co-authored-by: Orca <help@stably.ai>
* WIP: mobile markdown tabs before rebase
Co-authored-by: Orca <help@stably.ai>
* Add mobile markdown editing
Co-authored-by: Orca <help@stably.ai>
* Harden mobile tab and markdown sync
Co-authored-by: Orca <help@stably.ai>
* Fix mobile terminal reconnect loading race
Co-authored-by: Orca <help@stably.ai>
* Polish mobile terminal keyboard behavior
Co-authored-by: Orca <help@stably.ai>
* Simplify mobile markdown editor chrome
Co-authored-by: Orca <help@stably.ai>
* Move mobile markdown actions to top
Co-authored-by: Orca <help@stably.ai>
* Use app modals for markdown discard
Co-authored-by: Orca <help@stably.ai>
* Dismiss keyboard before markdown confirmations
Co-authored-by: Orca <help@stably.ai>
* Add mobile file explorer
Co-authored-by: Orca <help@stably.ai>
* Fix mobile file explorer type narrowing
Co-authored-by: Orca <help@stably.ai>
* Fix mobile files navigation param
Co-authored-by: Orca <help@stably.ai>
* Show mobile files connection wait state
Co-authored-by: Orca <help@stably.ai>
* Preview text files on mobile
Co-authored-by: Orca <help@stably.ai>
* Simplify mobile file previews
Co-authored-by: Orca <help@stably.ai>
* Clarify unavailable mobile file types
Co-authored-by: Orca <help@stably.ai>
* Fix mobile subscription and preview review issues
Co-authored-by: Orca <help@stably.ai>
* Keep fallback terminals visible on mobile
Co-authored-by: Orca <help@stably.ai>
* Keep mobile terminal tap active
Co-authored-by: Orca <help@stably.ai>
* Preserve mobile terminal fallback order
Co-authored-by: Orca <help@stably.ai>
* Fix mobile session tab authority
Co-authored-by: Orca <help@stably.ai>
* Run mobile tests in mobile CI lane
Co-authored-by: Orca <help@stably.ai>
* Bump mobile app version to 0.0.7
Co-authored-by: Orca <help@stably.ai>
* Allow main window IPC wiring size
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(agent-dashboard): persist hook status across Orca restart
Hydrates the hook server's per-pane lastStatusByPaneKey from
userData/agent-hooks/last-status.json before binding the HTTP listener,
mirrors mutations to disk via a 250ms trailing debounce, and flushes
synchronously on stop(). Renderer dismissals fan out a new
agentStatus:drop IPC so the on-disk file evicts the entry and a
relaunch cannot resurrect it. Adds a bounded bootstrap queue in
useIpcEvents so events replayed by setListener() during window creation
are not dropped while App.tsx is still hydrating tabsByWorktree.
Gated on settings.experimentalAgentDashboard. Done, blocked, and quiet
working rows now all survive across restart.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-dashboard): harden hook persistence IPC and gate-off deletion
Address review findings on the retention-restart branch:
- Wrap agentStatus:getSnapshot and agentStatus:drop IPC handlers in
try/catch so a throw cannot surface as an unhandled invoke rejection
(silent startup-hydration failure) or crash main from a fire-and-
forget listener.
- runStatusPersist no longer permanently suppresses gate-off deletion
retries on transient unlink errors (e.g. EPERM); deletedOnDisable
now flips only on success or ENOENT.
- Tighten tests: stale-version-hydrate now asserts the warn message
content; getSnapshot test uses toEqual; drop-handler test rejects
null/{}/[] in addition to the prior bad inputs.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-dashboard): bound on-disk hydrate growth and reject tabId/paneKey drift
- Drop hydrate entries older than 7 days (HYDRATE_MAX_AGE_MS) so stale
rows from worktrees archived weeks ago do not pile up forever. PTY-
teardown eviction handles closed panes; the TTL covers daemon-restored
PTYs that never re-attach and crash-recovery paths.
- Reject hydrate entries whose `tabId` field diverges from the paneKey's
tab segment. Cheap defensive add against future renamer/shape drift.
Doc updated to move TTL out of the follow-ups list (now in scope).
Tests: new "drops hydrate entries older than the TTL cutoff" and "drops
a hydrate entry whose tabId disagrees with the paneKey prefix"; existing
hydrate fixtures now use a `recentTs()` helper instead of fixed 2023
timestamps.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-dashboard): post-review polish on hook status persistence
Apply review-fix corrections on the agent-dashboard restart-persistence
work:
- Split dropStatusEntry from clearPaneState so renderer-driven dismiss
IPC no longer wipes lastPromptByPaneKey/lastToolByPaneKey for a
still-alive pane.
- Validate paneKey shape at the IPC boundary (isValidPaneKey).
- Let getSnapshot errors propagate instead of silently returning [] —
matches the renderer's existing .catch and avoids masking a broken
persistence path.
- Trust main's authoritative timing.stateStartedAt unconditionally on
same-state pings; fall back to existing only when timing is absent.
- Use strict < on the snapshot/live updatedAt guard so two events in
the same millisecond don't drop the second one (a <= guard regressed
two existing slice tests).
- Don't reset snapshotRequestedForReadyWindow in the catch handler;
combined with the per-store-update subscriber it would retry-storm
on persistent IPC failure.
- scheduleStatusPersist now resets the timer on each call (true
trailing-edge debounce) instead of leading-edge throttle.
- Fix doc references that named clearPaneState in dismiss/IPC context
where the implementation uses dropStatusEntry; add type-level JSDoc
on AgentStatusIpcPayload.
109/109 in-scope tests pass.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-dashboard): clean stale on-disk entries during hydrate
- Defensive `lastStatusByPaneKey.clear()` at top of `hydrateLastStatusFromDisk` keeps repeat-start() calls from silently merging prior-session state.
- When sanitize drops entries (drift, TTL, schema), log a single `[agent-hooks] last-status hydrate dropped N entries (kept M)` warn and synchronously rewrite the file. Pre-fix, stale entries stayed on disk until a fresh hook event triggered a debounced write — users who hadn't run an agent in 8+ days would re-drop the same entries every cold boot.
- Prime `lastWrittenJson` from the raw on-disk bytes (instead of re-serializing) when hydration is lossless — robust against future shape drift in `serializeStatusFile`.
- `LAST_STATUS_FILE_VERSION = 2` comment now records why v1 was skipped (in-flight branch shape).
- IPC test mock uses `vi.importActual` for `isValidPaneKey` so it stays in sync with the real validator.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-dashboard): persist acknowledgedAgentsByPaneKey across restart
Without this, agent rows the user already visited come back bold every relaunch now that the rows themselves survive restart (per docs/agent-dashboard-retention-restart.md). Hydrate sanitizes input field-by-field (rejects null/non-object/array, prototype-pollution keys, non-finite/non-positive values) and applies a 7-day TTL paralleling HYDRATE_MAX_AGE_MS in agent-hooks/server.ts so hard-quit/crash paths can't grow the persisted map forever.
Co-authored-by: Orca <help@stably.ai>
* docs(agent-dashboard): drop in-tree retention/restart design doc
Doc was a working artifact for this branch; the rationale lives in commit
history and the comments next to the persistence/hydrate code. Scrubs the
three call-site references that named it.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(agent-hooks): introduce relay wire envelope + connectionId stamping
Adds the shared `agent-hook-relay.ts` module with the `agent.hook` JSON-RPC
notification envelope, the `agent_hook.requestReplay` /
`agent_hook.installPlugins` method names, and the
`ORCA_FEATURE_REMOTE_AGENT_HOOKS` flag helper. Promotes `AgentHookSource` to
`shared/` so the relay can import it without dragging Electron in.
Threads a `connectionId: string | null` field through `AgentHookEventPayload`,
the `agentStatus:set` IPC contract, and the renderer-bound preload listener.
Local hook posts stamp `null`; the relay-forwarded path will stamp from `mux`
identity in a later commit. Renderer uses the stamp for stale-event filtering
when an SSH connection tears down with notifications still in flight.
See docs/design/agent-status-over-ssh.md §1, §5, §8 (commit #1).
Co-authored-by: Orca <help@stably.ai>
* refactor(agent-hooks): extract shared listener; add relay-side adapter
Extracts the listener internals (request parsing, payload normalization,
endpoint-file writing, per-CLI extractors, warn-once Sets, slowloris timer
helper, request size cap, paneKey caches) from `src/main/agent-hooks/server.ts`
into a new transport-agnostic `src/shared/agent-hook-listener.ts`. The shared
module uses only Node builtins (no Electron) so it is safe to import from
`src/relay/`.
Adds `src/relay/agent-hook-server.ts` — a thin HTTP-loopback adapter that
wires the shared listener to a `forward(envelope)` callback so `relay.ts` can
re-emit each parsed payload as an `agent.hook` JSON-RPC notification on the
existing SshChannelMultiplexer. The adapter owns:
- 127.0.0.1:0 socket + bearer-token auth, identical shape to the local server
- per-paneKey last-payload cache + replayCachedPayloadsForPanes() for the
request-driven replay path used after `--connect` reattach (see §5 Path 3)
- clearPaneState(paneKey) for PTY-exit eviction (symmetric with local server)
- buildPtyEnv() / endpoint-file writing for relay-spawned PTYs
Orca's `AgentHookServer` is now a ~200-LoC adapter over the shared listener
that owns the IPC fanout, listener replay, and `ingestRemote(envelope, connId)`
entry point that bypasses the HTTP path for relay-forwarded events.
See docs/design/agent-status-over-ssh.md §3, §8 (commit #2).
Co-authored-by: Orca <help@stably.ai>
* fix(preload): expose connectionId on agentStatus.onSet type
src/preload/index.ts already passes through `connectionId?: string | null`
from main, but the PreloadApi declaration in api-types.ts was missing the
field. Align the type with the runtime contract so renderer call sites
can read connectionId without an `as` cast.
Co-authored-by: Orca <help@stably.ai>
* fix(agent-hooks): harden ingestRemote + relay replay; review-driven cleanup
- ingestRemote: re-run normalizeAgentStatusPayload at trust boundary;
trim+validate connectionId/paneKey/tabId/worktreeId
- relay: preserve source/env/version through replay via sidecar map;
drop sourceFromAgentType fallback that mis-tagged unknown agents
- shared listener: exhaustive switch+never on AgentHookSource dispatch
chains; extractPromptText returns trimmed values; export MAX_PANE_KEY_LEN
- preload: tighten connectionId from optional to required (always sent)
- main IPC: reorder spread so explicit envelope fields win on collision
Co-authored-by: Orca <help@stably.ai>
* chore(docs): drop agent-status-over-ssh design doc from PR
The design RFC was useful for authoring this PR series but doesn't belong
in-tree — keeping it here would freeze line-number references and design
prose against future churn. Folding it into the PR description instead.
Co-authored-by: Orca <help@stably.ai>
* chore(agent-hooks): widen ingestRemote type for env/version (PR2 prep)
Declares `env?: string` and `version?: string` on the `ingestRemote` envelope
parameter so PR2 only needs to add the `warnOnHookEnvOrVersionMismatch`
callsite, not also widen the type. The fields are forwarded verbatim from
the agent CLI POST body on the remote and let Orca's warn-once cross-build
/ dev-vs-prod diagnostics fire identically on remote-sourced events.
Type-only addition; no runtime consumer in this PR.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* fix: address auto-review-fix-multi-agent findings
- Replace local ORCA_WORKTREE_ID_SEPARATOR with shared WORKTREE_ID_SEPARATOR
- Make hydrateLocalPtyRegistryAtBoot idempotent (one-shot per process,
but stays retry-eligible until daemon provider is available)
- Strengthen daemon-pty-adapter strict-parser test to actually exercise
the new short-circuit (test would have passed under the old loose
parser too without the change)
- Add eslint-disable max-lines directive to oversized merge test file
Co-authored-by: Orca <help@stably.ai>
* chore: archive auto-review context to .context/
Co-authored-by: Orca <help@stably.ai>
* fix: address auto-review-fix findings
Drop the destructive reconcileOnStartup call from boot-time PTY registry
hydration: a transient listRepoWorktrees failure (returns [] and only
warns) would otherwise let the reconcile pass kill live local sessions.
The boot path is now read-only against the daemon — listSessions() only.
Also: tighten parsePtySessionId to reject degenerate `::` halves; replace
stale pty.ts:1005 references and a misleading local-unknown comment in
the hydrate module; narrow Store dependency to Pick<Store, 'getRepos'>;
log adapter listSessions failures instead of silently swallowing them;
re-anchor design-doc references on stable symbols and align §1b/§1c/§1d
with the implementation.
Co-authored-by: Orca <help@stably.ai>
* docs(resource-usage): update remote badge spec
Co-authored-by: Orca <help@stably.ai>
* test(resource-usage): cover boot hydration failure modes + warm-reattach e2e
Adds the regression coverage flagged in PR #1667's test plan that wasn't
already locked down.
vitest (`hydrate-local-pty-registry.test.ts`):
- daemon offline at first call → no-op, hasHydrated stays false so a
later macOS dock re-activation can retry.
- listSessions rejection caught and logged, does not throw.
- pid-write ordering: a pre-existing registry entry with pid=12345 is
not clobbered by a stale `pid: null` from listSessions (§1d).
- SSH-gate: a session whose repo has a non-null connectionId stays out
of the registry, mirroring the spawn-time gate in pty.ts.
- Happy-path: a local session is registered with the daemon's pid.
Playwright e2e (`resource-usage-warm-reattach.spec.ts`):
Full quit→relaunch cycle against the same userDataDir; asserts that
on the second launch the snapshot includes the warm-reattached PTY
with a real pid before any pane mount, and that the seeded repo
resolves as local (no connectionId). Mirrors the existing
terminal-restart-persistence pattern.
Co-authored-by: Orca <help@stably.ai>
* fix(test): satisfy Pick<Store, 'getRepos'> in hydrator vitest
CI typecheck failed because FakeStore's getRepos returned objects missing
Repo's required fields (path, displayName, badgeColor, addedAt). Fill with
placeholder values; the hydrator only reads id + connectionId, but the
type signature still has to line up.
Co-authored-by: Orca <help@stably.ai>
* chore(resource-usage): drop bug-doc files; strip dead doc refs from comments
Remove docs/resource-usage-remote-mislabel.md (new in this PR) and revert
docs/resource-usage-merge-spec.md to the PR-base state. Strip the
matching `docs/...md §N` pointers from code/test comments, keeping the
surrounding "why" explanations intact so readers still get the
warm-reattach mislabel context.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(telemetry): instrument on_path:false triage on onboarding_agent_picked
Adds path_source and path_failure_reason to onboarding_agent_picked so the
~30% on_path:false rate on dashboard 1562016 can be split between shell
hydration failures and genuinely-not-on-PATH cases before picking a fix.
See docs/agent-on-path-detection.md.
Co-authored-by: Orca <help@stably.ai>
* fix(telemetry): close PathSource compile-time-sync hole
Add `_PathSourceSync` guard mirroring `_PathFailureReasonSync` so adding
a new `PathSource` value to the alias without updating the schema (or
vice versa) fails the build. Without it, drift would silently drop
`onboarding_agent_picked` at the strict validator. Also replace stale
line-number references in docs/agent-on-path-detection.md with named
function/handler references that survive future edits.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(telemetry): track agent_hook_install_failed per agent
Replaces the closure-style installer loop in `src/main/index.ts` with a
labelled `runManagedHookInstallers` so each catch can attribute the
failure to its agent. Adds the `agent_hook_install_failed` event +
`hookInstallAgentSchema` enum (claude/codex/gemini/cursor) and a unit
test pinning fail-open semantics, label routing, and the 200-char
error_message truncation.
Co-authored-by: Orca <help@stably.ai>
* fix(telemetry): harden agent-hook installer fail-open
- describeError always returns a string (JSON.stringify can return
literal undefined for throw undefined / Symbol / function, which
would crash the catch handler before track fires)
- wrap track() in inner try/catch so a telemetry-side throw can't
abort the installer loop
- dedupe AGENT_HOOK_TARGETS into one tuple in agent-hook-types so
the IPC AgentHookTarget type and hookInstallAgentSchema can't drift
- regression tests for object/undefined throws and track-throws
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Surface worktree creation immediately and reconcile remote base state
asynchronously, emitting drift/conflict events as fetches complete.
Co-authored-by: Orca <help@stably.ai>
* wip
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* WIP: Changes before auto-review fixes
Co-authored-by: Orca <help@stably.ai>
* fix: address auto-review findings (iteration 1)
Co-authored-by: Orca <help@stably.ai>
* fix: address auto-review findings (iteration 2)
Co-authored-by: Orca <help@stably.ai>
* fix: archive review context and improve agent detection on wizard mount
Co-authored-by: Orca <help@stably.ai>
* fix: address CI lint failures and split use-onboarding-flow.ts
Co-authored-by: Orca <help@stably.ai>
* fix: mock ./onboarding in register-core-handlers test
Co-authored-by: Orca <help@stably.ai>
* fix: also toggle light class on documentElement so onboarding e2e theme wait resolves
The onboarding e2e calls waitForFunction(() => classList.contains('dark') || classList.contains('light')) before snapshotting the starting theme. applyDocumentTheme only toggled 'dark', so on a host that resolves system to light the wait timed out (CI Linux headless). Toggle 'light' as the inverse class so consumers can observe the resolved theme symmetrically; Tailwind keys only on 'dark' so styling is unchanged.
Co-authored-by: Orca <help@stably.ai>
* fix: add braces to Landing menu close-on-outside-click handler
oxlint config requires braces for all if statements.
Co-authored-by: Orca <help@stably.ai>
* chore: trigger CI
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
When the new-workspace draft flow targets pi (e.g. user picks a GH issue
in the smart input + selects pi as the agent), the URL was supposed to
land in pi's input box as an editable draft. It didn't, because pi has
no `--prefill` flag and our bracketed-paste-after-ready path waits for
1.5s of stream silence after `\x1b[?2004h` — pi's startup banner
(Skills/Prompts/Extensions/conflicts) prints continuously for 5+ seconds,
which keeps the quiet-timer resetting until the 8s budget expires and
the paste is dropped. Claude works because of `--prefill`; codex works
because its startup is short enough to hit the quiet window.
Fix mirrors Claude's `--prefill` semantics for pi:
- Orca's pi overlay now installs a tiny `orca-prefill.ts` extension
alongside the existing titlebar spinner. On `session_start` it reads
`ORCA_PI_PREFILL` and calls `pi.ui.setEditorText(text)`, then deletes
the env var so subsequent /new sessions don't re-prefill.
- `TuiAgentConfig` gains `draftPromptEnvVar`; pi sets it to
`ORCA_PI_PREFILL`.
- `buildAgentDraftLaunchPlan` returns `{ launchCommand: 'pi', env: {...} }`
for env-var agents; the env is plumbed through `startup.env` into
`pty:spawn` (already supported by pty-connection.ts).
The launched shell command stays a clean `pi` — no `FOO='...' pi` prefix
typed into the terminal — and the readiness race is sidestepped entirely.
Co-authored-by: Orca <help@stably.ai>
* fix(gh-project): diagnose env-shadowed gh tokens in auth errors
`gh auth refresh -s project` silently no-ops when GITHUB_TOKEN/GH_TOKEN
is exported in the user's shell — gh prefers env tokens and refuses to
modify them, exiting 0. Users follow the canned remediation, see no
error, retry, and stay stuck.
Add a one-shot `gh auth status` probe (gh:diagnoseAuth IPC) that:
- Detects env-shadowed credentials and rewrites the fix to `unset
GITHUB_TOKEN` plus a grep to find where it's exported.
- Detects missing gh install, plain missing-scope on a keyring login,
and SAML SSO authorization.
- Surfaces a tailored multi-button error UI in ProjectViewWrapper and
ProjectPicker instead of one canned 'Copy command'.
Co-authored-by: Orca <help@stably.ai>
* fix(gh-project): address review feedback
- Cross-platform shell guidance: PowerShell commands on Windows
(Get-ChildItem Env:, Remove-Item Env:, [Environment]::SetEnvironmentVariable)
via navigator.userAgent platform check.
- Use `window.api.shell.openUrl` for the docs button instead of
`window.open`, matching SidebarToolbar's external-URL pattern.
- Tighten gh auth status parser: accept single-label hostnames and
optional trailing colon; recover host from the inline 'Logged in to
<host>' line so a missed section header never silently drops accounts.
- Add tests for multi-host output and host-recovery fallback.
- Drop dead command/copy locals in ProjectViewWrapper.ErrorState by
short-circuiting the auth-error case before they're computed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>