Skip to content

Bring the gateway up at the end of Inkbox setup - #46

Merged
dimavrem22 merged 7 commits into
mainfrom
feat/setup-gateway-restart
Jul 28, 2026
Merged

dimavrem22 merged 7 commits into
mainfrom
feat/setup-gateway-restart

Conversation

@dimavrem22

@dimavrem22 dimavrem22 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

The wizard wrote channels.inkbox to disk and ended on Run \openclaw inkbox doctor``. A gateway that was already running kept serving the old settings, and nothing said so — a rotated signing key or a new identity silently did not take effect.

Root cause: persistOpenClawConfigFile (src/setup-wizard.ts:262) does a plain temp-file + rename onto openclaw.json, bypassing the host's writeConfig path and the restart it schedules via afterWrite: { mode: "restart" } — the same hook the host's own plugin CLI uses for exactly this.

Setup now closes by asking the host for gateway status --json --no-probe and offering the action that fits. Every prompt defaults to yes.

Detected Prompt Runs
Gateway running Restart the gateway now? openclaw gateway restart
Not running, service slot exists Launch the gateway now? openclaw gateway start
Not running, no service slot Install and launch the gateway service now? openclaw gateway install
Can't tell — prints both commands, asks nothing

Then a closing banner instead of a to-do list with nothing left to do:

╭──────────────────────────────────────────────────────╮
│ Your OpenClaw agent is set up and running on Inkbox. │
│                                                      │
│   Inkbox identity:   smoke-agent                     │
│   Check its health:  openclaw inkbox doctor          │
╰──────────────────────────────────────────────────────╯

Exit codes are not evidence

Both the start and install branches confirm liveness by polling, not by trusting the command's exit status. gateway start exiting 0 only says the command ran; a gateway that fails to bind is gone a second or two later. When the window closes unconfirmed the step says so and points at gateway status — it never prints a success line, and never suggests starting a gateway that install may already have brought up.

This came out of testing the Hermes twin on macOS, where launchctl bootstrap fails and the host silently degrades to a plain background process. The first cut probed once and reported Gateway service installed. Start it with: gateway start — wrong twice: launchd had installed nothing, and a second start would have duplicated a live process.

Decisions worth a look

  • Only on the persisted path. When persistConfig is absent the wizard prints the snippet for manual paste; nothing is on disk yet, so there is nothing to pick up and the step is skipped.
  • Foreground gateway run is never executed, only printed — it would take over the wizard's terminal. That is why the no-slot branch offers install (which registers a launchd/systemd/schtasks slot and brings it up) rather than run.
  • Detection goes through the host CLI, not host internals. openclaw is an optional peer dependency, so importing its status modules is not safe. It re-invokes the entry script running the wizard ([process.execPath, process.argv[1]]), the way the host's updater shells back into its own gateway commands, falling back to openclaw on PATH.
  • Unknown state is not a guess. If the CLI is missing, errors, or writes nothing parseable, the step prints both commands and asks nothing.

Reverted mid-PR

8631fc8 added a prepare script claiming to fix openclaw plugins install git:<repo>. It cannot work — the host installs plugin dependencies with --ignore-scripts hardcoded unconditionally (src/infra/safe-package-install.ts:55) plus --omit=dev, so no lifecycle script runs and typescript is not present. a02ab09 reverts it and the CI job that went with it. Nothing regressed: git:owner/repo was never the documented install — the README says clone, build, openclaw plugins install -l ./, which is what CI exercises and what works.

Verified by hand

Ran the real install against the built branch in an isolated state dir:

openclaw plugins install -l ./   →  Linked plugin path: … / Restart the gateway to load plugins.
openclaw plugins list            →  Inkbox │ inkbox │ enabled │ …/dist/index.js │ 0.2.6
openclaw inkbox --help           →  doctor, setup, whoami

Re-running the install over an existing one relinks cleanly. All four detection branches, the banner and both poll confirmations are present in the compiled output.

Also

  • Dropped the mid-flow restart hint from the iMessage walkthrough — the closing step covers it.
  • Version 0.2.5 → 0.2.6, CHANGELOG updated.

19 tests in tests/gateway-service.test.ts cover status parsing (JSON preceded by warnings, pid-only runtime, unusable output), every offer branch, the decline paths, unconfirmed start and install, lifecycle failure, and the entry-script fallback. 380 tests pass, tsc --noEmit clean.

Siblings: inkbox-ai/hermes-agent-plugin#77, inkbox-ai/claude-code-plugin#47, inkbox-ai/codex-plugin#29, inkbox-ai/opencode-plugin#11.

