Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Windows batch files must keep CRLF line endings byte for byte.
*.cmd -text
39 changes: 31 additions & 8 deletions .github/ISSUE_TEMPLATE/installation-failure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ body:
- type: markdown
attributes:
value: |
Thanks for reporting this. Never paste an OpenRouter key, Codex device code, password, conversation, private Bot file, or Grok host source. GrokRouter's safe report contains the complete non-secret fingerprint we need.
Thanks for reporting this to the maintained GrokRouter repository, swcstudiospace/grokrouter. Never paste an OpenRouter key, Codex device code, password, conversation, private Bot file, or Grok host source. GrokRouter's safe report contains the complete non-secret fingerprint we need.

- type: dropdown
id: platform
attributes:
label: Platform
description: Choose the physical computer running the Grok Bot desktop app. Windows builds are source previews and have not completed native live acceptance; macOS Apple silicon is the supported beginner path. The Bot computer may separately report x86_64, which is normal cloud architecture.
description: Choose the physical computer running the Grok Bot desktop app. Windows builds, including the Windows source installer, have not completed native live acceptance; macOS Apple silicon is the supported beginner path. The Bot computer may separately report x86_64, which is normal cloud architecture.
options:
- Apple-silicon Mac
- Windows x64 preview
Expand All @@ -26,22 +26,45 @@ body:
id: grokrouter_version
attributes:
label: GrokRouter version
description: Enter the Installer line from Copy safe diagnostics, such as 0.1.0-beta.46. Do not enter Grok Bot 0.30.0 here.
placeholder: 0.1.0-beta.46
description: Enter the Installer line from Copy safe diagnostics, such as 0.1.0-beta.48. Do not enter the Grok Bot desktop version here.
placeholder: 0.1.0-beta.48
validations:
required: true

- type: input
id: grok_bot_version
attributes:
label: Grok Bot desktop version
description: Enter the official desktop app version separately from the GrokRouter version. State whether Grok Bot updated after installation or before this failure.
placeholder: "0.44.0; updated to 0.61.0 before Repair"
validations:
required: true

- type: dropdown
id: install_source
attributes:
label: Installation source
description: Choose the exact package or command you ran. The pinned Terminal command is macOS-only. Old forks and downloaded copies can contain an earlier router even when this README is current.
description: Choose the exact package or command you ran. Copies from the unmaintained promptadvisers/grokrouter upstream, other forks, and old downloads can contain an earlier router even when this README is current.
options:
- Pinned beta.46 Mac Terminal command from the official README
- Pinned Mac Terminal command from the official README
- Pinned Windows PowerShell command from the official README
- Install GrokRouter.cmd or install-windows.ps1 from an official source ZIP or clone
- Windows x64 CI preview artifact
- Windows Arm64 CI preview artifact
- ZIP downloaded from the official repository
- Fork, clone, old ZIP, or another source
- promptadvisers/grokrouter, another fork, an old ZIP, or another source
- I do not know
validations:
required: true

- type: dropdown
id: unreviewed_version
attributes:
label: Was "Allow unreviewed Grok Bot version (experimental)" turned on?
description: This default-off option lets GrokRouter try a Grok Bot desktop version newer than every reviewed one. Safe diagnostics then show HOSTTRUST=UNREVIEWED-ANCHOR-VERIFIED instead of an exact reviewed fingerprint.
options:
- "No"
- "Yes"
- I do not know
validations:
required: true
Expand Down Expand Up @@ -89,7 +112,7 @@ body:
id: safe_diagnostics
attributes:
label: Safe diagnostics
description: In the GrokRouter desktop app, click Copy safe diagnostics and paste the entire report once. Beta.46 includes the non-secret host fingerprint for a genuinely new stock variant. Windows reports from source after beta.46 also preserve the last installer phase and fingerprint lines.
description: In the GrokRouter desktop app, click Copy safe diagnostics and paste the entire report once. Include the host fingerprint and last installer phase. An unsupported app version and an unknown cloud-host hash are different failures.
render: text
validations:
required: true
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CI

