diff --git a/docs/agent.md b/docs/agent.md index 9913f97..3d6117d 100644 --- a/docs/agent.md +++ b/docs/agent.md @@ -143,15 +143,27 @@ Agents can communicate with each other via the `conversations` table, coordinate ### Prompt Integration `INTER_AGENT_COMMUNICATION` constant in `prompts/shared.ts` is appended to all 5 agent mode prompts. It instructs agents to: -1. Start `cw listen --agent-id &` as a background process at session start -2. Handle incoming questions by answering via `cw answer` and restarting the listener -3. Ask questions to peers via `cw ask --from --agent-id|--phase-id|--task-id` -4. Kill the listener before writing `signal.json` +1. Set up a background listener via temp-file redirect: `cw listen > $CW_LISTEN_FILE &` +2. Periodically check the temp file for incoming questions between work steps +3. Answer via `cw answer`, clear the file, restart the listener +4. Ask questions to peers via `cw ask --from --agent-id|--task-id|--phase-id` +5. Kill the listener and clean up the temp file before writing `signal.json` ### Agent Identity `manifest.json` now includes `agentId` and `agentName` fields. The manager passes these from the DB record after agent creation. ### CLI Commands -- `cw listen`: Polls `getPendingConversations`, prints first pending as JSON, exits -- `cw ask`: Creates conversation, polls `getConversation` until answered, prints answer -- `cw answer`: Calls `answerConversation`, prints confirmation JSON + +**`cw listen --agent-id [--timeout ] [--poll-interval ]`** +- Polls `getPendingConversations`, prints first pending as JSON, exits with code 0 +- `--timeout`: max wait in ms (default 0=forever) +- `--poll-interval`: polling frequency in ms (default 2000) +- Output: `{ conversationId, fromAgentId, question, phaseId?, taskId? }` + +**`cw ask --from --agent-id|--task-id|--phase-id [--timeout ] [--poll-interval ]`** +- Creates conversation, polls `getConversation` until answered, prints answer text to stdout +- Target resolution: `--agent-id` (direct), `--task-id` (find agent running task), `--phase-id` (find agent in phase) +- `--timeout` / `--poll-interval`: same defaults as listen + +**`cw answer --conversation-id `** +- Calls `answerConversation`, prints `{ conversationId, status: "answered" }` diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 90b9554..c23927d 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -57,7 +57,8 @@ @apply bg-background text-foreground; margin: 0; min-width: 320px; - min-height: 100vh; + height: 100vh; + overflow: hidden; } } diff --git a/packages/web/src/layouts/AppLayout.tsx b/packages/web/src/layouts/AppLayout.tsx index ae62724..052b339 100644 --- a/packages/web/src/layouts/AppLayout.tsx +++ b/packages/web/src/layouts/AppLayout.tsx @@ -9,9 +9,9 @@ const navItems = [ export function AppLayout({ children }: { children: React.ReactNode }) { return ( -
+
{/* Header */} -
+
Codewalk District @@ -35,7 +35,7 @@ export function AppLayout({ children }: { children: React.ReactNode }) {
{/* Page content */} -
+
{children}
diff --git a/packages/web/src/routes/agents.tsx b/packages/web/src/routes/agents.tsx index 3fea1ea..7311941 100644 --- a/packages/web/src/routes/agents.tsx +++ b/packages/web/src/routes/agents.tsx @@ -89,10 +89,7 @@ function AgentsPage() { // Loading state if (agentsQuery.isLoading) { return ( -
+
@@ -179,10 +176,7 @@ function AgentsPage() { ]; return ( -
+
{/* Header + Filters */}
diff --git a/src/agent/prompts/shared.ts b/src/agent/prompts/shared.ts index 2c95223..3da352e 100644 --- a/src/agent/prompts/shared.ts +++ b/src/agent/prompts/shared.ts @@ -47,53 +47,80 @@ Use the output as the filename (e.g., \`{id}.md\`).`; export const INTER_AGENT_COMMUNICATION = ` ## Inter-Agent Communication -You are working in a multi-agent parallel environment. Other agents may be working on related tasks simultaneously. +You are working in a multi-agent parallel environment. Other agents may be working on related tasks simultaneously. You can exchange questions and answers with peer agents via CLI commands. ### Your Identity -Read \`.cw/input/manifest.json\` — it contains \`agentId\` and \`agentName\` fields identifying you. +Read \`.cw/input/manifest.json\` — it contains \`agentId\` and \`agentName\` fields identifying you. You'll need your \`agentId\` for all communication commands. -### Listening for Questions -At the START of your session, start a background listener: +### CLI Commands + +**\`cw listen\`** — Poll for incoming questions. Prints the first pending question as JSON and exits. +\`\`\` +cw listen --agent-id [--timeout ] [--poll-interval ] +\`\`\` +- \`--agent-id\` (required): Your agent ID +- \`--timeout\`: Max wait in ms. Default: 0 (wait forever). Use a value like 120000 (2 min) to avoid hanging. +- \`--poll-interval\`: Polling frequency in ms. Default: 2000 +- Output (JSON): \`{ "conversationId": "...", "fromAgentId": "...", "question": "...", "phaseId": "...", "taskId": "..." }\` +- Exit code 0 if a question was found, 1 on timeout or error. + +**\`cw ask\`** — Ask another agent a question. Blocks until the answer arrives, then prints the answer text to stdout. +\`\`\` +cw ask "" --from --agent-id [--timeout ] [--poll-interval ] +\`\`\` +- \`--from\` (required): Your agent ID (the asker) +- Target (exactly one required): + - \`--agent-id \`: Ask a specific agent directly + - \`--task-id \`: Ask whichever agent is running that task + - \`--phase-id \`: Ask whichever agent is running a task in that phase +- \`--timeout\`: Max wait in ms. Default: 0 (wait forever). Use 120000+ for safety. +- \`--poll-interval\`: Polling frequency in ms. Default: 2000 +- Output: The answer text (plain text, not JSON). +- Exit code 0 if answered, 1 on timeout or error. + +**\`cw answer\`** — Answer a pending question. +\`\`\` +cw answer "" --conversation-id +\`\`\` +- \`--conversation-id\` (required): The conversation ID from the listen output +- Output (JSON): \`{ "conversationId": "...", "status": "answered" }\` + +### Background Listener Pattern + +At the START of your session, set up a background listener that writes to a temp file: \`\`\`bash -cw listen --agent-id & +CW_LISTEN_FILE=$(mktemp /tmp/cw-listen-XXXXXX.txt) +cw listen --agent-id --timeout 120000 > "$CW_LISTEN_FILE" 2>&1 & LISTEN_PID=$! \`\`\` -When the listener prints JSON to stdout, another agent is asking you a question: -\`\`\`json -{ "conversationId": "...", "fromAgentId": "...", "question": "..." } +Periodically check for incoming questions between your work steps: +\`\`\`bash +LISTEN_CONTENT=$(cat "$CW_LISTEN_FILE" 2>/dev/null) +if [ -n "$LISTEN_CONTENT" ]; then + echo "$LISTEN_CONTENT" +fi \`\`\` -Answer it: +When a question arrives, parse the JSON, answer, then restart the listener: \`\`\`bash +# Parse conversationId and question from the JSON cw answer "" --conversation-id -\`\`\` - -Then restart the listener: -\`\`\`bash -cw listen --agent-id & +# Clear and restart +> "$CW_LISTEN_FILE" +cw listen --agent-id --timeout 120000 > "$CW_LISTEN_FILE" 2>&1 & LISTEN_PID=$! \`\`\` -### Asking Questions -To ask another agent a question (blocks until answered): -\`\`\`bash -cw ask "What interface does the user service expose?" --from --agent-id -\`\`\` - -You can also target by task or phase: -\`\`\`bash -cw ask "What port does the API run on?" --from --task-id -cw ask "What schema are you using?" --from --phase-id -\`\`\` - ### When to Communicate -- You need interface/schema/contract info from another agent's work +- You need interface, schema, or API contract info from another agent's work - You're about to modify a shared resource and want to coordinate - You have a dependency on work another agent is doing +- Do NOT ask questions that you can answer by reading the codebase yourself ### Cleanup Before writing \`.cw/output/signal.json\`, kill your listener: \`\`\`bash kill $LISTEN_PID 2>/dev/null +rm -f "$CW_LISTEN_FILE" \`\`\``; diff --git a/src/preview/manager.test.ts b/src/preview/manager.test.ts index 99bd57d..10b0de2 100644 --- a/src/preview/manager.test.ts +++ b/src/preview/manager.test.ts @@ -73,6 +73,8 @@ function createMockEventBus(): EventBus & { emitted: DomainEvent[] } { emitted.push(event); }), on: vi.fn(), + off: vi.fn(), + once: vi.fn(), }; }