dimavrem22 and others added 2 commits July 28, 2026 03:04
The wizard writes channels.inkbox to the OpenClaw config with a plain
temp-file rename, which skips the host's config-write path and the
restart it schedules afterwards. A gateway that was already running kept
serving the old settings, and setup ended on "run `openclaw inkbox
doctor`" without ever mentioning it.

Setup now closes by asking the host for `gateway status --json`, then
offering the action that fits: restart a running gateway, launch an
installed-but-stopped one, or install and launch a service when no slot
exists. Each defaults to yes. Foreground `gateway run` is only ever
printed, since running it would take over the wizard's terminal. The
step runs only on the persisted path — when the snippet is printed for
manual paste there is nothing on disk yet to pick up.

Detection and the lifecycle calls live in a new gateway-service module
with an injectable runner, so tests never spawn the host CLI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found running the Hermes twin of this step on macOS: launchd refused the
bootstrap (exit 5), the host fell back to "Started gateway as a
background process instead" and exited 0, and the immediate re-check
still reported nothing running. The step then printed "Gateway service
installed. Start it with: gateway start" -- wrong twice over. launchd had
not installed anything, and a second start on top of the background
process would have duplicated the gateway.

A gateway is not visible to `gateway status` until it finishes claiming
its runtime state, so poll for up to 15s instead of asking once. When the
window still closes unconfirmed, say so and point at `gateway status`
rather than handing out a start command, since install reported success
and may well have brought one up.

Both the poll window and its delay are injectable so tests neither wait
nor spin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22

Copy link
Copy Markdown
Contributor Author

Pushed e65c8d0 mirroring a fix from local macOS testing of the Hermes twin (inkbox-ai/hermes-agent-plugin#77).

There, gateway install hit a launchd bootstrap failure, fell back to starting a plain background gateway, and exited 0 — but the immediate post-install liveness probe still reported nothing running, so the step printed Gateway service installed. Start it with: gateway start. Wrong twice: launchd installed nothing, and a second start would have duplicated the running process.

This module had the identical shape, so it gets the identical fix:

  • waitForGatewayRunning() polls for up to 15s instead of probing once.
  • An unconfirmed install reports Gateway install completed. and points at openclaw gateway status rather than handing out a start command, and returns true so the sign-off drops openclaw gateway run too.

Both the poll window and its delay are injectable — the first cut of the test span 15 real seconds against the hardcoded window. 19 tests in gateway-service.test.ts, 377 total, tsc clean.

Setup ended on "run doctor / start the gateway" even after it had just
started or restarted one, which reads as unfinished work when there is
none left.

When the gateway is live, close on a banner naming the identity the agent
now runs as and the one command worth knowing. The instruction lines stay
for the case they were written for: nothing is listening, so starting one
really is the next thing to do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22

Copy link
Copy Markdown
Contributor Author

Pushed fadce16 — ports the closing banner from inkbox-ai/hermes-agent-plugin#77.

Setup signed off with Run \openclaw inkbox doctor`/Start the gateway with ...` even right after it had started or restarted one, which reads as unfinished work when there is none left. When the gateway ends up live it now closes on:

╭──────────────────────────────────────────────────╮
│ Your OpenClaw agent is set up and running on Inkbox. │
│                                                  │
│   Inkbox identity:   smoke-agent                 │
│   Check its health:  openclaw inkbox doctor      │
╰──────────────────────────────────────────────────╯

The instruction lines stay for the case they were written for — nothing listening, so starting one really is the next step. Box width comes off the longest row, so long handles do not break it. 379 tests, tsc clean.

dimavrem22 and others added 3 commits July 28, 2026 07:10
The install branch already polled for liveness before claiming success;
the start branch right above it still trusted `gateway start` exiting 0.
That exit only says the command ran -- a gateway that fails to bind is
gone a second or two later, and the operator got "started with the new
Inkbox config" over it.

Same treatment as install: poll, and when the window closes unconfirmed
say so and point at `gateway status` rather than claiming a start that
may not have happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`openclaw plugins install git:inkbox-ai/openclaw-plugin` fails on main:
the host clones, runs npm install, then refuses to load the plugin
because package.json points main at ./dist/index.js and dist/ is
gitignored. The message reads as a publisher packaging issue, which is
exactly what it is.

npm runs `prepare` after installing dependencies in a git checkout, which
is the hook for this. One line, and the clone compiles itself.

Every existing job hid the problem: they all `npm run build` first, then
install from a linked local path, where the host accepts TypeScript
source outright. So the one install path a user actually takes was the
only one never exercised. The new git-install job runs it with no
pre-build, cloning this commit into a bare repo and installing through
`git:file://` -- the identical code path in the host as `git:owner/repo`,
without needing the branch to exist publicly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22

Copy link
Copy Markdown
Contributor Author

Pushed 8631fc8openclaw plugins install git:inkbox-ai/openclaw-plugin has been broken on main, for everyone. Found by installing this PR the way a user would:

