Repository navigation
Conversation
…lyai#19783) * fix(orchestration): scope @ group addresses to the sender's Run `@all`, `@idle`, and the agent-name groups (`@claude`, `@codex`, ...) resolved against every terminal on the host. A coordinator meaning "my three reviewers" reached 126 agents across every open project, twice in one day, and every unrelated agent burned a turn discarding mail that was never for it. Every group except `@worktree:<id>` now means the live Dispatches of the sender's own Run, each addressed as `dispatch:<id>` so delivery is durable even when the worker terminal is not attached yet. A sender bound to no Run is refused with `invalid_argument` naming `run:<id>` / `dispatch:<id>`; there is no host-wide fallback and the host's terminals are never enumerated for it. `@idle` and the agent-name groups filter within that set by the same terminal status and host-resolved identity as before. `ask --to @group` returns the same code and points at the owning Run mailbox. Federated Dispatches read relayed control mail rather than a local mailbox, so a Run-scoped fan-out skips them with a `recipient_unreachable` warning naming the direct `dispatch:<id>` address. Group addresses are resolved host-side, so no RPC or stream shape changes; an older CLI sending `@all` to a new host gets the Run-scoped meaning. Claude-Session: run-scoped-group-addresses * fix(orchestration): revalidate legacy takeover before the recipient verdict A legacy coordinator taken over while `listTerminals` was in flight reported `runtime_error` instead of `legacy_read_only`: Run scoping made "no live Dispatch in this Run" the first thing the group send could fail on, and that threw before the takeover check ran. Takeover is a precondition, not a commit-time detail — the sender must be told it is read-only whatever else is wrong with its recipient set. Revalidation moves to immediately after the only `await` in the path. Everything below it is synchronous, so the commit-time window it used to guard is unchanged; only the error paths now see it. The legacy partition test gave `term_current_worker` no Dispatch, so under Run scoping it is correctly not a recipient. It now holds a real current-contract Dispatch in the same adopted Run, which is what the test is named for: one `legacy_direct` and one `current_delivery` recipient in one fan-out. Claude-Session: run-scoped-group-addresses * fix(orchestration): address the Run a nested coordinator created, not its parent A nested coordinator is both a worker of its parent Run and the coordinator of the Run it created. `resolveMessageRun` answers with the parent, correctly, because that is where its own `worker_done` belongs — but audience is a different question. Scoping `@all` to that Run sent a nested coordinator's "shared context" to the siblings it was started beside instead of the workers it started, and reported success, so it never learned its sub-workers heard nothing. Before Run scoping the host-wide fan-out reached the sub-workers by accident; this turned an over-broad delivery into a wrong-audience one, the exact failure class the change exists to remove. Group audience now resolves off the Run the sender coordinates, falling back to its Dispatch's Run. A leaf worker coordinates nothing and is unaffected. This is a separate question from `routing.run`, not a second answer to the same one, so `resolveMessageRun` keeps its meaning for point-to-point mail. Also: when every live Dispatch in a Run is federated, the fan-out skipped them all and threw a bare `Error` that discarded the warnings naming those remote workers and how to address each one. The sender was told "no recipients" while three remote workers existed. That throw now carries a code and the skip explanations. Claude-Session: run-scoped-group-addresses * docs(orchestration): say that no group address reaches a coordinator A coordinator is not a Dispatch, so Run-scoped groups never include one. That follows from the rule, but nothing said it, and the old host-wide meaning did include the coordinator — a worker sending `@all` to raise a blocker would be heard by its siblings and by nobody who can act. The guide, the CLI note, and the docs page now say to use `run:<id>` for that, and that a worker which created its own Run addresses that Run's workers. Also restores the `@cursor` case dropped when the group tests moved: a Claude pane titled "Fix the text cursor blink" must not receive Cursor's mail. That hazard was recorded from real titles and `@droid` alone did not cover it. Claude-Session: run-scoped-group-addresses * fix(orchestration): preserve group audience and mailbox identity * fix(orchestration): validate group scope before dispatch routing * fix(orchestration): preserve pane identity and exclude coordinator dispatches
stablyai#19705) * fix(codex): reserve the label's share of a qualified command row A child's label is raw provider text and was spliced into the command row unbounded, then the pair clipped to the description cap. A label at or past that cap clipped the command away entirely, leaving a row of kind 'command' that named an agent and showed no command - the failure qualification exists to remove, inverted. The same clip could also cut a surrogate pair, which boundSubagentField already guards against on the agent row two lines away. Give the label a reserved share and clip it the way the agent row does. * feat(native-chat): show live background work and name each row by kind The strip suppressed itself in three places: the Claude tracker blanked its roster for the whole of any turn, the Codex tracker returned nothing while a primary turn was open, and the renderer view gated on `turnId === null`. Between them, work in flight was never shown — and a task backgrounded in an earlier turn vanished from the strip as soon as the next prompt was sent. Claude additionally dropped every foreground subagent, so a fan-out reported nothing at all. Report work while it is live, in all three layers. Foreground Claude work is turn-scoped, so `result` retires it — that is the provider's own outcome for a task it marked foreground, not a roster sweep. Nothing settles a Codex child on turn end: those keep reporting well past their parent, so turn frames only prompt a republish. Name each ROW by kind — Subagent, Shell command, Workflow, Monitor — instead of a generic "Background <kind>", each drawing the glyph the shared tool-icon table already uses for that category. A row that carries a provider description still shows it unchanged. The collapsed header summary is deliberately untouched; it is owned elsewhere. The conversation-command gate is unchanged in effect: an open turn already refuses first, and Claude foreground work never reaches the backgrounded set the gate reads. * fix(native-chat): withhold the row stop Claude foreground work cannot honour The strip now publishes foreground rows, but `stoppableTaskIds` still filters on `backgrounded`, so `stopClaudeBackgroundTasks` resolved an empty target list and returned `{ cancelled: false }` that no renderer reads: the user clicked "Stop Subagent" and nothing ever happened. Carry stoppability per row instead of widening the stop to a target the SDK has no way to reach. `AgentSessionBackgroundTask.stoppable` is absent-means-yes, so hosts that predate it keep their working control, Claude emits `false` only on foreground rows, and the strip hides that row's button the same way it already hides the stop-all a provider cannot honour. * fix(claude): scope aggregate-roster authority to the work it enumerates `background_tasks_changed` lists BACKGROUNDED tasks, so a foreground subagent can never appear in it. Treating it as the whole world meant any such frame cleared every live foreground row mid-flight and then dropped every later foreground `task_started` for the rest of the session, killing the in-turn fan-out the strip exists to show in any session that ever backgrounds anything. Decide `backgrounded` before the staleness guard and apply the guard only to a backgrounded start, and retain live foreground entries across a roster replace. Retained rows count against MAX_TRACKED_TASKS, so the map stays bounded, and a stale backgrounded start the roster no longer lists is still dropped. * test(native-chat): pin the strip's monitor amber to the constant that defines it `MONITOR_GLYPH_COLOR`'s comment claimed a test held it and AgentStateDot's amber together, but no test imported it — the assertions hardcoded 'text-yellow-500', so the two could drift with every test still green. Read the colour from the module, which is what the comment always said was happening. Drop the unused `BackgroundTaskGlyph` export too: nothing outside the module names it. * fix(native-chat): keep the task list open across a gap in live work The strip is now mounted on live work, so a sequential fan-out unmounts it between one subagent finishing and the next starting: local `useState` meant the expanded list collapsed itself on every such gap, on top of the strip flickering above the composer. Hand the disclosure to the session, keyed by session id so it does not leak across a session switch. The strip is now controlled and holds no state of its own, which is what makes it survive its own mount churn. * fix(codex): route every command-row cut through one surrogate-safe clip `boundLabel` avoided splitting a pair, then `qualifiedDescription` re-cut the COMPOSED string with a raw slice: label (<=96) plus separator plus description (<=512) is up to 611 chars, so that second cut landed at an arbitrary index inside the description and could publish a lone high surrogate — lossy through any non-JSON UTF-8 hop. `parse` had the identical hazard on an unqualified primary-thread command. One `boundText` helper now owns all three cuts, so no path in the file can emit a lone surrogate from well-formed input. * fix(claude): keep terminal evidence for ids an aggregate roster never lists Narrowing the admission guard to backgrounded starts left a finished FOREGROUND id with no defence: `replaceAggregateRoster` wiped `terminalTaskIds` wholesale, so after any `background_tasks_changed` a replayed `task_started` revived a task whose completion had already been seen — and only a later `result` could settle it again. Scope the wipe the same way the guard was scoped: delete only the ids the incoming roster actually enumerates. A roster still overrules terminal evidence for the work it lists, which is what that behaviour was added for. * fix(claude): keep retained rows in place and evict the stalest, not the newest Re-adding retained foreground entries after the roster made a live row the user is reading jump below the backgrounded rows on every `background_tasks_changed`, and the cap `break` kept the STALEST retained rows while dropping the newest. Merge in the tracked map's own order so a surviving row holds its position, and count the overflow up front so eviction takes the oldest retained rows. Roster entries are never starved and the map stays bounded either way. * fix(claude): retire leftover foreground rows when the next turn starts A foreground `task_started` arriving with no turn open has no `result` coming to retire it, so it sat in the strip indefinitely — with no per-row stop, since foreground rows are not stoppable — and refused conversation commands behind an instruction nobody could follow. Settle on turn start as well as on `result`. This is cleanup only: visibility never consults `startsTurn`, so a missed one degrades to today's behaviour and can never switch the feature off. It shortens the row's life to the next turn; the case where no further turn is ever sent is filed separately. * fix(agent-session): withhold unstoppable rows from readers that predate them Rule 3 of remote-wire-compatibility: changing what the host publishes reaches old clients with no wire change. The Claude host published no foreground rows before this feature; it does now, and a client that cannot read `stoppable` draws a per-row Stop on every one of them — Claude always sets `supportsTaskStop` — which filters to the backgrounded ids, stops nothing, and returns a result no renderer inspects. That is the dead button `stoppable` was added to remove, reappearing across a version skew. Negotiate it. A client can advertise the existing background-task-stop capability and still predate `stoppable`, so this needs its own constant. Readers that do not advertise it get unstoppable rows dropped, and a state whose every row is dropped becomes no strip — exactly their pre-feature view. RUNTIME_PROTOCOL_VERSION is not bumped: this adds an optional field and a new negotiated capability, and changes no existing field's meaning, which is the explicit do-not-bump case in protocol-version.ts. * test(agent-session): name the projected rows so the fixture typechecks An indexed lookup into the fixture's task list is possibly-undefined under `pnpm tc`; the rows are more readable named anyway. * test(web): advertise the row-stop capability in the e2ee auth expectation The web e2ee handshake started sending AGENT_SESSION_BACKGROUND_TASK_ROW_STOP_CAPABILITY, and this test asserts the advertised list by deep equality, so it went red on CI while every targeted test run stayed green. Add the capability in the position the router sends it. * test(claude): pin why the roster empties mid-turn in a sequential fan-out The strip unmounting between two sequential subagents is truthful, not a swept row: A leaves on the provider's own terminal frame, B does not exist yet, and backgrounded work spanning the same gap holds the roster open — so an empty roster is never work the strip is hiding. Also pins the previous-turn rule against the one the subagent roster already applies on the same frame: a still-working FOREGROUND child becomes `unverifiable` there and a backgrounded one is left alone, so the strip drops the first and keeps the second rather than asserting `live` for either. --------- Co-authored-by: Merge Sim <merge-sim@users.noreply.github.com> Co-authored-by: Merge Sim <sim@local>
… CLI agents already do (stablyai#19807) * feat(sidebar): indent native-chat subagents under their session row Stacked on stablyai#19311, which adds the background-task channel this reads. The bridge maps agent-kind background tasks into AgentStatusEntry.subagents, and the renderer status feed confirms per connection so a reconnect cannot leave a child asserting live from a stream that ended. * fix(sidebar): avoid completed age for unverifiable subagents * fix(sidebar): preserve unverifiable child verdicts --------- Co-authored-by: Merge Sim <sim@local>
…i#19868) * feat(native-chat): focus the message box when a chat appears Opening a native chat left focus nowhere, so you had to click the composer before typing. Nothing in the chat surface focused it on open; the only existing focus calls were reactive (typing on the bridge pane background, picker acceptance, attachments, dictation), and the structured pane had none of those. useNativeChatComposerRevealFocus focuses the composer on the reveal edge, covering a new chat tab, a worktree-create landing in chat, the chat-view toggle, and switching back to an existing chat tab. Mount is the wrong signal: retained panes hide with display:none + inert and never unmount on a tab switch. It reuses the existing composer handle and shouldPreserveEditableFocus rather than adding a parallel path, and retries across a bounded run of frames because Tiptap publishes its adapter after mount and Radix restores a closing dialog's trigger in a setTimeout(0). Two supporting changes: - isFocusedGroup, from activeGroupIdByWorktree. On worktree activation both columns of a split flip visible in the same commit, so without it two revealed chats fight over the caret. The bridge route already had this bit as controller.isActive; only the structured overlay needed it. - focusRuntimeTerminalSurface bails on a chat-covered pane. Its DOM-path twin already declines chat view via data-terminal-chat-view, but the runtime path focused the covered xterm unconditionally and pulled the caret out of the composer. Returns true, not false: false sends the caller to the DOM fallback, which for a structured tab id focuses an unrelated tab's xterm. * fix(native-chat): preserve reveal focus ownership --------- Co-authored-by: Merge Sim <sim@local>
stablyai#19915) * fix(relay): keep failed rehome polls out of the durable failure budget The regional rehome worker polls claimRegionalRehome about once a second. Any error thrown before an attempt was claimed - in practice a director pool timeout on the pre-claim control read, 52-74 a day against a pool of 3 - was charged to relay_region_rehome_worker_state.consecutive_failures, which durably disables the control at three. That counter only ever resets on a drain receipt, so while the control is disabled it never resets: production sits at 1068 and still climbing. Enabling the control leaves the stale counter in place, so the next pool timeout latches it straight back off. That is what ended the 2026-08-28 enable after ten minutes. - A poll that never claimed an attempt drained nothing, so it no longer feeds the dispatch-failure budget and logs .._poll_failed instead of .._dispatch_failed. recordRegionalRehomeWorkerFailure had no other caller and is removed. - Enabling the control clears consecutive_failures and paused_until, so a budget spent under a previous enable cannot kill a fresh one. The dispatch interval in next_dispatch_at is deliberately left alone. - The budget's auto-disable now emits orca_relay_regional_rehome_failure_budget_disabled, matching the existing .._safety_disabled precedent. It wrote no event before, which is why this went unnoticed for two weeks. No change to region selection, the candidate query, or host eligibility. * fix(relay): serialize rehome failure accounting with control updates
…g it (stablyai#19911) When a PTY retires mid-delivery, every staged message was marked undelivered, which made all of them redeliverable. That is right for a pointer whose Enter never fired, but an Enter that was already written may have landed: redelivering it types the same mail into the pane a second time. The Enter timer is cleared at the top of retirement, so a RESERVED or WRITE_ATTEMPTED pointer provably never submitted and is released. An ENTER_ATTEMPTED pointer is ambiguous and now stays at its phase for the resume path to revalidate, matching the policy mailbox-pointer-submit.ts already documents for an unverifiable settlement. Co-authored-by: Merge Sim <sim@local>
This reverts commit 68e207c.
Co-authored-by: Merge Sim <sim@local>
… survive (stablyai#19695) * Make the structured turn lifecycle row durable so completed durations survive A structured-chat turn used to end by tombstoning its running lifecycle item, which threw away the only durable record of when the turn ended. Completed "Worked for" labels therefore depended on the renderer having observed the turn finish, and vanished on reopen. The lifecycle item is now revised in place, never tombstoned: - running, with startedAt, at the provider's turn start - completed or interrupted, with completedAt, at the provider's terminal frame, a user stop, or a child exit the host observed - unverifiable, with no end, when a cold acquire finds a running row from a generation whose exit nobody observed Both timestamps are the execution host's clock at receipt, captured before the deferred sink, so the completed value is identical on every client and needs no client clock. Codex history restore uses the provider's own second-granular endpoints for turns that predate this change. Desktop and mobile read settled durations off the journal through one shared selector, and anchor the live counter on the host start with the client's local receipt so a skewed client clock never leaks into the label. Locally observed durations remain the fallback for hosts that still tombstone. Timestamps live inside the existing turnLifecycle field, which old clients strip, and every working-state consumer keys on state === 'running', so no capability negotiation is needed. * native-chat: avoid stale working status on settled turns * test: align settled turn status expectations * Name settled lifecycle rows by their terminal state An interrupted or unverifiable turn must not read as completed for any consumer that renders status text raw. One shared helper builds the text for both providers from the lifecycle state. * test: deduplicate turn lifecycle suites Each behavior keeps one test; duplicated harnesses and restated cases go. * Key lifecycle rows to their user item and record the provider's measured duration A lifecycle row now names the user item that opened the turn by its provider key, so clients attribute timing explicitly and fall back to journal order only for rows from older hosts. A provider-initiated turn with no prompt can no longer claim the previous prompt's duration. When the provider measures the turn itself (Codex turn.durationMs, Claude result.duration_ms) the terminal row records it and clients prefer it over the host interval, so a turn shows the same number live and after a history restore. Host receipt times remain the live-counter anchor and the fallback. * Record a turn as a first-class journal item The turn record is now its own item kind rather than a status row carrying a lifecycle field: no text to misuse, and the fold matches the durable turn record other systems keep. Rows that carry it are stamped journal schema v3; every other row stays v2, so an older host keeps reading them and latches read-only at the first v3 row instead of truncating the epoch. Clients that predate the item would paint an unknown kind as a text bubble, so the host publishes the legacy status form to any client that does not advertise agent-session.turn-item.v1, through the same per-client seam background tasks use. The downgrade is transitional and goes once no supported release lacks the capability. The shared projection now renders unknown item kinds as nothing, so later kinds need no gate. One shared reader handles both forms for old journals and old hosts. * Preserve observed turn end across settlement retries * Retain turn attribution for loaded chat history * Preserve Codex exit receipt across close retries * Register completed turn duration reliability gate * Keep earlier turns through a Codex rewind and count a mid-turn attach from the real start Findings from an independent adversarial review of the typed turn record: - A Codex rewind adopted the provider's item list as the new epoch, and the provider never returns the host's own turn rows, so every duration before the rewind point vanished. The host's turn rows are now spliced back beside the item each followed, and recovery no longer expects the provider to prove rows it never owned. - The epoch row was stamped with the current schema version, so an older host latched read-only at row 1 of every new session, defeating the mixed version design. It carries no body and stays at v2; a stored-row test now reads SQLite directly, because the reader upcasts every row on read. - A send Codex folds into a running turn shares the opening prompt's provider key, and the alias map credited the duration to the later prompt. The earliest submission naming a key now wins. - The live counter anchored on first sight, so a client attaching mid-turn counted from zero. Published frames now carry the host's clock, the reducer keeps the last sample with its local receipt time, and both clients anchor on how long the host says the turn has run. * Correct turn duration gate assertion reference * Respect authoritative unknown native chat duration * Preserve unverifiable timing across older host upgrade * Record final completed turn duration reliability evidence * Fix the CI failures the merge left behind - A merged import list named the same module twice, which the native code quality plugin fails on. - A running turn is now reported by the host with no duration, so the settled map carries an explicit null for it; the hook test still expected the entry to be absent. - main gave the older-page action a cursor with a head-trim guard, so the retention test's epoch-only action no longer typechecks; it now passes an unbounded sequence, which is what the old shape meant. - The roster comparator moved into the extracted module, leaving its import unused in the reducer. * Split two files back under the line cap after the merge Merging main put both one effective line over 300, and the cap forbids a disable or a shave. The wire module's refusal vocabulary moves to its own file and is re-exported, so its consumers are untouched; the host's four thin mutation delegates move next to the functions they call. * Advertise the turn-item capability on every client transport Local IPC and mobile advertised it; the remote and web transports did not, so a desktop paired to a remote host, the CLI, and web silently ran on the legacy carrier forever and the canonical row was never exercised there. The renderer that paints it is the same build on every transport. * Update the web auth-frame expectation for the new capability --------- Co-authored-by: Merge Sim <sim@local>
…structured chat (stablyai#19681) * wip(native-chat): first-pass draft routing into structured chat (to be reworked) * refactor(native-chat): gather agent launch route inputs in one builder Every launch entrypoint assembled the route resolver's inputs by hand and they disagreed: only three of seven passed the project runtime blocker, so a WSL-pinned project was refused structured chat from the tab bar but admitted from the create dialogs. buildAgentLaunchRouteInput is now the one place that gathers host, capabilities, workspace kind, project runtime and TUI customization, and works for workspaces that do not exist yet. Also deletes the dead draft-prompt blocker from the shared resolver; the renderer stopped passing it and the main process never did. * refactor(native-chat): share one structured launch settle loop Five entrypoints copied the same loop around startStructuredAgentLaunch: start, claim a refusal fallback, await, branch on refusal or unknown. The copies drifted: direct work-item and full create reported an unexpected launch error as success, and resume handled neither refusal nor unknown. settleStructuredAgentLaunch now owns that loop and returns one settlement (structured, refused-then-legacy, cancelled, visibility-unknown, failed). Direct work-item, full create, folder workspace, both onboarding folder paths and vault resume consume it; each keeps only its own legacy fallback. Resume deliberately has no fallback. Unknown outcomes release the caller uniformly so a stale fallback closure cannot fire on a later reconcile. * refactor(native-chat): route the new-tab launcher through the shared settle loop The new-tab launcher fired its refusal fallback and forgot it: nobody learned whether the terminal fallback ran, and a visibility-unknown outcome was never surfaced. Its structured branch now runs through settleStructuredAgentLaunch with the terminal launch as the legacy fallback. launchAgentInNewTab stays synchronous; the result gains a structuredSettlement promise, and promptDeliveryResult keeps following the terminal fallback's delivery on refusal as it did through the callers bridge before. * refactor(native-chat): one legacy prompt delivery path and one trust preflight The direct work-item flow kept its own seed-and-paste copy of the legacy prompt delivery; it now uses deliverLaunchPromptToAgentTab with its own timeout notice supplied as a callback. Three private copies of the trust preflight (session continuation, worktree creation, folder workspace) fold onto preflightAgentTrust. The direct work-item pre-launch mark keeps its own entry because it differs in timing, not mechanism. * refactor(native-chat): run quick create through the shared settle loop Quick create was the last entrypoint driving the launch handle itself, because its cancel lifecycle is real: when the creation is abandoned the structured launch must be cancelled immediately so a staged prompt never reaches the provider. The shared loop now takes a cancellation hook with an eager subscription plus a post-await check; it cancels the launch once, unsubscribes on settle, and reports cancelled without running the fallback. Quick create keeps its two-branch legacy fallback and retire-on-late-cancel. Also updates the surface-caller census for the onboarding launch module that step 2 introduced. * fix(native-chat): open editable drafts in structured chat for eligible local Codex launches Route order asked the default-view-mode question first, and that decider applies the terminal mirror gate (a TUI cannot clear more than forty lines of prefilled draft), so a PR body over forty lines reached the plain terminal before structured eligibility was checked. Structured eligibility now comes first; the mirror gate applies only on the legacy branch. The structured draft seed writes the launch-draft store directly with no mirror gate, since a structured session has no terminal copy to fall back on. Closing a settled structured tab clears an unadopted seed. The structured session treats idle and loading as unsettled so the adoption hook takes its baseline from the loaded transcript. Each caller passes one delivery-mode value to both the route builder and the settle loop. The structured session component test is split with a shared harness so it stays under the test file line cap. * test(native-chat): make the structured session test harness type-portable * fix(native-chat): close review gaps in the shared launch settle loop - Claim a refusal fallback only when the caller supplies one, so vault resume no longer reports a terminal fallback it never opened. - A failed or cancelled direct work-item launch returns no tab id, so the caller never pastes the prompt into a setup shell. - Terminal fork activates with providesInitialSurface for structured launches and gates its toast on the settlement; the draft blocker deletion made fork route structured too. - A failed launch clears its draft seed. The failure toast moves to its own module to keep the launch-state file under the line cap. - Ratchet for settle-loop callers; cancel-during-fallback documented. - Restore the local agent label lookup that the pane-agent identity inventory expects instead of the inventoried helper. * fix(native-chat): resolve the agent label through one module * fix(terminal-pane): keep the fork dialog from reopening a created worktree A failed or unknown structured settlement returned false after the fork worktree already existed, so the dialog stayed open and a second click created another worktree. Unknown now closes the dialog (the launch badge already reports it); failed copies the context the way a null launch does. * chore: restore pnpm-lock.yaml to main (local pnpm rewrite slipped into a commit) * test(native-chat): stop asserting the deleted draft feasibility input The routing-authority test expected the shared predicate to receive isDraftPrompt; delivery mode is prompt metadata and never reaches feasibility now, so assert its absence instead. * refactor(native-chat): decide every agent launch route in one planner The route was still resolved at seven callers, each also calling the settle loop; two census tests only stopped an eighth. planAgentSessionLaunch is now the one production caller of the resolver and its launch() the one caller of the settle loop, and both censuses pin exactly that file. The funnel is two-phase because three sites need the route before the workspace exists and quick create persists its request for recovery: a plan exposes route before creation and launches with the created worktree id; a persisted quick-create request carries the verdict as data and re-enters through adoptAgentSessionLaunchVerdict without re-resolving. Delivery mode is fixed on the request once, so route and launch cannot disagree. * test(native-chat): pin the two adopters of a planned launch verdict * fix(native-chat): answer route readability from the repo when the worktree row is absent The planner's transcript-readability input dropped the repo-level connection fallback the direct work-item path still computes for its startup payload, so a route planned in the window right after workspace creation saw `undefined` — which reads as "not locally readable" — and downgraded grok/omp launches from native chat to a raw terminal. Only `undefined` ("cannot determine the host") now defers to the repo; a resolved `null` stays the local answer. * refactor(native-chat): answer structured feasibility with a query, not a launch plan Every rendered AI Vault row built a whole launch plan — execution-host lookup, project-runtime resolution, capability read, plus a plan object and a launch closure it threw away — to read one boolean off it. Feasibility and a launch decision are different operations, so the planner now exports the predicate for the first and keeps the plan for the second, and the census pins the query's callers separately. Settings arrive by argument, which makes the AI Vault callback's dependency on them real rather than a comment the linter contradicts. The plan's `explicitStructured` branch had that gate as its only caller and goes with it; the vault's launch already re-enters on an adopted verdict. * refactor(terminal-pane): fold the fork's trust preflight onto the canonical one `preflightForkAgentTrust` was a behavioural duplicate of `preflightAgentTrust`, whose signature now accepts a nullable agent and workspace path and so is a drop-in replacement. Its file is left holding only the launch-platform resolver — which is not a duplicate, since it returns an override rather than a default — so the file is renamed for what it now contains. * refactor(native-chat): cancel a structured launch through an AbortSignal The settle loop's launch cancellation re-derived the standard poll-plus-eager- event primitive that `AbortSignal` already is, so it now takes one. The eager semantics are unchanged: the loop still cancels on the abort event rather than only polling after awaits, so a staged prompt is discarded before it reaches the provider, and it drops its listener on settle instead of leaving the signal holding the closure. Quick create owns the controller and bridges its store subscription to it. A cancel that lands after the refusal fallback already opened a terminal now carries that surface on the settlement. It is the fallback's tab that exists, so reporting the pre-launch one handed the caller a workspace with no agent in it. * fix(native-chat): tighten quick create's structured launch settle path Four things the launch path got wrong once the settle loop owned the flow: - The abandoned-creation check now runs before the first-message rename flag is written, so a creation being torn down is no longer marked for a rename that will never happen (the order the pre-planner code had). - A cancel that arrives after the refusal fallback opened its terminal reports that terminal rather than the pre-launch tab. - `plan.launch` is called outside the caller's try, and nothing awaits that caller, so a throw there would strand the creation panel. It is now caught and reported the way a failed launch already is. - The launch route is a required argument instead of defaulting to `terminal-tui`, which would have silently reported success with no surface opened. Both callers already gate on the structured route. * fix(native-chat): give one launch identity one prompt delivery mode A caller joining a pending launch computed its outbox text from its own delivery mode, so an auto-submit caller landing on a draft launch enqueued text the first caller's seed was already showing in the composer: the user saw it and it was sent. The mode is now fixed by the caller that opened the launch, and a joiner delivers its text that way. Seeding also moved to where the coalesce decision is made, so a launch whose callers already settled as refused is not given a fresh draft — the refusal path early-returns, so nothing would ever clear it and it would outlive every tab. * fix(work-item): report a failed structured launch as a failed direct launch `launchWorkItemDirect` returned true unconditionally, so a structured launch that opened no surface still read as a started workspace. Callers hang irreversible follow-up work off that boolean — the fix-checks dialog fires `onLaunched` on it, which is documented as the home for host writes — so a launch with no agent tab now reports false, matching what full create does. The settle result says so explicitly rather than leaving callers to infer it from a null tab id, which `notLaunched` also produces. * test(session-tabs): pin the id a first structured publication is minted under The launch draft seed is keyed on `structuredAgentSessionTabId(sessionId)` before the tab exists, while the mirror mints ids with collision avoidance that can append a `:history-N` suffix. The two agree today only because a fresh session's base id is unique. Pin that where the id is actually minted, with the collision arm alongside it so the divergence the seed depends on staying away is visible rather than assumed. * test(native-chat): pin the route connection fallback on the un-mocked resolver The suite that covers the builder stages `getConnectionIdFromState`, so it can characterize the fallback but cannot catch a defect that lives in owner resolution itself. This one runs the real resolution over real store rows: two repos publishing the same worktree id on different hosts, which is the documented case where the owner cannot be named and `undefined` is returned. Red with both fix files at the previous head, green with them. Reverts the two caller pins added to the route census — the feasibility predicate is exported from the planner, which the census already permits, so it passes unedited and needs no permit clause. * fix(native-chat): keep the structured launch's own agent eligibility check Quick create's structured launch narrowed its guard to a bare `agent` presence check, so a creation carrying an agent that cannot hold a structured session reported itself cancelled once dismissed, where it previously reported that it had done nothing. Unreachable through both callers today, but it is the last local eligibility check in a module that otherwise trusts its callers for the route, so it is restored rather than left to the required-route typing — which says nothing about the agent. Also corrects two comments that called the quick-create request "persisted". It lives in renderer session memory and dies with the renderer; calling it persisted made the plan/adopt split read as restart recovery, when what it actually buys is a route decided before the worktree exists. * fix(native-chat): keep the structured feasibility query typecheck-clean The query threaded its narrow settings through the store, but the route store's settings must satisfy the full GlobalSettings that two of its resolvers require, so the narrow copy never fit. Ride the named settings on the built input instead: the caller still names them, so a React memo still depends on them, and no store-shaped object is needed. Also give the launch state its delivery mode unconditionally; the key is required, and a conditional spread makes it optional under exactOptionalPropertyTypes. * docs(native-chat): name the feasibility query's one remaining settings asymmetry The builder reads launch customization off the store while the routing gate reads the named settings, so one answer has two settings sources. It cannot diverge with the single caller passing the object the store already holds, but a PR about removing split sources should not leave that unstated. * fix(native-chat): keep a coalesced joiner's draft unsent joinLaunchDelivery stripped the joiner's delivery mode when the launch it joined had established none, and an absent mode reads as submit. A joiner that asked for a draft therefore had its text sent — the send-without- consent this PR exists to prevent. Fall back to the joiner's own mode only when nothing was established, so the first caller still wins otherwise. * chore: re-trigger CI GitHub created no workflow run for e935ea5 — the pull_request synchronize event was dropped. No content change. --------- Co-authored-by: Merge Sim <sim@local>
…tablyai#19912) * refactor(cloud): share PostgreSQL schema startup between services * feat(cloud): add durable native push notification gateway * infra(push): define dedicated gateway resources and operational checks * fix(push): bound cross-host admission and simplify gateway configuration * fix(push): validate deploy configuration and preserve topic-error registrations
…tablyai#19927) * fix(browser): restore the Chrome-shaped browser identity (STA-7147) stablyai#18749 replaced every browser partition's Chrome-shaped UA with Electron's stock one, so since v1.4.198 the embedded browser announces itself on every non-Google host as: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Orca/1.4.198 Chrome/150.0.7871.224 Electron/43.4.1 Safari/537.36 No browser sends that. Sites that re-check the identity holding a session reject it: users report being signed out of x.com, LinkedIn and "most websites," and at least one was signed out of LinkedIn in their own Chrome and met LinkedIn's "suspicious activity" SMS check -- server-side revocation, which reaches beyond our app. The repo already documented the mechanism in browser-google-auth-ua.ts: copied-in cookies "sent under a UA that doesn't match a real first-party browser get flagged by anti-fraud." That is why the Google auth-host switch exists; stablyai#18749 kept it for accounts.google.com and handed every other host an Electron identity. Restore the pre-stablyai#18749 session identity: strip the Electron and app tokens, and rewrite sec-ch-ua to match. Nothing in the cookie-import write path changed -- it never did; cookies were always written correctly and servers were refusing them. Deliberately KEPT from stablyai#18749, all independent of the UA: - anti-detection.ts stays deleted. Its premises were measured false on Electron 43 and its overrides are themselves published bot signatures. - No Runtime.enable into cross-origin iframes (the documented Cloudflare CDP tell). - No unconditional CDP debugger attach on every browsing guest. Known tradeoff, measured: this re-opens stablyai#13822. On the unmerged predecessor branch brennan/sta-3905-cloudflare-ua, commit 9f0a477 recorded the stock UA clearing dash.cloudflare.com 5/5 while every rewritten variant failed 12/12, and noted that adding client hints does not rescue it. So Cloudflare-gated sites will show verification failures again until a coherent-identity fix lands. That is a bounded, in-app annoyance; session revocation damages users' real accounts. A CDP Emulation.setUserAgentOverride with full userAgentMetadata -- which drives navigator.userAgentData as well as the headers, and was never tested -- is the candidate that could satisfy both, and is being measured separately. Tests: the real-Electron wire-identity test now asserts the stripped identity on ordinary hosts and Firefox on Google auth hosts. Ablation-verified: neutering cleanElectronUserAgent turns it red on the Electron-token assertion. Its fixture also gained an app name -- without one the raw UA carried no app token, so the Orca/x.y.z half of the cleaner was never exercised. * fix(browser): finish the identity revert in the files CI caught browser-session-registry.persistence.test.ts still asserted stablyai#18749's behaviour ("keeps the stock UA", "keeps the engine UA"), so the shipped code and its test disagreed. Caught by CI shard 4/8, not locally: I reverted four test files and went to typecheck without re-running the browser suite. Also restores the accurate wording that stablyai#18749 generalised away, now that the behaviour it described is back: - browser-google-auth-ua.ts: names the Electron/Chrome-shaped UA again as what anti-fraud flags, which is the reason the auth-host switch exists at all. - docs/browser/profiles.mdx: documents the cleaned Chrome UA default and the --no-ua-spoof escape hatch, which is real again. - tests/tools/google-signin-ua-probe.cjs: comments name the live handler. Deliberately left at stablyai#18749's version, because those changes stay correct with anti-detection.ts deleted: - browser-manager-viewport.ts: its comment no longer cites the retired addScriptToEvaluateOnNewDocument injection. - browser-webauthn-profile-delete.test.ts: its added webRequest mock is REQUIRED by the restored setupClientHintsOverride, so reverting it would break the test. * fix(browser): keep restored UA hints browser-owned --------- Co-authored-by: Merge Sim <sim@local>
…creens from their routes (stablyai#19675)
…stablyai#19854) * fix(native-chat): never adopt an unlisted model as the launch default `modelIsAdoptableAsLaunchDefault` is the sole gate on whether a model id may become the persisted `-m` launch flag for future native chats. For a catalog that does not set `discoveredModelsAreAuthoritative` — Claude and Codex — `!catalog.discoveredModelsAreAuthoritative` short-circuited the discovered branch to `true` for any id at all. So a raw launch flag (`worker-start --model claude-opus-5`) is seeded verbatim into the session record, and the first option write or model re-pick adopted it as the durable default. Every later native chat with that agent then launched `-m claude-opus-5` — an id neither the host CLI's list nor the catalog seed carries. Both branches now sit behind one precondition: the active model list or the catalog seed must carry the id. Ids that are carried keep their existing behaviour, including the authoritative-retirement and tracked-model rules. * docs(native-chat): state the launch-default precondition by id, not by vector A typed `/model` cannot introduce an unlisted id: matchNativeChatCatalogModelId returns only ids drawn from the list it is handed, so a never-seen id either collapses to a catalog id (claude `/model claude-opus-5` -> `opus`) or matches nothing (codex). It can only re-assert an id already in the record. The origins that do enter verbatim are the launch flag and an agent report -- applyNativeChatReportedSessionOptions writes `values.model` with no catalog matching. Say that instead, so the comment is true of the code as written and does not lean on the reconciled row that stablyai#19852 removes. Comment only; no behaviour change. --------- Co-authored-by: Merge Sim <sim@local>
…rovider echo (stablyai#19863) * fix(native-chat): settle a structured send on admission, not on the provider echo Sending a message in structured native chat raised "Message delivery is unconfirmed." with a Retry button on a message that had in fact been delivered. Measured across 14 days of local journals: 44 of 173 delivered sends (25.4%) tripped it. The dispatch path wrote the message to the provider, then waited a fixed 10s for the provider to echo the message's uuid back. That echo is emitted when the provider STARTS the turn, so a message queued behind a running turn cannot be echoed until that turn ends. Echo latency is bounded by the previous turn's duration, which is unbounded -- one send took 105 minutes. The 10s constant sat at the p75 of real echo latency, with the slowest clean send at 9.76s, a margin of 0.24s. No constant can work: the wait was measuring the wrong event. The false banner was not cosmetic. It invited a Retry, and Retry bypassed the operation ledger to redeliver. One message reached the model five times through that path. Dispatch now returns as soon as the transport write completes and writes no dispatch row; the submission stays `pending`, a neutral state, and the provider's echo settles it `accepted` through the late-settlement channel whenever the turn ahead of it ends. Delivery doubt is reachable only from process facts -- a refused write, a dead child, a dead host -- never from elapsed time. Retry re-delivers only where the recorded reason proves the message never reached the provider. The list is deliberately fail-closed: refusing a legitimate retry costs the user a re-type, while allowing an illegitimate one sends the model a second copy of their message. A refused entry now leaves the outbox with an explicit notice instead of parking at the head, where it would have wedged every message queued behind it. The send-response classification moves to a pure module beside the existing outbox reconciler, so both writers of an entry's state now live together and the decision is unit-testable rather than reachable only through the hook. Scope and known gaps: - Codex carries the same 10s stopwatch. It has no late-settlement channel, matches waiters by queue order rather than identity, and has no waiter lifecycle at all, so there was no safe subset to land here. A marker constant records the debt and deletes itself when that lands. - A message refused re-delivery loses its standing delivery notice and leaves only a transient error line. A passive "waiting to be accepted" affordance is the follow-up. - The restart reconciler that would decide a dead child or a dead host on evidence rather than refusing them is fully written and has never had a production caller. Wiring it is the next change, and it removes the re-type cost above. * fix(native-chat): harden structured dispatch settlement * fix(native-chat): preserve dispatch recovery evidence * fix(native-chat): preserve pending send compatibility * fix(native-chat): satisfy native import audit * fix(native-chat): bound legacy send settlement --------- Co-authored-by: Merge Sim <sim@local>
* feat(desktop): integrate native mobile push delivery and lifecycle * fix(desktop): preserve notification replay policy and review invariants * fix(desktop): correct notification locale namespace and auto-ack tests
* fix(sidebar): let an agent row show the provider's own session title The row resolver never read `aiVaultTitle`, so a terminal agent's tab showed the title from its transcript while its sidebar row showed the scraped live title — two names for one session. Placed at the same rank the tab strip uses. * fix(sidebar): scope provider titles to their sessions * fix(dashboard): refresh retained agent tab metadata --------- Co-authored-by: Merge Sim <sim@local>
…valid (stablyai#19746) * fix(editor): make markdown images inline so a paragraph stays schema-valid Image was registered as a block node while paragraph is content:'inline*', but the markdown pipeline nests an inline image as a paragraph child. Schema.nodeFromJSON does not validate content, so the editor built a schema-invalid document that rendered fine and threw on the first step that reassembled the paragraph - i.e. on the user's next keystroke. Report 0e46c048 (1.4.198, macOS): RangeError "Invalid content for node paragraph" from checkContent via Node.replace, tearing down the editor.rich-markdown boundary. Register Image as inline and override paragraph's parseMarkdown so a lone image is not hoisted out of its paragraph. Also fixes the same crash class reachable through details/summary. Markdown output is byte-identical. * fix(editor): keep a fenced code block intact when an image is inserted into it Making the image node inline meant it could no longer be fitted into codeBlock (content:'text*', marks:''), so inserting one with the cursor inside a fence made ProseMirror close the block at the insertion point: the remaining code escaped as plain prose and the language attribute was lost, and autosave wrote that markdown to the user's file. The pre-fix block image split the fence into two intact blocks instead. Resolve the insert content against the target position: when an inline image cannot be fitted where the caret sits, wrap it in a paragraph so ProseMirror splits the block and both halves keep their ``` fencing and language. Prose insertion is unchanged. Every production insert path now shares that resolution - the toolbar picker, the slash command and the clipboard-screenshot paste through insertRichMarkdownImageFromPath, plus the GitHub/GitLab composer's image-URL insert - each with a regression test. Also guard the unchecked cast of Paragraph.config.parseMarkdown: a Tiptap upgrade that drops the field would otherwise turn every paragraph parse into a TypeError and take the whole editor down, instead of degrading to parseInline. Four of the new round-trip cases asserted only on getMarkdown(), which walks the document without running NodeType.checkContent and so emits byte-identical output from a schema-invalid document - they passed on the pre-fix code. roundTripMarkdown now runs doc.check(), the list-item and table-cell case performs a real edit, and the standalone-image case types beside the image. All twelve cases now fail on the merge-base. Adds an Electron e2e spec driving the real renderer: a paragraph image and a toggle-summary image each survive a keystroke, and Bold over a selection spanning the image keeps it. --------- Co-authored-by: m4air <m4air@m4airs-MacBook-Air.local> Co-authored-by: Neil <neil@stably.ai>
…tablyai#19908) * refactor(ai-vault): move the surrogate-safe slice to shared, one implementation `sliceAtCodeUnitLimit` lived in src/main/ai-vault, and src/shared never imports src/main, so a shared consumer could not reach it. Rather than add a second copy, it moves to src/shared and ai-vault imports and re-exports it, leaving every existing importer of that module untouched. Separated from the feature that needs it: this is the only change here to a subsystem the rest of the branch does not touch. * feat(native-chat): give an agent session one place to hold its name Adds the normalized conversation name and the record field that stores it, so Orca has a durable note that it already named a session and does not name it again on a later acquisition. No name is generated yet, and nothing displays this field: the AI Vault path stays the home for the name a user sees. - `agent-session-conversation-name` bounds and flattens the text once, at 200 characters, cutting on a character boundary. - `AgentSessionRecord.conversationName` carries it, validated on load. - `setAgentSessionRecordConversationName` sets or clears it, unfenced: the name is a durable note, not ownership, so writing it never contends with the writer lease. * refactor(runtime): move reserve-owner orchestration next to its admission logic Pure move, no behavior change. `reserveOwner`'s transaction body sequenced decisions that all live in agent-session-reservation-admission and then applied the winning one; it now sits there as `commitAgentSessionReservation`, and the store keeps the transaction boundary and a one-line delegation. Takes agent-session-record-store.ts from 300/300 to 279/300, which is what the conversation-name field needs to land. Every existing test passes unedited. The module header said nothing in it mutates; that is now qualified rather than left false, since the committer writes the state it is handed. * feat(runtime): let the store set a session's conversation name `setConversationName` is the one writer for the record field, so a producer never reaches the record shape itself. It normalizes at the boundary, so no caller can persist a name the loader would then reject as unreadable. Unfenced by design: the name is durable memory that Orca already named this session, not ownership, so naming never contends with the writer lease. No name generation and no provider code yet. * refactor(runtime): reuse the shared default chat label on a replacement tab The replacement tab open-coded `'Claude Chat' : 'Codex Chat'` while every other publisher already calls `defaultAgentChatLabel`. One writer for the placeholder. * fix(native-chat): reject noncanonical stored conversation names --------- Co-authored-by: Merge Sim <sim@local>
…nheriting them (stablyai#19945) * fix(release): prune optional natives before the linux arch floor check The linux-arm64 release build failed packaging, and retried three times: [verify-linux-glibc-floor] 1 bundled native binary is built for the wrong architecture (target arm64): resources/node_modules/@parcel/watcher-linux-x64-glibc/watcher.node is x64, expected arm64 (from its own path) `afterPack` ran `verifyLinuxGlibcFloor` at its top, before `prunePackagedRuntimeNodeModules`. A cross-build intentionally installs every optional native variant, so at that point the arm64 slice still carries the x64 `@parcel/watcher` package that the prune exists to drop. The check was reading a file that was never going to ship. Move the floor check below the prune so it inspects the binaries actually packed. Same ordering as 35f1b0e on the v1.4.197 release branch, which shipped green and was never merged back to main. (cherry picked from commit 6400598) (cherry picked from commit a883e17) * fix(release): restore the minified telemetry constant fallback The macOS, Windows, and both Linux release builds all failed on "Verify telemetry constants present in app.asar", blocking publish-release: ::error::BUILD_IDENTITY constant missing or unexpected value in dist/mac-arm64/Orca.app/Contents/Resources/app.asar The verifier printed no context sample, because the string `BUILD_IDENTITY` does not occur anywhere in the shipped bundle at all. stablyai#17527 added `minify: 'oxc'` to the main bundle, so oxc renames the module-local `const BUILD_IDENTITY` to a short identifier. `BUILD_IDENTITY_RE` keys off the literal name and therefore cannot match a production bundle. `MINIFIED_TELEMETRY_RE` matches the adjacent injected identity/key pair instead, which survives renaming. stablyai#11019 created these patterns without that fallback, and its own comment predicted this exact failure if minification were ever enabled on the main bundle. The fallback was written on the v1.4.197 release branch in 35f1b0e and never merged back to main, so main has never been able to verify a minified bundle. Verified against a real local bundle built with the release env vars (ORCA_BUILD_IDENTITY=stable, ORCA_POSTHOG_WRITE_KEY=phc_...): the bundle does not contain "BUILD_IDENTITY"; both the narrowed and the widened BUILD_IDENTITY_RE fail; MINIFIED_TELEMETRY_RE matches and recovers identity=stable plus the write key. (cherry picked from commit 39c3e44) (cherry picked from commit d0bbe44) * fix(skills): stop asserting readdir order in the skill root walk test The Windows skill-sharing release gate — which blocks publish-release — failed on this test, wedging the 1.4.198 cut. The implementation is correct; the assertion was not. `findSkillFiles` pushes results in `readdir` order and dedupes visited directories by realpath, so of the 32 junctions pointing at one target exactly one survives. The assertion hardcoded both the array order and which link won. `readdir` order is filesystem-dependent: APFS and ext4 return SKILL.md first, while NTFS enumerates its filename index alphabetically on the uppercased name, where "LINK00" sorts before "SKILL.MD" (L=0x4C < S=0x53). Windows therefore returned the same two paths in the opposite order. Assert the contract instead: the real file is present, exactly one link-routed path survives dedup, and nothing else does. (cherry picked from commit 9a832e9) (cherry picked from commit 505b85c) * test(release): pin ported release gate fixes * style(skills): apply pinned test formatting --------- Co-authored-by: Neil <neil@stably.ai> Co-authored-by: Merge Sim <sim@local>
* feat(cmd-j): compact palette location layout * chore(i18n): sync palette runtime fallbacks * fix(cmd-j): preserve agent snippet context * fix(cmd-j): elide all file-backed tab paths
…blyai#19966) * fix: prompt unexpectedly signed-out Cloud users once per version * fix: align sign-in card English catalog with runtime defaults * fix: stack notification cards by their rendered height * fix: wait for fresh auth before showing signout card * fix: require verified auth before signout recovery * fix: retry transient auth readiness failures
…nsumer (stablyai#19687) * feat(ai-vault-search): add the session search index schema and row modules The FTS5 index that PR 2 folds the transcript reader's message stream into: two FTS tables behind visibility views, the identifier shadow column, the resumable fork digest, redaction at the row-insert choke point, and the bounded compaction, warm-up and retention lanes. Schema version starts at 1: this branch drops the query log and the fts5vocab table, which the query engine reintroduces with its own bump. * feat(ai-vault-search): stage index writes and publish them atomically The writer stages a read's rows against an unpublished session row and a batch id, then flips the whole read visible in one transaction: both FTS tables are written together per row, and publish nulls the batch pointer before dropping the batch so a recycled rowid can never name a later in-flight write. Buffering replaces the branch's streamed producer. The transcript reader pushes messages synchronously, so a staged write cannot make it wait; rows are held to 128 of them or 256 KB and then flushed in one transaction, which bounds both the retained bytes and one stall. The store owns the database and the set of files the index is behind on. It carries no query, coverage or scheduling: draining that set is the indexer's. * feat(ai-vault-search): register the index as a transcript reader consumer The index keeps its own per-file cursor in the `files` table and never consults the parse cache; the branch's AsyncLocalStorage capture scope is gone. Three refusals, each leaving the cursor where it was and recording the file for a later whole re-read: an append whose predecessor offset is not this index's own cursor (or whose dev/ino changed) is declined in `beginRead`, a staging failure ends the read's rows without throwing back at the reader, and an `incomplete` outcome never publishes. A null session drops the file's rows and still advances the cursor, because the file was read through. Nothing in production registers it: the indexer decides when the index is live. * test(ai-vault-search): measure the index's disk and latency cost The write benchmark indexes a synthetic corpus through the real reader and reports rows/s, per-table bytes per transcript MB, write amplification and rebuild time; the retention benchmark compares the batched purge against a whole-file delete. The corpus generator ships with them, so the numbers are reproducible on any host and no real transcript is ever read. * fix(ai-vault-search): decline a source the message channel cannot reach An OpenCode SQLite candidate decodes inside a worker, so the reader reports every read of it as incomplete. Staging a batch first meant one staging session and one tombstone per scan of every such session, forever. Declining in `beginRead` also keeps it out of the re-read set, because no re-read helps: the index cannot cover that source until its capture path lands. * fix(ai-vault-search): redact a message before it is cut into rows A credential is a shape, and a shape split across two chunks matches neither half: an 8000-char boundary landing inside a PEM block or a JWT indexed the key material in full, identifier shadow terms included. Redaction moves ahead of chunking, and the row type is branded so only `searchMessageRows` can mint a row an insert site accepts. `upsertFile` stops erasing dev/ino when a candidate carries none. A host that cannot stat identity (SSH, WSL, a degraded Windows stat) was overwriting a proven identity with NULL, which made the next rename-replace of that path undetectable. * test(ai-vault-search): pin each index guard on its own Three guards were each shadowed by another, so mutating any one of them left every test green. Driving the store directly separates the writer's own predecessor-offset check from the consumer's cursor check, and a stubbed store proves `beginRead` refuses before the writer is ever asked. `publishable` gets the case it never had: a second read of the same path publishes first, and the stale stage is tombstoned rather than resurrected. It covers the overlap the parse file lane normally prevents, so the invalidation slot being per-path is not load-bearing on its own. Two ratchets: every provider fingerprint needs a fixture that reaches past `SECRET_ANCHOR`, and every FTS read in the index modules must subtract staged rows through a visibility view. * fix(ai-vault-search): rebuild an index that cannot be trusted, and index retention Opening self-heals. A file too torn to open, or one whose recovery fails, is unlinked with its sidecars and reopened once; a second failure throws. Before this a torn index broke open permanently, even though the index is a cache over the transcripts and throwing it away costs only a re-scan. A `meta` table with no readable version row is now `stale`, not `fresh`. Seeding the current version over it would have kept whatever rows the old schema left. `fresh` means no `meta` table at all, and no longer triggers a rebuild: the first open of a new profile was doing create-remove-create. Recovery moves inside open, because retiring a batch that outlived its writer is part of opening the index rather than of using it. Its append case gets the test it never had: 200 rows staged onto a live session and abandoned leave the published generation intact and nothing else. Also an index on files(mtime_ms), so the retention pass seeks the expiring end of the list instead of scanning and sorting it, a pragma read-back test, and a sessions.file_path comment that says what is actually true of OpenCode. * fix(ai-vault-search): key a session's cwd the way the sidebar already does `cwd_key` had its own normalizer, which qualified a WSL cwd with its distro: `/home/me/repo` was stored as `//wsl/ubuntu/home/me/repo` while the sidebar's `folderGroupKey` stored `/home/me/repo`. Any later join between an indexed hit and a sidebar group would have returned nothing for exactly the WSL users the qualification was meant to help. The key is now the shared normalizer verbatim, pinned against `folderGroupKey` for POSIX, Windows drive, /mnt/c, both WSL UNC aliases, a Linux path, and the root. The collision it guarded against is real: two distros both spell `/home/me/repo`. It is also a collision every SSH host has, neither key qualifies for SSH, and the honest fix is a column naming the execution host rather than a path key that only some hosts spell differently. `/` now keys as `/` instead of the empty string. An empty key cannot be told apart from a session with no cwd, and the scope filter builds its child prefix as `key + '/'`, which would have been `//`. * test(ai-vault-search): widen the FTS visibility ratchet past whole literals Concatenating or interpolating a table name hid it from the per-literal scan, so `'SELECT rowid FROM ' + table` passed. The unit is now the file, the scan covers src/main and src/relay rather than one directory, and both bypass shapes are checker unit tests. Exemptions carry a reason and the census fails on one that has stopped being needed, which is how the schema module lost its. * fix(ai-vault-search): keep a failed rebuild's real cause, and read per statement Closing the stale handle before the unlink left `db` dangling: if the unlink or the reopen threw, the failure path closed it a second time and node:sqlite's ERR_INVALID_STATE replaced the real cause. Nothing classifies that as worth retrying, so an index a virus scanner or a second Orca was holding would never rebuild. The handle is nulled while none is open. The visibility ratchet moves from the file to the statement. A file is far too coarse for what it exists to guard: the query module will name both views somewhere, and that whitelisted every raw read in it. Statements come from literals and their concatenation chains, split on `;`, and a table name assembled at runtime counts as a read in any file that names an FTS table. The roots now cover cli, shared and preload as well. Half a recorded identity is now stated to be no identity. `remote-session-file-stat` spreads dev and ino independently and the COALESCE preserves whichever half a host could prove, so one number cannot tell a rename-replace from a same-file re-read; comparing it would decline healthy resumes on a coincidence. `discard` clearing the staging slot gets a test: without it the map grew one entry per path for the store's life. * fix(ai-vault-search): give a behind consumer a way to get the read it needs `markStale` promised a whole re-read that nothing could deliver. The reader picks append or replace from the session list's resume point, so with an empty index and a warm parse cache every read arrives as `append`, the consumer declines every one, and nothing is ever indexed. That is the state on first enablement inside a running app. `requestWholeTranscriptRead` in the reader drops that path's resume point, which is the one lever that changes the next read's mode, and it lives with the cache that owns it rather than with the consumer that wants it. `takeStale` documents that its paths need it before a scan is re-dispatched. Pausing no longer empties the re-read set. `acceptsCandidate` answers whether a write may start now and was being used for both jobs, so reconfiguring retention while paused dropped every entry and a declined read during a pause was never recorded at all. Retention alone prunes; a paused decline is recorded, bounded, with the oldest dropped and the drops counted, because a set that silently forgets is worse than one that says it is incomplete. A read that decodes no session now tombstones its staged rows before the store schedules cleanup. The cleanup lane reads the tombstone table the moment it is scheduled, so the old order left that batch on disk until some later write, and for the last read before a shutdown that is never. * fix(ai-vault-search): create the directory the index lives in Nothing made `<userData>/ai-vault-search/`, and SQLite's failure for a missing parent is `unable to open database file`, which is correctly not classified as corruption — so the retry never fired and the feature stranded on any profile that had never held an index. * refactor(ai-vault-search): store transcript content as written Decision: the index does not redact. A secret in a transcript is already plaintext under the user's home directory and is treated as compromised, so the index is a second copy of content the user already holds, not a new exposure. The reference agent-session-search products do not redact either. What a snippet may carry once it leaves this machine is a transport policy and belongs where the wire is, not in the write path. Removes the redaction module and its census, the branded row type that existed to prove redaction had run before an insert, and the two chunk-boundary tests. `insertSearchMessage` takes a plain chunk again and identifier shadow terms come off the raw text. `src/main/observability/redactor.ts` goes back to what main has, so this PR no longer touches it at all. Chunking and the fork digest are unchanged; the digest always folded raw message text, ahead of chunking, so its tests hold as written. Cost: redaction was 14 ms per 10.5 MB of transcript, about 3% of a rebuild and below the benchmark's run-to-run spread, so the write numbers are unchanged at roughly 22-25k rows/s and 22-27 MB/s. * fix(ai-vault-search): hide a tombstoned session's messages, not only its row `visible_sessions` already subtracted session-keyed tombstones; the message half filtered on the batch pointer alone. A published row outlives its session row until the cleanup lane reaches it, so between a `replace` publish and that drain both generations answered, and a removed file kept answering after its session was gone. Both views now subtract the same set, over a partial index so neither read scans the tombstone table. * fix(ai-vault-search): drain the rows a read that never published staged Only `writePublished` and `removeFile` scheduled the cleanup lane. A read that staged rows and then declined to publish them tombstoned its batch and told nobody, so 256 rows of a 300-message incomplete read sat in `messages` and both FTS tables until some unrelated write happened to schedule a pass. Open-time recovery had the same hole: it wrote the tombstones and drained none. The abandoned branch now schedules the drain the published branch already got, and opening schedules one pass for what recovery just tombstoned. Recovery no longer adds a batch tombstone when the session-keyed one already covers those rows, which it did once more on every reopen. * fix(ai-vault-search): continue the cursor of a file that decoded no session A read that decodes no session — an excluded Codex worker transcript — advances the cursor and leaves `files.session_row_id` null. Every later append was then declined, so a file that only ever grows would be re-read whole on every pass for the rest of its life. An append now only has to continue this index's own cursor; it creates the session row when there is none to resume. That also collapses the `append` flag, which meant both "resume this session" and "do not own it", into the resumed row id itself. * refactor(ai-vault-search): drop the index path module nothing calls No caller in this PR or the two that follow it: the composition root that would capture the userData path is PR 3b's. * refactor(ai-vault-search): read the schema version as stale or not Only the stale answer was ever acted on; the fresh/current split named two ways of being fine. * fix(ai-vault-search): read the resumed session id off an optional row * refactor(ai-vault-search): leave page warmup and the freshness check to PR 4 `warm()` and `session-search-page-warmup.ts` have their first caller in the query engine, which is what knows which pages are worth warming; the module itself went with the previous commit. `isSessionSearchFileCurrent` has its first caller in the reconciler. `session-search-file-cursor.ts` stays, because `fileIdentity` and both its types are load-bearing here. * refactor(ai-vault-search): stop pruning the re-read set on a retention change The prune walked the whole set to drop what the next `beginRead` would refuse anyway: `acceptsCandidate` applies the window when the re-read is dispatched, and `markStale` applies it again before recording anything. The bound stays. * fix(ai-vault-search): keep a batch tombstone from outliving its batch row Draining a session-keyed tombstone deletes the session's batch rows, but left any batch-keyed tombstone naming them in place. `search_write_batches` empties on every publish, so ids restart low and the next read takes the freed rowid; the stale tombstone then deleted that live batch's staged rows and the session published missing messages, with no re-read to fill the gap. The session drain now clears those tombstones in the same transaction. * refactor(ai-vault-search): commit a file's rows and its cursor in one transaction The staged-publish model is replaced by one SQLite transaction per file in WAL mode. A read buffers its decoded rows and writes them, its session and its cursor together; a reader on another handle sees the last committed state, which is the "never a torn session" guarantee the staging machinery was built to provide. A crash rolls the whole file back and it is re-read. Gone with it: `search_write_batches`, `search_pending_deletes`, `messages.batch_id`, `sessions.index_ready`, both `visible_*` views, the open-time recovery pass, the cleanup lane and `settled()`, and the ratchet test that made every query site read through a view. Rounds 2 through 6 were all seams between those pieces. A file whose rows exceed `SESSION_SEARCH_COMMIT_CHARS` is cut into chunks. The reader only hands out a byte offset when a read finishes, so a chunk records one no append can continue from: its rows answer searches as a coherent prefix of the session, and the next whole read replaces them. Retention keeps no record of unfinished work. It cuts a session loose from its file in one small transaction, which is what stops it answering, then reclaims its rows in bounded batches and hands the freed pages back as it goes. Rows whose session row is gone are the record of what an interrupted purge left. Deleted for want of a caller in PR 2, 3 or 4: the WAL budget (a buffered write has no staging window to grow one across), the compaction module (folded into the drain), the `sessions_content_hash` index (fork folding runs in JS over rows PR 4 already holds), `lastWriteAt`, `failures`, `SessionSearchStoreOptions`, `openStageCount` and `chunkMessageText`. * test(ai-vault-search): price a per-file commit and the ceiling that bounds it The write benchmark now times every transaction, because with one per file that is the whole stall a file costs. A second phase indexes a single synthetic 100 MB transcript, which is what the commit ceiling is chosen against. * fix(ai-vault-search): stop a fenced write reopening a transaction per message A chunk write that finds the file record moved under it — a `removeFile`, or an overlapping read of the same path — can never land anything afterwards. It kept buffering, so every remaining message re-entered `BEGIN IMMEDIATE` only to roll back, once per message for the rest of a file that may be a hundred megabytes. It now stops at the first refusal and drops what it holds. * fix(ai-vault-search): never hand a live session the id of a purged one `sessions.id` was a plain rowid alias, so SQLite reissued it as max+1. That id names rows in `messages` for far longer than the row itself lives: retention cuts a session loose in one transaction and reclaims its messages over many, and a session created inside that window was handed a freed id and adopted whatever of the purged conversation the drain had not reached. The drain then skipped those rows for good, because their session row exists again, and a search answered for a purged transcript under a live session's name. Reachable with no crash at all: an append of a growing transcript the parser decoded no session from creates its session row mid-purge. AUTOINCREMENT is the class fix; the schema version goes to 3 so a file written by an earlier commit of this branch rebuilds rather than keeping a plain-rowid table under a `CREATE TABLE IF NOT EXISTS`. `messages.id` was checked and is not exposed to the same window: a row and its two FTS entries always go in one transaction. Two smaller ones in the same pass. `removeFile` did not fence a read of a path this index had never written: `current()` compared a cursor, and on an unknown path the absent row and the absent expectation are both undefined, so the write recreated the source after its owner had proven it gone. The writer now counts removals per path and a write compares that count, which is a positive fence rather than an inferred one. And `drainOrphanedMessages`, `CONTENT_HASH_MESSAGE_LIMIT` and `CONTENT_HASH_MIN_MESSAGES` are no longer exported: nothing outside their own modules reads them. * fix(ai-vault-search): report a half-written file as held, not as unknown `indexedFile` returned null for a file a chunked read left partway through, the same answer it gives for a path the index has never seen. A caller asking "do you hold this file" therefore read a half-written one as new, asked for whatever read the parse cache offered, and the reader picked append — which the consumer then declined. Only the stale set healed it, a cycle later. It now reports the record with a null `byteOffset`, and `requiresWholeRead(indexed)` names that state. Null rather than an added flag because the mtime and size on that record are the file's real ones: a freshness check comparing only those would call a half-written file current, and a boolean is easy to not read, while every site that does arithmetic on the offset has to say what null means at compile time. The test also found the writer accepting an append that passed the partial sentinel back as its predecessor offset. Nothing in the reader produces a negative offset, but the sentinel is not a byte offset and no caller should be able to continue it; `beginWrite` now refuses it outright. * fix(ai-vault-search): cut a chunk at whitespace, never mid-token The 8,000-char chunker backed up only to a newline, and only when one sat in the second half of the window. A wrapped paragraph, a CJK transcript or a minified log has no newline there, so the cut landed inside whatever word straddled the target: the user's term was filed as two halves and matched neither. It now backs up to the last Unicode whitespace in the second half, and falls through to the target when there is none, because 4,000 characters without a space is not a word. A phrase that straddles a chunk boundary is still not matched. Chunks are separate FTS rows and FTS5 cannot span them; that is stated at the chunker. * feat(ai-vault-search): cap an indexed tool row at 3,072 characters Tool output is 80-97 % of a transcript's bytes and a single message may be a quarter of a megabyte, so without a cap the index, the buffer a read holds and the transaction it commits are all sized by how much a tool printed rather than by how much is worth searching. A `tool` message now becomes one row of at most 3,072 characters, kept from the head, where the command and the first lines of its output are. User and assistant text is never capped. Measured on the write benchmark, 40 sessions with tool output at 95 % of message text (the real band), per transcript MB: index 1,334,126 -> 353,973 bytes, write amplification 1.27x -> 0.34x, rows 18,804 -> 9,600, rebuild 1,676 -> 379 ms, largest transaction 49.7 -> 12.1 ms. On the default corpus, whose tool results are 1.4 KB and so under the cap, nothing changes at all: 2,167,887 bytes per transcript MB on both arms. The corpus generator takes `toolResultWords` and the benchmark reads ORCA_SEARCH_BENCH_TOOL_WORDS so both arms are the same harness. * fix(ai-vault-search): check the commit ceiling per row, not per message The buffered-chars check ran after a whole message had been folded into rows, so a single message could carry a transaction as far past the ceiling as it was large. A conversation turn is one message and can be megabytes; the ceiling exists to bound how long one commit holds the process and how large a WAL it produces, and neither bound survived a message that overshot it. * style(ai-vault-search): undo a stray reformat of untouched assertions Three assertions in the file-write test were expanded by a formatter run that was not the repo's, and the expansion survived because a multi-line object literal is preserved once it exists. Restored to what they were. * feat(ai-vault-search): write a session's identity with its first commit A chunked read created its session row with an empty session id, an empty title, an empty resume command and null cwd and timestamps, and only filled them in on the final commit. Those chunks answer searches the moment they land, so until the read ended every hit they produced named a session nothing could identify — and a crash between chunks left it that way for good, since the re-read that heals it is the same read starting over. The reader already knows the id, cwd and timestamps from a transcript's opening lines; it just had nowhere to put them. `TranscriptReadStart` now carries an optional `identity()` the consumer calls during the read, backed by an optional `identity()` on `ResumableSessionParseState`: one line in the shared accumulator fold (which covers cursor, copilot, droid, gemini, antigravity and the graph parsers) and one each in the Claude and Codex folds, which keep their own state. A chunk commit writes what it returns; the final commit overwrites it from the decoded session, so the mid-read title stays provisional. Two `cwd` guards in the Codex fold collapse into the `?? ` form the `branch` line beside one of them already used. `extractString` never returns an empty string, so it is the same assignment in one line instead of four, and it is what keeps the file under the 300-line cap with the identity accessor added. * refactor(ai-vault-search): drop conversation_fts for a column filter The second FTS table existed for query latency alone: a column-filtered `messages_fts MATCH '{user_text assistant_text}: q'` returns the identical rowid set, which PR 2's round 5 review proved and PR 4's benchmark re-checked per query. PR 4 then measured the filter at 1.16-1.36x the p95 of the second table on a 105 MB corpus across two points in the tool-output band, under the 2x bar the decision was set at, and closes the stack's open decision 3. It cost a quarter of the index on a corpus whose tool output is half the message text. With the tool-row cap it is a smaller saving than the decision was framed around, but it is still a table, a write per conversational row and a delete per reclaimed one. Schema version 4: `CREATE TABLE IF NOT EXISTS` is a no-op over an existing index, so only the bump makes a file that still carries the table go. * feat(ai-vault-search): expose the index handle a composed reader queries PR 4's engine reads the index this store owns. One getter lets it compose over the store's handle instead of opening a second connection to the same file, and it carries the two rules this PR measured: never hold a read transaction across an await, and no `.iterate()` outliving its statement. Either pins a read snapshot, and a checkpoint cannot pass one, so the WAL grows without bound for as long as it is held (10 MB to 266 MB on the write benchmark). * fix(ai-vault-search): cut a chunk at punctuation, not only whitespace The 8,000-character chunker backed up to the last whitespace in the second half of the window and fell through to the target when it found none. A minified tool result has none to find: valid minified JSON runs past 8,000 characters without a space, so the cut landed inside whatever word straddled the target, and a search for `pericardium` matched neither `perica` nor `rdium`. That is the shape most likely to be chunked in the first place, because tool output is 80-97 % of a transcript's bytes. The backoff now takes any character that cannot be inside a token — anything outside a letter, digit or underscore — and still falls through to the target when the window holds none, because 4,000 characters without one is not a word. Surrogates are excluded from the class so a cut never lands between the halves of an astral character. A few of the tokenizer's own `tokenchars` (`. - / +`) are cut on even though unicode61 keeps them inside a token. That costs the joined form of a path, and only in a window with no whitespace anywhere, which is a far smaller loss than the torn word this exists to prevent. * fix(ai-vault-search): never chunk a read that cannot name its session `updateProvisionalSession` is fed by the resumable readers alone. Claude and Codex carry an `identity()` off their fold; `readWholeTranscript` supplies none, because a format rewritten in place has no resumable state to ask. So a Grok, Cursor, Gemini or OpenCode file large enough to pass the commit ceiling published its chunks under a session with an empty id, an empty title and a null cwd — rows that answer searches at once and that an interrupted read leaves behind for good, since the re-read that heals them is the same read starting over. `add` now commits a chunk only while the read can name what it is writing. A read with no identity, or one whose parser has decoded no id yet, keeps buffering and commits whole at `finish`. The whole-file formats are the ones with nothing to give and they are small — the largest on this machine is 5 MB — so buffering one to the end costs nothing, and chunking stays reserved for the readers that can say which session a prefix belongs to. `updateProvisionalSession` takes a non-null identity now, so the invariant is the type rather than a guard that silently wrote nothing. * perf(ai-vault-search): cut a replaced generation loose instead of deleting it The first transaction of a replace deleted every row of the old session before inserting its own bounded chunk, so the transaction was sized by the history being replaced rather than by the rows being written. On the synthetic 100 MB transcript the longest replace transaction was 1,255-1,283 ms against 668-672 ms for the same file read fresh, and the gap grows with the session. A replace now mints a new session row, points the `files` row at it and deletes the one old `sessions` row, all in the same transaction. Every retrieval joins `sessions`, so the old generation stops answering the moment that commits — the same thing retention's first transaction does — and its messages are reclaimed afterwards by `drainOrphanedMessages`, the bounded batch loop that already exists for exactly this set. `sessions.id` is AUTOINCREMENT, so the freed id is never handed to another session while those rows still name it. The longest replace transaction is now 746-848 ms, the same band as a fresh read. The trade is stated plainly: the pass does more total work, 3.7 s against 3.1 s, because the reclaim is 360 bounded transactions with an incremental_vacuum step each instead of one large delete. It yields between them, so none of it holds the process, and returning the pages per batch also takes the index from 173.6 MB to 162.0 MB. The store schedules the drain off the committing stack: an async function runs synchronously to its first `await`, so calling it inline would put the first batch back inside the transaction's own call. One drain at a time, with a repeat flag for a replace that commits while one is running. * fix(ai-vault-search): preserve search tokens and index Codex tools * fix(ai-vault-search): match SQLite marks and Codex file-change records * fix(ai-vault-search): preserve stat pairs and validate benchmark results
…e agent (stablyai#19929) * fix(native-chat): pass agent-implemented slash commands through to the agent Claim what the host implements; pass through what the agent implements. Claude's harness expands a slash command out of the message text, so the host claimed catalog commands it had no way to run and answered "/init is not available in chat sessions" for commands Claude does run. Codex's app-server has no slash parser at all, so its catalog stays claimed — except /goal, which the model carries out through its own goal tools. * fix(native-chat): offer the agent-run commands in the structured picker Codex reports no command catalog, so its structured `/` menu is the host fallback -- which listed only the host's own commands and hid `/goal`, the one command the model itself acts on. The picker now appends the profile's text-driven commands, described from the curated catalog, so a command that passes through is discoverable and not merely typable. The menu invariant holds either way: a pick is answered by the host or run by the agent, never refused with "not available in chat sessions". * fix mobile structured command reconciliation * fix(mobile): keep native chat controller within lint budget * fix mobile controller lint budget --------- Co-authored-by: Merge Sim <sim@local>
…ee switch (stablyai#19975) Two allocations scale with the workspace count and are rebuilt on inputs that cannot change their result. `workspaceSurfaces` took `renderedActiveWorktreeId` as a memo dep, but `projectWorkspaceSurfaces` reads that id only behind a truthy `activeWorkspaceResolvedHostId` (the folder-collision tie-break), which is null unless the active workspace is itself a folder workspace. Every git-worktree switch therefore re-projected every surface and re-derived the id array to reach an identical answer. Gate the id on the host so the memo holds. The parked-watcher sync built a fresh empty `Set` for every workspace surface, even though only a mounted workspace can park a tab and the sync only reads the set. Share one empty instance for the rest. Both keep every effect firing on exactly the inputs it fired on before.
…ated on parse parity (stablyai#19961)
…ai#20420) * Reduce native dependency installs to the host platform * Remove install policy documentation * Guard cross-arch packaging and scope release installs to the runner electron-builder only logs a warning for a missing extraResources source, so a host-only install silently shipped a foreign-arch slice without its natives — `pnpm build:mac` on Apple Silicon produced an x64 DMG with no sherpa-onnx-darwin-x64 and no @parcel/watcher-darwin-x64. The previous beforePack hook covered only win32. - Add assertPackagedNativeVariantsInstalled, an arch-aware check over the target's sherpa-onnx, @parcel/watcher, and (on Windows) node-gyp addons. beforePack now runs it for every platform, with remedies split: another architecture comes from install:release, the os:win32 addons need a Windows host. - Drop --os from the release installs. Every packaging job already runs on a runner whose OS matches its target, so only the macOS lanes need extra breadth, and only on CPU for their x64+arm64 config. Windows and Linux packaging return to a plain host-only install. - Add --frozen-lockfile to install:release so a bare run cannot rewrite the lockfile. - Restore the install policy reference doc and the CONTRIBUTING note, plus the rationale comments dropped from the runtime contract test. - Gate the packaging-closure assertions on whether the Windows addons are installed rather than on the host OS, so a cross-arch install exercises them off Windows too. - Make the workflow contract test read `run:` steps as well as retry-action commands, and enforce host-only scoping on the non-macOS packaging lanes. - Remove the unreferenced install measurement script; its numbers live in the policy doc. * Track the install policy doc and index it from AGENTS.md docs/** is ignored behind a per-file allow-list, so the new reference doc was only committed via git add -f and future edits would be skipped. Add it to the allow-list and give it an AGENTS.md entry like every other tracked reference doc, so the host-only install rule is discoverable before someone packages a second architecture. * Route Windows-lane removals through the retrying helper Adding these four specs to the PR Windows lane pulled them into the windows-lane-tree-removal-boundary ratchet, which failed on 20 raw recursive removals. On Windows a bare rmSync races a handle the OS has not released, throwing EPERM after the assertions already passed and reporting a green test as a lane failure. * Adapt the packaging guard to the vendored Windows registry addon main vendored windows-native-registry as the workspace package @orca/windows-registry (stablyai#20438). A workspace link resolves on every host, so including it in the installed-Windows-addons checks proved nothing. @vscode/windows-process-tree is the only os: win32 npm addon left, so it alone decides whether the win32 resource plan resolves.
…tablyai#20291) * perf: deduplicate unlimited vault scans once * perf: release discarded vault aliases during unlimited scans * fix: bound per-session bookkeeping in unlimited vault scans - Drop the per-session alias-key string, wrapper object and positions array the accumulator retained for every parsed row; index winning positions by the row's own sessionId instead (~430 B -> ~45 B per session at 50k rows). - Add a --expose-gc retention test asserting a 50k mostly-unique load-all corpus stays under 128 B of bookkeeping per session while matching dedupeCodexSessionsBySessionId exactly. * perf(ai-vault): bound per-row bookkeeping in CodexSessionCollection Key winners by the row's own sessionId string so an unlimited scan retains no alias-key string per live row (301 -> ~115 B/row measured over 50k rows), and split into a per-alias-key map only for the rare id that spans several hosts, namespaces, or rollout names, so admission stays O(1). Fold the PR's CodexSessionAccumulator into the collection main already routes every scan through, and rerun its scanner-level tests against that single class.
…20212) * perf(tooling): reuse directory entry types in source scans * fix(source-scan): stat DT_UNKNOWN dirents so untyped directories are still walked `readdirSync(..., { withFileTypes: true })` can hand back a Dirent whose type the filesystem did not report. For that entry every predicate is false, so the readdir-type fast path treated a real directory as a file and silently dropped its subtree from every ratchet guard. Fall back to `statSync` whenever the entry is neither conclusively a file nor a directory, keeping the no-stat fast path for ordinary entries. Also make the two readdir-order assertions in the walk test order-independent; `scanSourceTree` returns raw readdir order, which differs on tmpfs. * test(source-scan): unit-test the stat fallback via an extracted helper The fabricated-Dirent readdir mock could not satisfy both gates at once: vi.mocked(readdirSync) resolves to Node's Dirent<NonSharedBuffer> overload, so the mock needed a type assertion, and stablyai#19462's casting gate rejects new ones on changed lines. Removing the cast then failed tsc. Extract directoryEntryNeedsStat and test it directly with a structural probe. No mock, no cast, no top-level await, and the DT_UNKNOWN case is pinned: removing the fallback fails 'stats an entry whose type readdir could not report'. --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Neil <neil@stably.ai>
* fix(skills): evict removed runtime discovery cache * fix(skills): retire removed runtime cache entries using pending scan identity * fix: rescan mounted skill consumers when a runtime re-pairs under the same id - Fold the pairing revision into useActiveSkillDiscoveryRuntimeTarget's selector so a same-id re-pair yields a new runtime target and every mounted useInstalledAgentSkillNames effect re-runs instead of holding the retired peer's installed list after the module cache is evicted. - Reset hook-local result/loading state on runtime target identity change, which also bumps the refresh generation so an in-flight scan issued to the retired peer can no longer commit its result into React state. - Add mounted-hook regression tests covering the re-pair rescan and the in-flight stale-scan fence. * fix(skills): reset discovery state via render-adjusted state, not a ref write React Doctor flagged the render-phase write to stateResetInputRef. React can discard a render after the write, in which case the next render sees "already reset" and keeps painting the previous target's skill list until a rescan. Track the reset inputs in useState and adjust it during render instead, which React replays safely. Also drop the `as never` / `as GlobalSettings` casts from the tests this PR added, since main now enforces consistent-type-assertions on changed lines. --------- Co-authored-by: m4air <m4air@Mac.localdomain> Co-authored-by: Neil <neil@stably.ai>
…n steps (stablyai#20152) Two defects that change nothing about when an existing schedule fires. stablyai#16303: evaluateDueRuns awaited each row with no catch, so one unreadable schedule skipped every later due automation in that tick. Each row is isolated now; a poison record writes one folded skipped_unavailable run explaining itself and the tick continues. A renderer send that throws is closed out as dispatch_failed rather than mislabelled as an unreadable schedule. stablyai#15895: step validation only checked integer >= 1, so a step wider than its field degraded silently to a single value and still passed validation. Oversized steps are refused at input time only, bounded by the count of distinct values a field holds, so day of week rejects */8 while */7 stays legal. Runtime parsing stays lenient so rows saved before the gate keep running the cadence they have. isValidAutomationSchedule now answers only 'acceptable as new input'; a new isRunnableAutomationSchedule answers 'can Orca still run this', and the editor uses it so a legacy row opens intact and can be renamed without re-authoring a schedule that is still firing. Verified: 34/34 corpus expressions fire identically to main. Fixes stablyai#16303 Fixes stablyai#15895
…ilence (stablyai#20155) `terminal wait --for tui-idle` returned satisfied in ~0s while an agent was mid-turn. The shared title detector defaults a name-only agent title to `idle` so the sidebar can clear a stale spinner, and the wait accepted that stored value as completion. Rank the evidence instead. An explicit idle marker in the agent's own title or a known ready prompt settles the wait; a fresh first-party OSC 9999 status saying working/blocked/waiting vetoes it; a name-only title is a last resort that settles only once the stream has also gone quiet. The rank is derived at read time from `lastOscTitle` rather than stamped onto the record, because `syncWindowGraph` rebuilds leaves from an explicit field list and would drop a bespoke provenance field on any renderer publish. Two things the ranking alone gets wrong are handled here too. A quiet non-shell foreground process no longer proves idle on a pane where Orca launched a known agent — that is an agent still booting, and resolving on it is what let `dispatch --inject` lose the prompt (stablyai#9976). And the idle poll re-reads the live leaf each tick, because a record captured at registration stops advancing and its frozen `lastOutputAt` makes the quiescence gate pass while the pane streams. The demotion is scoped to agents that go on to announce rest explicitly. Grok, Copilot, Aider, Mimo, agy and OpenCode emit their name and nothing more at rest: a real idle Grok pane repaints its banner about four times a second forever, so demanding quiescence from it left no settle signal at all and the wait ran to timeout. The design is Brennan Benson's, from stablyai#14642, which won a cross-review against stablyai#6012, stablyai#6555 and this branch's earlier approach; it is ported here only because that branch shares no git history with main and cannot be merged. Neil's stablyai#6555 first drew the explicit-vs-ambiguous line the ranking rests on, and Revofusion's stablyai#6012 first identified that a single title sample cannot prove completion. Fixes stablyai#6011 Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Revofusion <syed@moonai.org>
…tablyai#20149) Escape is ambiguous at the source for Claude, OMP, Pi and Prime Agent: the same key closes an overlay and cancels a turn, and which one it meant is focus state only the TUI holds. Nothing downstream can recover it, so for these agents a plain Escape is never evidence a turn ended — the provider's own hook decides. Ctrl+C is untouched, and no other agent type changes. The renderer skips the round-trip and main re-checks the same rule, so a stale or direct inference request cannot route around it. A navigation Escape does not clear a Ctrl+C already waiting to settle: Escape is not a retraction. Fixes stablyai#13547 Fixes stablyai#9208 Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Rod Boev <rod.boev@gmail.com>
…0310) * fix(mobile): reuse the current workspace on notification taps * revert(mobile): restore notification setting hint
…ai#20398) * fix(sidebar): observe agent titles before workspace activation Reuse parked terminal watchers for eligible live tabs in never-mounted workspaces, with initial title catch-up and capability-driven admission. Preserve existing watcher cleanup and avoid allocating watcher sets for empty workspaces. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix: reconcile background watchers when remote coverage changes Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test: type-check terminal watcher fixtures without casts Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
) * Add compile-time RPC params catalog parity gate Check each registered handler against its catalog params type in both directions, with explicit exceptions for the three uncatalogued schemas. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(rpc): keep the params generator off its own output The parity gate imports the generated catalog for types, and it lives under RPC_DIR, which indexableModules() scans for shared imports. That re-added OUTPUT_PATH after line 46 removed it, so the generator bundled and require()d the committed catalog. A catalog referencing a renamed or deleted shared export then crashed regeneration — in exactly the state that requires regenerating. Reproduced before and after: with a dangling reference injected into the catalog, `generate:rpc-params-catalog` threw; it now rewrites the file. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
…awns (stablyai#20285) * Widen Windows shim ratchet to detect package bin spawns Follow local program expressions into node_modules/.bin while preserving the existing literal check, roots, and allow-list. Document static-analysis limits and cover unsafe and resolver-based invocations. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(scripts): fold dot segments before matching node_modules/.bin The predicate joins call arguments textually, so a literal '..' segment hid a path that resolves into node_modules/.bin at runtime. Folds '.' and '..' (and Windows separators) first. A '..' that genuinely escapes .bin still does not match. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * style(scripts): use .at(-1) in the dot-segment fold oxlint's prefer-at rule; the repo-wide lint gate is an error, not a warning. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
…e meaning (stablyai#20133) * refactor(native-chat): give each structured dispatch state exactly one meaning `unknown` meant five different things. Only one of them was genuine ambiguity. A transport write that the provider's input pump never took is provably undelivered -- which is what `rejected` already means. It was recorded as `unknown` anyway, and a one-entry allowlist then existed solely to teach Retry that this particular `unknown` was safe to re-deliver. Collapsing that case into `rejected` deletes the allowlist and turns a predicate into an invariant: Retry never re-delivers an `unknown`, with no exception to reason about. The four states now each assert one thing -- `pending` written and awaiting, `accepted` the provider has it, `rejected` provably did not happen, `unknown` genuinely cannot tell. A fail-closed guard is the right default here because the asymmetry is severe: refusing a legitimate retry costs the user a retype, while allowing an illegitimate one sends the model a second copy of their message. Also fixed, found while auditing every reader of `rejected`: - The renderer printed `submission.reason` verbatim, so a broken pipe put the internal token `provider_write_failed: broken pipe` on screen in destructive red. The journal reason is unchanged -- it is the durable evidence and the transport-versus-content discriminator -- but the screen now gets copy that names the cause and says the message is safe to resend. Content rejections still show the provider's own words. - The fallback copy "Message was not accepted" read as a content refusal. A null reason now yields "Message was not sent.", which asserts only what every rejection shares. - A refused worker-start preamble threw a plain Error out of the dispatch path. It now throws `OrchestrationError('dispatch_preamble_undelivered')` so a coordinator can tell "we could not send it" from "we sent it and something else broke" without parsing prose. Retain/discard behaviour is unchanged; only the verdict's legibility improves. Two behaviours improve as a consequence rather than by design: a provably undelivered message no longer blocks conversation commands, and no longer leaves the session reading as "working" in chat and in every session list. Not addressed here, and named rather than implied: a message left `unknown` by a dead child or a host restart still has no recourse but retyping. The restart reconciler that would decide those on evidence is written and has never had a production caller. Parking the refused entry instead would reintroduce the head-of-queue wedge removed in stablyai#19863, so it is not an option. Note for whoever edits `journal-reducer.ts` next: it sits at 297 of its 300 counted lines. The next statement added there needs a split, not a shave. * fix(native-chat): close two gaps review found in the rejection taxonomy Both are narrow and both were real. A journal written before a refused write became `rejected` still holds that submission as `unknown` with the transport marker. The predicate this change replaced excluded exactly that shape from provider-echo matching; the state-only check that replaced it does not, so on replay such a row could claim the echo of a later, genuinely delivered send of the same text and attach the delivery to the wrong message. Fail-closed still prevented any re-delivery, so nothing duplicated — but the wrong submission was credited. Replay now excludes the legacy shape too. And the content-versus-transport split had a third case neither side covers: a local capacity refusal is neither the provider explaining itself nor a frame that failed to leave. It fell through to the verbatim branch, so `claude structured dispatch queue is full` reached the screen — the same class of leak this change set out to fix, one reason short of being caught. Internal reasons now get copy; only a provider's own words are shown as written. Each is pinned by a test that fails with its guard reverted and passes with it restored. * fix(native-chat): preserve dispatch refusal across clients * fix(native-chat): rotate immediately rejected retries * docs(native-chat): correct rejection taxonomy reference * docs(native-chat): align mobile retry comment * docs(native-chat): clarify unknown replay semantics * fix(native-chat): keep a mobile send's operation id when delivery is unknown Mobile released the retained operation id whenever a send came back `unknown`, so the user's next send of the same text went out under a fresh id. A fresh id has no ledger row, so the host treats it as a first delivery and dispatches it -- even though `unknown` is the one answer that says the provider may already have the message. That is the duplicate this branch exists to remove, reintroduced on the client that has no outbox. Which case that was matters. Mobile only ever sees `unknown` from ack-loss (`isRpcDeliveryUnknown`: "the host may have processed it and only the ack was lost"), because the mapper reported every `ok` result as `accepted` without reading `dispatchState`. So the rotation fired exclusively where delivery was ambiguous and never where it was provably refused, which is the inverse of the rule this branch establishes. Retaining the id is what makes a retry safe, and it costs no liveness: `performSend` answers a second request under a recorded id from the journal and never puts it back on the wire, so a reused id delivers when nothing landed and replays when something did. Rotating can only ever add a second copy. The retention stays bounded by the host's admission window, which `retainStructuredSessionOperationId` already enforces. `retryUnknown` goes with it: the host ignores it for delivery, and all it does is skip the cached answer to re-read the same row. Keeping the id exposes what the rotation was hiding, so fix that too: a replayed `unknown` comes back `ok`, and mobile called it `accepted` and cleared the composer as if the message had landed. `dispatchState` now decides, in one pure function: accepted/pending sent, and the id is spent rejected provably did not happen and terminal in the reducer, so reusing the id could only replay that rejection: spent, and the next attempt is a first delivery under a new id unknown keeps its id Reading `dispatchState` at all is a pre-existing defect, fixed here because the false "sent" cannot be removed without it, and scoped to the send path. `mutate`'s rotation for prompt/option/cancel plans is untouched. The rejection copy is the desktop's notice, so an internal reason (`provider_write_failed: ...`) still never reaches a person. Tests: the hook test that was flipped to assert a rotated id now pins the opposite -- one id across an ack-loss and two `unknown` replays, each reported `unknown` rather than `accepted`. The send fixture grew the durable submission row a real host returns; without it every send test asserted against a shape that cannot express the bug. * fix(native-chat): enforce fail-closed structured send replay * fix(native-chat): align retry and mobile RPC contracts * fix(native-chat): keep transient admissions retryable * test(tab-bar): expand nested create menu in harness --------- Co-authored-by: Merge Sim <sim@local>
…story (stablyai#20139) * feat(native-chat): decide a restart-stranded send against provider history `markPendingSubmissionsUnknown` flips every surviving `pending` submission to `unknown` on attach and stops there. The module written to finish the job describes the intended two-step in its own header -- "Every surviving `pending` becomes `unknown` and is then matched against provider history" -- and only the first step ever shipped. `reconcileSubmissions` has been imported by exactly one test file and nothing else. So a message stranded by a dead child or a host restart had no recourse but retyping: Retry correctly refuses to redeliver something that may already be with the model, the outbox entry drops, and a transient error line is all that remains. This wires the second step, so those are decided on evidence instead of refused. Caller placement is the design decision, because where it runs determines what a consistent history boundary can mean. It runs in `attachJournal`, immediately after the sweep: attach happens after the record store's CAS hands this host the lease and before a provider child starts, so nothing can append to provider history while it is read, and the window stays valid until the resume consumes it. The three other settlement sites can all be overtaken by a newly started child before the read is acted on. The history source is the Claude project JSONL for the handle chain's provider session id -- definitionally what a resume replays, which is what makes absence meaningful. Boundary consistency reuses `proveClaudeTranscriptBranchFromJsonl` rather than inventing a check: a fork, a compacted log and a truncated tail each already throw there, and each maps onto `boundaryConsistent: false`. A null leaf uuid is also false, because there is no anchor to prove a start from. Two guards were needed that the reconciler cannot enforce itself, because Claude echoes no client message id and only the fingerprint pass can fire: - A transcript records a pasted image as base64, and the block decoder drops it silently for want of a url or path. Such a record would enter the window advertising a text-only fingerprint, where an unrelated text-only submission with identical text could claim it. The window now inspects raw content parts before decoding and excludes any record a part would be dropped from. - A submission carrying an image-ref path can never match a transcript that keeps only base64. Without a guard it matches nothing by construction rather than by absence and falls straight through to `not_delivered`, and a Retry would then redeliver an image already sent. Only text-only bodies are handed to the reconciler. Both guards fail a named test when removed. Limits, stated rather than implied. The exact-match tier needs the provider to echo our id, which Codex does and Claude does not, so Claude resolves by fingerprint alone -- and two identical prompts deliberately reach `ambiguous_match` instead of guessing. Repeated one-word prompts therefore stay unknown by construction. This decides what it can prove and refuses the rest, which is the intended contract, not a shortfall in the wiring. Found while doing this and not fixed here: the block decoder silently dropping base64 images has a blast radius beyond reconciliation and deserves its own change. * fix(native-chat): harden restart history reconciliation * fix(native-chat): keep Claude adapter within lint budget --------- Co-authored-by: Merge Sim <sim@local>
…auto-sync-fork-with-upstream-stablyai-orca-ru-20260914T1400 # Conflicts: # src/renderer/src/components/native-chat/NativeChatComposerField.tsx
Applies the manifest classifier's four lists, restores 49 undeclared fork edits the reset reverted, replays v1.4.202 into the tier-2 composer copies, and merges upstream's delta into the exception-owned files that discarded it.
Resolves 31 hunks -X ours silently took the fork side of across 13 seam files, restores the four RPC schema modules v1.4.202 turned into re-export barrels and moves their fork seams into src/shared/rpc-contract, re-homes the tool-name colouring seam into the new NativeChatToolLine module, and adapts the fork to v1.4.202's params-catalog parity gate.
…duals Re-homes the seams v1.4.202's rpc-contract split moved, retires the mid-prompt picker seam upstream absorbed, and declares the 49 project-ledger edits to upstream files that PR #55 left undeclared — a reset would have dropped them.
…oser-send test v1.4.202 added the test type-importing the attachment type from the deleted NativeChatComposerField; it takes the same swap its three siblings already do.
There was a problem hiding this comment.
🟡 Changes recommended
Critical CI/test failures and unresolved moderate correctness issues remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This pull request absorbs upstream stable v1.4.202 into the fork while replaying fork-owned seams and changes.
Changes:
- Resolves upstream module splits and ownership residuals.
- Updates native-chat, runtime/RPC contracts, localization, mobile, cloud, and tests.
- Refreshes synchronization and release workflow integrations.
Open findings: two critical test/CI issues, three moderate issues, and one nit remain unresolved, with the supplied vote counts preserved.
File summaries
| File | Review scope |
|---|---|
tests/e2e/linear-url-workspace-entry.spec.ts |
Upstream sync and fork replay |
tests/e2e/helpers/source-control-ai-generation.ts |
Upstream sync and fork replay |
src/types/psl.ts |
Upstream sync and fork replay |
src/shared/workspace-session-schema.ts |
Upstream sync and fork replay |
src/shared/utf8-byte-limits.ts |
Upstream sync and fork replay |
src/shared/text-search.ts |
Search result finalization |
src/shared/structured-native-chat-launch-route.test.ts |
Upstream sync and fork replay |
src/shared/structured-agent-session-mutation.ts |
Upstream sync and fork replay |
src/shared/runtime-worktree-contracts.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/workspace-ports-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/updater-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/terminal-viewport-methods-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/ssh-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/skills-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/session-tabs-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/runtime-client-capabilities-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/preflight-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/orchestration-worker-stop-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/orchestration-worker-release-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/orchestration-worker-control-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/orchestration-runs-mutation-request-show-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/github-repo-target-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/github-issue-update-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/git-admission-tier-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/files-target-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/computer-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/client-ui-workspace-filter-fields-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/client-events-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/browser-screencast-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/browser-extras-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/browser-core-params.ts |
Upstream sync and fork replay |
src/shared/rpc-contract/agent-hooks-params.ts |
Upstream sync and fork replay |
src/shared/remote-workspace-types.ts |
Upstream sync and fork replay |
src/shared/remote-runtime-client-capabilities.ts |
Upstream sync and fork replay |
src/shared/relay-frame-buffer.ts |
Upstream sync and fork replay |
src/shared/project-groups.ts |
Upstream sync and fork replay |
src/shared/pr-comment-audience.ts |
Upstream sync and fork replay |
src/shared/persisted-ui-state-types.ts |
Upstream sync and fork replay |
src/shared/pane-agent-identity-inventory.test.ts |
Upstream sync and fork replay |
src/shared/native-chat-types.ts |
Upstream sync and fork replay |
src/shared/native-chat-session-option-state.ts |
Upstream sync and fork replay |
src/shared/hosted-review-refs.ts |
Upstream sync and fork replay |
src/shared/fork-workspace-review-filters/workspace-review-ui-schema.ts |
Upstream sync and fork replay |
src/shared/fork-workspace-activity-window/workspace-activity-ui-schema.ts |
Upstream sync and fork replay |
src/shared/cross-platform-path.ts |
Upstream sync and fork replay |
src/shared/constants.ts |
Upstream sync and fork replay |
src/shared/commit-message-agent-specs-primary.ts |
Upstream sync and fork replay |
src/shared/child-process/windows-command-line.ts |
Upstream sync and fork replay |
src/shared/child-process/bounded-output-sink.ts |
Upstream sync and fork replay |
src/shared/agent-status-observation.ts |
Upstream sync and fork replay |
src/shared/agent-session-host-authority.ts |
Upstream sync and fork replay |
src/shared/agent-process-recognition.ts |
Upstream sync and fork replay |
src/renderer/src/web/web-runtime-client-timeout-budget.test.ts |
Upstream sync and fork replay |
src/renderer/src/web/web-preload-api-composition.test.ts |
Upstream sync and fork replay |
src/renderer/src/web/preload-api/web-notifications-api.ts |
Upstream sync and fork replay |
src/renderer/src/store/slices/ssh-target-cleanup.ts |
Upstream sync and fork replay |
src/renderer/src/store/slices/agent-status-live-entry-builder.ts |
Upstream sync and fork replay |
src/renderer/src/store/slices/agent-status-contract.ts |
Upstream sync and fork replay |
src/renderer/src/runtime/web-runtime-terminal-create-operation.ts |
Upstream sync and fork replay |
src/renderer/src/runtime/web-runtime-session-types.ts |
Upstream sync and fork replay |
src/renderer/src/lib/worktree-creation-flow.test.ts |
Upstream sync and fork replay |
src/renderer/src/lib/structured-agent-session-launch-label.ts |
Upstream sync and fork replay |
src/renderer/src/lib/structured-agent-session-launch-callers.ts |
Upstream sync and fork replay |
src/renderer/src/lib/session-write-subscriber.ts |
Upstream sync and fork replay |
src/renderer/src/lib/pending-worktree-creation.ts |
Upstream sync and fork replay |
src/renderer/src/lib/palette-match/palette-query.ts |
Upstream sync and fork replay |
src/renderer/src/lib/browser-palette-search.ts |
Upstream sync and fork replay |
src/renderer/src/i18n/locales/zh.json |
Upstream sync and fork replay |
src/renderer/src/i18n/locales/ko.json |
Upstream sync and fork replay |
src/renderer/src/i18n/locales/ja.json |
Upstream sync and fork replay |
src/renderer/src/i18n/locales/es.json |
Upstream sync and fork replay |
src/renderer/src/hooks/settings-navigation-interface-sections.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/remote-workspace-session-merge.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/ipc-events-test-harness.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/ipc-events-agent-status-window-test-fixtures.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/ipc-events-agent-status-store-test-fixtures.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/installed-agent-skill-discovery-cache.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/composer-state/full-creation-execution.test.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/composer-state/folder-submit-orchestration.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/composer-state/composer-submit-orchestration.ts |
Upstream sync and fork replay |
src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts |
Upstream sync and fork replay |
src/renderer/src/components/workspace-surface-projection.ts |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/TerminalPaneSurface.tsx |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/pty-connection/unbound-pane-spawn-recovery.ts |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/pty-connection/direct-ssh-retry-status.ts |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/pty-connection-types.ts |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.ts |
Upstream sync and fork replay |
src/renderer/src/components/terminal-pane/pane-helpers.ts |
Upstream sync and fork replay |
src/renderer/src/components/tab-bar/tab-bar-static-create-menu.tsx |
Upstream sync and fork replay |
src/renderer/src/components/status-bar/resource-session-classification-parity.test.ts |
Upstream sync and fork replay |
src/renderer/src/components/sidebar/worktree-list/viewport/virtual-row-context.ts |
Upstream sync and fork replay |
src/renderer/src/components/sidebar/worktree-list/drag/use-native-drag.ts |
Upstream sync and fork replay |
src/renderer/src/components/sidebar/worktree-list/drag/pointer-commit.ts |
Upstream sync and fork replay |
src/renderer/src/components/sidebar/sidebar-host-options.test.ts |
Upstream sync and fork replay |
src/renderer/src/components/sidebar/fork-worktree-groups/WorktreeGroupMenuItems.tsx |
Upstream sync and fork replay |
src/renderer/src/components/settings/AppearancePane.tsx |
Upstream sync and fork replay |
src/renderer/src/components/NotificationCardStack.tsx |
Upstream sync and fork replay |
src/renderer/src/components/new-workspace/smart-workspace-name-input-surface.tsx |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/use-native-chat-picker-command-dispatch.test.tsx |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/use-native-chat-composer-catalog.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/NativeChatView.tsx |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/native-chat-prompt-document.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/use-native-chat-live-session.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/native-chat-view-state.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/native-chat-session-transport.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/native-chat-runtime-owner.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/native-chat-runtime-contract.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/native-chat-pagination.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-native-chat-relay/native-chat-live-status.ts |
Upstream sync and fork replay |
src/renderer/src/components/native-chat/fork-agent-composer/AgentComposerActions.tsx |
Upstream sync and fork replay |
src/renderer/src/components/editor/combined-diff/browse-files/combined-diff-file-tree-filter.ts |
Upstream sync and fork replay |
src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts |
Upstream sync and fork replay |
src/renderer/src/components/dashboard/dashboard-card-labels.ts |
Upstream sync and fork replay |
src/renderer/src/components/dashboard/agent-finished-timestamp.ts |
Upstream sync and fork replay |
src/renderer/src/components/dashboard/agent-finished-timestamp.test.ts |
Upstream sync and fork replay |
src/renderer/src/components/cmd-j/palette-host-badge.ts |
Upstream sync and fork replay |
src/renderer/src/components/automations/use-automations-page-local-state.ts |
Upstream sync and fork replay |
src/renderer/src/components/automations/use-automation-editor-actions.ts |
Upstream sync and fork replay |
src/renderer/src/components/automations/AutomationSchedulePicker.test.ts |
Upstream sync and fork replay |
src/renderer/src/components/automations/AutomationSchedulePicker.render.test.tsx |
Upstream sync and fork replay |
src/renderer/src/components/automations/AutomationEditorDialog.tsx |
Upstream sync and fork replay |
src/renderer/src/assets/main.css |
Upstream sync and fork replay |
src/preload/updater-package-recovery.test.ts |
Upstream sync and fork replay |
src/preload/ssh-authority-forwarding.test.ts |
Upstream sync and fork replay |
src/preload/pty-snapshot-capability-ipc.test.ts |
Upstream sync and fork replay |
src/preload/index.ts |
Upstream sync and fork replay |
src/preload/app-restart-checkpoint-routing.test.ts |
Upstream sync and fork replay |
src/preload/api/runtime-api.ts |
Upstream sync and fork replay |
src/preload/api/os-permission-api.ts |
Upstream sync and fork replay |
src/preload/api/notifications-bridge.ts |
Upstream sync and fork replay |
src/preload/api-types.ts |
Upstream sync and fork replay |
src/main/wsl-running-path-filter.ts |
Upstream sync and fork replay |
src/main/windows/windows-pty-job.win32.test.ts |
Upstream sync and fork replay |
src/main/windows-native-registry.ts |
Upstream sync and fork replay |
src/main/startup/windows-shell-path-ownership.ts |
Upstream sync and fork replay |
src/main/startup/main-process-quit.ts |
Upstream sync and fork replay |
src/main/ssh/ssh-relay-session.ts |
Upstream sync and fork replay |
src/main/skills/skill-package-identity.ts |
Upstream sync and fork replay |
src/main/runtime/worktree-pty-host-fence.ts |
Upstream sync and fork replay |
src/main/runtime/terminal-tail-sentinel-index.test.ts |
Upstream sync and fork replay |
src/main/runtime/structured-conversation-tab-replacement.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-worktree-agent-source.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-rpc/runtime-rpc-mobile-method-allowlist.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-rpc-mobile-terminal-streaming.test.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-rpc-mobile-method-allowlist-fixtures.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-remote-managed-worktree-create.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-local-worktree-terminal-startup.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-folder-worktree-create.ts |
Upstream sync and fork replay |
src/main/runtime/runtime-desktop-surface.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/worktree-catalog-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/terminal/terminal-query-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/terminal/terminal-output-batcher.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/terminal/terminal-lifecycle-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/status.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/stats.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/session-tab-markdown-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/pairing.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/orchestration/runs/reset-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/orchestration-worker-start-mode.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/notification-stream-policy.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/mobile-markdown-tab-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/ledger.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/index.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/host-capabilities.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/github.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/github-repo-target-schemas.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/github-issue-update-schema.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/git.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/git-diff-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/git-commit-message-generation-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/git-admission-tier-schema.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/fork-ask-question-tool/ask.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/files-target-schemas.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/diagnostics.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/client-ui.test.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/client-ui-workspace-filter-fields.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/browser-text-rpc-methods.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/browser-client-host.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/methods/accounts.test.ts |
Upstream sync and fork replay |
src/main/runtime/rpc/errors.ts |
Upstream sync and fork replay |
src/main/runtime/remote-runtime-close-intent.integration.test.ts |
Upstream sync and fork replay |
src/main/runtime/relay/relay-region-probe-log.test.ts |
Upstream sync and fork replay |
src/main/runtime/relay/relay-control-protocol.ts |
Upstream sync and fork replay |
src/main/runtime/relay/relay-control-client.test.ts |
Upstream sync and fork replay |
src/main/runtime/push/push-gateway-origin.ts |
Upstream sync and fork replay |
src/main/runtime/orchestration/worker-output-archive.ts |
Upstream sync and fork replay |
src/main/runtime/orchestration/structured-mailbox-pointer-host.test.ts |
Upstream sync and fork replay |
src/main/runtime/orchestration/structured-mailbox-pointer-delivery.ts |
Upstream sync and fork replay |
src/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.ts |
Upstream sync and fork replay |
src/main/runtime/orchestration/mailbox-pointer-state.ts |
Upstream sync and fork replay |
src/main/runtime/orchestration/mailbox-pointer-stage.ts |
Upstream sync and fork replay |
src/main/runtime/orca-runtime.test.ts |
Upstream sync and fork replay |
src/main/runtime/orca-runtime-tests/terminal-output-and-worker-recovery-part-02.spec.ts |
Upstream sync and fork replay |
src/main/runtime/orca-runtime-tests/headless-snapshots.spec.ts |
Upstream sync and fork replay |
src/main/runtime/orca-runtime-tail-wait-memo.test.ts |
Upstream sync and fork replay |
src/main/runtime/orca-runtime-get-pty-record-for-pane-key.ts |
Upstream sync and fork replay |
src/main/runtime/orca-runtime-build-pty-terminal-summary.ts |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-ready-api-key-gemini-model.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-ready-account-info-hidden.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-dialog-trust-workspace.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-dialog-model-picker.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-dialog-dismissed.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-dialog-command-palette.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-busy-turn-ended.meta.json |
Upstream sync and fork replay |
src/main/runtime/__fixtures__/antigravity-busy-mid-turn.meta.json |
Upstream sync and fork replay |
src/main/orcad/orcad-sidecar-runtime-client.ts |
Upstream sync and fork replay |
src/main/observability/diagnostic-upload-http.ts |
Upstream sync and fork replay |
src/main/observability/bundle.ts |
Upstream sync and fork replay |
src/main/native-chat/transcript-record-blocks.ts |
Upstream sync and fork replay |
src/main/native-chat/transcript-reader-codex-history-mode.test.ts |
Upstream sync and fork replay |
src/main/native-chat/transcript-line-decoders-codex.ts |
Upstream sync and fork replay |
src/main/native-chat/fork-native-chat-relay/transcript-watch-subscription.ts |
Upstream sync and fork replay |
src/main/native-chat/fork-native-chat-relay/transcript-tail-reader.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-wire/unhandled-provider-frame.test.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-wire/structured-agent-session-host-handoff.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-wire/structured-agent-session-event-sink-queue.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-wire/structured-agent-session-close-retry.test.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-wire/structured-agent-session-attach-context.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-wire/agent-session-empty-batch.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-journal/journal-store-contracts.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-journal/journal-row-schema.ts |
Upstream sync and fork replay |
src/main/native-chat/agent-session-journal/journal-item-revision.ts |
Upstream sync and fork replay |
src/main/memory/collector.test.ts |
Upstream sync and fork replay |
src/main/memory/collector-windows-sweep.test.ts |
Upstream sync and fork replay |
src/main/ipc/worktree-git-common-watch.test.ts |
Upstream sync and fork replay |
src/main/ipc/terminal-preview-output-stream.ts |
Upstream sync and fork replay |
src/main/ipc/runtime-environment-support-routing.test.ts |
Upstream sync and fork replay |
src/main/ipc/runtime-environment-handler-channels.ts |
Upstream sync and fork replay |
src/main/ipc/filesystem-watcher-local-subscription.ts |
Upstream sync and fork replay |
src/main/ipc/ai-vault.test.ts |
Upstream sync and fork replay |
src/main/ipc/ai-vault-scan-coalescing.test.ts |
Upstream sync and fork replay |
src/main/host/electron-runtime-desktop-surface.ts |
Upstream sync and fork replay |
src/main/git/command-runner/git-exec-options.ts |
Upstream sync and fork replay |
src/main/codex/config-settings-promotion.test.ts |
Upstream sync and fork replay |
src/main/codex/config-settings-baseline-upgrade.test.ts |
Upstream sync and fork replay |
src/main/codex/codex-structured-journal-limits.ts |
Upstream sync and fork replay |
src/main/codex/codex-structured-journal-contracts.ts |
Upstream sync and fork replay |
src/main/codex/codex-persistent-command-retention.test.ts |
Upstream sync and fork replay |
src/main/codex-usage/codex-usage-rollup-projections.ts |
Upstream sync and fork replay |
src/main/claude/claude-structured-session-recovery.test.ts |
Upstream sync and fork replay |
src/main/claude/claude-structured-options.test.ts |
Upstream sync and fork replay |
src/main/claude/claude-structured-item-translation.ts |
Upstream sync and fork replay |
src/main/browser/browser-client-page-inventory.ts |
Upstream sync and fork replay |
src/main/browser/browser-client-page-inventory.test.ts |
Upstream sync and fork replay |
src/main/ai-vault/session-scanner-opencode-sqlite-open.ts |
Upstream sync and fork replay |
src/main/ai-vault/session-scanner-omp-subagent-transcripts.ts |
Upstream sync and fork replay |
src/main/ai-vault/session-parse-cache-store.ts |
Upstream sync and fork replay |
src/main/agent-hooks/server/server-status-identity.ts |
Upstream sync and fork replay |
src/main/agent-hooks/server/server-status-disposition.ts |
Upstream sync and fork replay |
src/main/agent-hooks/server/server-reaping.ts |
Upstream sync and fork replay |
src/main/agent-hooks/server/server-persistence-validation.ts |
Upstream sync and fork replay |
src/main/agent-hooks/server.ts |
Upstream sync and fork replay |
src/main/agent-hooks/server-relay-listener-replay.test.ts |
Upstream sync and fork replay |
src/main/agent-awake-service-platform-assertions.test.ts |
Upstream sync and fork replay |
resources/onboarding/feature-wall/tile-01.recorded-at.json |
Upstream sync and fork replay |
native/windows-registry/package.json |
Upstream sync and fork replay |
native/windows-registry/binding.gyp |
Upstream sync and fork replay |
mobile/src/transport/settings-host-client-lifecycle.test.ts |
Upstream sync and fork replay |
mobile/src/transport/runtime-capability-probe.ts |
Upstream sync and fork replay |
mobile/src/transport/rpc-client-stream-registry.ts |
Upstream sync and fork replay |
mobile/src/transport/rpc-client-capabilities.test.ts |
Upstream sync and fork replay |
mobile/src/transport/mobile-relay-rpc-streams.ts |
Upstream sync and fork replay |
mobile/src/transport/mobile-relay-rpc-session.ts |
Upstream sync and fork replay |
mobile/src/transport/host-open-recovery.test.tsx |
Upstream sync and fork replay |
mobile/src/terminal/terminal-live-text-commit.ts |
Upstream sync and fork replay |
mobile/src/settings/voice-settings-operations.ts |
Upstream sync and fork replay |
mobile/src/settings/notification-settings-operations.ts |
Upstream sync and fork replay |
mobile/src/session/use-mobile-structured-send-operation-reconciliation.ts |
Upstream sync and fork replay |
mobile/src/session/use-mobile-session-image-attachments.ts |
Upstream sync and fork replay |
mobile/src/session/use-mobile-session-controller.ts |
Upstream sync and fork replay |
mobile/src/session/use-mobile-native-chat-image-attachments.test.ts |
Upstream sync and fork replay |
mobile/src/session/MobileNativeChatOverlay.tsx |
Upstream sync and fork replay |
mobile/src/session/MobileNativeChatComposer.tsx |
Upstream sync and fork replay |
mobile/src/session/mobile-native-chat-message-text.ts |
Upstream sync and fork replay |
mobile/src/onboarding/mobile-onboarding-styles.ts |
Upstream sync and fork replay |
mobile/src/notifications/notification-viewing-policy.ts |
Upstream sync and fork replay |
mobile/src/notifications/native-push-dismissal.ts |
Upstream sync and fork replay |
mobile/src/notifications/native-push-dismissal.ios.ts |
Upstream sync and fork replay |
mobile/src/notifications/native-notification-data.ts |
Upstream sync and fork replay |
mobile/src/notifications/desktop-notification-events.ts |
Upstream sync and fork replay |
mobile/src/host-screen/use-host-repo-metadata.ts |
Upstream sync and fork replay |
mobile/src/host-route-action-state.ts |
Upstream sync and fork replay |
mobile/src/diagnostics/diagnostics-device-operations.ts |
Upstream sync and fork replay |
mobile/src/components/new-worktree-modal-types.ts |
Upstream sync and fork replay |
mobile/src/components/MobileSelectableText.tsx |
Upstream sync and fork replay |
mobile/src/components/mobile-rich-markdown-editor-script-string.ts |
Upstream sync and fork replay |
mobile/src/components/mobile-rich-markdown-editor-script-secondary.ts |
Upstream sync and fork replay |
mobile/src/components/mobile-rich-markdown-editor-document-suffix.ts |
Upstream sync and fork replay |
mobile/pnpm-workspace.yaml |
Upstream sync and fork replay |
mobile/modules/orca-notification-dismissal/package.json |
Upstream sync and fork replay |
mobile/modules/orca-notification-dismissal/expo-module.config.json |
Upstream sync and fork replay |
mobile/index.js |
Upstream sync and fork replay |
mobile/app/mobile-onboarding.tsx |
Upstream sync and fork replay |
mobile/app.json |
Upstream sync and fork replay |
electron.vite.config.ts |
Upstream sync and fork replay |
docs/reference/headless-linux-server.md |
Upstream sync and fork replay |
config/scripts/vendor-feature-wall-assets.mjs |
Upstream sync and fork replay |
config/scripts/rebuild-native-deps-test-fixtures.mjs |
Upstream sync and fork replay |
config/scripts/rebuild-native-deps-node-pty.test.mjs |
Upstream sync and fork replay |
config/scripts/pr-code-change-scope.mjs |
Upstream sync and fork replay |
config/scripts/orchestration-skill-guidance.test.mjs |
Upstream sync and fork replay |
config/scripts/locale-translation-policy.mjs |
Upstream sync and fork replay |
config/oxlint-code-quality-casting.json |
Upstream sync and fork replay |
cloud/packages/relay-contract/src/index.ts |
Upstream sync and fork replay |
cloud/packages/push-contract/tsconfig.json |
Upstream sync and fork replay |
cloud/packages/push-contract/tsconfig.build.json |
Upstream sync and fork replay |
cloud/packages/push-contract/src/index.ts |
Upstream sync and fork replay |
cloud/packages/postgres-schema/tsconfig.json |
Upstream sync and fork replay |
cloud/packages/postgres-schema/tsconfig.build.json |
Upstream sync and fork replay |
cloud/infra/terraform/relay.tf |
Upstream sync and fork replay |
cloud/infra/terraform/relay-github-workflow-trust.tf |
Upstream sync and fork replay |
cloud/infra/terraform/relay-github-actions.tf |
Upstream sync and fork replay |
cloud/infra/terraform/environments/staging.tfvars |
Upstream sync and fork replay |
cloud/dev/scripts/validate-relay-capacity-plan.test.mjs |
Upstream sync and fork replay |
cloud/apps/relay/tsconfig.build.json |
Upstream sync and fork replay |
cloud/apps/relay/src/admin-token-verifier.ts |
Upstream sync and fork replay |
cloud/apps/relay-ops/src/incident-monitor.ts |
Upstream sync and fork replay |
cloud/apps/push/vitest.config.ts |
Upstream sync and fork replay |
cloud/apps/push/tsconfig.json |
Upstream sync and fork replay |
cloud/apps/push/tsconfig.build.json |
Upstream sync and fork replay |
cloud/apps/push/src/push-provider-outcome.ts |
Upstream sync and fork replay |
cloud/apps/push/src/push-delivery-payload.ts |
Push payload parsing |
cloud/apps/push/src/provider-retry-delay.ts |
Upstream sync and fork replay |
cloud/apps/push/src/canonical-base64.ts |
Upstream sync and fork replay |
.oxlintrc.json |
Upstream sync and fork replay |
.github/workflows/windows-signing-rehearsal.yml |
Upstream sync and fork replay |
.github/workflows/release-cut.yml |
Upstream sync and fork replay |
.github/workflows/mobile.yml |
Upstream sync and fork replay |
.github/workflows/mobile-ios-release.yml |
Upstream sync and fork replay |
.github/workflows/dev-channel-win-build.yml |
Upstream sync and fork replay |
.github/workflows/cloud-verify.yml |
Upstream sync and fork replay |
.github/CONTRIBUTING.md |
Upstream sync and fork replay |
Review details
Suppressed comments (1)
cloud/apps/push/src/push-delivery-payload.ts:7
- The persisted JSON is only checked for being a non-array object and then cast to
PushNotification, so malformed or stale rows bypass the required field, size, dismissal, and strict-unknown-key checks inPushNotificationSchema.DurablePushStorefeeds this value directly into dismissal and delivery code; validate it with the shared schema instead of trusting the cast.
- Files reviewed: 188/2372 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+70
to
+71
| const test = steps.find((step) => step.name === 'Test Windows-specific boundaries') | ||
| expect(test.run).toContain('config/scripts/windows-process-tree-gyp-path.test.mjs') |
Comment on lines
+286
to
+290
| <NativeChatTranscriptItems | ||
| slots={slots} | ||
| context={rowContext} | ||
| window={transcriptWindow} | ||
| /> |
| max_attempts: 3 | ||
| retry_wait_seconds: 30 | ||
| command: pnpm install --frozen-lockfile | ||
| command: pnpm install --frozen-lockfile --cpu=current,x64,arm64 |
Reconciles the terminal-dock focus redirect with v1.4.202's chat-cover rule — the dock composer, when a pane has one, takes focus directly so the manager no longer focuses the xterm beneath it, and every other pane follows upstream's new focus:!covered contract. Installs upstream's windowing test viewport in the fork's code-coloring suite, defines the REPO_ROOT the arch-guard replay needs, and restates the new Windows-lane contract as the absence this fork intends.
Upstream landed the mid-prompt `/` picker itself with broader semantics than the fork shipped: commands are offered mid-draft too and every known profile enables skills, with `dispatchable` — not `commandsEnabled` — carrying the guarantee that a mid-draft pick completes the token instead of sending it.
zpyoung
deleted the
zpyoung/auto-sync-fork-with-upstream-stablyai-orca-ru-20260914T1400
branch
September 14, 2026 15:54
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Absorbs upstream stable v1.4.202 (
b1f7085315542b37f7726f3df03a72539cafa540) into the fork, resolving everydiffering path against
config/fork-ownership.json.Previous absorbed tag:
v1.4.200. Upstream'sv1.4.201was never synced, so this run spanstwo releases.
Merge
git merge) conflicted;git merge --abort.git merge -X ourscompleted with one tree conflict:src/renderer/src/components/native-chat/NativeChatComposerField.tsx— deleted by us, modifiedby them. Honoured the fork's declared
deleted: trueexception (git rm -f).Commit accounting
395 fork commits, every SHA still reachable at its original value (set comparison against
git rev-list HEAD, not a loop). The--no-mergesrange at the merged head is 399 — the 395 plusthis run's four commits. N includes upstream commits earlier syncs absorbed from release branches;
those are not on
upstream/mainand so fall inside the range by construction.Ownership resolution
Classifier over 3530 differing paths:
checkout2246,remove78,ours88,merge-review1118.checkout.txt— reset to the tag, then swept for fork edits the reset would have dropped.49 undeclared fork edits surfaced, all from PR #55 (
feat(ledger)): 956 insertions acrossupstream-owned files, none declared as seams. Each was restored by three-way merge
(base
v1.4.200, ours = pre-merge fork tip, theirs = the tag) and is now declared. One fileconflicted —
src/main/runtime/rpc/methods/repo.ts, where v1.4.202 moved its param schemas intosrc/shared/rpc-contract/repo-params.ts; resolved onto upstream's new structure with the fork'sExpectedLedgershunks replayed.remove.txt— applied, then swept fordeleted: trueexceptions this release invalidated.One real finding: v1.4.202's new
use-native-chat-structured-composer-send.test.tsxtype-importsthe attachment type from the deleted
NativeChatComposerField. Repointed atfork-agent-composer/AgentComposerField, matching the three siblings that already take that swap,and declared as an
import-swapseam.ours.txt— audited overv1.4.200..v1.4.202. 15 exception-owned paths had upstreammovement; 13 took a real three-way merge, 2 kept the fork outright because the exception's reason is
exactly what upstream's side undoes (
docs/assets/readme-downloads.svg— fork build artifact;resources/skills/release-mapping.json— upstream's rows name tags absent from this remote).Three conflicted:
package.json— version line; fork's1.4.201-rc.0.zy03kept, as the fork owns its version.config/scripts/check-changed-code-quality.mjs— both sides added functions at one point; keptboth (fork's
filesForScan, upstream's two casting-directive checks; both already wired)..github/workflows/pr.yml— kept upstream's new Check README local links step and the fork'sfork_ownership_guardjob; honoured the fork's deletion ofpackage_windows(ci: cap sync-sized PR scans and remove Windows lanes #52).merge-review.txt— 1118 paths took a real three-way merge.--verify-residualsflagged 19that drifted, and every one was
-X oursdamage or a module split:src/shared/rpc-contract/*-params.ts,gutting
client-ui-schemas.ts(241→8 lines),session-tabs-schemas.ts(229→14),worktree-schemas.ts(215→18) and thinningartifacts.ts.-X oursdefended the fork's stalemonoliths. Each file was restored to the tag and its seam re-homed into the new shared params
module;
fork-workspace-activity-window/fork-workspace-review-filtersUI schemas moved fromsrc/main/runtime/rpc/methods/into the existingsrc/shared/feature directories so the sharedcontract does not import main.
NativeChatToolRun.tsx(upstream splitToolLineout into thenew
NativeChatToolLine.tsx) andTerminalContextMenu.tsx. Both restored to the tag with thefork delta replayed; the tool-name colouring seam followed
ToolLineinto its new module.sync-runtime-graph.tscombinesupstream's new native-chat coverage bail with the fork's dock-composer focus redirect;
native-chat-composer-state.tstakes upstream's rework wholesale (it absorbed the fork'smid-prompt
/trigger) keeping only the plugin-namespace argument;native-chat-picker-items.tsthreads upstream's newskillSigilalongside the fork'snamespacePluginSkills;protocol-version.tskeeps both new runtime capabilities;native-chat-agent-profiles.tskeeps the fork'snamespacesPluginSkillsand upstream's two newoptional fields, dropping
groupedSlashwhich upstream retired.Checklists
Tier-2 forked-copy replay. 16 copies. 12 unchanged upstream; 4 had deltas, all replayed:
use-agent-composer-keydown.ts(mid-prompt Enter no longer dispatches —autocomplete.dispatchable),use-agent-composer-keydown.test.tsx(new mid-prompt case, adapted to the fork's imports),AgentComposerField.tsx('skill'autocomplete mode removed) andagent-composer-autogrow.test.tsx(no change — see needs-attention). All 16
FORK-COPY-SHAheaders advanced tob1f7085315542b37f7726f3df03a72539cafa540.Tier-4 pending-upstream. 22 entries across 6 ledger anchors; every anchor still present. Upstream
moved on 4 of the declared paths; all 4 three-way merged and the fork's delta verified still present
afterwards. No entry resolved, declined or moved upstream, so no manifest/ledger row changed.
Feature collision. 19 features reviewed against upstream's 255 commits in range.
18 ×
none.agent-composer×possible— raised below.Localization. Upstream added no new locale, so no fork bundle gained a gap.
verify:localization-catalogpasses.Verification (local gate)
pnpm install --frozen-lockfile--verify-seams--verify-residuals v1.4.202typecheck:node/:cli/:webpnpm typecheckcomposes exactly these three)pnpm lintcheck-fork-ownership.mjsTests are left to this PR's own CI, per the skill: vitest cannot run on the sync machine and the
testmatrix here is the authoritative run.Needs attention (not blocking this merge)
fork keys in upstream's
src/renderer/src/i18n/locales/en.json. This run restored and declaredthem so the next sync keeps them, but the shape violates
AGENTS.md— a seam is a line or two,and fork locale keys belong in per-feature bundles, not upstream's catalog. Restructuring is a
human decision.
agent-composerfeature collision:possible. v1.4.202 continues migrating upstream's chatcomposer from a
textareatoNativeChatPromptEditor(contenteditable). The fork's tier-2 copykeeps its textarea because its Markdown-overlay feature is built on one. Advancing the
FORK-COPY-SHAmeans the next sync's replay diff starts after that delta, so the divergence isnow permanent unless someone decides otherwise.
projectGroup.deleteandrepo.rmare uncatalogued in v1.4.202's new params-parity gate,because feat(ledger): add typed project and group ledgers across CLI, UI, and runtimes #55 extends them with a main-side schema. Declared in
src/main/runtime/rpc/fork-rpc-params/fork-uncatalogued-methods.ts; wire parity is weaker forthose two until the schema moves shared-side.
release-mac-build.ymltook upstream'spnpm install --cpu=current,x64,arm64despite thefork's arm64-only exception. No release workflow runs in PR CI, so this is unverified until the
next cut.
typecheck:e2ehas pre-existing upstream errors (e.g.toggleWorktreeCardPropertyexistsonly in upstream's e2e specs, not in
srcat either tag). Not inpnpm typecheckand not inthis workflow's typecheck job; not caused by this sync.
$-prefix skill-picker triggering is gone — upstream's product decision in fix(native-chat): one/picker for every agent, anywhere in the prompt stablyai/orca#19832, followedthrough into the fork copies because
ComposerAutocompleteno longer models a'skill'mode.main) left open anduntouched.
Backup
origin/backup/main-20260914-140411Zatab9284520831b76432006f5d94904ec8b1f3a745.