feat: local dev agents — project detection, agent team, dev server preview#3018
feat: local dev agents — project detection, agent team, dev server preview#3018
Conversation
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsSuggested: Minor ( React with an emoji to override the release type:
Current version:
|
There was a problem hiding this comment.
10 issues found across 30 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/views/settings/org-ai-providers.tsx">
<violation number="1" location="apps/mesh/src/web/views/settings/org-ai-providers.tsx:543">
P2: `onActivated` runs before refreshing virtual MCP data, so hub-agent navigation can race against stale cache and miss the newly created project agent.</violation>
</file>
<file name="apps/mesh/src/project/bootstrap.ts">
<violation number="1" location="apps/mesh/src/project/bootstrap.ts:70">
P1: Agent deduplication is organization-wide instead of project-scoped, so bootstrapping one project can suppress agent creation for another project in the same org.</violation>
</file>
<file name="apps/mesh/src/project/scanner.ts">
<violation number="1" location="apps/mesh/src/project/scanner.ts:106">
P2: `detectDeployTarget` misses Deno Deploy configs in `deno.jsonc`, causing false negatives for deploy target detection.</violation>
</file>
<file name="apps/mesh/src/web/hooks/use-pinned-agents.ts">
<violation number="1" location="apps/mesh/src/web/hooks/use-pinned-agents.ts:16">
P2: Reset the sync-tracking ref when the storage key changes; otherwise old org/user IDs can block syncing in the new context.</violation>
</file>
<file name="apps/mesh/src/project/dev-server.ts">
<violation number="1" location="apps/mesh/src/project/dev-server.ts:73">
P2: Return a cloned `logs` array to avoid exposing mutable internal state through `getDevServerState()`.</violation>
<violation number="2" location="apps/mesh/src/project/dev-server.ts:126">
P1: Guard the `exited` callback so only the currently active subprocess can update global dev-server state.</violation>
</file>
<file name="apps/mesh/src/api/routes/project.ts">
<violation number="1" location="apps/mesh/src/api/routes/project.ts:106">
P1: The preview proxy can be escaped to arbitrary hosts via `//`-prefixed paths, creating an open proxy/SSRF vector.</violation>
<violation number="2" location="apps/mesh/src/api/routes/project.ts:114">
P1: Do not forward authentication/session headers to the proxied dev server; this leaks sensitive credentials across origins.</violation>
</file>
<file name="apps/mesh/src/web/components/project-setup-sequence.tsx">
<violation number="1" location="apps/mesh/src/web/components/project-setup-sequence.tsx:206">
P2: This render-time `setTimeout` starts side effects before commit, which can run multiple times on render retries and duplicate the setup sequence.</violation>
</file>
<file name="apps/mesh/src/api/app.ts">
<violation number="1" location="apps/mesh/src/api/app.ts:570">
P1: `/api/project` is mounted as a public route, exposing unauthenticated dev-server start/stop/restart and preview-proxy endpoints.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| typeof agent.metadata === "string" | ||
| ? JSON.parse(agent.metadata) | ||
| : agent.metadata; | ||
| if (meta.projectAgentType) { |
There was a problem hiding this comment.
P1: Agent deduplication is organization-wide instead of project-scoped, so bootstrapping one project can suppress agent creation for another project in the same org.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/project/bootstrap.ts, line 70:
<comment>Agent deduplication is organization-wide instead of project-scoped, so bootstrapping one project can suppress agent creation for another project in the same org.</comment>
<file context>
@@ -0,0 +1,131 @@
+ typeof agent.metadata === "string"
+ ? JSON.parse(agent.metadata)
+ : agent.metadata;
+ if (meta.projectAgentType) {
+ existingTypes.add(meta.projectAgentType);
+ agentIds.push(agent.id);
</file context>
| if (child.stderr) pipeOutput(child.stderr as ReadableStream<Uint8Array>); | ||
|
|
||
| // Monitor process exit | ||
| child.exited.then((code) => { |
There was a problem hiding this comment.
P1: Guard the exited callback so only the currently active subprocess can update global dev-server state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/project/dev-server.ts, line 126:
<comment>Guard the `exited` callback so only the currently active subprocess can update global dev-server state.</comment>
<file context>
@@ -0,0 +1,189 @@
+ if (child.stderr) pipeOutput(child.stderr as ReadableStream<Uint8Array>);
+
+ // Monitor process exit
+ child.exited.then((code) => {
+ if (_state.status === "running" || _state.status === "starting") {
+ if (code === 0 || code === null) {
</file context>
| targetUrl.search = reqUrl.search; | ||
|
|
||
| try { | ||
| const headers = new Headers(c.req.raw.headers); |
There was a problem hiding this comment.
P1: Do not forward authentication/session headers to the proxied dev server; this leaks sensitive credentials across origins.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/project.ts, line 114:
<comment>Do not forward authentication/session headers to the proxied dev server; this leaks sensitive credentials across origins.</comment>
<file context>
@@ -0,0 +1,143 @@
+ targetUrl.search = reqUrl.search;
+
+ try {
+ const headers = new Headers(c.req.raw.headers);
+ // Remove host header to avoid conflicts
+ headers.delete("host");
</file context>
| } | ||
|
|
||
| // Reconstruct the target URL from the wildcard path | ||
| const path = c.req.path.replace(/^\/api\/project\/preview\/?/, "/"); |
There was a problem hiding this comment.
P1: The preview proxy can be escaped to arbitrary hosts via //-prefixed paths, creating an open proxy/SSRF vector.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/routes/project.ts, line 106:
<comment>The preview proxy can be escaped to arbitrary hosts via `//`-prefixed paths, creating an open proxy/SSRF vector.</comment>
<file context>
@@ -0,0 +1,143 @@
+ }
+
+ // Reconstruct the target URL from the wildcard path
+ const path = c.req.path.replace(/^\/api\/project\/preview\/?/, "/");
+ const targetUrl = new URL(path || "/", devServer.url);
+
</file context>
| // Public Configuration (no auth required) | ||
| // ============================================================================ | ||
| app.route("/api/config", publicConfigRoutes); | ||
| app.route("/api/project", projectRoutes); |
There was a problem hiding this comment.
P1: /api/project is mounted as a public route, exposing unauthenticated dev-server start/stop/restart and preview-proxy endpoints.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/api/app.ts, line 570:
<comment>`/api/project` is mounted as a public route, exposing unauthenticated dev-server start/stop/restart and preview-proxy endpoints.</comment>
<file context>
@@ -566,6 +567,7 @@ export async function createApp(options: CreateAppOptions = {}) {
// Public Configuration (no auth required)
// ============================================================================
app.route("/api/config", publicConfigRoutes);
+ app.route("/api/project", projectRoutes);
// ============================================================================
</file context>
| if (await fileExists(join(dir, "wrangler.jsonc"))) return "cloudflare"; | ||
|
|
||
| // Check deno.json for deploy config | ||
| const denoJson = await readJson(join(dir, "deno.json")); |
There was a problem hiding this comment.
P2: detectDeployTarget misses Deno Deploy configs in deno.jsonc, causing false negatives for deploy target detection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/project/scanner.ts, line 106:
<comment>`detectDeployTarget` misses Deno Deploy configs in `deno.jsonc`, causing false negatives for deploy target detection.</comment>
<file context>
@@ -0,0 +1,295 @@
+ if (await fileExists(join(dir, "wrangler.jsonc"))) return "cloudflare";
+
+ // Check deno.json for deploy config
+ const denoJson = await readJson(join(dir, "deno.json"));
+ if (denoJson?.deploy) return "deno-deploy";
+
</file context>
| ); | ||
|
|
||
| // Track which server IDs we've already synced to avoid re-running | ||
| const syncedRef = useRef<Set<string>>(new Set(pinnedIds)); |
There was a problem hiding this comment.
P2: Reset the sync-tracking ref when the storage key changes; otherwise old org/user IDs can block syncing in the new context.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/web/hooks/use-pinned-agents.ts, line 16:
<comment>Reset the sync-tracking ref when the storage key changes; otherwise old org/user IDs can block syncing in the new context.</comment>
<file context>
@@ -1,16 +1,37 @@
);
+ // Track which server IDs we've already synced to avoid re-running
+ const syncedRef = useRef<Set<string>>(new Set(pinnedIds));
+
+ // Sync new server-pinned IDs into the local list.
</file context>
| const syncedRef = useRef<Set<string>>(new Set(pinnedIds)); | |
| const syncedRef = useRef<Set<string>>(new Set(pinnedIds)); | |
| const syncedStorageKeyRef = useRef(storageKey); | |
| if (syncedStorageKeyRef.current !== storageKey) { | |
| syncedStorageKeyRef.current = storageKey; | |
| syncedRef.current = new Set(pinnedIds); | |
| } |
| } | ||
|
|
||
| export function getDevServerState(): DevServerState { | ||
| return { ..._state }; |
There was a problem hiding this comment.
P2: Return a cloned logs array to avoid exposing mutable internal state through getDevServerState().
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/project/dev-server.ts, line 73:
<comment>Return a cloned `logs` array to avoid exposing mutable internal state through `getDevServerState()`.</comment>
<file context>
@@ -0,0 +1,189 @@
+}
+
+export function getDevServerState(): DevServerState {
+ return { ..._state };
+}
+
</file context>
| return { ..._state }; | |
| return { ..._state, logs: [..._state.logs] }; |
…eam, start dev server When running `bunx decocms` from a project directory, the system now: - Detects the project stack (Next.js, Fresh, Astro, Vite, Remix, Nuxt, etc.) - Shows project name and path in the topbar with dev server status - Auto-creates a team of specialized agents (Overview, Dev Server, Dependencies, Performance, Framework Expert, Deploy) - Starts the project's dev server and shows a live preview in an iframe - Groups Claude Code / Codex as "Local models" with a highlighted card on the provider selection screen - Improves onboarding copy: "decocms is ready for agents" instead of "No model provider connected" - Fixes sidebar agent pinning to sync new server-pinned agents into localStorage New files: project/scanner.ts, project/agent-templates.ts, project/bootstrap.ts, project/dev-server.ts, project/state.ts, api/routes/project.ts, web/components/preview-panel.tsx, topbar-project-info.tsx, use-project-info.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
e3fbc41 to
4c4b348
Compare
Detects blog/, content/, docs/ directories during project scanning. When found, creates an Article Writer agent with instructions tailored to the project's content structure (frontmatter schema, tone of voice, content pipeline with briefs/drafts/published workflow). The agent uses Claude Code for actual file editing — no separate MCP server needed. The dev server preview shows rendered articles in real-time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/project/agent-templates.ts">
<violation number="1" location="apps/mesh/src/project/agent-templates.ts:371">
P2: The writer agent is activated for `docs`-only projects even though its instructions only handle blog/content directories, which produces incorrect guidance.</violation>
</file>
<file name="apps/mesh/src/project/scanner.ts">
<violation number="1" location="apps/mesh/src/project/scanner.ts:299">
P2: Check that each content candidate is a directory, not just an existing path, before adding it to `contentDirs`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| - Tags are lowercase strings | ||
| - Slugs are kebab-case`; | ||
| }, | ||
| applicableWhen: (scan) => scan.contentDirs.length > 0, |
There was a problem hiding this comment.
P2: The writer agent is activated for docs-only projects even though its instructions only handle blog/content directories, which produces incorrect guidance.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/project/agent-templates.ts, line 371:
<comment>The writer agent is activated for `docs`-only projects even though its instructions only handle blog/content directories, which produces incorrect guidance.</comment>
<file context>
@@ -284,4 +284,90 @@ When the user asks to deploy, guide them through the process step by step.`;
+- Tags are lowercase strings
+- Slugs are kebab-case`;
+ },
+ applicableWhen: (scan) => scan.contentDirs.length > 0,
+ },
];
</file context>
| applicableWhen: (scan) => scan.contentDirs.length > 0, | |
| applicableWhen: (scan) => | |
| scan.contentDirs.some( | |
| (d) => d.type === "blog" || d.type === "content", | |
| ), |
| { dir: "docs", type: "docs" }, | ||
| ]; | ||
| for (const candidate of contentCandidates) { | ||
| if (existsSync(join(projectDir, candidate.dir))) { |
There was a problem hiding this comment.
P2: Check that each content candidate is a directory, not just an existing path, before adding it to contentDirs.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/project/scanner.ts, line 299:
<comment>Check that each content candidate is a directory, not just an existing path, before adding it to `contentDirs`.</comment>
<file context>
@@ -280,6 +288,28 @@ export async function scanProject(
+ { dir: "docs", type: "docs" },
+ ];
+ for (const candidate of contentCandidates) {
+ if (existsSync(join(projectDir, candidate.dir))) {
+ const configFile = (await fileExists(
+ join(projectDir, candidate.dir, "config.json"),
</file context>
Summary
One command to optimize your site with AI. Run
bunx decocmsin any project directory → choose Claude Code → a team of AI agents starts working on your project.What it does
WIP / Next steps
Test plan
bun run apps/mesh/src/cli.ts dev --project ~/Projects/decocmsfrom worktree rootGET /api/projectreturns scan result and dev server state🤖 Generated with Claude Code
Summary by cubic
Adds local dev agents that auto-detect your project, spin up a specialist team, manage your dev server, and show a live preview in-app. Introduces
/api/projectendpoints, a new--projectCLI flag for explicit directory selection, and an Article Writer agent for content-rich projects.New Features
projectDiris set.blog/,content/, ordocs/is present; writes/edits markdown with frontmatter and tone-of-voice guidance; live preview via the dev server./api/project/preview/*strips frame-blocking headers./api/project(scan + state),/api/project/dev-server(start/stop/restart/logs), lightweight polling for dev server state, andGET /api/confignow includesprojectDirandprojectName.--projectflag and CWD auto-detection; improved provider selection with “Local models”; clearer empty states; project agents prioritized in the sidebar; onboarding setup sequence that plays after provider activation and navigates to the Dev Server agent.Bug Fixes
Written for commit 0f6212a. Summary will update on new commits.