Skip to content

feat(dashboard): one-shot model connect, live connect-check, setup checklist - #1157

Merged
aaronjmars merged 21 commits into
mainfrom
feat/dashboard-one-shot-connect
Oct 3, 2026
Merged

aaronjmars merged 21 commits into
mainfrom
feat/dashboard-one-shot-connect

Conversation

@aaronjmars

@aaronjmars aaronjmars commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Stacks on #1158 (credential manifest). The base is feat/aeon-init-credential-manifest, so this diff shows only the dashboard work. Merge #1158 first, then retarget this PR to main.

What

Connecting a model harness in the local dashboard is now one modal and two steps, followed by a live check that the credential really works on GitHub.

  • One ConnectModal replaces AuthModal, GrokAuthModal and HarnessAuthModal. It adapts to the harness:
    • Step 1, run this on your computer: the exact command with a Copy button. Examples: claude setup-token, or codex login && tar -czf - -C ~ .codex/auth.json | base64 | pbcopy (there is also a Linux variant that prints the result). Key-only harnesses (pi, vibe, fx, cursor) get "get a key" links instead. It also shows the ./aeon auth ... command for people working inside their repo.
    • Step 2, paste the result: one box. Before you save, it shows what it detected, e.g. "Detected: Claude subscription -> CLAUDE_CODE_OAUTH_TOKEN".
      • Keys are recognized by prefix (sk-ant-oat, sk-ant-api, sk-or-, sk-, xai-, bk_, inf_).
      • Login captures are base64 tar.gz archives that the server opens and maps to CODEX_AUTH / KIMI_AUTH / HERMES_AUTH / GROK_CREDENTIALS.
      • A small provider dropdown covers keys with no prefix (UsePod, Venice, GLM, HivemindOS, Mistral, Moonshot, Cursor, AI Gateway).
    • Other ways:
      • One-click OpenRouter (OAuth PKCE in a popup) on every harness that takes OPENROUTER_API_KEY.
      • Only when the dashboard runs locally:
        • Do it for me runs the existing CLI login flows.
        • Found on this machine lists existing CLI logins and model env keys by name only, never values.
  • Test connection: after any save, a tiny new connect-check skill runs on GitHub. The page (not the modal) follows the run, so HQ keeps updating after you close the modal.
    • For harnesses that report token counts, it only goes green if the run succeeded and the model used more than zero tokens.
    • cursor, kimi and vibe (token_usage: none in the manifest) pass on a successful run whose answer is exactly AEON_CONNECT_OK.
    • On failure it gives the reason and a next step. Example: a rejected Claude subscription token says to remove CLAUDE_CODE_OAUTH_TOKEN, and offers a one-click Remove subscription token button.
  • Telegram chat id without copy-paste:
    • After the bot token is saved, Settings shows a t.me/<bot>?start=<code> link.
    • The server polls getUpdates without an offset and saves TELEGRAM_CHAT_ID when the /start <code> message arrives.
    • It falls back to the manual helper if the bot uses a webhook or has 100+ unread updates.
  • ./aeon init tests the model too. After the model step, a new "Test connection" step asks first (default yes; without a terminal it only runs with --yes; --no-test skips it). It dispatches connect-check through the same lib the dashboard uses and reports either pass with the token count, or fail with the reason, the next step and ./aeon secrets rm CLAUDE_CODE_OAUTH_TOKEN when the subscription token is the cause. The result is a row in the Summary. An instance that predates connect-check is told to update instead of dispatching a run that would fail.
  • HQ auto-test: when a model key exists but no check has ever run for the active harness (e.g. right after ./aeon init --no-test), HQ starts one automatically, once per repo and harness, remembered in localStorage. The checklist button reads "Test connection".
  • HQ setup checklist, shown until everything is done: repo connected, Actions enabled, model connected and verified, notifications, first skill run. Each row has one action button.
  • The top bar Auth button and the Settings Connect buttons open the ConnectModal.
  • Other changes:
    • HIVEMINDOS_CREDIT_TOKEN is listed in Settings.
    • ensureActionsCanOpenPRs no longer forces default_workflow_permissions=write. It reads the current settings and only turns on PR creation.
    • aeon sync (lib/sync.ts) now pushes the same way as commitAndPush: git push origin HEAD:<branch> with one rebase-and-retry. It refuses, before committing, when origin is the Aeon template or missing. Both use the shared helpers originRefusal and pushHeadToOrigin in lib/github.ts.

