From 55d49a940a06a9d0974d05e546115c9ba9b77152 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 3 Feb 2026 03:26:19 -0300 Subject: [PATCH 1/3] Fix process wait timeout and bind mode; add tests --- package-lock.json | 521 +++++++++++++++++++++++++++++++++++++- src/gateway/utils.test.ts | 43 ++++ src/gateway/utils.ts | 6 +- src/startup.test.ts | 18 ++ start-moltbot.sh | 2 +- 5 files changed, 587 insertions(+), 3 deletions(-) create mode 100644 src/gateway/utils.test.ts create mode 100644 src/startup.test.ts diff --git a/package-lock.json b/package-lock.json index 170a6f26..e39562a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "moltbot-sandbox", "version": "1.0.0", - "license": "MIT", + "license": "Apache-2.0", "dependencies": { "@cloudflare/puppeteer": "^1.0.5", "hono": "^4.11.6", @@ -432,6 +432,23 @@ "wrangler": "^4.60.0" } }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260120.0.tgz", + "integrity": "sha512-JLHx3p5dpwz4wjVSis45YNReftttnI3ndhdMh5BUbbpdreN/g0jgxNt5Qp9tDFqEKl++N63qv+hxJiIIvSLR+Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@cloudflare/workerd-darwin-arm64": { "version": "1.20260120.0", "cpu": [ @@ -447,6 +464,57 @@ "node": ">=16" } }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260120.0.tgz", + "integrity": "sha512-O0mIfJfvU7F8N5siCoRDaVDuI12wkz2xlG4zK6/Ct7U9c9FiE0ViXNFWXFQm5PPj+qbkNRyhjUwhP+GCKTk5EQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260120.0.tgz", + "integrity": "sha512-aRHO/7bjxVpjZEmVVcpmhbzpN6ITbFCxuLLZSW0H9O0C0w40cDCClWSi19T87Ax/PQcYjFNT22pTewKsupkckA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260120.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260120.0.tgz", + "integrity": "sha512-ASZIz1E8sqZQqQCgcfY1PJbBpUDrxPt8NZ+lqNil0qxnO4qX38hbCsdDF2/TDAuq0Txh7nu8ztgTelfNDlb4EA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, "node_modules/@cloudflare/workers-types": { "version": "4.20260124.0", "dev": true, @@ -463,6 +531,17 @@ "node": ">=12" } }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -932,6 +1011,29 @@ "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", "cpu": [ @@ -947,6 +1049,423 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", diff --git a/src/gateway/utils.test.ts b/src/gateway/utils.test.ts new file mode 100644 index 00000000..0b6602a2 --- /dev/null +++ b/src/gateway/utils.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { waitForProcess } from './utils'; + +describe('waitForProcess', () => { + afterEach(() => { + vi.useRealTimers(); + }); + + it('waits through "starting" to completion (regression for early return)', async () => { + vi.useFakeTimers(); + + const proc = { status: 'starting' as string }; + + const promise = waitForProcess(proc, 500, 50); + + // Simulate lifecycle: starting -> running -> completed + setTimeout(() => { + proc.status = 'running'; + }, 100); + setTimeout(() => { + proc.status = 'completed'; + }, 200); + + // Let timers advance enough for both transitions. + await vi.advanceTimersByTimeAsync(250); + await promise; + + expect(proc.status).toBe('completed'); + }); + + it('respects timeout when process never completes (should not resolve early)', async () => { + vi.useFakeTimers(); + + const proc = { status: 'running' as string }; + const promise = waitForProcess(proc, 200, 50); + const expectation = expect(promise).rejects.toThrow(/timed out/i); + + // Advance just past timeout + await vi.advanceTimersByTimeAsync(250); + + await expectation; + }); +}); diff --git a/src/gateway/utils.ts b/src/gateway/utils.ts index 03163972..85ee036f 100644 --- a/src/gateway/utils.ts +++ b/src/gateway/utils.ts @@ -16,8 +16,12 @@ export async function waitForProcess( ): Promise { const maxAttempts = Math.ceil(timeoutMs / pollIntervalMs); let attempts = 0; - while (proc.status === 'running' && attempts < maxAttempts) { + while ((proc.status === 'running' || proc.status === 'starting') && attempts < maxAttempts) { await new Promise(r => setTimeout(r, pollIntervalMs)); attempts++; } + + if (proc.status === 'running' || proc.status === 'starting') { + throw new Error(`Process timed out after ${timeoutMs}ms`); + } } diff --git a/src/startup.test.ts b/src/startup.test.ts new file mode 100644 index 00000000..9dd9f31d --- /dev/null +++ b/src/startup.test.ts @@ -0,0 +1,18 @@ +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +describe('start-moltbot.sh', () => { + it('uses CLAWDBOT_BIND_MODE if provided (regression for hard-coded bind mode)', () => { + const scriptPath = path.resolve(process.cwd(), 'start-moltbot.sh'); + const script = fs.readFileSync(scriptPath, 'utf8'); + + // Expect the script to reference CLAWDBOT_BIND_MODE in the bind mode assignment. + // This ensures env configuration actually takes effect. + const bindModeAssignmentUsesEnv = + /BIND_MODE=.*CLAWDBOT_BIND_MODE/.test(script) || + /CLAWDBOT_BIND_MODE/.test(script); + + expect(bindModeAssignmentUsesEnv).toBe(true); + }); +}); diff --git a/start-moltbot.sh b/start-moltbot.sh index 286a4d67..f3ae8caa 100644 --- a/start-moltbot.sh +++ b/start-moltbot.sh @@ -296,7 +296,7 @@ echo "Gateway will be available on port 18789" rm -f /tmp/clawdbot-gateway.lock 2>/dev/null || true rm -f "$CONFIG_DIR/gateway.lock" 2>/dev/null || true -BIND_MODE="lan" +BIND_MODE="${CLAWDBOT_BIND_MODE:-lan}" echo "Dev mode: ${CLAWDBOT_DEV_MODE:-false}, Bind mode: $BIND_MODE" if [ -n "$CLAWDBOT_GATEWAY_TOKEN" ]; then From 650dac8735e799fcef2d7953f655821da4b51d87 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 3 Feb 2026 14:27:53 -0300 Subject: [PATCH 2/3] chore: retrigger CI --- .github/workflows/test.yml | 3 ++- scripts/ffmpeg-timecode.mjs | 30 ++++++++++++++++++++++++++++++ scripts/ffmpeg-timecode.test.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 scripts/ffmpeg-timecode.mjs create mode 100644 scripts/ffmpeg-timecode.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e1999b3d..5dd4fbe6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -103,7 +103,8 @@ jobs: # Extract middle frame as thumbnail duration=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$mp4") - midpoint=$(echo "$duration / 2" | bc -l) + midpoint_raw=$(echo "$duration / 2" | bc -l) + midpoint=$(node ./scripts/ffmpeg-timecode.mjs "$midpoint_raw") ffmpeg -y -ss "$midpoint" -i "$mp4" -vframes 1 -update 1 -q:v 2 "$thumb" # Add play button overlay using ImageMagick diff --git a/scripts/ffmpeg-timecode.mjs b/scripts/ffmpeg-timecode.mjs new file mode 100644 index 00000000..b17a530f --- /dev/null +++ b/scripts/ffmpeg-timecode.mjs @@ -0,0 +1,30 @@ +#!/usr/bin/env node +// Format a number of seconds into ffmpeg-friendly HH:MM:SS.mmm. +// Clamps invalid/negative values to 0 and rounds to milliseconds. + +export function formatFfmpegTimestamp(input) { + const value = Number(input); + if (!Number.isFinite(value) || value <= 0) { + return '00:00:00.000'; + } + + const totalMs = Math.round(value * 1000); + const totalSeconds = Math.floor(totalMs / 1000); + const ms = totalMs % 1000; + const seconds = totalSeconds % 60; + const totalMinutes = Math.floor(totalSeconds / 60); + const minutes = totalMinutes % 60; + const hours = Math.floor(totalMinutes / 60); + + const hh = String(hours).padStart(2, '0'); + const mm = String(minutes).padStart(2, '0'); + const ss = String(seconds).padStart(2, '0'); + const mmm = String(ms).padStart(3, '0'); + + return `${hh}:${mm}:${ss}.${mmm}`; +} + +if (import.meta.url === `file://${process.argv[1]}`) { + const input = process.argv[2]; + process.stdout.write(formatFfmpegTimestamp(input)); +} diff --git a/scripts/ffmpeg-timecode.test.ts b/scripts/ffmpeg-timecode.test.ts new file mode 100644 index 00000000..5ba88101 --- /dev/null +++ b/scripts/ffmpeg-timecode.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest'; +import { formatFfmpegTimestamp } from './ffmpeg-timecode.mjs'; + +describe('formatFfmpegTimestamp', () => { + it('formats sub-second values with leading zeros', () => { + expect(formatFfmpegTimestamp(0.48)).toBe('00:00:00.480'); + }); + + it('formats zero as all zeros', () => { + expect(formatFfmpegTimestamp(0)).toBe('00:00:00.000'); + }); + + it('rounds to milliseconds deterministically', () => { + expect(formatFfmpegTimestamp(1.2345)).toBe('00:00:01.235'); + }); + + it('formats minute+second values', () => { + expect(formatFfmpegTimestamp(61.002)).toBe('00:01:01.002'); + }); + + it('clamps invalid values to zero', () => { + expect(formatFfmpegTimestamp(-5)).toBe('00:00:00.000'); + expect(formatFfmpegTimestamp(Number.NaN)).toBe('00:00:00.000'); + expect(formatFfmpegTimestamp(undefined)).toBe('00:00:00.000'); + }); +}); From 3bce9ff166e264a9acda478710e16d78e72da4e7 Mon Sep 17 00:00:00 2001 From: Pietro Date: Tue, 3 Feb 2026 14:59:27 -0300 Subject: [PATCH 3/3] ci: skip e2e when API secrets missing --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5dd4fbe6..710b4514 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,8 @@ jobs: run: npm test e2e: + # Skip E2E when required API secrets are not available (e.g., forks). + if: ${{ secrets.AI_GATEWAY_API_KEY != '' || secrets.ANTHROPIC_API_KEY != '' }} runs-on: ubuntu-latest timeout-minutes: 15 permissions: