Commit Graph
447 Commits
Author SHA1 Message Date
Brennan Benson f7c07409d6 docs(orca-cli): teach agents cross-project parents and re-parenting (#29900)
* docs(orca-cli): teach agents cross-project parents and re-parenting

* chore(cli): regenerate bundled orca-cli guide

* docs(orca-cli): scope the parent host rule and paired-server selectors
2026-10-20 20:47:50 -07:00
Neil fece03b5ac fix(orcad): voice dictation on headless orca serve hosts (#31217)
* fix(orcad): voice dictation on headless orca serve hosts

Mobile dictation against an orca serve host answered speech_unavailable
since serve moved to orcad: orcad never installed the speech service
factories, and the model manager and download imported Electron.

The model download now takes a host request opener (Chromium net on the
desktop, Node https on orcad, with redirects mapped to the same event),
the STT worker ships in the orcad slot, and orca serve hands orcad the
installed app's on-device recognizer. A host without a recognizer (an
SSH or WSL managed slot) lists only cloud models and sends a phone that
picked an on-device model to the setup sheet. Mobile names the fix when
an older server still has no speech at all.

The parity test now requires both roots to install speech.

Reported-by: delorean2020 (@delorean2020)

* fix(orcad): retry Node download drops, stage the recognizer on Windows, review fixes

- Node https errors (ECONNRESET, ETIMEDOUT, ...) are retryable, so an
  interrupted orcad model download resumes (Codex P2).
- On Windows orca serve hands orcad a per-version copy of the recognizer
  in the profile, so a loaded addon never locks the app's files against
  an update (Opus).
- Drop a recognizer candidate path that could never match; one SAFETY
  helper per test file instead of repeated casts; comments.

* fix(orcad): cloud speech is ready only with a key this host can decrypt; prune superseded recognizer copies

Codex round 2: a desktop-sealed OpenAI key exists on disk but orcad's
secret store cannot open it, so the catalog reported cloud models ready
and dictation failed at the end. orcad's model manager now proves the
key decrypts. The desktop keeps its existence check (no keychain prompt).

* fix(orcad): never prune staged recognizers before the profile lock; copy linked packages as real files

Codex round 3. ref-oss check: versioned copies are cleaned only by the
process that owns them, so the pre-lock prune is dropped rather than
patched.

* fix(orcad): remove a partial recognizer copy when staging fails (Codex round 4)

* fix(speech): never hand out another store's ciphertext as the OpenAI key (Codex round 5)

* refactor(orcad): no recognizer copy on Windows; Windows orcad keeps cloud dictation only

Design check after review round 5: every remaining finding came from the
Windows copy that dodges addon file locks. Dropping it shrinks the
surface to a path lookup; Linux and macOS serve keep on-device models.

* fix(speech): name the fix when an old server refuses a mic tap; always set the recognizer env; retry ENOTFOUND (final Opus review)

* fix(managed-server): probe install completeness relative to the slot dir

CI: shipping stt-worker.js added one more absolute slot path to the
install-complete probe, pushing it further past the exec-size budget.
Testing each artifact relative to the slot (inside a subshell cd) cuts
the probe from ~12.2 KB to ~3.6 KB, so it drops off the over-budget
list instead of raising a ceiling.

* fix(mobile): pin host.platform's read to a platform type, independent of which zod copy resolves

With mobile's own dependencies installed (which CI does for any PR that
touches mobile/), the e2e program resolves two zod copies, and the
salvaged platform arm from src/shared inferred as {}. The schema now
narrows through an explicit platform guard.

* fix(mobile): keep the host.platform read line intact for its RPC mutant; narrow in a second step

Mobile verify: the recorded-RPC mutant anchors on the read line, which
the previous guard rewrote, so the mutant could not apply.

* refactor(cli): app-executable resolution in its own module; drop the platform guard now zod is aligned