Why

Connecting a model used to mean three different modals and knowing which secret name to use. The worst problem was a Claude subscription token: it saves fine, but GitHub runners often reject it, so the first real run exits instantly with zero usage. The live check catches that right away and says what to do.

How

  • One source of truth. lib/manifest.ts imports harness-adapter/harnesses.json and gateways.json from feat: aeon init + credential manifest with drift test #1158.
    • Detection takes its prefixes, labels and provider list from them.
    • The step 1 commands take the login command, ./aeon auth command, credential paths and get-a-key links from them.
    • The connect check takes token_usage from them.
    • Only UI wording and the OS clipboard wrapper are local.
    • next.config.ts sets the turbopack root to the repo so the JSON can be imported.
  • Pure modules, easy to copy to the hosted fork:
    • connect-detect.ts: detection, the tar check and re-pack
    • connect-commands.ts
    • connect-check.ts: log slicing, the verdict and the poller
    • telegram-link.ts
    • openrouter-oauth.ts
  • Server modules: connect-server.ts and connect-check-server.ts.
  • Short-lived state goes through KvStore in lib/connect-store.ts (get / set with TTL / take / del). It is in memory here; Upstash Redis can be swapped in for the hosted fork.
  • Capture safety.
    • The server reads every pax record and uses the last path, as tar does.
    • It allows only metadata pax keys. It refuses size/linkpath/GNU.sparse records, global headers, long-name records, links and special files, and checks header checksums.
    • It then stores a clean re-pack of just the verified regular files, never the pasted bytes.
    • The runner's own restore (base64 -d | tar xzf - -C $HOME) is tested against the re-pack.
  • Reading the connect-check run.
    • Real gh run view --log output (gh 2.10x) says UNKNOWN STEP for every line, and a logs zip often has no per-step files. So the slice is anchored on the last Token usage ... input: N notice: it runs from the end of the nearest ##[group]Run script block up to that notice, with group blocks dropped. Real step names are used as a fast path when present.
    • Failure patterns read only ##[error]/##[warning] lines plus that sliced output. The workflow script text (which mentions rate_limited) is never read.
    • The answer is the line right before the notice (the echo "$RESULT_TEXT" line; the harness stderr tail comes before it), so an echoed prompt or an empty result never counts.
    • "Remove subscription token" is only offered when the run succeeded, reached the model call, and reported zero usage.
  • connect-check is dispatched with -f harness=<h> -f dispatch_id=cc-<h>-<random> and found again by the [dispatch: ...] run-name suffix.
  • New skill skills/connect-check is read-only and on demand only (enabled: false, schedule: "workflow_dispatch").
    • The catalog was regenerated in a separate commit.
    • Its eyebrowlock entry comes from a scan by the CI-pinned eyebrow v0.5.6 (checksum verified).
    • Skill counts are updated.

