Compare commits
3 Commits
v2.0-propo
...
v2.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 006226cce5 | |||
| 62d818af23 | |||
| 531d39ace9 |
41
BOOCODER.md
41
BOOCODER.md
@@ -1,27 +1,32 @@
|
||||
# BooCoder
|
||||
# BooCoder — Container Guidance
|
||||
|
||||
> (Stub. v2.0 implementation pending. This file documents the intended contract.)
|
||||
You are BooCoder, a write-capable coding agent. You can read AND modify files within the project scope.
|
||||
|
||||
## Capabilities
|
||||
## You can
|
||||
|
||||
- Everything in `BOOCHAT.md`
|
||||
- Write tools (pending): `write_file`, `edit_file`, `delete_file` (all gated through pending-changes sandbox)
|
||||
- Shell (pending): `run_command` (Docker-isolated per-session)
|
||||
- Read files (view_file, list_dir, grep, find_files)
|
||||
- Edit files (edit_file, create_file, delete_file) — all changes queue in pending_changes
|
||||
- Apply pending changes to disk (apply_pending)
|
||||
- Revert applied changes (rewind)
|
||||
- Dispatch tasks to external agents (dispatch_external_agent)
|
||||
- Use MCP tools from configured servers
|
||||
|
||||
## Constraints
|
||||
## You cannot
|
||||
|
||||
- All writes land in a pending-changes virtual layer; nothing touches the real filesystem until `/apply`
|
||||
- `run_command` executes inside the session sandbox, not the host
|
||||
- No git commits, pushes, or pulls — Sam owns those
|
||||
- Stop and ask before destructive operations (delete, overwrite, recreate)
|
||||
- Write outside the project root (path-guard enforced)
|
||||
- Write to secret files (.env, *.pem, id_rsa*, credentials.json)
|
||||
- Apply changes without explicit user approval (unless auto-apply is enabled per task)
|
||||
- Push to git remotes
|
||||
- Access the internet except via configured MCP servers
|
||||
|
||||
## Pending changes discipline
|
||||
|
||||
Every file modification queues in `pending_changes` before touching disk. The user sees a diff preview and approves/rejects each change. Never bypass this queue — it is the safety boundary between inference and the filesystem.
|
||||
|
||||
## Behavior
|
||||
|
||||
- Show a diff preview before any write
|
||||
- Group related edits into a single `/apply` batch
|
||||
- If a tool fails, surface the error verbatim — don't paper over it
|
||||
- Show diffs clearly. Explain what you're changing and why.
|
||||
- For multi-file changes, organize as a logical unit (one task = one coherent change set).
|
||||
- If uncertain about scope, use smaller edits and verify between steps.
|
||||
- Cite file paths + line numbers for context.
|
||||
- Verify before reporting work complete: run the relevant test/build/smoke and confirm output matches the claim. Evidence first, assertion second.
|
||||
|
||||
## Convention: rules vs recipes
|
||||
|
||||
Always-true rules live here, in `BOOCHAT.md`, and in `CLAUDE.md` (100% present each turn). On-demand recipes live in `/data/skills/` (roughly 6% invoke rate in multi-turn per Codeminer42, 2026). Don't file workflow rules as skills — they misfire. See Anthropic agent-skills best-practices (platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices).
|
||||
|
||||
28
apps/coder/Dockerfile
Normal file
28
apps/coder/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM node:20-alpine AS builder
|
||||
RUN corepack enable
|
||||
WORKDIR /build
|
||||
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml tsconfig.base.json ./
|
||||
COPY apps/coder/package.json ./apps/coder/
|
||||
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
COPY apps/coder ./apps/coder
|
||||
|
||||
RUN pnpm -C apps/coder build
|
||||
|
||||
RUN pnpm deploy --filter=@boocode/coder --prod --legacy /out/coder
|
||||
|
||||
|
||||
FROM node:20-bookworm-slim AS runtime
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends ripgrep git && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /out/coder ./
|
||||
|
||||
ENV NODE_ENV=production
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["node", "dist/index.js"]
|
||||
25
apps/coder/package.json
Normal file
25
apps/coder/package.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@boocode/coder",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"build": "tsc && node -e \"import('node:fs').then(fs=>fs.copyFileSync('src/schema.sql','dist/schema.sql'))\"",
|
||||
"start": "node dist/index.js",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/static": "^7.0.4",
|
||||
"@fastify/websocket": "^10.0.1",
|
||||
"fastify": "^4.28.1",
|
||||
"postgres": "^3.4.4",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.10",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.5.0"
|
||||
}
|
||||
}
|
||||
28
apps/coder/src/config.ts
Normal file
28
apps/coder/src/config.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const ConfigSchema = z.object({
|
||||
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
|
||||
PORT: z.coerce.number().int().positive().default(3000),
|
||||
HOST: z.string().default('0.0.0.0'),
|
||||
DATABASE_URL: z.string().url(),
|
||||
LLAMA_SWAP_URL: z.string().url(),
|
||||
PROJECT_ROOT_WHITELIST: z.string().default('/opt'),
|
||||
LOG_LEVEL: z.string().default('info'),
|
||||
CONTAINER_GUIDANCE_FILE: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Config = z.infer<typeof ConfigSchema>;
|
||||
|
||||
let cached: Config | null = null;
|
||||
|
||||
export function loadConfig(): Config {
|
||||
if (cached) return cached;
|
||||
const parsed = ConfigSchema.safeParse(process.env);
|
||||
if (!parsed.success) {
|
||||
console.error('Invalid environment configuration:');
|
||||
console.error(parsed.error.flatten().fieldErrors);
|
||||
process.exit(1);
|
||||
}
|
||||
cached = parsed.data;
|
||||
return cached;
|
||||
}
|
||||
45
apps/coder/src/db.ts
Normal file
45
apps/coder/src/db.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import postgres from 'postgres';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import type { Config } from './config.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
export type Sql = ReturnType<typeof postgres>;
|
||||
|
||||
let sqlInstance: Sql | null = null;
|
||||
|
||||
export function getSql(config: Config): Sql {
|
||||
if (sqlInstance) return sqlInstance;
|
||||
sqlInstance = postgres(config.DATABASE_URL, {
|
||||
max: 10,
|
||||
idle_timeout: 30,
|
||||
connect_timeout: 10,
|
||||
onnotice: () => {},
|
||||
});
|
||||
return sqlInstance;
|
||||
}
|
||||
|
||||
export async function applySchema(sql: Sql): Promise<void> {
|
||||
const schemaPath = resolve(__dirname, 'schema.sql');
|
||||
const ddl = await readFile(schemaPath, 'utf8');
|
||||
await sql.unsafe(ddl);
|
||||
}
|
||||
|
||||
export async function pingDb(sql: Sql): Promise<boolean> {
|
||||
try {
|
||||
await sql`SELECT 1`;
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeDb(): Promise<void> {
|
||||
if (sqlInstance) {
|
||||
await sqlInstance.end({ timeout: 5 });
|
||||
sqlInstance = null;
|
||||
}
|
||||
}
|
||||
55
apps/coder/src/index.ts
Normal file
55
apps/coder/src/index.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import Fastify from 'fastify';
|
||||
import { loadConfig } from './config.js';
|
||||
import { getSql, applySchema, pingDb, closeDb } from './db.js';
|
||||
|
||||
async function main() {
|
||||
const config = loadConfig();
|
||||
|
||||
const app = Fastify({
|
||||
logger: { level: config.LOG_LEVEL },
|
||||
});
|
||||
|
||||
// Allow empty JSON bodies (same pattern as apps/server).
|
||||
app.removeContentTypeParser(['application/json']);
|
||||
app.addContentTypeParser('application/json', { parseAs: 'string' }, (_req, body, done) => {
|
||||
const str = (body as string) ?? '';
|
||||
if (str.trim().length === 0) {
|
||||
done(null, {});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
done(null, JSON.parse(str));
|
||||
} catch (err) {
|
||||
done(err as Error, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
const sql = getSql(config);
|
||||
await applySchema(sql);
|
||||
app.log.info('database schema applied');
|
||||
|
||||
// Health endpoint
|
||||
app.get('/api/health', async (_req, reply) => {
|
||||
const dbOk = await pingDb(sql);
|
||||
const status = dbOk ? 200 : 503;
|
||||
return reply.status(status).send({ ok: dbOk, db: dbOk });
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
const shutdown = async () => {
|
||||
app.log.info('shutting down');
|
||||
await app.close();
|
||||
await closeDb();
|
||||
process.exit(0);
|
||||
};
|
||||
process.on('SIGTERM', shutdown);
|
||||
process.on('SIGINT', shutdown);
|
||||
|
||||
await app.listen({ port: config.PORT, host: config.HOST });
|
||||
app.log.info(`BooCoder listening on ${config.HOST}:${config.PORT}`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('fatal:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
48
apps/coder/src/schema.sql
Normal file
48
apps/coder/src/schema.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- v2.0.0: BooCoder schema — pending changes, tasks, agent registry.
|
||||
-- Applied on startup by apps/coder/src/db.ts:applySchema().
|
||||
-- Lives in the same 'boochat' database as BooChat's tables.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pending_changes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
session_id UUID NOT NULL,
|
||||
task_id UUID,
|
||||
file_path TEXT NOT NULL,
|
||||
operation TEXT NOT NULL,
|
||||
diff TEXT NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT pending_changes_operation_chk CHECK (operation IN ('create', 'edit', 'delete')),
|
||||
CONSTRAINT pending_changes_status_chk CHECK (status IN ('pending', 'applied', 'rejected', 'reverted'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
project_id UUID NOT NULL,
|
||||
parent_task_id UUID REFERENCES tasks(id),
|
||||
state TEXT NOT NULL DEFAULT 'pending',
|
||||
input TEXT NOT NULL,
|
||||
output_summary TEXT,
|
||||
agent TEXT,
|
||||
model TEXT,
|
||||
execution_path TEXT,
|
||||
worktree_path TEXT,
|
||||
cost_tokens INTEGER,
|
||||
started_at TIMESTAMPTZ,
|
||||
ended_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT tasks_state_chk CHECK (state IN ('pending', 'running', 'completed', 'failed', 'blocked', 'cancelled')),
|
||||
CONSTRAINT tasks_execution_path_chk CHECK (execution_path IS NULL OR execution_path IN ('native', 'acp', 'pty'))
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS available_agents (
|
||||
name TEXT PRIMARY KEY,
|
||||
install_path TEXT,
|
||||
version TEXT,
|
||||
supports_acp BOOLEAN NOT NULL DEFAULT false,
|
||||
supports_mcp_client BOOLEAN NOT NULL DEFAULT false,
|
||||
last_probed_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Human inbox: tasks needing attention
|
||||
CREATE OR REPLACE VIEW human_inbox AS
|
||||
SELECT * FROM tasks WHERE state IN ('blocked', 'failed');
|
||||
15
apps/coder/tsconfig.json
Normal file
15
apps/coder/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["node"],
|
||||
"declaration": false,
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["src/**/__tests__/**", "**/*.test.ts"]
|
||||
}
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
environment:
|
||||
CODECONTEXT_URL: http://codecontext:8080
|
||||
CONTAINER_GUIDANCE_FILE: /app/BOOCHAT.md
|
||||
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boocode
|
||||
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boochat
|
||||
volumes:
|
||||
- /opt:/opt
|
||||
- /opt/projects:/opt/projects:rw
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3000
|
||||
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boocode
|
||||
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boochat
|
||||
volumes:
|
||||
- /opt:/opt:rw
|
||||
- /home/samkintop:/home/samkintop:rw
|
||||
@@ -50,6 +50,28 @@ services:
|
||||
networks:
|
||||
- boocode_net
|
||||
|
||||
boocoder:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/coder/Dockerfile
|
||||
container_name: boocoder
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "100.114.205.53:9502:3000"
|
||||
env_file: .env
|
||||
environment:
|
||||
CONTAINER_GUIDANCE_FILE: /app/BOOCODER.md
|
||||
DATABASE_URL: postgres://boocode:${POSTGRES_PASSWORD}@boocode_db:5432/boochat
|
||||
volumes:
|
||||
- /opt:/opt:rw
|
||||
- /opt/projects:/opt/projects:rw
|
||||
- ./data:/data
|
||||
- /opt/boocode/BOOCODER.md:/app/BOOCODER.md:ro
|
||||
depends_on:
|
||||
- boocode_db
|
||||
networks:
|
||||
- boocode_net
|
||||
|
||||
boocode_db:
|
||||
image: postgres:16-alpine
|
||||
container_name: boocode_db
|
||||
@@ -57,7 +79,7 @@ services:
|
||||
environment:
|
||||
POSTGRES_USER: boocode
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_DB: boocode
|
||||
POSTGRES_DB: boochat
|
||||
ports:
|
||||
- "127.0.0.1:5500:5432"
|
||||
volumes:
|
||||
|
||||
413
openspec/changes/v2.0-boocoder/implementation-plan.md
Normal file
413
openspec/changes/v2.0-boocoder/implementation-plan.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# v2.0 BooCoder — Implementation Plan
|
||||
|
||||
Ordered execution plan across all 4 sub-versions. Each phase is dispatchable as a single batch. Phases 1-4 are sequential (each builds on the prior); phases within a sub-version can sometimes be parallelized.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Foundation (v2.0.0-alpha)
|
||||
|
||||
**Goal:** Standalone BooCoder container boots, connects to DB, serves a health endpoint. No inference yet.
|
||||
|
||||
**Estimated:** ~200 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Clone lift sources** (prep, no code)
|
||||
- `cd /opt/forks && git clone agent-hub, plandex, opencode, qodo-ai/agents`
|
||||
- Read agent-hub schema, plandex pending-changes, opencode permission/evaluate.ts
|
||||
- Read RA.Aid README for three-stage pattern
|
||||
|
||||
2. **Create `apps/coder/` skeleton**
|
||||
- `apps/coder/package.json` (Fastify, postgres, zod — same deps as `apps/server`)
|
||||
- `apps/coder/tsconfig.json` (extends base, NodeNext)
|
||||
- `apps/coder/src/index.ts` (Fastify boot, health endpoint, DB connect)
|
||||
- `apps/coder/src/config.ts` (Zod config schema — DATABASE_URL, PORT, HOST, LLAMA_SWAP_URL, CONTAINER_GUIDANCE_FILE)
|
||||
- `apps/coder/src/db.ts` (postgres connection, schema apply — shared with `apps/server` or fresh)
|
||||
|
||||
3. **Create Dockerfile**
|
||||
- `apps/coder/Dockerfile` — Node 20 bookworm-slim (matches booterm for glibc compat with node-pty later)
|
||||
- Mount: `/opt:/opt:rw`
|
||||
- COPY built server + BOOCODER.md
|
||||
|
||||
4. **docker-compose.yml** — add `boocoder` service
|
||||
- Port `100.114.205.53:9502:3000`
|
||||
- Environment: `DATABASE_URL`, `LLAMA_SWAP_URL`, `CONTAINER_GUIDANCE_FILE=/app/BOOCODER.md`
|
||||
- Network: `boocode_net`
|
||||
- Depends on: `boocode_db`
|
||||
|
||||
5. **DB rename** — `boocode_db` → `boochat_db`
|
||||
- `ALTER DATABASE boocode RENAME TO boochat;` (one-time, run manually)
|
||||
- Update `DATABASE_URL` in all docker-compose services
|
||||
- Update volume name mapping
|
||||
- Verify all 3 services boot against renamed DB
|
||||
|
||||
6. **Schema migration** — new tables in `apps/coder/src/schema.sql`
|
||||
- `pending_changes` table
|
||||
- `tasks` table
|
||||
- `available_agents` table
|
||||
- `human_inbox` view
|
||||
- Applied idempotently on boot (same pattern as BooChat's `applySchema()`)
|
||||
|
||||
7. **BOOCODER.md** — container guidance file
|
||||
- Write tools enabled (unlike BOOCHAT.md which declares read-only)
|
||||
- Pending-changes queue discipline
|
||||
- Path-guard rules
|
||||
|
||||
### Verification
|
||||
- `docker compose up --build -d` — boocoder container starts
|
||||
- `curl http://100.114.205.53:9502/api/health` — 200 OK
|
||||
- `psql` confirms new tables exist
|
||||
- BooChat + BooTerm unaffected (still boot, still serve)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Write Tools + Pending Changes (v2.0.0-beta)
|
||||
|
||||
**Goal:** BooCoder can chat with the LLM, the LLM can call write tools, changes queue in `pending_changes`, user can apply/reject.
|
||||
|
||||
**Estimated:** ~400 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Write-path guard** (`apps/coder/src/services/write_guard.ts`)
|
||||
- `resolveWritePath(projectRoot, filePath): string` — `resolve()` + prefix check (no realpath — file may not exist for creates)
|
||||
- Deny list: inherit from BooChat's `secret_guard.ts` (`.env`, `*.pem`, `id_rsa*`, etc.)
|
||||
- Fuzz tests: `../` escape, symlink outside root, null bytes, non-existent parent dirs
|
||||
|
||||
2. **Pending-changes service** (`apps/coder/src/services/pending_changes.ts`)
|
||||
- `queueEdit(session_id, task_id, file_path, old_string, new_string): PendingChange` — computes unified diff, validates write path, INSERTs
|
||||
- `queueCreate(session_id, task_id, file_path, content): PendingChange`
|
||||
- `queueDelete(session_id, task_id, file_path): PendingChange`
|
||||
- `applyAll(session_id): ApplyResult[]` — re-validates each path, writes to disk, marks `status='applied'`
|
||||
- `applyOne(change_id): ApplyResult`
|
||||
- `rejectOne(change_id): void` — marks `status='rejected'`
|
||||
- `rejectAll(session_id): void`
|
||||
- `rewindOne(change_id): void` — inverse-diff, writes to disk, marks `status='reverted'`
|
||||
- `listPending(session_id): PendingChange[]`
|
||||
|
||||
3. **Write tools** (`apps/coder/src/services/tools/`)
|
||||
- `edit_file.ts` — input: `{file_path, old_string, new_string}`, calls `queueEdit`
|
||||
- `create_file.ts` — input: `{file_path, content}`, calls `queueCreate`
|
||||
- `delete_file.ts` — input: `{file_path}`, calls `queueDelete`
|
||||
- `apply_pending.ts` — calls `applyAll` for current session
|
||||
- `rewind.ts` — input: `{change_id}` or `{all: true}`, calls `rewindOne`/`rewindAll`
|
||||
|
||||
4. **Tool registry** — register write tools alongside ALL read tools from BooChat
|
||||
- Import BooChat's read tools (view_file, grep, etc.) + codecontext tools
|
||||
- Add the 5 write tools
|
||||
- Alpha-sort the combined list
|
||||
|
||||
5. **Inference loop** — port from BooChat or share via workspace package
|
||||
- Copy `apps/server/src/services/inference/` into `apps/coder/src/services/inference/` (or symlink via pnpm workspace)
|
||||
- The outer loop (v1.14) runs unchanged — write tools are just ToolDefs with `execute()` functions
|
||||
- Compaction, doom-loop, step cap all carry forward
|
||||
|
||||
6. **API routes**
|
||||
- `POST /api/sessions/:id/messages` — same as BooChat (creates user + assistant rows, enqueues inference)
|
||||
- `GET /api/sessions/:id/pending` — returns pending changes for the session
|
||||
- `POST /api/sessions/:id/pending/apply` — applies all pending
|
||||
- `POST /api/pending/:id/apply` — applies one
|
||||
- `POST /api/pending/:id/reject` — rejects one
|
||||
- `POST /api/pending/:id/rewind` — reverts one
|
||||
- WebSocket streaming (same protocol as BooChat)
|
||||
|
||||
### Verification
|
||||
- Send a chat asking BooCoder to edit a file
|
||||
- LLM calls `edit_file` → change queued in `pending_changes`
|
||||
- `GET /api/sessions/:id/pending` shows the queued change with diff
|
||||
- `POST /api/pending/:id/apply` writes to disk
|
||||
- `POST /api/pending/:id/rewind` reverts it
|
||||
- Fuzz test: attempt traversal via `edit_file("../../etc/passwd", ...)` → rejected by write_guard
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Frontend: Diff Pane + Chat (v2.0.0)
|
||||
|
||||
**Goal:** Browser UI at `coder.indifferentketchup.com` with chat pane + diff pane side by side.
|
||||
|
||||
**Estimated:** ~200 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Create `apps/coder/web/`** — React + Vite SPA (same stack as BooChat's `apps/web/`)
|
||||
- Copy BooChat's Vite config, Tailwind v4 setup, font pipeline
|
||||
- Shared components: `MarkdownRenderer`, `CodeBlock`, `Button`, `Input`
|
||||
- New app shell: sidebar (sessions) + workspace (panes)
|
||||
|
||||
2. **Chat pane** — reuse BooChat's ChatPane/MessageBubble pattern
|
||||
- Same WS streaming, same `useSessionStream` hook, same message rendering
|
||||
- ActionRow includes tool-call rendering for write tools
|
||||
|
||||
3. **Diff pane** — NEW (`apps/coder/web/src/components/DiffPane.tsx`)
|
||||
- Fetches `GET /api/sessions/:id/pending`
|
||||
- Lists pending changes: file path + operation badge (create/edit/delete)
|
||||
- Per-change: syntax-highlighted unified diff view (use Shiki or a diff-specific highlighter)
|
||||
- Buttons: Approve / Reject per change, Approve All / Reject All
|
||||
- Real-time updates via WS frame (`pending_change_added`, `pending_change_applied`, etc.)
|
||||
|
||||
4. **Workspace splitter** — chat left, diff right (or configurable)
|
||||
|
||||
5. **Caddy route** — `coder.indifferentketchup.com` → boocoder:9502
|
||||
- Authelia gating (same as BooChat)
|
||||
|
||||
### Verification
|
||||
- Open `coder.indifferentketchup.com` in browser
|
||||
- Send a message asking for a code change
|
||||
- See the change appear in the diff pane in real time
|
||||
- Click Approve → file written, change marked applied
|
||||
- Click Reject → change discarded
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Dispatcher + Tasks (v2.0.0 final)
|
||||
|
||||
**Goal:** Task queue works. User can create tasks, dispatcher picks them up and runs them through Path A.
|
||||
|
||||
**Estimated:** ~150 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Dispatcher** (`apps/coder/src/services/dispatcher.ts`)
|
||||
- In-process `setInterval(5000)` polling `tasks` WHERE `state='pending'` ORDER BY `created_at`
|
||||
- For each ready task: mark `state='running'`, run inference with the task's `input` as the user message
|
||||
- On completion: mark `state='completed'`
|
||||
- On error: mark `state='failed'`
|
||||
- On abort: mark `state='cancelled'`
|
||||
- Respects `app.addHook('onClose')` — stops polling, waits for in-flight task
|
||||
|
||||
2. **Task API routes**
|
||||
- `POST /api/tasks` — create a task `{project_id, input, agent?, model?}`
|
||||
- `GET /api/tasks` — list tasks (filterable by state, project)
|
||||
- `GET /api/tasks/:id` — get task details + output_summary
|
||||
- `POST /api/tasks/:id/cancel` — cancel a running task
|
||||
|
||||
3. **Task → session linkage**
|
||||
- Each task creates its own session + chat for isolation
|
||||
- Task's pending_changes reference the task_id
|
||||
- When task completes, its pending_changes are visible in the UI for approval
|
||||
|
||||
4. **Agent probing** (`apps/coder/src/services/agent-probe.ts`)
|
||||
- On startup: `which opencode`, `which goose`, `which claude`, `which pi`
|
||||
- Parse version from `<agent> --version`
|
||||
- Check ACP support: `opencode acp --help` exits 0 → supports_acp = true
|
||||
- Populate `available_agents` table
|
||||
|
||||
### Verification
|
||||
- `POST /api/tasks {input: "add a /api/version endpoint"}` → task created
|
||||
- Dispatcher picks it up → inference runs → `edit_file` queued → task completes
|
||||
- `GET /api/tasks/:id` shows `state='completed'` + output_summary
|
||||
- Pending changes visible in diff pane for approval
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — ACP Dispatch (v2.0.1)
|
||||
|
||||
**Goal:** Tasks can be dispatched to external agents via ACP. opencode and goose run as subprocesses, their events flow back into BooCode.
|
||||
|
||||
**Estimated:** ~350 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **ACP client** (`apps/coder/src/services/acp-client.ts`)
|
||||
- Install: `pnpm -C apps/coder add @zed-industries/agent-client-protocol`
|
||||
- `spawnAcpAgent(agent: string, task: string, worktree: string, mcpServers: McpConfig[]): AcpSession`
|
||||
- Uses SDK's `StdioTransport` — spawn `opencode acp` or `goose acp` as child
|
||||
- Pass `context_servers` for MCP auto-forward
|
||||
- Event listener: maps ACP events to BooCode's parts taxonomy
|
||||
|
||||
2. **ACP event mapping**
|
||||
- `file_operation` → queue into `pending_changes` (same as Path A native writes)
|
||||
- `tool_call` / `tool_result` → insert as `message_parts` in the task's session
|
||||
- `terminal_output` → publish as WS frame for BooTerm routing
|
||||
- `permission_request` → pause (same mechanism as `ask_user_input`)
|
||||
- `session_end` → task state → `completed` or `failed`
|
||||
|
||||
3. **Worktree management** (`apps/coder/src/services/worktrees.ts`)
|
||||
- `createWorktree(projectPath, taskId): string` — `git worktree add /tmp/booworktrees/<taskId> -b task-<taskId> HEAD`
|
||||
- `diffWorktree(worktreePath, projectPath): UnifiedDiff[]` — `git diff HEAD...<worktree-branch>`
|
||||
- `cleanupWorktree(worktreePath): void` — `git worktree remove`
|
||||
- On ACP session end: diff the worktree, queue diffs into `pending_changes`, cleanup
|
||||
|
||||
4. **PTY fallback** (`apps/coder/src/services/pty-dispatch.ts`)
|
||||
- For agents without ACP (claude, pi, smallcode)
|
||||
- `spawnPtyAgent(agent: string, task: string, worktree: string): PtySession`
|
||||
- Uses `node-pty` — spawn `claude` or `pi` with cwd = worktree
|
||||
- Capture stdout/stderr into `message_parts` (kind='text', less structured than ACP)
|
||||
- On exit: diff worktree → queue pending_changes → cleanup
|
||||
|
||||
5. **Dispatcher update** — transport selection
|
||||
- Check `available_agents[agent].supports_acp` at dispatch time
|
||||
- ACP-capable → `spawnAcpAgent`
|
||||
- PTY fallback → `spawnPtyAgent`
|
||||
- Native (no agent specified) → Path A inference loop (Phase 4)
|
||||
|
||||
6. **AGENTS.md extensions**
|
||||
- Add `execution_strategy: plan | act | research` field
|
||||
- Add `expert_model` field for cost-routing
|
||||
- Add `output_schema` field (optional JSON Schema for structured final output)
|
||||
|
||||
### Verification
|
||||
- Create task with `agent: 'opencode'` → ACP subprocess spawns
|
||||
- opencode edits files in worktree → events stream into UI
|
||||
- On completion: worktree diff queued in `pending_changes`
|
||||
- Approve → changes applied to main project
|
||||
- Fallback: create task with `agent: 'claude'` → PTY captures output → worktree diff queued
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — MCP Server (v2.0.2)
|
||||
|
||||
**Goal:** BooCoder exposes its own primitives as MCP tools. External opencode sessions in Termius can drive the task queue.
|
||||
|
||||
**Estimated:** ~250 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **MCP server** (`apps/coder/src/services/mcp-server.ts`)
|
||||
- Use `@modelcontextprotocol/sdk` server-side (`Server` class)
|
||||
- Stdio transport (read from stdin, write to stdout)
|
||||
- Entry point: `boocoder --mcp` CLI flag starts the MCP server instead of the HTTP server
|
||||
|
||||
2. **Tool handlers** (6 tools)
|
||||
- `boocoder.create_task` → INSERT into tasks table, return task_id
|
||||
- `boocoder.list_pending_changes` → SELECT from pending_changes WHERE session matches
|
||||
- `boocoder.apply` → call `applyOne(change_id)`
|
||||
- `boocoder.reject` → call `rejectOne(change_id)`
|
||||
- `boocoder.dispatch_external_agent` → create task with agent specified, return task_id
|
||||
- `boocoder.list_worktrees` → list active worktrees from tasks WHERE worktree_path IS NOT NULL AND state='running'
|
||||
|
||||
3. **10-question eval** (per `anthropics/skills/mcp-builder` framework)
|
||||
- Write 10 independent, read-only, verifiable questions about the BooCoder state
|
||||
- Run eval: `echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"boocoder.list_pending_changes","arguments":{}},"id":1}' | boocoder --mcp`
|
||||
- All 10 must return correct answers
|
||||
|
||||
4. **opencode integration test**
|
||||
- Add BooCoder as an MCP server in `~/.opencode/config.json`:
|
||||
```json
|
||||
{"mcpServers": {"boocoder": {"type": "stdio", "command": "boocoder", "args": ["--mcp"]}}}
|
||||
```
|
||||
- From opencode: call `boocoder.create_task` → verify task appears in BooCoder UI
|
||||
|
||||
### Verification
|
||||
- `echo '...' | boocoder --mcp` returns valid MCP responses
|
||||
- 10-question eval passes
|
||||
- opencode can drive BooCoder's task queue via MCP
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — CLI + Polish (v2.0.3)
|
||||
|
||||
**Goal:** `boocode` CLI client, human inbox UI, cost tracking, observation hooks.
|
||||
|
||||
**Estimated:** ~400 LoC
|
||||
|
||||
### Steps
|
||||
|
||||
1. **CLI client** (`apps/coder/src/cli.ts`)
|
||||
- Thin HTTP/WS client against BooCoder API
|
||||
- `boocode run "task description"` → POST /api/tasks → stream output via WS
|
||||
- `boocode ls` → GET /api/tasks → formatted table
|
||||
- `boocode attach <id>` → WS subscribe to task's session → stream live
|
||||
- `boocode send <id> "message"` → POST message to task's session chat
|
||||
- Build as a standalone binary via `pkg` or `esbuild --bundle`
|
||||
|
||||
2. **Human inbox UI** (frontend)
|
||||
- New route: `/inbox` → shows tasks WHERE `state IN ('blocked', 'failed')`
|
||||
- Per-task: view output, retry (reset state to pending), cancel, reassign agent
|
||||
- Badge on sidebar showing count of inbox items
|
||||
|
||||
3. **Cost tracking**
|
||||
- `tasks.cost_tokens` populated from inference `usage` callback (same as BooChat's `tokens_used`)
|
||||
- Summary API: `GET /api/stats/costs?group_by=project|agent|day` → aggregated token spend
|
||||
- Simple UI: cost badge on each task, totals in settings
|
||||
|
||||
4. **Observation hooks** (budi taxonomy)
|
||||
- Emit 5 event types on the BooCoder WS protocol for dispatched agents:
|
||||
- `session_start` — agent spawned
|
||||
- `user_prompt_submit` — task spec delivered
|
||||
- `post_tool_use` — each tool call completed
|
||||
- `subagent_start` — nested dispatch (Boomerang)
|
||||
- `stop` — agent finished
|
||||
- Consumed by frontend for real-time status indicators
|
||||
|
||||
5. **Boomerang `new_task` tool** (subagent isolation)
|
||||
- When an agent's toolset includes `new_task`:
|
||||
- Creates a child task (fresh session, fresh context)
|
||||
- Child runs to completion
|
||||
- Parent gets only `attempt_completion` summary
|
||||
- Orchestrator agent profile: tools = `[new_task, list_tasks, check_task_status]` ONLY
|
||||
|
||||
### Verification
|
||||
- `boocode run "add health endpoint"` from terminal → task runs → output streams → diff queued
|
||||
- `boocode ls` shows task list with states + cost
|
||||
- Inbox shows failed tasks, retry works
|
||||
- Boomerang: orchestrator creates subtask → subtask runs isolated → parent gets summary only
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — Hardening + Ship (v2.0.x)
|
||||
|
||||
**Goal:** Security hardening, integration tests, documentation, production deploy.
|
||||
|
||||
**Estimated:** ~100 LoC (mostly tests + docs)
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Path-guard fuzz suite** — property tests for every traversal pattern:
|
||||
- `../` sequences (all depths)
|
||||
- Symlink outside project root
|
||||
- Null bytes in path
|
||||
- Unicode normalization attacks
|
||||
- Race conditions (TOCTOU between validate + write)
|
||||
- MCP-served filesystem writes routed through pending_changes
|
||||
|
||||
2. **Integration tests**
|
||||
- End-to-end: create task → inference → edit_file → apply → file written → verify content
|
||||
- ACP dispatch: mock opencode → events flow → pending_changes queued
|
||||
- MCP server: 10-question eval automated in CI
|
||||
|
||||
3. **Documentation**
|
||||
- `BOOCODER.md` finalized (container guidance)
|
||||
- `CLAUDE.md` updated with BooCoder architecture section
|
||||
- `boocode_roadmap.md` v2.0 retrospective
|
||||
- `CHANGELOG.md` entries for each sub-version
|
||||
|
||||
4. **Production deploy**
|
||||
- Caddy config: `coder.indifferentketchup.com`
|
||||
- Authelia: same SSO group as BooChat
|
||||
- Smoke: full workflow (chat → edit → approve → verify)
|
||||
|
||||
5. **Tag** — `v2.0.0` (or `v2.0.0-rc1` if Sam wants a bake period)
|
||||
|
||||
---
|
||||
|
||||
## Execution order summary
|
||||
|
||||
```
|
||||
Phase 1 (foundation) → v2.0.0-alpha ~200 LoC container boots
|
||||
Phase 2 (write tools) → v2.0.0-beta ~400 LoC inference + pending_changes
|
||||
Phase 3 (frontend) → v2.0.0 ~200 LoC chat + diff panes
|
||||
Phase 4 (dispatcher) → v2.0.0-final ~150 LoC task queue + native dispatch
|
||||
Phase 5 (ACP dispatch) → v2.0.1 ~350 LoC external agents + worktrees
|
||||
Phase 6 (MCP server) → v2.0.2 ~250 LoC boocoder.* tools + eval
|
||||
Phase 7 (CLI + polish) → v2.0.3 ~400 LoC CLI + inbox + hooks + Boomerang
|
||||
Phase 8 (hardening) → v2.0.x ~100 LoC fuzz + integration tests + docs
|
||||
--------
|
||||
~2050 LoC total
|
||||
```
|
||||
|
||||
Each phase is independently dispatchable. Phases 1-4 are sequential (each needs the prior). Phases 5-7 are parallelizable after Phase 4 ships (they're independent protocol surfaces). Phase 8 gates the production tag.
|
||||
|
||||
---
|
||||
|
||||
## Risk register
|
||||
|
||||
| Risk | Mitigation |
|
||||
|---|---|
|
||||
| Path-guard bypass → arbitrary writes | Pending-changes double-validates (at queue time + apply time). Fuzz suite in Phase 8. OpenHands sandbox (v2.1) as fallback. |
|
||||
| ACP spec instability (remote transport WIP) | Use stdio only. No remote ACP in v2.0. |
|
||||
| node-pty native compilation breaks in Docker | bookworm-slim + glibc matches booterm's working config. Pin node-pty version. |
|
||||
| Worktree cleanup failure → disk bloat | 30-min idle timeout sweeper. `git worktree prune` on startup. |
|
||||
| DB rename breaks existing sessions | One-time migration with explicit backup. BooChat/BooTerm URLs unchanged. |
|
||||
| MCP server eval failure | Ship stdio MCP server only after 10/10 eval passes. |
|
||||
| Boomerang context leak (child leaks state to parent) | Architectural enforcement: child's session_id ≠ parent's. Summary field is the ONLY bridge. |
|
||||
@@ -262,13 +262,85 @@ All dependencies shipped. v2.0 is unblocked.
|
||||
- No OAuth in v2.0. MCP server is stdio-only until secret storage lands.
|
||||
- DB rename `boocode_db` → `boochat_db` lands with v2.0.0 (one-time migration).
|
||||
|
||||
## AGENTS.md extensions (v2.0.0)
|
||||
|
||||
Port from `qodo-ai/agents` (MIT) `agent.toml` schema and `ai-christianson/RA.Aid` (Apache-2.0) three-stage pattern:
|
||||
|
||||
| Field | Type | Purpose | Source |
|
||||
|---|---|---|---|
|
||||
| `steps` | number | Per-agent step cap (already shipped v1.14.0) | opencode |
|
||||
| `output_schema` | JSON Schema | Structured output constraint for the agent's final response | qodo-ai/agents |
|
||||
| `exit_expression` | string | Regex/predicate — when the agent considers itself done | qodo-ai/agents |
|
||||
| `execution_strategy` | `plan` \| `act` \| `research` | Which phase of the RA.Aid three-stage pattern this agent operates in | qodo-ai/agents + RA.Aid |
|
||||
| `model` | string | Per-agent model override (already shipped v1.8) | — |
|
||||
| `expert_model` | string | Escalation model for hard reasoning (RA.Aid "expert tool" escape hatch) | RA.Aid |
|
||||
|
||||
The three-stage pattern maps to BooCoder's use case:
|
||||
- **Research agent** (cheap model) → understand the task, find relevant files
|
||||
- **Planning agent** (standard model) → decide which files to edit, what the changes look like
|
||||
- **Implementation agent** (full model) → produce the actual diffs
|
||||
|
||||
`expert_model` is the escape hatch: a routine model handles most subtasks, but can call the expert model (e.g. qwopus27b) when stuck. Matches Sam's existing cost-routing discipline.
|
||||
|
||||
## Subagent isolation (Boomerang pattern, v2.0.1)
|
||||
|
||||
From Roo Code Boomerang Tasks (Apache-2.0 pattern):
|
||||
|
||||
When an orchestrator agent calls a `new_task` tool, BooCoder:
|
||||
1. Creates a fresh `tasks` row with `parent_task_id` pointing to the orchestrator's task
|
||||
2. Spawns a fresh inference session (Path A) or dispatch (Path B) with ONLY the task spec as context — no inherited conversation
|
||||
3. Child runs to `attempt_completion`, writes a summary to `tasks.output_summary`
|
||||
4. Parent resumes reading ONLY the summary (not the child's full conversation)
|
||||
|
||||
**Three principles:**
|
||||
- Orchestrator capability restriction: the orchestrator agent's tool list includes ONLY `new_task`, `list_tasks`, `check_task_status` — it cannot read files or call MCP tools directly
|
||||
- Down-pass: parent sends task spec via `new_task(input)`, nothing else inherited
|
||||
- Up-pass: child sends result via `attempt_completion(summary)`, nothing else surfaces to parent
|
||||
|
||||
This is the **single most important context-management primitive** — it prevents long-running orchestrators from poisoning their context with implementation detail.
|
||||
|
||||
## Observation hooks (v2.0.3)
|
||||
|
||||
From `siropkin/budi` (MIT) Claude Code 5-hook taxonomy:
|
||||
|
||||
Register BooCoder as a hook receiver for dispatched agents. Five events:
|
||||
- `SessionStart` — agent spawned
|
||||
- `UserPromptSubmit` — task spec delivered
|
||||
- `PostToolUse` — each tool call completed
|
||||
- `SubagentStart` — nested dispatch
|
||||
- `Stop` — agent finished
|
||||
|
||||
These map directly to BooCode's existing WS frame protocol. The hook receiver is the BooCoder Fastify server; events flow into the `message_parts` taxonomy as `step_start`-style instrumentation parts.
|
||||
|
||||
## Follow-up batches (v2.0+ optional, ordered by value)
|
||||
|
||||
| Batch | Source | What | When |
|
||||
|---|---|---|---|
|
||||
| **PR-resolver tool** | `qodo-ai/qodo-skills` (MIT) | Fetch GitHub issues → batch/interactive fix → inline PR reply. BooCoder tool that replaces Sam's manual PR workflow. | v2.0.3+ |
|
||||
| **HMAC audit log** | `sipyourdrink-ltd/bernstein` (verify license) | One new `audit_log` table with `prev_hmac` field. Tamper-evident history of every edit BooCoder makes. Small lift (~50 LoC). | v2.0.1+ |
|
||||
| **Blind-validation gate** | `covibes/zeroshot` (MIT) | Verify gate runs in a separate agent context that sees ONLY the diff + acceptance criteria, not the producing conversation. Complements Boomerang (isolation) + bernstein (lineage). | v2.0.2+ |
|
||||
| **Majority-vote ensembler** | `augmentcode/augment-swebench-agent` (MIT) | K candidate diffs from K agents → ranker model picks the best one. Optional layer above `pending_changes`. | v2.1+ |
|
||||
| **Drift detection** | `memovai/memov` (MIT) | `validate_commit` concept — detects when actual changes diverge from what was requested. Shadow timeline comparison. | v2.0.3+ |
|
||||
| **Anti-slop for frontend** | `Leonxlnx/taste-skill` (MIT) | 100+ specific font/color/layout ban list + 3-dial parameterization. Vendor into skills/ when BooCoder generates frontend code. | v2.0+ |
|
||||
| **Verify-before-commit gate** | `DeepSourceCorp/globstar` (MIT) | Rule-based AST linter as a pre-apply quality gate. YAML checkers in `.globstar/`. | v2.1+ (parked) |
|
||||
| **Docker sandbox** | `OpenHands/OpenHands` (MIT) | Per-session Docker container for write tools. Closes the `/opt:rw` mount risk if path-guard ever proves insufficient. | v2.1 (optional) |
|
||||
| **Multi-provider LLM** | `earendil-works/pi` (MIT) | Provider abstraction if a need for Anthropic/OpenAI/Mistral direct surfaces beyond llama-swap. | v2.x (optional) |
|
||||
|
||||
## Repos to clone before starting
|
||||
|
||||
```bash
|
||||
cd /opt/forks
|
||||
git clone https://github.com/Dominic789654/agent-hub.git # Apache-2.0, task DAG reference
|
||||
git clone https://github.com/Dominic789654/agent-hub.git # Apache-2.0, task DAG + dispatcher
|
||||
git clone https://github.com/plandex-ai/plandex.git # MIT, pending-changes UX
|
||||
git clone https://github.com/anomalyco/opencode.git # MIT, permission evaluate.ts + MCP client reference
|
||||
git clone https://github.com/anomalyco/opencode.git # MIT, permission evaluate.ts reference
|
||||
git clone https://github.com/qodo-ai/agents.git # MIT, agent.toml schema (output_schema, exit_expression, execution_strategy)
|
||||
```
|
||||
|
||||
ACP SDK is an npm package (`@zed-industries/agent-client-protocol`), installed at implementation time. Paseo is design-only (AGPL, no code lift).
|
||||
Also read (no clone needed):
|
||||
- `ai-christianson/RA.Aid` README — three-stage pattern + expert-tool escape hatch
|
||||
- `getpaseo/paseo` README + `skills/` directory — daemon architecture + CLI verbs (AGPL, design-only)
|
||||
- `agentclientprotocol.com` spec — ACP stdio protocol
|
||||
- `goose-docs.ai/docs/guides/acp-clients/` — `context_servers` auto-forward pattern
|
||||
- `siropkin/budi` README — 5-hook Claude Code taxonomy for observation
|
||||
|
||||
ACP SDK and MCP SDK are npm packages installed at implementation time.
|
||||
|
||||
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@@ -46,6 +46,34 @@ importers:
|
||||
specifier: ^5.5.0
|
||||
version: 5.9.3
|
||||
|
||||
apps/coder:
|
||||
dependencies:
|
||||
'@fastify/static':
|
||||
specifier: ^7.0.4
|
||||
version: 7.0.4
|
||||
'@fastify/websocket':
|
||||
specifier: ^10.0.1
|
||||
version: 10.0.1
|
||||
fastify:
|
||||
specifier: ^4.28.1
|
||||
version: 4.29.1
|
||||
postgres:
|
||||
specifier: ^3.4.4
|
||||
version: 3.4.9
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.25.76
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.14.10
|
||||
version: 20.19.41
|
||||
tsx:
|
||||
specifier: ^4.16.2
|
||||
version: 4.22.0
|
||||
typescript:
|
||||
specifier: ^5.5.0
|
||||
version: 5.9.3
|
||||
|
||||
apps/server:
|
||||
dependencies:
|
||||
'@ai-sdk/openai-compatible':
|
||||
|
||||
Reference in New Issue
Block a user