Migrate Maestro web control plane to Rust#206
Conversation
PR SummaryHigh Risk Overview Adds a new Rust control plane package. Introduces Adjusts build/ops wiring. Updates Reviewed by Cursor Bugbot for commit db6705f. Bugbot is set up for automated code reviews on this repo. Configure here. |
|
This PR changes mirrored Maestro source files in the public repo, but it does not link the matching private source-of-truth PR. Add one of these to the PR body, then re-run the check:
Mirrored files touched:
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: HEAD response sends incorrect Content-Length of zero
- The Rust control plane now preserves the GET payload size in
Content-LengthforHEADstatic responses while still omitting the body.
- The Rust control plane now preserves the GET payload size in
- ✅ Fixed: Chat SSE stream missing terminal event on channel close
- The chat SSE handler now emits a fallback error and
doneevent when the agent stream closes before a normal completion event arrives.
- The chat SSE handler now emits a fallback error and
Preview (54313ff060)
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig openssl-dev openssl-libs-static && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
+
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
+WORKDIR /app
+
+COPY proto ./proto
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ --mount=type=cache,target=/app/packages/control-plane-rs/target \
+ cd packages/control-plane-rs && \
+ cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
# ---------- runner ----------
-FROM node:22-alpine AS runner
+FROM alpine:3.23 AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates openssl && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.lock
@@ -1,0 +1,3714 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adobe-cmap-parser"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
+dependencies = [
+ "pom",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "mio",
+ "parking_lot",
+ "rustix 0.38.44",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "euclid"
+version = "0.20.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "eventsource-stream"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
+dependencies = [
+ "futures-core",
+ "nom",
+ "pin-project-lite",
... diff truncated: showing 800 of 6558 linesYou can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2b0bc2c6dd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Temp files leak when SSE streaming fails mid-chat
- Added a
Dropcleanup fallback forPreparedAttachmentsand made explicit cleanup consume the temp dir so attachment files are removed on every exit path.
- Added a
- ✅ Fixed: Server exits on transient accept errors
- Wrapped
listener.accept()in a retrying match that logs transient failures, backs off briefly, and continues accepting connections.
- Wrapped
Preview (323af4b8a5)
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig openssl-dev openssl-libs-static && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
+
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
+WORKDIR /app
+
+COPY proto ./proto
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ --mount=type=cache,target=/app/packages/control-plane-rs/target \
+ cd packages/control-plane-rs && \
+ cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
# ---------- runner ----------
-FROM node:22-alpine AS runner
+FROM alpine:3.23 AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates openssl && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.lock
@@ -1,0 +1,3714 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adobe-cmap-parser"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
+dependencies = [
+ "pom",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "mio",
+ "parking_lot",
+ "rustix 0.38.44",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "euclid"
+version = "0.20.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2bb7ef65b3777a325d1eeefefab5b6d4959da54747e33bd6258e789640f307ad"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "eventsource-stream"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74fef4569247a5f429d9156b9d0a2599914385dd189c539334c625d8099d90ab"
+dependencies = [
+ "futures-core",
+ "nom",
+ "pin-project-lite",
... diff truncated: showing 800 of 6847 linesYou can send follow-ups to the cloud agent here.
5fd4edd to
323af4b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 20726a092a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Git status counts miss staged and renamed files
- I replaced the narrow porcelain prefix checks with a parser that classifies index/worktree statuses and added coverage for staged, renamed, copied, deleted, conflicted, and untracked entries.
- ✅ Fixed: Telemetry and training overrides have confusingly inverted boolean semantics
- I replaced the shared
Option<bool>overrides with distinctTelemetryOverrideandTrainingOverrideenums so each endpoint uses explicit, self-documenting semantics.
- I replaced the shared
Preview (58dfc8e05a)
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@ coverage/
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
@@ -12,6 +12,7 @@ coverage/
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@ RUN apk add --no-cache python3 make g++ git
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -8,6 +8,7 @@ RUN apk add --no-cache python3 make g++ git
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@ COPY packages/slack-agent-ui/package.json packages/slack-agent-ui/
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig openssl-dev openssl-libs-static && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
-# ---------- runner ----------
-FROM node:22-alpine AS runner
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
WORKDIR /app
-ENV NODE_ENV=production
+COPY proto ./proto
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ --mount=type=cache,target=/app/packages/control-plane-rs/target \
+ cd packages/control-plane-rs && \
+ cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
+# ---------- runner ----------
+FROM alpine:3.23 AS runner
+WORKDIR /app
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates openssl nodejs npm && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
@@ -19,55 +20,80 @@ COPY packages/slack-agent-ui/package.json packages/slack-agent-ui/
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig openssl-dev openssl-libs-static && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
-# ---------- runner ----------
-FROM node:22-alpine AS runner
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
WORKDIR /app
-ENV NODE_ENV=production
+COPY proto ./proto
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ --mount=type=cache,target=/app/packages/control-plane-rs/target \
+ cd packages/control-plane-rs && \
+ cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
+# ---------- runner ----------
+FROM alpine:3.23 AS runner
+WORKDIR /app
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates openssl nodejs npm && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
diff --git a/packages/control-plane-rs/Cargo.toml b/packages/control-plane-rs/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "maestro-control-plane"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+description = "Native Rust HTTP control plane for Maestro"
+
+[[bin]]
+name = "maestro-control-plane"
+path = "src/main.rs"
+
+[dependencies]
+anyhow = "1"
+base64 = "0.22"
+chrono = "0.4"
+maestro-tui = { path = "../tui-rs" }
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "net", "process", "sync", "time", "fs"] }
@@ -0,0 +1,19 @@
+[package]
+name = "maestro-control-plane"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+description = "Native Rust HTTP control plane for Maestro"
+
+[[bin]]
+name = "maestro-control-plane"
+path = "src/main.rs"
+
+[dependencies]
+anyhow = "1"
+base64 = "0.22"
+chrono = "0.4"
+maestro-tui = { path = "../tui-rs" }
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "net", "process", "sync", "time", "fs"] }
diff --git a/packages/control-plane-rs/src/main.rs b/packages/control-plane-rs/src/main.rs
new file mode 100644
diff --git a/test/db/migration-files.test.ts b/test/db/migration-files.test.ts
--- a/test/db/migration-files.test.ts
+++ b/test/db/migration-files.test.ts
@@ -70,10 +70,14 @@ describe("database migration packaging", () => {
);
});
- it("keeps Docker and build scripts wired to ship migrations", () => {
+ it("keeps build scripts wired to ship migrations without the legacy Node image copy", () => {
const dockerfile = readFileSync(path.join(repoRoot, "Dockerfile"), "utf8");
+ expect(dockerfile).toContain("RUN bun run build:all");
expect(dockerfile).toContain(
- "COPY --from=builder /app/src/db/migrations ./dist/db/migrations",
+ "COPY --from=web-builder /app/packages/web/dist ./packages/web/dist",
+ );
+ expect(dockerfile).not.toContain(
+ "COPY --from=builder /app/src/db/migrations",
);
const packageJson = JSON.parse(
@@ -70,10 +70,14 @@ describe("database migration packaging", () => {
);
});
- it("keeps Docker and build scripts wired to ship migrations", () => {
+ it("keeps build scripts wired to ship migrations without the legacy Node image copy", () => {
const dockerfile = readFileSync(path.join(repoRoot, "Dockerfile"), "utf8");
+ expect(dockerfile).toContain("RUN bun run build:all");
expect(dockerfile).toContain(
- "COPY --from=builder /app/src/db/migrations ./dist/db/migrations",
+ "COPY --from=web-builder /app/packages/web/dist ./packages/web/dist",
+ );
+ expect(dockerfile).not.toContain(
+ "COPY --from=builder /app/src/db/migrations",
);
const packageJson = JSON.parse(You can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 58dfc8e05a
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ce11f0e44c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: HTTP header parsing silently drops duplicate header values
parse_request_headnow merges duplicate header values instead of overwriting them, with a regression test covering repeated headers.
Preview (296eccc4b0)
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/.github/workflows/ghcr-publish.yml b/.github/workflows/ghcr-publish.yml
--- a/.github/workflows/ghcr-publish.yml
+++ b/.github/workflows/ghcr-publish.yml
@@ -2,12 +2,14 @@
on:
pull_request:
+ branches:
+ - main
push:
branches:
- main
concurrency:
- group: ghcr-publish-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ghcr-publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
@@ -28,7 +30,7 @@
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
- if: github.event_name != 'pull_request'
+ if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
@@ -42,7 +44,6 @@
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
- type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
@@ -52,7 +53,7 @@
context: .
file: ./Dockerfile
platforms: linux/amd64
- push: ${{ github.event_name != 'pull_request' }}
+ push: ${{ github.event_name == 'push' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
+
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
+WORKDIR /app
+
+COPY proto ./proto
+COPY --from=web-builder /app/packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ cd packages/control-plane-rs && \
+ CARGO_TARGET_DIR=/app/rust-target cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp /app/rust-target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
# ---------- runner ----------
-FROM node:22-alpine AS runner
+FROM alpine:3.23 AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates libstdc++ && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/docs/MODELS.md b/docs/MODELS.md
--- a/docs/MODELS.md
+++ b/docs/MODELS.md
@@ -23,6 +23,19 @@
Paths are read in that order, later entries overriding earlier ones.
+The Rust control plane keeps the same local fallback, and can also hydrate
+`GET /api/models` from the llm-gateway model catalog before applying local
+Maestro overrides:
+
+- `MAESTRO_LLM_GATEWAY_MODELS_URL` points directly at the catalog endpoint.
+- `MAESTRO_LLM_GATEWAY_URL` derives the catalog URL as `<base>/v1/models`.
+- `MAESTRO_LLM_GATEWAY_TOKEN` is sent as a bearer token when set.
+- `MAESTRO_LLM_GATEWAY_ORG_ID` is sent as `X-Organization-ID` when set.
+- `MAESTRO_LLM_GATEWAY_TIMEOUT_MS` defaults to `2500`.
+
+If the gateway URL is unset, unavailable, or returns invalid JSON, Maestro falls
+back to the built-in models and `~/.maestro/models.json`.
+
## Format
Custom config files accept:
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.lock
@@ -1,0 +1,3665 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adobe-cmap-parser"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
+dependencies = [
+ "pom",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "mio",
+ "parking_lot",
+ "rustix 0.38.44",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
... diff truncated: showing 800 of 12019 linesYou can send follow-ups to the cloud agent here.
|
Bugbot Autofix prepared fixes for both issues found in the latest run.
Preview (89a9a66c45)diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/.github/workflows/ghcr-publish.yml b/.github/workflows/ghcr-publish.yml
--- a/.github/workflows/ghcr-publish.yml
+++ b/.github/workflows/ghcr-publish.yml
@@ -2,12 +2,14 @@
on:
pull_request:
+ branches:
+ - main
push:
branches:
- main
concurrency:
- group: ghcr-publish-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ghcr-publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
@@ -28,7 +30,7 @@
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
- if: github.event_name != 'pull_request'
+ if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
@@ -42,7 +44,6 @@
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
- type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
@@ -52,7 +53,7 @@
context: .
file: ./Dockerfile
platforms: linux/amd64
- push: ${{ github.event_name != 'pull_request' }}
+ push: ${{ github.event_name == 'push' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
+
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
+WORKDIR /app
+
+COPY proto ./proto
+COPY --from=web-builder /app/packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ cd packages/control-plane-rs && \
+ CARGO_TARGET_DIR=/app/rust-target cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp /app/rust-target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
# ---------- runner ----------
-FROM node:22-alpine AS runner
+FROM alpine:3.23 AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates libstdc++ && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/docs/MODELS.md b/docs/MODELS.md
--- a/docs/MODELS.md
+++ b/docs/MODELS.md
@@ -23,6 +23,19 @@
Paths are read in that order, later entries overriding earlier ones.
+The Rust control plane keeps the same local fallback, and can also hydrate
+`GET /api/models` from the llm-gateway model catalog before applying local
+Maestro overrides:
+
+- `MAESTRO_LLM_GATEWAY_MODELS_URL` points directly at the catalog endpoint.
+- `MAESTRO_LLM_GATEWAY_URL` derives the catalog URL as `<base>/v1/models`.
+- `MAESTRO_LLM_GATEWAY_TOKEN` is sent as a bearer token when set.
+- `MAESTRO_LLM_GATEWAY_ORG_ID` is sent as `X-Organization-ID` when set.
+- `MAESTRO_LLM_GATEWAY_TIMEOUT_MS` defaults to `2500`.
+
+If the gateway URL is unset, unavailable, or returns invalid JSON, Maestro falls
+back to the built-in models and `~/.maestro/models.json`.
+
## Format
Custom config files accept:
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.lock
@@ -1,0 +1,3665 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adobe-cmap-parser"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
+dependencies = [
+ "pom",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "mio",
+ "parking_lot",
+ "rustix 0.38.44",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
... diff truncated: showing 800 of 11990 linesYou can send follow-ups to the cloud agent here. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 296eccc4b0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Query flag treats present-but-valueless params as false
- The branch now treats present-but-valueless query params as enabled in
query_flag, with regression coverage for bare and explicit false values.
- The branch now treats present-but-valueless query params as enabled in
Preview (45b0f532ad)
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/.github/workflows/ghcr-publish.yml b/.github/workflows/ghcr-publish.yml
--- a/.github/workflows/ghcr-publish.yml
+++ b/.github/workflows/ghcr-publish.yml
@@ -2,12 +2,14 @@
on:
pull_request:
+ branches:
+ - main
push:
branches:
- main
concurrency:
- group: ghcr-publish-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ghcr-publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
@@ -28,7 +30,7 @@
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
- if: github.event_name != 'pull_request'
+ if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
@@ -42,7 +44,6 @@
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
- type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
@@ -52,7 +53,7 @@
context: .
file: ./Dockerfile
platforms: linux/amd64
- push: ${{ github.event_name != 'pull_request' }}
+ push: ${{ github.event_name == 'push' }}
load: ${{ github.event_name == 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
+
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
+WORKDIR /app
+
+COPY proto ./proto
+COPY --from=web-builder /app/packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ cd packages/control-plane-rs && \
+ CARGO_TARGET_DIR=/app/rust-target cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp /app/rust-target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
# ---------- runner ----------
-FROM node:22-alpine AS runner
+FROM alpine:3.23 AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates libstdc++ && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/docs/MODELS.md b/docs/MODELS.md
--- a/docs/MODELS.md
+++ b/docs/MODELS.md
@@ -23,6 +23,19 @@
Paths are read in that order, later entries overriding earlier ones.
+The Rust control plane keeps the same local fallback, and can also hydrate
+`GET /api/models` from the llm-gateway model catalog before applying local
+Maestro overrides:
+
+- `MAESTRO_LLM_GATEWAY_MODELS_URL` points directly at the catalog endpoint.
+- `MAESTRO_LLM_GATEWAY_URL` derives the catalog URL as `<base>/v1/models`.
+- `MAESTRO_LLM_GATEWAY_TOKEN` is sent as a bearer token when set.
+- `MAESTRO_LLM_GATEWAY_ORG_ID` is sent as `X-Organization-ID` when set.
+- `MAESTRO_LLM_GATEWAY_TIMEOUT_MS` defaults to `2500`.
+
+If the gateway URL is unset, unavailable, or returns invalid JSON, Maestro falls
+back to the built-in models and `~/.maestro/models.json`.
+
## Format
Custom config files accept:
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.lock
@@ -1,0 +1,3665 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adobe-cmap-parser"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
+dependencies = [
+ "pom",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "mio",
+ "parking_lot",
+ "rustix 0.38.44",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
+dependencies = [
+ "cfg-if",
... diff truncated: showing 800 of 12382 linesYou can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45b0f532ad
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Content-Length/body size mismatch possible in response helpers
- Added an assertion that rejects non-empty responses whose explicit Content-Length does not match the body bytes, while preserving empty-body HEAD/204-style responses.
Preview (7750b435d0)
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/.github/workflows/ghcr-publish.yml b/.github/workflows/ghcr-publish.yml
--- a/.github/workflows/ghcr-publish.yml
+++ b/.github/workflows/ghcr-publish.yml
@@ -2,12 +2,14 @@
on:
pull_request:
+ branches:
+ - main
push:
branches:
- main
concurrency:
- group: ghcr-publish-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ghcr-publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
@@ -21,14 +23,20 @@
packages: write
steps:
+ - name: Validate publish gate
+ if: github.event_name == 'pull_request'
+ run: echo "Image publishing runs only for pushes to main."
+
- name: Checkout repository
+ if: github.event_name == 'push'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
+ if: github.event_name == 'push'
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
- if: github.event_name != 'pull_request'
+ if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
@@ -36,24 +44,24 @@
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract image metadata
+ if: github.event_name == 'push'
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
- type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build image
+ if: github.event_name == 'push'
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
- push: ${{ github.event_name != 'pull_request' }}
- load: ${{ github.event_name == 'pull_request' }}
+ push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
+
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
+WORKDIR /app
+
+COPY proto ./proto
+COPY --from=web-builder /app/packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ cd packages/control-plane-rs && \
+ CARGO_TARGET_DIR=/app/rust-target cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp /app/rust-target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
# ---------- runner ----------
-FROM node:22-alpine AS runner
+FROM alpine:3.23 AS runner
WORKDIR /app
-ENV NODE_ENV=production
-
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates libstdc++ && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/docs/MODELS.md b/docs/MODELS.md
--- a/docs/MODELS.md
+++ b/docs/MODELS.md
@@ -23,6 +23,19 @@
Paths are read in that order, later entries overriding earlier ones.
+The Rust control plane keeps the same local fallback, and can also hydrate
+`GET /api/models` from the llm-gateway model catalog before applying local
+Maestro overrides:
+
+- `MAESTRO_LLM_GATEWAY_MODELS_URL` points directly at the catalog endpoint.
+- `MAESTRO_LLM_GATEWAY_URL` derives the catalog URL as `<base>/v1/models`.
+- `MAESTRO_LLM_GATEWAY_TOKEN` is sent as a bearer token when set.
+- `MAESTRO_LLM_GATEWAY_ORG_ID` is sent as `X-Organization-ID` when set.
+- `MAESTRO_LLM_GATEWAY_TIMEOUT_MS` defaults to `2500`.
+
+If the gateway URL is unset, unavailable, or returns invalid JSON, Maestro falls
+back to the built-in models and `~/.maestro/models.json`.
+
## Format
Custom config files accept:
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.lock
@@ -1,0 +1,3665 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "adobe-cmap-parser"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae8abfa9a4688de8fc9f42b3f013b6fffec18ed8a554f5f113577e0b9b3212a3"
+dependencies = [
+ "pom",
+]
+
+[[package]]
+name = "aes"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
+dependencies = [
+ "cfg-if",
+ "cipher",
+ "cpufeatures",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
+
+[[package]]
+name = "anstyle-parse"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
+dependencies = [
+ "anstyle",
+ "once_cell_polyfill",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.102"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+
+[[package]]
+name = "arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+dependencies = [
+ "derive_arbitrary",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "byteorder-lite"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
+
+[[package]]
+name = "bytes"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
+
+[[package]]
+name = "bzip2"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
+dependencies = [
+ "bzip2-sys",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.13+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "cassowary"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
+
+[[package]]
+name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
+dependencies = [
+ "find-msvc-tools",
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chrono"
+version = "0.4.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cipher"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
+dependencies = [
+ "crypto-common",
+ "inout",
+]
+
+[[package]]
+name = "clap"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+
+[[package]]
+name = "compact_str"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
+dependencies = [
+ "castaway",
+ "cfg-if",
+ "itoa",
+ "rustversion",
+ "ryu",
+ "static_assertions",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
+
+[[package]]
+name = "crc32fast"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crossterm"
+version = "0.28.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "futures-core",
+ "mio",
+ "parking_lot",
+ "rustix 0.38.44",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "deflate64"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "powerfmt",
+]
+
+[[package]]
+name = "derive_arbitrary"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
... diff truncated: showing 800 of 12761 linesYou can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7750b435d0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing 409 status maps to wrong reason phrase
- Added the missing 409-to-Conflict mapping so pending request resume conflicts now emit the correct HTTP reason phrase.
Preview
diff --git a/.dockerignore b/.dockerignore
--- a/.dockerignore
+++ b/.dockerignore
@@ -12,6 +12,7 @@ coverage/
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
@@ -12,6 +12,7 @@ coverage/
.DS_Store
.external/
packages/tui-rs/target
+packages/control-plane-rs/target
packages/desktop
packages/jetbrains-plugin
packages/vscode-extension
diff --git a/.github/workflows/ghcr-publish.yml b/.github/workflows/ghcr-publish.yml
--- a/.github/workflows/ghcr-publish.yml
+++ b/.github/workflows/ghcr-publish.yml
@@ -2,12 +2,14 @@ name: GHCR Publish
on:
pull_request:
+ branches:
+ - main
push:
branches:
- main
concurrency:
- group: ghcr-publish-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ghcr-publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
@@ -2,12 +2,14 @@ name: GHCR Publish
on:
pull_request:
+ branches:
+ - main
push:
branches:
- main
concurrency:
- group: ghcr-publish-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ group: ghcr-publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
@@ -21,39 +23,45 @@ jobs:
packages: write
steps:
+ - name: Validate publish gate
+ if: github.event_name == 'pull_request'
+ run: echo "Image publishing runs only for pushes to main."
+
- name: Checkout repository
+ if: github.event_name == 'push'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
+ if: github.event_name == 'push'
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
- if: github.event_name != 'pull_request'
+ if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract image metadata
+ if: github.event_name == 'push'
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
- type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build image
+ if: github.event_name == 'push'
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
- push: ${{ github.event_name != 'pull_request' }}
- load: ${{ github.event_name == 'pull_request' }}
+ push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
@@ -21,39 +23,45 @@ jobs:
packages: write
steps:
+ - name: Validate publish gate
+ if: github.event_name == 'pull_request'
+ run: echo "Image publishing runs only for pushes to main."
+
- name: Checkout repository
+ if: github.event_name == 'push'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
+ if: github.event_name == 'push'
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
- if: github.event_name != 'pull_request'
+ if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract image metadata
+ if: github.event_name == 'push'
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
- type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
- name: Build image
+ if: github.event_name == 'push'
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
- push: ${{ github.event_name != 'pull_request' }}
- load: ${{ github.event_name == 'pull_request' }}
+ push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
diff --git a/Dockerfile b/Dockerfile
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@ RUN apk add --no-cache python3 make g++ git
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -8,6 +8,7 @@ RUN apk add --no-cache python3 make g++ git
COPY package.json bun.lockb ./
COPY packages/ai/package.json packages/ai/
+COPY packages/consumer-sdk/package.json packages/consumer-sdk/
COPY packages/contracts/package.json packages/contracts/
COPY packages/core/package.json packages/core/
COPY packages/github-agent/package.json packages/github-agent/
@@ -19,55 +20,80 @@ COPY packages/slack-agent-ui/package.json packages/slack-agent-ui/
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
-# ---------- runner ----------
-FROM node:22-alpine AS runner
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
WORKDIR /app
-ENV NODE_ENV=production
+COPY proto ./proto
+COPY --from=web-builder /app/packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ cd packages/control-plane-rs && \
+ CARGO_TARGET_DIR=/app/rust-target cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp /app/rust-target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
+# ---------- runner ----------
+FROM alpine:3.23 AS runner
+WORKDIR /app
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates libstdc++ && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
@@ -19,55 +20,80 @@ COPY packages/slack-agent-ui/package.json packages/slack-agent-ui/
COPY packages/tui/package.json packages/tui/
COPY packages/web/package.json packages/web/
-# ambient-agent-rs and tui-rs are pure Rust (no package.json)
+# ambient-agent-rs, control-plane-rs, and tui-rs are pure Rust (no package.json)
# desktop, jetbrains-plugin, vscode-extension excluded via .dockerignore
RUN bun install --no-frozen-lockfile
-# ---------- builder ----------
-FROM oven/bun:1.3-alpine AS builder
+# ---------- builder base ----------
+FROM oven/bun:1.3-alpine AS builder-base
WORKDIR /app
# contracts build generates Rust protocol files and formats with rustfmt
-RUN apk add --no-cache python3 make g++ git nodejs && \
+RUN apk add --no-cache python3 make g++ git nodejs pkgconfig && \
wget -qO- https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal -c rustfmt && \
ln -s /root/.cargo/bin/rustfmt /usr/local/bin/rustfmt
+ENV PATH="/root/.cargo/bin:${PATH}"
+
+# ---------- web builder ----------
+FROM builder-base AS web-builder
+WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
-COPY . .
+COPY package.json bun.lockb ./
+COPY biome.json buf.gen.yaml buf.yaml drizzle.config.ts nx.json openapi.json project.json ./
+COPY tsconfig.base.json tsconfig.build.json tsconfig.json vitest.config.ts ./
+COPY proto ./proto
+COPY scripts ./scripts
+COPY skills ./skills
+COPY src ./src
+COPY types ./types
+COPY packages/ai ./packages/ai
+COPY packages/consumer-sdk ./packages/consumer-sdk
+COPY packages/contracts ./packages/contracts
+COPY packages/core ./packages/core
+COPY packages/github-agent ./packages/github-agent
+COPY packages/governance ./packages/governance
+COPY packages/governance-mcp-server ./packages/governance-mcp-server
+COPY packages/memory ./packages/memory
+COPY packages/slack-agent ./packages/slack-agent
+COPY packages/slack-agent-ui ./packages/slack-agent-ui
+COPY packages/tui ./packages/tui
+COPY packages/tui-rs ./packages/tui-rs
+COPY packages/web ./packages/web
# Write lockfile hash stamp so ensure-deps.js skips re-install
RUN node -e "const c=require('crypto'),f=require('fs');const h=c.createHash('sha256').update(f.readFileSync('bun.lockb')).digest('hex');f.mkdirSync('node_modules',{recursive:true});f.writeFileSync('node_modules/.bun-lockb.sha256',h);"
RUN bun run build:all
-# ---------- runner ----------
-FROM node:22-alpine AS runner
+# ---------- rust builder ----------
+FROM builder-base AS rust-builder
WORKDIR /app
-ENV NODE_ENV=production
+COPY proto ./proto
+COPY --from=web-builder /app/packages/tui-rs ./packages/tui-rs
+COPY packages/control-plane-rs ./packages/control-plane-rs
+RUN --mount=type=cache,target=/root/.cargo/registry \
+ --mount=type=cache,target=/root/.cargo/git \
+ cd packages/control-plane-rs && \
+ CARGO_TARGET_DIR=/app/rust-target cargo build --release --bin maestro-control-plane && \
+ mkdir -p /app/target-bin && \
+ cp /app/rust-target/release/maestro-control-plane /app/target-bin/maestro-control-plane
+
+# ---------- runner ----------
+FROM alpine:3.23 AS runner
+WORKDIR /app
-RUN apk add --no-cache tini git && \
+RUN apk add --no-cache tini git ca-certificates libstdc++ && \
addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 appuser
-COPY --from=builder /app/dist ./dist
-COPY --from=builder /app/src/db/migrations ./dist/db/migrations
-COPY --from=builder /app/node_modules ./node_modules
-COPY --from=builder /app/package.json ./
-COPY --from=builder /app/skills ./skills
-COPY --from=builder /app/packages/contracts/dist ./packages/contracts/dist
-COPY --from=builder /app/packages/contracts/package.json ./packages/contracts/
-COPY --from=builder /app/packages/memory ./packages/memory
-COPY --from=builder /app/packages/tui/dist ./packages/tui/dist
-COPY --from=builder /app/packages/tui/package.json ./packages/tui/
-COPY --from=builder /app/packages/web/dist ./packages/web/dist
-COPY --from=builder /app/packages/web/package.json ./packages/web/
-COPY --from=builder /app/packages/ai/dist ./packages/ai/dist
-COPY --from=builder /app/packages/ai/package.json ./packages/ai/
+COPY --from=web-builder /app/packages/web/dist ./packages/web/dist
+COPY --from=rust-builder /app/target-bin/maestro-control-plane ./bin/maestro-control-plane
USER appuser
EXPOSE 8080
ENTRYPOINT ["tini", "--"]
-CMD ["node", "--enable-source-maps", "dist/web-server.js"]
+CMD ["./bin/maestro-control-plane"]
diff --git a/docs/MODELS.md b/docs/MODELS.md
--- a/docs/MODELS.md
+++ b/docs/MODELS.md
@@ -23,6 +23,19 @@ order and how to customize providers.
Paths are read in that order, later entries overriding earlier ones.
+The Rust control plane keeps the same local fallback, and can also hydrate
+`GET /api/models` from the llm-gateway model catalog before applying local
+Maestro overrides:
+
+- `MAESTRO_LLM_GATEWAY_MODELS_URL` points directly at the catalog endpoint.
+- `MAESTRO_LLM_GATEWAY_URL` derives the catalog URL as `<base>/v1/models`.
+- `MAESTRO_LLM_GATEWAY_TOKEN` is sent as a bearer token when set.
+- `MAESTRO_LLM_GATEWAY_ORG_ID` is sent as `X-Organization-ID` when set.
+- `MAESTRO_LLM_GATEWAY_TIMEOUT_MS` defaults to `2500`.
+
+If the gateway URL is unset, unavailable, or returns invalid JSON, Maestro falls
+back to the built-in models and `~/.maestro/models.json`.
+
## Format
Custom config files accept:
@@ -23,6 +23,19 @@ order and how to customize providers.
Paths are read in that order, later entries overriding earlier ones.
+The Rust control plane keeps the same local fallback, and can also hydrate
+`GET /api/models` from the llm-gateway model catalog before applying local
+Maestro overrides:
+
+- `MAESTRO_LLM_GATEWAY_MODELS_URL` points directly at the catalog endpoint.
+- `MAESTRO_LLM_GATEWAY_URL` derives the catalog URL as `<base>/v1/models`.
+- `MAESTRO_LLM_GATEWAY_TOKEN` is sent as a bearer token when set.
+- `MAESTRO_LLM_GATEWAY_ORG_ID` is sent as `X-Organization-ID` when set.
+- `MAESTRO_LLM_GATEWAY_TIMEOUT_MS` defaults to `2500`.
+
+If the gateway URL is unset, unavailable, or returns invalid JSON, Maestro falls
+back to the built-in models and `~/.maestro/models.json`.
+
## Format
Custom config files accept:
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -57,6 +57,7 @@
"bun:cli": "bun run ./src/cli.ts",
"bun:watch": "bun run --watch ./src/cli.ts",
"web": "node dist/web-server.js",
+ "web:rust-control": "packages/control-plane-rs/target/release/maestro-control-plane",
"web:dev": "concurrently --names \"server,ui\" --prefix-colors \"blue,green\" \"tsx --watch src/web-server.ts\" \"bun run dev:web\"",
"bun:lint": "bunx biome check . && bun run lint:evals",
"bun:test": "node ./scripts/run-vitest.js --run",
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
@@ -109,6 +110,8 @@
"vscode:package": "npx nx run vscode-extension:package",
"vscode:publish": "npx nx run vscode-extension:publish",
"tui-rs:build": "cd packages/tui-rs && cargo build --release",
+ "control-plane-rs:build": "cd packages/control-plane-rs && cargo build --release --bin maestro-control-plane",
+ "control-plane-rs:check": "cd packages/control-plane-rs && cargo check --bin maestro-control-plane",
"tui-rs:build:debug": "cd packages/tui-rs && cargo build",
"tui-rs:check": "cd packages/tui-rs && cargo check",
"tui-rs:test": "cd packages/tui-rs && cargo test"
diff --git a/packages/control-plane-rs/Cargo.lock b/packages/control-plane-rs/Cargo.lock
new file mode 100644
diff --git a/packages/control-plane-rs/Cargo.toml b/packages/control-plane-rs/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "maestro-control-plane"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+description = "Native Rust HTTP control plane for Maestro"
+
+[[bin]]
+name = "maestro-control-plane"
+path = "src/main.rs"
+
+[dependencies]
+anyhow = "1"
+base64 = "0.22"
+chrono = "0.4"
+getrandom = "0.3.4"
+hmac = "0.12"
+jsonwebtoken = "9"
+maestro-tui = { path = "../tui-rs" }
+pdf-extract = "0.7"
+reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+sha1 = "0.10"
+sha2 = "0.10"
+tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "net", "process", "sync", "time", "fs"] }
+zip = "2"
@@ -0,0 +1,27 @@
+[package]
+name = "maestro-control-plane"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+description = "Native Rust HTTP control plane for Maestro"
+
+[[bin]]
+name = "maestro-control-plane"
+path = "src/main.rs"
+
+[dependencies]
+anyhow = "1"
+base64 = "0.22"
+chrono = "0.4"
+getrandom = "0.3.4"
+hmac = "0.12"
+jsonwebtoken = "9"
+maestro-tui = { path = "../tui-rs" }
+pdf-extract = "0.7"
+reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+sha1 = "0.10"
+sha2 = "0.10"
+tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "net", "process", "sync", "time", "fs"] }
+zip = "2"
diff --git a/packages/control-plane-rs/src/http.rs b/packages/control-plane-rs/src/http.rs
new file mode 100644
--- /dev/null
+++ b/packages/control-plane-rs/src/http.rs
@@ -0,0 +1,449 @@
+use serde::Serialize;
+use std::collections::HashMap;
+use std::env;
+use tokio::io::AsyncReadExt;
+use tokio::net::TcpStream;
+
+const MAX_HEADER_BYTES: usize = 64 * 1024;
+pub(crate) const MAX_JSON_BODY_BYTES: usize = 32 * 1024 * 1024;
+const CORS_ALLOW_HEADERS: &str = "authorization,content-type,x-composer-artifact-access,x-composer-api-key,x-composer-approval-mode,x-composer-client,x-composer-client-tools,x-composer-csrf,x-composer-agent-id,x-composer-slim-events,x-composer-workspace,x-composer-workspace-id,x-maestro-artifact-access,x-maestro-api-key,x-maestro-approval-mode,x-maestro-agent-id,x-maestro-client,x-maestro-client-tools,x-maestro-csrf,x-maestro-slim-events,x-maestro-workspace,x-maestro-workspace-id,x-csrf-token,x-xsrf-token";
+
+#[derive(Debug)]
+pub(crate) struct RequestHead {
+ pub(crate) method: String,
+ pub(crate) path: String,
+ pub(crate) query: HashMap<String, String>,
+ pub(crate) headers: HashMap<String, String>,
+}
+
+pub(crate) async fn read_request_head(
+ stream: &mut TcpStream,
+ initial: &mut Vec<u8>,
+) -> Result<RequestHead, String> {
+ let mut chunk = [0_u8; 4096];
+ loop {
+ let read = stream
+ .read(&mut chunk)
+ .await
+ .map_err(|error| error.to_string())?;
+ if read == 0 {
+ return Err("connection closed before request headers".into());
+ }
+ initial.extend_from_slice(&chunk[..read]);
+ if initial.windows(4).any(|window| window == b"\r\n\r\n") {
+ break;
+ }
+ if initial.len() > MAX_HEADER_BYTES {
+ return Err("request headers exceeded limit".into());
+ }
+ }
+ parse_request_head(initial)
+}
+
+pub(crate) async fn read_request_body(
+ stream: &mut TcpStream,
+ initial: &mut Vec<u8>,
+ head: &RequestHead,
+) -> Result<Vec<u8>, String> {
+ read_request_body_with_limit(stream, initial, head, MAX_JSON_BODY_BYTES).await
+}
+
+pub(crate) async fn read_request_body_with_limit(
+ stream: &mut TcpStream,
+ initial: &mut Vec<u8>,
+ head: &RequestHead,
+ max_body_bytes: usize,
+) -> Result<Vec<u8>, String> {
+ let header_end = header_end(initial)?;
+ let body_start = header_end + 4;
+ let content_length = head
+ .headers
+ .get("content-length")
+ .and_then(|value| parse_content_length(value))
+ .ok_or_else(|| "content-length is required".to_string())?;
+ if content_length > max_body_bytes {
+ return Err(format!(
+ "request body exceeded limit: {content_length} > {max_body_bytes}"
+ ));
+ }
+
+ while initial.len().saturating_sub(body_start) < content_length {
+ let mut chunk = [0_u8; 8192];
+ let read = stream
+ .read(&mut chunk)
+ .await
+ .map_err(|error| error.to_string())?;
+ if read == 0 {
+ return Err("connection closed before request body completed".into());
+ }
+ initial.extend_from_slice(&chunk[..read]);
+ if initial.len().saturating_sub(body_start) > max_body_bytes {
+ return Err("request body exceeded limit".into());
+ }
+ }
+
+ Ok(initial[body_start..body_start + content_length].to_vec())
+}
+
+pub(crate) fn parse_request_head(initial: &[u8]) -> Result<RequestHead, String> {
+ let header_end = header_end(initial)?;
+ let header_text = std::str::from_utf8(&initial[..header_end])
+ .map_err(|error| format!("request headers are not utf-8: {error}"))?;
+ let mut lines = header_text.split("\r\n");
+ let request_line = lines
+ .next()
+ .ok_or_else(|| "request line missing".to_string())?;
+ let mut parts = request_line.split_whitespace();
+ let method = parts
+ .next()
+ .ok_or_else(|| "request method missing".to_string())?
+ .to_uppercase();
+ let raw_target = parts
+ .next()
+ .ok_or_else(|| "request target missing".to_string())?;
+ let (path, query) = raw_target
+ .split_once('?')
+ .map(|(path, query)| (path.to_string(), parse_query(query)))
+ .unwrap_or_else(|| (raw_target.to_string(), HashMap::new()));
+ let mut headers = HashMap::new();
+ for (name, value) in lines
+ .filter_map(|line| line.split_once(':'))
+ .map(|(name, value)| (name.trim().to_lowercase(), value.trim().to_string()))
+ {
+ headers
+ .entry(name)
+ .and_modify(|existing: &mut String| {
+ existing.push_str(", ");
+ existing.push_str(&value);
+ })
+ .or_insert(value);
+ }
+ Ok(RequestHead {
+ method,
+ path,
+ query,
+ headers,
+ })
+}
+
+fn parse_content_length(value: &str) -> Option<usize> {
+ let mut lengths = value
+ .split(',')
+ .map(|part| part.trim().parse::<usize>().ok());
+ let first = lengths.next()??;
+ lengths.all(|length| length == Some(first)).then_some(first)
+}
+
+fn parse_query(query: &str) -> HashMap<String, String> {
+ query
+ .split('&')
+ .filter(|part| !part.is_empty())
+ .filter_map(|part| {
+ let (key, value) = part.split_once('=').unwrap_or((part, ""));
+ let key = percent_decode_component(key);
+ if key.is_empty() {
+ None
+ } else {
+ Some((key, percent_decode_component(value)))
+ }
+ })
+ .collect()
+}
+
+pub(crate) fn query_flag(head: &RequestHead, name: &str) -> bool {
+ head.query
+ .get(name)
+ .map(|value| !matches!(value.as_str(), "0" | "false" | "off"))
+ .unwrap_or(false)
+}
+
+pub(crate) fn header_end(initial: &[u8]) -> Result<usize, String> {
+ initial
+ .windows(4)
+ .position(|window| window == b"\r\n\r\n")
+ .ok_or_else(|| "request header terminator not found".to_string())
+}
+
+pub(crate) fn text_response(status: u16, body: &str) -> Vec<u8> {
+ response(status, "text/plain; charset=utf-8", body.as_bytes())
+}
+
+pub(crate) fn json_response<T: Serialize>(status: u16, value: &T) -> Vec<u8> {
+ let body = serde_json::to_vec(value)
+ .unwrap_or_else(|_| br#"{"error":"failed to serialize response"}"#.to_vec());
+ response(status, "application/json", &body)
+}
+
+pub(crate) fn response(status: u16, content_type: &str, body: &[u8]) -> Vec<u8> {
+ response_with_extra_headers(status, content_type, body, "")
+}
+
+pub(crate) fn response_with_cache(
+ status: u16,
+ content_type: &str,
+ body: &[u8],
+ cache_seconds: u64,
+) -> Vec<u8> {
+ response_with_extra_headers_and_length(
+ status,
+ content_type,
+ body,
+ &format!("Cache-Control: public, max-age={cache_seconds}\r\n"),
+ body.len(),
+ )
+}
+
+pub(crate) fn response_with_cache_and_length(
+ status: u16,
+ content_type: &str,
+ body: &[u8],
+ cache_seconds: u64,
+ content_length: usize,
+) -> Vec<u8> {
+ response_with_extra_headers_and_length(
+ status,
+ content_type,
+ body,
+ &format!("Cache-Control: public, max-age={cache_seconds}\r\n"),
+ content_length,
+ )
+}
+
+pub(crate) fn response_with_no_store(status: u16, content_type: &str, body: &[u8]) -> Vec<u8> {
+ response_with_no_store_and_length(status, content_type, body, body.len())
+}
+
+pub(crate) fn response_with_no_store_and_length(
+ status: u16,
+ content_type: &str,
+ body: &[u8],
+ content_length: usize,
+) -> Vec<u8> {
+ response_with_extra_headers_and_length(
+ status,
+ content_type,
+ body,
+ "Cache-Control: no-store, no-cache, must-revalidate\r\n",
+ content_length,
+ )
+}
+
+pub(crate) fn response_with_extra_headers(
+ status: u16,
+ content_type: &str,
+ body: &[u8],
+ extra_headers: &str,
+) -> Vec<u8> {
+ response_with_extra_headers_and_length(status, content_type, body, extra_headers, body.len())
+}
+
+pub(crate) fn response_with_extra_headers_and_length(
+ status: u16,
+ content_type: &str,
+ body: &[u8],
+ extra_headers: &str,
+ content_length: usize,
+) -> Vec<u8> {
+ assert!(
+ body.is_empty() || content_length == body.len(),
+ "response body length must match Content-Length unless body is empty"
+ );
+ let reason = match status {
+ 200 => "OK",
+ 204 => "No Content",
+ 400 => "Bad Request",
... diff truncated: showing 800 of 2687 linesYou can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit db6705f. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5d2843d74f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Summary
maestro-control-planepackage that serves the web control plane, static UI assets, status/model/chat endpoints, and common local UI APIsVerification
cargo fmt --checkcargo check --bin maestro-control-planecargo test --bin maestro-control-planecargo clippy --bin maestro-control-plane -- -D warningsdocker build -t maestro-rust-control-plane-migrate .Source provenance
Notes
maestro-tuibecauseNativeAgentcurrently lives there; extracting an agent-core crate is the next build-size reduction.