Verified

  • apps/dashboard: npm run typecheck, npm run lint (0 errors; the existing warnings are unchanged), and npm run build pass. npm test passes 303 tests. They include the review regressions:
    • a sanitized skeleton of a real gh log (lib/fixtures/connect-check-run.log): pass with tokens, zero usage giving the subscription hint, failed-before-model with no fix, and the zip form
    • the repeated pax path attack, non-finite or huge pax mtime, and files named like folders
    • refused pax keys and record types
    • the re-pack restoring through the runner's command
    • zero usage not being called rate-limited
    • token_usage-none pass and fail, including a log that echoes the prompt with no real answer
    • the subscription remove-advice and fix
    • page-level polling with timeout and cancel
    • the Telegram backlog case, and 409 webhook vs transient
    • the workflow-permissions change
    • the gateway sync for direct Claude credentials
  • apps/cli: typecheck and lint pass, and apps/cli/test/init-sandbox.sh passes. fake-gh now answers workflow run / run list / run view from the fixture log. The new sandbox cases: pass with 22584 tokens, zero-usage fail with the remove command, --no-test, no dispatch without confirmation, and an instance without the skill.
  • The new log reader was also checked against two real logs (aeon-agent run 34677070781, miroshark-aeon run 36101660961): both pass with their token counts.
  • lib/sync-push.test.ts tests aeon sync against real local git repos: a branch tracking the template, rebase-and-retry, the template refusal, and a missing origin.
  • Scripts:
    • scripts/tests/test_credential_manifest.sh: 266 checks pass.
    • The ci-tests scripts for skill registration, model choices, harness choices and manifests pass: skill_mode, skill_requires, all_secrets_allowlist, validate_readme_catalog, validate_skill_packs, dashboard_model_choices, workflow_harness_choices, generate_harnesses_json, readonly_notify, resolve_harness.
  • Skill gates pass:
    • the regenerate-and-diff steps of ci-skills-json and ci-packs-json (LC_ALL=C)
    • generate-skill-icons --check
    • validate-readme-catalog
    • check-skill-categories
    • validate-config
    • gen-agents-md --check
    • the eyebrow "lockfile covers every skill" loop
  • Dashboard screenshots via agent-browser on a random local port: HQ checklist; Connect modals for Claude, Codex and Pi; paste previews. After the manifest change, the Claude modal shows the manifest command ./aeon auth --harness claude-code and the OpenRouter key detection.
  • Nothing was saved or dispatched while testing.

Not verified live

  • A real OpenRouter code exchange (needs a signed-in account).
  • A real connect-check run on an instance.
  • The Telegram link against a real bot.

…ift test

Each adapter's rh-meta block now lists its credentials (secret, kind,
auth_mode, label, prefix, get_url, login_cmd, aeon_cmd, cred_paths,
expires/refresh, aux_secrets), most preferred first in exactly the order
scripts/resolve-harness.sh picks them, plus default_model. The old auth
summary is derived from that list by the generator, so the two cannot
disagree. The generator also writes harness-adapter/gateways.json from a
gw-meta block in adapters/claude.sh: every llm-gateway.sh provider in the
default cascade order with secrets, prefixes, base URL and transport.

Drift fixed on the way:
- grok no longer claims an OpenRouter fallback it does not have
- pi lists ANTHROPIC_OAUTH_TOKEN, which resolve-harness.sh already honours
- cli.install/min_version match the real pins (kimi/vibe had no install
  command; fx said 0.0.5 vs v0.0.12; cursor/hermes said latest)
- cursor with no CURSOR_API_KEY is labelled AUTH_MODE=none instead of
  openrouter (label only; install still fails closed)
- hivemindos added to the dashboard gateway registry

scripts/tests/test_credential_manifest.sh (wired into ci-tests) holds the
mirror to resolve-harness.sh (parsed and exercised), harness-auth.ts,
the aeon.yml env blocks, install pins, llm-gateway.sh and
gateway-registry.ts. ci-harnesses-json now diffs gateways.json too.
./aeon init takes a clone of aeonfun/aeon to a running instance and is safe
to re-run (each step checks first, then prints what it fixed):
1. gh installed, signed in, token has repo + workflow (gh auth login --web
   -s workflow / gh auth refresh)
2. instance repo created from the template, not a fork (forks start with
   Actions off); this folder is switched to it with aeonfun/aeon kept as
   upstream, refusing when that could lose work; --dir clones elsewhere.
   Never adopts aeonfun/aeon, aaronjmars/aeon or any name that redirects.
3. gh repo set-default to the instance
4. Actions enabled and allowed to open PRs; the default token permission
   and an existing allowed_actions choice are left as they are; workflows
   GitHub left off (disabled_fork / disabled_inactivity) are re-enabled
5. GH_GLOBAL from the gh token, only when it has repo + workflow
6. model: harness menu and credential list from harnesses.json, reusing
   the aeon auth flows (login capture, setup-token, pasted key, gateways)
7. Telegram: bot token, then a /start nonce deep link found by polling
   getUpdates without an offset; 409 or timeout falls back to manual paste
8. summary checklist, then optionally the dashboard

Also:
- captureGithubToken (dashboard route + aeon auth --github) refuses a
  token without repo + workflow, or a fine-grained one it cannot inspect