on:
workflow_call:
pull_request:
push:
branches: [main]
Expand All @@ -23,6 +24,8 @@ jobs:
cache-dependency-path: runtime/package-lock.json
- name: Install pinned runtime dependencies
run: npm ci --prefix runtime --ignore-scripts --no-audit --no-fund
- name: Verify release version consistency
run: node scripts/verify-release.mjs
- name: Test all layers
run: npm test
- name: Build ad-hoc beta installer
Expand Down Expand Up @@ -75,3 +78,51 @@ jobs:
build/grokrouter-*-windows-arm64-setup.exe
build/grokrouter-*-windows-arm64-setup.exe.sha256
if-no-files-found: error
- name: Parse the Windows source installer
shell: powershell
run: |
foreach ($shell in 'powershell', 'pwsh') {
& $shell -NoProfile -Command {
$tokens = $null; $errors = $null
[void][System.Management.Automation.Language.Parser]::ParseFile((Resolve-Path 'scripts/install-windows.ps1').Path, [ref]$tokens, [ref]$errors)
foreach ($problem in $errors) { Write-Host "install-windows.ps1:$($problem.Extent.StartLineNumber): $($problem.Message)" }
exit $errors.Count
}
if ($LASTEXITCODE -ne 0) { throw "$shell could not parse scripts/install-windows.ps1" }
}
# Runs after the artifact upload because it rebuilds build/windows. No Grok
# Bot is needed: the installer only builds, places, and links the app.
- name: Install twice from the checkout with the Windows source installer
shell: powershell
env:
GROKROUTER_NO_OPEN: "1"
run: |
$ErrorActionPreference = 'Stop'
$env:GROKROUTER_INSTALL_DIR = Join-Path $env:RUNNER_TEMP 'GrokRouter'
$stale = Join-Path $env:RUNNER_TEMP 'GrokRouter.previous-19990101-000000'
# A non-GrokRouter folder that merely shares the prefix must survive pruning.
$foreign = Join-Path $env:RUNNER_TEMP 'GrokRouter.previous-user-notes'
foreach ($pass in 1, 2) {
if ($pass -eq 2) {
New-Item -ItemType Directory -Path $stale, $foreign | Out-Null
Set-Content -LiteralPath (Join-Path $stale 'GrokRouter.exe') -Value 'stale'
}
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/install-windows.ps1
if ($LASTEXITCODE -ne 0) { throw "install pass $pass failed" }
if (-not (Test-Path -LiteralPath (Join-Path $env:GROKROUTER_INSTALL_DIR 'GrokRouter.exe') -PathType Leaf)) { throw "install pass $pass did not place GrokRouter.exe" }
}
if (-not (Test-Path -LiteralPath $foreign)) { throw 'pruning removed a folder that was not a GrokRouter install' }
$backups = @(Get-ChildItem -LiteralPath $env:RUNNER_TEMP -Directory -Filter 'GrokRouter.previous-*' | Where-Object FullName -ne $foreign)
if ($backups.Count -ne 1 -or $backups[0].FullName -eq $stale) { throw "expected only the newest previous install, found: $($backups.Name -join ', ')" }
if (-not (Test-Path -LiteralPath (Join-Path $backups[0].FullName 'GrokRouter.exe') -PathType Leaf)) { throw 'the previous install was not preserved' }
if (@(Get-ChildItem -LiteralPath $env:RUNNER_TEMP -Directory -Filter 'GrokRouter.installing-*').Count -ne 0) { throw 'a staging directory was left behind' }
if (-not (Test-Path -LiteralPath (Join-Path ([Environment]::GetFolderPath('Programs')) 'GrokRouter.lnk'))) { throw 'the Start menu shortcut is missing' }
$occupied = Join-Path $env:RUNNER_TEMP 'NotGrokRouter'
New-Item -ItemType Directory -Path $occupied | Out-Null
Set-Content -LiteralPath (Join-Path $occupied 'keep.txt') -Value 'user data'
$env:GROKROUTER_INSTALL_DIR = $occupied
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/install-windows.ps1
if ($LASTEXITCODE -eq 0) { throw 'the installer replaced a folder that was not a GrokRouter install' }
if (-not (Test-Path -LiteralPath (Join-Path $occupied 'keep.txt'))) { throw 'the installer touched a folder that was not a GrokRouter install' }
# The refusal above is the expected outcome; the step wrapper would otherwise exit with its code.
$global:LASTEXITCODE = 0
26 changes: 26 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CodeQL
on:
push:
branches: [main]
pull_request:
schedule:
- cron: '22 6 * * 1'
permissions:
contents: read
jobs:
analyze:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [javascript-typescript, python]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: github/codeql-action/init@fddeee1a7ece751b577e409a89057319e3172939 # v4
with:
languages: ${{ matrix.language }}
build-mode: none
- uses: github/codeql-action/analyze@fddeee1a7ece751b577e409a89057319e3172939 # v4
25 changes: 16 additions & 9 deletions .github/workflows/probe-ingest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ name: Ingest host probe
# plus one reviewed anchor per line;
# - version-watch.yml calls it with the sanitized probe that
# scripts/auto-probe.py produced on the self-hosted Bot-computer runner.
# The workflow validates the probe (stock host, exact anchor counts, no router
# marker), writes patch/manifests/<version>.json, and updates the installer
# version lists. Merging still requires `npm test` and the full live fresh-Bot
# gate in docs/FRESH-BOT-ACCEPTANCE.md recorded in docs/TEST-MATRIX.md.
# The workflow validates the probe (stock host, exact anchor and patch-seam
# counts, no router marker), writes patch/manifests/<version>.json with
# structural trust disabled, adds the version to supported-apps.json, writes
# the UNSIGNED compatibility/<version>-hosts.json, and updates the installer
# version lists. CI never holds the registry signing key: a maintainer signs
# locally with scripts/sign-host-registry.mjs. Merging still requires
# `npm test` and the full live fresh-Bot gate in docs/FRESH-BOT-ACCEPTANCE.md
# recorded in docs/TEST-MATRIX.md.
on:
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -77,7 +81,7 @@ jobs:
run: |
set -euo pipefail
python3 -m unittest tests/test_patch.py tests/test_version_tracking.py
bash -n scripts/install-macos.sh
node --check installer-windows/main.cjs
bash -n remote/install.sh
- name: Open a draft PR with the live-acceptance checklist
env:
Expand All @@ -91,21 +95,24 @@ jobs:
git config user.email "grokrouter-bot@users.noreply.github.com"
git checkout -b "$branch"
git add patch/manifests/"$NEW_VERSION".json \
compatibility/supported-apps.json \
compatibility/"$NEW_VERSION"-hosts.json \
installer/GrokBotRouterInstaller.swift \
scripts/install-macos.sh remote/install.sh
installer-windows/main.cjs
git commit -m "Draft support for Grok Bot $NEW_VERSION from a live host probe" \
-m "Automated scaffold from a reviewed host-probe report. DO NOT MERGE until npm test and docs/FRESH-BOT-ACCEPTANCE.md pass on a live $NEW_VERSION Bot computer and docs/TEST-MATRIX.md records the result."
-m "Automated scaffold from a reviewed host-probe report. DO NOT MERGE until a maintainer signs compatibility/$NEW_VERSION-hosts.json locally, npm test and docs/FRESH-BOT-ACCEPTANCE.md pass on a live $NEW_VERSION Bot computer, and docs/TEST-MATRIX.md records the result."
git push --set-upstream origin "$branch"
tracking=""
if [[ -n "$TRACKING_ISSUE" ]]; then
tracking="Tracking issue: #$TRACKING_ISSUE"
fi
gh pr create --draft --head "$branch" --title "Draft: support Grok Bot $NEW_VERSION" --body "$(cat <<EOF
Automated scaffold from a \`scripts/host-probe.py\` report of the untouched $NEW_VERSION host. The stock hash, byte count, and exact-once anchors come from that live host; no gate was loosened. $tracking
Automated scaffold from a \`scripts/host-probe.py\` report of the untouched $NEW_VERSION host. The stock hash, byte count, exact-once anchors, and patch seams come from that live host; no gate was loosened. $tracking

## Do-not-merge checklist

- [ ] \`npm test\` passes with this manifest.
- [ ] Maintainer signed the registry locally: \`node scripts/sign-host-registry.mjs compatibility/$NEW_VERSION-hosts.json\` (private key at \`~/.config/grokrouter/release/host-registry-private.pem\`, never in CI), then committed \`compatibility/$NEW_VERSION-hosts.json.sig\`.
- [ ] \`npm test\` passes with this manifest and the signed registry.
- [ ] Full fresh-Bot procedure in \`docs/FRESH-BOT-ACCEPTANCE.md\` passes on Grok Bot $NEW_VERSION (install → restore → reinstall → new Bot → \`/router doctor\` → \`/provider\` → normal turn).
- [ ] Result recorded in \`docs/TEST-MATRIX.md\` with the visible evidence and redacted audit receipt.
- [ ] README supported-version badge/message updated only after the gate above passes.
Expand Down
46 changes: 27 additions & 19 deletions .github/workflows/tag-release.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
name: Tag source release

# Creates the annotated source tag that the README's pinned Terminal command
# downloads. Run it from the Actions tab after the version bump has merged,
# or opt in from a release commit by putting [tag-release] in the message of
# the commit that lands on main. It refuses to tag a commit whose version
# fields disagree with the requested version.
# Creates the annotated source tag that the README's pinned macOS Terminal and
# Windows PowerShell commands download. Run it from the Actions tab after the
# version bump has merged, or opt in from a release commit by putting
# [tag-release] in the message of the commit that lands on main. It refuses to
# tag a commit whose version fields, installer source refs, or README commands
# disagree with the requested version. Only the maintained repository tags:
# the pinned commands download from swcstudiospace/grokrouter, so a tag pushed
# anywhere else would publish nothing users can reach.
on:
workflow_dispatch:
inputs:
version:
description: "Release version exactly as in package.json (for example 0.1.0-beta.46)"
description: "Release version exactly as in package.json (for example 0.1.0-beta.48)"
required: true
type: string
push:
branches: [main]

permissions:
contents: write
contents: read

concurrency:
group: grokrouter-source-release
cancel-in-progress: false

jobs:
verify:
if: github.repository == 'swcstudiospace/grokrouter' && github.ref == 'refs/heads/main' && (github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[tag-release]'))
uses: ./.github/workflows/ci.yml
tag:
if: github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[tag-release]')
needs: verify
permissions:
contents: write
if: github.repository == 'swcstudiospace/grokrouter' && (github.event_name == 'workflow_dispatch' || contains(github.event.head_commit.message, '[tag-release]'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -45,20 +58,15 @@ jobs:
echo "Version '$REQUESTED' is not a valid release version" >&2
exit 1
fi
for file in package.json runtime/package.json installer-windows/package.json; do
actual="$(node -p "require('./$file').version")"
if [[ "$actual" != "$REQUESTED" ]]; then
echo "$file is $actual but $REQUESTED was requested" >&2
exit 1
fi
done
grep -Fq "SOURCE_REF=\"source-v$REQUESTED\"" scripts/install-macos.sh
grep -Fq "source-v$REQUESTED/scripts/install-macos.sh" README.md
node scripts/verify-release.mjs "$REQUESTED"
node scripts/verify-acceptance.mjs
if git ls-remote --exit-code --tags origin "source-v$REQUESTED" >/dev/null 2>&1; then
echo "Tag source-v$REQUESTED already exists; nothing to do" >&2
exit 1
existing="$(git rev-list -n 1 "source-v$REQUESTED")"
[[ "$existing" == "$(git rev-parse HEAD)" ]] || { echo "Refusing to move an existing release tag" >&2; exit 1; }
echo "TAG_EXISTS=true" >> "$GITHUB_ENV"
fi
- name: Create and push the annotated source tag
if: env.TAG_EXISTS != 'true'
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/version-watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
set -euo pipefail
title="Grok Bot $NEW_VERSION has no router manifest"
body="$(cat <<EOF
The official stable feed now reports **Grok Bot $NEW_VERSION**, which has no manifest in \`patch/manifests/\`. GrokRouter refuses to install there until a real host probe lands. This issue tracks that work; it does not loosen any gate.
The official stable feed now reports **Grok Bot $NEW_VERSION**, which has no manifest in \`patch/manifests/\`. GrokRouter's normal install refuses it until a real host probe lands; users can only opt into the experimental **Allow unreviewed Grok Bot version** installer checkbox, which accepts the host by structural checks instead of a reviewed hash. This issue tracks reviewed support; it does not loosen any gate.

## Automatic path

Expand All @@ -74,11 +74,11 @@ jobs:
python3 scripts/host-probe.py --anchor 'function createMockPromptExecutor(options2)' --anchor 'createSession(onRequestId, sessionOptions)' --anchor 'const mainSessionOptions = {' > /tmp/probe.txt
\`\`\`

If an anchor reports a count other than 1, use the probe's \`candidates\` lines to pick the replacement source line for this build, re-run the probe with \`--anchor '<exact line>'\` for it, and confirm every chosen anchor counts exactly once with an empty \`candidates.routerMarker\`.
If an anchor reports a count other than 1, use the probe's \`candidates\` lines to pick the replacement source line for this build, re-run the probe with \`--anchor '<exact line>'\` for it, and confirm every chosen anchor and every \`patchAnchors\` seam counts exactly once with an empty \`candidates.routerMarker\`.

## 2. Ingest the probe

Paste the complete probe output into the **Ingest host probe** workflow (Actions tab): \`version\` = \`$NEW_VERSION\`, \`probe_json\` = the full output, \`anchors\` = one reviewed anchor per line. It scaffolds \`patch/manifests/$NEW_VERSION.json\`, updates the installer version lists, and opens a draft PR.
Paste the complete probe output into the **Ingest host probe** workflow (Actions tab): \`version\` = \`$NEW_VERSION\`, \`probe_json\` = the full output, \`anchors\` = one reviewed anchor per line. It scaffolds \`patch/manifests/$NEW_VERSION.json\`, the supported-apps entry, the unsigned \`compatibility/$NEW_VERSION-hosts.json\`, and the installer version lists, then opens a draft PR. A maintainer signs the registry locally with \`scripts/sign-host-registry.mjs\`.

## 3. Prove support before claiming it

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ runtime/channel-control-latch.json
*.zip
handoff/
__pycache__/
runtime/channel-control-latch.json.*
runtime/openrouter-catalog.json
runtime/xai-oauth.json
runtime/model-catalog.*.json
Expand Down
Loading
Loading