launch.ts went one line past max-lines with main; resolveAppRoot,
resolveForegroundOrcaExecutable, getExecutableAppArgs and
stripElectronRunAsNode move to orca-app-executable.ts (re-exported, so
callers are unchanged). With mobile and root on one zod (#31260), the
host.platform schema no longer needs the extra narrowing step.

* refactor(cli): import app-executable helpers from their module; no re-exports from launch.ts
2026-10-20 19:54:55 -07:00
Jinwoo Hong d7ef7741d1 fix(serve): restart the macOS serve supervisor from the updated app (#31200)
* refactor(serve): move the update handoff file reads and writes into their own module

Move-only: the supervisor module is at its line budget, and the handoff file
IO is a separate concern from the child supervision loop.

Claude-Session: serve-supervisor-reexec

* fix(serve): restart the macOS serve supervisor from the updated app

After a remote update on macOS, the node-mode supervisor kept running the
old Orca executable, which the installer moved aside and deleted. Under a
LaunchAgent the system charges every descendant's privacy permissions
(Accessibility, Screen Recording, files) to that supervisor, so once its
path no longer resolved, screen capture and computer use failed for the
whole served tree while the permission check still reported them granted.

Once the new bundle is verified on disk and no stop is pending, the
supervisor now re-runs itself in place (same pid) from the installed
executable instead of spawning the replacement itself. The handoff stays
install-requested, so startup's existing resume path spawns, verifies and
completes it. A --supervise supervisor that runs from its copy outside the
app restarts through the app's own CLI entry, so it copies the new release
aside again rather than mixing old scripts with the new executable. If the
re-run fails it logs and starts the replacement as before. Only applies
when serving our own executable on macOS; Linux, Windows, orcad and
overridden executables are unchanged.

Claude-Session: serve-supervisor-reexec

* fix(serve): restart a supervisor whose executable the install replaced

Three review fixes on top of the update restart:

- Decide the restart by file identity instead of which path observed the
  install. The supervisor records the device and inode of its executable
  at start; after any install wait (including resuming an interrupted
  update at startup) it restarts only if the installed executable is a
  different file. A supervisor that started on the old app before the swap
  now restarts too, and the process after a restart never restarts again.
- A relocated --supervise supervisor releases its marker on the copy it is
  leaving, so the next relocation can delete that copy. Before, every
  update kept one more full copy of the app's scripts for the life of the
  service.
- A failed process.execve aborts the process natively rather than
  throwing, so the fallback never ran. The restart now checks first that
  the executable can run and the script entry exists, and otherwise logs
  and starts the replacement directly as before.

Claude-Session: serve-supervisor-reexec

* fix(serve): restart a parked serve service whose app a desktop install replaced

When the desktop app's Update & Restart parks an `orca serve --supervise`
service for another profile, it writes no update handoff for that profile.
After the install the service's outer loop just starts its next server, so
neither update restart point ran and the supervisor kept the deleted
executable.

Every server start now runs the same identity check right after the
park/stop gate and before spawning, so a supervisor whose executable was
replaced restarts in place first. The check is a no-op when the executable
is unchanged, so first starts and the process after a restart do not loop.

The test drives the real park and commit functions, so the CLI typecheck
project now includes that updater module (it imports only shared code).

Claude-Session: serve-supervisor-reexec

* refactor(serve): check for a replaced app in the one place every server start passes

The restart check ran at three call sites and was threaded through the update
loop as a hook, and each review round found another start path it missed. Every
path (first start, resumed update, after an install, after a park or crash)
spawns its server through the function spawnElectronServe builds, after its stop
and park gates, so the check now lives in that spawn and nothing else carries it.

The restart moves to its own module, the hook and the handoff-file move it
forced are gone, and the tests drive serveOrcaApp instead of the hook.

Claude-Session: serve-supervisor-reexec
2026-10-20 22:01:54 -04:00
Jinwoo Hong 4e483ae2e6 perf(serve): clone the supervisor's code copy in one step on macOS (#31303)
* perf(serve): copy the supervisor's code aside with one cp clone on macOS

orca serve --supervise copies the app's unpacked code and node_modules out
of the app before re-running itself from the copy. Node's fs.cp clones those
~6,000 files one at a time, which took 8-15 s in the foreground and around
35 s under the service manager's throttled I/O. One /bin/cp -cR per tree
does the same copy in about 1.5 s.

The clone-then-plain-copy fallback the terminal host copy already used now
lives in src/shared/macos-tree-clone.ts, and both copies call it. Linux keeps
fs.cp with reflink cloning, and Windows does not support --supervise.

Claude-Session: serve-snapshot-clone

* fix(serve): let the macOS tree copy run without a time limit and throw on failure

The shared cp helper ran under runProcess's 30 s default time limit, which the old fs.cp copy never had. At background I/O priority a large tree can outlast it, so the supervisor would quietly give up and run from the app itself. The helper now runs cp with no limit; the terminal helper copy stays bounded by its deadline signal.

The helper moves to src/main beside the asar-safe rm and uses it directly, so it no longer takes an rm option. It throws instead of returning a boolean, so copyBundle is gone. The relocation reads process.platform instead of a platform dependency, and its copy test now runs on every platform but Windows.

Claude-Session: serve-snapshot-clone

* fix(build): keep the macOS tree copy module where the packaged CLI loads it

The serve supervisor's relocation code in the CLI now imports the macOS
tree copy module from the main-process sources. The CLI is compiled file by
file, but the later main-process build replaces out/main and keeps only the
files it is told to build as entries, so the packaged CLI failed at load
with "Cannot find module '../../main/macos-tree-clone'" on every platform,
and the CLI runtime verifier rejected the build.

Register the module as a CLI main entry, like the other main modules the
CLI imports, and unpack it from the app archive. Its asar-safe removal
dependency is bundled into a shared main chunk, which is already unpacked.
The build output contract test now covers both.

Claude-Session: serve-snapshot-clone

* fix(build): unpack the asar-safe rm the CLI's tree clone imports

The compiled CLI layout keeps out/main/macos-tree-clone.js requiring
./asar-transparent-fs directly, and that file was still packed, so the
packaged CLI failed to load (caught by packaged-cli-assets in CI). The
bundled build only passed because the main build inlines it into an
unpacked chunk.

Claude-Session: serve-snapshot-clone
2026-10-20 21:42:36 -04:00
Brennan Benson c93514db5b refactor(protocol): each area owns its published runtime capability list (#30641)
* refactor(protocol): each area owns its published runtime capability list

RUNTIME_CAPABILITIES now spreads one array per area instead of a flat
hand-appended list, and runtime-capability-constants.ts is split into
the areas that own each constant. The published set is unchanged and
pinned by an exact sorted test.

* test: preserve capability baseline while allowing additions
2026-10-20 17:12:19 -07:00
Brennan Benson 6e973fa6d4 A launch can name chat or terminal: agent.launch gets a surface pick and the CLI gets --surface chat|terminal (#31191)
* feat(agent.launch): a launch can name chat or terminal, and the CLI gets --surface chat|terminal

agent.launch gains an optional `surface` field, read by hosts advertising
agent.launch.surface.v1. `terminal` always runs the agent's CLI; `chat` runs a
chat even when the user's default is a terminal, and one the host can prove it
cannot open is refused with a typed no-effects error before anything is
created, so a retry answers with the same refusal. The pick is part of the
replay fingerprint. `orca agent launch` and `orca worktree create --agent`
accept --surface chat|terminal and refuse it on a host that would drop it,
including the legacy worktree.create fallback.

* test(agent.launch): pin the surface pick beside the other pre-start failures

* fix(agent.launch): settle requested surfaces from resolved workspace facts

* fix(agent.launch): describe fallback boundaries and correct test types
2026-10-20 16:45:05 -07:00
Neil d563f8e0e0 feat(speech): set the OpenAI dictation key on a headless host (#31230)
* feat(speech): set the OpenAI dictation key on a headless host (orca speech openai-key)

Cloud dictation needs an OpenAI key on the host that transcribes, and
desktop Settings was the only way to save one. A headless orca serve or
managed server had none. speech.openAiKey.{status,set,clear} save it
through the host's existing key store and secret sealing; the CLI reads
the key from stdin so it never lands in shell history.

* fix(cli): name the update when an older server lacks the speech key methods; one key length cap; PowerShell example (Opus review)
2026-10-20 15:12:28 -07:00
Neil 49a2203b0e Remote Orca Servers: one list instead of six sections; SSH Hosts: bulk remove that stays removed (#31082)
* feat(settings): one-list Remote Orca Servers page and bulk-manageable SSH Hosts

Remote Orca Servers: replace the three mode cards, Advanced fold, duplicate
managed/SSH-access lists and troubleshooting accordion with a single list
(one row per server: status, one action, overflow menu), an always-visible
'New projects open on' row, and Share / Cloud VM cards that open dialogs.

SSH Hosts: group by source, filter, checkbox + shift-range selection, one
confirm for bulk remove (skipping hosts with a managed server or
workspaces), bulk Test. 'Sync ~/.ssh/config' no longer clears removal
tombstones; 'Restore removed hosts (n)' is a separate confirmed action.

* fix(settings): address review findings on servers and SSH hosts redesign

- SSH filter stays clearable when the list shrinks under the threshold
- Cmd/Ctrl-A only selects hosts when pressed in the list itself
- group legacy no-source hosts with main's import predicate (now shared)
- capture the shift-range anchor before queuing the selection update
- blank server names de-duplicate instead of colliding
- Share dialog Done refreshes link count and resets name editing
- Install / SSH-route dialogs reload SSH hosts when opened
- default-host row shows the selector when the default is a WSL host
- managed 'Not running' no longer masks connecting/revoked/update states
- servers without a dropped transport can be re-checked

* test(e2e): WSL host spec matches repo.rm ending a removed project's shells

repo.rm now stops the project's terminals on the host (#29964), so the
'orphan left by older builds' setup can no longer be built; use a shell
only the host knows of instead. Resolve the project path so macOS
/private/var matches what the host reports.

* fix(settings): carry 'Orca on <host>' naming and paired-twice warning into the new server list

Ports #31099 and #31100 into the redesigned rows, install dialog and SSH
host dialogs.

* test(e2e): keep the WSL spec's project path as created
2026-10-20 05:22:39 -07:00
NeilandClaude 0ee13f831d fix(ssh): say "Orca on <host>" instead of "managed server" in the UI (#31099)
* fix(ssh): say where Orca runs instead of "managed server" in the UI

Users read "managed server" as hosting run by someone else, so they declined
moving their SSH hosts. Every user-facing string now says "Orca on <host>" or
"this host's Orca server", in all six locales; Settings > "Orca on your SSH
hosts" and the setup line say the code and files stay where they are and only
Orca's list of projects, tabs and terminals moves to the host. CLI command names,
logs and identifiers are unchanged.

Fixes #30880

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): reassure only when something moves; name the host's label in its row

Review follow-ups: the reassurance shows only while Orca sets itself up or
moves a host, not on every reconnect; the converting line no longer repeats it;
the row names the SSH host's current label; the section description and the
Move anyway dialog stop repeating themselves; translate the Restart dialog's
buttons; drop "managed" from the remaining raw causes and the external
automation refusal.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-20 04:18:08 -07:00
Neil 63246eba3a fix(pairing): adding a server you already saved updates it instead of saving it twice (#31080)
* fix(pairing): pairing a saved server again refreshes its entry instead of saving it twice

* fix(pairing): only Settings refreshes a saved server in place; other add paths refuse the duplicate

* fix(pairing): a same-key refresh keeps the server's workspaces and tabs; raw add refuses; match any endpoint key

* fix(pairing): refuse a code for a hidden saved server; one choke point publishes verified status only for the pairing still saved

* refactor(pairing): verified-pairing publish in its own module; an unreadable store skips the stamp instead of failing a saved pairing

* fix(pairing): match a host key however its Base64 is spelled

* fix(pairing): a refresh keeps the saved key spelling so the host fingerprint stays put
2026-10-20 03:09:31 -07:00
Neil abd1fcf217 fix(cli): worktree create sends the host --project/--host selected (#31066)
* fix(cli): worktree create sends the host --project/--host selected

Codex review 6 #6: the CLI picked the right setup but sent only its repo id,
so a repo id set up on two hosts failed as selector_ambiguous; and
--project-host-setup ignored --host when setup ids repeated.

worktree.create gains optional repoHostId (older hosts drop it and keep the
old behaviour); the runtime narrows the repo selector to that host.

* refactor(runtime): the repo selector carries its host through every lookup

Review follow-up (Codex + Opus on #31066): a separate repoHostId was dropped
by the startup-agent lookup, the CLI's GitLab link check and the create
dedupe key. RuntimeRepoSelector now carries the host with the selector, so
showRepo/resolveRepoSelector and startup-agent resolution keep it; the
create dedupe key includes it; repo.show gains the same optional
repoHostId; the CLI spreads one CliRepoTarget into every repo lookup.
Handler-level tests cover worktree.create (plain, startupAgent,
startupDraft), agent.launch, repo.show and the dedupe key.

* test(runtime): drop a needless cast in the agent.launch host test; fold an import
2026-10-20 02:57:14 -07:00
Neil 536a32ae82 fix(updater): a live orcad's browser helper pauses for macOS desktop installs (#31059)
* fix(updater): a live orcad's browser helper pauses for macOS desktop installs

orcad starts its browser helper from the installed Orca.app, so ShipIt waits
for it, and the installer refused it as an unregistered blocker with no
supported way out. orcad now registers the helper in the existing serve
install park: it stops the helper for the install and starts it again from
the new bundle once the hold ends. The refusal card for a helper an older
orcad still runs says what to do instead of "stop the server".

Reported-by: @routesmith

* fix(orcad): retry a failed helper restart, hold through a mid-swap bundle, catalog the guidance

* fix(orcad): a new park cancels a slow helper restart; reset backoff per install
2026-10-20 01:34:52 -07:00
Neil 00d25d1ee5 Clarify automation run CLI results and follow-up targets (#28262)
Report the execution host's accepted, launching, started, skipped, failed, or completed status and print the available run/workspace/terminal identities. Add safe same-target follow-up guidance and complete UUID examples. Preserve the existing JSON envelope and run fields while adding result.runId.

Keep scheduling, persistence, request count, and SSH owner fencing unchanged. Isolate CLI output tests from inherited connection selectors.

Fixes #27542
2026-10-19 21:31:13 -07:00
Jinwoo HongandE2E Test fe6a2a0924 fix(codex): keep Orca's terminal identity in Codex commands when the user's Codex config strips environment variables (#30857)
* fix(codex): keep Orca's terminal identity in Codex commands under a filtering shell_environment_policy (#25775)

With inherit = "core" in a user's Codex config, every command Codex runs lost
ORCA_TERMINAL_HANDLE and the other ORCA_* values, so orca orchestration commands
failed with no_active_sender_terminal. Orca's codex shell function (bash, zsh,
fish, PowerShell) now passes those identity keys as per-launch
shell_environment_policy.set overrides, which Codex applies after its filter.
Skipped when the user opted into Codex's shared server, since Codex treats any
-c as opting out of it. The CLI error now names Codex's policy when it runs
inside Codex without an Orca identity. Daemon protocol 49 -> 50.

* test(windows): keep the node-as-codex Git Bash fixture free of forwarded identity

* fix(ci): keep the PowerShell wrapper inside its command-line budget; move the build contract to protocol 50; correct include_only advice

* refactor(codex): name the shared-server args for what they are; shorten the Codex identity error

* fix(codex): skip identity values holding an apostrophe instead of passing a truncated path

A TOML literal can't carry ', and Codex's fallback for the failed parse
truncates values such as /a' or /a' #b. Also drop the reversed key list
(order of distinct keys is irrelevant; tests now compare unordered) and
rename the host-side skip set to NO_DAEMON_SKIP_ARGS.

---------

Co-authored-by: E2E Test <e2e@test.local>
2026-10-19 23:54:57 -04:00
Neil e04c2f9e62 fix(cli): --environment connects to the server a differently-cased name resolved to (#30834) 2026-10-19 12:18:31 -07:00
Neil 148944c020 refactor: de-barrel remote-runtime-client, runtime-terminal-stream and runtime-terminal-inspection (D2, D3) (#30684)
* refactor: de-barrel remote-runtime-client and runtime-terminal-stream (D2)

importers name the defining modules; the two vi.mock factories that named a re-export move to
the defining module (pty-id spy) or drop the dead key (RemoteRuntimeClientError)

* refactor(renderer): de-barrel runtime-terminal-inspection (D3)

importers name runtime-terminal-input-recording, runtime-terminal-verified-input and
shared/remote-runtime-pty-id; 22 vi.mock factories move the re-exported keys to those modules
(pty-id keeps its other exports via importOriginal)

* test: drop the duplicated verified-input mock (Opus P3)
2026-10-19 03:58:26 -07:00
Neil cffcef7be0 fix(projects): setup edits and removals name the host, so the twin on another host is never touched (#30664)
* fix(projects): setup edits and removals name the host, so a twin on another host is never touched

Repo-backed project host setups take the repo id, so a project on two hosts has
two setups with one id. Update and delete took the first match by id, so editing
or removing the SSH copy could rename or remove the local one. The args gain an
optional hostId (older peers strip it and keep the first match).

* fix(projects): tolerate only the legacy runtime stamp when a named host misses an independent setup

* fix(projects): refuse a twin edit on a server that would act on the wrong twin; read legacy runtime stamps as local

* fix(cli): refuse setup --host on a server that would ignore it

* refactor(projects): exact host match only; name the host on the wire only for twins on one route

* fix(projects): a named host the catalog lacks refuses; settings remove stays by id

* refactor(projects): name hosts on the wire only for this computer's catalog; paired servers keep the by-id request
2026-10-19 03:47:23 -07:00
Neil 4f425a2861 fix(cli): refuse server names --environment cannot tell apart, and say how to pick one of twin projects (#30676)
* fix(cli): refuse a paired server name --environment cannot tell apart, and say how to pick one of twin projects

* fix(cli): point an ambiguous --worktree at its host-qualified identity

* fix(cli): keep the ambiguity listing on the paired server the command chose

* fix(cli): compare saved server names the way --environment does, and route cross-host twin projects to --project --host
2026-10-19 03:12:30 -07:00
NeilandClaude 6d8e4cfef2 refactor(shell): one shell-argument-quoting module for the duplicated quote copies (#30564)
* refactor(shell): one shell-argument-quoting module for the duplicated quote copies

Each former copy now calls the canonical function with its exact spelling; a table test pins
byte-identical output for every copy over hostile inputs. Distinct spellings stay distinct
functions: '\'' vs '"'"', the two plain-word sets, and the cmd.exe doubled quote.

Co-Authored-By: Claude <noreply@anthropic.com>

* style: keep the quoting import at the top of two import-less files

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 23:12:27 -07:00
Neil e3c9370da4 feat(agent-status): read OSC 7501 program status beside hooks (#30261)
Programs can tell Orca what they are doing through the Program Status
Protocol (OSC 7501); Claude Code, Codex, Grok, pi and oh-my-pi already do.
Orca answers the protocol's feature-detection probe (visible xterm and the
hidden-pane emulator), parses reports from PTY output on the execution host
(src/shared/program-status/), and keeps two lanes:

- Lifecycle state keeps one owner per pane. Hooks win; OSC 7501 drives the
  row when hooks are off, missing, or silent on a state the program has
  left (server/program-status/hook-precedence).
- The program's own report (progress, its one-line message, blocks such as
  sign-in) is a live-only `programStatus` facet kept per pane
  (server/program-status/facet-store), stitched onto rows at publish time
  and carried to the renderer store, mobile, paired clients and
  `worktree ps`. Facet-only changes patch just the facet.

Title-driven readers (idle edges, delivery, tui-idle, exit inference)
classify by the program's OSC 7501 activity while it reports one and fall
back to title glyphs only for agents that report nothing explicit, so they
keep working when Claude stops painting its title glyph after the probe.
2026-10-18 22:12:37 -07:00
NeilandClaude 6328a104bd refactor(ssh): split ssh-connection-utils into concretely named modules (#30488)
* refactor(ssh): split ssh-connection-utils into concretely named modules

- classifiers -> ssh-reconnect-error-classification.ts
- retry/timeout constants and sleep -> connection/ssh-connection-timing.ts
- buildConnectConfig -> connection/ssh-connect-config.ts
- credential callbacks -> connection/ssh-connection-callbacks.ts
- exec options and abort error -> connection/ssh-exec-options.ts
- wrapRemoteCommandForPosixShell -> ssh-posix-command-wrapper.ts
- shellEscape and shared/wsl-login-shell-command quotePosixShell (identical) -> shared/shell-argument-quoting.ts quotePosixShell

No re-exports are left behind.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: point the WSL git benchmark and the WSL lane route at shell-argument-quoting

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 20:48:05 -07:00
Neilandstevelliu 347d0e46a1 feat(cli): set the title of one terminal pane (#30485)
* feat(cli): set the title of one terminal pane

Adds `orca terminal set-pane-title --terminal <handle> --title <text>`; `--title ""`
clears it. The runtime resolves the live handle to its saved leaf and asks the
desktop window to name that pane through a new optional notifier hook
(`ui:setPaneTitle`). Mounted panes apply it through the same local setter the GUI
uses; parked panes get it in the saved layout their next mount reads. The tab's
own name is never touched.

Rebuilt on current main from #24755.

Co-authored-by: stevelliu <stevelliu@tencent.com>

* fix(renderer): a main-owned layout takes a CLI pane title before the window shows it

* fix(renderer): a CLI pane title reaches a never-mounted tab with a rootless layout

---------

Co-authored-by: stevelliu <stevelliu@tencent.com>
2026-10-18 19:48:36 -07:00
Neilandwangshifeng 34e03046b7 fix(terminal): host's saved pane titles reach terminal.list, and clearing the last one sticks (#30433)
A client's pane rename already lands in the host's saved layout (titlesByLeafId),
but terminal.list/show never read it, and clearing a remote tab's last pane title
sent no titles at all, which every host reads as 'unchanged'.

- terminal summaries carry an optional paneTitle from the host's saved layout;
  the CLI list prints it ahead of the terminal title. Older hosts omit it.
- clearing the last pane title of a remote tab pushes titlesByLeafId: {}, which
  old and new hosts both persist as cleared.

Supersedes #30333, which renamed the whole tab via terminal.rename.

Co-authored-by: wangshifeng <shifeng.wang@magic-shield.com>
2026-10-18 15:25:48 -07:00
4940654793 fix(cli): --host and --project accept the names host list and project list print (#30438)
* Accept host aliases and infer the host for a single-setup project

`orca worktree create --project scalp-it` failed with "Project is not set
up on the selected host" even though the project has exactly one ready
setup, and `--host trader-local` was rejected as an invalid value even
though `orca host list` prints that name. Three separate causes:

- `--project` was matched against the stored `projectId` only, which is a
  provider-scoped string (`github:owner/repo`). The display name shown by
  `orca project list` never matched, so the lookup found nothing and the
  failure was reported as a missing host.
- With no `--host`, the host fallback existed but was unreachable, because
  the project lookup above had already filtered to zero candidates.
- `parseExecutionHostId` accepts only `local`, `ssh:<id>` and
  `runtime:<id>`, so a bare alias could not parse at all. `ssh:<label>`
  already resolved labels; only the unprefixed spelling was missing.

Now:

- `--project` takes the project id or the display name, resolving through
  `project.list` and falling back to the setup's own display name for
  hosts that predate that method. Two projects sharing a name are refused
  rather than guessed.
- `--host` is only required when the project is set up on more than one
  host. A single setup is used directly; an ambiguous one fails with every
  host and the flag to select it.
- `--host <alias>` resolves against both axes: paired servers by name
  before routing, SSH targets by label once a client exists. An alias that
  names a machine on both is refused with the prefixed flag for each.

Errors now echo the flag the caller typed instead of an `ssh:` spelling
they did not use, and carry the hosts to retry with in `nextSteps`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse a host alias that collides with duplicated SSH labels

assertAliasNamesOneHost asked findSshTargetByName whether the alias also
named an SSH target, but that function returns undefined for a label two
targets share — deliberately, so an ambiguous label never resolves. Read
as a boolean it says "no SSH target by that name", so an alias naming one
paired server and two same-labelled SSH targets looked conflict-free and
resolved to the server.

The opposite direction was already covered: a unique SSH target against
two servers sharing a name fails through assertEnvironmentNameUnambiguous.
Fall back to ambiguousSshTargets so both axes refuse, and list every
colliding target in nextSteps rather than only the first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Only fall back to setup display names on a server without project.list

findProjectsByName caught every error from project.list and returned an
empty list, so a network failure, a permission error, or a genuine server
fault was indistinguishable from "no project by that name" — and the
caller was then told `Project is not set up on the selected host` for a
project that exists and is set up.

Narrow the catch to method_not_found, the only code that means the weaker
fallback is warranted, matching callProjectHostSetup and listSshTargets in
the same layer. The function now returns null for that version gap and an
empty array for a real miss, so the two cases stay distinguishable at the
call site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse a legacy display-name match that spans two projects

The pre-project.list fallback matches ProjectHostSetup.displayName, which
is the repo's folder name rather than the project's name, so two projects
holding repos with the same folder name both match. selectSetupOnHost
would then have taken the first candidate — targeting a project the caller
never named.

Reject when the matched setups span more than one projectId and name each
one, the same rule the project.list path already applies to duplicate
display names. A single project with several setups is unaffected; the
host selection below still decides between them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Spell the --host placeholder as <host> in the project setup usage lines

The worktree create usage was updated to <host> when aliases became
valid, but the four project setup-* lines kept <host-id> in both the
command specs and the root help text they mirror. Those commands accept
an alias too, so the narrower placeholder hid it from anyone reading help.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Document host names and the optional --host in the CLI docs

The published CLI reference described `--host` as taking only `local`,
`ssh:<target-id>` or `runtime:<environment-id>`, and the automations page
showed `--host <hostId>` as required alongside `--project`. Both now
understate what the commands accept, which is how the original report
started: `orca host list` prints a name, the docs never said you could
type it, so the generated id looked mandatory.

Spell out that `--host` takes the bare name from `host list` on either
axis, that a name resolves only when unique and is otherwise refused with
the prefixed flag for each candidate, and that `--host` is unnecessary
when the project has a single setup. Also note `--project` accepts the
display name from `orca project list`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Name the runtime host id as the paired-server tie-breaker in the docs

The host-selection page told readers to fall back to ids when a name is
ambiguous but only ever showed `--environment <server-name>` for paired
servers — and that selector is exactly the one that cannot separate two
servers sharing a name. Spell out the pair the resolver actually emits in
its nextSteps: `--host ssh:<target-id>` and `--host runtime:<environment-id>`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Spell the automations create usage as --project <id-or-name> [--host <host>]

`automations create` resolves --project and --host through the same
alias-capable resolver as `worktree create`, and its own notes already say
--project takes a display name and --host takes a bare host name. The usage
line still read `--project <id> [--host <id>]`, the last spot left on the old
spelling (flagged by CodeRabbit after the main merge).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give the fake runtime client cast in the host alias tests its SAFETY note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): accept every name host list and project list print, everywhere --project/--host is taken

Extends the alias work to the commands it did not reach and the names it
missed:

- `project setups --project <display name>` filtered by projectId only.
- `project setup-existing-folder|setup-clone|setup-create --project <name>`
  now send the project id that name resolves to (pass-through for ids and
  for servers without project.list).
- `--host "this machine"` (the local row's name) resolves to local, and is
  refused if an SSH target or paired server shares it.
- One pair of wrapping quotes is ignored, since cmd.exe passes single
  quotes through literally.
- With --environment/--pairing-code, a bare --host name is a machine the
  routed server reaches, not a different locally paired server.
- An unknown project is reported as a project miss, not a host miss.

Co-authored-by: Younghwan Chae <chyohw97@gmail.com>

* fix(cli): keep exact project ids over names, and refuse an unverifiable host name

An exact --project id whose only setup is not ready no longer falls through
to a project whose display name equals that id. A bare --host name that
matches a paired server now routes there only once this machine's SSH
targets were actually read, and under --environment/--pairing-code a bare
name always means a machine the routed server reaches.

Co-authored-by: Younghwan Chae <chyohw97@gmail.com>

---------

Co-authored-by: younghwan <chyohw97@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-18 15:22:37 -07:00
Neil ed3ee7e08a refactor: name the vm transcript and mobile pending-write caps for what they count (#30251)
Both cap UTF-16 string length for local display and memory, never wire bytes, so _BYTES misled readers into treating them as consumer byte budgets.
2026-10-18 00:35:30 -07:00
Neil 0d74111ee8 test(remote-runtime): pin the 12 hand-kept error-code lists to their callers' policy (#30246)
Each list that decides delivered / undelivered / recoverable / definitive by code now has a
test naming its codes, the message fragments it relies on, and nearby codes it must not
treat the same. Includes the #30146 shape: a client-minted runtime_manually_disconnected
keeps a user's close for replay. Tests only; no production change.
2026-10-18 00:24:34 -07:00
Neil d3d6162f5c test(orcad): orca serve's argv makes orcad serve the web client and 404 other paths (#30120) (#30222) 2026-10-18 00:05:29 -07:00
Neil d4aa9dcadc revert: restore pinned npx skills installer (#30207)
Reverts #30185 so the separate installer project can proceed. The restored shared command already pins skills@1.7.2 for CLI, desktop updates, and setup commands.
2026-10-17 23:24:02 -07:00
Neil ffb818bf61 Ship the locked skills installer instead of executing npx (#30185)
* security: ship a locked skills installer instead of running npx

* chore: remove retired skills process import exception

* test: include the locked installer in packaged layout fixtures

* test: reuse the real installer builder for package fixtures
2026-10-17 22:51:14 -07:00
Neil d71e3e9d45 fix(serve): orca serve on orcad serves the browser client and answers plain HTTP (#30120)
* fix(serve): orca serve on orcad serves the browser client and answers plain HTTP

orcad, the default local orca serve host since the compatibility launcher,
was never handed the app's web client directory, so its HTTP server had no
request listener: every GET hung and no Web client URL was printed (#30100).

The CLI now passes the app's unpacked out/web to orcad via --web-client-root,
a server without a web client answers plain HTTP with 404 instead of hanging,
and packaging fails when the unpacked web client or a file it loads is missing.

* test(serve): pin orca serve handing orcad the app's web client root
2026-10-17 16:51:37 -07:00
Brennan Benson 21e3d76576 feat(linear): edit and delete Linear comments from the CLI (#30003)
* feat(linear): edit and delete Linear comments from the CLI

Adds orca linear comment edit <commentId> and orca linear comment delete <commentId>
(alias rm). The comment id alone names the comment; Orca finds the connected workspace
that owns it unless --workspace is given. Both refresh a linked worktree's issue and
report unconfirmed writes with a read-back step. The orca-linear guide documents them.

* test(linear): use a checked linked worktree fixture

* fix(linear): bound and cancel comment actions
2026-10-17 13:14:48 -07:00
Neil 83baa08352 fix(cli): explain why file open fails on a server with no app window (#30014)
On a headless server (orca serve, orcad) files.open and files.openDiff
answer with the bare renderer_unavailable token, which the CLI printed
as-is. Translate it in the CLI into a clear message with next steps,
keeping a stable renderer_unavailable code. The wire answer is
unchanged because the mobile app keys its preview fallback on it.

Fixes #29822
2026-10-17 00:37:16 -07:00
Neil e1a7c9725d fix(cli): stop suggesting --host to listings that reject it (#30012)
The hostScope annotation on terminal list, worktree list and worktree ps
named --host local / --host ssh:<id> as the follow-up for an omitted host,
but none of those commands accept --host. Those hosts are reached through
the runtime that answered, so no flag selects them: report a null selector
and say so in the human scope line. Paired servers keep --environment.

Fixes #29901
2026-10-17 00:27:55 -07:00
Neil 633df3ac7a fix(cli): automations create/edit for an SSH project work against an older paired server (#29924)
Capturing the destination host called ssh.listTargetSummaries directly, so a
paired server predating it failed the whole write with a raw method_not_found.
Reuse the listTargets fallback host-selector-alternatives already applies,
factored so the destination check still surfaces every other error instead
of treating it as "no targets".
2026-10-16 19:38:32 -07:00
Neil 0bf5209ac8 fix(cli): infer the repo for worktree create when the parent is host-scoped by identity (#29909)
When two hosts share a worktree id, the caller's cwd resolves to an identity: selector, which
carries no repo id, so worktree create reported "Missing repo selector". Keep the selected
worktree id alongside the selector and infer the repo from it.
2026-10-16 19:02:47 -07:00
OrcaWinandOrcaWin 20fca451c5 test(serve): wait for supervisor signal readiness (#29527)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-16 01:01:50 -07:00
Neil bb42048601 fix(cli): describe --environment and point a stale terminal handle at it (#29453)
terminal send/read/wait listed --environment and --pairing-code with no
description, and a paired server's handle sent without --environment failed
as terminal_handle_stale with no hint that a server flag was missing.

Refs STA-6812
2026-10-15 23:28:21 -07:00
0fac887dbc feat(terminal): show a paired server's terminal in this desktop on terminal switch (#28023)
* fix(terminal): log a failed pane reveal instead of rethrowing it

The bridge already replies false when the reveal throws, so the rethrow only
surfaced as an uncaught renderer error after the caller had its answer.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(terminal): refuse terminal.reveal from paired clients

It moves this host's window. terminal.focus lets a paired client do that only
by asking for host navigation, and the callers this method serves (the CLI and
companion apps on the same machine) never arrive as paired clients.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(terminal): show a paired server's terminal in this desktop on terminal switch

A paired `orca serve` host has no window, so `orca terminal switch --environment <name>`
resolved the handle there, answered `navigated: false`, and the desktop stayed put (#24688).

Add `terminal.reveal`, a desktop runtime method that names a pane by worktree, tab and leaf, as
the owning host knows them, and runs the Agent Dashboard's click-to-focus sequence before raising
the window. When a paired server cannot navigate, `terminal switch` asks the desktop on this
machine to reveal the terminal instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(e2e): terminal switch on a paired server moves the desktop on this machine

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test: update terminal manifest count after main and assert the revealed pane renders

Main added a terminal method since this branch was cut, so the characterization
count is 39 with terminal.reveal. The e2e now proves the target pane is visible
in the DOM, not only selected in the store.

Co-authored-by: Velm14 <Velm14@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Velm14 <Velm14@users.noreply.github.com>
2026-10-15 23:26:59 -07:00
Neil e9548691ed fix(cli): page automations runs so a paired server's history fits its replies (#29446) 2026-10-15 23:24:11 -07:00
Neil f2fd97c16e fix(serve): fail when an explicit --port is taken instead of serving on another port (#29432) 2026-10-15 23:22:32 -07:00
Neil 52419be3a5 feat(linux): opt-in per-terminal memory and swap limits for the terminal host (#29413)
ORCA_TERMINAL_MEMORY_MAX and ORCA_TERMINAL_MEMORY_SWAP_MAX set MemoryMax and
MemorySwapMax on each terminal's systemd user scope. Off by default, applied only
after the probe scope shows the kernel enforcing them, and dropped (logged) where
the host cannot, so terminal scopes never turn off because of them.

Refs STA-3430
2026-10-15 23:18:21 -07:00
Neil 1faa210d4c fix(serve): reap orphaned processes when orca serve is PID 1 (#29390)
* fix(serve): reap orphaned processes when orca serve is PID 1

* fix(serve): keep AppImage, eval launches and exit status intact under the PID 1 reaper
2026-10-15 23:15:53 -07:00
OrcaWinandOrcaWin ac0d8c4fd0 test: consolidate CLI regressions and simplify IPC lifecycle proof (#29298)
* test: batch lint fixtures and remove repeated runtime scan

* test(cli): consolidate owner coverage and remove false decoding proof

* test(renderer): retain IPC lifecycle behavior without copied inventories

* test(git): keep checkout progress proof within file limit

* fix(lint): classify the shared mobile chat harness as test support

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-15 22:21:36 -07:00
Neil a54236751e fix(serve): start Linux headless serve on X11 so an inherited Wayland session can't abort it (#29260)
* fix(serve): pin headless Linux serve to X11 so an inherited Wayland session can't abort startup

Fixes #25400

* fix(serve): put --ozone-platform=x11 on the launch argv for Linux headless serve

Chromium caches its Ozone platform in PreSandboxStartup, before main JS runs,
so app.commandLine changes cannot move it off Wayland. Pin X11 on the CLI and
orcad serve spawns, and re-exec in-process serve (same pid) when Electron
already resolved Wayland.
2026-10-15 19:30:36 -07:00
Brennan BensonandOrcaWin 7c23f79622 fix(quick-commands): save agent prompts whole, bounded by what a chat message carries (#24488)
* fix(quick-commands): save agent prompts whole, bounded by what a chat message carries

Agent quick-command prompts were silently cut to 6,000 characters on save:
the editor accepted a long paste, settings normalization trimmed it, and
reopening showed the shortened copy. The 6,000 was a Windows launch speed
threshold, not a limit on prompts.

- Storage no longer trims. A save is refused, never trimmed, when a prompt's
  chat text block is over 256 KiB of JSON (measured on its real escaped size,
  the measure the chat send schema applies) or the list is too large for one
  paired reply. Host and desktop store both check before writing.
- The editor shows the size against the limit and blocks Save past it, and
  stays open with the text when the host refuses the save.
- terminal.quick-commands.long-prompts.v1: hosts withhold prompts over 6,000
  characters from clients that do not advertise it (older clients reject a
  whole list holding one); editors keep an older host's 6,000 cap.
- The phone editor no longer drops pasted text past 6,000 via maxLength.

Launch delivery of long prompts is left to the launch-prompt series
(#24257, #23962, #24080); this branch's earlier paste-after-ready delivery
duplicated it and is removed.

* fix(quick-commands): drop a stale duplicate doc comment

* fix(quick-commands): advertise long prompts to this build's own host too

* fix(quick-commands): keep submitted editor immutable until save settles

* test(quick-commands): keep storage and save lifecycle coverage focused

* fix(quick-commands): keep portalled choices within pending form ownership

* Preserve retained credentials when settings validation refuses a save

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
2026-10-15 18:37:41 -07:00
Jinwoo Hong ebef2ea99c fix(skills): share again under an existing skill bundle name as a new version (#29090)
* fix(skills): republish under an existing skill bundle name as a new version

Look up the caller's package by bundle name before packaging so a re-share
reuses its package id, and explain a name a teammate already holds instead of
a generic failure. Also stop classifying cloud request failures as filesystem
failures (same fix as #20848).

Fixes #20770

* fix(skills): keep the cloud request error importable without Electron

The runtime now classifies SkillCloudRequestError, and orcad has no Electron.

* fix(skills): map a held name only at lookup; leave classification to #20848

A finalize 409 after a signed-out preview is the user's own name, so it falls
back to the generic publish failure instead of blaming a teammate.

* fix(skills): say the skill name is in use instead of blaming a teammate

Orca has no team UI, so the copy states only what the server told us.
2026-10-15 14:09:41 -04:00
Jinwoo Hong 78bb358c72 fix(linux): run each terminal in its own systemd scope so one out-of-memory kill no longer closes every terminal (#28871)
* refactor(daemon): rename the PTY process ceiling modules to terminal scopes

Pure move ahead of making per-terminal scopes the Linux default.

* fix(linux): run each terminal in its own systemd scope

The daemon and every terminal shared one systemd scope, so one kernel OOM
kill stopped the whole scope, systemd-oomd killed every terminal at once,
and all terminals shared one TasksMax. When the daemon runs in the fresh
scope its launcher made, each terminal now gets its own transient scope
under a slice bound to the daemon's scope, as the opt-in process ceiling
already did. OOMPolicy=continue is used where a probe scope accepts it,
the ceiling becomes TasksMax per terminal, and oomd is asked to avoid the
daemon's scope. The slice's binding replaces the death watch's slice
argument and the exit-time slice stop.

* test(linux): cover per-terminal scopes in the daemon scope Docker harness

Runs on systemd 249, 252 and 257: an OOM kill in one terminal leaves the
daemon and the other terminals running, the process ceiling applies per
terminal, and stopping the daemon scope or SIGKILLing the daemon reaps the
slice and every terminal. The baseline reports today's whole-daemon kill.

* chore: prune the sync-spawn baseline after removing the exit-time slice stop

* fix(linux): read the terminal scope wrappers as no foreground process yet

systemd-run and env own a new terminal until the shell execs, which took up
to ~270 ms per terminal when 50 started at once on Ubuntu 24.04. Reported as
the foreground, they read as running work and could pass the four-poll
child-process readiness gates before the shell existed.

* fix(linux): simplify terminal scope state and keep scopes without pids delegation

One nullable runtime replaces the unread ceiling state; losing the slice
logs once and later terminals stay in the daemon scope. The probe no longer
fails when pids.max is absent, so only the ceiling turns off there. The
resolution is one on/off value, the launch id comes from the daemon scope
alone, and the ManagedOOMPreference request no longer blocks startup. The
foreground filter matches Orca's exact wrapper paths, so a user-run
systemd-run stays visible.

* refactor(linux): name the terminal slice from the daemon scope and start the oomd hint at init

The slice and scope names now come from the daemon scope's own unit name, so
nothing parses its prefix back out. The ManagedOOMPreference request moves
out of the resolver into initializeTerminalScopes, where a spawn error can
no longer land in the resolver's catch after the slice exists.

* test(linux): run the terminal scope wrapper foreground test off Windows only

* test(linux): tolerate an already unloaded scope after the baseline OOM stop

* fix(linux): read Orca's scope wrapper as the shell, and bound startup systemd calls

node-pty names the wrapper path both while it execs the shell and as its
spawn file once the foreground group's leader has exited (true | claude,
seq | less). Hiding it skipped the agent scan; reporting the shell instead
matches what the unwrapped spawn reports. Each startup systemd call now
times out after 1.5 s, so a wedged user manager turns terminal scopes off
inside the launcher's 10 s wait instead of failing the launch.

* fix(linux): mark a timed-out terminal scope setup in the daemon log

A busy user manager and one that refused both reported slice_unavailable.
The log-only off detail now carries timedOut: true for the first; the
reported ceiling reason and the wire are unchanged.
2026-10-15 03:34:22 -04:00
Jinwoo Hong 21ba1c399d fix(skills): force git messages to English so skill updates work in translated locales (#28994)
skills@1.7.2 tries `git clone --branch <sha>` first for a commit ref and falls
back to fetching the commit only when git's error text is English. Under a
Korean or German git, every pinned install and update failed. Set LC_ALL=C
for the in-app updater and `orca skills install|update`.
2026-10-15 01:04:05 -04:00
Neil 6607c54984 perf(ci): cut redundant tests and reuse expensive setup (#28669)
* perf(ci): reduce test overhead and reuse expensive build work

* test(ci): keep worker startup outside failure-case deadlines

* test: isolate push fixture advisory locks

* test(ci): preserve fixed drain retry allowance

* Align CI contracts with current triggers and guide content

* Stabilize consolidated reveal scenarios and retain behavioral CI contracts

* Fix upstream lint blockers while retaining behavior and assertions

* Classify global Jira preferences in the WSL migration inventory

* Align Jira test fixtures with startup and build safety checks

* Include package-test routing dependency in CI checkouts and fixtures

* Keep native stream classes intact in transcript dependency tests
2026-10-14 16:14:27 -07:00
NeilandEugenio Valeiras 6d04aea3f1 feat(cli): resolve Jira keys in workspace references (#28619)
Recognize Jira issue references in workspace creation while retaining site-qualified identity and existing provider parsing.

Co-authored-by: Eugenio Valeiras <eugenio.valeiras@gmail.com>
2026-10-14 03:21:31 -07:00