- aeon auth --harness grok (X login capture or xAI key)
- aeon auth refuses to write secrets while gh points at the template
- bin/onboard: instance + gh default checks, model check from the
  manifest (gateway-only setups pass), one secret list per run, --remote
  dispatches heartbeat (the onboard skill never existed)
- codex manifest note: the capture eventually expires
- README quick start: clone + ./aeon init; the manual path stays as a
  fallback and now says forks start with Actions disabled and why
  gh repo set-default is mandatory
- GH_GLOBAL = one classic PAT (repo + workflow) or the gh login token via
  ./aeon init / aeon auth --github; GH_SECRETS_PAT = optional, tried first
  by the rotating-login refresh paths, falls back to GH_GLOBAL. Same
  wording in CONFIGURATION.md, mcp-oauth.md, harnesses.md and bin/onboard
- docs/harnesses.md: credential precedence table generated from the
  manifest; fx/cursor rows now show their aeon auth --key path
- aeon setup skill (both copies, drift unchanged): Start mode runs
  ./aeon init
- CONTRIBUTING: new gateways also go in the gw-meta block
- apps/cli README: init + grok auth
aaronjmars added a commit that referenced this pull request Oct 2, 2026
…fests

Review fixes:
- Capture check: parse every pax record and use the LAST path (as tar
  does), allow only metadata pax keys, refuse size/linkpath/GNU.sparse,
  global headers, long-name records and any non-file/dir entry, verify
  header checksums, and store a clean server-side re-pack of the verified
  regular files instead of the pasted bytes.
- connect-check reads only the Run step's own output (script groups
  dropped) plus ##[error]/##[warning] lines, so workflow text such as
  "rate_limited" no longer misreports zero-usage runs.
- Harnesses with token_usage none (cursor, kimi, vibe) pass on success
  plus an exact AEON_CONNECT_OK answer, taken only from the result line
  printed right before the usage notice, never from an echoed prompt.
- The page owns connect-check dispatch and polling (with a timeout), so
  the HQ checklist keeps updating after the modal closes.
- Saving CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY pins the gateway to
  auto again, as configureAuth did.
- A rejected subscription token now says to remove CLAUDE_CODE_OAUTH_TOKEN
  (it is first in the auto order and a zero-usage success does not fail
  over) and offers a one-click "Remove subscription token".
- Telegram link: backlog status at 100+ unread updates; a 409 only means
  webhook mode when Telegram says so, otherwise keep waiting.
- OpenRouter popup wait is torn down on unmount and never sets state after
  close.

One source of truth: connect-detect.ts and connect-commands.ts now derive
prefixes, labels, provider options (HivemindOS included), login and aeon
commands, credential paths and get-a-key links from
harness-adapter/harnesses.json and gateways.json (lib/manifest.ts), and
connect-check reads token_usage from it. next.config.ts sets the turbopack
root to the repo so the JSON can be imported. CONTRIBUTING and the PR
template no longer ask gateway authors to edit the dashboard dropdown.
@aaronjmars
aaronjmars force-pushed the feat/dashboard-one-shot-connect branch from 9fe5f89 to 7c2082d Compare October 2, 2026 20:27
@aaronjmars
aaronjmars changed the base branch from main to feat/aeon-init-credential-manifest October 2, 2026 20:28
…ompts

Review fixes for aeon init:
- Switch-over fetches and checks out the instance under a temporary remote
  first and renames remotes only after that worked, so an interruption can
  never leave main tracking the template; a re-run inside an instance
  repairs a branch that still tracks something other than origin.
- commitAndPush pushes to origin by name (HEAD:<branch>, pull --rebase from
  origin) and refuses when origin is the template; the edit stays local.
- --harness on an already-connected harness reports it and only switches
  aeon.yml; no login is re-run (grok rotates its capture on login).
- Without a terminal every confirm is "no" unless --yes, and no browser
  login or key prompt is started.
- --dir waits for the template copy to have aeon.yml, verifies the clone,
  removes only what it created, refuses a non-empty folder, and reports a
  failed hand-over.
- Dirty / local-commit checks run before the repo is created and count
  commits on every branch.
- Secret checks query the instance with -R, so a dry run from a template
  clone never reads aeonfun/aeon.
- Telegram: still no offset; a backlog of 100+ updates goes straight to
  the manual chat id.
