@promptprojectmanager/mcp-server 4.6.6 → 4.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -96,45 +96,48 @@ function parseStreamJsonLog(logContent) {
|
|
|
96
96
|
if (!trimmed || trimmed.startsWith("===")) continue;
|
|
97
97
|
try {
|
|
98
98
|
const json = JSON.parse(trimmed);
|
|
99
|
-
if (json.type === "
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
99
|
+
if (json.type === "assistant" && json.message?.content) {
|
|
100
|
+
for (const block of json.message.content) {
|
|
101
|
+
if (block.type === "tool_use") {
|
|
102
|
+
events.push({
|
|
103
|
+
type: "tool_use",
|
|
104
|
+
toolName: block.name,
|
|
105
|
+
toolInput: block.input || {}
|
|
106
|
+
});
|
|
107
|
+
} else if (block.type === "text" && block.text) {
|
|
108
|
+
events.push({
|
|
109
|
+
type: "text",
|
|
110
|
+
text: block.text
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} else if (json.type === "user" && json.message?.content) {
|
|
115
|
+
for (const block of json.message.content) {
|
|
116
|
+
if (block.type === "tool_result") {
|
|
117
|
+
const resultText = Array.isArray(block.content) ? block.content.map((c) => c.text || "").join("") : typeof block.content === "string" ? block.content : JSON.stringify(block.content);
|
|
118
|
+
events.push({
|
|
119
|
+
type: "tool_result",
|
|
120
|
+
toolResult: resultText
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} else if (json.type === "result") {
|
|
125
|
+
if (json.is_error) {
|
|
126
|
+
events.push({
|
|
127
|
+
type: "error",
|
|
128
|
+
error: json.result || "Execution failed"
|
|
129
|
+
});
|
|
130
|
+
} else if (json.result) {
|
|
115
131
|
events.push({
|
|
116
132
|
type: "text",
|
|
117
|
-
text
|
|
133
|
+
text: `[FINAL] ${json.result}`
|
|
118
134
|
});
|
|
119
135
|
}
|
|
120
|
-
} else if (json.type === "content_block_delta" && json.delta?.text) {
|
|
121
|
-
events.push({
|
|
122
|
-
type: "text",
|
|
123
|
-
text: json.delta.text
|
|
124
|
-
});
|
|
125
136
|
} else if (json.type === "error" || json.error) {
|
|
126
137
|
events.push({
|
|
127
138
|
type: "error",
|
|
128
139
|
error: json.error?.message || json.message || JSON.stringify(json)
|
|
129
140
|
});
|
|
130
|
-
} else if (json.result) {
|
|
131
|
-
if (json.result.tool_use) {
|
|
132
|
-
events.push({
|
|
133
|
-
type: "tool_use",
|
|
134
|
-
toolName: json.result.tool_use.name,
|
|
135
|
-
toolInput: json.result.tool_use.input || {}
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
141
|
}
|
|
139
142
|
} catch {
|
|
140
143
|
if (trimmed.length > 10 && !trimmed.includes("YOLO") && !trimmed.includes("===")) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/watcher/watcher_daemon.ts","../../src/watcher/watcher_spawn.ts","../../src/watcher/watcher_poll.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Watcher Daemon - Background Process for YOLO Ticket Execution\n *\n * This is the standalone daemon entry point that runs as a detached\n * background process. It polls for YOLO tickets and spawns headless\n * Claude CLI processes to execute them.\n *\n * Usage (spawned by controller):\n * node watcher_daemon.js --config <base64-encoded-config>\n *\n * State files in .ppm/yolo/:\n * - watcher.pid: Daemon's process ID\n * - status.json: Current status (for MCP status tool)\n * - watcher.log: Daemon activity log\n * - logs/{ticket}.log: Individual ticket execution logs\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport { appendFileSync, readFileSync } from \"fs\";\nimport type {\n WatcherConfig,\n WatcherStatus,\n WatcherTicket,\n TrackedChildProcess,\n CompletedTicket,\n} from \"./watcher_types.js\";\nimport {\n writePid,\n writeStatus,\n readStatus,\n getLogFile,\n addCompletedTicket,\n readTicketLog,\n} from \"./watcher_state.js\";\nimport { spawnClaudeWithHandle, getDetailedSummary } from \"./watcher_spawn.js\";\nimport { executePollCycle } from \"./watcher_poll.js\";\n\n// ============================================================================\n// Global State\n// ============================================================================\n\n/** Tracked child processes by PID */\nconst childProcesses = new Map<number, TrackedChildProcess>();\n\n/** Polling interval handle */\nlet pollInterval: ReturnType<typeof setInterval> | null = null;\n\n/** Daemon configuration */\nlet config: WatcherConfig;\n\n/** Convex client */\nlet convexClient: ConvexHttpClient;\n\n/** Path to daemon log file */\nlet daemonLogFile: string;\n\n// ============================================================================\n// Logging\n// ============================================================================\n\n/**\n * Log a message to the daemon log file and console.\n */\nfunction log(message: string): void {\n const timestamp = new Date().toISOString();\n const line = `[${timestamp}] ${message}\\n`;\n\n // Write to log file\n try {\n appendFileSync(daemonLogFile, line);\n } catch {\n // Ignore log write errors\n }\n\n // Also write to console (will go to /dev/null when detached)\n console.log(message);\n}\n\n// ============================================================================\n// Child Process Management\n// ============================================================================\n\n/**\n * Spawn a Claude CLI process to execute a ticket.\n *\n * @param ticket - Ticket to execute\n */\nfunction spawnTicketExecution(ticket: WatcherTicket): void {\n log(`Spawning execution for ticket: ${ticket.slug}`);\n\n const spawnResult = spawnClaudeWithHandle({\n ticket,\n workingDirectory: config.workingDirectory,\n projectSlug: config.projectSlug,\n timeout: config.ticketTimeout,\n });\n\n if (!spawnResult.success || !spawnResult.child) {\n log(`Failed to spawn for ${ticket.slug}: ${spawnResult.error}`);\n return;\n }\n\n const child = spawnResult.child;\n const pid = child.pid!;\n const startedAt = new Date().toISOString();\n\n // Track the child process\n const tracked: TrackedChildProcess = {\n pid,\n ticketSlug: ticket.slug,\n ticketNumber: ticket.ticketNumber,\n startedAt,\n logFile: spawnResult.logFile!,\n };\n\n // Set timeout for the execution\n if (config.ticketTimeout > 0) {\n tracked.timeout = setTimeout(() => {\n handleTimeout(pid);\n }, config.ticketTimeout);\n }\n\n childProcesses.set(pid, tracked);\n\n // Update status file\n updateExecutingStatus();\n\n // Handle child process events\n child.on(\"exit\", (code, signal) => {\n handleChildExit(pid, code, signal);\n });\n\n child.on(\"error\", (error) => {\n log(`Process error for ${ticket.slug}: ${error.message}`);\n // Will also trigger 'exit' event\n });\n\n log(`Spawned PID ${pid} for ${ticket.slug}`);\n\n // Send macOS notification if enabled\n if (config.enableNotifications) {\n sendNotification(`YOLO: Executing ${ticket.slug}`, `Started ticket execution`);\n }\n}\n\n/**\n * Handle child process exit.\n *\n * @param pid - Process ID\n * @param code - Exit code (null if killed)\n * @param signal - Signal that killed process (if any)\n */\nfunction handleChildExit(\n pid: number,\n code: number | null,\n signal: NodeJS.Signals | null\n): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) {\n log(`Unknown child exited: PID ${pid}`);\n return;\n }\n\n // Clear timeout if set\n if (tracked.timeout) {\n clearTimeout(tracked.timeout);\n }\n\n // Remove from tracking\n childProcesses.delete(pid);\n\n // Calculate duration\n const startTime = new Date(tracked.startedAt).getTime();\n const durationMs = Date.now() - startTime;\n\n // Read log file and extract detailed summary (parses stream-json)\n const logContent = readTicketLog(config.workingDirectory, tracked.ticketSlug);\n const detailedSummary = logContent ? getDetailedSummary(logContent) : null;\n\n // Determine success (exit code 0 AND ticket was closed)\n const success = code === 0 && (detailedSummary?.ticketClosed ?? false);\n\n log(\n `Child exited: ${tracked.ticketSlug} (PID ${pid}) - ` +\n `code=${code}, signal=${signal}, duration=${Math.round(durationMs / 1000)}s, ` +\n `ticketClosed=${detailedSummary?.ticketClosed ?? false}`\n );\n\n // Log tool calls for visibility\n if (detailedSummary?.toolCalls && detailedSummary.toolCalls.length > 0) {\n log(` Tools used: ${detailedSummary.toolCalls.map(t => t.tool).join(\", \")}`);\n }\n\n // Record completion with detailed info\n const completed: CompletedTicket = {\n ticketSlug: tracked.ticketSlug,\n ticketNumber: tracked.ticketNumber,\n startedAt: tracked.startedAt,\n completedAt: new Date().toISOString(),\n durationMs,\n exitCode: code,\n success,\n summary: detailedSummary?.summary,\n logFile: tracked.logFile,\n toolCalls: detailedSummary?.toolCalls,\n ticketClosed: detailedSummary?.ticketClosed,\n errors: detailedSummary?.errors,\n };\n\n addCompletedTicket(config.workingDirectory, completed);\n\n // Update status\n updateExecutingStatus();\n\n // Send notification\n if (config.enableNotifications) {\n const emoji = success ? \"✅\" : \"❌\";\n sendNotification(\n `YOLO: ${emoji} ${tracked.ticketSlug}`,\n success ? \"Ticket completed\" : `Exited with code ${code}`\n );\n }\n}\n\n/**\n * Handle execution timeout.\n *\n * @param pid - Process ID to kill\n */\nfunction handleTimeout(pid: number): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) return;\n\n log(`Timeout for ${tracked.ticketSlug} (PID ${pid}) - killing process`);\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Process may have already exited\n }\n\n // Note: handleChildExit will be called when process actually exits\n}\n\n/**\n * Update the currently executing list in status file.\n */\nfunction updateExecutingStatus(): void {\n const status = readStatus(config.workingDirectory);\n if (!status) return;\n\n const currentlyExecuting = Array.from(childProcesses.values()).map((p) => ({\n ticketSlug: p.ticketSlug,\n ticketNumber: p.ticketNumber,\n startedAt: p.startedAt,\n pid: p.pid,\n logFile: p.logFile,\n }));\n\n writeStatus(config.workingDirectory, {\n ...status,\n currentlyExecuting,\n lastPollAt: new Date().toISOString(),\n });\n}\n\n// ============================================================================\n// Notifications\n// ============================================================================\n\n/**\n * Send a macOS notification using osascript.\n * Silently fails on non-macOS platforms.\n *\n * @param title - Notification title\n * @param message - Notification message\n */\nfunction sendNotification(title: string, message: string): void {\n if (process.platform !== \"darwin\") return;\n\n try {\n const { execSync } = require(\"child_process\");\n const script = `display notification \"${message}\" with title \"${title}\"`;\n execSync(`osascript -e '${script}'`, { stdio: \"ignore\" });\n } catch {\n // Ignore notification errors\n }\n}\n\n// ============================================================================\n// Polling Loop\n// ============================================================================\n\n/**\n * Execute a single poll cycle.\n */\nasync function poll(): Promise<void> {\n try {\n await executePollCycle({\n client: convexClient,\n config,\n onSpawnTicket: spawnTicketExecution,\n onLog: log,\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n log(`Poll error: ${msg}`);\n }\n}\n\n/**\n * Start the polling loop.\n */\nfunction startPolling(): void {\n log(`Starting poll loop (interval: ${config.pollIntervalMs}ms)`);\n\n // Run first poll immediately\n poll();\n\n // Then poll at interval\n pollInterval = setInterval(() => {\n poll();\n }, config.pollIntervalMs);\n}\n\n/**\n * Stop the polling loop.\n */\nfunction stopPolling(): void {\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n log(\"Stopped polling\");\n }\n}\n\n// ============================================================================\n// Shutdown\n// ============================================================================\n\n/**\n * Graceful shutdown handler.\n */\nfunction shutdown(signal: string): void {\n log(`Received ${signal} - shutting down`);\n\n // Stop polling\n stopPolling();\n\n // Kill any running child processes\n for (const [pid, tracked] of childProcesses) {\n log(`Killing child process: ${tracked.ticketSlug} (PID ${pid})`);\n if (tracked.timeout) clearTimeout(tracked.timeout);\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Already exited\n }\n }\n\n // Update status to stopped\n const status = readStatus(config.workingDirectory);\n if (status) {\n writeStatus(config.workingDirectory, {\n ...status,\n state: \"stopped\",\n currentlyExecuting: [],\n });\n }\n\n log(\"Shutdown complete\");\n process.exit(0);\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Parse command line arguments and extract config.\n */\nfunction parseArgs(): WatcherConfig {\n const args = process.argv.slice(2);\n\n // Find --config argument\n const configIndex = args.indexOf(\"--config\");\n if (configIndex === -1 || !args[configIndex + 1]) {\n console.error(\"Usage: watcher_daemon --config <base64-encoded-config>\");\n process.exit(1);\n }\n\n const configB64 = args[configIndex + 1];\n\n try {\n const configJson = Buffer.from(configB64, \"base64\").toString(\"utf-8\");\n return JSON.parse(configJson) as WatcherConfig;\n } catch (error) {\n console.error(\"Failed to parse config:\", error);\n process.exit(1);\n }\n}\n\n/**\n * Main daemon entry point.\n */\nasync function main(): Promise<void> {\n // Parse configuration\n config = parseArgs();\n\n // Initialize log file\n daemonLogFile = getLogFile(config.workingDirectory);\n\n log(\"=\".repeat(50));\n log(\"YOLO Watcher Daemon Starting\");\n log(`Project: ${config.projectSlug}`);\n log(`Working Directory: ${config.workingDirectory}`);\n log(`Poll Interval: ${config.pollIntervalMs}ms`);\n log(`Max Parallel: ${config.maxParallel}`);\n log(`Ticket Timeout: ${config.ticketTimeout}ms`);\n log(\"=\".repeat(50));\n\n // Write PID file\n writePid(config.workingDirectory, process.pid);\n log(`PID: ${process.pid}`);\n\n // Initialize Convex client\n convexClient = new ConvexHttpClient(config.convexUrl);\n\n // Write initial status\n const initialStatus: WatcherStatus = {\n state: \"running\",\n pid: process.pid,\n projectSlug: config.projectSlug,\n startedAt: new Date().toISOString(),\n ticketsProcessed: 0,\n currentlyExecuting: [],\n config: {\n projectSlug: config.projectSlug,\n convexUrl: config.convexUrl,\n pollIntervalMs: config.pollIntervalMs,\n maxParallel: config.maxParallel,\n ticketTimeout: config.ticketTimeout,\n enableNotifications: config.enableNotifications,\n workingDirectory: config.workingDirectory,\n },\n };\n writeStatus(config.workingDirectory, initialStatus);\n\n // Set up signal handlers\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n\n // Send startup notification\n if (config.enableNotifications) {\n sendNotification(\"YOLO Watcher Started\", `Watching ${config.projectSlug}`);\n }\n\n // Start polling\n startPolling();\n}\n\n// Run main\nmain().catch((error) => {\n console.error(\"Daemon error:\", error);\n process.exit(1);\n});\n","/**\n * Watcher Spawn - Headless Claude CLI Spawner\n *\n * Spawns `claude` CLI processes in headless mode for ticket execution.\n * Output is captured to log files for later review.\n *\n * Key features:\n * - Cross-platform (no Terminal.app/AppleScript dependency)\n * - Output captured to .ppm/yolo/logs/{ticket}.log\n * - Child processes tracked by daemon for completion handling\n */\n\nimport { spawn, type ChildProcess } from \"child_process\";\nimport { openSync, closeSync, appendFileSync } from \"fs\";\nimport type { SpawnResult, WatcherTicket } from \"./watcher_types.js\";\nimport { getTicketLogFile } from \"./watcher_state.js\";\n\n// ============================================================================\n// Claude CLI Spawning\n// ============================================================================\n\n/**\n * Options for spawning a Claude CLI process\n */\nexport interface SpawnClaudeOptions {\n /** Ticket being executed */\n ticket: WatcherTicket;\n\n /** Working directory for Claude session */\n workingDirectory: string;\n\n /** Project slug for context */\n projectSlug: string;\n\n /** Optional timeout in ms (default: 30 minutes) */\n timeout?: number;\n}\n\n/**\n * Spawn a headless Claude CLI process to execute a ticket.\n *\n * The process runs with --dangerously-skip-permissions for autonomous execution.\n * All output (stdout + stderr) is captured to a log file.\n *\n * @param options - Spawn configuration\n * @returns Spawn result with PID and log file path, or error\n */\nexport function spawnClaudeCli(options: SpawnClaudeOptions): SpawnResult {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing (creates if doesn't exist)\n const logFd = openSync(logFile, \"w\");\n\n // Write header to log file\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI in headless mode with JSON streaming output\n // --output-format stream-json gives us detailed tool calls and results\n // --verbose is required when using stream-json with -p (print mode)\n const child = spawn(\"claude\", [\n \"-p\", prompt,\n \"--dangerously-skip-permissions\",\n \"--output-format\", \"stream-json\",\n \"--verbose\",\n ], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd], // stdin: ignore, stdout+stderr: log file\n detached: false, // NOT detached - daemon tracks it\n env: {\n ...process.env,\n // Ensure Claude doesn't try to use a TTY\n TERM: \"dumb\",\n // Disable color output for cleaner logs\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n // Close the file descriptor in the parent process\n // The child process has its own reference to the file\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n pid: child.pid,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Spawn claude and return the ChildProcess for event handling.\n *\n * This version returns the child process directly so the daemon can\n * attach event handlers for exit, error, etc.\n *\n * @param options - Spawn configuration\n * @returns Object with child process and log file, or error\n */\nexport function spawnClaudeWithHandle(options: SpawnClaudeOptions): {\n success: boolean;\n child?: ChildProcess;\n logFile?: string;\n error?: string;\n} {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing\n const logFd = openSync(logFile, \"w\");\n\n // Write header\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI with JSON streaming output\n // --verbose is required when using stream-json with -p (print mode)\n const child = spawn(\"claude\", [\n \"-p\", prompt,\n \"--dangerously-skip-permissions\",\n \"--output-format\", \"stream-json\",\n \"--verbose\",\n ], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd],\n detached: false,\n env: {\n ...process.env,\n TERM: \"dumb\",\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n child,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Prompt Building\n// ============================================================================\n\n/**\n * Build the prompt that Claude will execute for the ticket.\n *\n * @param ticket - Ticket to execute\n * @param projectSlug - Project context\n * @returns Formatted prompt string\n */\nfunction buildTicketPrompt(ticket: WatcherTicket, projectSlug: string): string {\n // Use flattened content if available, otherwise raw content\n const content = ticket.flattenedContent || ticket.content;\n\n return `You are executing a YOLO ticket autonomously in project \"${projectSlug}\".\n\n## Ticket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\n\n${content}\n\n---\n\n## Your Instructions\n\n1. Execute the task described above completely\n2. When ALL tasks are done, close the ticket using: tickets_close with ticketSlug: \"${ticket.slug}\"\n3. If you encounter blocking issues, update the ticket using: tickets_update with your findings\n4. Be thorough but efficient - this is autonomous execution\n\nMode: YOLO (autonomous execution - you have full permission to proceed)`;\n}\n\n// ============================================================================\n// Stream-JSON Log Parsing\n// ============================================================================\n\n/**\n * Parsed execution event from stream-json log\n */\nexport interface ExecutionEvent {\n type: \"tool_use\" | \"tool_result\" | \"text\" | \"error\";\n timestamp?: string;\n toolName?: string;\n toolInput?: Record<string, unknown>;\n toolResult?: string;\n text?: string;\n error?: string;\n}\n\n/**\n * Parsed execution summary from stream-json log\n */\nexport interface ExecutionSummary {\n /** Tool calls made during execution */\n toolCalls: Array<{\n tool: string;\n input: Record<string, unknown>;\n result?: string;\n success: boolean;\n }>;\n /** Final response text */\n finalResponse?: string;\n /** Whether ticket was closed */\n ticketClosed: boolean;\n /** Any errors encountered */\n errors: string[];\n /** Human-readable summary */\n summary: string;\n}\n\n/**\n * Parse a stream-json log file into execution events.\n *\n * Stream-json format is newline-delimited JSON objects.\n * Each line contains event type and data.\n *\n * @param logContent - Raw log file contents\n * @returns Array of parsed events\n */\nexport function parseStreamJsonLog(logContent: string): ExecutionEvent[] {\n const events: ExecutionEvent[] = [];\n const lines = logContent.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"===\")) continue;\n\n try {\n const json = JSON.parse(trimmed);\n\n // Handle different event types from Claude's stream-json\n if (json.type === \"tool_use\" || json.tool_use) {\n const toolUse = json.tool_use || json;\n events.push({\n type: \"tool_use\",\n toolName: toolUse.name || toolUse.tool,\n toolInput: toolUse.input || toolUse.arguments || {},\n });\n } else if (json.type === \"tool_result\" || json.tool_result) {\n const toolResult = json.tool_result || json;\n events.push({\n type: \"tool_result\",\n toolResult: typeof toolResult.content === \"string\"\n ? toolResult.content\n : JSON.stringify(toolResult.content),\n });\n } else if (json.type === \"text\" || json.content) {\n const text = json.text || json.content;\n if (typeof text === \"string\" && text.trim()) {\n events.push({\n type: \"text\",\n text: text,\n });\n }\n } else if (json.type === \"content_block_delta\" && json.delta?.text) {\n events.push({\n type: \"text\",\n text: json.delta.text,\n });\n } else if (json.type === \"error\" || json.error) {\n events.push({\n type: \"error\",\n error: json.error?.message || json.message || JSON.stringify(json),\n });\n } else if (json.result) {\n // Sometimes results come in a \"result\" wrapper\n if (json.result.tool_use) {\n events.push({\n type: \"tool_use\",\n toolName: json.result.tool_use.name,\n toolInput: json.result.tool_use.input || {},\n });\n }\n }\n } catch {\n // Not JSON - might be plain text error or header\n if (trimmed.length > 10 && !trimmed.includes(\"YOLO\") && !trimmed.includes(\"===\")) {\n // Could be an error message\n events.push({\n type: \"text\",\n text: trimmed,\n });\n }\n }\n }\n\n return events;\n}\n\n/**\n * Generate an execution summary from parsed events.\n *\n * @param events - Parsed execution events\n * @returns Execution summary with tool calls and final response\n */\nexport function generateExecutionSummary(events: ExecutionEvent[]): ExecutionSummary {\n const toolCalls: ExecutionSummary[\"toolCalls\"] = [];\n const errors: string[] = [];\n let finalResponse = \"\";\n let ticketClosed = false;\n\n let currentTool: { tool: string; input: Record<string, unknown> } | null = null;\n\n for (const event of events) {\n switch (event.type) {\n case \"tool_use\":\n if (currentTool) {\n // Previous tool had no result\n toolCalls.push({ ...currentTool, success: true });\n }\n currentTool = {\n tool: event.toolName || \"unknown\",\n input: event.toolInput || {},\n };\n // Check if this is a ticket close\n if (event.toolName?.includes(\"close\") || event.toolName?.includes(\"tickets_close\")) {\n ticketClosed = true;\n }\n break;\n\n case \"tool_result\":\n if (currentTool) {\n toolCalls.push({\n ...currentTool,\n result: event.toolResult?.slice(0, 200), // Truncate long results\n success: !event.toolResult?.toLowerCase().includes(\"error\"),\n });\n currentTool = null;\n }\n break;\n\n case \"text\":\n if (event.text) {\n finalResponse += event.text;\n }\n break;\n\n case \"error\":\n if (event.error) {\n errors.push(event.error);\n }\n break;\n }\n }\n\n // Handle any pending tool call\n if (currentTool) {\n toolCalls.push({ ...currentTool, success: true });\n }\n\n // Generate human-readable summary\n const summary = generateHumanSummary(toolCalls, finalResponse, ticketClosed, errors);\n\n return {\n toolCalls,\n finalResponse: finalResponse.trim() || undefined,\n ticketClosed,\n errors,\n summary,\n };\n}\n\n/**\n * Generate a human-readable summary from execution data.\n */\nfunction generateHumanSummary(\n toolCalls: ExecutionSummary[\"toolCalls\"],\n finalResponse: string,\n ticketClosed: boolean,\n errors: string[]\n): string {\n const parts: string[] = [];\n\n // Tool usage summary\n if (toolCalls.length > 0) {\n const toolNames = [...new Set(toolCalls.map((t) => t.tool))];\n parts.push(`Tools: ${toolNames.join(\", \")}`);\n }\n\n // Status\n if (ticketClosed) {\n parts.push(\"✅ Ticket closed\");\n } else if (errors.length > 0) {\n parts.push(`❌ Errors: ${errors.length}`);\n }\n\n // Final response excerpt\n if (finalResponse) {\n const excerpt = finalResponse.slice(0, 150).replace(/\\n/g, \" \");\n parts.push(`Response: ${excerpt}${finalResponse.length > 150 ? \"...\" : \"\"}`);\n }\n\n return parts.join(\" | \") || \"No details available\";\n}\n\n/**\n * Extract a brief summary from Claude's log output.\n *\n * Parses stream-json format and generates human-readable summary.\n *\n * @param logContent - Full log file contents\n * @returns Brief summary or undefined\n */\nexport function extractSummaryFromLog(logContent: string): string | undefined {\n const events = parseStreamJsonLog(logContent);\n\n if (events.length === 0) {\n // Fallback for non-JSON logs\n const lines = logContent.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith(\"===\") && trimmed.length > 20) {\n return trimmed.slice(0, 100);\n }\n }\n return undefined;\n }\n\n const summary = generateExecutionSummary(events);\n return summary.summary;\n}\n\n/**\n * Get detailed execution summary from log file.\n *\n * @param logContent - Full log file contents\n * @returns Full execution summary with tool calls\n */\nexport function getDetailedSummary(logContent: string): ExecutionSummary {\n const events = parseStreamJsonLog(logContent);\n return generateExecutionSummary(events);\n}\n","/**\n * Watcher Poll - Polling Logic for YOLO Tickets\n *\n * Handles fetching YOLO-flagged tickets from Convex and claiming them\n * for execution. Used by the daemon's polling loop.\n *\n * Flow:\n * 1. Query Convex for open tickets with yolo: true\n * 2. Check capacity (currentlyExecuting.length < maxParallel)\n * 3. Claim ticket via workMcpTicket mutation\n * 4. Return ticket data for spawning\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport type { WatcherConfig, WatcherTicket } from \"./watcher_types.js\";\nimport { readStatus } from \"./watcher_state.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Result from a poll operation\n */\nexport interface PollResult {\n /** Whether the poll was successful */\n success: boolean;\n\n /** Tickets available for execution */\n availableTickets: WatcherTicket[];\n\n /** How many we can actually pick up (based on capacity) */\n canPickUp: number;\n\n /** Error message if poll failed */\n error?: string;\n}\n\n/**\n * Result from claiming a ticket\n */\nexport interface ClaimResult {\n /** Whether claim was successful */\n success: boolean;\n\n /** The claimed ticket data */\n ticket?: WatcherTicket;\n\n /** Error message if claim failed */\n error?: string;\n}\n\n// ============================================================================\n// Convex Queries\n// ============================================================================\n\n/**\n * Fetch YOLO tickets from Convex.\n *\n * Queries for tickets with status=\"open\" AND yolo=true.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @returns Poll result with available tickets\n */\nexport async function fetchYoloTickets(\n client: ConvexHttpClient,\n config: WatcherConfig\n): Promise<PollResult> {\n try {\n // Query for YOLO tickets\n const tickets = await client.query<WatcherTicket[]>(\"mcp_tickets:listYoloTickets\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n });\n\n // Check current capacity\n const status = readStatus(config.workingDirectory);\n const currentlyExecuting = status?.currentlyExecuting?.length ?? 0;\n const canPickUp = Math.max(0, config.maxParallel - currentlyExecuting);\n\n return {\n success: true,\n availableTickets: tickets,\n canPickUp,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n availableTickets: [],\n canPickUp: 0,\n error: `Failed to fetch YOLO tickets: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Claim a ticket for execution.\n *\n * Calls workMcpTicket mutation to transition ticket from open to working.\n * This also returns the full ticket content for execution.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @param ticketSlug - Ticket to claim\n * @returns Claim result with ticket data\n */\nexport async function claimTicket(\n client: ConvexHttpClient,\n config: WatcherConfig,\n ticketSlug: string\n): Promise<ClaimResult> {\n try {\n // Call workMcpTicket to claim the ticket\n // Returns the ticket object directly, or null if not found/claimable\n const result = await client.mutation<{\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string; // This is flattenedContent\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n } | null>(\"mcp_tickets:workMcpTicket\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n ticketSlug,\n });\n\n if (!result) {\n return {\n success: false,\n error: \"Ticket not found or not claimable\",\n };\n }\n\n return {\n success: true,\n ticket: {\n slug: result.slug,\n ticketNumber: result.ticketNumber,\n content: result.content,\n flattenedContent: result.content, // workMcpTicket returns flattenedContent as 'content'\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to claim ticket: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Poll and Execute Coordinator\n// ============================================================================\n\n/**\n * Options for a single poll-and-execute cycle\n */\nexport interface PollCycleOptions {\n /** Convex HTTP client */\n client: ConvexHttpClient;\n\n /** Watcher configuration */\n config: WatcherConfig;\n\n /** Callback when a ticket should be spawned */\n onSpawnTicket: (ticket: WatcherTicket) => void;\n\n /** Callback for logging */\n onLog?: (message: string) => void;\n}\n\n/**\n * Execute a single poll cycle.\n *\n * Fetches available YOLO tickets, claims as many as capacity allows,\n * and triggers spawn callback for each.\n *\n * @param options - Poll cycle options\n * @returns Number of tickets spawned\n */\nexport async function executePollCycle(options: PollCycleOptions): Promise<number> {\n const { client, config, onSpawnTicket, onLog } = options;\n const log = onLog ?? (() => {});\n\n // 1. Fetch available tickets\n log(\"[Poll] Checking for YOLO tickets...\");\n const pollResult = await fetchYoloTickets(client, config);\n\n if (!pollResult.success) {\n log(`[Poll] Error: ${pollResult.error}`);\n return 0;\n }\n\n if (pollResult.availableTickets.length === 0) {\n log(\"[Poll] No YOLO tickets pending\");\n return 0;\n }\n\n log(`[Poll] Found ${pollResult.availableTickets.length} ticket(s), capacity: ${pollResult.canPickUp}`);\n\n if (pollResult.canPickUp === 0) {\n log(\"[Poll] At capacity - waiting for current executions to complete\");\n return 0;\n }\n\n // 2. Claim and spawn tickets up to capacity\n let spawned = 0;\n const ticketsToProcess = pollResult.availableTickets.slice(0, pollResult.canPickUp);\n\n for (const pendingTicket of ticketsToProcess) {\n log(`[Poll] Claiming ticket: ${pendingTicket.slug}`);\n\n const claimResult = await claimTicket(client, config, pendingTicket.slug);\n\n if (!claimResult.success || !claimResult.ticket) {\n log(`[Poll] Failed to claim ${pendingTicket.slug}: ${claimResult.error}`);\n continue;\n }\n\n log(`[Poll] Claimed ${pendingTicket.slug} - spawning execution`);\n onSpawnTicket(claimResult.ticket);\n spawned++;\n }\n\n return spawned;\n}\n"],"mappings":"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","names":["appendFileSync","config","log","appendFileSync"]}
|
|
1
|
+
{"version":3,"sources":["../../src/watcher/watcher_daemon.ts","../../src/watcher/watcher_spawn.ts","../../src/watcher/watcher_poll.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Watcher Daemon - Background Process for YOLO Ticket Execution\n *\n * This is the standalone daemon entry point that runs as a detached\n * background process. It polls for YOLO tickets and spawns headless\n * Claude CLI processes to execute them.\n *\n * Usage (spawned by controller):\n * node watcher_daemon.js --config <base64-encoded-config>\n *\n * State files in .ppm/yolo/:\n * - watcher.pid: Daemon's process ID\n * - status.json: Current status (for MCP status tool)\n * - watcher.log: Daemon activity log\n * - logs/{ticket}.log: Individual ticket execution logs\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport { appendFileSync, readFileSync } from \"fs\";\nimport type {\n WatcherConfig,\n WatcherStatus,\n WatcherTicket,\n TrackedChildProcess,\n CompletedTicket,\n} from \"./watcher_types.js\";\nimport {\n writePid,\n writeStatus,\n readStatus,\n getLogFile,\n addCompletedTicket,\n readTicketLog,\n} from \"./watcher_state.js\";\nimport { spawnClaudeWithHandle, getDetailedSummary } from \"./watcher_spawn.js\";\nimport { executePollCycle } from \"./watcher_poll.js\";\n\n// ============================================================================\n// Global State\n// ============================================================================\n\n/** Tracked child processes by PID */\nconst childProcesses = new Map<number, TrackedChildProcess>();\n\n/** Polling interval handle */\nlet pollInterval: ReturnType<typeof setInterval> | null = null;\n\n/** Daemon configuration */\nlet config: WatcherConfig;\n\n/** Convex client */\nlet convexClient: ConvexHttpClient;\n\n/** Path to daemon log file */\nlet daemonLogFile: string;\n\n// ============================================================================\n// Logging\n// ============================================================================\n\n/**\n * Log a message to the daemon log file and console.\n */\nfunction log(message: string): void {\n const timestamp = new Date().toISOString();\n const line = `[${timestamp}] ${message}\\n`;\n\n // Write to log file\n try {\n appendFileSync(daemonLogFile, line);\n } catch {\n // Ignore log write errors\n }\n\n // Also write to console (will go to /dev/null when detached)\n console.log(message);\n}\n\n// ============================================================================\n// Child Process Management\n// ============================================================================\n\n/**\n * Spawn a Claude CLI process to execute a ticket.\n *\n * @param ticket - Ticket to execute\n */\nfunction spawnTicketExecution(ticket: WatcherTicket): void {\n log(`Spawning execution for ticket: ${ticket.slug}`);\n\n const spawnResult = spawnClaudeWithHandle({\n ticket,\n workingDirectory: config.workingDirectory,\n projectSlug: config.projectSlug,\n timeout: config.ticketTimeout,\n });\n\n if (!spawnResult.success || !spawnResult.child) {\n log(`Failed to spawn for ${ticket.slug}: ${spawnResult.error}`);\n return;\n }\n\n const child = spawnResult.child;\n const pid = child.pid!;\n const startedAt = new Date().toISOString();\n\n // Track the child process\n const tracked: TrackedChildProcess = {\n pid,\n ticketSlug: ticket.slug,\n ticketNumber: ticket.ticketNumber,\n startedAt,\n logFile: spawnResult.logFile!,\n };\n\n // Set timeout for the execution\n if (config.ticketTimeout > 0) {\n tracked.timeout = setTimeout(() => {\n handleTimeout(pid);\n }, config.ticketTimeout);\n }\n\n childProcesses.set(pid, tracked);\n\n // Update status file\n updateExecutingStatus();\n\n // Handle child process events\n child.on(\"exit\", (code, signal) => {\n handleChildExit(pid, code, signal);\n });\n\n child.on(\"error\", (error) => {\n log(`Process error for ${ticket.slug}: ${error.message}`);\n // Will also trigger 'exit' event\n });\n\n log(`Spawned PID ${pid} for ${ticket.slug}`);\n\n // Send macOS notification if enabled\n if (config.enableNotifications) {\n sendNotification(`YOLO: Executing ${ticket.slug}`, `Started ticket execution`);\n }\n}\n\n/**\n * Handle child process exit.\n *\n * @param pid - Process ID\n * @param code - Exit code (null if killed)\n * @param signal - Signal that killed process (if any)\n */\nfunction handleChildExit(\n pid: number,\n code: number | null,\n signal: NodeJS.Signals | null\n): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) {\n log(`Unknown child exited: PID ${pid}`);\n return;\n }\n\n // Clear timeout if set\n if (tracked.timeout) {\n clearTimeout(tracked.timeout);\n }\n\n // Remove from tracking\n childProcesses.delete(pid);\n\n // Calculate duration\n const startTime = new Date(tracked.startedAt).getTime();\n const durationMs = Date.now() - startTime;\n\n // Read log file and extract detailed summary (parses stream-json)\n const logContent = readTicketLog(config.workingDirectory, tracked.ticketSlug);\n const detailedSummary = logContent ? getDetailedSummary(logContent) : null;\n\n // Determine success (exit code 0 AND ticket was closed)\n const success = code === 0 && (detailedSummary?.ticketClosed ?? false);\n\n log(\n `Child exited: ${tracked.ticketSlug} (PID ${pid}) - ` +\n `code=${code}, signal=${signal}, duration=${Math.round(durationMs / 1000)}s, ` +\n `ticketClosed=${detailedSummary?.ticketClosed ?? false}`\n );\n\n // Log tool calls for visibility\n if (detailedSummary?.toolCalls && detailedSummary.toolCalls.length > 0) {\n log(` Tools used: ${detailedSummary.toolCalls.map(t => t.tool).join(\", \")}`);\n }\n\n // Record completion with detailed info\n const completed: CompletedTicket = {\n ticketSlug: tracked.ticketSlug,\n ticketNumber: tracked.ticketNumber,\n startedAt: tracked.startedAt,\n completedAt: new Date().toISOString(),\n durationMs,\n exitCode: code,\n success,\n summary: detailedSummary?.summary,\n logFile: tracked.logFile,\n toolCalls: detailedSummary?.toolCalls,\n ticketClosed: detailedSummary?.ticketClosed,\n errors: detailedSummary?.errors,\n };\n\n addCompletedTicket(config.workingDirectory, completed);\n\n // Update status\n updateExecutingStatus();\n\n // Send notification\n if (config.enableNotifications) {\n const emoji = success ? \"✅\" : \"❌\";\n sendNotification(\n `YOLO: ${emoji} ${tracked.ticketSlug}`,\n success ? \"Ticket completed\" : `Exited with code ${code}`\n );\n }\n}\n\n/**\n * Handle execution timeout.\n *\n * @param pid - Process ID to kill\n */\nfunction handleTimeout(pid: number): void {\n const tracked = childProcesses.get(pid);\n if (!tracked) return;\n\n log(`Timeout for ${tracked.ticketSlug} (PID ${pid}) - killing process`);\n\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Process may have already exited\n }\n\n // Note: handleChildExit will be called when process actually exits\n}\n\n/**\n * Update the currently executing list in status file.\n */\nfunction updateExecutingStatus(): void {\n const status = readStatus(config.workingDirectory);\n if (!status) return;\n\n const currentlyExecuting = Array.from(childProcesses.values()).map((p) => ({\n ticketSlug: p.ticketSlug,\n ticketNumber: p.ticketNumber,\n startedAt: p.startedAt,\n pid: p.pid,\n logFile: p.logFile,\n }));\n\n writeStatus(config.workingDirectory, {\n ...status,\n currentlyExecuting,\n lastPollAt: new Date().toISOString(),\n });\n}\n\n// ============================================================================\n// Notifications\n// ============================================================================\n\n/**\n * Send a macOS notification using osascript.\n * Silently fails on non-macOS platforms.\n *\n * @param title - Notification title\n * @param message - Notification message\n */\nfunction sendNotification(title: string, message: string): void {\n if (process.platform !== \"darwin\") return;\n\n try {\n const { execSync } = require(\"child_process\");\n const script = `display notification \"${message}\" with title \"${title}\"`;\n execSync(`osascript -e '${script}'`, { stdio: \"ignore\" });\n } catch {\n // Ignore notification errors\n }\n}\n\n// ============================================================================\n// Polling Loop\n// ============================================================================\n\n/**\n * Execute a single poll cycle.\n */\nasync function poll(): Promise<void> {\n try {\n await executePollCycle({\n client: convexClient,\n config,\n onSpawnTicket: spawnTicketExecution,\n onLog: log,\n });\n } catch (error) {\n const msg = error instanceof Error ? error.message : \"Unknown error\";\n log(`Poll error: ${msg}`);\n }\n}\n\n/**\n * Start the polling loop.\n */\nfunction startPolling(): void {\n log(`Starting poll loop (interval: ${config.pollIntervalMs}ms)`);\n\n // Run first poll immediately\n poll();\n\n // Then poll at interval\n pollInterval = setInterval(() => {\n poll();\n }, config.pollIntervalMs);\n}\n\n/**\n * Stop the polling loop.\n */\nfunction stopPolling(): void {\n if (pollInterval) {\n clearInterval(pollInterval);\n pollInterval = null;\n log(\"Stopped polling\");\n }\n}\n\n// ============================================================================\n// Shutdown\n// ============================================================================\n\n/**\n * Graceful shutdown handler.\n */\nfunction shutdown(signal: string): void {\n log(`Received ${signal} - shutting down`);\n\n // Stop polling\n stopPolling();\n\n // Kill any running child processes\n for (const [pid, tracked] of childProcesses) {\n log(`Killing child process: ${tracked.ticketSlug} (PID ${pid})`);\n if (tracked.timeout) clearTimeout(tracked.timeout);\n try {\n process.kill(pid, \"SIGTERM\");\n } catch {\n // Already exited\n }\n }\n\n // Update status to stopped\n const status = readStatus(config.workingDirectory);\n if (status) {\n writeStatus(config.workingDirectory, {\n ...status,\n state: \"stopped\",\n currentlyExecuting: [],\n });\n }\n\n log(\"Shutdown complete\");\n process.exit(0);\n}\n\n// ============================================================================\n// Main Entry Point\n// ============================================================================\n\n/**\n * Parse command line arguments and extract config.\n */\nfunction parseArgs(): WatcherConfig {\n const args = process.argv.slice(2);\n\n // Find --config argument\n const configIndex = args.indexOf(\"--config\");\n if (configIndex === -1 || !args[configIndex + 1]) {\n console.error(\"Usage: watcher_daemon --config <base64-encoded-config>\");\n process.exit(1);\n }\n\n const configB64 = args[configIndex + 1];\n\n try {\n const configJson = Buffer.from(configB64, \"base64\").toString(\"utf-8\");\n return JSON.parse(configJson) as WatcherConfig;\n } catch (error) {\n console.error(\"Failed to parse config:\", error);\n process.exit(1);\n }\n}\n\n/**\n * Main daemon entry point.\n */\nasync function main(): Promise<void> {\n // Parse configuration\n config = parseArgs();\n\n // Initialize log file\n daemonLogFile = getLogFile(config.workingDirectory);\n\n log(\"=\".repeat(50));\n log(\"YOLO Watcher Daemon Starting\");\n log(`Project: ${config.projectSlug}`);\n log(`Working Directory: ${config.workingDirectory}`);\n log(`Poll Interval: ${config.pollIntervalMs}ms`);\n log(`Max Parallel: ${config.maxParallel}`);\n log(`Ticket Timeout: ${config.ticketTimeout}ms`);\n log(\"=\".repeat(50));\n\n // Write PID file\n writePid(config.workingDirectory, process.pid);\n log(`PID: ${process.pid}`);\n\n // Initialize Convex client\n convexClient = new ConvexHttpClient(config.convexUrl);\n\n // Write initial status\n const initialStatus: WatcherStatus = {\n state: \"running\",\n pid: process.pid,\n projectSlug: config.projectSlug,\n startedAt: new Date().toISOString(),\n ticketsProcessed: 0,\n currentlyExecuting: [],\n config: {\n projectSlug: config.projectSlug,\n convexUrl: config.convexUrl,\n pollIntervalMs: config.pollIntervalMs,\n maxParallel: config.maxParallel,\n ticketTimeout: config.ticketTimeout,\n enableNotifications: config.enableNotifications,\n workingDirectory: config.workingDirectory,\n },\n };\n writeStatus(config.workingDirectory, initialStatus);\n\n // Set up signal handlers\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n\n // Send startup notification\n if (config.enableNotifications) {\n sendNotification(\"YOLO Watcher Started\", `Watching ${config.projectSlug}`);\n }\n\n // Start polling\n startPolling();\n}\n\n// Run main\nmain().catch((error) => {\n console.error(\"Daemon error:\", error);\n process.exit(1);\n});\n","/**\n * Watcher Spawn - Headless Claude CLI Spawner\n *\n * Spawns `claude` CLI processes in headless mode for ticket execution.\n * Output is captured to log files for later review.\n *\n * Key features:\n * - Cross-platform (no Terminal.app/AppleScript dependency)\n * - Output captured to .ppm/yolo/logs/{ticket}.log\n * - Child processes tracked by daemon for completion handling\n */\n\nimport { spawn, type ChildProcess } from \"child_process\";\nimport { openSync, closeSync, appendFileSync } from \"fs\";\nimport type { SpawnResult, WatcherTicket } from \"./watcher_types.js\";\nimport { getTicketLogFile } from \"./watcher_state.js\";\n\n// ============================================================================\n// Claude CLI Spawning\n// ============================================================================\n\n/**\n * Options for spawning a Claude CLI process\n */\nexport interface SpawnClaudeOptions {\n /** Ticket being executed */\n ticket: WatcherTicket;\n\n /** Working directory for Claude session */\n workingDirectory: string;\n\n /** Project slug for context */\n projectSlug: string;\n\n /** Optional timeout in ms (default: 30 minutes) */\n timeout?: number;\n}\n\n/**\n * Spawn a headless Claude CLI process to execute a ticket.\n *\n * The process runs with --dangerously-skip-permissions for autonomous execution.\n * All output (stdout + stderr) is captured to a log file.\n *\n * @param options - Spawn configuration\n * @returns Spawn result with PID and log file path, or error\n */\nexport function spawnClaudeCli(options: SpawnClaudeOptions): SpawnResult {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing (creates if doesn't exist)\n const logFd = openSync(logFile, \"w\");\n\n // Write header to log file\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI in headless mode with JSON streaming output\n // --output-format stream-json gives us detailed tool calls and results\n // --verbose is required when using stream-json with -p (print mode)\n const child = spawn(\"claude\", [\n \"-p\", prompt,\n \"--dangerously-skip-permissions\",\n \"--output-format\", \"stream-json\",\n \"--verbose\",\n ], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd], // stdin: ignore, stdout+stderr: log file\n detached: false, // NOT detached - daemon tracks it\n env: {\n ...process.env,\n // Ensure Claude doesn't try to use a TTY\n TERM: \"dumb\",\n // Disable color output for cleaner logs\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n // Close the file descriptor in the parent process\n // The child process has its own reference to the file\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n pid: child.pid,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Spawn claude and return the ChildProcess for event handling.\n *\n * This version returns the child process directly so the daemon can\n * attach event handlers for exit, error, etc.\n *\n * @param options - Spawn configuration\n * @returns Object with child process and log file, or error\n */\nexport function spawnClaudeWithHandle(options: SpawnClaudeOptions): {\n success: boolean;\n child?: ChildProcess;\n logFile?: string;\n error?: string;\n} {\n const { ticket, workingDirectory, projectSlug } = options;\n\n // Build the prompt for Claude\n const prompt = buildTicketPrompt(ticket, projectSlug);\n\n // Get log file path\n const logFile = getTicketLogFile(workingDirectory, ticket.slug);\n\n try {\n // Open log file for writing\n const logFd = openSync(logFile, \"w\");\n\n // Write header\n const header = `=== YOLO Ticket Execution ===\nTicket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\nProject: ${projectSlug}\nStarted: ${new Date().toISOString()}\nWorking Directory: ${workingDirectory}\n${\"=\".repeat(50)}\n\n`;\n appendFileSync(logFile, header);\n\n // Spawn claude CLI with JSON streaming output\n // --verbose is required when using stream-json with -p (print mode)\n const child = spawn(\"claude\", [\n \"-p\", prompt,\n \"--dangerously-skip-permissions\",\n \"--output-format\", \"stream-json\",\n \"--verbose\",\n ], {\n cwd: workingDirectory,\n stdio: [\"ignore\", logFd, logFd],\n detached: false,\n env: {\n ...process.env,\n TERM: \"dumb\",\n NO_COLOR: \"1\",\n FORCE_COLOR: \"0\",\n },\n });\n\n closeSync(logFd);\n\n if (!child.pid) {\n return {\n success: false,\n error: \"Failed to spawn claude process - no PID returned\",\n };\n }\n\n return {\n success: true,\n child,\n logFile,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to spawn claude: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Prompt Building\n// ============================================================================\n\n/**\n * Build the prompt that Claude will execute for the ticket.\n *\n * @param ticket - Ticket to execute\n * @param projectSlug - Project context\n * @returns Formatted prompt string\n */\nfunction buildTicketPrompt(ticket: WatcherTicket, projectSlug: string): string {\n // Use flattened content if available, otherwise raw content\n const content = ticket.flattenedContent || ticket.content;\n\n return `You are executing a YOLO ticket autonomously in project \"${projectSlug}\".\n\n## Ticket: ${ticket.slug}${ticket.ticketNumber ? ` (#${ticket.ticketNumber})` : \"\"}\n\n${content}\n\n---\n\n## Your Instructions\n\n1. Execute the task described above completely\n2. When ALL tasks are done, close the ticket using: tickets_close with ticketSlug: \"${ticket.slug}\"\n3. If you encounter blocking issues, update the ticket using: tickets_update with your findings\n4. Be thorough but efficient - this is autonomous execution\n\nMode: YOLO (autonomous execution - you have full permission to proceed)`;\n}\n\n// ============================================================================\n// Stream-JSON Log Parsing\n// ============================================================================\n\n/**\n * Parsed execution event from stream-json log\n */\nexport interface ExecutionEvent {\n type: \"tool_use\" | \"tool_result\" | \"text\" | \"error\";\n timestamp?: string;\n toolName?: string;\n toolInput?: Record<string, unknown>;\n toolResult?: string;\n text?: string;\n error?: string;\n}\n\n/**\n * Parsed execution summary from stream-json log\n */\nexport interface ExecutionSummary {\n /** Tool calls made during execution */\n toolCalls: Array<{\n tool: string;\n input: Record<string, unknown>;\n result?: string;\n success: boolean;\n }>;\n /** Final response text */\n finalResponse?: string;\n /** Whether ticket was closed */\n ticketClosed: boolean;\n /** Any errors encountered */\n errors: string[];\n /** Human-readable summary */\n summary: string;\n}\n\n/**\n * Parse a stream-json log file into execution events.\n *\n * Claude CLI stream-json format is newline-delimited JSON objects.\n * Structure: {\"type\": \"assistant|user|result\", \"message\": {...}, ...}\n *\n * Key event types:\n * - system: Init info with tools, session_id, model\n * - assistant: Model responses with content[] containing text/tool_use\n * - user: Tool results in content[]\n * - result: Final execution summary with success/error status\n *\n * @param logContent - Raw log file contents\n * @returns Array of parsed events\n */\nexport function parseStreamJsonLog(logContent: string): ExecutionEvent[] {\n const events: ExecutionEvent[] = [];\n const lines = logContent.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"===\")) continue;\n\n try {\n const json = JSON.parse(trimmed);\n\n // Handle Claude CLI stream-json format\n if (json.type === \"assistant\" && json.message?.content) {\n // Assistant messages contain text and tool_use in content array\n for (const block of json.message.content) {\n if (block.type === \"tool_use\") {\n events.push({\n type: \"tool_use\",\n toolName: block.name,\n toolInput: block.input || {},\n });\n } else if (block.type === \"text\" && block.text) {\n events.push({\n type: \"text\",\n text: block.text,\n });\n }\n }\n } else if (json.type === \"user\" && json.message?.content) {\n // User messages contain tool_result in content array\n for (const block of json.message.content) {\n if (block.type === \"tool_result\") {\n const resultText = Array.isArray(block.content)\n ? block.content.map((c: { text?: string }) => c.text || \"\").join(\"\")\n : typeof block.content === \"string\" ? block.content : JSON.stringify(block.content);\n events.push({\n type: \"tool_result\",\n toolResult: resultText,\n });\n }\n }\n } else if (json.type === \"result\") {\n // Final result event with success/error and summary\n if (json.is_error) {\n events.push({\n type: \"error\",\n error: json.result || \"Execution failed\",\n });\n } else if (json.result) {\n events.push({\n type: \"text\",\n text: `[FINAL] ${json.result}`,\n });\n }\n } else if (json.type === \"error\" || json.error) {\n events.push({\n type: \"error\",\n error: json.error?.message || json.message || JSON.stringify(json),\n });\n }\n } catch {\n // Not JSON - might be plain text error or header\n if (trimmed.length > 10 && !trimmed.includes(\"YOLO\") && !trimmed.includes(\"===\")) {\n events.push({\n type: \"text\",\n text: trimmed,\n });\n }\n }\n }\n\n return events;\n}\n\n/**\n * Generate an execution summary from parsed events.\n *\n * @param events - Parsed execution events\n * @returns Execution summary with tool calls and final response\n */\nexport function generateExecutionSummary(events: ExecutionEvent[]): ExecutionSummary {\n const toolCalls: ExecutionSummary[\"toolCalls\"] = [];\n const errors: string[] = [];\n let finalResponse = \"\";\n let ticketClosed = false;\n\n let currentTool: { tool: string; input: Record<string, unknown> } | null = null;\n\n for (const event of events) {\n switch (event.type) {\n case \"tool_use\":\n if (currentTool) {\n // Previous tool had no result\n toolCalls.push({ ...currentTool, success: true });\n }\n currentTool = {\n tool: event.toolName || \"unknown\",\n input: event.toolInput || {},\n };\n // Check if this is a ticket close\n if (event.toolName?.includes(\"close\") || event.toolName?.includes(\"tickets_close\")) {\n ticketClosed = true;\n }\n break;\n\n case \"tool_result\":\n if (currentTool) {\n toolCalls.push({\n ...currentTool,\n result: event.toolResult?.slice(0, 200), // Truncate long results\n success: !event.toolResult?.toLowerCase().includes(\"error\"),\n });\n currentTool = null;\n }\n break;\n\n case \"text\":\n if (event.text) {\n finalResponse += event.text;\n }\n break;\n\n case \"error\":\n if (event.error) {\n errors.push(event.error);\n }\n break;\n }\n }\n\n // Handle any pending tool call\n if (currentTool) {\n toolCalls.push({ ...currentTool, success: true });\n }\n\n // Generate human-readable summary\n const summary = generateHumanSummary(toolCalls, finalResponse, ticketClosed, errors);\n\n return {\n toolCalls,\n finalResponse: finalResponse.trim() || undefined,\n ticketClosed,\n errors,\n summary,\n };\n}\n\n/**\n * Generate a human-readable summary from execution data.\n */\nfunction generateHumanSummary(\n toolCalls: ExecutionSummary[\"toolCalls\"],\n finalResponse: string,\n ticketClosed: boolean,\n errors: string[]\n): string {\n const parts: string[] = [];\n\n // Tool usage summary\n if (toolCalls.length > 0) {\n const toolNames = [...new Set(toolCalls.map((t) => t.tool))];\n parts.push(`Tools: ${toolNames.join(\", \")}`);\n }\n\n // Status\n if (ticketClosed) {\n parts.push(\"✅ Ticket closed\");\n } else if (errors.length > 0) {\n parts.push(`❌ Errors: ${errors.length}`);\n }\n\n // Final response excerpt\n if (finalResponse) {\n const excerpt = finalResponse.slice(0, 150).replace(/\\n/g, \" \");\n parts.push(`Response: ${excerpt}${finalResponse.length > 150 ? \"...\" : \"\"}`);\n }\n\n return parts.join(\" | \") || \"No details available\";\n}\n\n/**\n * Extract a brief summary from Claude's log output.\n *\n * Parses stream-json format and generates human-readable summary.\n *\n * @param logContent - Full log file contents\n * @returns Brief summary or undefined\n */\nexport function extractSummaryFromLog(logContent: string): string | undefined {\n const events = parseStreamJsonLog(logContent);\n\n if (events.length === 0) {\n // Fallback for non-JSON logs\n const lines = logContent.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith(\"===\") && trimmed.length > 20) {\n return trimmed.slice(0, 100);\n }\n }\n return undefined;\n }\n\n const summary = generateExecutionSummary(events);\n return summary.summary;\n}\n\n/**\n * Get detailed execution summary from log file.\n *\n * @param logContent - Full log file contents\n * @returns Full execution summary with tool calls\n */\nexport function getDetailedSummary(logContent: string): ExecutionSummary {\n const events = parseStreamJsonLog(logContent);\n return generateExecutionSummary(events);\n}\n","/**\n * Watcher Poll - Polling Logic for YOLO Tickets\n *\n * Handles fetching YOLO-flagged tickets from Convex and claiming them\n * for execution. Used by the daemon's polling loop.\n *\n * Flow:\n * 1. Query Convex for open tickets with yolo: true\n * 2. Check capacity (currentlyExecuting.length < maxParallel)\n * 3. Claim ticket via workMcpTicket mutation\n * 4. Return ticket data for spawning\n */\n\nimport { ConvexHttpClient } from \"convex/browser\";\nimport type { WatcherConfig, WatcherTicket } from \"./watcher_types.js\";\nimport { readStatus } from \"./watcher_state.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Result from a poll operation\n */\nexport interface PollResult {\n /** Whether the poll was successful */\n success: boolean;\n\n /** Tickets available for execution */\n availableTickets: WatcherTicket[];\n\n /** How many we can actually pick up (based on capacity) */\n canPickUp: number;\n\n /** Error message if poll failed */\n error?: string;\n}\n\n/**\n * Result from claiming a ticket\n */\nexport interface ClaimResult {\n /** Whether claim was successful */\n success: boolean;\n\n /** The claimed ticket data */\n ticket?: WatcherTicket;\n\n /** Error message if claim failed */\n error?: string;\n}\n\n// ============================================================================\n// Convex Queries\n// ============================================================================\n\n/**\n * Fetch YOLO tickets from Convex.\n *\n * Queries for tickets with status=\"open\" AND yolo=true.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @returns Poll result with available tickets\n */\nexport async function fetchYoloTickets(\n client: ConvexHttpClient,\n config: WatcherConfig\n): Promise<PollResult> {\n try {\n // Query for YOLO tickets\n const tickets = await client.query<WatcherTicket[]>(\"mcp_tickets:listYoloTickets\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n });\n\n // Check current capacity\n const status = readStatus(config.workingDirectory);\n const currentlyExecuting = status?.currentlyExecuting?.length ?? 0;\n const canPickUp = Math.max(0, config.maxParallel - currentlyExecuting);\n\n return {\n success: true,\n availableTickets: tickets,\n canPickUp,\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n availableTickets: [],\n canPickUp: 0,\n error: `Failed to fetch YOLO tickets: ${errorMessage}`,\n };\n }\n}\n\n/**\n * Claim a ticket for execution.\n *\n * Calls workMcpTicket mutation to transition ticket from open to working.\n * This also returns the full ticket content for execution.\n *\n * @param client - Convex HTTP client\n * @param config - Watcher configuration\n * @param ticketSlug - Ticket to claim\n * @returns Claim result with ticket data\n */\nexport async function claimTicket(\n client: ConvexHttpClient,\n config: WatcherConfig,\n ticketSlug: string\n): Promise<ClaimResult> {\n try {\n // Call workMcpTicket to claim the ticket\n // Returns the ticket object directly, or null if not found/claimable\n const result = await client.mutation<{\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string; // This is flattenedContent\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n } | null>(\"mcp_tickets:workMcpTicket\", {\n projectToken: config.projectToken,\n projectSlug: config.projectSlug,\n ticketSlug,\n });\n\n if (!result) {\n return {\n success: false,\n error: \"Ticket not found or not claimable\",\n };\n }\n\n return {\n success: true,\n ticket: {\n slug: result.slug,\n ticketNumber: result.ticketNumber,\n content: result.content,\n flattenedContent: result.content, // workMcpTicket returns flattenedContent as 'content'\n },\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n return {\n success: false,\n error: `Failed to claim ticket: ${errorMessage}`,\n };\n }\n}\n\n// ============================================================================\n// Poll and Execute Coordinator\n// ============================================================================\n\n/**\n * Options for a single poll-and-execute cycle\n */\nexport interface PollCycleOptions {\n /** Convex HTTP client */\n client: ConvexHttpClient;\n\n /** Watcher configuration */\n config: WatcherConfig;\n\n /** Callback when a ticket should be spawned */\n onSpawnTicket: (ticket: WatcherTicket) => void;\n\n /** Callback for logging */\n onLog?: (message: string) => void;\n}\n\n/**\n * Execute a single poll cycle.\n *\n * Fetches available YOLO tickets, claims as many as capacity allows,\n * and triggers spawn callback for each.\n *\n * @param options - Poll cycle options\n * @returns Number of tickets spawned\n */\nexport async function executePollCycle(options: PollCycleOptions): Promise<number> {\n const { client, config, onSpawnTicket, onLog } = options;\n const log = onLog ?? (() => {});\n\n // 1. Fetch available tickets\n log(\"[Poll] Checking for YOLO tickets...\");\n const pollResult = await fetchYoloTickets(client, config);\n\n if (!pollResult.success) {\n log(`[Poll] Error: ${pollResult.error}`);\n return 0;\n }\n\n if (pollResult.availableTickets.length === 0) {\n log(\"[Poll] No YOLO tickets pending\");\n return 0;\n }\n\n log(`[Poll] Found ${pollResult.availableTickets.length} ticket(s), capacity: ${pollResult.canPickUp}`);\n\n if (pollResult.canPickUp === 0) {\n log(\"[Poll] At capacity - waiting for current executions to complete\");\n return 0;\n }\n\n // 2. Claim and spawn tickets up to capacity\n let spawned = 0;\n const ticketsToProcess = pollResult.availableTickets.slice(0, pollResult.canPickUp);\n\n for (const pendingTicket of ticketsToProcess) {\n log(`[Poll] Claiming ticket: ${pendingTicket.slug}`);\n\n const claimResult = await claimTicket(client, config, pendingTicket.slug);\n\n if (!claimResult.success || !claimResult.ticket) {\n log(`[Poll] Failed to claim ${pendingTicket.slug}: ${claimResult.error}`);\n continue;\n }\n\n log(`[Poll] Claimed ${pendingTicket.slug} - spawning execution`);\n onSpawnTicket(claimResult.ticket);\n spawned++;\n }\n\n return spawned;\n}\n"],"mappings":"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","names":["appendFileSync","config","log","appendFileSync"]}
|
package/package.json
CHANGED