mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
stack-structure
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
381a3da46f |
feat(build): Route A, the phone's host routes bundled for the web, dark (OTA phase C, C0.7) (#21449)
* refactor(mobile-web): share the bundle manifest assembly with a second builder Manifest assembly and the on-disk write move to writeMobileWebBundleTree, and the helpers the Phase C app builder needs become exports. No behaviour change to the shipped bootstrap bundle. The CRLF guard grows two exemptions it needs once it is pointed at mobile/src: the image and font extensions .gitattributes already pins -text, and the gitignored webview engine modules the postinstall writes. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(mobile): web entry for the host route tree, and its two transport siblings The entry mounts app/h on react-native-web through expo-router's own ExpoRoot. It lives inside mobile/ so one React resolves, and supplies RpcClientProvider itself: the route tree starts below the native root layout that owns it. route-manifest.ts is a real typed module whose body the builder replaces -- esbuild has no require.context. A virtual specifier would need an ambient declaration and would leave the entry unchecked. Two .web.* siblings, both listed with a reason in web-overrides.json: the transport substitution point (a placeholder client until C0.4 lands BridgeRpcClient) and the device token store, whose native path imports expo-secure-store, which is {} on web. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(build): build:mobile-web:app, the phone's host routes bundled for the web Same builder shape as the Phase A bootstrap into a separate out/mobile-web-app, with the same manifest and the same two-scratch-build determinism check. Dark: build:mobile-web, packaging and the A2 census are untouched, and C1 is what flips build:release. Six shims, each a named Metro or RN Web gap. Images are emitted as same-origin hashed assets rather than data: URLs, because the shell's CSP sets img-src 'self'; the render check under that exact header is what found it. The script is referenced root-absolute for the same reason a <base> tag cannot be used: the document is served at every route depth and base-uri is 'none'. The budget sits below the contract's per-asset ceiling so growth trips a build rather than a refused asset on a phone. esbuild splitting does not lower it: one entry with only static imports emits one chunk (measured). Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): let React Native Web paint under the shell CSP RN Web 0.21.2 injects its stylesheet at runtime with no nonce support, so style-src 'self' blocks every rule and the page renders unstyled. Measured, not predicted: the render check serves the document under this exact header and reported the violation. 'unsafe-inline' is granted to style-src and nothing else. script-src 'self' holds, which is the directive that decides whether page code can arrive any way other than as a fetched same-origin script. The test now pins that scoping rather than rejecting the token everywhere. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * ci: prove the Route A app bundle on every PR A dedicated job, for the same reason the browser provider has one: it needs mobile/node_modules and a real browser, and the sharded test matrix would pay for both on every shard. It builds the bundle, verifies it, and runs the builder, override-census and render suites. It ships nothing. The mobile_web_app signal is lifted out of should_run the way static_analysis is. A mobile-only diff is desktop-irrelevant and skips every gated job, and that is exactly the diff that changes the page this job builds. Also the C0.6 review follow-up: mobile/package.json and mobile/pnpm-lock.yaml join the installer cache keys in the two workflows that build an installer off a hashFiles key, since beforePack requires out/mobile-web and a mobile-only change must miss those caches rather than reuse a stale build. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): pin the shipped builder against the app builder's own module name The assertion named a specifier that no longer exists, so it held vacuously. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(mobile): assert the RN Web style-src grant in the Swift checks The Swift twin of the Kotlin CSP test still required style-src 'self' and no unsafe-inline anywhere, so it trapped on the approved grant. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): make the Route A render check name what each route paints The check asserted only "some html, no errors", which expo-router's Unmatched screen satisfies: pointing HOST_ROUTE at /zzz/not-a-real-prefix stayed green. Each route now asserts content only its own component produces, and the unmatched case asserts the screen positively so the negatives discriminate. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): read the shell CSP past the comments that quote directives Both constants document themselves with // comments containing quoted directive text, which the quoted-string scan picked up as directives. One parser now drops comment lines, and iOS and Android go through it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(build): honour a .web.* route sibling in the app bundle Routes were imported by absolute path with the extension, so esbuild's resolveExtensions never applied and a .web.tsx under app/ was dead code the census still accepted. The manifest now carries a key and a module: the key stays the native filename so the URL does not move, and the module is the web sibling when one exists. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): tie each named shim to the esbuild option that implements it The shim list was asserted against a literal copy of itself, which passes however the build is configured. Each entry now carries an appliesTo that reads its own option, checked against the real options object. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * chore(build): line up the CRLF exemptions, the budget comment, and the job scope The builder loads .gif as a file but neither .gitattributes nor the CRLF scan exempted it, so the blanket eol=lf pin would have rewritten one. A test now keeps the two lists in step. The Phase C byte budget's comment sat on the asset count, and a root package.json edit could change build:mobile-web:app without running the job that proves it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * style(build): satisfy the index-check lint rule in the CSP parser Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * ci: key the installer caches on the mobile page trees too beforePack builds the mobile web bundle into the installer. Today those bytes are Phase A's, which src/** already covers, but once C1 flips the entry to mobile/app a page-only change would hit a cache holding a stale installer. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): skip the bundling tests where mobile dependencies are absent The sharded `test` job collects config/scripts/**/*.test.mjs and installs no mobile dependencies, so the two new suites failed there on "Could not resolve react-native-web". They now skip themselves with a message naming the job that runs them, and that job sets ORCA_MOBILE_WEB_APP_DEPS_REQUIRED so a missing install fails it instead of skipping everything it exists to prove. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(build): scan mobile/packages in the .web.* census The census claimed the app entry never resolves into packages/, but the dictation hook imports @orca/expo-two-way-audio and the built script carries ExpoTwoWayAudioModule.web.ts. That file is now listed with its reason, and planting a .web.* in each scanned tree proves the scan is not passing because a tree happens to be empty. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): assert the route exclusions against a tree that has them mobile/app holds no test, spec or +api file, so the exclusion rule was asserted against a tree it could not fire on. A scratch tree plants one of each; dropping the rule now fails this test. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): 404 unknown file paths in the render check's page server The server answered every path with the document, so pointing publicPath at /wrong-prefix still rendered three green routes: the script is fetched from the one prefix that is served. A path naming a file now has to come out of the bundle, which is what the shell's manifest map does. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): cover the app bundle verifier's own checks The verifier had no test. One doctors the buildId, which the packaged assert catches; the other rewrites the tree so every digest still agrees and only the two fresh builds can tell, which is what a stale out/ looks like. Deleting either check now fails a test. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * chore(build): tidy the app bundle comments and the job's path prefixes Drops an export nothing read, merges two comments that had drifted apart from the constant they describe, and corrects the claim that the job runs on every PR when it is path-gated. package.json leaves the prefix list because GLOBAL_FORCE_FILES already forces every job on it; mobile/packages/ joins it, since the page resolves a .web.ts out of there. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * style(build): merge the duplicate node:fs/promises import in the census Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile): redirect the hybrid shell route on the web page app/h/[hostId]/web.tsx reaches OrcaMobileWebShellView, whose module calls requireNativeViewManager at import. In a browser that throws before React mounts, and the route manifest imports every route statically, so one native route left the whole page blank at every URL. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): fail the render check with the error that stopped the mount The check waited on "#root has children" with Playwright's animation-frame polling, so a route module that threw at import read as a bare 30s timeout naming nothing. It now waits on a mount attribute the entry sets after the router commits, polls on a timer, and races the wait against the first uncaught error so the failure carries it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): answer the favicon the render browser asks for CI resolves the runner's Google Chrome, which requests /favicon.ico; the bundled headless shell does not. The bundle carries no icon, so the server answers 204 rather than turning a browser habit into a console error the render assertions read as a page fault. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): settle the render check's uncaught-error race without rejecting The entry throws during goto, before anything awaits the race, so a rejected promise surfaced as an unhandled rejection beside the real failure. The same signal now resolves with the error. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * chore(mobile): list the page transport in the raw request port inventory The placeholder client implements the port, so the boundary test counts it as an unlisted file. It belongs under OWNERS until C0.4's BridgeRpcClient replaces it. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
7909dad7ba |
fix(ci): keep mobile patches LF so Windows can parse them (#21439)
A Windows checkout CRLF-converted mobile/patches/*.patch because no gitattributes rule covered them, and pnpm rejected the result with ERR_PNPM_INVALID_PATCH, failing package (windows) and the verify aggregate. config/patches/*.patch has been pinned -text for this exact reason; mobile/patches/ was added later and never got the same rule. git ls-files --eol showed all three mobile patches with an empty attr against attr/-text on every config patch. |
||
|
|
ca2ae89011 |
ci: install mobile dependencies in every desktop packaging job, pin mobile page source to LF (OTA phase C, C0.6) (#21425)
* chore(mobile): pin mobile page source to LF so a Windows checkout keeps buildId The Phase C web bundle hashes every text byte under mobile/src and mobile/app into its asset digests and from there into buildId. There is no global text=auto, so a CRLF checkout on Windows would give the Windows release a different buildId for identical source, the same failure the src/mobile-web pin above exists for. All 1924 tracked files in those directories are already LF in the index, so the pin renormalises nothing. mobile/web-entry does not exist yet; the pin is forward-looking for the Phase C entry point. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * ci: install mobile dependencies in every desktop packaging job Ten workflows reach build:release/build:desktop and none of them installs mobile/node_modules. Root has no react-native, react-native-web or expo, so once the mobile web bundle builds from mobile/ its packaging jobs would fail at electron-builder's beforePack with an unresolvable import. Extract the frozen mobile install that pr.yml's static analysis job already ran inline into .github/actions/install-mobile-dependencies, and invoke it from every job the packaging census enumerates, after the root install and before the build. Same --frozen-lockfile, same lockfile-drift guard, and still no --ignore-scripts: mobile's postinstall generates the gitignored webview engine modules that tracked source imports. pr.yml now uses the action too, so there is one definition. Where a packaging job's setup-node caches the pnpm store, mobile/pnpm-lock.yaml joins cache-dependency-path so a mobile lockfile change invalidates it. Two jobs (daemon-relocation-spike, win-update-survival-e2e) do not cache at all and are left alone. The census test grows a per-job assertion that the action is present, so a new packaging job has to add the install deliberately rather than discover it at beforePack. release-cut's composite-action restore is no longer Windows-only: every platform consumes this action now, so any of them can be the leg whose cut ref predates it. No job builds anything different; this only makes mobile/node_modules present. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(ci): assert the mobile install contract on the shared action pr.yml's static analysis job no longer carries the install inline, so the scope test's findIndex by step name resolved to -1. Match the step by the action it uses, and read working-directory and --frozen-lockfile off the action itself so the job cannot keep the step while the action stops installing anything. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * chore(mobile): exempt binary asset types from the mobile LF pin /mobile/{src,app,web-entry}/** text eol=lf would mark a future PNG or font as text and rewrite its bytes on a Windows checkout. Exempt the asset types an RN page carries, the same way src/mobile-web exempts its PNG. -text after text eol=lf wins: probed a CRLF-bearing .png under the pin, it stays i/crlf attr/-text while a sibling .ts still normalises to i/lf. No tracked file changes classification; the 1924 files under mobile/src and mobile/app stay i/lf. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * ci: gate the mobile install with the build it feeds in the cached lanes win-crash-survival, win-update-survival and daemon-relocation-spike all skip electron-builder on an installer/unpacked cache hit, so an unconditional mobile install spent time on node_modules nothing then consumed. Move each `uses:` below its cache step and carry the same cache-hit condition as the build. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
ad4f26cdd4 |
feat(build): build, verify and package the mobile web bundle with every desktop release (OTA phase A, 2/5) (#21326)
* feat(mobile-web): add the Phase A bootstrap web source A peer of src/ so the root workspace owns it and mobile's separate lockfile stays out of packaging. Four assets across four content types, enough to exercise multi-asset manifest handling rather than assume it. The page reads buildId from manifest.json at runtime: buildId hashes the asset list that index.html belongs to, so injecting it into a hashed asset would make that asset's hash depend on itself. Registered as a fourth typecheck project; without it the entry would be the only TypeScript in a release path that tsc never sees. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(build): build and verify the mobile web bundle from the root workspace Root esbuild over mobile-web/ into out/mobile-web/, content-addressed as assets/<sha256>.<ext> with index.html the only stable name. buildId is the sha256 of the canonical serialization of the sorted asset list, so it is a pure function of content and usable as a cache key with no further reasoning. The verifier builds twice into scratch dirs and compares: a timestamp, an absolute path, or an unstable ordering fails the build when someone introduces it, not the first time a phone gets a spurious cache miss. It also enforces the Phase A budget of 16 assets and 256 KiB, separate from the permanent contract ceiling. build:release does not call build:desktop, so build:mobile-web is wired into build:desktop, build:release, and build:release:parallel. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * feat(packaging): fail the release when the mobile web bundle is missing or stale electron-builder only warns about a missing input, so without a beforePack guard a release ships an app that advertises the bundle capability and then errors on every request. The hash check, not the existence check, is what catches a half-written or stale out/. The source tree is excluded from app.asar; out/mobile-web ships inside it under the existing out rules, exactly as out/web does. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * refactor(mobile-web): narrow the manifest with `in` instead of a cast The changed-code casting gate rejects assertions, and `in` narrows the same untrusted JSON without one. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile-web): move the bundle source under src/ so the root guard passes .github/scripts/check-root-directory-entries.mjs blocks any new top-level entry by name, so mobile-web/ could not live at the root. The source is excluded from app.asar by the existing '!src{,/**/*}' rule; the explicit '!src/mobile-web{,/**/*}' entry stays as a marker. out/mobile-web is unaffected and still ships under the out rules like out/web. No tsconfig includes src/**, so node, web, cli, and relay do not pick the tree up; it is registered as a knip entry so audit:dead-code does not call it unused. buildId is unchanged at 9d78435e: the builder hashes content, not paths. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(build): resolve the entry-script guard through pathToFileURL `file://${process.argv[1]}` never equals import.meta.url on Windows, where that url is file:///C:/... So the builder exited 0 having written nothing and the Windows packaging job failed later, at the guard, with no clue why. Every other script in config/scripts already uses pathToFileURL; this one now does too, via an exported predicate a posix runner can exercise with a win32 path. The verify script had no entry guard at all, so importing its budget constants ran the whole verification — including its process.exit — inside the test worker. It is now a function behind the same guard. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(ci): build the mobile web bundle in the PR package job That job assembles packaging inputs step by step instead of calling build:release, so the new beforePack guard hard-failed it. The census test added here is the oracle: it walks every workflow job that invokes electron-builder without --prepackaged (which short-circuits doPack before beforePack) and requires a bundle-producing script in the same job. It goes red on exactly pr.yml's package job when this step is removed. Ten jobs covered; the other nine already ran build:release, build:release:parallel, or build:desktop. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(mobile-web): pin source line endings, because CRLF changes the buildId Every text byte under src/mobile-web is hashed into an asset digest and from there into buildId, so a CRLF checkout produces a different bundle id for the same commit: 91af2897 instead of 9d78435e. That would make a Windows-built desktop disagree with a mac-built one about which bundle a phone has cached. .gitattributes pins eol=lf for the text sources and -text for the PNG, matching the four trees already pinned for byte-hashing. The verify script asserts no source file carries a CR, so the build fails if the pin ever stops applying rather than silently shipping a second bundle identity. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * style(build): read the test's own path from import.meta.filename oxlint unicorn/prefer-import-meta-properties. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(test): census packaging jobs over raw workflow text, not re-serialized YAML yaml.stringify folds long lines, and in dev-channel-win-build.yml's build-win the fold landed between `electron-builder` and `--config`, so a real packaging job was invisible to the census: 11 jobs exist, the test saw 10. Slice each job's raw source by its parsed boundaries instead, and pin the inventory so a new packaging workflow has to be added here on purpose. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * test(build): assert the script chain the packaging census trusts The census only checks that a packaging job invokes one of ten build scripts; that those scripts still reach build:mobile-web was asserted nowhere, so a dropped link would leave every job looking covered while packaging failed at beforePack. Resolve each script for real, and pin pr.yml's hand-rolled step, since that job never calls build:release. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(build): realpath the entry path before the direct-invocation compare Node resolves symlinks in import.meta.url but not in argv[1], so `node /tmp/...` against a /private/tmp realpath compared two different strings: the builder and the verifier exited 0 having written and checked nothing. Same silent-success shape as the Windows file:// bug, so the fix sits next to it, with both seams injectable. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * style(mobile-web): format bootstrap.css with oxfmt It was the only tracked CSS failing oxfmt --check. The buildId is unchanged at 9d78435e8bb73c3341f833c20aaefbd7bfdfc414b68dadf87c1689d86728fe33, because esbuild's CSS minifier normalises the whitespace this touches before the asset is hashed. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(packaging): reject bundle files the manifest does not list The guard only walked the manifest, so a dropped assets/stale.js passed: assets are content-addressed, nothing ever overwrites a stale copy, and it would ship inside asar unreachable and unverified. Require every file under out/mobile-web to be the manifest or a listed asset. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(packaging): give beforePack an explicit mobile web bundle root The bundle guard read the repo's out/mobile-web unconditionally, so the two arch-aware packaging tests that call the real beforePack went red in the unit-test job, which never runs build:mobile-web. beforePack now takes the bundle root as a second parameter defaulting to out/mobile-web, which is what electron-builder gets, and those tests build a real bundle into a temp dir instead. The guard is neither skipped nor made tolerant of a missing bundle. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb * fix(packaging): census sees script-wrapped packers; dev verify reuses the guard The workflow census only matched a literal `electron-builder --config` line, so daemon-relocation-spike's `pnpm run build:unpack` (which packs and runs beforePack) was invisible to it. Jobs now count when any `pnpm run <script>` they invoke chains to electron-builder without --prepackaged; the spike joins the pinned list (12 jobs). verify-mobile-web-bundle.mjs re-implemented a weaker subset of the packaging guard (no safe-path check, no buildId recompute). It now calls assertMobileWebBundleBuilt, so a manifest edited after the build fails at `pnpm build:mobile-web` exactly as at beforePack. Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb |
||
|
|
6bb2b0c6d7 |
test(runtime): capture real Antigravity transcripts — the detector is inverted on live output (#19983)
* test(runtime): capture real agent PTY transcripts before rewriting Antigravity readiness
Antigravity readiness has been written five times against a five-line screen
typed from memory. There is no Antigravity transcript in this repository, so
every attempt was a guess tested against another guess. This adds the recorder,
the protocol and the fixture-driven suite so the sixth attempt can be written
against evidence, and changes no detector logic.
- config/scripts/capture-agent-pty-transcript.mjs records a live agent session
through a real PTY, escapes and wrapping intact. Ctrl-] is consumed by the
recorder and never forwarded, which is the only way to end a capture while a
dialog still owns the screen.
- config/scripts/pty-transcript-secret-scan.mjs finds account identifiers and
credentials, redacts them with same-length placeholders so wrapping survives,
and recognises its own placeholders so a scrubbed file verifies clean.
- src/main/runtime/antigravity-readiness-transcripts.test.ts asserts a verdict
per transcript and skips by name until the transcripts land, with a
doc-coverage ratchet and a guard that a fixture contains escape bytes.
The escape-byte guard exists because the three cursor-agent fixtures carry a
comment claiming they were captured verbatim through Orca, yet contain zero ESC
bytes and zero carriage returns. That comment is corrected here to say what
those files are; the fixtures and the rules built on them are untouched.
* test(runtime): capture real Antigravity transcripts, and pin what they prove
`agy` 1.1.25 turned out to be installed, so the transcripts this scaffold was
built for now exist. Six are recorded from live sessions and committed; the
rest are named as skipped, because reaching them would mean signing the
operator out or deleting their config.
The captures invert the story. On real output the shipped detector refuses a
genuinely ready screen and accepts a live `/model` picker:
- Antigravity paints a block-glyph logo down the left, so the model row never
starts a line. `startsWith('gemini', trimmedStart)` cannot match a real ready
screen, on any account or model. Stripping the logo flips the same screen to
ready, which means a decorative glyph decides readiness today.
- The `/model` picker prints `Gemini 3.x Flash` one per line, at line start, and
a bare `>` composer sits earlier in the tail. Both halves of the rule are
satisfied while a dialog owns the screen.
- For an API-key user the identity row reads `Gemini API key` — no `@`, no
domain — and `AGY_CLI_HIDE_ACCOUNT_INFO=1` removes the row entirely. The
account-row requirement of attempts 4 and 5 can never pass for those users.
- The banner is printed once and never reprinted after a dialog is dismissed, so
`headerIndex` cannot be the ordering anchor.
Four suite cases are pinned as KNOWN DEFECT: they assert what the detector does
so CI stays honest instead of permanently red, and flip to failing the moment
someone fixes it. No detector logic changed.
The recorder gains `--send "<ms>:<text>"` because a dialog capture has to be
driven and an unattended run has no TTY, and the scrub scanner gains a UUID rule
because agy prints a resumable conversation id on exit.
* test(runtime): capture agy mid-turn, and make the scan file reviewable
Answers the busy-frame question a P1 review raised against attempt six, with
two new captures from a live turn.
At the frame level the review is right: a busy frame parks the caret with the
same bytes as an idle one, `CR ESC[2A ESC[2C`, and the only differing row —
`esc to cancel` versus `? for shortcuts` — is erased by that park.
At the retained-tail level it does not reproduce. Each spinner tick is its own
repaint with its own `CR ESC[2A`, two rows higher than the frame's, which
splices the composer away: a live turn's tail ends on `⣟ Generating...`, with
no bare caret to match. A constructed input that keeps the park and edits only
the status text is not faithful, because a live turn has a spinner row
repainting below the composer.
The residual is the gap between a frame park and the next tick, where the tail
does end on the bare caret. Quiescence-gated paths are safe there because ticks
keep arriving; text-only paths are not, and for those the capture supports one
clause: a braille glyph on the last visible line means working. That predicate
already exists here for cursor-agent and should be reused, scoped to the last
line — a first-run transcript prints `⠾ Signing in...` during startup.
Also in this commit, from the same review:
- pty-transcript-secret-scan.mjs held raw 0x00-0x1f bytes in a character class,
so the one file gating real PTY data into history was binary to git and
unreviewable in a diff. It now tests codepoints, which the formatter cannot
fold back into control bytes.
- Pin `src/main/runtime/__fixtures__/*.txt` as -text. A Windows checkout would
otherwise normalise line endings and rewrite the CR bytes that make these
files evidence.
The recorder now stops appending at the stop moment rather than through
shutdown: an agent repaints an idle frame on its way out, which was overwriting
the mid-turn state the capture existed to record.
* test(tooling): allowlist the transcript scan test in the batch-shim ratchet
pty-transcript-secret-scan.test.mjs asserts that the capture recorder routes
an 'agy.cmd' shim through cmd.exe, so the shim literal it names is the
assertion, not a spawn. Fits the existing assert-on-shim-files category.
|
||
|
|
c84007c541 | feat(rpc): generate a shared params catalog from the host registry, gated on parse parity (#19961) | ||
|
|
fb322046e8 |
skills: rewrite and trim the seven non-orchestration guides (#19128)
* skills: rewrite the seven non-orchestration guides to one outcome-first standard
Every guide leads with Result / Done / Safe failure, states conditions instead of case lists, keeps one done bar and one autonomy envelope, and loads references at the point of use via `skills get <topic> --full`. orca-cli drops from 424 to 260 always-loaded lines with three references; orca-per-workspace-env from 794 to 397 with five.
Defects fixed in shipped guides: `emulator camera` (no such command), iOS `permissions` (backend refuses it), Android pane described as in development, `relayGracePeriodSeconds: 0` documented as immediate teardown (it is unbounded), doctor `ok: true` hiding `warn`, an SSH exemplar setting both `jumpHost` and `proxyCommand`, a provisioned-root fetch from `origin`, and the Linear unconfirmed-write rule keyed on four verbs when ten emit it.
The resolver ladder, placeholder rule, and older-binary fallback shared by every installable SKILL.md now come from one skill-stubs/_shared/cli-resolution.md fragment composed by the generator, which also bundles per-guide references into --full. New guards: every ORCA invocation and flag resolves against COMMAND_SPECS, descriptions carry no angle-bracket tokens, reference routing is checked both ways, and an always-loaded size ratchet (300 lines) that guides may leave but never join.
* skills: address review on the SSH recipe and the parity guard
- ssh-host create script: route the bootstrap ssh through the chosen jump host or proxy command, refuse both at once, use StrictHostKeyChecking=accept-new instead of a blind ssh-keyscan append, and pass gh_token/project_root/repo_url/repo_ref to the remote bash via printf %q so a quote in a value cannot break out of the command.
- per-workspace-env envelope: the step-10 workspace test the user asked for is no longer forbidden by the same paragraph.
- linear guides: name the full verb, ORCA linear list-issues.
- parity guard: a prefix reference such as ORCA linear --help or ORCA emulator --webcam now has its flags checked against every command under that prefix; only an exact path or an explicit ... was checked before.
* skills: tighten prose in the seven rewritten guides
Shorter outcome spines, one idea per sentence, no restated rationale after a rule. No rule, command, or pinned phrase changes; 47 net lines fewer across the guides and references.
* skills: route orca-cli and per-workspace-env gates through --reference
Both guides told agents to load --full at a gate because the per-reference
selector did not exist when they were written. Now that main serves
`skills get <topic> --reference references/<file>.md`, load only the
named file and keep --full as the fallback for an older CLI, matching the
orchestration kernel.
* skills: drop outcome-spine boilerplate from the CLI-wrapper guides
The Result/Done/Safe-failure preambles and Next Action closers restated
rules the body already carries. Agents stop fine without them, and for
a CLI wrapper the command surface is the guide. Keeps the one substantive
rule computer-use's Done block added (never report unverified as success)
inside Action Rules. orchestration and per-workspace-env keep theirs:
those are multi-step workflows where the done bar is load-bearing.
(cherry picked from commit
|
||
|
|
b8311d509a |
Revert "skills: rewrite the seven non-orchestration guides to one outcome-first standard (#18724)" (#19126)
This reverts commit
|
||
|
|
15d0f8aedf |
skills: rewrite the seven non-orchestration guides to one outcome-first standard (#18724)
<!-- orca-pr-loc -->
<!-- Programmatic LoC summary. Do not edit by hand; rewritten on every commit. -->
| | Files | Added | Deleted | Net |
| :--- | ---: | ---: | ---: | ---: |
| Test | 6 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$544 | $\color{#cf222e}{\Huge{\mathbf{−}}}$49 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$495 |
| Prod | 36 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$1719 | $\color{#cf222e}{\Huge{\mathbf{−}}}$1703 | $\color{#1a7f37}{\Huge{\mathbf{+}}}$16 |
<!-- /orca-pr-loc -->
## ELI5
Orca ships eight skill guides that agents read before running the CLI. Seven of them (everything except `orchestration`, which #16904 rewrites) were command catalogs that had drifted from the binary. This PR rewrites them so an agent reads the outcome, the done bar, and the safe-failure rule first, loads reference material only at the step that needs it, and never sees a command or flag the installed CLI does not define.
## What changed
- **Seven guides rewritten** to one standard: outcome spine first (Result / Done / Safe failure), conditions instead of case lists, one done bar, one autonomy envelope, references loaded at the point of use via `skills get <topic> --full`, every runnable invocation spelled `ORCA`. `orca-cli` is 424→260 always-loaded lines with three references (browser, automations, publishing); `orca-per-workspace-env` is 794→397 with five (provider-vercel, ssh-host, docker-ssh, windows-scripts, failure-modes).
- **Defects fixed in shipped guides:** `emulator camera` (no such command), iOS `permissions` (backend refuses it), Android pane described as "in development" (shipped in June), `relayGracePeriodSeconds: 0` documented as immediate teardown (it is unbounded), doctor `ok: true` hiding `warn`, an SSH exemplar setting both `jumpHost` and `proxyCommand`, a provisioned-root fetch from `origin`, the Linear unconfirmed-write rule keyed on four verbs when ten emit it. Linear and emulator descriptions dropped embedded commands and angle-bracket placeholders (651→329, 732→404 chars).
- **Generator bundles references.** `skill-guides/<name>/references/*.md` is appended to `--full`; `skills get` help says compact by default, full with references.
- **Stubs single-authored.** The resolver ladder, placeholder rule, and older-binary fallback shared by all eight installable `SKILL.md` files come from one `skill-stubs/_shared/cli-resolution.md` fragment composed by the generator. Projections were byte-identical before the content fixes.
- **Guards:** every `ORCA <cmd>` and flag in every guide and reference resolves against `COMMAND_SPECS` (this found the camera defect); descriptions ≤1024 chars with no angle-bracket tokens; reference routing checked both directions; an always-loaded size ratchet (300 lines) that guides may leave but never join. `orchestration` (440 lines on main) is recorded as an exception until #16904 lands its kernel.
## Relationship to #16904
Split out of #16904 so that PR carries only the orchestration guide. On main, `terminal send` has no `--wait-submit` / `--retry-request` and the orchestration kernel still carries the resolver ladder and worktree-selector rule, so this branch pins `accepted: true` for handoff receipts and leaves the orchestration pins where main has them. The merge in either direction is mechanical: #16904 rebased on this becomes a one-file `orchestration.md` change plus dropping the two exceptions.
## Standard
Compound Engineering's portable skill-authoring guidance (outcome spine, conditions not cases, pinned fragile commands with an ordered hatch, references at point of use). NVIDIA SkillEvaluator Tier 1 (`schema,pii,license,quality,unicode,lint`) was run on every guide; its deterministic checks pass, its template nudges (Instructions/Examples sections, 50–150 char descriptions) do not apply to Orca's stub architecture and were not applied.
## Testing
- `pnpm typecheck:tsc:cli` clean; `check:code-quality:changed` and `check:react-doctor:changed` 0 findings
- `pnpm verify:bundled-skill-guides` and skill-bundle manifest verify clean
- vitest over `config/scripts`, `src/cli/skill-guide-cli-parity.test.ts`, `src/cli/skills.test.ts`, `src/cli/specs/skills.test.ts`, `src/cli/help.test.ts`, `src/main/skills`: 240 files / 2,019 pass
- Live smoke on the built CLI of every `skills get <topic>` and `--full`, every emulator, linear, and vm verb named in the guides, and every projection's resolver, GNOME warning, and bounded fallback (done on the #16904 branch before the split; the guide bodies are identical here except the send-receipt vocabulary noted above)
## Deferred product decisions
Merging `orca-emulator` and `orca-emulator-android` into one skill with a platform branch; collapsing `linear-tickets` to a guide alias; a `skills get --reference <name>` selector so a gate table can load one file; a fresh-agent routing eval before trimming the `orca-cli` (1,015 chars) and `orchestration` descriptions, whose quoted triggers each fixed a routing misroute.
|
||
|
|
0f27445789 |
fix(build): pin config/relay-assets LF so one release is one relay hash (#19024)
* fix(build): pin config/relay-assets LF so one release is one relay hash * test(build): correct why the negative fixtures exist Review measured it: the first assertion checks the eol attribute via check-attr, not file content, so it fails first without the pin. The fixtures add over-broadness coverage, they do not carry the test. * test(build): key the relay line-ending pin off the manifest, not a directory A path glob proves the directory is non-empty, not that it is still the directory build-relay reads from. Relocating an asset into config/scripts (where only **/*.mjs is pinned) reintroduced the CRLF bug with the suite fully green. RELAY_ARTIFACTS is the right anchor: build-relay refuses to emit an artifact absent from it, so a relocated or new asset cannot slip past. Bundles have no tracked source and drop out with zero hits. --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
bfc6a262a7 |
fix(windows): read command lines from the kernel, not each process's PEB (#17886)
* fix(windows): read command lines from the kernel, not each process's PEB MDE incident D scored Orca for suspicious memory activity: the vendored `@vscode/windows-process-tree` recovered every process's command line by opening it with `PROCESS_QUERY_INFORMATION | PROCESS_VM_READ` and chaining three `ReadProcessMemory` calls through the PEB and `RTL_USER_PROCESS_PARAMETERS`. On a 750ms/2s cadence over the whole table that is the credential-dumping primitive, whatever the intent. Windows 8.1 added `NtQueryInformationProcess`'s `ProcessCommandLineInformation` class (60), which returns the same string as a kernel-built `UNICODE_STRING` under `PROCESS_QUERY_LIMITED_INFORMATION` alone. Electron's floor is Windows 10, so every supported OS has it. The PEB reader stays behind a process-wide latch that only `STATUS_INVALID_INFO_CLASS`/`NOT_SUPPORTED`/`NOT_IMPLEMENTED` can set; a pid that merely denied a handle does not re-arm it, because `PROCESS_QUERY_INFORMATION` implicitly grants the limited right and so cannot be obtained where the weaker open already failed. The same hunk drops `PROCESS_VM_READ` from `GetProcessMemoryUsage` and `GetCpuUsage`, which acquired it and never read an address space. Measured on Windows 11 (514 processes), counted in-process by swapping the addon's import table entries for counting stubs, per CommandLine scan: `ReadProcessMemory` 1128 -> 0, desired access 0x0410 -> 0x1000, p50 12.7ms -> 9.3ms. Command lines were byte-identical on every process both readers recovered (376/376, 379/379 across runs), including a 24,068-character argv with quotes, non-ASCII and trailing whitespace, and a WOW64 target. Three processes that refused the old rights granted the new one; none went the other way. * chore(deps): refresh the windows-process-tree patch hash in the lockfile * fix(windows): drop the PEB fallback and detect the unpatched prebuilt Review of #17886 found three ways the reader could still perform, or silently resume, the primitive it exists to remove. The class-missing latch was a permanent, process-wide, one-way downgrade back to the PEB read, and any single target returning STATUS_INVALID_INFO_CLASS / NOT_SUPPORTED / NOT_IMPLEMENTED could trip it. On an EDR-hooked ntdll -- the entire premise of this change -- a hook that does not recognise class 60 would have restored PROCESS_VM_READ plus three ReadProcessMemory per pid per scan for the life of the process, unobservably, on precisely the machines this was written for. The fallback is deleted rather than guarded: GetProcessCommandLine now returns false and leaves the command line empty, which callers already handle, so the addon imports no ReadProcessMemory at all. That absence is what makes the property checkable on the artifact. The published 0.8.0 tarball ships a loadable prebuilt built from unpatched source; it is node-addon-api, so a bare require() accepts it, allowBuilds is false and CI installs with --ignore-scripts, and a rebuild that soft-exits on a Windows file lock leaves it in place. Source-text guards could never see it. windowsProcessTreeAddonReadsProcessMemory() checks the compiled binary instead, and is wired into the install check, the rebuild, and the relay build. The repair itself never worked: `git apply` run inside a work tree prefixes patch paths with the cwd-relative prefix, skips what does not match, and exits 0, so the branch always fell through to its own post-check throw. The package dir is always under the project root, while the fixture that covered it was in %TEMP%, outside any repo. Blinding git with GIT_DIR fixes it, and the test now runs inside a real work tree. Also from review: bounds-check the returned UNICODE_STRING against the allocation (not the size the second query clobbers) and cap the probe so a bogus length cannot bad_alloc a whole scan; test NT_SUCCESS explicitly; value- initialize ProcessInfo, which left `memory` as stack garbage -- measured, 82 processes reported the same bogus working set; and correct a comment in windows-process-table.ts that still described the command line as a PEB read. Re-measured on Windows 11 (543 processes): ReadProcessMemory 1128 -> 0, with the symbol absent from the import table so the IAT hook finds no slot to count; desired access 0x0410 -> 0x1000 on all 543 opens; p50 13.5 -> 12.3ms; 405/405 command lines byte-identical including a 24,087-character quoted non-ASCII argv and a WOW64 target; 3 processes recovered only by the new path, 0 only by the old. * chore(deps): refresh the windows-process-tree patch hash in the lockfile * test(scripts): stage a script's local imports into the native-runtime fixture ensure-native-runtime.mjs gained an import of windows-process-tree-gyp-rebuild.mjs, but the fixture copied only the script itself, so every case in the suite died with ERR_MODULE_NOT_FOUND before reaching its own assertions. copyScriptWithLocalModules already walks a script's co-located imports for exactly this reason -- its own doc comment names this failure -- so use it rather than listing files by hand. The two Windows cases still fail here, on a missing node-pty ConPTY runtime that also fails on main; this only stops a resolution error from standing in front of whatever they were meant to catch. * fix(windows): route a locked stale addon to the Windows file-lock message `pnpm install` with Orca running aborted with a raw EPERM stack. The stale-binary guard -- which deletes an addon that still imports ReadProcessMemory so a skipped rebuild cannot use it -- ran outside the try whose catch classifies Windows file locks, and whose message is literally "Close running Orca/Electron/dev processes for this worktree": exactly this situation. Measured rather than assumed: rmSync against a loaded (memory-mapped) addon throws EPERM, and `force: true` does not help, since it only swallows ENOENT. Cold copies of the same file delete fine. So the delete threw a page before the handler that knows what it means. Moving the guard inside the try is the whole fix; the classifier already matches the EPERM text. The new case runs the real script against a temp project whose stale addon is held open by a live child process, and fails against the old placement with the raw `syscall: 'rm'` stack the report described. * feat(windows): warn once when command-line recovery is refused host-wide Removing the PEB fallback removed a total-defeat vector, but it left a cliff: if NtQueryInformationProcess(ProcessCommandLineInformation) is refused -- a hooked ntdll that does not know class 60 -- every command line comes back empty and agent identity matching silently degrades to image names. The addon still loads and still enumerates, so every health check the app has stays green. A cliff nobody can see is the failure mode this area keeps producing. The querying process is the unambiguous probe. A process can always open itself with PROCESS_QUERY_LIMITED_INFORMATION, so its own command line coming back empty means the query is refused for every process -- not that some target denied a handle, which is normal for roughly a quarter of the table. Keying on our own row rather than a fraction means no threshold to tune and no false positive on a hardened box where most processes deny. One warning per session, gated on the CommandLine flag actually being requested so a future identity-only reader cannot trip it. The suite's own SELF fixture gains a command line for the same reason: a self row without one is the alarm, not a detail. * fix(windows): check the relay's staged addon at load, and answer tri-state Two gaps in the ReadProcessMemory check, both about what it does not see. It only ever looked at node_modules/@vscode/windows-process-tree. A relay host has no node_modules of ours: it loads ./windows-process-tree.node staged beside the bundle. The relay build asserts the symbol on the artifact it produces, but a bundle and the addon beside it redeploy independently, so a host that has not taken a new bundle keeps whatever binary is already there -- and the published prebuilt is node-addon-api, so it binds cleanly and then walks every process's address space. loadWindowsProcessTree now checks that file too and refuses it, falling back to the CIM scan: slower, but not the thing an EDR quarantines a host for. The predicate is duplicated rather than imported, because the config-script copy is install-time tooling that drags in node-gyp and child_process, and this module is bundled into the app and the relay. And it returned false for a binary that is not there. All three callers happened to be safe, but the name read as a safety predicate, so a future caller would take a missing binary as verified. inspectWindowsProcessTreeAddon() now answers clean/unpatched/missing over an explicit binary path -- which is also what lets the relay's staged addon be checked at all -- and each caller states which state it acts on. Both are covered by cases that fail against the old code: without the load-time check the unpatched staged addon is bound and the CIM fallback never runs, and with 'missing' folded back into 'clean' the absence case fails outright. * test(windows): load the addon in beforeAll, not at collection time loadAddon() ran while the file was being collected, so on a Windows checkout with no built addon the require threw before any case existed and took the seven patch-text cases down with it -- cases that read only the patch file and need no binary at all. Verified both ways against a deliberately unresolvable addon path: at collection time vitest reports "no tests" for the file; from beforeAll the seven text cases pass and only the three addon cases go. * fix(deps): normalize the windows-process-tree patch to LF and let pnpm own its hash `pnpm install --frozen-lockfile` failed on this branch on every platform with ERR_PNPM_LOCKFILE_CONFIG_MISMATCH, which breaks CI and the release build. Two coupled defects. The patch file was committed with CRLF -- 174 CR bytes, against zero on main -- and `.gitattributes` pins `/config/patches/*.patch -text` precisely so checkout cannot convert it, so those bytes reached every runner. And pnpm hashes a patch **LF-normalized**, so the raw sha256 of a CRLF file is a value pnpm never computes: raw sha256 322965470c05f63d8527f7d8e892ee26ee444136b66b57fd64c362a9f2ff05d1 LF-normalized f8ea245391c94da5770045aeea01fa6de466c2199c6ef46b5b769b398aa9823e The lockfile carried the raw one, at all three sites. It is the only one of the seven patches where the two digests differ, which is why the other six passed. Normalized the patch to LF and took pnpm's own value from `pnpm install --no-frozen-lockfile`; nothing here is hand-computed. With the file LF-only the two interpretations coincide, so the lockfile, the contract test's no-CR assertion and its hash assertion all agree at one number -- and `config/scripts/windows-process-tree-patch-contract.test.mjs`, which was red on this branch for the same reason, is green again. The lockfile diff is exactly the three hash lines. The regression check is the installer, not a digest. Two separate reviews "verified" the shipped hash by recomputing sha256(patchBytes) and matching the lockfile; both were wrong, because both repeated the same wrong assumption about which bytes pnpm hashes. A check that reproduces the original mistake is not independent. So the new case runs `pnpm install --frozen-lockfile --lockfile-only --ignore-scripts` against a copy of the manifest, lockfile and patches, and asserts exit 0 -- verified by deletion: restoring the shipped hash fails it with the exact ERR_PNPM_LOCKFILE_CONFIG_MISMATCH from the branch's package (windows) job. Also corrected the `.gitattributes` comment claiming pnpm hashes patches byte-for-byte. The `-text` setting is right -- `git apply` needs the exact bytes -- but that sentence is the claim that produced the wrong hash twice. * ci(windows): run the process-tree patch suites in CI Both suites only self-skip off Windows, so the binary-level check that the addon carries no ReadProcessMemory passed vacuously in every lane. * fix(windows): force core.autocrlf=input for the patch repair My LF normalization of the windows-process-tree patch broke the `git apply` repair path introduced in this PR. The two are coupled and I checked only one. Those 174 CR bytes were not editor noise. They sat on exactly the pre-image lines and nowhere else -- 107/107 in src/process.cc, 67/67 in src/process_commandline.cc, 0 on every added or context line -- because @vscode/windows-process-tree@0.8.0 ships those two sources as CRLF. Normalizing the patch made its pre-image stop matching the file it is applied against. Measured, reconstructing the true CRLF pre-image from the pre-normalization blob and applying the current LF patch: core.autocrlf plain -c core.autocrlf=input true exit 0 exit 0 input exit 0 exit 0 false exit 1 exit 0 `false` is Git's own built-in default and what "checkout as-is" selects in the Git for Windows installer -- on this box the `true` that hides it comes from the installer's system gitconfig, not from anything in the repo. There the repair throws, ensureWindowsProcessTreeCommandLinePatch reports "still reads the PEB, and repairing it ... failed", isWindowsNativeLockError does not match that text, and `pnpm install` dies with no path forward. Forcing the mode rather than `--ignore-whitespace`: both fix every cell and both leave the applied file fully LF, but `input` relaxes line endings only, so a hunk whose real content drifted is still rejected. The repair rewrites a security-relevant source file; it should stay strict about everything except the thing that is legitimately ambiguous. Not reverting the patch to CRLF: windows-process-tree-patch-contract.test.mjs (pre-existing on main) forbids CR bytes in it, and pnpm computes the same hash either way. LF plus the forced mode is the end state. The suite could not have caught this. The fixture built its pre-image from the patch itself and joined with '\n', so fixture and patch agreed by construction on any encoding -- once again a test that passes without its fix. It now emits the CRLF the real package ships, and the case runs under both autocrlf modes pinned through a temp HOME gitconfig, because the repair blinds git to the repo and so reads global config. Verified by deletion in both directions: with the flag removed the autocrlf=false case fails with the exact "still reads the PEB" dead end while autocrlf=true still passes, and with the fixture back on LF all eight cases pass with no fix present at all. Also corrected the .gitattributes comment I added last commit. It said `git apply` needs the bytes the patch was written against, which is now false -- the pinned bytes are LF and the bytes it was written against are CRLF. That is the same class of confident-and-wrong claim that produced the bad hash twice. * fix(windows): assert the rebuilt addon, and install the patch for real in tests Three follow-ups from review. **The packaged binary had no check.** The relay build asserts its own artifact and ensure-native-runtime asserts what it loads, but nothing looked at the addon copied into the packaged app -- so a rebuild that silently produced the upstream reader shipped. `rebuild-native-deps.mjs` now asserts `clean` on it after `rebuild()`. This is also the caller D4's tri-state was missing: every existing site branches on `=== 'unpatched'`, so `missing` still behaved exactly like `clean` everywhere, which was the thing making it a state rather than a boolean. Here both non-clean states fail, and they fail differently: after a rebuild that reported success, an absent binary is a broken build, not an absence to shrug at. The fake `rebuild()` had to start producing a binary for that to mean anything, so it now emits stand-in bytes and takes `addon: 'clean' | 'unpatched' | 'none'`. Verified by deletion: with the assertion removed both new cases pass. **The frozen-install case could not see a patch at all.** `--lockfile-only` resolves and never applies one, so its coverage stops at hash consistency. Added a case that installs `@vscode/windows-process-tree@0.8.0` for real with the patch and asserts the materialized `src/process_commandline.cc` carries the marker and no longer carries `ReadProcessMemory` -- about 1.5s for the pair. Correcting the brief on that one: it does **not** catch the `git apply` breakage from the previous commit. Measured -- with `-c core.autocrlf=input` removed it passes cleanly, because `pnpm install` uses pnpm's own patch applier and never runs our repair script. What it does catch is a patch pnpm can no longer apply: corrupting one pre-image line fails both cases. The repair path stays covered by the CRLF fixture in rebuild-native-deps-node-pty.test.mjs. Worth recording, since it decides whether the LF normalization was safe at all: pnpm applies the LF patch to the CRLF tarball sources without complaint, and materializes them as LF with the marker present and `ReadProcessMemory` absent. The primary install path was never affected -- only the `git apply` fallback was. **Dead timeout.** The frozen-install case passed `timeoutMs: 300_000` to the spawn while vitest capped the case itself at 30s, so on a cold runner vitest would have killed it first. Both cases now declare the budget they use. * test(windows): route the frozen-install check through the pnpm invocation owner The new patched-dependencies check hand-rolled a PATH walk naming 'pnpm.cmd', which the windows batch shim spawn boundary ratchet rejects: pnpm-cli-invocation already owns that decision for every other script, and its allowlist only shrinks. Reuse resolvePnpmCliInvocation for the command and prefixArgs, and the shared resolveCliCommand for the presence check, so no shim name is spelled here. Its `shell` flag is dropped because runProcessSync refuses it and already drives a shim through the interpreter itself. --------- Co-authored-by: Orca Worker <orca-worker@localhost> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
968dbd905f |
perf(renderer): take the English catalog and the xterm WebGL addon off the boot graph (#18326)
* perf(renderer): take the English catalog, xterm WebGL addon and emoji data off the boot graph The renderer's boot graph — the entry chunk plus its 331 modulepreload links, all fetched and evaluated before first paint — carried three payloads nothing needs at that moment. `en.json` (644 KB) was an eager i18next resource, but every renderer string goes through `translate(key, fallback)` and `en` resolves that inline default, so most of the catalog was dead weight. The renderer now bundles a generated `en-runtime-required.json` holding only the 2,583 of 13,828 entries a default cannot reproduce: plural-suffixed keys, keys whose catalog value differs from a call site's default, and keys no call site references with a literal default. `en.json` stays the translator source and the input to the four lazy catalogs. `@xterm/addon-webgl` (243.6 KB) and `emojibase-data` (170 KB) are now primed right after the React root renders instead of statically imported. The load stays eager and `attachWebgl` stays synchronous — it reads the resolved constructor — so no terminal ever falls back to the DOM renderer for a frame. `isPluginPanelTabKey`/`isQualifiedPluginKey` move to schema-free sibling modules, re-exported from `plugin-manifest.ts`. This evicts the plugin manifest schema graph from the boot chunk but measures ~0 KB, because six other shared modules still put zod on the boot path. Boot graph: 332 chunks / 5107.2 KB -> 336 chunks / 4161.5 KB (-945.7 KB, -18.5%). A new ratchet parses the built index.html and fails if `en.json`, `@xterm/addon-webgl` or `emojibase-data` is preloaded again; it runs at the end of every `build:electron-vite`. * chore(i18n): pin the generated English subset to LF and mark it generated * fix(i18n): make the runtime-catalog gate merge-robust and prime emoji data in tests CI builds the merge of a PR with main, so a byte-for-byte comparison against a committed generated file fails the moment any unrelated PR adds a translate() call — which is what happened here. The check now asserts the property that actually matters instead of byte equality: every runtime-required entry is shipped, and nothing shipped disagrees with en.json. Entries that stopped being required are dead weight, never a wrong string, so they are reported and tolerated. Failures now name the offending keys rather than saying "stale". The generator itself was already deterministic (plain code-unit sort, no locale collation, order-independent set construction); a test now pins that a reversed call-site walk produces byte-identical output. Test fixes for the catalog prune and the deferred emoji load: - browser-search / NativeChatSupportedAgents asserted key presence on the renderer's runtime resource. The durable contract is en.json — the renderer deliberately no longer bundles entries a call site default reproduces — so they assert against the translator catalog. - Four emoji tests typed a shortcode in the same tick as mount, before the catalog the hook primes on mount resolves. Not reachable by a human; the tests now await the prime. * revert(renderer): keep the emoji shortcode catalog statically imported Deferring emojibase-data introduced a window that did not exist before: until the dynamic import settled, getPrimedEmojiShortcodeEntries returned [], so exactShortcodeIndex built an empty map and replaceCompletedWorkspaceEmojiShortcode returned null — leaving a typed `:wink:` in the field literally, and persisting it as the workspace display name. Pre-change the shared catalog was statically imported, so the first call at any tick returned full data. The window is reachable by anything that dispatches input in the same task as the field's mount effect — Playwright/CDP in the e2e suite and agent automation both do, and the WorktreeMetaDialog test failure was exactly that, producing 'Feature 😉' instead of 'Feature 😉'. Nothing that resolves a shortcode can be async without that race, and a wrong persisted name is not an acceptable trade for 166.7 KB, so the deferral is reverted rather than papered over in the tests. The boot-graph ratchet drops its emojibase-data probe and records why. Boot graph: 5108.9 KB -> 4329.9 KB (-779.0 KB, -15.2%), down from -945.7 KB. * fix(terminal): make the deferred WebGL addon load recoverable and refit on late attach Two defects the deferral introduced, neither possible with a static import. A failed load latched the DOM renderer for the whole session. `.then(onOk, onError)` settles fulfilled, so the memoized promise was cached forever with a null constructor: attachWebgl's re-prime got the cached promise back, and resetTerminalWebglSuggestion — the documented "GPU setting changed, retry" path — could not clear it either. The rejection path now clears the memo, latches the queued panes the way a failed construction does so they retry at a recovery boundary rather than every frame, and caps attempts so a genuinely missing chunk is not re-fetched forever. The recovery boundary re-arms it. The queued-attach drain skipped the refit. Every other late-attach path pairs attach with a refit because the grid was measured under DOM cell metrics and WebGL floors the device cell width. Post-deferral, openTerminal's attachWebgl queued and returned, the initial fit rAF then measured DOM metrics and sized the PTY from them, and the addon attached with no refit — a persistently narrow PTY and an unpainted right gutter, not a one-frame flicker. Both paths now go through one attachWebglAndRefit pairing so they cannot diverge again. Regression tests cover both, and each was verified to fail without its fix. The addon-load state machine moves to terminal-webgl-addon-loader.ts and the viewport presentation helpers to pane-viewport-present.ts, keeping pane-webgl-renderer.ts under the 300-line budget without a suppression. |
||
|
|
ee82feb776 |
fix(build): pin config/scripts LF so Windows can run their tests (#18056)
core.autocrlf=true ships in the Git-for-Windows system config, so a fresh Windows checkout materializes config/scripts/*.mjs with CRLF. Vite's SSR transform finds the shebang with /^#!.*\n/, and \r is a JS regex line terminator, so the pattern misses on CRLF: the hoisted import/export preamble lands at offset 0 ahead of the shebang, which then defeats the code[0] === '#' guard that blanks it. A literal #! survives into the middle of the module and every suite importing the script dies at load with SyntaxError: Invalid or unexpected token. Eight suites were unrunnable on Windows. .gitattributes already pinned eight of these scripts individually; replace those with one glob over the directory so the pin does not have to be remembered per file, and add a ratchet that fails when a shebanged script is left on the platform default. Co-authored-by: Orca Worker <orca-worker@localhost> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
487e43d619 | refactor(test): use __fixtures__ for wrapper snapshots and teach the LoC bot (#15365) | ||
|
|
12550fcc28 | refactor(test): move shell wrapper fixtures where they read as test data (#15363) | ||
|
|
49752477a6 |
build(xterm): restore the patch regeneration harness and gate it in CI (#15223)
* build(xterm): restore the patch regeneration harness and gate it in CI docs/reference/ime-architecture.md says "Never hand-edit the bundles in the patch" and links to docs/reference/xterm-patch-regeneration.md. That doc does not exist, and neither does the harness it describes. Both landed in |
||
|
|
17cfc968cf |
Revert the terminal IME composition-ownership change (#13282)
* Revert "test(ime): restore coverage the composition-ownership change removed (#13168)" This reverts commit |
||
|
|
17b3dff3c4 |
refactor(terminal): return IME composition ownership to xterm (#13128)
* fix(terminal): return IME composition ownership to xterm * fix(mobile): derive terminal input from native replacement ranges * test(mobile): record iOS Japanese IME traces * fix(mobile): preserve native IME replacement ranges * fix(xterm): flush queued application input after IME commit * test(terminal): pin Korean intermediate commit * test: pin Windows IME shortcut ownership * test: replay IBus number candidate commit * fix: preserve native macOS input-method punctuation * refactor(terminal): remove stale mac focus override * fix(mobile): preserve soft keyboard deletion ranges * fix: keep IME-owned palette chords in renderer * fix: stop carried IME shortcuts at renderer owner * fix: preserve carried IME shortcut dispatch * fix: narrow main-owned shortcut actions * test(mobile): pin Japanese IME replacement traces * test(terminal): retain paired native IME trace * fix(chat): preserve browser IME composition ownership * fix(chat): retain macOS IME confirm gesture * fix(chat): expire unmatched IME confirm carry * fix(chat): isolate IME confirmation expiry * fix(chat): retain active IME confirmation * refactor(terminal): remove dead composition handler * feat(ime): add shared Enter-ownership seams for CJK composition The confirming Enter of a CJK composition arrives as two keydowns and the orderings differ by platform: Windows/Linux redispatch the unmarked Enter/13 before keyup, macOS delivers keyup first. A guard reading only isComposing or keyCode 229 misses the redispatch, so surfaces submitted on a confirm. Adds useImeEnterGestureOwnership (carry token, next-frame expiry), a shared ImeEnterGuardedForm for native implicit submission, and the cmdk seam covering 18 CommandInput surfaces at one site. A chorded Enter arms the carry but is never swallowed — the reverse would eat a user's deliberate Cmd/Ctrl+Enter. Both failure modes are pinned by ime-enter-gesture-ownership-contract.test.ts. Co-authored-by: Orca <help@stably.ai> * refactor(terminal): consolidate native input listeners and parked-screen owner Extracts the shared native-input listener installer and renames the parked-screen detector for what it actually does, replacing per-call-site duplication. The listener installer keeps a forgetOptionKeyLocationOnBlur flag so per-window semantics are preserved rather than flattened. Net deletion; no behaviour change intended. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin recorded IME shapes as regression tests Nine regression tests built from hashed affected-platform captures, each with a paired ordinary negative and a discriminating mutation verified to take the file from all-passing to exactly one failure. Covers the Windows MS-Korean Shift family (#12179, #11878, #12151, #11946, #12152) and the Korean TUI line-break rows (STA-3237, STA-3222, STA-3129). STA-3237 pins the empirical 3-Shift / 2-active-composition / 2-newline ratio the device run established — the third Shift produces nothing because Space has already committed. That ratio is not derivable from a static capture. Co-authored-by: Orca <help@stably.ai> * fix(ime): guard Enter-commit surfaces against CJK confirm Applies the Enter-ownership guards across the surfaces whose Enter commits something: publishes, clones, pairs, installs, posts, or persists. Tiered deliberately rather than uniformly. Irreversible and remote-effect sites take the carry token, which also blocks the unmarked redispatch. Locally reversible sites take the oracle check with a one-line comment naming the residual, because a spurious commit there costs one undo. Three numeric fields are left unguarded with the reason in-code: Chromium blanks number inputs at compositionstart, so a confirm-Enter only ever reaches an empty-draft reset. Measured with a CDP probe rather than assumed — a guard that cannot fire is noise. Co-authored-by: Orca <help@stably.ai> * test(ime): teeth-check the Enter guards on every guarded surface One suite per guarded surface, each verified by deleting the guard and confirming the test fails. A green guard test without that check is unverified, not verified. Two shapes pass vacuously in happy-dom and are avoided here: native implicit form submission never fires, and blur() is inert on an unfocused element. Both made "the commit did not happen" assertions pass with the guard removed, so the suites assert the guard's contract directly instead. Co-authored-by: Orca <help@stably.ai> * fix(mobile): keep iOS Korean commits whole through the live-input path iOS Korean reports isComposing: false on every event, so it bypasses the composition guard entirely. The strict owner rejected UIKit's transformed post-change field and sent only the leading jamo — the reported symptom. Prefers the authoritative same-event field text over the predicted text when the supplied operation cannot produce it. Generic: no Korean special-case, no locale classifier, no normalization. Adds the RN-target-keyed submit carry alongside it. Co-authored-by: Orca <help@stably.ai> * test(e2e): make IME capture harnesses fail loudly instead of silently Four instruments recorded silence as success, so a void run scored as a clean one: - readTerminalImeBoundaryTrace returned an empty trace when the probe never installed, making every "nothing leaked" negative pass vacuously - summarizeLatencies([]) returned a perfect zero distribution that passed all three latency thresholds - the macOS Vietnamese spec pinned an input-source ID that does not exist, and failed as though the operator had chosen the wrong source - the expectedLineCount=1 prefix property was undocumented and one edit from silently downgrading a PTY assertion Input sources now resolve by enumeration and name the near-matches on failure. Co-authored-by: Orca <help@stably.ai> * test(terminal): cover Cangjie cancellation and fix a cross-namespace assertion Adds #11951's recorded Cangjie cancel shape to the existing cancellation suite, which covered Pinyin and Sogou but not Cangjie. One keystroke then Backspace arriving as deleteContentBackward with data: null, so the stale preedit is the only thing a fallback could replay. Verified against the historical pre-6cd944c62b3 bundle: the positive fails with ['尸'] where [] is expected, while the ordinary negative stays green. Also fixes the Vietnamese spec, which asserted a TIS-space input-source ID against getKeyboardInputSourceId(). Those two Orca APIs report the same source in different namespaces — TIS nests it under VietnameseIM, the app API does not. The resolver stays as an installation precondition; the assertion matches the leaf. Co-authored-by: Orca <help@stably.ai> * test(e2e): add a real-IME macOS arm for the Korean chord commit The existing korean-ime-terminal-shift-enter-commit spec synthesizes composition over CDP: Input.imeSetComposition sets the preedit directly and Input.insertText performs the commit. Asserting the IME produced events you injected yourself is circular, so that spec cannot certify real-IME behaviour. This arm selects 2-Set Korean via TIS, reads it back live, and injects through System Events key codes, so the OS owns the preedit, the commit instant, and isComposing. PTY byte expectations are preserved verbatim. Covers 2 of the original 4 cases by design. The other two are the Windows/Linux redispatch-before-keyup ordering, which macOS cannot produce and which cannot be selected -- the OS decides it. Reintroducing synthesis to "restore coverage" would reintroduce the circularity. Co-authored-by: Orca <help@stably.ai> * test(e2e): assert the macOS chord arm at the PTY boundary, not the renderer The byte expectations were transcribed from korean-ime-terminal-shift-enter-commit :364/:383, which assert against onData -- a renderer boundary where the terminator is CR. This spec reads the PTY child, where the tty has already converted CR to LF. Names both forms per row rather than swapping the constant, so the conversion reads as evidence that the capture reached past the renderer, as #11936 and #11951 record. Ctrl+Enter's CSI-u sequence is unaffected and is identical at both boundaries. Co-authored-by: Orca <help@stably.ai> * test(e2e): measure composer-to-onData latency and stop dropping IME keystrokes Two defects in the echo latency probe. It hooked onWriteParsed and onRender but never onData, so it measured key->parse->render echo rather than the composer-vs-onData delta the latency rows need. Adds a third hook feeding its own sample set. And `event.key.length !== 1` silently dropped IME keystrokes: Pinyin and Cangjie keydowns arrive as key:'Process' (length 7). Replayed over the captured corpus, the old filter accepted 580 of 4137 Chinese IME keydowns -- it was discarding 80% of them. The new filter matches the shape the owner itself branches on. Attribution charges each onData to the latest keydown rather than a FIFO head, because composing jamo emit no onData at all and a queue would credit a whole composition to its first keystroke. The consumer now asserts sample count before any percentile, so a zero-sample run cannot render as a flawless distribution. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the WSL shifted-jamo newline shape for #11919 In Korean 2-set, Shift types ordinary letters -- the double consonants and the compound vowels. Each such keystroke reaches Chromium as key='Process', keyCode=229, shiftKey=true. The v1.4.163 classifier matched exactly that pattern with no code guard, so it called those keystrokes Enter, rewrote them to a synthetic Shift+Enter, and injected a newline into the middle of the word -- with no Enter key pressed. That is why the reporters said "no modifier key pressed": they had not chorded Shift+Enter, but they had pressed Shift, to type the double consonant. Asserts the row's own recorded capture: 40 immediate keydowns, exactly 3 of them Shift-carrying inside a single syllable, and an onData stream with one newline per Enter press and none mid-word. Two ordinary negatives keep it from being a blanket mute -- the same session's non-IME keydowns still reach shortcut policy, and an ordinary Shift+Enter still resolves through the real policy. Co-authored-by: Orca <help@stably.ai> * test(terminal): pin the composition commit lag that made Korean type one behind macOS Korean 2-Set commits syllable N only when the first jamo of N+1 arrives, so compositionend and compositionstart land in the same task. A composition-start handler cancelled the pending finalizer that was the only path to triggerDataEvent and ended the session without emitting bytes, so every committed syllable reached onData exactly one syllable late and the backlog cleared only at a Space or Enter. Types continuously with no Enter and no Space -- either would flush the backlog and hide it -- and samples onData at every syllable boundary. Paired with a length-matched ASCII arm that stays green throughout, so the positive is a fact about composition rather than about timing in general. Bisected to a single call site across five builds: pristine, 1.4.155 and 1.4.162 pass, 1.4.163 fails, removing the one call repairs it, restoring it fails identically. That window is exactly the reporter's "started immediately after updating". Co-authored-by: Orca <help@stably.ai> * test(mobile): cover the send-queue abort that silently drops queued keystrokes One failed send in use-terminal-live-input-commit aborts every keystroke queued behind it, with the error swallowed by .catch(() => false). The existing test resolves(true) on every send, so the failure branch was uncovered. Four arms: the abort itself, an ordinary negative on the healthy path, a throwing sender, and a liveness control proving the queue recovers once the chain settles. Deleting the abort takes 4 passed to 3 failed, with the ordinary negative correctly surviving. Scope is stated in the docblock: this is a transport send-queue abort, reachable only via a real disconnect or RPC error. REQUEST_TIMEOUT_MS is 30s, so latency alone cannot reach the branch — consistent with #7094's symptom class, not proven to be its cause. * test(terminal): pin that daemon snapshot/restore cannot disturb a composition Two independent reporters attributed broken Korean composition to the always-on PTY daemon repainting terminal state over the preedit. The attribution is wrong on ancestry — the daemon shipped three months before the version both call good — but the boundary was never actually tested. Runs the real applyMainBufferSnapshot choreography against a live composition, including the full 2J/3J/H wipe plus the resize and alt-screen branches. textarea.value, selectionStart/End, compositionView.textContent and .active all survive byte-identical, and interleaving a restore between every jamo of 문제 still commits 문제 at onData. Also pins that the uncommitted preedit is absent from the captured snapshot: it lives in the textarea, never the buffer, so a restore has nothing stale to echo back. Injecting one textarea.value = '' into the restore fails exactly the three restore-boundary tests. * test(terminal): pin that Cmd tears down a composition where Ctrl and Shift do not xterm's composition keydown exempts only keyCode 16/17/18 (Shift/Ctrl/Alt) plus 20/229. macOS Meta — 91/93/224 — is absent, so a Cmd press mid-composition takes _finalizeComposition(false): the overlay goes dark and never recovers, because compositionstart is not re-fired. The user composes the rest of the word blind. Linux and Windows users press Ctrl and are exempt. xterm already has a Meta-aware modifier predicate in wasModifierKeyOnlyEvent, so this is an internal inconsistency rather than a deliberate choice. Owns no reported row and is version-neutral: 5/5 on both 1.4.162 and 1.4.163. The branch is unexercised in all 328 recorded traces, so this is a hazard pin, not a regression guard. Only the teardown is asserted; the likely duplicated commit needs a compositionend the IME kept alive across the Cmd, which no capture contains. Deleting the exemption fails exactly the three paired negatives; adding Meta to it fails exactly the two Cmd arms. * test(native-chat): characterize preedit loss when a question card replaces the composer An AskUserQuestion card fully replaces the composer by design, but the in-flight composition goes with it: the composer unmounts before compositionend reaches it, so the preedit is never committed to the draft. The committed text survives only because the draft is cached and restored via defaultValue. Node identity changes, value 'abc' is preserved, the 가 is gone. Drives the real NativeChatView -> SessionGate -> InteractiveCard -> questionActive swap -> Composer -> ComposerField, flipped by writing the same store field an AskUserQuestion hook event writes. Flipping questionActive to false fails exactly this test and nothing else across 639 native-chat tests, so the path was entirely unguarded. CHARACTERIZATION TEST: it asserts the loss. Fixing the defect — committing the preedit before the swap, or keeping the composer mounted — will make this file fail. Update the expectations to the new contract rather than working around them. Owns no reported row. #12118/STA-3219 flicker is keyed to token counters, which provably do not remount, and a question card arrives once per question. * test(terminal): pin the duplicated commit when Meta interrupts a composition _finalizeComposition(false) sends textarea.value.substring(start, end) but cannot clear the IME-owned textarea, so a later compositionend re-sends the same range. Meta reaches that path because CompositionHelper exempts only Shift/Ctrl/Alt; xterm's own wasModifierKeyOnlyEvent covers Meta four ways, so the omission is an internal inconsistency rather than a choice. Companion to the modifier-exemption guard, which deliberately pins only the overlay teardown. This pins the data consequence. HAZARD PIN: owns no reported row. The trigger is unverified on hardware — no capture in the corpus contains a Meta-during-composition gesture, and whether macOS keeps the composition alive across it is unmeasured. The duplication follows from the code given that sequence; whether users reach the sequence is the open half. An earlier premise that Space (keyCode 32) reaches this path was refuted by a corpus scan: 0 of 731 evidence files carry a keyCode-32 Space while composing, against 171 at 229, and 229 returns early. * test(terminal): characterize the syllable lost when the textarea blurs mid-composition CoreBrowserTerminal._handleTextAreaBlur clears the helper textarea unconditionally — "Text can safely be removed on blur" — while CompositionHelper._finalizeComposition reads the committed text back out of that same value from a deferred timeout. By the time it runs the value is empty, the substring is '', and triggerDataEvent never sees the syllable. xterm checks composition state in _syncTextArea and omits the same check here. Six cases. Blurring mid-composition loses the syllable in every ordering, including compositionend-before-blur, which is Chromium's real order — so it is not an ordering artifact. A bare textarea.blur() with no Orca code loses it too, which places the owner upstream: Orca's unguarded release on outside pointerdown is one trigger, not the cause. Committing 한 then blurring mid-가 yields ['한'] where ['한','가'] is correct: one syllable gone, surrounding text intact. Teeth checked by inverting — adding an Orca-side composition guard flips exactly the three cases that route through the release path and leaves the bare-blur and no-blur cases green, which is the scope split: a fix in regular-terminal-focus-ownership alone would not close this. HAZARD PIN, but unlike the others this one has a real production injector — clicking outside the terminal mid-composition. Owns no reported row. The shape matches #9738's report; the injector does not, and a shape match with a mismatched injector is not an owner. * test(terminal): say which arm the STA-3237 fixture came from The recorded keydowns are wave 4's A-shift-unmarked-only — the arm that emits no PTY bytes. Nothing in the file said so, so two readers concluded the row's events fail the owner's predicate and that STA-3237 and STA-3222 were different defects. They share an owner; the arm that fires is Process/229+Shift, absent from this bubble-phase trace because the owner claims it in the capture phase. Also corrects "code-blind": the v1.4.163 policy emits \x1b\r only for a shift-only key:'Enter', and a jamo keydown reaches that branch solely via the isTerminalImeProcessEnter rewrite. The mock is deliberately wider so the ownership guard stays under test if that rewrite moves. Comments only — no assertion, fixture value, or mock behaviour changed. * test(e2e): track the input-source selector the macOS specs shell out to Five tracked macOS IME specs ran `swift .tmp/select-input-source.swift`, a file that is gitignored and existed only on one machine. Anyone else checking out the repo — or the same machine after .tmp is cleaned — could not run them, and they are the capture drivers for the macOS rows that are blocked waiting for exactly those runs. Moves it to tests/e2e/ beside its callers. The chord spec now resolves it from __dirname rather than reaching two levels up into .tmp. * test(terminal): pin the CJK repaint decision against the reporter's own output #12164 comment 1 and #5921 report agent output with double-width glyphs rendering duplicated character-by-character while ASCII in the same line stays clean. No IME, no composition, no keystroke — the user never types the CJK. Segmenting all three verbatim samples into maximal same-risk-class runs gives 33 runs and zero violations of "this run is corrupted iff the production detector flags it": 17 wide runs all corrupted, 16 narrow runs all byte-identical. The paired negative is co-located in the same line rather than in a separate run — the reporter supplied it without knowing. Doubling is asserted as present, not uniform: 자바스크립트 and 시스템 each leave a jamo undoubled, which is a repaint-region boundary artifact rather than a per-character transform. The discriminating arm is in the test rather than a source mutation: |
||
|
|
97e4776dfe |
feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) (#8549)
* feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) Adds Orca's experimental plugin system behind a settings flag: a supervised kernel, declarative content packs (VM recipes, commands and keybindings, language packs), sandboxed iframe panels, forked worker hosts, and a Git-backed marketplace v0 with consent, provenance and kill-list enforcement. Theme, icon-theme and terminal-theme contributions are deferred to a follow-up pass. * fix(plugins): make unsupported marketplace listings unreachable by key findPlugin() backs preview/install/previewInstalledUpdate via requireListing(), so filtering only listPlugins() hid the catalog card while leaving the dead install path reachable one click later. * fix(plugins): fan Pi session-only status out to plugin subscribers The providerSessionOnly early-return in applyNormalizedStatus emitted to onAgentStatus (main-window fanout) but skipped enrichedStatusListeners, so plugins subscribed to agent.status.changed silently missed every Pi session_start event. Route both emit sites through one helper so a future early return cannot drop the plugin tap again. Co-authored-by: Orca <help@stably.ai> * plugins: drop dead code and hoist duplicated trust-boundary patterns Cleanup pass over the P1 diff, no behavior change: - Delete `readPluginTreeSnapshot`/`readSnapshotFile` and their types, plus the now-vestigial `directories`/`signal` plumbing in `collectFiles`. - Delete `resolveContainedPluginDirectory` (no callers). - Delete `plugin-content-load-pool.ts`; it reimplemented the existing `mapWithConcurrency`, whose index arg also removes the pairing wrapper in `buildPluginList`. - Hoist `PLUGIN_CONTENT_HASH_PATTERN` and `PLUGIN_COMMIT_PATTERN` into the install-lockfile module; 11 sites hand-rolled these identically. - Point the new reliability gate at the PR instead of gitignored docs paths, matching every other gate's link form. * fix(plugins): retry plugin state renames on Windows AV/EPERM locks Six plugin write paths (lockfile, provenance, current pointer, kill list, marketplace cache, staged install dir) did a plain rename, so an antivirus or indexer holding the target open surfaced as a failed install. The repo already retries this hazard for issue #1507, but only through a sync helper; these paths are all async. Adds one bounded async retry + atomic write used by all six, and trims a consent-provenance header that restated its own JSX. * test(plugins): cover the Windows rename retry path The retry loop shipped untested: both existing cases hit the non-retry path, and the temp-cleanup test passed identically with the `finally` removed. Mock `rename` to queue errno codes so CI can exercise locks it cannot provoke. Co-authored-by: Orca <help@stably.ai> * fix(plugins): pin bundled plugin resources to LF Windows CI checks out with autocrlf, so the byte-hashed launch tree arrived as CRLF and verify-packaged-plugin-resources rejected it — the packaged build could never pass on Windows. Reproduced locally: CRLF yields the exact CI error, LF verifies clean. Files are already LF, so nothing renormalizes. Co-authored-by: Orca <help@stably.ai> * test: guard the bundled-plugin LF pin against a CRLF checkout The byte-hash mismatch only surfaced in Windows packaging CI. Assert the .gitattributes pin and that a CRLF tree is rejected, so a regression fails on any platform instead of waiting for a packaged Windows build. Co-authored-by: Orca <help@stably.ai> * ci: trigger packaged-build check on bundled plugin resource changes The launch tree is byte-hashed during packaging, but no trigger path covered it — so the CRLF fix for that check would not have re-run the check. Add the resources, verifier and .gitattributes paths that can break packaging. Co-authored-by: Orca <help@stably.ai> * perf(plugins): rebuild the panel frame only when its baked theme values change The revision keys the panel iframe, so every bump destroys the sandboxed frame and its in-panel state. It counted root attribute mutations, but --workspace-sidebar-live-width is written every rAF of a sidebar drag, so dragging with a panel open blanked it ~60x/sec. Compare the two values the shell actually bakes in instead. Co-authored-by: Orca <help@stably.ai> * test: stop pinning a plugin name in the CRLF guard The CRLF case rewrites every launch file, so the reported mismatch is whichever plugin sorts first. P2 adds theme plugins that sort ahead of orca-navigation-shortcuts, which broke the assertion there. Co-authored-by: Orca <help@stably.ai> * style: drop stray blank lines left by the rebase resolutions Both sides of the agent-hooks and orca-runtime conflicts contributed a trailing blank, which oxfmt rejects. Whitespace only. Co-authored-by: Orca <help@stably.ai> * test(plugins): stop the startup budget failing on machine load P95 runs 16-34ms idle but exceeds the 50ms bound under full-suite parallelism, so the gate flaked. Widen it to catch an order-of-magnitude regression instead; the no-worker/no-plugin-code assertions are the real guarantee. Verified a 400ms regression still fails. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
f8b430f725 |
feat(skills): ship orca-cli as a first-generation hybrid stub (#9238)
* feat(skills): ship orca-cli as a first-generation hybrid stub Convert the installable orca-cli SKILL.md from a full fat guide into a hybrid discovery stub: a safe CLI resolver, an `orca skills get orca-cli` pointer, and a bounded read-only fallback for pre-guide binaries. The version-matched command reference now lives only in the Orca binary (embedded guide table, served by `orca skills get`), so the distributed file can no longer drift from the binary that runs the commands. - generator projects STUB_TOPICS from skill-stubs/<name>.md, reusing the guide's own frontmatter so the routing/description surface is unchanged; the embedded full guide (bundled-skill-guides.ts) is untouched. - manifest regenerated: orca-cli releaseRevision 32->33 as an append-only snapshot; existing fat installs classify `outdated` and get the targeted `npx skills update` nudge (no in-app writes). - tests: command-guidance assertions repointed to the guide source (their home now), plus stub-projection + safety coverage. Only orca-cli converts; the other skills stay fat. Per notes/skill-freshness-design.md, the E.3 pointer-compliance spike and the E.5 RC window remain before any further thinning. allowed-tools is intentionally not added yet (frontmatter kept byte-identical to the guide). * fix(skills): distinguish guide lookup failures * chore(skills): refresh released skill mapping |
||
|
|
31f643ca42 |
Add version-matched skill guides to the CLI (#8624)
* Add version-matched bundled skill guides * Clarify skill freshness rollout PRs * Add canonical skills show alias * fix(skills): address guide review feedback * fix(skills): make guide commands cross-platform * fix(skills): apply the ORCA convention to the emulator guides Review follow-up: the emulator guides still instructed literal `orca emulator ...` in sh fences with no Linux disambiguation, so on unmanaged Linux they could launch the GNOME screen reader — the exact failure the executable-selection preamble prevents. Both emulator guides now carry the preamble and ORCA placeholder across fences, tables, and prose, and the cross-platform safety test covers all four converted guides. Also replaces computer-use's "unless a block names a shell" carve-out, which contradicted its own POSIX example, with the unconditional placeholder rule. |
||
|
|
43e481b1c3 |
Revert "Decouple feature copy from translated locale catalogs (#8488)" (#8500)
This reverts commit
|
||
|
|
a5e9e139b1 |
Decouple feature copy from translated locale catalogs (#8488)
* Decouple feature copy from locale catalogs * Update PR workflow contract tests * Address localization review findings * Document localization cache context |
||
|
|
0ec3882cb8 |
Add project Windows runtime selection (#5519)
* Add project Windows runtime selection * Fix project Windows runtime selection Co-authored-by: Orca <help@stably.ai> * fix: preserve WSL shell variables --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: Neil <neil@stably.ai> |