- GH_GLOBAL scope refusals are a typed error, 400 from the route, with
  neutral wording; docs and init note that gh login tokens can be revoked
  and recommend a dedicated classic PAT for long-lived instances.
- bin/onboard: --remote help matches what heartbeat does; an unknown
  harness warns and checks claude, as resolve-harness.sh runs it.
- test_credential_manifest: key-order independent registry parse, guarded
  lookups, oauth capture parity check, clearer CLAUDE_AUTH_SECRETS message.

Tests: apps/cli/test/init-sandbox.sh (fake gh + bare repos, 34 checks, in
ci-apps) and lib/github-push.test.ts (commitAndPush against local repos).
aaronjmars added a commit that referenced this pull request Oct 2, 2026
…fests

Review fixes:
- Capture check: parse every pax record and use the LAST path (as tar
  does), allow only metadata pax keys, refuse size/linkpath/GNU.sparse,
  global headers, long-name records and any non-file/dir entry, verify
  header checksums, and store a clean server-side re-pack of the verified
  regular files instead of the pasted bytes.
- connect-check reads only the Run step's own output (script groups
  dropped) plus ##[error]/##[warning] lines, so workflow text such as
  "rate_limited" no longer misreports zero-usage runs.
- Harnesses with token_usage none (cursor, kimi, vibe) pass on success
  plus an exact AEON_CONNECT_OK answer, taken only from the result line
  printed right before the usage notice, never from an echoed prompt.
- The page owns connect-check dispatch and polling (with a timeout), so
  the HQ checklist keeps updating after the modal closes.
- Saving CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY pins the gateway to
  auto again, as configureAuth did.
- A rejected subscription token now says to remove CLAUDE_CODE_OAUTH_TOKEN
  (it is first in the auto order and a zero-usage success does not fail
  over) and offers a one-click "Remove subscription token".
- Telegram link: backlog status at 100+ unread updates; a 409 only means
  webhook mode when Telegram says so, otherwise keep waiting.
- OpenRouter popup wait is torn down on unmount and never sets state after
  close.

One source of truth: connect-detect.ts and connect-commands.ts now derive
prefixes, labels, provider options (HivemindOS included), login and aeon
commands, credential paths and get-a-key links from
harness-adapter/harnesses.json and gateways.json (lib/manifest.ts), and
connect-check reads token_usage from it. next.config.ts sets the turbopack
root to the repo so the JSON can be imported. CONTRIBUTING and the PR
template no longer ask gateway authors to edit the dashboard dropdown.
@aaronjmars
aaronjmars force-pushed the feat/dashboard-one-shot-connect branch from 7c2082d to 7d34a71 Compare October 2, 2026 20:33
- A template copy starts a fresh history, so when the folder's branch
  shares no commits with origin, init now checks out origin/<branch>
  (only with a clean tree and no unpushed commits) instead of only
  re-pointing @{u}; otherwise it stops with a hint.
- --dir is checked before the repo is created: a file or a non-empty,
  non-Aeon folder is refused (no ENOTDIR crash). A failed clone empties a
  folder that already existed instead of removing it.
- Clearer hints when the aeon-instance remote cannot be added and when the
  old origin cannot be moved (existing origin-previous).
- fake-gh `repo create` now makes a fresh root commit like GitHub; sandbox
  adds the fresh-history repair (and its dirty-folder refusal) and the
  --dir-on-a-file case.
@aaronjmars
aaronjmars force-pushed the feat/dashboard-one-shot-connect branch from 7d34a71 to 0e9c710 Compare October 3, 2026 01:34
aaronjmars added a commit that referenced this pull request Oct 3, 2026
…fests

Review fixes:
- Capture check: parse every pax record and use the LAST path (as tar
  does), allow only metadata pax keys, refuse size/linkpath/GNU.sparse,
  global headers, long-name records and any non-file/dir entry, verify
  header checksums, and store a clean server-side re-pack of the verified
  regular files instead of the pasted bytes.
- connect-check reads only the Run step's own output (script groups
  dropped) plus ##[error]/##[warning] lines, so workflow text such as
  "rate_limited" no longer misreports zero-usage runs.
