Start and resume Qoder through the existing execution-host selector when only the documented qoder command exists. Preserve legacy qodercli preference, explicit commands, quoting and session identity; disconnected SSH execution refuses without local fallback.
Mobile history uses command-at-create only with the optional owned-create capability and an existing stable mutation identity. Reconcile authoritative execution-host inventory before retrying creation, adopt the same surviving operation, preserve WSL/incarnation metadata and restore only missing original launch metadata. Changed retry settings cannot replace original capture. Bounded evidence expires after 15 minutes; unavailable inventory or original evidence refuses recovery and leaves surviving execution untouched. Authoritative inventory proving absence preserves the existing recreation behavior; this is not a durable exactly-once ledger for completed one-shot side effects. Older hosts retain the acknowledged create-then-send path.
Scope mobile launch authority to the current committed host/client generation, and recheck operation ownership after asynchronous preparation before later sends. Retire the mutation once the host acknowledges the resume; later ownership changes stop navigation without reporting a completed resume as failed or reusing a cached legacy pane. Preserve genuinely interrupted mutation identity. Preserve current-main OpenCode validation and merged Pi/Cursor behavior. Correct unchanged-main editor test fixtures to their production insertion-range and store contracts while retaining original assertions and production behavior.
Credit: Neil Parker (@nwparker); Soperf and jyang for Qoder integration/history groundwork in #24614; actual Pullfrog and CodeRabbit reviews and the independent Source reviewer for the mobile retry, launch capture, evidence lifetime and connection ownership findings. Conservative positional process recognition and existing folder-history matching-worktree limitations remain documented.
* Add Qoder session history and search with real CLI coverage
* Allow the real Qoder marker file to end with a newline
* Keep Qoder tool output out of history previews and search
* Keep Qoder search pages readable by older clients
* Verify persisted Qoder history after a real generated and resumed task
* Negotiate Qoder filters before searching an older execution host
* Combine search client imports for the CI plugin gate
* Keep the relay search oracle aligned with legacy agent filtering
* Register supervised Qoder China and Qwen lifecycle integration
* Cover Qoder China mobile assets and mixed-host resume gates
* Verify Qoder provider tags against the older released wire parser
* Verify China and Qwen keep independent Windows hook scripts
* Verify Qoder registrations against the installed older Windows release
* test(qoder): align search capability contracts and pin old-host fencing
* fix(qoder): rank exact picker identities and command aliases first
* test(qoder): preserve the regional CLI shared icon expectation
Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.
* fix(qoder): align China catalog entry with fallback order
---------
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
* feat(agents): add first-class DeepSeek Harness (dsh) support
Register DSH as a supervised Orca agent: catalog entry and detection for its
dsh-tui profile, status/question hooks through DeepSeek's own Claude-Code hook
bridge, composer-ready prompt delivery, session resume, headless Source Control
AI, and title identity that no longer collides with Gemini's.
* fix(dsh): reach Orca through DSH's credential scrub and stop reading its title as Gemini
DSH runs command hooks through its own shell executor, which drops every env var whose
name contains KEY, TOKEN, SECRET or PASSWORD — taking ORCA_PANE_KEY and
ORCA_AGENT_LAUNCH_TOKEN with it, so every hook exited without posting. Mirror both onto
scrub-safe aliases at spawn and restore them at the top of the DSH hook script.
Its title collided too: DSH rests on the same glyph Gemini works on, so a resting DSH
pane was relabelled Gemini CLI and reported working forever. Defer both the Gemini
classifier and the title status detector on DSH's whale, in the base module both copies
of that classifier read.
* test(mobile): repin the session-route closure for the DSH agent icon
* fix(dsh): address review — never splice user rows, cover remote panes, keep the diff off argv
- findManagedDshPatchRegion paired an orphan start marker with a later block's end, so a
truncated write made install/remove delete the user's own rows. Pair each end with the
nearest preceding start; regression test fails without the fix.
- The relay PTY env builder never applied the scrub-safe aliases, so remote DSH status
silently never appeared even with the remote hook installed.
- Source Control AI sent the whole diff on argv; send it over stdin with DSH's '-' marker.
- dsh-tui/dst already chose the interactive profile, so a workspace folder named 'web' or
'plugin' no longer marks a live agent pane non-interactive.
- Isolate USERPROFILE as well as HOME so a Windows run cannot edit the real home.
- Drop the duplicate README badge and revert an incidental doc reformat.
* refactor(dsh): share the managed-hooks reader and tighten the new modules
Reuse before reimplementing: readManagedDshHookEvents was a near-verbatim copy of Muse's,
with byte-identical private helpers. Both now call one readManagedHookEventsFromJson.
Also: one readTextOrAbsent instead of two spellings of the same read (dropping an
existsSync TOCTOU), one status() builder instead of four inline literals, rmSync(force)
instead of exists-then-unlink, and a redundant empty-string guard before JSON.parse.
The patch-file transforms lose their index juggling for a predicate plus a filter.
* fix(dsh): refuse a flow-style patch file, keep its mode, and stop the relay inheriting a pane
- applyManagedDshPatch matched only an exact `[]`, so `[] # keep empty` or a non-empty
flow sequence got a block entry appended after it — invalid YAML that would leave DSH
unable to load the user's own patch layer either. It now strips the token from an empty
sequence (keeping a trailing comment) and returns null for a non-empty one; install
reports that and changes nothing.
- The patch rewrite dropped an owner-only file to the umask default (CWE-732); pass
preserveMode.
- The relay PTY env never dropped inherited pane identity the way the local and daemon
builders do, so a spawn that specified none could inherit the relay's own and every
agent's hook would report against that pane.
* fix(dsh): keep the flow-style refusal in every status read, and scope the mode test to POSIX
A refused patch file carries no managed region, so getStatus() fell through to a bare
not_installed with detail null — the actionable 'rewrite it as a block sequence' message
only ever reached the one-shot install() return. Export the predicate and check it first,
behind one shared message constant.
The owner-only mode assertion cannot hold on Windows, where chmod only toggles the
read-only attribute and mode & 0o777 reads 0o666 for any writable file.
* docs(readme): restore the DeepSeek Harness badge lost in the rebase
* test(mobile): repin the session-route closure to the measured 4221
Measured, not derived: 4220 without the DSH icon entry, 4221 with it. Two of the three
modules above main's 4218 pin are not this change's — they arrived with the mobile work
after #22570 and were never repinned; the changelog records that split explicitly.
* fix(dsh): settle tui-idle on the agent's own hook, so supervised workers see it ready
Reported by a tester on the adhoc build: `terminal wait --for tui-idle` ran to its 90s
timeout against an already-ready DSH composer, so a supervised worker never sees the agent
as ready.
Every existing tier reads the title, and DSH deliberately carries no title status: its rest
prefix is Gemini's working glyph, so the detector reports none. A fresh first-party `done`
is better evidence than any title anyway — it is the agent's own account of its own turn,
and normalizeDshEvent drops subagent events, so it is the lead's. Scoped to DSH: for agents
whose hooks report child turns, a mid-turn `done` is the #6011 class this file prevents.
* test(daemon): record the DSH transcript's true-colour I2 divergences
Adding the dsh-tui capture to __fixtures__ enrolled it in the serialize replay sweep, where
it reports 10 I2 divergences and failed the unlisted-transcript default of 0.
Every one is the same shape — visible-grid row=0, a 24-bit background the round trip does
not restore to default — which is DSH's whale intro painting whole rows of true colour.
Verified as an upstream limitation rather than a regression by replaying against the
previous build (build-serialize-addon-at-ref.mjs --ref origin/main): I1 and I3 both hold.
* fix(dsh): return the new tui-idle verdict from the first-party done lane
Main refactored isTuiIdleSatisfied into evaluateTuiIdle, which returns a verdict rather
than a boolean. The DSH lane still returned `true`; it is tier-1 positive evidence, so it
returns READY_STRONG like the title/body lane above it. Re-verified the regression test
still fails without the lane.
* test(relay): pin the scrub-safe pane-identity aliases on the relay spawn path
The relay builds a remote pane's env itself, so the alias mirroring there had no
test: removing the call left every suite green while remote DSH status silently
vanished. Both cases fail without it.
* docs(dsh): point the hook service at the integration reference
The reference doc had no inbound link from anywhere in the repo.
* feat(agents): add first-class ZCode harness
Add ZCode (Z.ai's `zcode` CLI) as a supervised Orca agent: managed lifecycle
hooks on local, SSH and Windows hosts; status, question and approval reporting;
synthetic status titles; session resume; orchestration worker launch options;
and desktop + mobile agent-picker registration.
Written against the newly open-sourced `zai-org/ZCode` (agent CLI 0.16.9), not
against a remembered screen:
- ZCode's hook runner writes a Claude-compatible stdin alias set, so it routes
through the existing Claude-compatible vendor path while keeping its own
identity in the sidebar.
- `PermissionRequest` fires only once the approval card is on screen and racing
the user's answer, so it is proof the pane is blocked, not an auto-approval.
- ZCode's clarification tool is literally `AskUserQuestion` with Claude's
questions/options shape, so Orca's question card renders it unchanged.
- ZCode's `hooks.enabled` defaults to false, which is why configured hooks were
reported as never firing; the installer sets it.
- ZCode renames its own process to `zcode-cli`, so the expected foreground
process cannot be the launch command or dispatch refuses the pane.
- ZCode emits no OSC title in any state and repaints its ASCII banner forever,
so readiness comes from Orca's synthetic hook title and launch drafts wait on
the composer box rather than on a quiet render window.
Three files crossed their max-lines limit, so each is split along a real seam:
command-line entrypoint parsing out of agent process recognition, skill
classification out of skill root discovery, and registry coverage out of the
remote hook installer tests.
Refs #10564
* fix(zcode): drop the session-option catalog and pin the orchestration contract
ZCode's CLI exposes no `--model` flag at all, and the session-option launch path
refuses to apply any option until a model id is chosen. A catalog therefore could
not deliver `--mode` per worker, and would have accepted `--model` only to drop
it silently. Take opencode's position instead: no catalog, so `worker-start
--model` is refused with a clear message and ZCode launches with the model from
its own config. `--mode` stays reachable through agent args, which is also how
the yolo default is applied.
Add a contract test covering the parts that make ZCode a usable worker:
dispatchable foreground process, stdin prompt delivery, the prompt staying out
of the launch command, and the composer-gated draft paste.
* refactor(zcode): reuse shared helpers and cut the harness down
No behaviour change; every ZCode test still passes.
- Use installer-utils' own `hookDefinitionHasManagedCommand` instead of
re-walking a hook definition by hand, which also drops a local string reader.
- Share one `readZCodeEventMap` instead of keeping the same narrowing in both
hook-settings and hook-config-json.
- Collapse five identical error returns into one `zcodeHookError` builder, and
return early from the status branches instead of assigning through `let`.
- Split the event-to-status decision out of `normalizeZCodeEvent` into a pure
`readZCodeTurn`, so the normalizer reads as decide-then-build and stops
computing the tool name for events that never look at it.
- Take a script file name in `readManagedZCodeHookEvents` like its siblings,
which removes a `Parameters<typeof …>` indirection at the call site.
- Drop the unused `ZCodeHookEvent` export and inline a single-use path helper.
- Correct a stale comment: ZCode's loader is a strict `JSON.parse`, so the
in-place edit preserves key order and indentation, not comments.
* fix(zcode): address review — keep unmanaged event keys, correct comment, de-dupe README
- `removeZCodeManagedHooks` deleted any event key whose list ended up empty, so an
unrelated `"Notification": []` the user wrote was removed as collateral whenever a
managed hook elsewhere made the write happen. Only touch an event Orca actually
owned something in; covered by a new regression test.
- The `isNewTurnEvent` comment claimed UserPromptSubmit was ZCode's only turn
boundary while the expression below it also returned true for SessionStart. Say
what the code does: SessionStart lands the idle boundary, UserPromptSubmit is the
turn boundary (the Codex/Claude shape).
- ZCode appeared twice in the README's single agent-badge block; keep the
local-icon entry the link checker validates and drop the favicon duplicate.
* docs(zcode): call out that the desktop bundle's CLI cannot open a session
From live testing on #22464: pointing `zcode` at the desktop app's bundled
`glm/zcode.cjs` installs Orca's hooks fine but then fails with
`Cannot find package '@zcode/tui'`, so the pane never opens a session. The
symptom reads as a broken harness when the CLI simply has no TUI. Say which
build to use and how to check before reporting a problem.
Reported-by: JWu527
* feat(agents): add first-class Muse Code harness
Add Muse as a supervised Orca agent across desktop, mobile, session history, source control, local hooks, SSH, WSL, and native Windows. Preserve user settings, support Muse 1.3 hook environment allowlists, and recognize versioned foreground processes. Include question, waiting, completion, resume, and readiness coverage.
Co-authored-by: homesh-dev <300847526+homesh-dev@users.noreply.github.com>
Co-authored-by: jeffhuen <32542276+jeffhuen@users.noreply.github.com>
Co-authored-by: John Cusack <johncusackccm@gmail.com>
Co-authored-by: Adrien De oliveira <75085839+adriendeoliveira@users.noreply.github.com>
* test(agents): cover Muse remote hook registration
* test(agents): cover Muse hook and source-control contracts
* test(agents): exclude Muse hook metadata from script mode check
* test(agents): keep Muse skill picker coverage stable
* test(ai-vault): include Muse in every-agent fixture
* test(mobile): repin Muse agent icon closure
* fix(muse): detect questions and approvals from structured Muse signals
Muse 1.3 fires no hook for request_user_input, so a pending question left
the pane "working". Its internal reminder subagents also post hooks with
their own session ids (even after Stop), which surfaced "tool failed" rows
and flipped finished panes back to working.
- Read pending questions from Muse's session log
(user_input_prompt_requested/settled) via the existing transcript poll,
now generalized from Codex subagents to Muse on main and relay.
- Drop child-session hooks (SubagentStart ids, or turn_id === session_id).
- Treat Notification permission_prompt as the approval wait; PermissionRequest
also fires for auto-approved calls, so it only caches the approval card.
- Ignore Notification copy as the prompt; poll replays are not new prompts
or turn boundaries.
- Allowlist USERPROFILE so Windows cmd AutoRun doesn't fail every hook.
* perf(muse): parse only question events from the session log
Most Muse session-log lines are large model/tool records. Filter raw lines
by the user_input_prompt_ marker before JSON.parse via an optional
readJsonlCursor line filter.
* fix(muse): unwrap batched log records and scope questions to the live turn
Review follow-ups: question events inside retained_frame batches were
skipped, and a question left open by a crash or interrupt stayed pending
for the pane's life. Share the history scanner's retained_frame unwrapper,
and only report a pending question whose run_id matches the hook turn_id.
* refactor(muse): drop type assertion in retained_frame unwrap
* fix(agent-hooks): satisfy exhaustive-switch lint in transcript poll policy
---------
Co-authored-by: Adrien De oliveira <75085839+adriendeoliveira@users.noreply.github.com>
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as
a re-export barrel so the import sites did not have to change. This removes
the barrel: every consumer now imports from the module that actually declares
the type, and `src/shared/types.ts` is deleted.
Barrels hide where a type lives, make every consumer look like it depends on
the whole domain, and let an unrelated edit invalidate a module that ~2,000
files transitively import.
2,323 import declarations across 2,321 files. Rewritten mechanically: each
specifier was resolved to an absolute path via the TypeScript AST and
recomputed, rather than string-substituted, so alias forms (`@/../../shared/
types`) and per-specifier `type` modifiers survive.
Four cases the mechanical pass had to handle, each found by a gate rather than
by reading the diff:
- Modules inside `src/shared` import the barrel as `./types`, not
`shared/types`. A pre-filter on the latter string skipped 176 of them and
left imports dangling at a deleted file, which surfaced as confusing
`Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>`
errors rather than "module not found".
- The barrel RENAMED one type on the way through
(`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name
in the owning module has to be re-aliased at each consumer.
- Three test files put `;(globalThis as ...)` on the line after the import.
TypeScript parses that `;` as the import statement's terminator, so
replacing through `statement.getEnd()` deletes it and breaks ASI. The
rewrite now stops at the module specifier.
- A file that already imported directly from a module got a SECOND import
from it, because the barrel re-exported those same names — which trips
`import/no-duplicates` under `--deny-warnings`. A post-pass merges
declarations sharing a specifier and type-only-ness; the `import type` plus
`import` pair from one module is left alone, since that form is allowed.
Splitting one barrel import into several genuinely adds lines, which pushed
`terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character
import must wrap, and neither local type collapses onto one line (101 and 116
characters). Rather than contort a type declaration to fit a line budget,
`collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` —
they are pure structural operations on the layout tree and independent of PTY
ownership. `visible-worktrees.ts` similarly loses its own mini-barrel
re-export of `isDefaultBranchWorkspace`, with the four real consumers
repointed at the declaring module. No `max-lines` bypass added.
Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted
first — these projects are `composite: true` and reuse stale caches); the full
`pnpm lint` green, not just bare oxlint — the narrower local check is what let
the duplicate imports reach CI; max-lines ratchet OK at 344.
* 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>
* fix(terminal): trust Pi CSI-u Shift+Enter on Windows (#9703)
Pi enables the Kitty keyboard protocol at startup and decodes CSI-u, but
TUI_AGENT_CONFIG['pi'] never set windowsShiftEnterEncoding, so on Windows
Pi could only get CSI-u via the flaky live-KKP-flag path
(isKittyKeyboardActivePane). After a tool ran a subprocess that emitted a
reset sequence, the KKP flags dropped to 0, Orca sent Esc+CR, and Pi read
it as plain Enter -> submit. It recovered on the next pane refocus.
Set windowsShiftEnterEncoding: 'csi-u' for pi, mirroring the Droid fix
(#7668), so the trusted CSI-u route covers Pi reliably independent of
KKP-flag churn from tool subprocesses.
* fix(terminal): complete Pi Windows CSI-u trust lifecycle
* test(terminal): name foreground retry timing
* test(git): accept bounded SSH remote probes
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* Suppress Git Credential Manager OAuth popup on git clone (fixes#7652)
Orca's git runner disables the interactive credential prompt on every git
call that goes through gitExecFileAsync/gitStreamStdout, but the two raw
'git clone' spawns (desktop repos:clone and the runtime clone path) passed
no env, so they inherited process.env with no guard. On Windows a clone
that needs GitHub auth then makes Git Credential Manager pop its
'Connect to GitHub' OAuth window, and in a network-restricted intranet the
browser/device flow never completes while git's credential retry re-pops it.
Apply nonInteractiveGitEnv() to both clone spawns so the prompt is
suppressed (GCM_INTERACTIVE=never, credential.interactive=false,
GIT_TERMINAL_PROMPT=0). The credential *helper* is kept, so cached-token
clones for private repos still work; only the interactive fallback popup is
disabled and the clone fails fast with a clear error instead.
* Suppress GCM OAuth popup in agent terminals and setup hooks too (#7652)
The clone-spawn fix stopped Orca's own managed git from popping Git
Credential Manager, but git run in terminals and setup scripts inherited
process.env with no guard. That is the more likely source of the reported
loop: agents are told to run 'git pull --rebase'/'git fetch'/retry 'git
push' (preamble + conflict/push-failure prompts), and each retry re-pops
GCM's 'Connect to GitHub' window in a network-restricted intranet.
Apply the credential-prompt guard to:
- setup/archive/hook scripts (hooks.ts non-WSL exec env), which run
unattended on worktree create/archive.
- the shared PTY host env (buildPtyHostEnv), via a small
applyTerminalGitCredentialPromptGuard helper. Agent terminals are
guarded unconditionally (they cannot dismiss a GUI popup); user
terminals are guarded by default via the new
terminalSuppressGitCredentialPrompt setting so power users can opt out.
The credential helper is kept, so cached gh auth still works; only the
interactive fallback prompt is disabled. Verified end-to-end in a real
Orca terminal (GIT_TERMINAL_PROMPT=0 + GCM_INTERACTIVE=never by default;
absent when the opt-out is set).
* Scope user-terminal credential guard to Windows, add settings toggle, forward guard into WSL (#7652)
* Retrigger PR checks (Actions dropped the synchronize dispatch for 57e7ce249)
* Keep shell locale out of the terminal/hook credential guard (#7652 review fix)
* Fix Fable review findings: guard WSL hook branch, wire settings search, catalog keyword keys, sparse-env askpass, one-shot agent classification (#7652)
* fix(terminal): harden Git credential popup guard
* test(pty): cover SSH credential guard setting
* fix(git): guard remote clones and setup runners
* fix(git): scope credential guards to unattended work
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>
Enable three unicorn rules — one correctness, two performance — and fix every
existing violation repo-wide so the rules pass as errors.
prefer-number-properties (76 sites)
- parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed).
- isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its
argument, Number.isNaN does not. Verified every call site already passes a
number (Number.parseInt results, number-typed fields, Date.getTime()), so the
conversion is behavior-preserving today and guards against a future non-numeric
argument silently coercing.
prefer-array-find (26 sites)
- .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred).
Drops the intermediate array and short-circuits.
prefer-array-index-of (5 sites)
- .findIndex(x => x === v) -> .indexOf(v).
Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests),
oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023
.toReversed()); config scripts and e2e helpers run on Node 24.
Identify specific agents (e.g., codex, gemini, aider) running under
interpreter wrappers like node or python by querying and inspecting
descendant process tables.
- Add Windows process-tree queries using PowerShell and WMIC.
- Support deep process inspection on remote SSH relays.
- Retry wrapper checks in OrcaRuntime for delayed async cache resolution.
- Prevent false positives by validating only recognized agent processes.