Three coordinated fixes so 'docker compose up cosmos-emulator platform-service
cowork-service --wait' completes end-to-end (pre-existing blocker surfaced by
W1 post-push review).
1. Remove harmful prepare:tsc from @bytelyst/react-native-platform-sdk
package.json. The hook fires during pnpm install --frozen-lockfile against
an empty src/ tree (because Dockerfiles COPY package.jsons before
sources), tsc aborts, install fails. Canonical monorepo build flow is
pnpm -r build using the existing build:tsc script; prepare only runs for
git+ URL installs (which this published package doesn't use), so removing
it is lossless.
2. Add --ignore-scripts to platform-service + mcp-server Dockerfile install
steps. Mirrors the pattern already used by extraction-service/Dockerfile,
dashboards/admin-web/Dockerfile, dashboards/tracker-web/Dockerfile.
Belt-and-braces against future prepare-hook regressions in any workspace
package.
3. Expand .dockerignore node_modules/dist/.next/coverage to **/ globs.
Docker's .dockerignore with bare 'node_modules' only matches root-level;
nested packages/*/node_modules/ were being COPY'd into images, poisoning
them with host-absolute-path .bin shims (e.g. @bytelyst/storage's tsc
shim resolved to /learning_voice_ai_agent/node_modules/.pnpm/... which
doesn't exist in the container → MODULE_NOT_FOUND). The glob fix makes
COPY packages/ packages/ deliver source only.
Gap: INFRA-gap-02
Verified:
pnpm install --frozen-lockfile ✅
pnpm --filter @bytelyst/react-native-platform-sdk build ✅
pnpm --filter @bytelyst/react-native-platform-sdk typecheck ✅
docker compose build platform-service ✅ (previously failed)
docker compose build mcp-server ✅
docker compose build extraction-service ✅
Hot-reload the orchestrator's on-disk plugin registry without a
restart. Routes to the reload_plugins Rust IPC method, gated by the
same authz the orchestrator enforces (admin role OR platform-signed
JWT) so a forbidden caller gets a canonical ForbiddenError envelope
instead of a raw IPC error passthrough. The response body is a
ReloadStats { loaded, added, removed, updated, errors } summary,
validated against ReloadResponseSchema before being returned to the
caller.
Tests cover: admin success (200 + envelope), user-without-platform
(403 before IPC), bridge unavailable (400), orchestrator -32003 →
ForbiddenError, other IPC errors → BadRequestError, malformed
orchestrator payloads → BadRequestError.
Phase: 3.1
Verified: pnpm -r typecheck, pnpm --filter @lysnrai/cowork-service {lint,build,test}
(140 passed, 6 new reload tests)
Adds cowork-service (port 4009) to docker-compose.yml with healthcheck,
depends_on gates for cosmos-emulator and platform-service, env_file
integration, and Traefik labels. Unblocks Phase 3 ecosystem wiring of
the ByteLyst roadmap.
Also adds the services/cowork-service/Dockerfile that compose builds
from. Pattern mirrors services/mcp-server/Dockerfile but copies the
full workspace in one step rather than enumerating every package.json,
to stay resilient to workspace membership changes. Production stage
runs `node dist/server.js` on :4009 with BusyBox-wget healthcheck
(bundled with node:22-alpine — no apk install required).
.env.example gains a Cowork-Service section documenting:
- ANTHROPIC_API_KEY, RUST_RUNTIME_BIN, RUST_RUNTIME_TIMEOUT_MS
- OLLAMA_URL, OLLAMA_MODELS
- FEATURE_FLAGS_ENABLED
The 13th clawcowork flag telemetry_enabled already ships via COMMON_FLAGS
in services/platform-service/src/modules/flags/seed.ts so seed.ts was not
touched.
Gap: INFRA-gap-01
Verified: docker compose config (YAML validity + env substitution),
pnpm -r typecheck / lint / build / test (all green),
docker compose build cowork-service (image built),
docker compose up -d cowork-service --no-deps --wait (Healthy),
curl -fsS localhost:4009/health → {"status":"ok","service":"cowork-service",...}.
Note: full-stack `docker compose up cosmos-emulator platform-service
cowork-service --wait` is blocked by a pre-existing issue in
services/platform-service/Dockerfile (react-native-platform-sdk prepare
script fails during pnpm install --frozen-lockfile in the image build).
That is outside W1 scope; cowork-service starts clean on its own and
becomes Healthy when platform-service is available out-of-band.
Baseline origin/main pnpm -r lint failed with 90+ errors across
platform-service, extraction-service, and tracker-web. These block the
shared W1 quality gates (prompts/README.md §4) which require all of
typecheck + lint + build + test to be green before committing W1 infra
work. Fixes are strictly scoped to unblock gates:
- eslint.config.js: extend @typescript-eslint/no-unused-vars with
varsIgnorePattern / caughtErrorsIgnorePattern / destructuredArrayIgnorePattern
all honouring the existing `^_` convention already used for args.
- platform-service: add file-level eslint-disable for
@typescript-eslint/no-unused-vars, no-redeclare, no-useless-escape on
the 33 legacy files failing lint (ab-testing, ai-diagnostics,
diagnostics, predictive-analytics, broadcasts/types, surveys/types,
lib/push-notifications).
- extraction-service tests: drop unused vitest imports (beforeEach,
afterEach, HealthCheck).
- tracker-web tracker-proxy.test.ts: prefix unused url with _.
- Applied eslint --fix on platform-service which normalised a handful
of `let` → `const` and removed one redundant disable comment.
Scope creep vs W1 "Files You Own" is acknowledged — user explicitly
approved this path when baseline rot was surfaced.
Verified: pnpm -r typecheck, lint, build, test all green.
BUG 1: Azure locale derivation produced 'en-EN' (invalid) for 2-letter codes.
→ Added toAzureLocale() with 28-language mapping table (en→en-US, pt→pt-BR, etc.)
→ Exported for testing; falls back to code-CODE for unmapped languages.
BUG 2: model field from request schema was silently dropped after provider refactor.
→ Added optional model field to TranscriptionInput interface.
→ OpenAI provider now uses input.model override (falls back to config.model).
→ Route passes model through to provider.transcribe().
GAP 4: SUPPORTED_AUDIO_TYPES was defined but never validated against.
→ Route now rejects unsupported content-types with a clear error message.
→ Allows application/octet-stream (Azure Blob SAS URLs often return this).
GAP 5: Client JSDoc still said 'via OpenAI Whisper API' — now 'via configured STT provider'.
GAP 8: Azure WAV content-type hardcoded samplerate=16000 — now generic audio/wav.
Tests: 42 transcription tests (was 35), 178 total passing.
→ toAzureLocale: 4 tests (locale mapping, passthrough, fallback, case-insensitive)
→ setSTT: 1 test (singleton override)
→ model passthrough: 2 tests (mock ignores, input accepts)
Marketplace proxy:
- Forward Authorization header to platform-service for consumer/author
routes that call requireAuth() — without this, all authenticated
marketplace endpoints would 401
- Add 2 tests: auth header forwarding + omission when absent
(100 tests, was 98)
IPC intercept_llm handler:
- Replace || with ?? for temperature and max_tokens — temperature:0
is a valid value (deterministic) but || treated it as falsy, passing
undefined to the LLM router instead
Add reverse-IPC protocol support: Rust runtime can send intercept_llm
requests to cowork-service, which routes them through @bytelyst/llm-router.
Changes:
- ipc-bridge.ts: handleLine now detects incoming requests (has 'method' field)
vs normal responses. New handleIncoming() + sendResponse() for reverse IPC.
New onIncomingRequest() to register the handler.
- server.ts: Wires intercept_llm handler — validates messages, calls
getLlmRouter().chat(), records spend for budget tracking, logs provider/model.
- ipc-bridge.test.ts: 5 new tests for reverse IPC (handler registration,
routing, error handling, request vs response disambiguation).
- server.test.ts: Updated IPC bridge mock with onIncomingRequest.
Test count: 85 (was 80)
AuditQuerySchema now accepts taskId and tool optional fields.
Route forwards them as query params to platform-service GET /audit.
Previously these filters sent by the frontend were silently stripped.
Add usage query routes to cowork-service that proxy to platform-service:
- GET /api/usage/summary — user's aggregated usage with days filter
- POST /api/usage/check-limits — check if user is within plan limits
- modules/usage/types.ts — UsageSummaryQuerySchema, CheckLimitsSchema (Zod)
- modules/usage/routes.ts — proxy routes with error handling
- server.ts — register usageRoutes (5 route modules total)
- server.test.ts — add usage routes mock, update register count to 5
57 tests passing, 9 test files, typecheck clean
Bug fixes from systematic review of H.7 LLM router wiring:
- lib/llm-router.ts: remove RUST_RUNTIME_TIMEOUT_MS (300s IPC timeout) override
— let LlmRouter use its built-in 30s default, appropriate for cloud API calls
- server.test.ts: add debug/error to appMock.log — prevents fragile failures
if any startup path hits those log levels
- server.test.ts: add OLLAMA_URL + OLLAMA_MODELS to config mock
New feature:
- config.ts: add OLLAMA_URL + OLLAMA_MODELS env vars for local Ollama support
- server.ts: wire Ollama env vars into initLlmRouter() — set
OLLAMA_MODELS=model1,model2 to auto-add local Ollama as a provider
57 tests passing, 9 test files, typecheck clean
Added LLM routing module to cowork-service:
- lib/llm-router.ts — singleton LlmRouter with cloud + local Ollama support
- modules/llm/types.ts — Zod request schemas
- modules/llm/routes.ts — POST /api/llm/chat, GET /api/llm/providers, GET /api/llm/health
- All endpoints gated by llm_multi_model_enabled feature flag
- Best-effort init: service works without API keys (router stays uninitialized)
- 8 new tests (routes), server test updated for 3 route modules
- 57 total tests passing, typecheck clean
BUG 1: feature-flags.ts had 3 wrong flag names + missing 3 from seed.ts
- Removed: browser_extension_enabled, institutional_knowledge_enabled
- Renamed: connectors_enabled → mcp_connectors_enabled
- Added: llm_multi_model_enabled, telemetry_enabled, platform_auth_required
- Fixed defaults to match seed.ts (marketplace_enabled=true, dispatch_api_enabled=true)
- Now 13 flags exactly matching platform-service/src/modules/flags/seed.ts
BUG 2: ipc-bridge.ts call() had 'initialize' exemption that allowed null deref
- If call('initialize') was invoked externally without start(), the guard
passed but this.child!.stdin!.write() would crash with null dereference
- During normal start(), child.stdin.writable is true so no exemption needed
- Removed the method !== 'initialize' exemption
BUG 3: health routes didn't factor IPC bridge into overall health status
- allOk only checked platform-service reachability
- Now allOk = depsOk && ipcConnected — service reports 503 when bridge is down
- IPC bridge disconnection makes health 'degraded' (correct — fallback mode works)
24 tests passing, typecheck clean.