{"version":3,"file":"watch.mjs","names":[],"sources":["../src/watch.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\"\nimport { basename } from \"node:path\"\nimport { getCurrentBranch, listAgentStates, listTaskRecords } from \"../../core/src/index.js\"\nimport { isDirectExecution } from \"./entrypoint.js\"\nimport { getTownHomeDir } from \"./paths.js\"\nimport { resolveRepoContext } from \"./repo-context.js\"\nimport { resolveLatestRunPointer } from \"./status.js\"\n\ninterface WatchSummarySnapshot {\n\trunId: string | null\n\tpiExitCode: number | null\n\tmode: string | null\n\tmessage: string | null\n\tstopReason: string | null\n}\n\ninterface WatchManifestSnapshot {\n\tbranch: string | null\n\tgoal: string | null\n\tplanPath: string | null\n\trecommendedPlanDir: string | null\n}\n\nexport interface WatchSnapshot {\n\trepoRoot: string\n\trepoSlug: string\n\trepoName: string\n\tbranch: string | null\n\tsummary: WatchSummarySnapshot\n\tmanifest: WatchManifestSnapshot\n\tagents: ReturnType<typeof listAgentStates>\n\ttasks: ReturnType<typeof listTaskRecords>\n}\n\nfunction readJsonIfExists<T>(path: string): T | null {\n\tif (!existsSync(path)) return null\n\n\ttry {\n\t\treturn JSON.parse(readFileSync(path, \"utf-8\")) as T\n\t} catch {\n\t\treturn null\n\t}\n}\n\nfunction truncate(text: string | null | undefined, max: number): string {\n\tif (!text) return \"—\"\n\tconst single = text.replace(/\\n/g, \" \").trim()\n\tif (single.length <= max) return single\n\treturn `${single.slice(0, max - 1)}...`\n}\n\nexport function buildWatchSnapshot(argv = process.argv.slice(2)): WatchSnapshot {\n\tconst repo = resolveRepoContext(argv)\n\tconst latest = resolveLatestRunPointer([\"--repo\", repo.repoRoot])\n\tconst summary = latest\n\t\t? (readJsonIfExists<{ runId?: string; piExitCode?: number; mode?: string; message?: string }>(latest.summaryPath) ?? null)\n\t\t: null\n\tconst manifest = latest\n\t\t? (readJsonIfExists<{ branch?: string; goal?: string | null; planPath?: string | null; recommendedPlanDir?: string | null; stopReason?: string | null }>(\n\t\t\t\tlatest.manifestPath,\n\t\t\t) ?? null)\n\t\t: null\n\n\treturn {\n\t\trepoRoot: repo.repoRoot,\n\t\trepoSlug: repo.repoSlug,\n\t\trepoName: basename(repo.repoRoot),\n\t\tbranch: getCurrentBranch(repo.repoRoot),\n\t\tsummary: {\n\t\t\trunId: summary?.runId ?? null,\n\t\t\tpiExitCode: summary?.piExitCode ?? null,\n\t\t\tmode: summary?.mode ?? null,\n\t\t\tmessage: summary?.message ?? null,\n\t\t\tstopReason: manifest?.stopReason ?? null,\n\t\t},\n\t\tmanifest: {\n\t\t\tbranch: manifest?.branch ?? null,\n\t\t\tgoal: manifest?.goal ?? null,\n\t\t\tplanPath: manifest?.planPath ?? null,\n\t\t\trecommendedPlanDir: manifest?.recommendedPlanDir ?? null,\n\t\t},\n\t\tagents: listAgentStates(repo.artifactsDir),\n\t\ttasks: listTaskRecords(repo.artifactsDir),\n\t}\n}\n\nexport function createWatchFingerprint(snapshot: WatchSnapshot): string {\n\treturn JSON.stringify({\n\t\tsummary: snapshot.summary,\n\t\tmanifest: snapshot.manifest,\n\t\tagents: snapshot.agents.map((agent) => ({\n\t\t\tagentId: agent.agentId,\n\t\t\trole: agent.role,\n\t\t\tstatus: agent.status,\n\t\t\ttaskId: agent.taskId,\n\t\t\ttask: agent.task,\n\t\t\tlastMessage: agent.lastMessage,\n\t\t\twaitingOn: agent.waitingOn,\n\t\t\tblocked: agent.blocked,\n\t\t\tupdatedAt: agent.updatedAt,\n\t\t})),\n\t\ttasks: snapshot.tasks.map((task) => ({\n\t\t\ttaskId: task.taskId,\n\t\t\ttitle: task.title,\n\t\t\tstatus: task.status,\n\t\t\tassignedAgentId: task.assignedAgentId,\n\t\t\tupdatedAt: task.updatedAt,\n\t\t})),\n\t})\n}\n\nexport function renderWatchSnapshot(snapshot: WatchSnapshot): string[] {\n\tconst branchLabel = snapshot.branch ? ` (${snapshot.branch})` : \"\"\n\tconst lines = [`[pitown] watch - ${snapshot.repoName}${branchLabel}`]\n\n\tlines.push(`- town home: ${getTownHomeDir()}`)\n\tlines.push(`- repo root: ${snapshot.repoRoot}`)\n\tif (snapshot.summary.runId) lines.push(`- latest run: ${snapshot.summary.runId}`)\n\tif (snapshot.summary.mode) lines.push(`- mode: ${snapshot.summary.mode}`)\n\tif (snapshot.summary.piExitCode !== null) lines.push(`- latest pi exit code: ${snapshot.summary.piExitCode}`)\n\tif (snapshot.summary.stopReason) lines.push(`- stop reason: ${snapshot.summary.stopReason}`)\n\tif (snapshot.manifest.goal) lines.push(`- goal: ${snapshot.manifest.goal}`)\n\tif (snapshot.manifest.planPath) lines.push(`- plan path: ${snapshot.manifest.planPath}`)\n\tif (!snapshot.manifest.planPath && snapshot.manifest.recommendedPlanDir) {\n\t\tlines.push(`- recommended plans: ${snapshot.manifest.recommendedPlanDir}`)\n\t}\n\tif (snapshot.summary.message) lines.push(`- note: ${snapshot.summary.message}`)\n\n\tlines.push(\"\")\n\tlines.push(\"Agents:\")\n\tif (snapshot.agents.length === 0) {\n\t\tlines.push(\"  (no agents)\")\n\t} else {\n\t\tfor (const agent of snapshot.agents) {\n\t\t\tconst id = agent.agentId.padEnd(14)\n\t\t\tconst role = agent.role.padEnd(10)\n\t\t\tconst status = agent.status.padEnd(10)\n\t\t\tconst task = truncate(agent.task, 56)\n\t\t\tconst msg = agent.lastMessage ? ` | ${truncate(agent.lastMessage, 36)}` : \"\"\n\t\t\tconst waiting = agent.waitingOn ? ` | waiting on: ${agent.waitingOn}` : \"\"\n\t\t\tlines.push(`  ${id}${role}${status}${task}${msg}${waiting}`)\n\t\t}\n\t}\n\n\tlines.push(\"\")\n\tlines.push(\"Tasks:\")\n\tif (snapshot.tasks.length === 0) {\n\t\tlines.push(\"  (no tasks)\")\n\t} else {\n\t\tfor (const task of snapshot.tasks) {\n\t\t\tconst id = task.taskId.padEnd(14)\n\t\t\tconst status = task.status.padEnd(12)\n\t\t\tconst assignee = task.assignedAgentId.padEnd(14)\n\t\t\tlines.push(`  ${id}${status}${assignee}${truncate(task.title, 56)}`)\n\t\t}\n\t}\n\n\treturn lines\n}\n\nfunction printSnapshot(snapshot: WatchSnapshot) {\n\tconsole.log(renderWatchSnapshot(snapshot).join(\"\\n\"))\n}\n\nexport function watchTown(argv = process.argv.slice(2)) {\n\tconst initialSnapshot = buildWatchSnapshot(argv)\n\tlet previousFingerprint = createWatchFingerprint(initialSnapshot)\n\n\tprintSnapshot(initialSnapshot)\n\tconsole.log(\"- press Ctrl+C to stop\")\n\n\tconst interval = setInterval(() => {\n\t\tconst nextSnapshot = buildWatchSnapshot(argv)\n\t\tconst nextFingerprint = createWatchFingerprint(nextSnapshot)\n\t\tif (nextFingerprint === previousFingerprint) return\n\n\t\tpreviousFingerprint = nextFingerprint\n\t\tconsole.log(\"\")\n\t\tconsole.log(`[pitown] watch update - ${new Date().toISOString()}`)\n\t\tprintSnapshot(nextSnapshot)\n\t}, 1000)\n\n\tconst stopWatching = () => {\n\t\tclearInterval(interval)\n\t}\n\n\tprocess.once(\"SIGINT\", stopWatching)\n\tprocess.once(\"SIGTERM\", stopWatching)\n}\n\nif (isDirectExecution(import.meta.url)) {\n\twatchTown()\n}\n"],"mappings":"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"}