- Harnesses with token_usage none (cursor, kimi, vibe) pass on success
  plus an exact AEON_CONNECT_OK answer, taken only from the result line
  printed right before the usage notice, never from an echoed prompt.
- The page owns connect-check dispatch and polling (with a timeout), so
  the HQ checklist keeps updating after the modal closes.
- Saving CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY pins the gateway to
  auto again, as configureAuth did.
- A rejected subscription token now says to remove CLAUDE_CODE_OAUTH_TOKEN
  (it is first in the auto order and a zero-usage success does not fail
  over) and offers a one-click "Remove subscription token".
- Telegram link: backlog status at 100+ unread updates; a 409 only means
  webhook mode when Telegram says so, otherwise keep waiting.
- OpenRouter popup wait is torn down on unmount and never sets state after
  close.

One source of truth: connect-detect.ts and connect-commands.ts now derive
prefixes, labels, provider options (HivemindOS included), login and aeon
commands, credential paths and get-a-key links from
harness-adapter/harnesses.json and gateways.json (lib/manifest.ts), and
connect-check reads token_usage from it. next.config.ts sets the turbopack
root to the repo so the JSON can be imported. CONTRIBUTING and the PR
template no longer ask gateway authors to edit the dashboard dropdown.
- First run prints one line, "Installing dashboard dependencies (first run
  only)...", and runs `npm ci` (or `npm install` with no lockfile) with
  --no-audit --no-fund --loglevel=error. Output goes to a temp log that is
  shown (last 30 lines + path) only when the install fails, instead of
  pages of eslint peer warnings and a dev-only audit summary.
- The banner drops the two-column emoji and pads every row to the same
  width for any port length (checked with 4, 5 and 7 digit ports).
A live connect-check on a GitHub-hosted runner passed with a
CLAUDE_CODE_OAUTH_TOKEN from claude setup-token, so "frequently rejected"
overstated it. The note now says it can be rejected in some cases, to
confirm with Test connection, and to switch to an API key or a gateway key
(OpenRouter etc.) only if it shows zero usage.
A tiny read-only skill the dashboard dispatches after a model credential is
saved. It must answer AEON_CONNECT_OK; the dashboard passes it only when the
run succeeds and token usage is nonzero.
…s, eyebrowlock)

Add connect-check to catalog/skills.json and the core pack in
catalog/packs.json (surgical insert, other entries untouched), give it a
plug glyph in catalog/skill-icons.json with the regenerated SVG and
dashboard icon map, and splice its eyebrowlock.json artifact from an
eyebrow v0.5.6 scan.
Register connect-check in aeon.yml as an on-demand workflow_dispatch
entry (enabled: false; the scheduler skips dispatch-only skills and
manual dispatch is not gated on enabled). Bump first-party skill counts
from 85 to 86 in the README, docs, hero SVG and the aeon operator skill
(both copies), and add connect-check to the core row in
docs/skill-packs.md.
…ect-check, Telegram link

Server side of the one-shot connect flow, with a small KvStore interface
(in-memory here, Redis in the hosted fork) for short-lived state.

- lib/connect-detect.ts: pure paste detection shared by the browser preview
  and the save path (sk-ant-oat, sk-ant, sk-or-, sk-, xai-, bk_, inf_, and
  base64 tar.gz login captures mapped to CODEX_AUTH / KIMI_AUTH /
  HERMES_AUTH / GROK_CREDENTIALS by their entry names; 48 KB cap; refuses
  links, absolute paths and files outside the login paths).
- lib/connect-commands.ts: per-harness step 1 commands that produce exactly
  the format the runner restores (tar czf rooted at $HOME, base64).
- POST /api/connect, /api/connect/detect, /api/connect/found (local only,
  names never values).
- /api/openrouter-auth (+ callback): OpenRouter OAuth PKCE with a
  state-bound callback, single-use verifier, 10 minute TTL; saves
  OPENROUTER_API_KEY and re-syncs the gateway.
- /api/connect-check: dispatches the connect-check skill with a dispatch id
  and judges the run; green needs success AND nonzero token usage, with a
  concrete next step on failure (e.g. a rejected subscription token).
- /api/telegram/link (+ check): t.me deep link with a nonce, polls
  getUpdates without an offset, saves TELEGRAM_CHAT_ID; 409 means webhook.
