* fix(mobile-markdown): enable keyboard dismissal while editing
Allow users to dismiss the soft keyboard while composing markdown content. Extract the MarkdownReader component into its own file and add WebView-based caret preservation to restore the cursor position after the keyboard closes. This prevents the editor from losing focus and erasing the user's selected caret location when the keyboard hides.
* improve test
* perf(native-chat): suspend hidden transcript streams
* fix(native-chat): keep assembly renders pure
* fix(native-chat): keep a transient error from stranding a revealed chat
- An error snapshot frame no longer latches frameArrived, so the in-flight
read can still seed the pane instead of leaving it on the error surface.
- Retained history is shown over a full-pane read error for the same source.
- The paged read window survives a hide/reveal; only a source change resets it.
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): match the trimmed native-chat retention signature
The shared retention type no longer takes `loading`, so mobile's call was an
excess-property error that broke the mobile typecheck job. Dropping it also
lets mobile inherit the desktop behavior: a stream error or dropped client
keeps the last transcript instead of swapping it for the error empty state.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Add copy button to quick commands with visual feedback
Quick command rows now display a copy button that copies the command body to clipboard. The button shows brief visual feedback ("Copied" or "Couldn't copy") and is disabled when the command body is empty. Includes desktop and mobile UI, tests, and full i18n support.
* fix(ci): unblock verify for quick-command copy button
Key feedback to the copied body so prop changes drop stale labels without
setState-in-effect, and mock expo-clipboard in the mobile list test.
* feat(agents): add Prime Agent as a supported TUI agent with session history
Wire Prime Intellect's prime-agent CLI (a Pi fork) into the desktop and
mobile agent catalogs following the Trae registration pattern, and into
the Agent Session History browser following the OMP pattern:
- types.ts, tui-agent-config.ts: register 'prime-agent' with argv prompt
injection behind a `--` separator (its own help documents `--` as
"treat all following arguments as messages"; without it, prompts
starting with `help`/`agents`/`-…` dispatch as subcommands or
flags), plus csi-u Shift+Enter encoding matching the Pi TUI it embeds.
- agent-kind.ts, telemetry-events.ts, agent-status-types.ts,
agent-type-label.ts, tui-agent-display-names.ts,
tui-agent-selection.ts, skills-cli-agent-keys.ts: standard per-agent
registrations.
- agent-headless-command.ts: `-p/--print` one-shot runs share the
print-mode matcher with Claude/Trae so they are not mistaken for live
interactive panes.
- agent-process-recognition: the npm shim launches a generic bundled
cli.js, so only the exact package path is an authoritative identity
(same as Pi and cursor-agent). The three per-agent regex branches are
now one table in agent-node-entrypoint-identities.ts — the module was
at its max-lines budget and a table makes the next agent one entry.
- AI Vault: sessions are Pi's message-graph JSONL under
~/.prime/agent/sessions (override PRIME_AGENT_CODING_AGENT_DIR —
Prime Agent brands Pi's env contract instead of sharing
PI_CODING_AGENT_DIR); parsed by the shared message-graph parser with
incremental append-resume; discovered locally, in WSL homes, and over
remote SSH; resumes by absolute transcript path
(`prime-agent --resume <path>`) like OMP, with session-id fallback.
- skill-discovery-sources.ts: ~/.prime/agent/skills home source.
- Catalog, i18n (en/es/ja/ko/zh), mobile registries, and a bundled
64x64 favicon (required by mobile's offline-icon invariant).
Scanner-test fixtures for OMP and Prime Agent move into
session-scanner-test-fixtures.ts and the incremental fixture into its
own module, keeping every touched file inside its max-lines budget
without ratchet bumps.
* fix(ai-vault): map custom Prime Agent roots to their sessions child
PRIME_AGENT_CODING_AGENT_DIR is consumed verbatim by the CLI as its agent
config dir, with transcripts always in <agentDir>/sessions — unlike
PI_CODING_AGENT_DIR's <home>/agent/sessions shape the shared normalizer
models. A custom root with a non-special basename (or a `.prime` leaf)
was therefore scanned as-is instead of its sessions child. Dedicated
normalizePrimeAgentSessionsDir appends `sessions` to every configured
root, taking only an explicit `.../sessions` path as-is; the shared
Pi/OMP normalizer drops the `.prime` widening it no longer needs.
Raised in review on #12935.
* fix(ai-vault): guard degenerate Prime Agent roots and cover the remote source
normalizePrimeAgentSessionsDir stripped a filesystem-root value ('/' or '//')
to '', which then joined into the relative root 'sessions' and would walk the
main-process cwd. session-scanner-roots.ts already carries this guard for the
OMP variant; apply the same fallback here.
The remote SSH source had no test: deleting jsonlSource('prime-agent', ...)
left the suite green, unlike the local path which is pinned by the
AI_VAULT_AGENTS exhaustiveness assertion in session-scanner.test.ts. Add a
case that fixes the .prime/agent/sessions root segments, the .jsonl
extension, and parser routing.
Raised in review on #12935.
* fix(ai-vault): honor Prime Agent's sessions-root env and non-interactive modes
Verified against upstream PrimeIntellect-ai/prime-agent source rather than
inferred from the CLI's help text.
config.ts getSessionsDir() reads PRIME_AGENT_SESSION_DIR (and its legacy
PRIME_AGENT_CODING_AGENT_SESSION_DIR alias) ahead of the agent dir and uses it
verbatim; setting either left the vault silently empty. It also appends
`sessions` to the agent dir unconditionally, with no basename escape hatch, so
PRIME_AGENT_CODING_AGENT_DIR=/data/sessions writes to /data/sessions/sessions
while Orca scanned /data/sessions. getAgentDir() and the session-dir override
both run through expandTildePath, so a `~` value set outside a shell resolves.
cli/args.ts also spells the non-interactive runs `--mode json|rpc|acp|daemon`,
which the shared print-mode matcher does not know, so those panes were counted
as live interactive agents and the paste-submit path would write user text into
a JSON-RPC/ACP stream. Match upstream exactly: only the space-separated form,
since `--mode=json` is not parsed by the CLI and does start the TUI.
Raised in review on #12935.
* fix(ai-vault): keep Prime Agent roots absolute and remote segments posix
Two holes in the previous commit.
The degenerate-root guard only rejected pure-separator values, so a relative
env value still resolved against the main-process cwd:
PRIME_AGENT_CODING_AGENT_DIR='.' scanned '<cwd>/sessions' and, worse,
PRIME_AGENT_SESSION_DIR='.' scanned the cwd itself. Require an absolute path in
both branches and fall back to the default otherwise.
remotePrimeAgentSessionsSegments() built its segments with the local-platform
join, so on a Windows client scanning a posix SSH host it produced
'\.prime\agent\sessions' and split('/') collapsed it to one bogus segment —
remote discovery would have found nothing. Remote roots are posix regardless of
client platform, so keep them literal. Pi and OMP are unaffected: their
normalizer returns a '.../sessions' input unchanged and never joins.
Raised in review on #12935.
* test(ai-vault): pin Windows drive roots to the Prime Agent default fallback
'C:\' and 'C:/' strip to the drive-relative 'C:', which isAbsolute
rejects on every platform — assert they land in the default fallback so
a looser truthiness check can't reintroduce a 'C:sessions' scan root.
Raised in review on #12935.
* test(ai-vault): pin the drive-relative root form and state what the posix runner can assert
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Revert "test(ime): restore coverage the composition-ownership change removed (#13168)"
This reverts commit 25a8c517e1.
* Revert "refactor(terminal): return IME composition ownership to xterm (#13128)"
This reverts commit 17b3dff3c4.
* test(ime): keep the architecture-neutral Korean trace coverage
The recorded IBus/fcitx5 and Windows MS-Korean traces from #13168 assert PTY
byte order, not composition ownership, so they still hold once the terminal
composition layer is restored. The mobile accessory-order test pinned the new
handleLiveInputChange signature and does not.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): keep the macOS Backslash bypass through the revert
The restored native-text forwarder only claims keys for input sources in its
hardcoded CJK allowlist, so third-party IMEs off that list (Qingg, #10896) still
get a raw backslash. #13128 added this bypass as a partial replacement; keep it
rather than trade the open issue back.
Scoped to the bare backslash key. The rest of shouldBypassXtermForMacNativeText
bypassed all unmodified non-ASCII text, which would race the restored forwarder.
Co-authored-by: Orca <help@stably.ai>
* fix(mobile): move the mirror-step ref write out of render
The restored hook assigned runMirrorStepRef during render, which is not
replay-safe — React can discard render work, so the mutation can leak from UI
that never commits. Its only read is inside the held-commit timer, which fires
long after commit, and the ref has a safe default, so an effect is soon enough.
Surfaced by the changed-lines React Doctor gate: the rule postdates this code,
so restoring the file re-introduced it as a new violation.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* test(terminal): pin the recorded Korean commit-before-newline order (STA-3132)
Recorded first-party on Windows 11 + Microsoft Korean (HKL 0412) against the
defect-era v1.4.164 build, with bytes read on the far side of the PTY: the
terminal received ea b0 80 0d, the syllable strictly before the CR.
The capture did not reproduce the suspected deferred-newline inversion. That
route needed a session end carrying dataPendingReconciliation, which plain
compose-then-Enter cannot produce because the IME finalizes first and the
newline is never held; back-to-back arms at 25/60/120 ms did not reach it
either. The test therefore pins the ordering rather than discriminating a fix.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): restore Hangul back-to-back flush coverage deleted with the composition layer
#12278 fixed a Hangul syllable that was not flushed before the next
composition began — the force-end path, and the one that leaves stale glyphs
behind. Returning composition ownership to xterm deleted both that patch and
its test, so nothing guarded the behavior any more.
Replays the recorded back-to-back arms (25/60/120 ms, read as 가\r나 at the
PTY) against a real xterm Terminal. It passes on main: stock xterm flushes the
committed syllable natively, so the removal was safe rather than a silent
regression.
Co-authored-by: Orca <help@stably.ai>
* test(mobile): pin accessory-byte ordering behind a Hangul commit
Returning composition ownership to xterm deleted the accessory-input commit
tests along with the hook they targeted, but the guarantee they protected is
user-visible and still applies: an accessory-bar keystroke must not overtake
the syllable being committed, and must be suppressed when that commit fails.
Drives the current hook with an Android composing-region trace rather than
reconstructing the deleted coordinator.
Co-authored-by: Orca <help@stably.ai>
* test(terminal): replay recorded IBus and fcitx5 Hangul traces offline
Commits interleaved with ASCII (한abc글) are the Linux IME gesture users report
on, and its failure modes are a lost syllable and a doubled one. That gesture
was only covered by tests/e2e/terminal-linux-ime-native.spec.ts, which needs a
Linux host running a real input framework.
Fixtures are the recorded captures from the sealed linux-final evidence run,
replayed against a real xterm Terminal: exact onData, exactly-once counts across
five repetitions, and the PTY bytes the recorded run actually received.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>