From 13967c54c7ec107f852ce96997c25081735680cf Mon Sep 17 00:00:00 2001 From: vladsarandan Date: Sun, 24 May 2026 17:35:10 +0300 Subject: [PATCH 1/2] fix(cli): make iii-engine ready timeout configurable, bump default to 60s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wrapper's hardcoded 15-second readiness window is too tight on Windows. iii spawns the `iii-exec` worker (`node dist/index.mjs`), which has to cold-start Node, import native deps, reconnect to the engine over WebSocket, and register HTTP triggers before the REST surface answers. On Windows that handshake regularly takes longer than 15s, so `agentmemory` exits with "REST API never responded" and orphans iii — leaving the user with a port-bound engine that has no agentmemory routes registered. This change: - Introduces AGENTMEMORY_READY_TIMEOUT_MS (default 60000ms). - Replaces both `waitForEngine(15000)` call sites with the constant. - Templates the timeout into the "did not become ready within Ns" error so the message matches whatever the user configured. - Documents the new env var in --help. No behavior change for users whose engine starts in <15s; Windows installs now succeed out of the box. Users on slow disks / CI can raise the timeout further; users wanting fast-fail smoke tests can lower it. --- src/cli.ts | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/cli.ts b/src/cli.ts index e1ea9757..fc171def 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -74,6 +74,21 @@ const IS_RESET = args.includes("--reset"); const IIPINNED_VERSION = process.env["AGENTMEMORY_III_VERSION"] || "0.11.2"; +// How long the wrapper waits for iii-engine's REST API to start responding +// before giving up and printing the "REST API never responded" error. +// The default 15s is too tight on Windows: iii spawns the `iii-exec` worker +// (`node dist/index.mjs`), which has to reconnect over WebSocket and +// register HTTP triggers before the REST surface answers — and Windows cold +// node startup + native deps regularly push that past 15s. Bumping to 60s +// makes Windows installs work out of the box; AGENTMEMORY_READY_TIMEOUT_MS +// lets users tune it further (e.g. CI containers, slow disks). +const READY_TIMEOUT_MS = (() => { + const raw = process.env["AGENTMEMORY_READY_TIMEOUT_MS"]; + if (!raw) return 60000; + const parsed = Number.parseInt(raw, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : 60000; +})(); + // Map Node platform/arch → the asset name iii-hq/iii ships under // https://github.com/iii-hq/iii/releases/download/iii/v/ function iiiReleaseAsset(): string | null { @@ -153,6 +168,10 @@ Environment: AGENTMEMORY_USE_DOCKER=1 Prefer the bundled docker-compose path over the native iii-engine binary on first run. AGENTMEMORY_III_VERSION Override pinned iii-engine version (default ${IIPINNED_VERSION}). + AGENTMEMORY_READY_TIMEOUT_MS How long to wait for iii-engine's REST API to + come up before giving up (default 60000). + Raise on slow disks / CI; lower for fast-fail + smoke tests. Quick start: npx @agentmemory/agentmemory # start with local iii-engine or Docker @@ -1037,10 +1056,10 @@ async function main() { const s = p.spinner(); s.start("Waiting for iii-engine to be ready..."); - const ready = await waitForEngine(15000); + const ready = await waitForEngine(READY_TIMEOUT_MS); if (!ready) { const port = getRestPort(); - s.stop("iii-engine did not become ready within 15s"); + s.stop(`iii-engine did not become ready within ${Math.round(READY_TIMEOUT_MS / 1000)}s`); if (startupFailure?.kind === "engine-crashed" || startupFailure?.kind === "docker-crashed") { p.log.error("The iii-engine process crashed on startup."); @@ -1384,10 +1403,12 @@ function buildDoctorEffects(): DoctorEffects { try { const started = await startEngine(); if (!started) return { ok: false, message: "startEngine() returned false" }; - const ready = await waitForEngine(15000); + const ready = await waitForEngine(READY_TIMEOUT_MS); return { ok: ready, - message: ready ? "Engine ready" : "Engine did not become ready within 15s", + message: ready + ? "Engine ready" + : `Engine did not become ready within ${Math.round(READY_TIMEOUT_MS / 1000)}s`, }; } catch (err) { return { From d2c8bf462b0d2fa8220754f03e1f007a11d35c11 Mon Sep 17 00:00:00 2001 From: vladsarandan Date: Sun, 24 May 2026 17:44:17 +0300 Subject: [PATCH 2/2] fix(windows): correct watch path + use IPv4 for engine WS so iii-exec child boots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two latent bugs that combine to make `agentmemory` unusable on Windows (and silently fragile on Linux): 1. iii-config*.yaml's `iii-exec` worker watches `src/**/*.ts`. The npm package only ships `dist/`, so that glob resolves to nothing for end users. On some platforms an empty watch glob causes iii-exec to abort startup before it ever spawns `node dist/index.mjs`, which is the process that actually registers the REST HTTP triggers. Switch to `dist/**/*.mjs` so the watcher sees the shipped artifact. 2. `loadConfig()` defaults `engineUrl` to `ws://localhost:49134`. On Windows, `localhost` prefers IPv6 (`::1`), but iii-engine binds IPv4 only — so the node child enters an ECONNREFUSED reconnect loop and never finishes registering its HTTP triggers, even when iii-exec did spawn it. Default to `ws://127.0.0.1:49134` to force IPv4. Existing `III_ENGINE_URL` overrides are unchanged. Verified locally on Windows 11 / Node 22: with both fixes plus the timeout bump from the previous commit, `node dist/cli.mjs` brings the server to `{"status":"healthy"}` in ~1.3s. Before this commit the engine never registered any routes — every REST/MCP call hit a 404 backend even though `:3111` was bound. --- iii-config.docker.yaml | 3 ++- iii-config.yaml | 6 +++++- src/config.ts | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/iii-config.docker.yaml b/iii-config.docker.yaml index aa6c3b99..291fb742 100644 --- a/iii-config.docker.yaml +++ b/iii-config.docker.yaml @@ -46,7 +46,8 @@ workers: logs_console_output: true - name: iii-exec config: + # See iii-config.yaml: watch the shipped artifact, not the source. watch: - - src/**/*.ts + - dist/**/*.mjs exec: - node dist/index.mjs diff --git a/iii-config.yaml b/iii-config.yaml index f7e0f188..c329e025 100644 --- a/iii-config.yaml +++ b/iii-config.yaml @@ -46,7 +46,11 @@ workers: logs_console_output: true - name: iii-exec config: + # Watch the shipped artifact, not the source tree. The npm package + # ships only `dist/` — `src/**/*.ts` resolves to nothing for end + # users, and on some platforms an empty watch glob causes iii-exec + # to abort startup before it ever spawns the node child. watch: - - src/**/*.ts + - dist/**/*.mjs exec: - node dist/index.mjs diff --git a/src/config.ts b/src/config.ts index eed5725e..74d5e396 100644 --- a/src/config.ts +++ b/src/config.ts @@ -137,7 +137,11 @@ export function loadConfig(): AgentMemoryConfig { const provider = detectProvider(env); return { - engineUrl: env["III_ENGINE_URL"] || "ws://localhost:49134", + // Use 127.0.0.1 (not "localhost") so we force IPv4. On Windows, + // `localhost` prefers IPv6 (::1) but iii-engine binds IPv4 only, so + // resolving via "localhost" leads to an ECONNREFUSED reconnect loop + // and the iii-exec worker never registers its HTTP triggers. + engineUrl: env["III_ENGINE_URL"] || "ws://127.0.0.1:49134", restPort: parseInt(env["III_REST_PORT"] || "3111", 10) || 3111, streamsPort: parseInt(env["III_STREAMS_PORT"] || "3112", 10) || 3112, provider,