- /api/onboarding: whether GitHub Actions is enabled, for the checklist.

Unit tests cover detection, the PKCE flow, the Telegram nonce matcher and
the connect-check result parser.
…link

- ConnectModal replaces AuthModal, GrokAuthModal and HarnessAuthModal.
  Step 1 shows the exact command to run (copy button, macOS and Linux
  variants for login captures) or "get a key" links; step 2 is one paste
  box that previews what was detected before saving. Other ways: one-click
  OpenRouter for every harness that takes OPENROUTER_API_KEY, and in local
  mode only "Do it for me" (existing CLI login flows) and "Found on this
  machine". After a save it auto-starts the connect-check test and shows
  the verdict with a next step.
- The top bar Auth button and the Settings Connect buttons open the modal.
- OnboardingChecklist on HQ until done: repo, Actions enabled, model
  connected and verified, notifications, first skill run, one action each.
- TelegramLinkCard in Settings links the chat with a t.me deep link and
  falls back to the manual helper when the bot is in webhook mode.
- CONTRIBUTING and the PR template point gateway authors at
  lib/connect-detect.ts PROVIDER_OPTIONS instead of the removed AuthModal.
…tion

ensureActionsCanOpenPRs used to force default_workflow_permissions=write.
It now reads the current workflow settings, sends the default token
permission back unchanged (same as `aeon init`), and only turns on
can_approve_pull_request_reviews. Every aeon workflow declares its own
permissions, so widening the repo default only loosened the ones that don't.
No call is made when the switch is already on.
Adds the HivemindOS gateway credential to BUILTIN_SECRETS so it shows in
Settings and `aeon secrets ls`. It is also in the Connect modal's provider
dropdown, which now reads harness-adapter/gateways.json.
…fests

Review fixes:
- Capture check: parse every pax record and use the LAST path (as tar
  does), allow only metadata pax keys, refuse size/linkpath/GNU.sparse,
  global headers, long-name records and any non-file/dir entry, verify
  header checksums, and store a clean server-side re-pack of the verified
  regular files instead of the pasted bytes.
- connect-check reads only the Run step's own output (script groups
  dropped) plus ##[error]/##[warning] lines, so workflow text such as
  "rate_limited" no longer misreports zero-usage runs.
- Harnesses with token_usage none (cursor, kimi, vibe) pass on success
  plus an exact AEON_CONNECT_OK answer, taken only from the result line
  printed right before the usage notice, never from an echoed prompt.
- The page owns connect-check dispatch and polling (with a timeout), so
  the HQ checklist keeps updating after the modal closes.
- Saving CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY pins the gateway to
  auto again, as configureAuth did.
- A rejected subscription token now says to remove CLAUDE_CODE_OAUTH_TOKEN
  (it is first in the auto order and a zero-usage success does not fail
  over) and offers a one-click "Remove subscription token".
- Telegram link: backlog status at 100+ unread updates; a 409 only means
  webhook mode when Telegram says so, otherwise keep waiting.
- OpenRouter popup wait is torn down on unmount and never sets state after
  close.

One source of truth: connect-detect.ts and connect-commands.ts now derive
prefixes, labels, provider options (HivemindOS included), login and aeon
commands, credential paths and get-a-key links from
harness-adapter/harnesses.json and gateways.json (lib/manifest.ts), and
connect-check reads token_usage from it. next.config.ts sets the turbopack
root to the repo so the JSON can be imported. CONTRIBUTING and the PR
template no longer ask gateway authors to edit the dashboard dropdown.
…mplate

syncPush (`aeon sync`, POST /api/sync) used a bare `git push`, which follows
whatever the branch tracks; a half-converted template clone can still track
aeonfun/aeon. It now shares commitAndPush's rules through two helpers in
lib/github.ts:
- originRefusal(): no origin remote, or origin is the Aeon template, stops
  the sync before anything is committed.
- pushHeadToOrigin(): `git push origin HEAD:<branch>`, and on rejection
  `pull --rebase --autostash origin <branch>` then one retry (abort on
  conflict).
