import { query, type SDKMessage, type Query } from "@anthropic-ai/claude-agent-sdk"; import type { Session } from "../db/sessions"; import { getPhaseConfig, getNextPhase, getArtifactFilename, getSessionArtifactDir } from "./phases"; import type { Phase, UserPermissionMode } from "./phases"; import { getProject } from "../db/projects"; import { updateSession } from "../db/sessions"; import fs from "node:fs"; import path from "node:path"; // Track active queries by session ID const activeQueries = new Map(); function ensureDir(dirPath: string): void { if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } } export interface SendMessageOptions { session: Session; message: string; onMessage: (msg: SDKMessage) => void; } export async function sendMessage({ session, message, onMessage, }: SendMessageOptions): Promise { const project = getProject(session.project_id); if (!project) throw new Error("Project not found"); // Ensure session artifact directory exists const sessionDir = path.join(project.path, getSessionArtifactDir(session.id)); ensureDir(sessionDir); const phaseConfig = getPhaseConfig( session.phase as Phase, session.permission_mode as UserPermissionMode ); const q = query({ prompt: message, options: { cwd: project.path, resume: session.claude_session_id ?? undefined, tools: phaseConfig.tools, permissionMode: phaseConfig.permissionMode, systemPrompt: phaseConfig.systemPrompt, }, }); activeQueries.set(session.id, q); try { for await (const msg of q) { // Capture session ID from init message if (msg.type === "system" && msg.subtype === "init") { if (!session.claude_session_id) { updateSession(session.id, { claude_session_id: msg.session_id }); } } onMessage(msg); } } finally { activeQueries.delete(session.id); } } export function interruptSession(sessionId: string): void { const q = activeQueries.get(sessionId); if (q) { q.close(); activeQueries.delete(sessionId); } } /** * Trigger a review: Claude reads the document and addresses user annotations */ export async function triggerReview( session: Session, onMessage: (msg: SDKMessage) => void ): Promise { const artifactPath = getArtifactPath(session); const message = `I've updated ${artifactPath} with annotations. Read the file, find all my inline notes (marked with // REVIEW:, // NOTE:, TODO:, or similar), address each one, and update the document accordingly. Do not implement anything yet.`; await sendMessage({ session, message, onMessage }); } /** * Advance to the next phase */ export function advancePhase(session: Session): Phase | null { const nextPhase = getNextPhase(session.phase as Phase); if (nextPhase) { updateSession(session.id, { phase: nextPhase }); } return nextPhase; } /** * Get the artifact path for a session and phase */ export function getArtifactPath(session: Session): string { const filename = getArtifactFilename(session.phase as Phase); return path.join(getSessionArtifactDir(session.id), filename); } /** * Read an artifact file for a session */ export function readSessionArtifact( projectPath: string, sessionId: string, filename: string ): string | null { const filePath = path.join(projectPath, getSessionArtifactDir(sessionId), filename); if (fs.existsSync(filePath)) { return fs.readFileSync(filePath, "utf-8"); } return null; } /** * Write an artifact file for a session */ export function writeSessionArtifact( projectPath: string, sessionId: string, filename: string, content: string ): void { const dir = path.join(projectPath, getSessionArtifactDir(sessionId)); ensureDir(dir); fs.writeFileSync(path.join(dir, filename), content, "utf-8"); } /** * Read CLAUDE.md from project root */ export function readClaudeMd(projectPath: string): string | null { const filePath = path.join(projectPath, "CLAUDE.md"); if (fs.existsSync(filePath)) { return fs.readFileSync(filePath, "utf-8"); } return null; } /** * Write CLAUDE.md to project root */ export function writeClaudeMd(projectPath: string, content: string): void { const filePath = path.join(projectPath, "CLAUDE.md"); fs.writeFileSync(filePath, content, "utf-8"); } /** * Read an artifact file from the project's .claude-flow directory (legacy path) */ export function readArtifact( projectPath: string, filename: string ): string | null { const filePath = path.join(projectPath, ".claude-flow", filename); if (fs.existsSync(filePath)) { return fs.readFileSync(filePath, "utf-8"); } return null; } /** * Write an artifact file to the project's .claude-flow directory (legacy path) */ export function writeArtifact( projectPath: string, filename: string, content: string ): void { const dir = path.join(projectPath, ".claude-flow"); ensureDir(dir); fs.writeFileSync(path.join(dir, filename), content, "utf-8"); } /** * Clear session artifacts */ export function clearSessionArtifacts(projectPath: string, sessionId: string): void { const dir = path.join(projectPath, getSessionArtifactDir(sessionId)); if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true, force: true }); } } /** * Get the initial message for a phase */ export function getPhaseInitialMessage(phase: Phase): string { return getPhaseConfig(phase).initialMessage; } // Re-export types export type { SDKMessage } from "@anthropic-ai/claude-agent-sdk"; export type { Phase, UserPermissionMode } from "./phases";