Cloning inkbox-ai/openclaw-plugin...
Installing plugin dependencies with npm…
package install requires compiled runtime output for TypeScript entry ./index.ts:
expected ./dist/index.js, ./dist/index.mjs, ./dist/index.cjs, ...
This is a plugin packaging issue, not a local config problem

The host clones, runs npm install, then looks for a loadable entry. package.json points main at ./dist/index.js, dist/ is gitignored, and nothing built it. The error is right — it is a packaging issue on our side.

Fix: "prepare": "npm run build". npm runs prepare after installing dependencies in a git checkout, which is exactly how the host arrives. Verified against a fresh bare clone:

--- before: --- no dist/ in the clone
--- npm install (runs prepare) ---
--- after: --- dist/index.js  ✓ built by the prepare hook

Why CI never caught it

Every job builds first, then installs from a linked local path — where the host accepts TypeScript source outright:

npm ci
npm run build                                    # ← builds dist/ itself
openclaw plugins install -l "$GITHUB_WORKSPACE"  # ← -l, local path

Same shape in all four live workflows and canary. Both conditions that would surface the bug are bypassed. The one install path a user actually takes was the only one never exercised.

So 8631fc8 also adds a git-install job that runs it for real: no pre-build, clone this commit into a bare repo, install through git:file:///... — the identical code path in the host as git:owner/repo (parseGitPluginSpec routes both through isGitUrl) — then assert the host lists the plugin. Without that job the prepare line regresses silently the next time packaging is touched.

Unrelated papercut in the host

git:owner/repo@ref cannot express a branch containing /. splitGitSpecRef locates the ref with lastIndexOf("@") and only accepts it when the @ falls after the last /:

if (atIndex > lastSlashIndex && atIndex > 0) { ... }

feat/setup-gateway-restart puts a slash after the @, so the ref is never split and the whole string is read as the repo. That rules out most branch-naming conventions. Nothing we can fix from the plugin side — worth an upstream issue.

380 tests, tsc clean.

The `prepare` hook I added cannot run. The host installs plugin
dependencies with --ignore-scripts hardcoded unconditionally
(src/infra/safe-package-install.ts:55) on top of --omit=dev, so no
lifecycle script executes and typescript is not even present. Verified
by installing this branch through `git:file://` on a real machine: the
identical "requires compiled runtime output" failure.

The git-install CI job goes with it -- it asserts a behaviour we do not
actually support, so it would fail on every run.

Nothing is regressed by this: `git:owner/repo` was never the documented
install. README says clone, npm install, npm run build, then
`openclaw plugins install -l ./`, which is also what CI exercises and
what works today. Making `git:` installs work needs either a committed
dist/ or an npm publish, which is a distribution decision, not a fix to
slip into this PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dimavrem22

Copy link
Copy Markdown
Contributor Author

Correcting myself: 8631fc8 could not have worked, and I have reverted it in a02ab09.

The host installs plugin dependencies with --ignore-scripts hardcoded unconditionally:

// src/infra/safe-package-install.ts:48
export function createSafeNpmInstallArgs(options = {}): string[] {
  return [
    "install",
    ...(options.omitDev ? ["--omit=dev"] : []),
    ...
    "--ignore-scripts",     // ← always

and git-install.ts:313 calls it with omitDev: true. So no lifecycle script ever runs, and typescript is not installed either. A prepare hook is inert by design — the host is deliberately refusing to execute publisher code at install time.

Confirmed on a real machine: installing this branch through git:file:///... produced the identical requires compiled runtime output failure. I should have read the host's install path before claiming a fix; the local bare-clone check I ran only proved npm's behaviour, not the host's.

The CI job goes with it — it asserted something we do not support, so it would have failed every run.

Nothing is regressed. git:owner/repo was never the documented install. The README says:

git clone https://github.com/inkbox-ai/openclaw-plugin.git
cd openclaw-plugin && npm install && npm run build
openclaw plugins install -l ./

which is what CI exercises and what works. My earlier claim that "CI never tests the path a user takes" was wrong on that point — CI tests exactly the documented path.

Making git: installs work needs a distribution decision, not a code fix: either commit dist/ (currently gitignored; the host's own error says "after the publisher ships compiled JavaScript") or publish @inkbox/inkbox to npm, which is presently a 404 so openclaw plugins install @inkbox/inkbox is unavailable too. Both are out of scope here and belong to whoever owns releases.

This PR is back to what it says on the tin: the gateway (re)start step, the poll-don't-probe confirmations, and the closing banner.

@dimavrem22
dimavrem22 marked this pull request as ready for review July 28, 2026 07:46
@dimavrem22
dimavrem22 merged commit 61b1fba into main Jul 28, 2026
29 of 31 checks passed
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