* Restore mobile push for delivery validation
* fix(mobile): register push task before headless startup
* Add authenticated mobile push test and fix iOS release entitlements
* Mock push-test transport in notification consent tests
* Fix slept workspace test for structured remount result
* Fix mobile notification review findings
* Pad Android notification icon to prevent square cropping
* fix(mobile): present visible Android data pushes in foreground
* test: use deterministic clock for teardown deadline
* fix(mobile): present foreground pushes through Expo public APIs
* fix(mobile): check push eligibility before foreground scheduling
* fix(mobile): register push from shared host connection lifecycle
Scalar promotion omitted the marketplace and plugin tables, and the mirror rebuilt
ordinary config from canonical while only trust sections survived, so a managed-account
registration and refreshed provider metadata were both destroyed at the same boundary.
Registrations now reconcile through one baseline-aware pass before the canonical->runtime
copy: a runtime-only table is promoted, a table the canonical config removed since the
last mirror stays removed, canonical wins on an identity change, marketplace refresh
metadata is promoted only for a strictly newer valid timestamp with its paired revision,
and a plugin `enabled` toggle promotes only when the runtime alone changed it.
The settings baseline gains an optional `registrations` map at version 3. Absent means
never mirrored, which makes the v2 upgrade lossless; an older build rejects version 3 and
rebuilds, so downgrade is a safe degrade.
Verified end to end against a real codex-cli binary, which wrote the registration into a
managed home and read the promoted result back: `No marketplace plugins found.` becomes
`ponytail@ponytail installed, enabled`.
Fixes#10489Fixes#11770
Co-authored-by: BsTiger <96857444+Bongseop-Kim@users.noreply.github.com>
Co-authored-by: Rod Boev <rod.boev@gmail.com>
A managed block missing its end marker was treated as Orca-owned through EOF,
so uninstall/reinstall deleted appended user tables. The same shape existed a
second time in the Codex legacy profile cleanup.
Ownership is now two separate claims: a marker pair proves extent, and a
provider that can recognize its own emitted tables owns them wherever they
sit. An orphaned marker owns only its own line. Recognition uses the same
test for remove, install and status, so a table Orca cannot see is never one
it leaves running.
Co-authored-by: maoking <secretxierluo@gmail.com>
Fixes#18861
Four E2E specs failed once each across six main runs. Each traces to a
timing boundary the test could not control, not to product instability:
- linear-url-workspace-entry: pasted before X selection ownership landed,
delivering stale text. Gate on a clipboard read-back.
- native-chat-first-flush-race: a bare 1_500ms sleep is exactly
UNFLUSHED_SETTLE_MS, so it straddled the boundary deciding which of two
hydration paths carried the test. Observe the not-yet-flushed read
instead; a notFound is never cached, so this cannot perturb hydration.
- orchestration-idle-mail-delivery: asserted that a PTY -> daemon -> main
round trip beats a 500ms production heuristic. Use the existing
ORCA_E2E_ORCHESTRATION_POINTER_ENTER_DELAY_MS knob.
- tasks-page: the probe timeout was the one figure in the file not derived
from GITHUB_TASK_SEARCH_IDLE_MS.
worktree.spec.ts exposed a real product race rather than a test bug: the
emoji caret-restore frame stayed armed through ordinary typing, so a
late frame could yank the caret back mid-input. Cancel it on the
non-emoji onChange path.
Also repairs a stale assertion: #20025 changed
remountTerminalTabForRecovery to return a result object and updated the
sibling call site but missed this one, so the comparison to `true` could
never pass. It is a deterministic break, not a flake.
Co-authored-by: Merge Sim <sim@local>
* fix(workspaces): complete a worktree create when a post-create step throws
executeWorktreeCreation's try/catch ends once createWorktree resolves, and all
three callers fire it with a bare void and no .catch. completeWorktreeCreation
is the only thing that removes the pending creation, so a throw in that tail
left pendingWorktreeCreations and activePendingCreationId set: the creation
surface stayed up, workspaceChromeActive went false, and the finished workspace
rendered no tab chrome while its panes mounted invisibly behind the panel. It
only cleared when the user switched workspaces, because setActiveWorktree nulls
the pointer. Silently -- no toast, no error state.
activateAndRevealWorktree, ensureWorktreeHasInitialTerminal and
ensureWebRuntimeWorktreeTerminalAfterWake are all synchronous with no internal
guard; launchStructuredWorktreeSession guards only its awaited launch, and that
catch's comment already names this stranding hazard.
The worktree exists past that point, so each follow-up step is now guarded
individually and falls back to the values the skip paths already used; control
flow always reaches completion. The structured-launch cancelled/visibility
returns keep their semantics, and a throw there is treated as a failed launch,
matching what that module already returns for 'failed'. A .catch backstop on
the three call sites turns anything that still escapes -- including
prepareRequestForCreate, whose VM await has try/finally with no catch -- into a
visible error state plus toast.
Ablated: with the guards removed the new suite fails 4 of 5, the survivor being
the no-throw control.
* fix(workspaces): recover terminal after partial activation
* fix(workspaces): preserve stamped launch tab on recovery
* test(workspaces): cover recovered agent tab delivery
* test(workspaces): name recovered agent delivery coverage
---------
Co-authored-by: Merge Sim <sim@local>
planColdActivationTabDeferral can install an empty allowed set, deferring every
tab so the pane filter renders none. The drain that undoes that,
useActivationDeferredTabAdmission, depends only on backgroundMountRevision and
renderedActiveWorktreeId while reading the deferred set from a mutable ref, and
the install bumps neither: the only revision producers are the drain itself and
the background-mount event path, which the activation plan never reaches.
So this pass strands the workspace with zero panes: the worktree is already
rendered-active while the startup gate is closed, which resets
lastActivationWorktreeIdRef, then the gate opens on the same worktree and
installs a plan. Nothing re-runs the drain until the user switches workspaces
and back. The hook's own comment anticipates this launch shape and relies on
re-reading on growth, but that re-read only happens on a dep change.
applyTerminalColdActivation now returns activationDeferralPlanRevision, backed
by a ref in the parking foundation and incremented only when the plan actually
installs, which the admission effect takes as a dep. A ref rather than state
because the pass runs during render, where a setState would be a render-phase
update.
The 4-tab admission cap is deliberately untouched: it reproduces the warm set
an eager activation used to mount, so steady-state pane and WebGL-context
population is unchanged.
Ablated: with the change reverted the new suite's drain case fails on the
deferred set surviving the timers; the precondition and the away-and-back
control pass either way.
Co-authored-by: Merge Sim <sim@local>
* fix(browser): decode Chromium SameSite storage values
* fix(browser): document Firefox's real SameSite domain and pin its default-arm inputs
Third-reviewer finding: the replacement comment reproduced the failure mode this
PR exists to kill. It said "Firefox's moz_cookies uses the same 0/1/2 values",
which is true of the overlap and silently wrong about the rest of the domain.
Firefox writes 256 (nsICookie SAMESITE_UNSET) for every cookie with no SameSite
attribute -- the most common shape in a modern profile -- NULL on pre-v10 rows,
and 0 for explicit None OR a legacy unset row the schema-15 migration left
behind, which are not distinguishable in the column. All of those must land on
unspecified, so the default arm is load-bearing for Firefox rather than
incidental. A later reader making the switch exhaustive against the old comment
would have regressed Firefox silently.
Also pins the inputs that were riding the default untested: 256, and the
non-integer arrivals (null, undefined, NaN) that the `?? -1` scan fallback and
pre-v10 Firefox rows produce. Decoder behaviour is unchanged; 11/11 pass.
---------
Co-authored-by: Merge Sim <sim@local>
* docs(agent-status): plan PR 1b at file level
Names the five RuntimeAgentRowStore call sites and what each becomes, why
terminalHandle has to be stamped before the store can go, and the one
intended behavior change.
* feat(agent-status): stamp the pane terminal handle on hook-server rows
The runtime's retained row store carried the pty binding two readers need. Put
that fact on the row that already owns the pane instead, resolved through the
same lookup the renderer-facing IPC boundary runs, so the two surfaces cannot
disagree about which terminal a pane is.
Carried forward when a later write resolves no handle (only main's OSC parse
can), and never persisted: a handle belongs to the runtime that issued it.
* refactor(agent-status): route the session-tabs republish off the store
`retain()` was not only a duplicate store: its boolean return was the signal
that republished `session.tabs` for a status-only transition, which no title
change covers (#7970). `hook-status-session-tabs-invalidation.ts` already
mirrors that change set plus hook restore provenance, so route the signal off
the store rather than keep a second comparator.
Adds the status-drop arm a user dismissal emits, which the pane-clear fan-out
deliberately skips — now load-bearing, because a dismissed row leaves the
listing at once.
Installed on both hosts. orcad had neither the OSC producer nor this signal, so
its runtime observed agent status and published it nowhere; deleting the
retained copy without wiring it would list no PTY agents there at all.
* refactor(agent-status): delete the runtime's duplicate retained row store
`RuntimeAgentRowStore` held the same payload the hook server already holds, so
the same pane could legitimately read differently in the sidebar, in
`worktree ps`, and on the phone. Both of its readers move onto the store's
snapshot in `runtime-hook-agent-row-selection.ts`, and
`collectRuntimeWorktreePtyAgentSources` loses the retained-versus-hook
reconciliation that only existed because two stores could disagree.
`ConnectedPtyEvidence` trades its flat pty-id set for `ptyIdByTerminalHandle`,
which is how a row still resolves the connected PTY behind it — the
working-terminal rollup's match key, and the last rescue for a row whose pane
binding a controller incarnation nulled under it.
The one intended behavior change: a row the user dismisses on the desktop
leaves `worktree ps` and mobile at once instead of lingering until the pty
exits. One store means one dismissal.
The suites written against the retained store are rewired to a real
AgentHookServer rather than deleted, so each still asserts the listing
behavior it named.
* docs(agent-status): record what PR 1b landed
Past tense, plus two corrections to the plan: `terminalHandle` is not the pty
id (they are different identifiers, and the explicit-status reader was already
comparing against a real handle), and the legacy numeric pane key is a
consequence the plan did not name.
* fix(agent-status): harden single-store lifecycle
* fix(agent-status): preserve mobile terminal rejoin
* fix(agent-status): preserve unverifiable remote rows
* fix(agent-status): own PTY row lifecycle in hook server
* fix(agent-status): preserve state and renew freshness
* fix(agent-status): ignore freshness for dismissed identity rows
* fix(agent-status): fence orcad observed identities
* fix(orcad): always release daemon adapter on cleanup
* fix(agent-status): cover remint and headless lifecycle edges
* fix agent status identity recovery gaps
* fix(agent-status): suppress duplicate child-only row mutation
* test(runtime): preserve hook store wiring in transcript harness
---------
Co-authored-by: Merge Sim <sim@local>
* fix(mobile): surface host create warnings and terminal-create errors
A workspace created from the phone could land on "No tabs in this session"
with a bare red "Failed to create terminal" and no way to tell why. Two
independent drops hid the host's own explanation:
- createWorktreeWithNameRetry returned only {worktreeId, name}, discarding
worktree.create's `warning`, and hostNewWorktreeSessionRoute built the
session route with only `name` + `created=1`. The session screen has always
had the banner (MobileSessionContentRow + createWarningState) -- only the
tasks create path ever fed it, so the New Workspace path could never report
a startup terminal that failed to spawn.
- handleCreateTerminal collapsed every failure to the literal
'Failed to create terminal', throwing away response.error.message.
Both now propagate, so the daemon's pty-allocation hint ("Your system cannot
allocate any more pty devices.") reaches the phone instead of dying in the
main process. Behaviour is otherwise unchanged: a blank warning is still
omitted from the route, and a host that gives no reason still reads
'Failed to create terminal'.
* test(mobile): refresh route parity baselines
---------
Co-authored-by: Merge Sim <sim@local>
* feat(native-chat): show when a Codex goal is set, changed, or cleared
Codex never emits the model's `create_goal` call as an item, so
`thread/goal/updated` is the only truthful evidence that a goal exists. Both
goal notifications were classified `status-chrome`, which meant no typed
handler read them and no row was written -- the only thing reaching the reader
was the model's own prose. That prose can be wrong: in a session where no goal
was ever created the model still wrote "Goal created: ...".
Classify both frames as timeline-substantive and give them a sentence, so the
reader can tell a goal that exists from one the model merely claimed. Status is
translated rather than echoed, and an unrecognised future status still reads as
"Goal updated: <objective>" instead of the bare opcode.
Codex re-sends the goal as its token and time counters climb, so rows are
deduped on what a reader would notice -- objective, status and budget. A live
session sent the same goal three times in one turn with only accounting moving.
* fix(native-chat): write the goal signature separator as an escape, not a raw NUL
A literal NUL byte in the source made git classify the file as binary, which
hid its diff from review. The string built at runtime is unchanged.
* fix(native-chat): make Codex goal rows retry-safe
* fix(native-chat): preserve Codex goal identity on resume
* fix(codex): ignore empty goal clear snapshots
* fix(codex): preserve goal lifecycle across rewinds
---------
Co-authored-by: Merge Sim <sim@local>