* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules
Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.
Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):
error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse (19: copy-then-reverse -> toReversed)
warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type (aliasing footgun guard)
- typescript/no-unsafe-function-type (bans bare Function type)
- unicorn/prefer-array-flat-map (map().flat() -> flatMap())
- unicorn/prefer-regexp-test (.match() in bool ctx -> .test())
mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.
Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.
* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse
mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).
Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): attach dropped image files via bracketed paste
Dragging an image file into a terminal pane wrote the shell-escaped path
as raw PTY input. Terminal TUIs (Claude Code, Codex, etc.) only turn a
path into an image attachment when it arrives as a *bracketed paste*, so
dropped images showed up as a literal path instead of an `[Image]`
placeholder — unlike iTerm2/Warp, which wrap dropped paths in bracketed
paste, and unlike Orca's own clipboard screenshot flow (#2842).
Route dropped image files (by extension, mirroring IMAGE_MIME_TYPES)
through `wrapTerminalBracketedPasteText` with the raw, un-escaped path so
the file-existence check those tools run on the pasted path succeeds.
Non-image drops keep the original shell-escaped, space-separated
behaviour for use in shell commands.
* fix(terminal): separate image paste from following non-image path
A mixed drop where an image precedes a non-image path concatenated the
two: the image bracketed-paste payload has no trailing space, so the
next shell-escaped path was appended directly after it. Add a single
separating space after an image payload only when the next path is a
non-image — back-to-back image pastes are self-delimiting and a stray
space between them would land in the TUI input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Review terminal image drop path handling
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* feat(ssh): stream fs.readFile to lift 10MB SSH preview cap (#1095)
Replaces the single-shot fs.readFile path on the SSH relay with a
push-style stream protocol modeled on VS Code's readFileStream.
Wire shape:
- fs.readFileStream request returns metadata (streamId, totalSize,
isBinary, mimeType, chunkEncoding, resultEncoding, optional empty)
- Relay pumps fs.streamChunk notifications (256 KB base64 chunks) and
ends with fs.streamEnd or fs.streamError
- Client cancels via fs.cancelStream notification
Invariants:
- Max 16 concurrent streams per FsHandler (TooManyStreams)
- Client clamps totalSize against caps before allocating
- Sequence-number defense against out-of-order/missing chunks
- Subscribe-before-await with frame queueing until streamId is known
- Pump cleans up registry+handle in finally; disposeAll aborts before
release so in-flight reads exit cleanly instead of EBADF
- Empty files short-circuit (no streamId, no handle open)
Compat:
- New client tries fs.readFileStream first, falls back to legacy
fs.readFile on JSON-RPC -32601 (with once-per-session warn log)
- Bumps MAX_PREVIEWABLE_BINARY_SIZE 10 MB to 50 MB to match local
Tests: 91 streaming tests across relay, client, mux, integration.
Co-authored-by: Orca <help@stably.ai>
* test(ssh): wait for streamEnd instead of fixed flush() in stream test
Why: the binary-streaming test relied on 5 setImmediate ticks to drain
the pump, which is racy on slower CI runners (each handle.read is async
I/O). Swap to a deadline-bounded waitFor(streamEnd) so the test is
deterministic regardless of scheduler latency.
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): preserve small binary detection in streamed reads
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): rebind file watcher when connection id hydrates
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): refresh explorer for update-only file creates
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): recompute file watches when repo connection changes
Co-authored-by: Orca <help@stably.ai>
* Revert "fix(ssh): refresh explorer for update-only file creates"
This reverts commit 7c3c683cd0.
* fix(ssh): install relay watcher dependency
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Extracts rg and git-grep search logic into src/shared/text-search.ts so
the local main and SSH relay paths stop reinventing arg construction,
JSON parsing, submatch regex, and accumulator/truncation semantics.
Fixes silent truncation in the relay: searchWithRg used execFile with
a 50MB maxBuffer cap that rg --json easily exceeds on large repos,
dropping matches with no error surfaced to the user. The relay now
streams via spawn, matching the local path.
See docs/design/share-text-search.md for full rationale.
Co-authored-by: Orca <help@stably.ai>