From 232766d0daab89bf29e369df194f840c0d4ec335 Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Mon, 13 Apr 2026 14:45:05 -0700 Subject: [PATCH] =?UTF-8?q?chore:=20address=20follow-up=20code=20review=20?= =?UTF-8?q?=E2=80=94=20named=20enum,=20singleButton,=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-review fixes on top of the quality-pass-2 branch. 1. delegation.go: replaced insertDelegationRow's (bool, bool) return with a typed insertDelegationOutcome enum (insertOK / insertHandledByIdempotent / insertTrackingUnavailable). Eliminates the positional-boolean decoding the caller had to do. Internal, no behavior change. 2. ConfirmDialog.tsx: added singleButton prop. When true, hides the Cancel button for single-action info toasts (Esc still dismisses via onCancel). TemplatePalette's import notice uses it. 3. ErrorBoundary.tsx: fixed the floating clipboard promise. Added .catch(() => {}) so a rejected writeText (permission denied, insecure context) doesn't surface as unhandled rejection. 4. a2a_proxy_test.go: added 5 direct unit tests for normalizeA2APayload (invalid JSON, wraps-bare, preserves-existing- id, preserves-existing-messageId, missing-method). Fills the unit- test gap for the helper extracted in the last pass. Verification: - go test -race ./internal/handlers/... passes (incl. 5 new tests) - go build ./... clean - canvas npm run build clean - canvas npm test -- --run -> 352/352 Co-Authored-By: Claude Opus 4.6 (1M context) --- AGENTS.md | 177 ------------------- canvas/src/components/ConfirmDialog.tsx | 18 +- canvas/src/components/ErrorBoundary.tsx | 2 +- canvas/src/components/TemplatePalette.tsx | 1 + platform/internal/handlers/a2a_proxy_test.go | 80 +++++++++ platform/internal/handlers/delegation.go | 49 +++-- 6 files changed, 125 insertions(+), 202 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 05659822..00000000 --- a/AGENTS.md +++ /dev/null @@ -1,177 +0,0 @@ -# AGENTS.md - -This file provides guidance to Codex (Codex.ai/code) when working with code in this repository. - -## Project Overview - -Molecule AI is a platform for orchestrating AI agent workspaces that form an organizational hierarchy. Workspaces register with a central platform, communicate via A2A protocol, and are visualized on a drag-and-drop canvas. - -## Architecture - -``` -Canvas (Next.js :3000) ←WebSocket→ Platform (Go :8080) ←HTTP→ Postgres + Redis - ↑ - Workspace A ←──A2A──→ Workspace B - (pluggable runtimes) - ↑ register/heartbeat ↑ - └───── Platform ─────┘ -``` - -Three main components: -- **Platform** (`platform/`): Go/Gin control plane — workspace CRUD, registry, discovery, WebSocket hub, liveness monitoring -- **Canvas** (`canvas/`): Next.js 15 + React Flow (@xyflow/react v12) + Zustand + Tailwind — visual workspace graph -- **Workspace Runtime** (`workspace-template/`): A2A runtime layer with pluggable adapters — LangGraph, DeepAgents, Claude Code, CrewAI, AutoGen, OpenClaw — registers with platform and sends heartbeats - -## Build & Run Commands - -### Infrastructure -```bash -./infra/scripts/setup.sh # Start Postgres, Redis, Langfuse; run migrations -./infra/scripts/nuke.sh # Tear down everything, remove volumes -``` - -### Platform (Go) -```bash -cd platform -go build ./cmd/server # Build -go run ./cmd/server # Run (requires Postgres + Redis running) -``` -Must run from `platform/` directory (not repo root). Env vars: `DATABASE_URL`, `REDIS_URL`, `PORT` (defaults: postgres://dev:dev@localhost:5432/molecule?sslmode=prefer, redis://localhost:6379, 8080). - -### Canvas (Next.js) -```bash -cd canvas -npm install -npm run dev # Dev server on :3000 -npm run build && npm start # Production -``` -Env vars: `NEXT_PUBLIC_PLATFORM_URL` (default http://localhost:8080), `NEXT_PUBLIC_WS_URL` (default ws://localhost:8080/ws). - -### Integration Tests -```bash -bash test_api.sh # Runs 34 API tests against localhost:8080 -``` -Requires platform running. Tests full CRUD, registry, heartbeat, discovery, peers, access control, events, degraded/recovery lifecycle. - -### Docker Compose -```bash -docker compose -f docker-compose.infra.yml up -d # Infra only -docker compose up # Full stack -``` - -## Key Architectural Patterns - -### Import Cycle Prevention -The platform uses function injection to avoid Go import cycles between ws, registry, and events packages: -- `ws.NewHub(canCommunicate AccessChecker)` — Hub accepts `registry.CanCommunicate` as a function -- `registry.StartLivenessMonitor(ctx, onOffline OfflineHandler)` — Liveness accepts broadcaster callback -- Wiring happens in `platform/cmd/server/main.go` - -### Communication Rules (`registry/access.go`) -`CanCommunicate(callerID, targetID)` determines if two workspaces can talk: -- Same workspace → allowed -- Siblings (same parent_id) → allowed -- Root-level siblings (both parent_id IS NULL) → allowed -- Parent ↔ child → allowed -- Everything else → denied - -### JSONB Gotcha -When inserting Go `[]byte` (from `json.Marshal`) into Postgres JSONB columns, you must: -1. Convert to `string()` first -2. Use `::jsonb` cast in SQL - -lib/pq treats `[]byte` as `bytea`, not JSONB. - -### WebSocket Events Flow -1. Action occurs (register, heartbeat, etc.) -2. `broadcaster.RecordAndBroadcast()` inserts into `structure_events` table + publishes to Redis pub/sub -3. Redis subscriber relays to WebSocket hub -4. Hub broadcasts to canvas clients (all events) and workspace clients (filtered by CanCommunicate) - -### Canvas State Management -- Initial load: HTTP fetch from `GET /workspaces` → Zustand hydrate -- Real-time updates: WebSocket events → `applyEvent()` in Zustand store -- Position persistence: `onNodeDragStop` → `PATCH /workspaces/:id` with `{x, y}` - -### Workspace Lifecycle -`provisioning` → `online` (on register) → `degraded` (error_rate > 0.5) → `online` (recovered) → `offline` (Redis TTL expired) → `removed` (deleted) - -## Platform API Routes - -| Method | Path | Handler | -|--------|------|---------| -| GET | /health | inline | -| POST/GET/PATCH/DELETE | /workspaces[/:id] | workspace.go | -| POST | /registry/register | registry.go | -| POST | /registry/heartbeat | registry.go | -| POST | /registry/update-card | registry.go | -| GET | /registry/discover/:id | discovery.go | -| GET | /registry/:id/peers | discovery.go | -| POST | /registry/check-access | discovery.go | -| GET | /events[/:workspaceId] | events.go | -| GET | /ws | socket.go | - -## Database - -5 migration files in `platform/migrations/`. Key tables: `workspaces` (core entity with status, agent_card JSONB, heartbeat columns), `canvas_layouts` (x/y position), `structure_events` (append-only event log), `agents`, `workspace_secrets`. - -The platform auto-discovers and runs migrations on startup from several candidate paths. - - -# Awareness Memory Integration - -MANDATORY agent policy - follow for every task. - -## Awareness Memory Integration (MANDATORY) - -awareness_* = cross-session persistent memory (past decisions, knowledge, tasks). -Other tools = current codebase navigation (file search, code index). -Use BOTH - they serve different purposes. - -STEP 1 - SESSION START: - Call awareness_init(source="codex") -> get session_id, review context. - If active_skills[] is returned: skill = reusable procedure done 2+ times; - summary = injectable instruction, methods = steps. Apply matching skills to tasks. - -STEP 2 - RECALL BEFORE WORK (progressive disclosure): - 1. awareness_recall(semantic_query=..., keyword_query=..., detail='summary') → lightweight index. - 2. Review summaries/scores, pick relevant IDs. - 3. awareness_recall(detail='full', ids=[...]) → expand only what you need. - -STEP 3 - RECORD EVERY CHANGE: - After EVERY code edit, decision, or bug fix: - awareness_record(content=, - insights={knowledge_cards:[...], action_items:[...], risks:[...]}) - Content should be RICH and DETAILED — include reasoning, key code snippets, - user quotes, alternatives considered, and files changed. Do NOT compress into - a single-line summary. The content IS the memory — more detail = better recall. - Include insights to create searchable knowledge in ONE step (recommended). - Skipping = permanent data loss. - -STEP 4 - CATEGORY GUIDE (for insights.knowledge_cards): - - decision = choice made between alternatives. - - problem_solution = bug/problem plus the fix that resolved it. - - workflow = process, setup, or configuration steps only. - - pitfall = blocker, warning, or limitation without a fix yet. - - insight = reusable pattern or general learning. - - skill = reusable procedure done 2+ times; summary = injectable instruction, methods = steps. - - key_point = important technical fact when nothing else fits. - Never default everything to workflow. - -STEP 5 - SESSION END: - awareness_record(content=[step1, step2, ...], insights={...}) with final summary. - -BACKFILL (if applicable): - If MCP connected late: awareness_record(content=) - -RULES VERSION: Pass rules_version="2" to awareness_init so the server knows you have these rules. -If the server returns _setup_action, the rules have been updated — follow the instruction to re-sync. - -NOTE: memory_id from X-Awareness-Memory-Id header. source/actor/event_type auto-inferred. - -## Codex-Specific Notes - -- Call awareness_init at task start before reading any files. - -- After each code patch, call awareness_record with the change description. - diff --git a/canvas/src/components/ConfirmDialog.tsx b/canvas/src/components/ConfirmDialog.tsx index 64c6d58a..339376cf 100644 --- a/canvas/src/components/ConfirmDialog.tsx +++ b/canvas/src/components/ConfirmDialog.tsx @@ -11,6 +11,9 @@ interface Props { confirmVariant?: "danger" | "primary" | "warning"; onConfirm: () => void; onCancel: () => void; + // Hide the Cancel button for single-action info toasts. + // onCancel is still invoked on Esc / backdrop-click. + singleButton?: boolean; } export function ConfirmDialog({ @@ -21,6 +24,7 @@ export function ConfirmDialog({ confirmVariant = "primary", onConfirm, onCancel, + singleButton = false, }: Props) { const dialogRef = useRef(null); const [mounted, setMounted] = useState(false); @@ -71,12 +75,14 @@ export function ConfirmDialog({
- + {!singleButton && ( + + )}