commitAndPush now uses the same helpers (behaviour unchanged).
lib/sync-push.test.ts covers the template-tracking branch, the
rebase-and-retry path, the template refusal, and a missing origin against
real local git repos.
gh 2.10x prints "UNKNOWN STEP" in the step column of `gh run view --log`
for every line, and a logs zip often has no per-step files, so filtering on
step == "Run" left the Run output empty: a real successful run was judged
"zero model usage" and the UI offered to remove a working subscription token.

extractRunOutput now anchors on the LAST "Token usage ... input: N" notice
(the Run step's final line in aeon.yml), and takes the output from the end
of the nearest preceding "##[group]Run" script block up to that notice, with
group blocks dropped. Real step names are still used as a fast path when
present. Failure signatures read only ##[error]/##[warning] lines plus that
sliced output. The reply is the line right before the notice (the
`echo "$RESULT_TEXT"` line; the harness stderr tail is printed before it), so
an empty result or an echoed prompt never counts.

The "Remove subscription token" fix is offered only when the run succeeded,
reached the model call (notice present) and reported zero usage.

lib/fixtures/connect-check-run.log is a sanitized structural skeleton of a
real run log (aaronjmars/aeon-agent run 34677070781, all content lines
neutralized). Tests cover a real-shape pass with tokens, the same run with
zero usage (subscription hint and fix), a run that failed before the model
(no fix), and the zip form without job/step columns. Checked against two
real logs (aeon-agent 34677070781, miroshark-aeon 36101660961): both pass.
…copy

- Pax mtime must be a finite number; every mtime is clamped to the ustar
  range 0..8^11-1, so a re-pack can't overflow its header.
- Only directory entries may match a login's parent folders; a file named
  like a folder (".codex") or with a trailing slash is refused. Re-packed
  names use normName.
- startCheck cancels the previous poll for that harness before dispatching,
  so a late result can't overwrite the new "queued" state.
- Telegram backlog message: messages.yml is off on live instances, so it now
  says to paste the chat id or clear the bot's old updates.
parseUsage now takes `total:` straight from the "Token usage" notice
(input + output, as the workflow prints it), falling back to input + output
when the field is missing, so "answered from GitHub (N tokens)" matches the
run log. Pass/fail still counts any token traffic (cache reads included)
through usedTokens(). The real-log fixture test asserts 22584.
`./aeon init` stored a model credential but never proved it worked, so a
fresh instance had zero runs and HQ showed the model as untested.

aeon init:
- New step 7 "Test connection" after the model step: when the configured
  harness has a credential on the instance, it asks (default yes; without a
  terminal only with --yes; skip with --no-test), dispatches connect-check
  through the same lib the dashboard uses (lib/connect-check-server.ts), polls
  with a short progress line, and reports pass with the token count, or fail
  with the reason, the next step, the remove-token command when the verdict
  carries that fix, and the run link. It is a row in the Summary.
- An instance that predates connect-check gets "update your instance"
  instead of a dispatch that would fail.

connect-check:
- instanceHasConnectCheck() checks the skill exists on GitHub before
  dispatching; the API answers 409 { missingSkill } and the UI explains it.
- The remove-subscription fix carries its CLI form (./aeon secrets rm ...).

Dashboard:
- With a model key and no connect-check ever run for the active harness,
  HQ starts one automatically, once per repo and harness (remembered in
  localStorage), and the checklist button reads "Test connection".

apps/cli/test: fake-gh answers workflow run / run list / run view (from a
fixture log) and contents lookups; init-sandbox.sh covers pass with tokens,
zero-usage fail with the remove command, --no-test, no dispatch without
confirmation, and the missing-skill case.
@aaronjmars
aaronjmars force-pushed the feat/dashboard-one-shot-connect branch from d03c987 to efd138d Compare October 3, 2026 13:59
main tree equals the #1158 head, which this branch already contains.
@aaronjmars
aaronjmars changed the base branch from feat/aeon-init-credential-manifest to main October 3, 2026 14:07
@aaronjmars
aaronjmars merged commit 4b0ea87 into main Oct 3, 2026
24 of 38 checks passed
@aaronjmars
aaronjmars deleted the feat/dashboard-one-shot-connect branch October 3, 2026 14:42
@aaronjmars aaronjmars mentioned this pull request Oct 3, 2026
5 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant