Merge pull request #532 from feat/direct-assist-context-upgrade #864
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build Smoke | |
| on: | |
| pull_request: | |
| # Main had NO per-commit signal: this workflow ran on pull_request only, so a | |
| # PR whose Build Smoke failed could be merged and main itself was never | |
| # re-checked. That is exactly how c2ad3133 landed a TS7005 that broke the | |
| # FIRST build step — `Typecheck Electron main process` — and went unnoticed | |
| # for three days while every subsequent PR inherited a red check it did not | |
| # cause. With this trigger, a break is attributable to the commit that | |
| # introduced it instead of to whoever opens the next PR. | |
| push: | |
| branches: | |
| - main | |
| workflow_dispatch: | |
| # MUST live here, not on the job. `schedule` is not a valid job key, and an | |
| # unknown key makes GitHub reject the whole workflow at parse time — every run | |
| # since it was added completed with zero jobs, so no PR has actually been | |
| # smoke-tested. Verified: three most recent runs each report total_count 0. | |
| # | |
| # Moving it made the workflow PARSE. Making it PASS additionally required the | |
| # private `premium` submodule (electron/tsconfig.premium-check.json includes | |
| # '../premium/electron/**/*.ts', and esbuild bundles require('../premium/…') | |
| # calls) — see the checkout steps below, which fetch it explicitly. | |
| # | |
| # Do not use `submodules: true` here. Both gitlinks are registered, but this | |
| # build needs only the private `premium` dependency; fetching it explicitly | |
| # avoids an unrelated natively-api checkout and lets us provide the separate | |
| # repository credential only to the command that needs it. | |
| schedule: | |
| - cron: '0 9 * * 1' # Every Monday at 09:00 UTC | |
| jobs: | |
| electron-entry-smoke: | |
| # Natively ships on macOS AND Windows, so shared build/test behavior must be | |
| # proven on both. A macOS-only job cannot catch Windows-specific breakage in | |
| # package scripts: npm runs them through cmd.exe there, which has no POSIX | |
| # subshells, no `VAR=value cmd` prefixes, no `${VAR:-default}`, and does not | |
| # strip single quotes (so single-quoted globs reach Node literally and match | |
| # nothing). Every such break shipped green on the macOS-only matrix. | |
| name: Electron entry smoke (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| # Keep both legs running: knowing whether a failure is shared or | |
| # platform-specific is the whole point of the matrix. | |
| fail-fast: false | |
| matrix: | |
| os: | |
| - macos-latest | |
| - windows-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| # NOT `submodules: true`. Both gitlinks are now registered, but this | |
| # build needs only `premium`, which is private and requires a token | |
| # with access to that separate repository. The explicit step below is | |
| # path-limited to the build dependency and supplies that credential. | |
| # It avoids fetching the unrelated natively-api checkout on every leg. | |
| # | |
| # persist-credentials keeps the checkout token in .git/config so the | |
| # submodule clone can reuse the header rewrite configured below. | |
| persist-credentials: true | |
| # Fork PRs cannot receive repository secrets, so they cannot fetch the | |
| # private premium submodule. They run a real core compile + focused core | |
| # tests instead. Trusted PRs and main must have the credential and keep | |
| # running the complete premium build/test gate; a missing trusted secret | |
| # is an explicit failure, never a silent downgrade. | |
| - name: Select smoke scope | |
| id: smoke_scope | |
| env: | |
| SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN || secrets.GH_APP_TOKEN }} | |
| IS_FORK_PR: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }} | |
| shell: bash | |
| run: | | |
| if [ -n "${SUBMODULE_TOKEN:-}" ]; then | |
| echo "mode=full" >> "$GITHUB_OUTPUT" | |
| elif [ "$IS_FORK_PR" = "true" ]; then | |
| echo "mode=core" >> "$GITHUB_OUTPUT" | |
| echo "::notice::Private premium source is unavailable to fork PRs; running the fork-safe core smoke scope." | |
| else | |
| echo "mode=invalid" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Require premium credential on trusted runs | |
| if: steps.smoke_scope.outputs.mode == 'invalid' | |
| shell: bash | |
| run: | | |
| echo "::error::Trusted Build Smoke requires SUBMODULE_TOKEN or GH_APP_TOKEN to fetch the private premium submodule." | |
| exit 1 | |
| - name: Report fork-safe smoke scope | |
| if: steps.smoke_scope.outputs.mode == 'core' | |
| shell: bash | |
| run: | | |
| { | |
| echo "### Fork-safe Build Smoke" | |
| echo "Private premium source is not exposed to fork pull requests." | |
| echo "This run compiles the core Electron graph and executes focused Direct Assist, screenshot-priority, SSRF, trace, and coding-task tests." | |
| echo "Trusted runs still require and validate the full premium integration." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Checkout premium submodule | |
| if: steps.smoke_scope.outputs.mode == 'full' | |
| env: | |
| SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN || secrets.GH_APP_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # `git -c`, NOT `git config --local`. The submodule clone runs as a | |
| # SUBPROCESS, and a --local rewrite on the superproject does not reach | |
| # it — the first attempt used --local and the runner fell straight | |
| # through to an interactive credential prompt: | |
| # fatal: could not read Username for 'https://github.com' | |
| # `-c` is exported through GIT_CONFIG_PARAMETERS, so the child git | |
| # inherits it. Verified both ways against a local repro. | |
| # | |
| # It also keeps the token out of .git/config entirely: nothing is | |
| # written to disk, so there is no cleanup step that could be skipped | |
| # by an earlier failure and leave a credential behind in the workspace. | |
| # | |
| # Path-limited to the only submodule required by this build. | |
| git -c url."https://x-access-token:${SUBMODULE_TOKEN}@github.com/".insteadOf="https://github.com/" \ | |
| submodule update --init --force -- premium | |
| # Prove the checkout really produced the file the typecheck needs, | |
| # rather than an empty directory that fails 200 lines later. | |
| test -f premium/electron/knowledge/CompanyResearchEngine.ts | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| # Belt-and-suspenders: confirm postinstall produced correct-arch | |
| # binaries. The macOS runner is arm64, so x86_64 here means the install | |
| # itself was poisoned. Mirrors the developer-side pre-commit + boot-time | |
| # gates in main.ts. macOS-only by design: the check guards against Rosetta | |
| # arch drift, which has no Windows analogue (verify-native-arch.js already | |
| # returns `skipped` off darwin, so running it there proves nothing). | |
| - name: Verify native module architecture | |
| if: runner.os == 'macOS' | |
| run: node scripts/verify-native-arch.js | |
| # Real-execution proof that this platform's sqlite-vec binary loads and | |
| # answers a vector query (2026-08-15). The Windows leg is the ONLY | |
| # automated Windows this repo has, and vec0.dll had never been executed | |
| # anywhere — the Windows pin (sqlite-vec-windows-x64) was verified from | |
| # macOS as fetch + loader-name mapping only. BLOCKING on BOTH legs by | |
| # design: VectorStore degrades to a JS-cosine fallback when the native | |
| # extension is missing, which is exactly how a broken platform binary | |
| # would otherwise ship green. Runs under Electron's ABI because | |
| # postinstall builds better-sqlite3 for Electron, not system Node. | |
| - name: Verify sqlite-vec native extension loads (vec0) | |
| env: | |
| ELECTRON_RUN_AS_NODE: "1" | |
| run: npx --no-install electron scripts/verify-sqlite-vec-load.mjs | |
| # TypeScript 7 is now the PRIMARY type-checker: `typecheck:electron` runs | |
| # node_modules/typescript7/bin/tsc. Emit is untouched — esbuild owns | |
| # dist-electron and vite owns dist; TS 7 only checks. | |
| # | |
| # This runs on BOTH legs on purpose. TS 7 is a native Go binary shipped as | |
| # per-platform optional dependencies (@typescript/typescript-darwin-arm64, | |
| # -win32-x64, ...), so the Windows leg is the ONLY thing that proves the | |
| # win32 binary resolves and runs — a macOS-only check would say nothing | |
| # about it. | |
| # | |
| # ⚠️ EXPECTED RED until the two deferred findings are resolved | |
| # IntelligenceEngine's queryOkfCards vs | |
| # EvidenceResolver's DI port, and ModeHybridRetriever's genuinely-nullable | |
| # reranker. Deliberately left BLOCKING rather than downgraded to | |
| # continue-on-error — muting a real gate to hide two known type errors is | |
| # exactly the loosening this migration is not allowed to do. TS 5.9.3 and | |
| # TS 7.0.2 report byte-identical error lists here, so this is a deferral | |
| # backlog, NOT a TS 7 incompatibility. | |
| - name: Typecheck Electron main process (TypeScript 7) | |
| run: npm run typecheck:electron | |
| # Coverage gate for the `premium/` submodule. tsconfig.json deliberately | |
| # drops premium from its ROOT FILE SET so `strict` applies only to this | |
| # repo's sources — which silently took 38 premium files (KnowledgeOrchestrator, | |
| # StructuredExtractor, NegotiationEngine, LicenseManager, all of roleInsight/) | |
| # out of type-checking entirely, because they are reached via untyped | |
| # require() and nothing else checks them: premium/ has no tsconfig of its own. | |
| # This runs them at the strictness they were written against. | |
| - name: Typecheck premium submodule (TypeScript 7) | |
| if: steps.smoke_scope.outputs.mode == 'full' | |
| run: npm run typecheck:premium | |
| # Insurance for one release cycle: if TS 7 ever diverges from the compiler | |
| # this repo shipped on, this step is what tells us. Non-blocking because | |
| # TS 7 is the source of truth now. `typescript` 5.x stays installed | |
| # regardless — react-doctor (.husky/pre-commit, every commit by everyone), | |
| # typescript-eslint and tap all cap below TS 7. | |
| - name: Typecheck Electron main process with TypeScript 5 (non-blocking insurance) | |
| continue-on-error: true | |
| run: npm run typecheck:ts5:electron | |
| # `npm run build` now type-checks the renderer with TS 7 before vite emits. | |
| - name: Build renderer | |
| run: npm run build | |
| - name: Build Electron main process | |
| if: steps.smoke_scope.outputs.mode == 'full' | |
| run: npm run build:electron | |
| - name: Build Electron main process (fork-safe core) | |
| if: steps.smoke_scope.outputs.mode == 'core' | |
| run: npm run build:electron:core-smoke | |
| # `test -f` is a POSIX builtin and does not exist in the Windows runner's | |
| # default shell (PowerShell). Node is guaranteed present on both legs. | |
| - name: Verify packaged entrypoint exists | |
| run: node -e "require('node:fs').accessSync('dist-electron/electron/main.js')" | |
| # Required packaged local-fallback assets (ONNX/Transformers deps + | |
| # bundled MiniLM/MobileBERT models + local ONNX worker scripts). Fail the | |
| # build here if any required core-fallback asset is missing — the app must | |
| # work on a clean machine with no Ollama and no API keys. | |
| - name: Verify required local models present | |
| run: node scripts/download-models.js --verify | |
| - name: Verify packaged local fallback assets (source mode) | |
| run: npm run verify:packaged-local-assets | |
| # Windows runs these ADVISORY (2026-08-10). Previously both steps were | |
| # skipped outright on Windows, which meant the windows-latest leg reported | |
| # green while running ZERO tests — its signal covered install and build | |
| # only, and the matrix looked stronger than it was. | |
| # | |
| # The original gate was deliberate, and its reasoning stands: measured on a | |
| # Windows 11 host, `npm test` failed 139 of 2832 and `test:intelligence` 3 | |
| # of 957, all pre-existing Windows incompatibilities in the suites (POSIX | |
| # path assumptions, tsc-isolated-tree helpers, symlink fixtures). Shipping | |
| # that red on every PR trains reviewers to ignore the workflow and would | |
| # destroy the macOS signal that already works. | |
| # | |
| # `continue-on-error` keeps both properties: the suites actually RUN on | |
| # Windows so the gap is visible and measurable per-PR, while only macOS can | |
| # fail the check. A Windows regression now shows up as a warning instead of | |
| # being invisible. | |
| # | |
| # RE-MEASURED 2026-08-17 on main @ e701c809 (the first fully green run), by | |
| # attributing each count to its own step in the Windows job log rather than | |
| # trusting step conclusions — two of them carry continue-on-error, so their | |
| # "success" proves nothing: | |
| # | |
| # npm test 7540 tests, 41 fail <- still advisory, below | |
| # test:intelligence 963 tests, 0 fail <- now ENFORCING | |
| # test:lib 327 tests, 0 fail <- already enforcing | |
| # test:scripts 20 tests, 0 fail <- already enforcing | |
| # | |
| # So the old "139 of 2832 / 3 of 957" figures are superseded: the real | |
| # Windows gap is 41 tests in ONE suite, and intelligence has closed | |
| # completely. The TODO below is being worked off suite by suite as each | |
| # goes green, which is what it asked for. | |
| # | |
| # TODO: fix the remaining 41 Windows failures in `npm test`, then drop its | |
| # `continue-on-error` too and the leg is enforcing on both platforms. | |
| - name: Run Electron unit tests | |
| if: steps.smoke_scope.outputs.mode == 'full' | |
| continue-on-error: ${{ runner.os == 'Windows' }} | |
| run: npm test | |
| - name: Run fork-safe core Electron tests | |
| if: ${{ !cancelled() && steps.smoke_scope.outputs.mode == 'core' }} | |
| run: npm run test:core-smoke | |
| # The four test steps here are INDEPENDENT suites over disjoint globs, so | |
| # each of the three below carries `!cancelled()`. Without it a failed step | |
| # skips every later one, and on the macOS leg — the only enforcing leg — | |
| # that meant one red suite suppressed the other three: `test:intelligence` | |
| # had never once been measured in CI, because `npm test` failed on every | |
| # run that ever reached it. (Windows was unaffected only because | |
| # `continue-on-error` keeps its steps' conclusion green.) | |
| # | |
| # `!cancelled()` rather than `always()`: both keep a later suite running | |
| # after an earlier one fails, but `always()` ALSO forces the step to run | |
| # while the job is being cancelled, turning a cancel into a multi-minute | |
| # wait. The job still fails if any suite fails — this only stops one | |
| # failure from hiding the others' results. | |
| # ENFORCING ON BOTH LEGS since 2026-08-17. This carried | |
| # `continue-on-error: runner.os == 'Windows'` while Windows failed 3 of 957 | |
| # here; it now passes 963 of 963 on Windows (green on the two consecutive | |
| # runs before this change landed), so the gate was muting a suite that no | |
| # longer needs muting — and a muted step cannot report the regression it | |
| # exists to catch. | |
| - name: Run intelligence unit tests | |
| if: ${{ !cancelled() && steps.smoke_scope.outputs.mode == 'full' }} | |
| run: npm run test:intelligence | |
| # Covers src/lib/__tests__ and src/lib/onboarding/__tests__ (renderer-side | |
| # onboarding orchestrator/stageCatalog, overlay state, etc.). Previously | |
| # unwired: no CI job ran these, including the regression invariant added | |
| # for the quiet_window drain-loop OOM crash. --experimental-strip-types | |
| # lets the suite import stageCatalog.ts directly (not just its .mjs test | |
| # mirror), so a real production-catalog regression fails CI, not just the | |
| # hand-maintained mirror. | |
| - name: Run src/lib unit tests | |
| if: ${{ !cancelled() && steps.smoke_scope.outputs.mode != 'invalid' }} | |
| run: npm run test:lib | |
| # Runs on BOTH legs by design. scripts/package-app.js and | |
| # scripts/run-with-env.mjs exist solely to reconcile /bin/sh and cmd.exe | |
| # behavior for `npm run dist` and `test:electron`; verifying them on one | |
| # OS would defeat their purpose. The helpers are pure and take tmpdir / | |
| # resolver / signal-table as parameters, so each leg exercises both | |
| # platform shapes rather than only its own. | |
| - name: Run build-script unit tests | |
| if: ${{ !cancelled() && steps.smoke_scope.outputs.mode != 'invalid' }} | |
| run: npm run test:scripts | |
| api-smoke: | |
| # Runs weekly (Monday 09:00 UTC, see the schedule trigger above) and on | |
| # demand via workflow_dispatch. Requires secrets: NATIVELY_API_KEY (or | |
| # NATIVELY_TRIAL_TOKEN) set in repo. Without credentials the suite is | |
| # skipped cleanly via the env gate. | |
| name: API Smoke (weekly) | |
| runs-on: macos-latest | |
| if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| # NOT `submodules: true`. The tree carries TWO gitlinks — `premium` and | |
| # `natively-api` — but .gitmodules only describes `premium`. | |
| # `submodules: true` runs `git submodule update --init --recursive`, | |
| # which aborts on the undescribed one: | |
| # | |
| # fatal: No url found for submodule path 'natively-api' in .gitmodules | |
| # | |
| # and it aborts BEFORE cloning `premium`, so the checkout would fail | |
| # while still leaving the directory empty. The explicit step below is | |
| # path-limited to `premium` and therefore unaffected by the orphan. | |
| # | |
| # persist-credentials keeps the checkout token in .git/config so the | |
| # submodule clone can reuse the header rewrite configured below. | |
| persist-credentials: true | |
| # `premium` is a PRIVATE submodule (Natively-AI-assistant/natively-premium) | |
| # that both build steps genuinely need: electron/tsconfig.premium-check.json includes | |
| # ../premium/electron/**/*.ts, and esbuild bundles require('../premium/…') | |
| # calls. Without it every run dies at the first typecheck with | |
| # TS2307: Cannot find module '../../premium/electron/knowledge/CompanyResearchEngine' | |
| # which is what every run of this workflow has done since it was added. | |
| # | |
| # GITHUB_TOKEN cannot read another private repo, so a token with access is | |
| # required. Absent one, this step is SKIPPED rather than failed: the job | |
| # then dies at the typecheck exactly as it does today, which is no worse | |
| # than the status quo and keeps the reason visible in the step list. | |
| # | |
| # Two steps, because a step's own `env:` is NOT in scope for its `if:`. | |
| # This one publishes the availability as an output the next step can gate | |
| # on, without ever putting the secret itself in a condition. | |
| - name: Detect submodule token | |
| id: submodule_token | |
| env: | |
| SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN || secrets.GH_APP_TOKEN }} | |
| shell: bash | |
| run: | | |
| if [ -n "${SUBMODULE_TOKEN:-}" ]; then | |
| echo "available=true" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "available=false" >> "$GITHUB_OUTPUT" | |
| echo "::warning::No SUBMODULE_TOKEN/GH_APP_TOKEN; the private premium submodule cannot be fetched and the typecheck step will fail on TS2307." | |
| fi | |
| - name: Checkout premium submodule | |
| if: steps.submodule_token.outputs.available == 'true' | |
| env: | |
| SUBMODULE_TOKEN: ${{ secrets.SUBMODULE_TOKEN || secrets.GH_APP_TOKEN }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # `git -c`, NOT `git config --local`. The submodule clone runs as a | |
| # SUBPROCESS, and a --local rewrite on the superproject does not reach | |
| # it — the first attempt used --local and the runner fell straight | |
| # through to an interactive credential prompt: | |
| # fatal: could not read Username for 'https://github.com' | |
| # `-c` is exported through GIT_CONFIG_PARAMETERS, so the child git | |
| # inherits it. Verified both ways against a local repro. | |
| # | |
| # It also keeps the token out of .git/config entirely: nothing is | |
| # written to disk, so there is no cleanup step that could be skipped | |
| # by an earlier failure and leave a credential behind in the workspace. | |
| # | |
| # Path-limited to `premium` on purpose — see the checkout comment above. | |
| git -c url."https://x-access-token:${SUBMODULE_TOKEN}@github.com/".insteadOf="https://github.com/" \ | |
| submodule update --init --force -- premium | |
| # Prove the checkout really produced the file the typecheck needs, | |
| # rather than an empty directory that fails 200 lines later. | |
| test -f premium/electron/knowledge/CompanyResearchEngine.ts | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Build Electron (test prerequisite) | |
| run: npm run build:electron | |
| # Dedicated script rather than `npm test -- --test-name-pattern`: the test | |
| # runner now launches under Electron (so native modules built for | |
| # Electron's ABI load), and the extra argv layer does not forward node's | |
| # --test-name-pattern through to the runner — the flag was silently | |
| # ignored and the FULL suite ran. Targeting the one file is also cheaper | |
| # than filtering the whole glob set. | |
| - name: Run API smoke tests | |
| run: npm run test:api-smoke | |
| env: | |
| RUN_NATIVELY_API_E2E: ${{ secrets.RUN_NATIVELY_API_E2E }} | |
| NATIVELY_API_KEY: ${{ secrets.NATIVELY_API_KEY }} | |
| NATIVELY_TRIAL_TOKEN: ${{ secrets.NATIVELY_TRIAL_TOKEN }} | |
| NATIVELY_API_BASE: ${{ secrets.NATIVELY_API_BASE }} |