@hua-labs/tap 0.5.0 → 0.5.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/engine/termination.ts","../../src/engine/review.ts","../../src/engine/headless-loop.ts","../../src/bridges/codex-bridge-runner.ts","../../src/config/resolve.ts","../../src/utils.ts","../../src/runtime/resolve-node.ts","../../src/engine/bridge-app-server-lifecycle.ts"],"sourcesContent":["/**\n * Termination engine — decides when a review session should stop.\n *\n * Strategies are evaluated in priority order. First non-\"continue\" verdict wins.\n * Default order: manual-stop → round-cap → repetition → quality → diff-insignificance\n */\nimport * as fs from \"node:fs\";\nimport * as crypto from \"node:crypto\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport type TerminationStrategy =\n | \"diff-insignificance\"\n | \"repetition-detection\"\n | \"quality-threshold\"\n | \"round-cap\"\n | \"manual-stop\";\n\nexport type TerminationVerdict = \"continue\" | \"stop\" | \"escalate\";\n\nexport type FindingSeverity =\n | \"critical\"\n | \"high\"\n | \"medium\"\n | \"low\"\n | \"nitpick\";\n\nexport interface ReviewFinding {\n severity: FindingSeverity;\n category: string;\n description: string;\n file?: string;\n line?: number;\n}\n\nexport interface ReviewRound {\n round: number;\n timestamp: string;\n findingCount: number;\n findings: ReviewFinding[];\n suggestedDiffLines: number;\n findingHash: string;\n}\n\nexport interface TerminationConfig {\n strategies: TerminationStrategy[];\n maxRounds: number;\n diffThreshold: number;\n repetitionThreshold: number;\n qualitySeverityFloor: FindingSeverity;\n}\n\nexport interface TerminationContext {\n round: number;\n rounds: ReviewRound[];\n stopSignalPath: string;\n config: TerminationConfig;\n}\n\nexport interface TerminationResult {\n verdict: TerminationVerdict;\n reason: string;\n strategy: TerminationStrategy;\n summary: string;\n}\n\n// ── Defaults ───────────────────────────────────────────────────────\n\nexport const DEFAULT_TERMINATION_CONFIG: TerminationConfig = {\n strategies: [\n \"manual-stop\",\n \"round-cap\",\n \"repetition-detection\",\n \"quality-threshold\",\n \"diff-insignificance\",\n ],\n maxRounds: 5,\n diffThreshold: 3,\n repetitionThreshold: 2,\n qualitySeverityFloor: \"high\",\n};\n\n// ── Severity ranking ───────────────────────────────────────────────\n\nconst SEVERITY_RANK: Record<FindingSeverity, number> = {\n critical: 5,\n high: 4,\n medium: 3,\n low: 2,\n nitpick: 1,\n};\n\nfunction isAtOrAbove(\n severity: FindingSeverity,\n floor: FindingSeverity,\n): boolean {\n return SEVERITY_RANK[severity] >= SEVERITY_RANK[floor];\n}\n\n// ── Finding hash ───────────────────────────────────────────────────\n\nexport function computeFindingHash(findings: ReviewFinding[]): string {\n const normalized = findings\n .filter((f) => isAtOrAbove(f.severity, \"high\"))\n .map((f) => `${f.category}:${f.description.slice(0, 100)}`)\n .sort()\n .join(\"|\");\n\n if (!normalized) return \"empty\";\n\n return crypto\n .createHash(\"sha256\")\n .update(normalized)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\n// ── Strategy evaluators ────────────────────────────────────────────\n\nfunction evalManualStop(ctx: TerminationContext): TerminationResult | null {\n if (fs.existsSync(ctx.stopSignalPath)) {\n return {\n verdict: \"stop\",\n reason: `Manual stop signal found at ${ctx.stopSignalPath}`,\n strategy: \"manual-stop\",\n summary: `Review stopped manually at round ${ctx.round}`,\n };\n }\n return null;\n}\n\nfunction evalRoundCap(ctx: TerminationContext): TerminationResult | null {\n if (ctx.round >= ctx.config.maxRounds) {\n return {\n verdict: \"stop\",\n reason: `Round cap reached (${ctx.round}/${ctx.config.maxRounds})`,\n strategy: \"round-cap\",\n summary: `Review stopped at round cap (${ctx.config.maxRounds})`,\n };\n }\n return null;\n}\n\nfunction evalRepetition(ctx: TerminationContext): TerminationResult | null {\n if (ctx.rounds.length < 2) return null;\n\n const latest = ctx.rounds[ctx.rounds.length - 1];\n if (!latest) return null;\n\n let count = 0;\n for (const round of ctx.rounds) {\n if (round.findingHash === latest.findingHash) count++;\n }\n\n if (count >= ctx.config.repetitionThreshold) {\n return {\n verdict: \"stop\",\n reason: `Same finding hash repeated ${count} times (threshold: ${ctx.config.repetitionThreshold})`,\n strategy: \"repetition-detection\",\n summary: `Review going in circles — same findings repeated ${count}x`,\n };\n }\n\n return null;\n}\n\nfunction evalQualityThreshold(\n ctx: TerminationContext,\n): TerminationResult | null {\n if (ctx.rounds.length === 0) return null;\n\n const latest = ctx.rounds[ctx.rounds.length - 1];\n if (!latest) return null;\n\n // Guard: if parser extracted nothing at all (0 findings + 0 diff lines),\n // treat as inconclusive — not \"clean\". The parser may have failed to\n // extract from malformed output.\n if (\n latest.findingCount === 0 &&\n latest.suggestedDiffLines === 0 &&\n latest.findings.length === 0\n ) {\n return null; // inconclusive — continue to next strategy\n }\n\n const significantFindings = latest.findings.filter((f) =>\n isAtOrAbove(f.severity, ctx.config.qualitySeverityFloor),\n );\n\n if (significantFindings.length === 0) {\n return {\n verdict: \"stop\",\n reason: `No findings at ${ctx.config.qualitySeverityFloor}+ severity in round ${ctx.round}`,\n strategy: \"quality-threshold\",\n summary: `Review clean — no ${ctx.config.qualitySeverityFloor}+ findings in round ${ctx.round}`,\n };\n }\n\n return null;\n}\n\nfunction evalDiffInsignificance(\n ctx: TerminationContext,\n): TerminationResult | null {\n if (ctx.rounds.length === 0) return null;\n\n const latest = ctx.rounds[ctx.rounds.length - 1];\n if (!latest) return null;\n\n // Guard: same as quality-threshold — empty output is inconclusive, not trivial\n if (\n latest.findingCount === 0 &&\n latest.suggestedDiffLines === 0 &&\n latest.findings.length === 0\n ) {\n return null;\n }\n\n if (latest.suggestedDiffLines < ctx.config.diffThreshold) {\n return {\n verdict: \"stop\",\n reason: `Suggested diff (${latest.suggestedDiffLines} lines) below threshold (${ctx.config.diffThreshold})`,\n strategy: \"diff-insignificance\",\n summary: `Review suggestions are trivial (${latest.suggestedDiffLines} lines)`,\n };\n }\n\n return null;\n}\n\nconst STRATEGY_EVALUATORS: Record<\n TerminationStrategy,\n (ctx: TerminationContext) => TerminationResult | null\n> = {\n \"manual-stop\": evalManualStop,\n \"round-cap\": evalRoundCap,\n \"repetition-detection\": evalRepetition,\n \"quality-threshold\": evalQualityThreshold,\n \"diff-insignificance\": evalDiffInsignificance,\n};\n\n// ── Main evaluator ─────────────────────────────────────────────────\n\nexport function evaluate(ctx: TerminationContext): TerminationResult {\n for (const strategy of ctx.config.strategies) {\n const evaluator = STRATEGY_EVALUATORS[strategy];\n if (!evaluator) continue;\n\n const result = evaluator(ctx);\n if (result) return result;\n }\n\n return {\n verdict: \"continue\",\n reason: \"All strategies passed — review continues\",\n strategy:\n ctx.config.strategies[ctx.config.strategies.length - 1] ?? \"round-cap\",\n summary: `Round ${ctx.round} complete, continuing`,\n };\n}\n","/**\n * Review engine — detects review requests, builds prompts, parses output.\n *\n * This module handles the \"what\" of review sessions.\n * The termination engine handles the \"when to stop.\"\n * The bridge handles the \"how to deliver.\"\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type {\n ReviewFinding,\n ReviewRound,\n FindingSeverity,\n TerminationConfig,\n} from \"./termination.js\";\nimport { computeFindingHash } from \"./termination.js\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport type AgentRole = \"reviewer\" | \"validator\" | \"long-running\";\n\nexport interface ReviewRequest {\n sourcePath: string;\n sender: string;\n recipient: string;\n prNumber: number;\n branch?: string;\n generation: string;\n isReReview: boolean;\n round: number;\n}\n\nexport interface ReviewSession {\n request: ReviewRequest;\n agentName: string;\n role: AgentRole;\n rounds: ReviewRound[];\n startedAt: string;\n terminatedAt?: string;\n reviewFilePath: string;\n}\n\nexport interface ReviewEngineConfig {\n role: AgentRole;\n generation: string;\n commsDir: string;\n repoRoot: string;\n agentName: string;\n termination: TerminationConfig;\n}\n\nexport interface HeadlessConfig {\n enabled: boolean;\n role: AgentRole;\n termination: TerminationConfig;\n}\n\n// ── Request Detection ──────────────────────────────────────────────\n\nconst REVIEW_KEYWORDS = [/리뷰\\s*요청/, /review[- ]?request/i];\n\nconst REREVIEW_KEYWORDS = [/재리뷰/, /re-?review/i];\n\nconst PR_NUMBER_PATTERNS = [\n /PR\\s*#?\\s*(\\d+)/i,\n /pull\\/(\\d+)/,\n /review[-_ ]?(\\d+)/i,\n];\n\nfunction trimAddress(value: string): string {\n return value.trim();\n}\n\nfunction canonicalizeAgentId(value: string): string {\n return trimAddress(value).replace(/-/g, \"_\").toLowerCase();\n}\n\nexport function isOwnMessageAddress(\n sender: string,\n agentId: string,\n agentName: string,\n): boolean {\n const normalizedSender = trimAddress(sender);\n if (!normalizedSender) return false;\n\n return (\n canonicalizeAgentId(normalizedSender) === canonicalizeAgentId(agentId) ||\n normalizedSender.toLowerCase() === trimAddress(agentName).toLowerCase()\n );\n}\n\n/**\n * Parse inbox filename to extract routing info.\n * Format: YYYYMMDD-sender-recipient-subject.md\n */\nexport function parseInboxFilename(filename: string): {\n date: string;\n sender: string;\n recipient: string;\n subject: string;\n} | null {\n const base = path.basename(filename, \".md\");\n const match = base.match(/^(\\d{8})-([^-]+)-([^-]+)-(.+)$/);\n if (!match) return null;\n\n return {\n date: match[1],\n sender: match[2],\n recipient: match[3],\n subject: match[4],\n };\n}\n\n/**\n * Extract PR number from text content.\n */\nexport function extractPrNumber(text: string): number | null {\n for (const pattern of PR_NUMBER_PATTERNS) {\n const match = text.match(pattern);\n if (match?.[1]) return parseInt(match[1], 10);\n }\n return null;\n}\n\n/**\n * Detect if a file represents a review request.\n * Returns a ReviewRequest if detected, null otherwise.\n */\nexport function detectReviewRequest(\n filePath: string,\n content: string,\n generation: string,\n): ReviewRequest | null {\n const parsed = parseInboxFilename(filePath);\n if (!parsed) return null;\n\n const fullText = `${parsed.subject} ${content}`;\n\n // Check for review keywords\n const isReview = REVIEW_KEYWORDS.some((re) => re.test(fullText));\n const isReReview = REREVIEW_KEYWORDS.some((re) => re.test(fullText));\n\n if (!isReview && !isReReview) return null;\n\n // Extract PR number\n const prNumber = extractPrNumber(fullText);\n if (!prNumber) return null;\n\n return {\n sourcePath: filePath,\n sender: parsed.sender,\n recipient: parsed.recipient,\n prNumber,\n generation,\n isReReview,\n round: isReReview ? 2 : 1, // Will be adjusted by session tracking\n };\n}\n\n// ── Review Prompt ──────────────────────────────────────────────────\n\nexport function buildReviewPrompt(\n request: ReviewRequest,\n agentName: string,\n round: number,\n): string {\n const roundLabel = round > 1 ? ` (re-review round ${round})` : \"\";\n\n return [\n `You are a code reviewer for the HUA Platform monorepo.`,\n ``,\n `## Task`,\n `Review PR #${request.prNumber}${roundLabel}.`,\n ``,\n `## Instructions`,\n `1. Run: gh pr diff ${request.prNumber}`,\n `2. Read changed files for understanding`,\n `3. Apply review checklist: security > data integrity > performance > error handling > code quality`,\n `4. Write structured findings`,\n ``,\n `## Output`,\n `Write review to: ${path.join(\"reviews\", request.generation, `review-PR${request.prNumber}-${agentName}.md`)}`,\n ``,\n `### Review File Format`,\n `\\`\\`\\`markdown`,\n `---`,\n `date: ${new Date().toISOString().split(\"T\")[0]}`,\n `reviewer: ${agentName}`,\n `pr: ${request.prNumber}`,\n `round: ${round}`,\n `status: clean | p1-Nitems | p2-Nitems`,\n `merge: merge | fix-then-merge | hold`,\n `---`,\n ``,\n `## Findings`,\n ``,\n `### Critical / High`,\n `- [severity] [category] file:line — description`,\n ``,\n `### Medium / Low`,\n `- [severity] [category] file:line — description`,\n ``,\n `## Checks`,\n `- [ ] Build verified`,\n `- [ ] Typecheck passed`,\n `- [ ] Scope check (only expected files changed)`,\n ``,\n `## Suggested Diff Lines`,\n `{number of lines the author should change to address findings}`,\n ``,\n `## Decision`,\n `{one-line merge recommendation}`,\n `\\`\\`\\``,\n ``,\n `## After Review`,\n `- Update reviews/INDEX.md`,\n `- Write inbox reply to ${request.sender}`,\n `- Commit and push comms changes`,\n ].join(\"\\n\");\n}\n\n// ── Review Output Parsing ──────────────────────────────────────────\n\nconst SEVERITY_PATTERNS: Record<FindingSeverity, RegExp> = {\n critical: /\\bcritical\\b/i,\n high: /\\bhigh\\b/i,\n medium: /\\bmedium\\b/i,\n low: /\\blow\\b/i,\n nitpick: /\\bnitpick\\b/i,\n};\n\nconst CATEGORY_PATTERNS = [\n \"security\",\n \"performance\",\n \"correctness\",\n \"data-integrity\",\n \"error-handling\",\n \"code-quality\",\n \"style\",\n];\n\n/**\n * Parse frontmatter from review file.\n */\nexport function parseFrontmatter(\n content: string,\n): Record<string, string> | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match?.[1]) return null;\n\n const fields: Record<string, string> = {};\n for (const line of match[1].split(\"\\n\")) {\n const kv = line.match(/^(\\w+):\\s*(.+)$/);\n if (kv?.[1] && kv[2]) {\n fields[kv[1]] = kv[2].trim();\n }\n }\n return fields;\n}\n\n/**\n * Extract suggested diff lines from review content.\n */\nexport function extractSuggestedDiffLines(content: string): number {\n const match = content.match(/## Suggested Diff Lines\\s*\\n\\s*(\\d+)/i);\n if (match?.[1]) return parseInt(match[1], 10);\n\n // Fallback: count lines in code blocks that look like suggestions\n const codeBlocks = content.match(/```[\\s\\S]*?```/g) ?? [];\n let totalLines = 0;\n for (const block of codeBlocks) {\n totalLines += block.split(\"\\n\").length - 2; // minus fences\n }\n return totalLines;\n}\n\n/**\n * Extract findings from review content.\n * Best-effort parsing — reviews may not follow exact format.\n */\nexport function extractFindings(content: string): ReviewFinding[] {\n const findings: ReviewFinding[] = [];\n\n // Match lines that look like finding entries\n const lines = content.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"-\") && !trimmed.startsWith(\"*\")) continue;\n\n // Detect severity\n let severity: FindingSeverity = \"medium\";\n for (const [sev, pattern] of Object.entries(SEVERITY_PATTERNS)) {\n if (pattern.test(trimmed)) {\n severity = sev as FindingSeverity;\n break;\n }\n }\n\n // Detect category\n let category = \"general\";\n for (const cat of CATEGORY_PATTERNS) {\n if (trimmed.toLowerCase().includes(cat)) {\n category = cat;\n break;\n }\n }\n\n // Extract file:line if present\n const fileMatch = trimmed.match(/([a-zA-Z0-9_/.-]+\\.[a-zA-Z]+):(\\d+)/);\n\n // Only include if it looks like an actual finding (has severity keyword or file ref)\n const hasSeverityKeyword = Object.values(SEVERITY_PATTERNS).some((p) =>\n p.test(trimmed),\n );\n if (hasSeverityKeyword || fileMatch) {\n findings.push({\n severity,\n category,\n description: trimmed.replace(/^[-*]\\s*/, \"\").slice(0, 200),\n file: fileMatch?.[1],\n line: fileMatch?.[2] ? parseInt(fileMatch[2], 10) : undefined,\n });\n }\n }\n\n return findings;\n}\n\n/**\n * Parse a review output file into a ReviewRound.\n */\nexport function parseReviewOutput(\n reviewFilePath: string,\n round: number,\n): ReviewRound | null {\n if (!fs.existsSync(reviewFilePath)) return null;\n\n const content = fs.readFileSync(reviewFilePath, \"utf-8\");\n const findings = extractFindings(content);\n const suggestedDiffLines = extractSuggestedDiffLines(content);\n\n return {\n round,\n timestamp: new Date().toISOString(),\n findingCount: findings.length,\n findings,\n suggestedDiffLines,\n findingHash: computeFindingHash(findings),\n };\n}\n\n// ── Review File Path ───────────────────────────────────────────────\n\nexport function reviewFilePath(\n commsDir: string,\n generation: string,\n prNumber: number,\n agentName: string,\n): string {\n return path.join(\n commsDir,\n \"reviews\",\n generation,\n `review-PR${prNumber}-${agentName}.md`,\n );\n}\n\n// ── Stale Detection ────────────────────────────────────────────────\n\n/**\n * Check if a review request is stale (already handled).\n * Mirrors PS1 Test-IsStaleRequest logic.\n */\nexport function isStaleReviewRequest(\n request: ReviewRequest,\n commsDir: string,\n agentName: string,\n): boolean {\n // 1. Check if review file exists and is newer than request\n const revPath = reviewFilePath(\n commsDir,\n request.generation,\n request.prNumber,\n agentName,\n );\n if (fs.existsSync(revPath) && fs.existsSync(request.sourcePath)) {\n const reviewStat = fs.statSync(revPath);\n const requestStat = fs.statSync(request.sourcePath);\n if (reviewStat.mtimeMs > requestStat.mtimeMs) return true;\n }\n\n return false;\n}\n\n// ── Processed Marker ───────────────────────────────────────────────\n\nexport function computeRequestMarkerId(filePath: string): string {\n const stat = fs.statSync(filePath);\n const input = `${filePath}|${stat.mtimeMs}`;\n return crypto.createHash(\"sha1\").update(input).digest(\"hex\");\n}\n\nexport function isAlreadyProcessed(\n stateDir: string,\n filePath: string,\n): boolean {\n const markerId = computeRequestMarkerId(filePath);\n return fs.existsSync(path.join(stateDir, \"processed\", `${markerId}.done`));\n}\n\nexport function unmarkProcessed(\n stateDir: string,\n request: ReviewRequest,\n): void {\n const markerId = computeRequestMarkerId(request.sourcePath);\n const markerPath = path.join(stateDir, \"processed\", `${markerId}.done`);\n if (fs.existsSync(markerPath)) {\n fs.unlinkSync(markerPath);\n }\n}\n\nexport function markAsProcessed(\n stateDir: string,\n request: ReviewRequest,\n): void {\n const markerId = computeRequestMarkerId(request.sourcePath);\n const markerDir = path.join(stateDir, \"processed\");\n fs.mkdirSync(markerDir, { recursive: true });\n const markerPath = path.join(markerDir, `${markerId}.done`);\n const payload = {\n prNumber: request.prNumber,\n sourcePath: request.sourcePath,\n processedAt: new Date().toISOString(),\n };\n const tmp = `${markerPath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), \"utf-8\");\n fs.renameSync(tmp, markerPath);\n}\n\n// ── Bridge Receipt ─────────────────────────────────────────────────\n\n/**\n * Write immediate inbox acknowledgment before review starts.\n * Mirrors PS1 Write-BridgeReceipt pattern.\n */\nexport function writeReviewReceipt(\n commsDir: string,\n request: ReviewRequest,\n agentName: string,\n): string {\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n const filename = `${date}-${agentName}-${request.sender}-PR${request.prNumber}-ack.md`;\n const content = [\n `## ${agentName} > ${request.sender}`,\n ``,\n `- PR #${request.prNumber} review request received.`,\n `- headless reviewer processing.`,\n `- request: ${path.basename(request.sourcePath)}`,\n ].join(\"\\n\");\n\n const inboxDir = path.join(commsDir, \"inbox\");\n fs.mkdirSync(inboxDir, { recursive: true });\n const inboxPath = path.join(inboxDir, filename);\n const tmp = `${inboxPath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, content, \"utf-8\");\n fs.renameSync(tmp, inboxPath);\n return inboxPath;\n}\n\n// ── Orchestrator Entry Point ───────────────────────────────────\n\n/**\n * Check if the current bridge process is running in headless reviewer mode.\n * Reads from env vars set by engine/bridge.ts startBridge().\n */\nexport function isHeadlessReviewer(): boolean {\n return process.env.TAP_HEADLESS === \"true\";\n}\n\n/**\n * Get headless reviewer configuration from env vars.\n * Returns null if not in headless mode.\n */\nexport function getHeadlessEnvConfig(): {\n role: string;\n maxRounds: number;\n qualityFloor: string;\n} | null {\n if (!isHeadlessReviewer()) return null;\n return {\n role: process.env.TAP_AGENT_ROLE ?? \"reviewer\",\n maxRounds: parseInt(process.env.TAP_MAX_REVIEW_ROUNDS ?? \"5\", 10),\n qualityFloor: process.env.TAP_QUALITY_FLOOR ?? \"high\",\n };\n}\n\n/**\n * Scan inbox for pending review requests.\n * This is the entry point for the headless review loop.\n *\n * Phase 3 will wire this into the bridge runner's poll cycle:\n * 1. scanInboxForReviews() → detect pending requests\n * 2. For each: writeReviewReceipt() → dispatch to bridge → parseReviewOutput()\n * 3. evaluate() termination → continue or stop\n */\nexport function scanInboxForReviews(\n commsDir: string,\n stateDir: string,\n generation: string,\n agentName: string,\n agentId: string = agentName,\n): ReviewRequest[] {\n const inboxDir = path.join(commsDir, \"inbox\");\n if (!fs.existsSync(inboxDir)) return [];\n\n const files = fs.readdirSync(inboxDir).filter((f) => f.endsWith(\".md\"));\n const requests: ReviewRequest[] = [];\n\n for (const file of files) {\n const filePath = path.join(inboxDir, file);\n const content = fs.readFileSync(filePath, \"utf-8\");\n const request = detectReviewRequest(filePath, content, generation);\n\n if (!request) continue;\n\n // Only process requests addressed to this agent or broadcast (\"전체\"/\"all\")\n const to = request.recipient.toLowerCase();\n if (\n to !== agentName.toLowerCase() &&\n to !== \"전체\" &&\n to !== \"all\" &&\n to !== \"\"\n ) {\n continue;\n }\n\n if (isOwnMessageAddress(request.sender, agentId, agentName)) continue;\n\n if (isStaleReviewRequest(request, commsDir, agentName)) continue;\n if (isAlreadyProcessed(stateDir, filePath)) continue;\n\n requests.push(request);\n }\n\n return requests;\n}\n","/**\n * Headless review loop — poll-based review orchestrator for bridge processes.\n *\n * Runs alongside the bridge script. When TAP_HEADLESS=true:\n * 1. Periodically scans inbox for review requests\n * 2. Writes review dispatch files that the bridge picks up\n * 3. Monitors review output for completion\n * 4. Evaluates termination conditions\n * 5. Continues or stops the review session\n *\n * This is a control loop, not a WebSocket client — the bridge handles dispatch.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n scanInboxForReviews,\n isHeadlessReviewer,\n getHeadlessEnvConfig,\n buildReviewPrompt,\n writeReviewReceipt,\n parseReviewOutput,\n reviewFilePath,\n markAsProcessed,\n unmarkProcessed,\n type ReviewRequest,\n type ReviewSession,\n} from \"./review.js\";\nimport {\n evaluate,\n DEFAULT_TERMINATION_CONFIG,\n type TerminationContext,\n type TerminationConfig,\n type FindingSeverity,\n} from \"./termination.js\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface HeadlessLoopOptions {\n commsDir: string;\n stateDir: string;\n repoRoot: string;\n agentId?: string;\n agentName: string;\n generation: string;\n pollIntervalMs: number;\n}\n\nexport interface HeadlessLoopState {\n running: boolean;\n activeSession: ReviewSession | null;\n completedSessions: number;\n lastPollAt: string | null;\n}\n\n// ── Loop implementation ────────────────────────────────────────────\n\nexport function createHeadlessLoop(options: HeadlessLoopOptions): {\n start: () => void;\n stop: () => void;\n getState: () => HeadlessLoopState;\n} {\n const envConfig = getHeadlessEnvConfig();\n const terminationConfig: TerminationConfig = {\n ...DEFAULT_TERMINATION_CONFIG,\n maxRounds: envConfig?.maxRounds ?? DEFAULT_TERMINATION_CONFIG.maxRounds,\n qualitySeverityFloor:\n (envConfig?.qualityFloor as FindingSeverity) ??\n DEFAULT_TERMINATION_CONFIG.qualitySeverityFloor,\n };\n\n const state: HeadlessLoopState = {\n running: false,\n activeSession: null,\n completedSessions: 0,\n lastPollAt: null,\n };\n\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function log(msg: string): void {\n const ts = new Date().toISOString();\n console.error(`[${ts}] [headless-loop] ${msg}`);\n }\n\n function writeStateFile(): void {\n try {\n const payload = {\n running: state.running,\n agentName: options.agentName,\n generation: options.generation,\n pollIntervalMs: options.pollIntervalMs,\n completedSessions: state.completedSessions,\n lastPollAt: state.lastPollAt,\n activeReview: state.activeSession\n ? {\n prNumber: state.activeSession.request.prNumber,\n round: state.activeSession.rounds.length + 1,\n startedAt: state.activeSession.startedAt,\n sender: state.activeSession.request.sender,\n }\n : null,\n terminationConfig: {\n maxRounds: terminationConfig.maxRounds,\n qualitySeverityFloor: terminationConfig.qualitySeverityFloor,\n },\n updatedAt: new Date().toISOString(),\n };\n const filePath = path.join(options.stateDir, \"headless-state.json\");\n const tmp = `${filePath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), \"utf-8\");\n fs.renameSync(tmp, filePath);\n } catch {\n // Non-critical — state dump is best-effort\n }\n }\n\n function pollOnce(): void {\n state.lastPollAt = new Date().toISOString();\n\n // Skip if already processing a review\n if (state.activeSession) {\n checkActiveSession();\n writeStateFile();\n return;\n }\n\n // Scan for new review requests\n const requests = scanInboxForReviews(\n options.commsDir,\n options.stateDir,\n options.generation,\n options.agentName,\n options.agentId,\n );\n\n if (requests.length === 0) {\n writeStateFile();\n return;\n }\n\n // Process first request (sequential — one at a time)\n const request = requests[0];\n startReviewSession(request);\n writeStateFile();\n }\n\n function startReviewSession(request: ReviewRequest): void {\n log(`Starting review for PR #${request.prNumber}`);\n\n // Mark as processed EAGERLY to prevent race with generic bridge.\n // If anything fails after this point, we roll back the marker.\n markAsProcessed(options.stateDir, request);\n\n try {\n // Write receipt\n writeReviewReceipt(options.commsDir, request, options.agentName);\n\n // Build review prompt\n const prompt = buildReviewPrompt(request, options.agentName, 1);\n\n // Write dispatch file to commsDir/inbox/ — the bridge watches this\n // directory and will inject it as a turn/start\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n const dispatchFilename = `${date}-headless-${options.agentName}-review-PR${request.prNumber}.md`;\n const inboxDir = path.join(options.commsDir, \"inbox\");\n fs.mkdirSync(inboxDir, { recursive: true });\n const dispatchFile = path.join(inboxDir, dispatchFilename);\n const tmp = `${dispatchFile}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, prompt, \"utf-8\");\n fs.renameSync(tmp, dispatchFile);\n\n state.activeSession = {\n request,\n agentName: options.agentName,\n role:\n (envConfig?.role as \"reviewer\" | \"validator\" | \"long-running\") ??\n \"reviewer\",\n rounds: [],\n startedAt: new Date().toISOString(),\n reviewFilePath: reviewFilePath(\n options.commsDir,\n request.generation,\n request.prNumber,\n options.agentName,\n ),\n };\n\n log(`Dispatched review prompt for PR #${request.prNumber} (round 1)`);\n } catch (err) {\n // Roll back processed marker so request can be retried on next poll\n log(\n `Failed to start review for PR #${request.prNumber}: ${err instanceof Error ? err.message : String(err)}`,\n );\n unmarkProcessed(options.stateDir, request);\n }\n }\n\n function checkActiveSession(): void {\n if (!state.activeSession) return;\n\n const session = state.activeSession;\n const revPath = session.reviewFilePath;\n\n // Check for new output FIRST — if output arrived, process it regardless\n // of elapsed time. Timeouts only apply when there's genuinely no output.\n // (덱 review: timeout before file check drops late-arriving valid output)\n let hasNewOutput = false;\n if (fs.existsSync(revPath)) {\n const stat = fs.statSync(revPath);\n const lastRound = session.rounds[session.rounds.length - 1];\n const lastCheck = lastRound?.timestamp ?? session.startedAt;\n hasNewOutput = stat.mtime.toISOString() > lastCheck;\n }\n\n if (hasNewOutput) {\n // New output arrived — parse and evaluate (skip timeout)\n const roundNum = session.rounds.length + 1;\n const round = parseReviewOutput(revPath, roundNum);\n if (!round) return;\n\n session.rounds.push(round);\n log(\n `PR #${session.request.prNumber} round ${roundNum}: ${round.findingCount} findings, ${round.suggestedDiffLines} suggested diff lines`,\n );\n\n // Evaluate termination\n const stopSignalPath = path.join(options.stateDir, \"stop-signal\");\n const ctx: TerminationContext = {\n round: roundNum,\n rounds: session.rounds,\n stopSignalPath,\n config: terminationConfig,\n };\n\n const result = evaluate(ctx);\n\n if (result.verdict === \"stop\") {\n log(\n `PR #${session.request.prNumber} terminated: ${result.reason} (${result.strategy})`,\n );\n completeSession(session);\n } else {\n log(\n `PR #${session.request.prNumber} continues to round ${roundNum + 1}`,\n );\n dispatchFollowUp(session, roundNum + 1);\n }\n return;\n }\n\n // No new output — apply timeout checks.\n\n // Session timeout: no output at all after SESSION_TIMEOUT_MS\n const SESSION_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes\n const elapsed = Date.now() - new Date(session.startedAt).getTime();\n if (elapsed > SESSION_TIMEOUT_MS && session.rounds.length === 0) {\n log(\n `PR #${session.request.prNumber} timed out — no output after ${Math.round(elapsed / 60000)}min. Releasing session.`,\n );\n state.activeSession = null;\n return;\n }\n\n // Round timeout: no new output between rounds for ROUND_TIMEOUT_MS\n const ROUND_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes between rounds\n if (session.rounds.length > 0) {\n const lastRoundTime = new Date(\n session.rounds[session.rounds.length - 1]!.timestamp,\n ).getTime();\n if (Date.now() - lastRoundTime > ROUND_TIMEOUT_MS) {\n log(\n `PR #${session.request.prNumber} round timeout — no new output after ${Math.round((Date.now() - lastRoundTime) / 60000)}min. Completing session.`,\n );\n completeSession(session);\n return;\n }\n }\n }\n\n function dispatchFollowUp(session: ReviewSession, round: number): void {\n const prompt = buildReviewPrompt(session.request, options.agentName, round);\n\n // Write follow-up dispatch to commsDir/inbox/ for bridge to steer\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n const dispatchFilename = `${date}-headless-${options.agentName}-review-PR${session.request.prNumber}-r${round}.md`;\n const inboxDir = path.join(options.commsDir, \"inbox\");\n fs.mkdirSync(inboxDir, { recursive: true });\n const dispatchFile = path.join(inboxDir, dispatchFilename);\n const tmp = `${dispatchFile}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, prompt, \"utf-8\");\n fs.renameSync(tmp, dispatchFile);\n }\n\n function completeSession(session: ReviewSession): void {\n session.terminatedAt = new Date().toISOString();\n\n // Note: request was already marked as processed eagerly in startReviewSession()\n\n // Clean up dispatch files from inbox\n const inboxDir = path.join(options.commsDir, \"inbox\");\n if (fs.existsSync(inboxDir)) {\n const prefix = `headless-${options.agentName}-review-PR${session.request.prNumber}`;\n const files = fs.readdirSync(inboxDir).filter((f) => f.includes(prefix));\n for (const f of files) {\n fs.unlinkSync(path.join(inboxDir, f));\n }\n }\n\n state.activeSession = null;\n state.completedSessions++;\n log(\n `PR #${session.request.prNumber} review complete (${session.rounds.length} rounds)`,\n );\n }\n\n return {\n start() {\n if (!isHeadlessReviewer()) {\n log(\"Not in headless mode — loop not started\");\n return;\n }\n\n state.running = true;\n log(\n `Headless review loop started (${envConfig?.role ?? \"reviewer\"}, poll ${options.pollIntervalMs}ms, max ${terminationConfig.maxRounds} rounds)`,\n );\n\n // Write initial state\n writeStateFile();\n\n // Initial poll\n pollOnce();\n\n // Set up interval\n timer = setInterval(pollOnce, options.pollIntervalMs);\n },\n\n stop() {\n state.running = false;\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n writeStateFile();\n log(\"Headless review loop stopped\");\n },\n\n getState() {\n return { ...state };\n },\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { spawn } from \"node:child_process\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport {\n resolveConfig,\n SHARED_CONFIG_FILE,\n LOCAL_CONFIG_FILE,\n} from \"../config/index.js\";\nimport { resolveAppServerUrl } from \"../engine/bridge.js\";\nimport { resolveNodeRuntime, buildRuntimeEnv } from \"../runtime/index.js\";\n\n// ─── Repo root discovery (fallback for unbundled runs) ─────────\n\nfunction findRepoRootFromRunner(): string | null {\n let dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)));\n\n while (true) {\n if (fs.existsSync(path.join(dir, SHARED_CONFIG_FILE))) return dir;\n if (fs.existsSync(path.join(dir, LOCAL_CONFIG_FILE))) return dir;\n if (fs.existsSync(path.join(dir, \"scripts\", \"codex-app-server-bridge.ts\")))\n return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// ─── Headless review loop integration ──────────────────────────\n\nfunction maybeStartHeadlessLoop(\n repoRoot: string,\n commsDir: string,\n stateDir: string | undefined,\n): void {\n if (process.env.TAP_HEADLESS !== \"true\") return;\n\n // Dynamic import to avoid loading review/termination engines in non-headless mode\n import(\"../engine/headless-loop.js\")\n .then(({ createHeadlessLoop }) => {\n const agentName =\n process.env.TAP_AGENT_NAME ??\n process.env.CODEX_TAP_AGENT_NAME ??\n \"reviewer\";\n const agentId =\n process.env.TAP_AGENT_ID ??\n process.env.TAP_BRIDGE_INSTANCE_ID ??\n agentName;\n const generation = process.env.TAP_REVIEW_GENERATION ?? \"gen11\";\n const resolvedStateDir = stateDir ?? path.join(repoRoot, \".tap-comms\");\n\n const loop = createHeadlessLoop({\n commsDir,\n stateDir: resolvedStateDir,\n repoRoot,\n agentId,\n agentName,\n generation,\n pollIntervalMs: 3_000, // Poll faster than generic bridge (5s) for review priority\n });\n\n loop.start();\n\n // Clean shutdown\n process.on(\"SIGTERM\", () => loop.stop());\n process.on(\"SIGINT\", () => loop.stop());\n })\n .catch((err) => {\n console.error(\"[headless-loop] Failed to start:\", err);\n });\n}\n\n// ─── Main ──────────────────────────────────────────────────────\n\ninterface BridgeScriptArgsOptions {\n repoRoot: string;\n commsDir: string;\n appServerUrl: string;\n gatewayTokenFile?: string;\n stateDir?: string;\n agentName?: string;\n}\n\nexport function resolveBridgeDaemonScript(\n repoRoot: string,\n runnerUrl: string = import.meta.url,\n fileExists: (candidate: string) => boolean = fs.existsSync,\n): string | null {\n const moduleDir = path.dirname(fileURLToPath(runnerUrl));\n const candidates = [\n // 1. Bundled standalone/npm install\n path.join(moduleDir, \"codex-app-server-bridge.mjs\"),\n // 2. Source run from monorepo package\n path.join(moduleDir, \"codex-app-server-bridge.ts\"),\n // 3. Built monorepo package dist\n path.join(\n repoRoot,\n \"packages\",\n \"tap-comms\",\n \"dist\",\n \"bridges\",\n \"codex-app-server-bridge.mjs\",\n ),\n // 4. Monorepo source wrapper\n path.join(\n repoRoot,\n \"packages\",\n \"tap-comms\",\n \"src\",\n \"bridges\",\n \"codex-app-server-bridge.ts\",\n ),\n // 5. Legacy monorepo root script\n path.join(repoRoot, \"scripts\", \"codex-app-server-bridge.ts\"),\n ];\n\n for (const candidate of candidates) {\n if (fileExists(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function buildBridgeScriptArgs(\n scriptPath: string,\n options: BridgeScriptArgsOptions,\n): string[] {\n const args = [\n scriptPath,\n `--repo-root=${options.repoRoot}`,\n `--comms-dir=${options.commsDir}`,\n `--app-server-url=${options.appServerUrl}`,\n ];\n\n if (options.agentName) {\n args.push(`--agent-name=${options.agentName}`);\n }\n\n if (options.gatewayTokenFile) {\n args.push(`--gateway-token-file=${options.gatewayTokenFile}`);\n }\n\n if (options.stateDir) {\n args.push(`--state-dir=${options.stateDir}`);\n }\n\n return args;\n}\n\nasync function main(): Promise<void> {\n const repoRootHint = findRepoRootFromRunner() ?? undefined;\n const { config } = resolveConfig({}, repoRootHint);\n\n const repoRoot = config.repoRoot;\n const commsDir = config.commsDir;\n const instancePortRaw = process.env.TAP_BRIDGE_PORT;\n const instancePort = instancePortRaw\n ? Number.parseInt(instancePortRaw, 10)\n : undefined;\n const envAppServerUrl = process.env.CODEX_APP_SERVER_URL?.trim();\n const gatewayTokenFile = process.env.TAP_GATEWAY_TOKEN_FILE?.trim();\n const appServerUrl =\n envAppServerUrl ||\n resolveAppServerUrl(\n config.appServerUrl,\n Number.isFinite(instancePort) ? instancePort : undefined,\n );\n\n // Multi-instance: derive instance-specific state dir\n // Honor TAP_STATE_DIR env (set by config resolver) before falling back to .tmp/\n const instanceId = process.env.TAP_BRIDGE_INSTANCE_ID;\n const envStateDir = process.env.TAP_STATE_DIR;\n let stateDir: string | undefined;\n if (envStateDir) {\n stateDir = envStateDir;\n } else if (instanceId) {\n const resolved = path.resolve(\n path.join(repoRoot, \".tmp\", `codex-app-server-bridge-${instanceId}`),\n );\n const expectedBase = path.resolve(repoRoot, \".tmp\") + path.sep;\n if (!resolved.startsWith(expectedBase)) {\n throw new Error(\n `Path traversal blocked: runtime state dir escapes .tmp/ directory`,\n );\n }\n stateDir = resolved;\n }\n\n // Honor pre-resolved node from parent (2-stage spawn: engine → runner → daemon)\n // TAP_STRIP_TYPES preserves metadata so bun doesn't get --experimental-strip-types.\n const preResolved = process.env.TAP_RESOLVED_NODE;\n const resolved = preResolved\n ? {\n command: preResolved,\n supportsStripTypes: process.env.TAP_STRIP_TYPES === \"1\",\n source: \"env\" as const,\n majorVersion: null,\n }\n : resolveNodeRuntime(config.runtimeCommand, repoRoot);\n\n const command = resolved.command;\n const agentName =\n process.env.TAP_AGENT_NAME?.trim() ||\n process.env.CODEX_TAP_AGENT_NAME?.trim() ||\n undefined;\n\n // Locate bridge script\n const scriptPath = resolveBridgeDaemonScript(repoRoot);\n if (!scriptPath) {\n throw new Error(\n `Bridge script not found for repo root ${repoRoot}.\\n` +\n `Expected a packaged dist/bridges/codex-app-server-bridge.mjs or monorepo bridge script.`,\n );\n }\n\n // Build args\n const args: string[] = [];\n if (resolved.supportsStripTypes) {\n args.push(\"--experimental-strip-types\");\n }\n args.push(\n ...buildBridgeScriptArgs(scriptPath, {\n repoRoot,\n commsDir,\n appServerUrl,\n gatewayTokenFile,\n stateDir,\n agentName,\n }),\n );\n\n // Forward bridge operational flags from env (set by engine/bridge.ts)\n const busyMode = process.env.TAP_BUSY_MODE;\n if (busyMode) args.push(`--busy-mode=${busyMode}`);\n\n const pollSeconds = process.env.TAP_POLL_SECONDS;\n if (pollSeconds) args.push(`--poll-seconds=${pollSeconds}`);\n\n const reconnectSeconds = process.env.TAP_RECONNECT_SECONDS;\n if (reconnectSeconds) args.push(`--reconnect-seconds=${reconnectSeconds}`);\n\n const lookbackMinutes = process.env.TAP_MESSAGE_LOOKBACK_MINUTES;\n if (lookbackMinutes)\n args.push(`--message-lookback-minutes=${lookbackMinutes}`);\n\n const threadId = process.env.TAP_THREAD_ID;\n if (threadId) args.push(`--thread-id=${threadId}`);\n\n if (process.env.TAP_EPHEMERAL === \"true\") args.push(\"--ephemeral\");\n if (process.env.TAP_PROCESS_EXISTING === \"true\")\n args.push(\"--process-existing-messages\");\n\n // Spawn with fnm-aware PATH so any further child spawns also find the right Node\n const runtimeEnv = buildRuntimeEnv(repoRoot);\n\n const child = spawn(command, args, {\n cwd: repoRoot,\n env: runtimeEnv,\n stdio: \"inherit\",\n });\n\n // Start headless review loop if in headless mode\n maybeStartHeadlessLoop(repoRoot, commsDir, stateDir);\n\n child.on(\"exit\", (code: number | null, signal: NodeJS.Signals | null) => {\n if (signal) {\n process.kill(process.pid, signal);\n return;\n }\n process.exit(code ?? 0);\n });\n\n child.on(\"error\", (error: Error) => {\n console.error(String(error));\n process.exit(1);\n });\n}\n\nfunction isDirectExecution(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n return import.meta.url === pathToFileURL(path.resolve(entry)).href;\n}\n\nif (isDirectExecution()) {\n main().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n });\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n TapSharedConfig,\n TapLocalConfig,\n TapResolvedConfig,\n ConfigSource,\n ConfigResolution,\n TrackedConfigSource,\n TrackedValue,\n TapTrackedConfig,\n} from \"./types.js\";\nimport { computeConfigHash } from \"./config-hash.js\";\n\n// ─── File names ────────────────────────────────────────────────\n\nexport const SHARED_CONFIG_FILE = \"tap-config.json\";\nexport const LOCAL_CONFIG_FILE = \"tap-config.local.json\";\nexport const LEGACY_CONFIG_FILE = \".tap-config\";\n\n// ─── Defaults ──────────────────────────────────────────────────\n\nconst DEFAULT_RUNTIME_COMMAND = \"node\";\nconst DEFAULT_APP_SERVER_URL = \"ws://127.0.0.1:4501\";\n\n// ─── Repo root discovery ───────────────────────────────────────\n\nimport { _noGitWarned, _setNoGitWarned, log } from \"../utils.js\";\n\nexport function findRepoRoot(startDir: string = process.cwd()): string {\n let dir = path.resolve(startDir);\n while (true) {\n if (fs.existsSync(path.join(dir, \".git\"))) return dir;\n if (fs.existsSync(path.join(dir, \"package.json\"))) {\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No .git directory found. Resolved tap root via package.json. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No git repository or package.json found. Using the current directory as tap root. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return process.cwd();\n}\n\n// ─── JSON file loading ─────────────────────────────────────────\n\nfunction loadJsonFile<T>(filePath: string): T | null {\n if (!fs.existsSync(filePath)) return null;\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nexport function loadSharedConfig(repoRoot: string): TapSharedConfig | null {\n return loadJsonFile<TapSharedConfig>(path.join(repoRoot, SHARED_CONFIG_FILE));\n}\n\nexport function loadLocalConfig(repoRoot: string): TapLocalConfig | null {\n return loadJsonFile<TapLocalConfig>(path.join(repoRoot, LOCAL_CONFIG_FILE));\n}\n\nfunction readLegacyShellValue(configText: string, key: string): string | null {\n const match = configText.match(new RegExp(`^${key}=\"?(.+?)\"?$`, \"m\"));\n return match?.[1]?.trim() || null;\n}\n\nfunction loadLegacyShellConfig(repoRoot: string): TapSharedConfig | null {\n const filePath = path.join(repoRoot, LEGACY_CONFIG_FILE);\n if (!fs.existsSync(filePath)) return null;\n\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const commsDir = readLegacyShellValue(raw, \"TAP_COMMS_DIR\");\n if (!commsDir) return null;\n return { commsDir };\n } catch {\n return null;\n }\n}\n\n// ─── CLI overrides ─────────────────────────────────────────────\n\nexport interface ConfigOverrides {\n commsDir?: string;\n stateDir?: string;\n runtimeCommand?: string;\n appServerUrl?: string;\n}\n\n// ─── Resolution ────────────────────────────────────────────────\n\n/**\n * Resolve config with priority: CLI flag > env > local config > shared config > auto.\n */\nexport function resolveConfig(\n overrides: ConfigOverrides = {},\n startDir?: string,\n): ConfigResolution {\n const repoRoot = findRepoRoot(startDir);\n const shared = loadSharedConfig(repoRoot) ?? {};\n const local = loadLocalConfig(repoRoot) ?? {};\n const legacy = loadLegacyShellConfig(repoRoot) ?? {};\n\n const sources: Record<keyof TapResolvedConfig, ConfigSource> = {\n repoRoot: \"auto\",\n commsDir: \"auto\",\n stateDir: \"auto\",\n runtimeCommand: \"auto\",\n appServerUrl: \"auto\",\n towerName: \"auto\",\n };\n\n // ─── commsDir ──────────────────────────────────────────────\n let commsDir: string;\n if (overrides.commsDir) {\n commsDir = resolvePath(repoRoot, overrides.commsDir);\n sources.commsDir = \"cli-flag\";\n } else if (process.env.TAP_COMMS_DIR) {\n commsDir = resolvePath(repoRoot, process.env.TAP_COMMS_DIR);\n sources.commsDir = \"env\";\n } else if (local.commsDir) {\n commsDir = resolvePath(repoRoot, local.commsDir);\n sources.commsDir = \"local-config\";\n } else if (shared.commsDir) {\n commsDir = resolvePath(repoRoot, shared.commsDir);\n sources.commsDir = \"shared-config\";\n } else if (legacy.commsDir) {\n commsDir = resolvePath(repoRoot, legacy.commsDir);\n sources.commsDir = \"legacy-shell-config\";\n } else {\n commsDir = path.join(repoRoot, \"tap-comms\");\n }\n\n // ─── stateDir ──────────────────────────────────────────────\n let stateDir: string;\n if (overrides.stateDir) {\n stateDir = resolvePath(repoRoot, overrides.stateDir);\n sources.stateDir = \"cli-flag\";\n } else if (process.env.TAP_STATE_DIR) {\n stateDir = resolvePath(repoRoot, process.env.TAP_STATE_DIR);\n sources.stateDir = \"env\";\n } else if (local.stateDir) {\n stateDir = resolvePath(repoRoot, local.stateDir);\n sources.stateDir = \"local-config\";\n } else if (shared.stateDir) {\n stateDir = resolvePath(repoRoot, shared.stateDir);\n sources.stateDir = \"shared-config\";\n } else {\n stateDir = path.join(repoRoot, \".tap-comms\");\n }\n\n // ─── runtimeCommand ────────────────────────────────────────\n let runtimeCommand: string;\n if (overrides.runtimeCommand) {\n runtimeCommand = overrides.runtimeCommand;\n sources.runtimeCommand = \"cli-flag\";\n } else if (process.env.TAP_RUNTIME_COMMAND) {\n runtimeCommand = process.env.TAP_RUNTIME_COMMAND;\n sources.runtimeCommand = \"env\";\n } else if (local.runtimeCommand) {\n runtimeCommand = local.runtimeCommand;\n sources.runtimeCommand = \"local-config\";\n } else if (shared.runtimeCommand) {\n runtimeCommand = shared.runtimeCommand;\n sources.runtimeCommand = \"shared-config\";\n } else {\n runtimeCommand = DEFAULT_RUNTIME_COMMAND;\n }\n\n // ─── appServerUrl ──────────────────────────────────────────\n let appServerUrl: string;\n if (overrides.appServerUrl) {\n appServerUrl = overrides.appServerUrl;\n sources.appServerUrl = \"cli-flag\";\n } else if (process.env.TAP_APP_SERVER_URL) {\n appServerUrl = process.env.TAP_APP_SERVER_URL;\n sources.appServerUrl = \"env\";\n } else if (local.appServerUrl) {\n appServerUrl = local.appServerUrl;\n sources.appServerUrl = \"local-config\";\n } else if (shared.appServerUrl) {\n appServerUrl = shared.appServerUrl;\n sources.appServerUrl = \"shared-config\";\n } else {\n appServerUrl = DEFAULT_APP_SERVER_URL;\n }\n\n // ─── towerName ──────────────────────────────────────────────\n const towerName = local.towerName ?? shared.towerName ?? null;\n\n return {\n config: {\n repoRoot,\n commsDir,\n stateDir,\n runtimeCommand,\n appServerUrl,\n towerName,\n },\n sources,\n };\n}\n\n// ─── Save helpers ──────────────────────────────────────────────\n\nexport function saveSharedConfig(\n repoRoot: string,\n config: TapSharedConfig,\n): void {\n const filePath = path.join(repoRoot, SHARED_CONFIG_FILE);\n const tmp = `${filePath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, filePath);\n}\n\nexport function saveLocalConfig(\n repoRoot: string,\n config: TapLocalConfig,\n): void {\n const filePath = path.join(repoRoot, LOCAL_CONFIG_FILE);\n const tmp = `${filePath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, filePath);\n}\n\n// ─── Helpers ───────────────────────────────────────────────────\n\n/** Resolve a path relative to repoRoot, or keep absolute as-is. */\nfunction resolvePath(repoRoot: string, p: string): string {\n const normalized = normalizeTapPath(p);\n return path.isAbsolute(normalized)\n ? normalized\n : path.resolve(repoRoot, normalized);\n}\n\n// ─── Instance / Session Config Loading ────────────────────────\n\n/** Subset of config fields that can be overridden per-instance or per-session. */\ninterface ResolveOverrides {\n agentName?: string | null;\n port?: number | null;\n bridgeMode?: string | null;\n commsDir?: string;\n stateDir?: string;\n runtimeCommand?: string;\n appServerUrl?: string;\n towerName?: string;\n}\n\ntype SessionOverrides = ResolveOverrides;\n\n/**\n * Verify that a resolved path stays within the expected subdirectory.\n * Prevents crafted IDs from crossing source boundaries\n * (e.g., instanceId=\"../sessions/gen22\" reading a session file as instance).\n */\nfunction assertConfigPathContained(\n resolved: string,\n baseDir: string,\n subDir: string,\n): string {\n const expectedDir = path.resolve(baseDir, subDir) + path.sep;\n const normalizedResolved = path.resolve(resolved);\n if (!normalizedResolved.startsWith(expectedDir)) {\n throw new Error(\n `Config path traversal blocked: resolved path escapes \"${subDir}/\" directory`,\n );\n }\n return normalizedResolved;\n}\n\nexport function loadInstanceConfig(\n stateDir: string,\n instanceId: string,\n): ResolveOverrides | null {\n const filePath = path.join(stateDir, \"instances\", `${instanceId}.json`);\n assertConfigPathContained(filePath, stateDir, \"instances\");\n return loadJsonFile<ResolveOverrides>(filePath);\n}\n\nexport function loadSessionConfig(\n stateDir: string,\n sessionId: string,\n): SessionOverrides | null {\n const filePath = path.join(stateDir, \"sessions\", `${sessionId}.json`);\n assertConfigPathContained(filePath, stateDir, \"sessions\");\n return loadJsonFile<SessionOverrides>(filePath);\n}\n\n// ─── Tracked Config Resolution ────────────────────────────────\n\nfunction tracked<T>(\n value: T,\n source: TrackedConfigSource,\n sourceFile: string | null = null,\n): TrackedValue<T> {\n return { value, source, sourceFile };\n}\n\nexport interface TrackedResolveOpts extends ConfigOverrides {\n instanceId?: string;\n sessionId?: string;\n}\n\n/**\n * Resolve config with full source tracking.\n * 7-level priority: cli > env > instance > session > local > project > default\n */\nexport function resolveTrackedConfig(\n opts: TrackedResolveOpts = {},\n startDir?: string,\n): { tracked: TapTrackedConfig; hash: string } {\n const repoRoot = findRepoRoot(startDir);\n const shared = loadSharedConfig(repoRoot) ?? {};\n const local = loadLocalConfig(repoRoot) ?? {};\n const legacy = loadLegacyShellConfig(repoRoot) ?? {};\n\n // Resolve stateDir first (needed for instance/session paths)\n const rawStateDir =\n opts.stateDir ??\n process.env.TAP_STATE_DIR ??\n local.stateDir ??\n shared.stateDir ??\n null;\n const stateDir = rawStateDir\n ? resolvePath(repoRoot, rawStateDir)\n : path.join(repoRoot, \".tap-comms\");\n\n // Load instance/session configs (graceful fallback)\n const inst = opts.instanceId\n ? loadInstanceConfig(stateDir, opts.instanceId)\n : null;\n const instFile = opts.instanceId\n ? path.join(stateDir, \"instances\", `${opts.instanceId}.json`)\n : null;\n const sess = opts.sessionId\n ? loadSessionConfig(stateDir, opts.sessionId)\n : null;\n const sessFile = opts.sessionId\n ? path.join(stateDir, \"sessions\", `${opts.sessionId}.json`)\n : null;\n\n // Chain resolution helper — finds first defined value from highest priority\n function resolveField<T>(\n cliVal: T | undefined,\n envVal: T | undefined,\n instVal: T | undefined,\n sessVal: T | undefined,\n localVal: T | undefined,\n projectVal: T | undefined,\n defaultVal: T,\n ): TrackedValue<T> {\n if (cliVal !== undefined) return tracked(cliVal, \"cli\");\n if (envVal !== undefined) return tracked(envVal, \"env\");\n if (instVal !== undefined) return tracked(instVal, \"instance\", instFile);\n if (sessVal !== undefined) return tracked(sessVal, \"session\", sessFile);\n if (localVal !== undefined)\n return tracked(localVal, \"local\", path.join(repoRoot, LOCAL_CONFIG_FILE));\n if (projectVal !== undefined)\n return tracked(\n projectVal,\n \"project\",\n path.join(repoRoot, SHARED_CONFIG_FILE),\n );\n return tracked(defaultVal, \"default\");\n }\n\n const commsDirTracked = resolveField(\n opts.commsDir ? resolvePath(repoRoot, opts.commsDir) : undefined,\n process.env.TAP_COMMS_DIR\n ? resolvePath(repoRoot, process.env.TAP_COMMS_DIR)\n : undefined,\n inst?.commsDir ? resolvePath(repoRoot, inst.commsDir) : undefined,\n sess?.commsDir ? resolvePath(repoRoot, sess.commsDir) : undefined,\n local.commsDir ? resolvePath(repoRoot, local.commsDir) : undefined,\n (shared.commsDir ?? legacy.commsDir)\n ? resolvePath(repoRoot, (shared.commsDir ?? legacy.commsDir)!)\n : undefined,\n path.join(repoRoot, \"tap-comms\"),\n );\n\n const stateDirTracked = resolveField(\n opts.stateDir ? resolvePath(repoRoot, opts.stateDir) : undefined,\n process.env.TAP_STATE_DIR\n ? resolvePath(repoRoot, process.env.TAP_STATE_DIR)\n : undefined,\n inst?.stateDir ? resolvePath(repoRoot, inst.stateDir) : undefined,\n sess?.stateDir ? resolvePath(repoRoot, sess.stateDir) : undefined,\n local.stateDir ? resolvePath(repoRoot, local.stateDir) : undefined,\n shared.stateDir ? resolvePath(repoRoot, shared.stateDir) : undefined,\n stateDir,\n );\n\n const runtimeCommandTracked = resolveField(\n opts.runtimeCommand,\n process.env.TAP_RUNTIME_COMMAND,\n inst?.runtimeCommand,\n sess?.runtimeCommand,\n local.runtimeCommand,\n shared.runtimeCommand,\n DEFAULT_RUNTIME_COMMAND,\n );\n\n const appServerUrlTracked = resolveField(\n opts.appServerUrl,\n process.env.TAP_APP_SERVER_URL,\n inst?.appServerUrl,\n sess?.appServerUrl,\n local.appServerUrl,\n shared.appServerUrl,\n DEFAULT_APP_SERVER_URL,\n );\n\n const towerNameTracked = resolveField<string | null>(\n undefined, // no CLI flag for towerName\n undefined, // no env for towerName\n inst?.towerName ?? undefined,\n sess?.towerName ?? undefined,\n local.towerName ?? undefined,\n shared.towerName ?? undefined,\n null,\n );\n\n const agentNameTracked = resolveField<string | null>(\n undefined,\n undefined,\n inst?.agentName ?? undefined,\n sess?.agentName ?? undefined,\n undefined,\n undefined,\n null,\n );\n\n const portTracked = resolveField<number | null>(\n undefined,\n undefined,\n inst?.port ?? undefined,\n sess?.port ?? undefined,\n undefined,\n undefined,\n null,\n );\n\n const bridgeModeTracked = resolveField<string | null>(\n undefined,\n undefined,\n inst?.bridgeMode ?? undefined,\n sess?.bridgeMode ?? undefined,\n undefined,\n undefined,\n null,\n );\n\n const trackedConfig: TapTrackedConfig = {\n repoRoot: tracked(repoRoot, \"default\"),\n commsDir: commsDirTracked,\n stateDir: stateDirTracked,\n runtimeCommand: runtimeCommandTracked,\n appServerUrl: appServerUrlTracked,\n towerName: towerNameTracked,\n agentName: agentNameTracked,\n port: portTracked,\n bridgeMode: bridgeModeTracked,\n };\n\n return { tracked: trackedConfig, hash: computeConfigHash(trackedConfig) };\n}\n\nexport function normalizeTapPath(input: string): string {\n const trimmed = input.trim().replace(/^[\"'`]+|[\"'`]+$/g, \"\");\n if (/^[A-Za-z]:[\\\\/]/.test(trimmed)) {\n return trimmed;\n }\n\n // MSYS/Git Bash `/c/...` → `C:\\...` conversion — Windows only.\n // On POSIX, `/d/...` is a legitimate absolute path and must not be rewritten.\n if (process.platform === \"win32\") {\n const match = trimmed.match(/^\\/([A-Za-z])\\/(.*)$/);\n if (match) {\n return `${match[1].toUpperCase()}:\\\\${match[2].replace(/\\//g, \"\\\\\")}`;\n }\n }\n\n return trimmed;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n AdapterContext,\n CommandCode,\n InstanceId,\n Platform,\n RuntimeName,\n TapState,\n} from \"./types.js\";\nimport { resolveConfig, normalizeTapPath } from \"./config/index.js\";\n\nconst VALID_RUNTIMES: RuntimeName[] = [\"claude\", \"codex\", \"gemini\"];\n\nexport function isValidRuntime(name: string): name is RuntimeName {\n return VALID_RUNTIMES.includes(name as RuntimeName);\n}\n\nexport function detectPlatform(): Platform {\n return process.platform as Platform;\n}\n\n/** Shared flag: suppress duplicate no-git warnings across modules. */\nexport let _noGitWarned = false;\nconst _loggedWarnings = new Set<string>();\n\nexport function _setNoGitWarned() {\n _noGitWarned = true;\n}\n\nexport function resetLoggedWarnings() {\n _loggedWarnings.clear();\n}\n\nexport function wasWarningLogged(message: string): boolean {\n return _loggedWarnings.has(message);\n}\n\nexport function findRepoRoot(startDir: string = process.cwd()): string {\n let dir = path.resolve(startDir);\n while (true) {\n if (fs.existsSync(path.join(dir, \".git\"))) return dir;\n if (fs.existsSync(path.join(dir, \"package.json\"))) {\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No .git directory found. Resolved tap root via package.json. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No git repository or package.json found. Using the current directory as tap root. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return process.cwd();\n}\n\nexport function resolveCommsDir(args: string[], repoRoot: string): string {\n // Check --comms-dir flag\n const idx = args.indexOf(\"--comms-dir\");\n if (idx !== -1 && args[idx + 1]) {\n return path.resolve(normalizeTapPath(args[idx + 1]));\n }\n\n // Delegate to config resolution (env > local > shared > auto)\n const { config } = resolveConfig({}, repoRoot);\n return config.commsDir;\n}\n\nexport function createAdapterContext(\n commsDir: string,\n repoRoot: string,\n): AdapterContext {\n // Use config-resolved stateDir if available\n const { config } = resolveConfig({}, repoRoot);\n return {\n commsDir: path.resolve(normalizeTapPath(commsDir)),\n repoRoot: path.resolve(normalizeTapPath(repoRoot)),\n stateDir: config.stateDir,\n platform: detectPlatform(),\n };\n}\n\nexport function parseArgs(args: string[]): {\n positional: string[];\n flags: Record<string, string | boolean>;\n} {\n const positional: string[] = [];\n const flags: Record<string, string | boolean> = {};\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n const next = args[i + 1];\n if (next && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n } else if (arg.startsWith(\"-\")) {\n flags[arg.slice(1)] = true;\n } else {\n positional.push(arg);\n }\n }\n\n return { positional, flags };\n}\n\n// ─── JSON mode suppression ──────────────────────────────────────\n\nlet _jsonMode = false;\n\nexport function setJsonMode(enabled: boolean): void {\n _jsonMode = enabled;\n}\n\nexport function log(message: string): void {\n if (!_jsonMode) console.log(` ${message}`);\n}\n\nexport function logSuccess(message: string): void {\n if (!_jsonMode) console.log(` + ${message}`);\n}\n\nexport function logWarn(message: string): void {\n if (_jsonMode) return;\n _loggedWarnings.add(message);\n console.log(` ! ${message}`);\n}\n\nexport function logError(message: string): void {\n if (!_jsonMode) console.error(` x ${message}`);\n}\n\nexport function logHeader(message: string): void {\n if (!_jsonMode) console.log(`\\n ${message}\\n`);\n}\n\n// ─── CLI argument validation ──────────────────────────────────\n\n/**\n * Parse and validate an integer CLI flag within a range.\n * Returns undefined if the flag is not provided, or the validated number.\n * Throws a descriptive error if invalid.\n */\nexport function parseIntFlag(\n value: string | undefined,\n name: string,\n min: number,\n max: number,\n): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(\n `Invalid ${name}: ${value}. Must be an integer between ${min} and ${max}.`,\n );\n }\n return parsed;\n}\n\n/**\n * Parse and validate a port number (1-65535).\n */\nexport function parsePortFlag(value: string | undefined): number | null {\n if (value === undefined) return null;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {\n throw new RangeError(\n `Invalid port: ${value}. Must be between 1 and 65535.`,\n );\n }\n return parsed;\n}\n\n// ─── Instance ID utilities ─────────────────────────────────────\n\nexport type ResolveResult =\n | { ok: true; instanceId: InstanceId }\n | { ok: false; code: CommandCode; message: string };\n\n/**\n * Resolve a user-provided identifier to an instance ID.\n * Accepts either an exact instance ID or a runtime name (if unambiguous).\n */\nexport function resolveInstanceId(\n identifier: string,\n state: TapState,\n): ResolveResult {\n // Exact match\n if (state.instances[identifier]) {\n return { ok: true, instanceId: identifier };\n }\n\n // Runtime name → find matching instances\n if (isValidRuntime(identifier)) {\n const matches = Object.values(state.instances).filter(\n (inst) => inst.runtime === identifier,\n );\n\n if (matches.length === 1) {\n return { ok: true, instanceId: matches[0].instanceId };\n }\n\n if (matches.length > 1) {\n const ids = matches.map((m) => m.instanceId).join(\", \");\n return {\n ok: false,\n code: \"TAP_INSTANCE_AMBIGUOUS\",\n message: `Multiple ${identifier} instances found: ${ids}. Specify one explicitly.`,\n };\n }\n }\n\n return {\n ok: false,\n code: \"TAP_INSTANCE_NOT_FOUND\",\n message: `Instance not found: ${identifier}`,\n };\n}\n\n/**\n * Reject instance names containing path-traversal sequences or separators.\n * Prevents directory escape when the ID is interpolated into file paths.\n */\nexport function validateInstanceName(name: string): void {\n if (/[/\\\\]/.test(name) || name.includes(\"..\")) {\n throw new Error(\n `Invalid instance name \"${name}\": must not contain path separators or \"..\" sequences`,\n );\n }\n}\n\n/** Build an instance ID from runtime + optional name. */\nexport function buildInstanceId(\n runtime: RuntimeName,\n name?: string,\n): InstanceId {\n if (name) {\n validateInstanceName(name);\n }\n return name ? `${runtime}-${name}` : runtime;\n}\n\n/** Extract the runtime name from an instance ID. */\nexport function extractRuntimeFromInstanceId(id: InstanceId): RuntimeName {\n for (const r of VALID_RUNTIMES) {\n if (id === r || id.startsWith(`${r}-`)) return r;\n }\n throw new Error(`Cannot extract runtime from instance ID: ${id}`);\n}\n\n/** Check if a port is already claimed by another instance. */\nexport function findPortConflict(\n state: TapState,\n port: number,\n excludeInstanceId?: InstanceId,\n): InstanceId | null {\n for (const [id, inst] of Object.entries(state.instances)) {\n if (id !== excludeInstanceId && inst.port === port) return id;\n }\n return null;\n}\n\n/** Find the next available port starting from basePort (default 4501). */\nexport function findNextAvailablePort(\n state: TapState,\n basePort: number = 4501,\n excludeInstanceId?: InstanceId,\n): number {\n let port = basePort;\n while (findPortConflict(state, port, excludeInstanceId) !== null) {\n port++;\n }\n return port;\n}\n","/**\n * Common Node.js runtime resolver for all tap-comms child processes.\n *\n * Resolution chain:\n * .node-version + fnm probe → configured command → tsx fallback\n *\n * Extracted from codex-bridge-runner.ts (M69) to share across:\n * - bridge engine spawn\n * - bridge runner spawn\n * - future CLI commands\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { execSync } from \"node:child_process\";\n\n// ─── Types ─────────────────────────────────────────────────────\n\nexport type RuntimeSource = \"fnm\" | \"config\" | \"path\" | \"tsx-fallback\" | \"bun\";\n\nexport interface ResolvedRuntime {\n /** Absolute path or command name for the resolved runtime. */\n command: string;\n /** Whether --experimental-strip-types is supported and should be used. */\n supportsStripTypes: boolean;\n /** Where the runtime was resolved from (for diagnostics). */\n source: RuntimeSource;\n /** Detected major version, if available. */\n majorVersion: number | null;\n}\n\n// ─── .node-version ─────────────────────────────────────────────\n\nexport function readNodeVersion(repoRoot: string): string | null {\n const nvFile = path.join(repoRoot, \".node-version\");\n if (!fs.existsSync(nvFile)) return null;\n try {\n const raw = fs.readFileSync(nvFile, \"utf-8\").trim();\n return raw.length > 0 ? raw.replace(/^v/, \"\") : null;\n } catch {\n return null;\n }\n}\n\n// ─── fnm probe ─────────────────────────────────────────────────\n\nfunction fnmCandidateDirs(): string[] {\n if (process.platform === \"win32\") {\n return [\n process.env.FNM_DIR,\n process.env.APPDATA ? path.join(process.env.APPDATA, \"fnm\") : null,\n process.env.LOCALAPPDATA\n ? path.join(process.env.LOCALAPPDATA, \"fnm\")\n : null,\n process.env.USERPROFILE\n ? path.join(process.env.USERPROFILE, \"scoop\", \"persist\", \"fnm\")\n : null,\n ].filter(Boolean) as string[];\n }\n // macOS / Linux\n return [\n process.env.FNM_DIR,\n process.env.HOME\n ? path.join(process.env.HOME, \".local\", \"share\", \"fnm\")\n : null,\n process.env.HOME ? path.join(process.env.HOME, \".fnm\") : null,\n process.env.XDG_DATA_HOME\n ? path.join(process.env.XDG_DATA_HOME, \"fnm\")\n : null,\n ].filter(Boolean) as string[];\n}\n\nfunction nodeExecutableName(): string {\n return process.platform === \"win32\" ? \"node.exe\" : \"node\";\n}\n\nexport function probeFnmNode(desiredVersion: string): string | null {\n const dirs = fnmCandidateDirs();\n const exe = nodeExecutableName();\n\n for (const baseDir of dirs) {\n const candidate = path.join(\n baseDir,\n \"node-versions\",\n `v${desiredVersion}`,\n \"installation\",\n exe,\n );\n if (!fs.existsSync(candidate)) continue;\n\n try {\n const v = execSync(`\"${candidate}\" --version`, {\n encoding: \"utf-8\",\n timeout: 5000,\n }).trim();\n if (v.startsWith(`v${desiredVersion.split(\".\")[0]}.`)) {\n return candidate;\n }\n } catch {\n // candidate exists but doesn't work — skip\n }\n }\n\n return null;\n}\n\n// ─── Version detection ─────────────────────────────────────────\n\nexport function detectNodeMajorVersion(command: string): number | null {\n try {\n const version = execSync(`\"${command}\" --version`, {\n encoding: \"utf-8\",\n timeout: 5000,\n }).trim();\n const match = version.match(/^v?(\\d+)\\./);\n return match ? parseInt(match[1], 10) : null;\n } catch {\n return null;\n }\n}\n\nexport function checkStripTypesSupport(command: string): boolean {\n const major = detectNodeMajorVersion(command);\n if (major !== null && major >= 22) return true;\n try {\n execSync(`\"${command}\" --experimental-strip-types -e \"\"`, {\n timeout: 5000,\n stdio: \"pipe\",\n });\n return true;\n } catch {\n return false;\n }\n}\n\n// ─── tsx fallback ──────────────────────────────────────────────\n\nexport function findTsxFallback(repoRoot: string): string | null {\n const candidates = [\n path.join(repoRoot, \"node_modules\", \".bin\", \"tsx.exe\"),\n path.join(repoRoot, \"node_modules\", \".bin\", \"tsx.CMD\"),\n path.join(repoRoot, \"node_modules\", \".bin\", \"tsx\"),\n ];\n for (const c of candidates) {\n if (fs.existsSync(c)) return c;\n }\n return null;\n}\n\n// ─── fnm bin directory (for PATH prepending) ───────────────────\n\n/**\n * Returns the directory containing the fnm-managed node binary,\n * suitable for prepending to PATH in child processes.\n */\nexport function getFnmBinDir(repoRoot: string): string | null {\n const desiredVersion = readNodeVersion(repoRoot);\n if (!desiredVersion) return null;\n\n const nodePath = probeFnmNode(desiredVersion);\n if (!nodePath) return null;\n\n return path.dirname(nodePath);\n}\n\n// ─── Main resolver ─────────────────────────────────────────────\n\n/**\n * Resolve the Node.js runtime to use for spawning child processes.\n *\n * Priority: bun passthrough → .node-version + fnm → configured command → tsx fallback\n */\nexport function resolveNodeRuntime(\n configCommand: string,\n repoRoot: string,\n): ResolvedRuntime {\n // Bun: native TS support, no strip-types needed\n if (configCommand === \"bun\" || configCommand.endsWith(\"bun.exe\")) {\n return {\n command: configCommand,\n supportsStripTypes: false,\n source: \"bun\",\n majorVersion: null,\n };\n }\n\n // .node-version + fnm discovery\n const desiredVersion = readNodeVersion(repoRoot);\n if (desiredVersion) {\n const fnmNode = probeFnmNode(desiredVersion);\n if (fnmNode) {\n const major = detectNodeMajorVersion(fnmNode);\n return {\n command: fnmNode,\n supportsStripTypes: checkStripTypesSupport(fnmNode),\n source: \"fnm\",\n majorVersion: major,\n };\n }\n }\n\n // Configured command (from config or PATH)\n const major = detectNodeMajorVersion(configCommand);\n if (major !== null) {\n return {\n command: configCommand,\n supportsStripTypes: checkStripTypesSupport(configCommand),\n source: major === detectNodeMajorVersion(\"node\") ? \"path\" : \"config\",\n majorVersion: major,\n };\n }\n\n // tsx fallback\n const tsx = findTsxFallback(repoRoot);\n if (tsx) {\n return {\n command: tsx,\n supportsStripTypes: false,\n source: \"tsx-fallback\",\n majorVersion: null,\n };\n }\n\n // Last resort\n return {\n command: configCommand,\n supportsStripTypes: false,\n source: \"path\",\n majorVersion: null,\n };\n}\n\n// ─── Env builder for child processes ───────────────────────────\n\n/**\n * Build an env object with fnm Node prepended to PATH.\n * Use this when spawning child processes that need the correct Node.\n */\nexport function buildRuntimeEnv(\n repoRoot: string,\n baseEnv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n const fnmBin = getFnmBinDir(repoRoot);\n if (!fnmBin) return { ...baseEnv };\n\n const pathKey = process.platform === \"win32\" ? \"Path\" : \"PATH\";\n const currentPath = baseEnv[pathKey] ?? baseEnv.PATH ?? \"\";\n\n return {\n ...baseEnv,\n [pathKey]: `${fnmBin}${path.delimiter}${currentPath}`,\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n InstanceId,\n BridgeState,\n AppServerState,\n Platform,\n} from \"../types.js\";\n\nimport { appServerLogFilePath } from \"./bridge-paths.js\";\nimport { removeFileIfExists } from \"./bridge-file-io.js\";\nimport { isLoopbackHost } from \"./bridge-port-network.js\";\nimport { resolveCodexCommand } from \"./bridge-codex-command.js\";\nimport {\n startWindowsCodexAppServer,\n findListeningProcessId,\n} from \"./bridge-windows-spawn.js\";\nimport {\n startUnixCodexAppServer,\n findUnixListeningProcessId,\n} from \"./bridge-unix-spawn.js\";\nimport { terminateProcess, isProcessAlive } from \"./bridge-process-control.js\";\nimport {\n checkAppServerHealth,\n waitForManagedAppServerReady,\n markAppServerHealthy,\n} from \"./bridge-app-server-health.js\";\nimport {\n readGatewayToken,\n createManagedAppServerAuth,\n canReuseManagedAppServer,\n} from \"./bridge-app-server-auth.js\";\nimport { rotateLog } from \"./bridge-observability.js\";\n\nexport interface EnsureCodexAppServerOptions {\n instanceId: InstanceId;\n stateDir: string;\n repoRoot: string;\n platform: Platform;\n appServerUrl: string;\n existingAppServer?: AppServerState | null;\n noAuth?: boolean;\n}\n\nexport const DEFAULT_APP_SERVER_URL = \"ws://127.0.0.1:4501\";\nexport const APP_SERVER_START_TIMEOUT_MS = 20_000;\nexport const APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5_000;\n\n/**\n * Check if any OTHER running bridge is using the same managed app-server.\n * Used to prevent killing a shared app-server when one bridge fails to start.\n */\nexport function isAppServerUsedByOtherBridge(\n stateDir: string,\n excludeInstanceId: InstanceId,\n appServer: AppServerState,\n): boolean {\n const pidDir = path.join(stateDir, \"pids\");\n if (!fs.existsSync(pidDir)) return false;\n\n for (const name of fs.readdirSync(pidDir)) {\n if (!name.startsWith(\"bridge-\") || !name.endsWith(\".json\")) continue;\n const otherId = name.slice(\"bridge-\".length, -\".json\".length);\n if (otherId === excludeInstanceId) continue;\n\n try {\n const raw = fs.readFileSync(path.join(pidDir, name), \"utf-8\");\n const state = JSON.parse(raw) as BridgeState;\n if (\n state.appServer?.url === appServer.url &&\n state.appServer?.pid === appServer.pid &&\n isProcessAlive(state.pid)\n ) {\n return true;\n }\n } catch {\n continue;\n }\n }\n return false;\n}\n\nexport function findReusableManagedAppServer(\n stateDir: string,\n publicUrl: string,\n): AppServerState | null {\n const pidDir = path.join(stateDir, \"pids\");\n if (!fs.existsSync(pidDir)) {\n return null;\n }\n\n for (const name of fs.readdirSync(pidDir)) {\n if (!name.startsWith(\"bridge-\") || !name.endsWith(\".json\")) {\n continue;\n }\n\n try {\n const raw = fs.readFileSync(path.join(pidDir, name), \"utf-8\");\n const parsed = JSON.parse(raw) as BridgeState;\n if (parsed.appServer?.url !== publicUrl) {\n continue;\n }\n if (canReuseManagedAppServer(parsed.appServer)) {\n return markAppServerHealthy(parsed.appServer!);\n }\n } catch {\n // Ignore stale or corrupted bridge state.\n }\n }\n\n return null;\n}\n\nexport function resolveAppServerUrl(\n baseUrl: string | undefined,\n port?: number,\n): string {\n const resolvedBase = (baseUrl ?? DEFAULT_APP_SERVER_URL).replace(/\\/$/, \"\");\n if (port == null) {\n return resolvedBase;\n }\n\n try {\n const parsed = new URL(resolvedBase);\n parsed.port = String(port);\n return parsed.toString().replace(/\\/$/, \"\");\n } catch {\n return resolvedBase;\n }\n}\n\nexport async function ensureCodexAppServer(\n options: EnsureCodexAppServerOptions,\n): Promise<AppServerState> {\n const effectiveUrl = resolveAppServerUrl(options.appServerUrl);\n const fallbackManualCommand = formatCodexAppServerCommand(\n \"codex\",\n effectiveUrl,\n );\n if (\n options.existingAppServer?.url === effectiveUrl &&\n canReuseManagedAppServer(options.existingAppServer)\n ) {\n return markAppServerHealthy(options.existingAppServer);\n }\n\n const sharedManaged = findReusableManagedAppServer(\n options.stateDir,\n effectiveUrl,\n );\n if (sharedManaged) {\n return sharedManaged;\n }\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(effectiveUrl);\n } catch {\n throw new Error(\n `Invalid app-server URL: ${effectiveUrl}\\nStart it manually:\\n ${fallbackManualCommand}`,\n );\n }\n\n if (!isLoopbackHost(parsedUrl.hostname)) {\n throw new Error(\n `Auto-start only supports loopback app-server URLs. Current URL: ${effectiveUrl}\\nStart it manually:\\n ${fallbackManualCommand}`,\n );\n }\n\n if (await checkAppServerHealth(effectiveUrl)) {\n const hint = options.noAuth\n ? \"Stop it first or use --no-server for an unmanaged external app-server.\"\n : \"A listener is already running, so tap cannot insert the auth gateway there.\\nStop it first or use --no-server for an unmanaged external app-server.\";\n throw new Error(`${effectiveUrl}: ${hint}`);\n }\n\n const resolvedCommand = resolveCodexCommand(options.platform);\n if (!resolvedCommand) {\n throw new Error(\n `Codex CLI not found in PATH.\\nStart the app-server manually:\\n ${fallbackManualCommand}`,\n );\n }\n\n const logPath = appServerLogFilePath(options.stateDir, options.instanceId);\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n rotateLog(logPath);\n\n // --no-auth: start app-server directly on the public URL (no gateway).\n // TUI and bridge both connect to the same port without token auth.\n if (options.noAuth) {\n const manualCommand = formatCodexAppServerCommand(\"codex\", effectiveUrl);\n let pid: number | null;\n\n if (options.platform === \"win32\") {\n try {\n pid = startWindowsCodexAppServer(\n resolvedCommand,\n effectiveUrl,\n options.repoRoot,\n logPath,\n );\n } catch (err) {\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n } else {\n try {\n pid = startUnixCodexAppServer(\n resolvedCommand,\n effectiveUrl,\n options.repoRoot,\n logPath,\n options.platform,\n );\n } catch (err) {\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n }\n\n if (pid == null) {\n throw new Error(\n `Failed to spawn Codex app-server.\\nStart it manually:\\n ${manualCommand}`,\n );\n }\n\n // Managed startup uses HTTP /readyz (with TCP fallback for older servers)\n // so readiness is verified without opening an extra WebSocket session.\n const healthy = await waitForManagedAppServerReady(\n effectiveUrl,\n APP_SERVER_START_TIMEOUT_MS,\n );\n if (!healthy) {\n await terminateProcess(pid, options.platform);\n throw new Error(\n `Codex app-server did not become healthy at ${effectiveUrl}.\\nCheck ${logPath}\\nOr start it manually:\\n ${manualCommand}`,\n );\n }\n\n pid =\n (options.platform === \"win32\"\n ? findListeningProcessId(effectiveUrl, options.platform)\n : findUnixListeningProcessId(effectiveUrl, options.platform)) ?? pid;\n const healthyAt = new Date().toISOString();\n return {\n url: effectiveUrl,\n pid,\n managed: true,\n healthy: true,\n lastCheckedAt: healthyAt,\n lastHealthyAt: healthyAt,\n logPath,\n manualCommand,\n auth: null,\n };\n }\n\n // Default: auth gateway mode — gateway on publicUrl, app-server on random upstream port\n const auth = await createManagedAppServerAuth({\n instanceId: options.instanceId,\n stateDir: options.stateDir,\n repoRoot: options.repoRoot,\n platform: options.platform,\n publicUrl: effectiveUrl,\n });\n const manualCommand = formatCodexAppServerCommand(\"codex\", auth.upstreamUrl);\n\n let pid: number | null;\n\n if (options.platform === \"win32\") {\n try {\n pid = startWindowsCodexAppServer(\n resolvedCommand,\n auth.upstreamUrl,\n options.repoRoot,\n logPath,\n );\n } catch (err) {\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n } else {\n try {\n pid = startUnixCodexAppServer(\n resolvedCommand,\n auth.upstreamUrl,\n options.repoRoot,\n logPath,\n options.platform,\n );\n } catch (err) {\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n }\n\n if (pid == null) {\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Failed to spawn Codex app-server.\\nStart it manually:\\n ${manualCommand}`,\n );\n }\n\n // Managed startup uses HTTP /readyz (with TCP fallback for older servers)\n // so readiness is verified without opening an extra WebSocket session.\n const healthy = await waitForManagedAppServerReady(\n auth.upstreamUrl,\n APP_SERVER_START_TIMEOUT_MS,\n );\n\n if (!healthy) {\n await terminateProcess(pid, options.platform);\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Codex app-server did not become healthy at ${auth.upstreamUrl}.\\nCheck ${logPath}\\nOr start it manually:\\n ${manualCommand}`,\n );\n }\n\n const gatewayToken = readGatewayToken(auth);\n if (!gatewayToken) {\n await terminateProcess(pid, options.platform);\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\"Tap auth gateway token is missing after startup.\");\n }\n\n // Gateway readiness hits /readyz on the public URL, which verifies the\n // gateway itself plus upstream readiness end-to-end without creating a\n // WebSocket session.\n const gatewayHealthy = await waitForManagedAppServerReady(\n effectiveUrl,\n APP_SERVER_GATEWAY_START_TIMEOUT_MS,\n );\n if (!gatewayHealthy) {\n await terminateProcess(pid, options.platform);\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Tap auth gateway did not become healthy at ${effectiveUrl}.\\nCheck ${auth.gatewayLogPath ?? \"the gateway log\"} and ${logPath}.`,\n );\n }\n\n const healthyAt = new Date().toISOString();\n pid =\n (options.platform === \"win32\"\n ? findListeningProcessId(auth.upstreamUrl, options.platform)\n : findUnixListeningProcessId(auth.upstreamUrl, options.platform)) ?? pid;\n return {\n url: effectiveUrl,\n pid,\n managed: true,\n healthy: true,\n lastCheckedAt: healthyAt,\n lastHealthyAt: healthyAt,\n logPath,\n manualCommand,\n auth,\n };\n}\n\nexport function formatCodexAppServerCommand(\n command: string,\n url: string,\n): string {\n return `${command} app-server --listen ${url}`;\n}\n"],"mappings":";;;;;;;;;;;AAMA,YAAYA,SAAQ;AACpB,YAAY,YAAY;AAqFxB,SAAS,YACP,UACA,OACS;AACT,SAAO,cAAc,QAAQ,KAAK,cAAc,KAAK;AACvD;AAIO,SAAS,mBAAmB,UAAmC;AACpE,QAAM,aAAa,SAChB,OAAO,CAAC,MAAM,YAAY,EAAE,UAAU,MAAM,CAAC,EAC7C,IAAI,CAAC,MAAM,GAAG,EAAE,QAAQ,IAAI,EAAE,YAAY,MAAM,GAAG,GAAG,CAAC,EAAE,EACzD,KAAK,EACL,KAAK,GAAG;AAEX,MAAI,CAAC,WAAY,QAAO;AAExB,SACG,kBAAW,QAAQ,EACnB,OAAO,UAAU,EACjB,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAChB;AAIA,SAAS,eAAe,KAAmD;AACzE,MAAO,eAAW,IAAI,cAAc,GAAG;AACrC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,+BAA+B,IAAI,cAAc;AAAA,MACzD,UAAU;AAAA,MACV,SAAS,oCAAoC,IAAI,KAAK;AAAA,IACxD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,KAAmD;AACvE,MAAI,IAAI,SAAS,IAAI,OAAO,WAAW;AACrC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,sBAAsB,IAAI,KAAK,IAAI,IAAI,OAAO,SAAS;AAAA,MAC/D,UAAU;AAAA,MACV,SAAS,gCAAgC,IAAI,OAAO,SAAS;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,eAAe,KAAmD;AACzE,MAAI,IAAI,OAAO,SAAS,EAAG,QAAO;AAElC,QAAM,SAAS,IAAI,OAAO,IAAI,OAAO,SAAS,CAAC;AAC/C,MAAI,CAAC,OAAQ,QAAO;AAEpB,MAAI,QAAQ;AACZ,aAAW,SAAS,IAAI,QAAQ;AAC9B,QAAI,MAAM,gBAAgB,OAAO,YAAa;AAAA,EAChD;AAEA,MAAI,SAAS,IAAI,OAAO,qBAAqB;AAC3C,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,8BAA8B,KAAK,sBAAsB,IAAI,OAAO,mBAAmB;AAAA,MAC/F,UAAU;AAAA,MACV,SAAS,yDAAoD,KAAK;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,qBACP,KAC0B;AAC1B,MAAI,IAAI,OAAO,WAAW,EAAG,QAAO;AAEpC,QAAM,SAAS,IAAI,OAAO,IAAI,OAAO,SAAS,CAAC;AAC/C,MAAI,CAAC,OAAQ,QAAO;AAKpB,MACE,OAAO,iBAAiB,KACxB,OAAO,uBAAuB,KAC9B,OAAO,SAAS,WAAW,GAC3B;AACA,WAAO;AAAA,EACT;AAEA,QAAM,sBAAsB,OAAO,SAAS;AAAA,IAAO,CAAC,MAClD,YAAY,EAAE,UAAU,IAAI,OAAO,oBAAoB;AAAA,EACzD;AAEA,MAAI,oBAAoB,WAAW,GAAG;AACpC,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,kBAAkB,IAAI,OAAO,oBAAoB,uBAAuB,IAAI,KAAK;AAAA,MACzF,UAAU;AAAA,MACV,SAAS,0BAAqB,IAAI,OAAO,oBAAoB,uBAAuB,IAAI,KAAK;AAAA,IAC/F;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,uBACP,KAC0B;AAC1B,MAAI,IAAI,OAAO,WAAW,EAAG,QAAO;AAEpC,QAAM,SAAS,IAAI,OAAO,IAAI,OAAO,SAAS,CAAC;AAC/C,MAAI,CAAC,OAAQ,QAAO;AAGpB,MACE,OAAO,iBAAiB,KACxB,OAAO,uBAAuB,KAC9B,OAAO,SAAS,WAAW,GAC3B;AACA,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,qBAAqB,IAAI,OAAO,eAAe;AACxD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ,mBAAmB,OAAO,kBAAkB,4BAA4B,IAAI,OAAO,aAAa;AAAA,MACxG,UAAU;AAAA,MACV,SAAS,mCAAmC,OAAO,kBAAkB;AAAA,IACvE;AAAA,EACF;AAEA,SAAO;AACT;AAeO,SAAS,SAAS,KAA4C;AACnE,aAAW,YAAY,IAAI,OAAO,YAAY;AAC5C,UAAM,YAAY,oBAAoB,QAAQ;AAC9C,QAAI,CAAC,UAAW;AAEhB,UAAM,SAAS,UAAU,GAAG;AAC5B,QAAI,OAAQ,QAAO;AAAA,EACrB;AAEA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,UACE,IAAI,OAAO,WAAW,IAAI,OAAO,WAAW,SAAS,CAAC,KAAK;AAAA,IAC7D,SAAS,SAAS,IAAI,KAAK;AAAA,EAC7B;AACF;AAnQA,IAoEa,4BAgBP,eAkJA;AAtON;AAAA;AAAA;AAoEO,IAAM,6BAAgD;AAAA,MAC3D,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,WAAW;AAAA,MACX,eAAe;AAAA,MACf,qBAAqB;AAAA,MACrB,sBAAsB;AAAA,IACxB;AAIA,IAAM,gBAAiD;AAAA,MACrD,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AA4IA,IAAM,sBAGF;AAAA,MACF,eAAe;AAAA,MACf,aAAa;AAAA,MACb,wBAAwB;AAAA,MACxB,qBAAqB;AAAA,MACrB,uBAAuB;AAAA,IACzB;AAAA;AAAA;;;ACxOA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,YAAYC,aAAY;AA6DxB,SAAS,YAAY,OAAuB;AAC1C,SAAO,MAAM,KAAK;AACpB;AAEA,SAAS,oBAAoB,OAAuB;AAClD,SAAO,YAAY,KAAK,EAAE,QAAQ,MAAM,GAAG,EAAE,YAAY;AAC3D;AAEO,SAAS,oBACd,QACA,SACA,WACS;AACT,QAAM,mBAAmB,YAAY,MAAM;AAC3C,MAAI,CAAC,iBAAkB,QAAO;AAE9B,SACE,oBAAoB,gBAAgB,MAAM,oBAAoB,OAAO,KACrE,iBAAiB,YAAY,MAAM,YAAY,SAAS,EAAE,YAAY;AAE1E;AAMO,SAAS,mBAAmB,UAK1B;AACP,QAAM,OAAY,eAAS,UAAU,KAAK;AAC1C,QAAM,QAAQ,KAAK,MAAM,gCAAgC;AACzD,MAAI,CAAC,MAAO,QAAO;AAEnB,SAAO;AAAA,IACL,MAAM,MAAM,CAAC;AAAA,IACb,QAAQ,MAAM,CAAC;AAAA,IACf,WAAW,MAAM,CAAC;AAAA,IAClB,SAAS,MAAM,CAAC;AAAA,EAClB;AACF;AAKO,SAAS,gBAAgB,MAA6B;AAC3D,aAAW,WAAW,oBAAoB;AACxC,UAAM,QAAQ,KAAK,MAAM,OAAO;AAChC,QAAI,QAAQ,CAAC,EAAG,QAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAAA,EAC9C;AACA,SAAO;AACT;AAMO,SAAS,oBACd,UACA,SACA,YACsB;AACtB,QAAM,SAAS,mBAAmB,QAAQ;AAC1C,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,WAAW,GAAG,OAAO,OAAO,IAAI,OAAO;AAG7C,QAAM,WAAW,gBAAgB,KAAK,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC;AAC/D,QAAM,aAAa,kBAAkB,KAAK,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC;AAEnE,MAAI,CAAC,YAAY,CAAC,WAAY,QAAO;AAGrC,QAAM,WAAW,gBAAgB,QAAQ;AACzC,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,QAAQ,OAAO;AAAA,IACf,WAAW,OAAO;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,aAAa,IAAI;AAAA;AAAA,EAC1B;AACF;AAIO,SAAS,kBACd,SACA,WACA,OACQ;AACR,QAAM,aAAa,QAAQ,IAAI,qBAAqB,KAAK,MAAM;AAE/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,QAAQ,QAAQ,GAAG,UAAU;AAAA,IAC3C;AAAA,IACA;AAAA,IACA,sBAAsB,QAAQ,QAAQ;AAAA,IACtC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,oBAAyB,WAAK,WAAW,QAAQ,YAAY,YAAY,QAAQ,QAAQ,IAAI,SAAS,KAAK,CAAC;AAAA,IAC5G;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAS,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,IAC/C,aAAa,SAAS;AAAA,IACtB,OAAO,QAAQ,QAAQ;AAAA,IACvB,UAAU,KAAK;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,0BAA0B,QAAQ,MAAM;AAAA,IACxC;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AA4CO,SAAS,0BAA0B,SAAyB;AACjE,QAAM,QAAQ,QAAQ,MAAM,uCAAuC;AACnE,MAAI,QAAQ,CAAC,EAAG,QAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAG5C,QAAM,aAAa,QAAQ,MAAM,iBAAiB,KAAK,CAAC;AACxD,MAAI,aAAa;AACjB,aAAW,SAAS,YAAY;AAC9B,kBAAc,MAAM,MAAM,IAAI,EAAE,SAAS;AAAA,EAC3C;AACA,SAAO;AACT;AAMO,SAAS,gBAAgB,SAAkC;AAChE,QAAM,WAA4B,CAAC;AAGnC,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,aAAW,QAAQ,OAAO;AACxB,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,WAAW,GAAG,EAAG;AAG1D,QAAI,WAA4B;AAChC,eAAW,CAAC,KAAK,OAAO,KAAK,OAAO,QAAQ,iBAAiB,GAAG;AAC9D,UAAI,QAAQ,KAAK,OAAO,GAAG;AACzB,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AAGA,QAAI,WAAW;AACf,eAAW,OAAO,mBAAmB;AACnC,UAAI,QAAQ,YAAY,EAAE,SAAS,GAAG,GAAG;AACvC,mBAAW;AACX;AAAA,MACF;AAAA,IACF;AAGA,UAAM,YAAY,QAAQ,MAAM,qCAAqC;AAGrE,UAAM,qBAAqB,OAAO,OAAO,iBAAiB,EAAE;AAAA,MAAK,CAAC,MAChE,EAAE,KAAK,OAAO;AAAA,IAChB;AACA,QAAI,sBAAsB,WAAW;AACnC,eAAS,KAAK;AAAA,QACZ;AAAA,QACA;AAAA,QACA,aAAa,QAAQ,QAAQ,YAAY,EAAE,EAAE,MAAM,GAAG,GAAG;AAAA,QACzD,MAAM,YAAY,CAAC;AAAA,QACnB,MAAM,YAAY,CAAC,IAAI,SAAS,UAAU,CAAC,GAAG,EAAE,IAAI;AAAA,MACtD,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,kBACdC,iBACA,OACoB;AACpB,MAAI,CAAI,eAAWA,eAAc,EAAG,QAAO;AAE3C,QAAM,UAAa,iBAAaA,iBAAgB,OAAO;AACvD,QAAM,WAAW,gBAAgB,OAAO;AACxC,QAAM,qBAAqB,0BAA0B,OAAO;AAE5D,SAAO;AAAA,IACL;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC,cAAc,SAAS;AAAA,IACvB;AAAA,IACA;AAAA,IACA,aAAa,mBAAmB,QAAQ;AAAA,EAC1C;AACF;AAIO,SAAS,eACd,UACA,YACA,UACA,WACQ;AACR,SAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY,QAAQ,IAAI,SAAS;AAAA,EACnC;AACF;AAQO,SAAS,qBACd,SACA,UACA,WACS;AAET,QAAM,UAAU;AAAA,IACd;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,EACF;AACA,MAAO,eAAW,OAAO,KAAQ,eAAW,QAAQ,UAAU,GAAG;AAC/D,UAAM,aAAgB,aAAS,OAAO;AACtC,UAAM,cAAiB,aAAS,QAAQ,UAAU;AAClD,QAAI,WAAW,UAAU,YAAY,QAAS,QAAO;AAAA,EACvD;AAEA,SAAO;AACT;AAIO,SAAS,uBAAuB,UAA0B;AAC/D,QAAM,OAAU,aAAS,QAAQ;AACjC,QAAM,QAAQ,GAAG,QAAQ,IAAI,KAAK,OAAO;AACzC,SAAc,mBAAW,MAAM,EAAE,OAAO,KAAK,EAAE,OAAO,KAAK;AAC7D;AAEO,SAAS,mBACd,UACA,UACS;AACT,QAAM,WAAW,uBAAuB,QAAQ;AAChD,SAAU,eAAgB,WAAK,UAAU,aAAa,GAAG,QAAQ,OAAO,CAAC;AAC3E;AAEO,SAAS,gBACd,UACA,SACM;AACN,QAAM,WAAW,uBAAuB,QAAQ,UAAU;AAC1D,QAAM,aAAkB,WAAK,UAAU,aAAa,GAAG,QAAQ,OAAO;AACtE,MAAO,eAAW,UAAU,GAAG;AAC7B,IAAG,eAAW,UAAU;AAAA,EAC1B;AACF;AAEO,SAAS,gBACd,UACA,SACM;AACN,QAAM,WAAW,uBAAuB,QAAQ,UAAU;AAC1D,QAAM,YAAiB,WAAK,UAAU,WAAW;AACjD,EAAG,cAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAM,aAAkB,WAAK,WAAW,GAAG,QAAQ,OAAO;AAC1D,QAAM,UAAU;AAAA,IACd,UAAU,QAAQ;AAAA,IAClB,YAAY,QAAQ;AAAA,IACpB,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,EACtC;AACA,QAAM,MAAM,GAAG,UAAU,QAAQ,QAAQ,GAAG;AAC5C,EAAG,kBAAc,KAAK,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;AAC/D,EAAG,eAAW,KAAK,UAAU;AAC/B;AAQO,SAAS,mBACd,UACA,SACA,WACQ;AACR,QAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,QAAQ,MAAM,EAAE;AACpE,QAAM,WAAW,GAAG,IAAI,IAAI,SAAS,IAAI,QAAQ,MAAM,MAAM,QAAQ,QAAQ;AAC7E,QAAM,UAAU;AAAA,IACd,MAAM,SAAS,MAAM,QAAQ,MAAM;AAAA,IACnC;AAAA,IACA,SAAS,QAAQ,QAAQ;AAAA,IACzB;AAAA,IACA,cAAmB,eAAS,QAAQ,UAAU,CAAC;AAAA,EACjD,EAAE,KAAK,IAAI;AAEX,QAAM,WAAgB,WAAK,UAAU,OAAO;AAC5C,EAAG,cAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAC1C,QAAM,YAAiB,WAAK,UAAU,QAAQ;AAC9C,QAAM,MAAM,GAAG,SAAS,QAAQ,QAAQ,GAAG;AAC3C,EAAG,kBAAc,KAAK,SAAS,OAAO;AACtC,EAAG,eAAW,KAAK,SAAS;AAC5B,SAAO;AACT;AAQO,SAAS,qBAA8B;AAC5C,SAAO,QAAQ,IAAI,iBAAiB;AACtC;AAMO,SAAS,uBAIP;AACP,MAAI,CAAC,mBAAmB,EAAG,QAAO;AAClC,SAAO;AAAA,IACL,MAAM,QAAQ,IAAI,kBAAkB;AAAA,IACpC,WAAW,SAAS,QAAQ,IAAI,yBAAyB,KAAK,EAAE;AAAA,IAChE,cAAc,QAAQ,IAAI,qBAAqB;AAAA,EACjD;AACF;AAWO,SAAS,oBACd,UACA,UACA,YACA,WACA,UAAkB,WACD;AACjB,QAAM,WAAgB,WAAK,UAAU,OAAO;AAC5C,MAAI,CAAI,eAAW,QAAQ,EAAG,QAAO,CAAC;AAEtC,QAAM,QAAW,gBAAY,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AACtE,QAAM,WAA4B,CAAC;AAEnC,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAgB,WAAK,UAAU,IAAI;AACzC,UAAM,UAAa,iBAAa,UAAU,OAAO;AACjD,UAAM,UAAU,oBAAoB,UAAU,SAAS,UAAU;AAEjE,QAAI,CAAC,QAAS;AAGd,UAAM,KAAK,QAAQ,UAAU,YAAY;AACzC,QACE,OAAO,UAAU,YAAY,KAC7B,OAAO,kBACP,OAAO,SACP,OAAO,IACP;AACA;AAAA,IACF;AAEA,QAAI,oBAAoB,QAAQ,QAAQ,SAAS,SAAS,EAAG;AAE7D,QAAI,qBAAqB,SAAS,UAAU,SAAS,EAAG;AACxD,QAAI,mBAAmB,UAAU,QAAQ,EAAG;AAE5C,aAAS,KAAK,OAAO;AAAA,EACvB;AAEA,SAAO;AACT;AAliBA,IA4DM,iBAEA,mBAEA,oBAgKA,mBAQA;AAxON;AAAA;AAAA;AAgBA;AA4CA,IAAM,kBAAkB,CAAC,WAAW,qBAAqB;AAEzD,IAAM,oBAAoB,CAAC,OAAO,aAAa;AAE/C,IAAM,qBAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA;AAAA,IACF;AA4JA,IAAM,oBAAqD;AAAA,MACzD,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,KAAK;AAAA,MACL,SAAS;AAAA,IACX;AAEA,IAAM,oBAAoB;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA;;;AChPA;AAAA;AAAA;AAAA;AAYA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AA2Cf,SAAS,mBAAmB,SAIjC;AACA,QAAM,YAAY,qBAAqB;AACvC,QAAM,oBAAuC;AAAA,IAC3C,GAAG;AAAA,IACH,WAAW,WAAW,aAAa,2BAA2B;AAAA,IAC9D,sBACG,WAAW,gBACZ,2BAA2B;AAAA,EAC/B;AAEA,QAAM,QAA2B;AAAA,IAC/B,SAAS;AAAA,IACT,eAAe;AAAA,IACf,mBAAmB;AAAA,IACnB,YAAY;AAAA,EACd;AAEA,MAAI,QAA+C;AAEnD,WAASC,KAAI,KAAmB;AAC9B,UAAM,MAAK,oBAAI,KAAK,GAAE,YAAY;AAClC,YAAQ,MAAM,IAAI,EAAE,qBAAqB,GAAG,EAAE;AAAA,EAChD;AAEA,WAAS,iBAAuB;AAC9B,QAAI;AACF,YAAM,UAAU;AAAA,QACd,SAAS,MAAM;AAAA,QACf,WAAW,QAAQ;AAAA,QACnB,YAAY,QAAQ;AAAA,QACpB,gBAAgB,QAAQ;AAAA,QACxB,mBAAmB,MAAM;AAAA,QACzB,YAAY,MAAM;AAAA,QAClB,cAAc,MAAM,gBAChB;AAAA,UACE,UAAU,MAAM,cAAc,QAAQ;AAAA,UACtC,OAAO,MAAM,cAAc,OAAO,SAAS;AAAA,UAC3C,WAAW,MAAM,cAAc;AAAA,UAC/B,QAAQ,MAAM,cAAc,QAAQ;AAAA,QACtC,IACA;AAAA,QACJ,mBAAmB;AAAA,UACjB,WAAW,kBAAkB;AAAA,UAC7B,sBAAsB,kBAAkB;AAAA,QAC1C;AAAA,QACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACpC;AACA,YAAM,WAAgB,WAAK,QAAQ,UAAU,qBAAqB;AAClE,YAAM,MAAM,GAAG,QAAQ,QAAQ,QAAQ,GAAG;AAC1C,MAAG,kBAAc,KAAK,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;AAC/D,MAAG,eAAW,KAAK,QAAQ;AAAA,IAC7B,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,WAAS,WAAiB;AACxB,UAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAG1C,QAAI,MAAM,eAAe;AACvB,yBAAmB;AACnB,qBAAe;AACf;AAAA,IACF;AAGA,UAAM,WAAW;AAAA,MACf,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV;AAEA,QAAI,SAAS,WAAW,GAAG;AACzB,qBAAe;AACf;AAAA,IACF;AAGA,UAAM,UAAU,SAAS,CAAC;AAC1B,uBAAmB,OAAO;AAC1B,mBAAe;AAAA,EACjB;AAEA,WAAS,mBAAmB,SAA8B;AACxD,IAAAA,KAAI,2BAA2B,QAAQ,QAAQ,EAAE;AAIjD,oBAAgB,QAAQ,UAAU,OAAO;AAEzC,QAAI;AAEF,yBAAmB,QAAQ,UAAU,SAAS,QAAQ,SAAS;AAG/D,YAAM,SAAS,kBAAkB,SAAS,QAAQ,WAAW,CAAC;AAI9D,YAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,QAAQ,MAAM,EAAE;AACpE,YAAM,mBAAmB,GAAG,IAAI,aAAa,QAAQ,SAAS,aAAa,QAAQ,QAAQ;AAC3F,YAAM,WAAgB,WAAK,QAAQ,UAAU,OAAO;AACpD,MAAG,cAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAC1C,YAAM,eAAoB,WAAK,UAAU,gBAAgB;AACzD,YAAM,MAAM,GAAG,YAAY,QAAQ,QAAQ,GAAG;AAC9C,MAAG,kBAAc,KAAK,QAAQ,OAAO;AACrC,MAAG,eAAW,KAAK,YAAY;AAE/B,YAAM,gBAAgB;AAAA,QACpB;AAAA,QACA,WAAW,QAAQ;AAAA,QACnB,MACG,WAAW,QACZ;AAAA,QACF,QAAQ,CAAC;AAAA,QACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,QAClC,gBAAgB;AAAA,UACd,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV;AAAA,MACF;AAEA,MAAAA,KAAI,oCAAoC,QAAQ,QAAQ,YAAY;AAAA,IACtE,SAAS,KAAK;AAEZ,MAAAA;AAAA,QACE,kCAAkC,QAAQ,QAAQ,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACzG;AACA,sBAAgB,QAAQ,UAAU,OAAO;AAAA,IAC3C;AAAA,EACF;AAEA,WAAS,qBAA2B;AAClC,QAAI,CAAC,MAAM,cAAe;AAE1B,UAAM,UAAU,MAAM;AACtB,UAAM,UAAU,QAAQ;AAKxB,QAAI,eAAe;AACnB,QAAO,eAAW,OAAO,GAAG;AAC1B,YAAM,OAAU,aAAS,OAAO;AAChC,YAAM,YAAY,QAAQ,OAAO,QAAQ,OAAO,SAAS,CAAC;AAC1D,YAAM,YAAY,WAAW,aAAa,QAAQ;AAClD,qBAAe,KAAK,MAAM,YAAY,IAAI;AAAA,IAC5C;AAEA,QAAI,cAAc;AAEhB,YAAM,WAAW,QAAQ,OAAO,SAAS;AACzC,YAAM,QAAQ,kBAAkB,SAAS,QAAQ;AACjD,UAAI,CAAC,MAAO;AAEZ,cAAQ,OAAO,KAAK,KAAK;AACzB,MAAAA;AAAA,QACE,OAAO,QAAQ,QAAQ,QAAQ,UAAU,QAAQ,KAAK,MAAM,YAAY,cAAc,MAAM,kBAAkB;AAAA,MAChH;AAGA,YAAM,iBAAsB,WAAK,QAAQ,UAAU,aAAa;AAChE,YAAM,MAA0B;AAAA,QAC9B,OAAO;AAAA,QACP,QAAQ,QAAQ;AAAA,QAChB;AAAA,QACA,QAAQ;AAAA,MACV;AAEA,YAAM,SAAS,SAAS,GAAG;AAE3B,UAAI,OAAO,YAAY,QAAQ;AAC7B,QAAAA;AAAA,UACE,OAAO,QAAQ,QAAQ,QAAQ,gBAAgB,OAAO,MAAM,KAAK,OAAO,QAAQ;AAAA,QAClF;AACA,wBAAgB,OAAO;AAAA,MACzB,OAAO;AACL,QAAAA;AAAA,UACE,OAAO,QAAQ,QAAQ,QAAQ,uBAAuB,WAAW,CAAC;AAAA,QACpE;AACA,yBAAiB,SAAS,WAAW,CAAC;AAAA,MACxC;AACA;AAAA,IACF;AAKA,UAAM,qBAAqB,KAAK,KAAK;AACrC,UAAM,UAAU,KAAK,IAAI,IAAI,IAAI,KAAK,QAAQ,SAAS,EAAE,QAAQ;AACjE,QAAI,UAAU,sBAAsB,QAAQ,OAAO,WAAW,GAAG;AAC/D,MAAAA;AAAA,QACE,OAAO,QAAQ,QAAQ,QAAQ,qCAAgC,KAAK,MAAM,UAAU,GAAK,CAAC;AAAA,MAC5F;AACA,YAAM,gBAAgB;AACtB;AAAA,IACF;AAGA,UAAM,mBAAmB,IAAI,KAAK;AAClC,QAAI,QAAQ,OAAO,SAAS,GAAG;AAC7B,YAAM,gBAAgB,IAAI;AAAA,QACxB,QAAQ,OAAO,QAAQ,OAAO,SAAS,CAAC,EAAG;AAAA,MAC7C,EAAE,QAAQ;AACV,UAAI,KAAK,IAAI,IAAI,gBAAgB,kBAAkB;AACjD,QAAAA;AAAA,UACE,OAAO,QAAQ,QAAQ,QAAQ,6CAAwC,KAAK,OAAO,KAAK,IAAI,IAAI,iBAAiB,GAAK,CAAC;AAAA,QACzH;AACA,wBAAgB,OAAO;AACvB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,WAAS,iBAAiB,SAAwB,OAAqB;AACrE,UAAM,SAAS,kBAAkB,QAAQ,SAAS,QAAQ,WAAW,KAAK;AAG1E,UAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,QAAQ,MAAM,EAAE;AACpE,UAAM,mBAAmB,GAAG,IAAI,aAAa,QAAQ,SAAS,aAAa,QAAQ,QAAQ,QAAQ,KAAK,KAAK;AAC7G,UAAM,WAAgB,WAAK,QAAQ,UAAU,OAAO;AACpD,IAAG,cAAU,UAAU,EAAE,WAAW,KAAK,CAAC;AAC1C,UAAM,eAAoB,WAAK,UAAU,gBAAgB;AACzD,UAAM,MAAM,GAAG,YAAY,QAAQ,QAAQ,GAAG;AAC9C,IAAG,kBAAc,KAAK,QAAQ,OAAO;AACrC,IAAG,eAAW,KAAK,YAAY;AAAA,EACjC;AAEA,WAAS,gBAAgB,SAA8B;AACrD,YAAQ,gBAAe,oBAAI,KAAK,GAAE,YAAY;AAK9C,UAAM,WAAgB,WAAK,QAAQ,UAAU,OAAO;AACpD,QAAO,eAAW,QAAQ,GAAG;AAC3B,YAAM,SAAS,YAAY,QAAQ,SAAS,aAAa,QAAQ,QAAQ,QAAQ;AACjF,YAAM,QAAW,gBAAY,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,CAAC;AACvE,iBAAW,KAAK,OAAO;AACrB,QAAG,eAAgB,WAAK,UAAU,CAAC,CAAC;AAAA,MACtC;AAAA,IACF;AAEA,UAAM,gBAAgB;AACtB,UAAM;AACN,IAAAA;AAAA,MACE,OAAO,QAAQ,QAAQ,QAAQ,qBAAqB,QAAQ,OAAO,MAAM;AAAA,IAC3E;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AACN,UAAI,CAAC,mBAAmB,GAAG;AACzB,QAAAA,KAAI,8CAAyC;AAC7C;AAAA,MACF;AAEA,YAAM,UAAU;AAChB,MAAAA;AAAA,QACE,iCAAiC,WAAW,QAAQ,UAAU,UAAU,QAAQ,cAAc,WAAW,kBAAkB,SAAS;AAAA,MACtI;AAGA,qBAAe;AAGf,eAAS;AAGT,cAAQ,YAAY,UAAU,QAAQ,cAAc;AAAA,IACtD;AAAA,IAEA,OAAO;AACL,YAAM,UAAU;AAChB,UAAI,OAAO;AACT,sBAAc,KAAK;AACnB,gBAAQ;AAAA,MACV;AACA,qBAAe;AACf,MAAAA,KAAI,8BAA8B;AAAA,IACpC;AAAA,IAEA,WAAW;AACT,aAAO,EAAE,GAAG,MAAM;AAAA,IACpB;AAAA,EACF;AACF;AA/VA;AAAA;AAAA;AAcA;AAaA;AAAA;AAAA;;;AC3BA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,SAAS,aAAa;AACtB,SAAS,eAAe,qBAAqB;;;ACH7C,YAAYC,SAAQ;AACpB,YAAYC,WAAU;;;ACDtB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAsBf,IAAI,eAAe;AAGnB,SAAS,kBAAkB;AAChC,iBAAe;AACjB;AA8FA,IAAI,YAAY;AAMT,SAAS,IAAI,SAAuB;AACzC,MAAI,CAAC,UAAW,SAAQ,IAAI,KAAK,OAAO,EAAE;AAC5C;;;ADlHO,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAIlC,IAAM,0BAA0B;AAChC,IAAM,yBAAyB;AAMxB,SAAS,aAAa,WAAmB,QAAQ,IAAI,GAAW;AACrE,MAAI,MAAW,cAAQ,QAAQ;AAC/B,SAAO,MAAM;AACX,QAAO,eAAgB,WAAK,KAAK,MAAM,CAAC,EAAG,QAAO;AAClD,QAAO,eAAgB,WAAK,KAAK,cAAc,CAAC,GAAG;AACjD,UAAI,CAAC,cAAc;AACjB,wBAAgB;AAChB;AAAA,UACE;AAAA,QAEF;AAAA,MACF;AACA,aAAO;AAAA,IACT;AACA,UAAM,SAAc,cAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,MAAI,CAAC,cAAc;AACjB,oBAAgB;AAChB;AAAA,MACE;AAAA,IAEF;AAAA,EACF;AACA,SAAO,QAAQ,IAAI;AACrB;AAIA,SAAS,aAAgB,UAA4B;AACnD,MAAI,CAAI,eAAW,QAAQ,EAAG,QAAO;AACrC,MAAI;AACF,UAAM,MAAS,iBAAa,UAAU,OAAO;AAC7C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,iBAAiB,UAA0C;AACzE,SAAO,aAAmC,WAAK,UAAU,kBAAkB,CAAC;AAC9E;AAEO,SAAS,gBAAgB,UAAyC;AACvE,SAAO,aAAkC,WAAK,UAAU,iBAAiB,CAAC;AAC5E;AAEA,SAAS,qBAAqB,YAAoB,KAA4B;AAC5E,QAAM,QAAQ,WAAW,MAAM,IAAI,OAAO,IAAI,GAAG,eAAe,GAAG,CAAC;AACpE,SAAO,QAAQ,CAAC,GAAG,KAAK,KAAK;AAC/B;AAEA,SAAS,sBAAsB,UAA0C;AACvE,QAAM,WAAgB,WAAK,UAAU,kBAAkB;AACvD,MAAI,CAAI,eAAW,QAAQ,EAAG,QAAO;AAErC,MAAI;AACF,UAAM,MAAS,iBAAa,UAAU,OAAO;AAC7C,UAAM,WAAW,qBAAqB,KAAK,eAAe;AAC1D,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,EAAE,SAAS;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,cACd,YAA6B,CAAC,GAC9B,UACkB;AAClB,QAAM,WAAW,aAAa,QAAQ;AACtC,QAAM,SAAS,iBAAiB,QAAQ,KAAK,CAAC;AAC9C,QAAM,QAAQ,gBAAgB,QAAQ,KAAK,CAAC;AAC5C,QAAM,SAAS,sBAAsB,QAAQ,KAAK,CAAC;AAEnD,QAAM,UAAyD;AAAA,IAC7D,UAAU;AAAA,IACV,UAAU;AAAA,IACV,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,WAAW;AAAA,EACb;AAGA,MAAI;AACJ,MAAI,UAAU,UAAU;AACtB,eAAW,YAAY,UAAU,UAAU,QAAQ;AACnD,YAAQ,WAAW;AAAA,EACrB,WAAW,QAAQ,IAAI,eAAe;AACpC,eAAW,YAAY,UAAU,QAAQ,IAAI,aAAa;AAC1D,YAAQ,WAAW;AAAA,EACrB,WAAW,MAAM,UAAU;AACzB,eAAW,YAAY,UAAU,MAAM,QAAQ;AAC/C,YAAQ,WAAW;AAAA,EACrB,WAAW,OAAO,UAAU;AAC1B,eAAW,YAAY,UAAU,OAAO,QAAQ;AAChD,YAAQ,WAAW;AAAA,EACrB,WAAW,OAAO,UAAU;AAC1B,eAAW,YAAY,UAAU,OAAO,QAAQ;AAChD,YAAQ,WAAW;AAAA,EACrB,OAAO;AACL,eAAgB,WAAK,UAAU,WAAW;AAAA,EAC5C;AAGA,MAAI;AACJ,MAAI,UAAU,UAAU;AACtB,eAAW,YAAY,UAAU,UAAU,QAAQ;AACnD,YAAQ,WAAW;AAAA,EACrB,WAAW,QAAQ,IAAI,eAAe;AACpC,eAAW,YAAY,UAAU,QAAQ,IAAI,aAAa;AAC1D,YAAQ,WAAW;AAAA,EACrB,WAAW,MAAM,UAAU;AACzB,eAAW,YAAY,UAAU,MAAM,QAAQ;AAC/C,YAAQ,WAAW;AAAA,EACrB,WAAW,OAAO,UAAU;AAC1B,eAAW,YAAY,UAAU,OAAO,QAAQ;AAChD,YAAQ,WAAW;AAAA,EACrB,OAAO;AACL,eAAgB,WAAK,UAAU,YAAY;AAAA,EAC7C;AAGA,MAAI;AACJ,MAAI,UAAU,gBAAgB;AAC5B,qBAAiB,UAAU;AAC3B,YAAQ,iBAAiB;AAAA,EAC3B,WAAW,QAAQ,IAAI,qBAAqB;AAC1C,qBAAiB,QAAQ,IAAI;AAC7B,YAAQ,iBAAiB;AAAA,EAC3B,WAAW,MAAM,gBAAgB;AAC/B,qBAAiB,MAAM;AACvB,YAAQ,iBAAiB;AAAA,EAC3B,WAAW,OAAO,gBAAgB;AAChC,qBAAiB,OAAO;AACxB,YAAQ,iBAAiB;AAAA,EAC3B,OAAO;AACL,qBAAiB;AAAA,EACnB;AAGA,MAAI;AACJ,MAAI,UAAU,cAAc;AAC1B,mBAAe,UAAU;AACzB,YAAQ,eAAe;AAAA,EACzB,WAAW,QAAQ,IAAI,oBAAoB;AACzC,mBAAe,QAAQ,IAAI;AAC3B,YAAQ,eAAe;AAAA,EACzB,WAAW,MAAM,cAAc;AAC7B,mBAAe,MAAM;AACrB,YAAQ,eAAe;AAAA,EACzB,WAAW,OAAO,cAAc;AAC9B,mBAAe,OAAO;AACtB,YAAQ,eAAe;AAAA,EACzB,OAAO;AACL,mBAAe;AAAA,EACjB;AAGA,QAAM,YAAY,MAAM,aAAa,OAAO,aAAa;AAEzD,SAAO;AAAA,IACL,QAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AA2BA,SAAS,YAAY,UAAkB,GAAmB;AACxD,QAAM,aAAa,iBAAiB,CAAC;AACrC,SAAY,iBAAW,UAAU,IAC7B,aACK,cAAQ,UAAU,UAAU;AACvC;AA2OO,SAAS,iBAAiB,OAAuB;AACtD,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,oBAAoB,EAAE;AAC3D,MAAI,kBAAkB,KAAK,OAAO,GAAG;AACnC,WAAO;AAAA,EACT;AAIA,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAM,QAAQ,QAAQ,MAAM,sBAAsB;AAClD,QAAI,OAAO;AACT,aAAO,GAAG,MAAM,CAAC,EAAE,YAAY,CAAC,MAAM,MAAM,CAAC,EAAE,QAAQ,OAAO,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AAEA,SAAO;AACT;;;AExeA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AACtB,SAAS,gBAAgB;AAmBlB,SAAS,gBAAgB,UAAiC;AAC/D,QAAM,SAAc,WAAK,UAAU,eAAe;AAClD,MAAI,CAAI,eAAW,MAAM,EAAG,QAAO;AACnC,MAAI;AACF,UAAM,MAAS,iBAAa,QAAQ,OAAO,EAAE,KAAK;AAClD,WAAO,IAAI,SAAS,IAAI,IAAI,QAAQ,MAAM,EAAE,IAAI;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIA,SAAS,mBAA6B;AACpC,MAAI,QAAQ,aAAa,SAAS;AAChC,WAAO;AAAA,MACL,QAAQ,IAAI;AAAA,MACZ,QAAQ,IAAI,UAAe,WAAK,QAAQ,IAAI,SAAS,KAAK,IAAI;AAAA,MAC9D,QAAQ,IAAI,eACH,WAAK,QAAQ,IAAI,cAAc,KAAK,IACzC;AAAA,MACJ,QAAQ,IAAI,cACH,WAAK,QAAQ,IAAI,aAAa,SAAS,WAAW,KAAK,IAC5D;AAAA,IACN,EAAE,OAAO,OAAO;AAAA,EAClB;AAEA,SAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI,OACH,WAAK,QAAQ,IAAI,MAAM,UAAU,SAAS,KAAK,IACpD;AAAA,IACJ,QAAQ,IAAI,OAAY,WAAK,QAAQ,IAAI,MAAM,MAAM,IAAI;AAAA,IACzD,QAAQ,IAAI,gBACH,WAAK,QAAQ,IAAI,eAAe,KAAK,IAC1C;AAAA,EACN,EAAE,OAAO,OAAO;AAClB;AAEA,SAAS,qBAA6B;AACpC,SAAO,QAAQ,aAAa,UAAU,aAAa;AACrD;AAEO,SAAS,aAAa,gBAAuC;AAClE,QAAM,OAAO,iBAAiB;AAC9B,QAAM,MAAM,mBAAmB;AAE/B,aAAW,WAAW,MAAM;AAC1B,UAAM,YAAiB;AAAA,MACrB;AAAA,MACA;AAAA,MACA,IAAI,cAAc;AAAA,MAClB;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAI,eAAW,SAAS,EAAG;AAE/B,QAAI;AACF,YAAM,IAAI,SAAS,IAAI,SAAS,eAAe;AAAA,QAC7C,UAAU;AAAA,QACV,SAAS;AAAA,MACX,CAAC,EAAE,KAAK;AACR,UAAI,EAAE,WAAW,IAAI,eAAe,MAAM,GAAG,EAAE,CAAC,CAAC,GAAG,GAAG;AACrD,eAAO;AAAA,MACT;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,SAAO;AACT;AAIO,SAAS,uBAAuB,SAAgC;AACrE,MAAI;AACF,UAAM,UAAU,SAAS,IAAI,OAAO,eAAe;AAAA,MACjD,UAAU;AAAA,MACV,SAAS;AAAA,IACX,CAAC,EAAE,KAAK;AACR,UAAM,QAAQ,QAAQ,MAAM,YAAY;AACxC,WAAO,QAAQ,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,uBAAuB,SAA0B;AAC/D,QAAM,QAAQ,uBAAuB,OAAO;AAC5C,MAAI,UAAU,QAAQ,SAAS,GAAI,QAAO;AAC1C,MAAI;AACF,aAAS,IAAI,OAAO,sCAAsC;AAAA,MACxD,SAAS;AAAA,MACT,OAAO;AAAA,IACT,CAAC;AACD,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,SAAS,gBAAgB,UAAiC;AAC/D,QAAM,aAAa;AAAA,IACZ,WAAK,UAAU,gBAAgB,QAAQ,SAAS;AAAA,IAChD,WAAK,UAAU,gBAAgB,QAAQ,SAAS;AAAA,IAChD,WAAK,UAAU,gBAAgB,QAAQ,KAAK;AAAA,EACnD;AACA,aAAW,KAAK,YAAY;AAC1B,QAAO,eAAW,CAAC,EAAG,QAAO;AAAA,EAC/B;AACA,SAAO;AACT;AAQO,SAAS,aAAa,UAAiC;AAC5D,QAAM,iBAAiB,gBAAgB,QAAQ;AAC/C,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,WAAW,aAAa,cAAc;AAC5C,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAY,cAAQ,QAAQ;AAC9B;AASO,SAAS,mBACd,eACA,UACiB;AAEjB,MAAI,kBAAkB,SAAS,cAAc,SAAS,SAAS,GAAG;AAChE,WAAO;AAAA,MACL,SAAS;AAAA,MACT,oBAAoB;AAAA,MACpB,QAAQ;AAAA,MACR,cAAc;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,iBAAiB,gBAAgB,QAAQ;AAC/C,MAAI,gBAAgB;AAClB,UAAM,UAAU,aAAa,cAAc;AAC3C,QAAI,SAAS;AACX,YAAMC,SAAQ,uBAAuB,OAAO;AAC5C,aAAO;AAAA,QACL,SAAS;AAAA,QACT,oBAAoB,uBAAuB,OAAO;AAAA,QAClD,QAAQ;AAAA,QACR,cAAcA;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,QAAQ,uBAAuB,aAAa;AAClD,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,oBAAoB,uBAAuB,aAAa;AAAA,MACxD,QAAQ,UAAU,uBAAuB,MAAM,IAAI,SAAS;AAAA,MAC5D,cAAc;AAAA,IAChB;AAAA,EACF;AAGA,QAAM,MAAM,gBAAgB,QAAQ;AACpC,MAAI,KAAK;AACP,WAAO;AAAA,MACL,SAAS;AAAA,MACT,oBAAoB;AAAA,MACpB,QAAQ;AAAA,MACR,cAAc;AAAA,IAChB;AAAA,EACF;AAGA,SAAO;AAAA,IACL,SAAS;AAAA,IACT,oBAAoB;AAAA,IACpB,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB;AACF;AAQO,SAAS,gBACd,UACA,UAA6B,QAAQ,KAClB;AACnB,QAAM,SAAS,aAAa,QAAQ;AACpC,MAAI,CAAC,OAAQ,QAAO,EAAE,GAAG,QAAQ;AAEjC,QAAM,UAAU,QAAQ,aAAa,UAAU,SAAS;AACxD,QAAM,cAAc,QAAQ,OAAO,KAAK,QAAQ,QAAQ;AAExD,SAAO;AAAA,IACL,GAAG;AAAA,IACH,CAAC,OAAO,GAAG,GAAG,MAAM,GAAQ,eAAS,GAAG,WAAW;AAAA,EACrD;AACF;;;AC5PA,YAAYC,SAAQ;AACpB,YAAYC,WAAU;AA2Cf,IAAMC,0BAAyB;AAqE/B,SAAS,oBACd,SACA,MACQ;AACR,QAAM,gBAAgB,WAAWC,yBAAwB,QAAQ,OAAO,EAAE;AAC1E,MAAI,QAAQ,MAAM;AAChB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,YAAY;AACnC,WAAO,OAAO,OAAO,IAAI;AACzB,WAAO,OAAO,SAAS,EAAE,QAAQ,OAAO,EAAE;AAAA,EAC5C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AJnHA,SAAS,yBAAwC;AAC/C,MAAI,MAAW,cAAa,cAAQ,cAAc,YAAY,GAAG,CAAC,CAAC;AAEnE,SAAO,MAAM;AACX,QAAO,eAAgB,WAAK,KAAK,kBAAkB,CAAC,EAAG,QAAO;AAC9D,QAAO,eAAgB,WAAK,KAAK,iBAAiB,CAAC,EAAG,QAAO;AAC7D,QAAO,eAAgB,WAAK,KAAK,WAAW,4BAA4B,CAAC;AACvE,aAAO;AACT,UAAM,SAAc,cAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;AAIA,SAAS,uBACP,UACA,UACA,UACM;AACN,MAAI,QAAQ,IAAI,iBAAiB,OAAQ;AAGzC,8EACG,KAAK,CAAC,EAAE,oBAAAC,oBAAmB,MAAM;AAChC,UAAM,YACJ,QAAQ,IAAI,kBACZ,QAAQ,IAAI,wBACZ;AACF,UAAM,UACJ,QAAQ,IAAI,gBACZ,QAAQ,IAAI,0BACZ;AACF,UAAM,aAAa,QAAQ,IAAI,yBAAyB;AACxD,UAAM,mBAAmB,YAAiB,WAAK,UAAU,YAAY;AAErE,UAAM,OAAOA,oBAAmB;AAAA,MAC9B;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA;AAAA,IAClB,CAAC;AAED,SAAK,MAAM;AAGX,YAAQ,GAAG,WAAW,MAAM,KAAK,KAAK,CAAC;AACvC,YAAQ,GAAG,UAAU,MAAM,KAAK,KAAK,CAAC;AAAA,EACxC,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,YAAQ,MAAM,oCAAoC,GAAG;AAAA,EACvD,CAAC;AACL;AAaO,SAAS,0BACd,UACA,YAAoB,YAAY,KAChC,aAAgD,gBACjC;AACf,QAAM,YAAiB,cAAQ,cAAc,SAAS,CAAC;AACvD,QAAM,aAAa;AAAA;AAAA,IAEZ,WAAK,WAAW,6BAA6B;AAAA;AAAA,IAE7C,WAAK,WAAW,4BAA4B;AAAA;AAAA,IAE5C;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IAEK;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA,IAEK,WAAK,UAAU,WAAW,4BAA4B;AAAA,EAC7D;AAEA,aAAW,aAAa,YAAY;AAClC,QAAI,WAAW,SAAS,GAAG;AACzB,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,sBACd,YACA,SACU;AACV,QAAM,OAAO;AAAA,IACX;AAAA,IACA,eAAe,QAAQ,QAAQ;AAAA,IAC/B,eAAe,QAAQ,QAAQ;AAAA,IAC/B,oBAAoB,QAAQ,YAAY;AAAA,EAC1C;AAEA,MAAI,QAAQ,WAAW;AACrB,SAAK,KAAK,gBAAgB,QAAQ,SAAS,EAAE;AAAA,EAC/C;AAEA,MAAI,QAAQ,kBAAkB;AAC5B,SAAK,KAAK,wBAAwB,QAAQ,gBAAgB,EAAE;AAAA,EAC9D;AAEA,MAAI,QAAQ,UAAU;AACpB,SAAK,KAAK,eAAe,QAAQ,QAAQ,EAAE;AAAA,EAC7C;AAEA,SAAO;AACT;AAEA,eAAe,OAAsB;AACnC,QAAM,eAAe,uBAAuB,KAAK;AACjD,QAAM,EAAE,OAAO,IAAI,cAAc,CAAC,GAAG,YAAY;AAEjD,QAAM,WAAW,OAAO;AACxB,QAAM,WAAW,OAAO;AACxB,QAAM,kBAAkB,QAAQ,IAAI;AACpC,QAAM,eAAe,kBACjB,OAAO,SAAS,iBAAiB,EAAE,IACnC;AACJ,QAAM,kBAAkB,QAAQ,IAAI,sBAAsB,KAAK;AAC/D,QAAM,mBAAmB,QAAQ,IAAI,wBAAwB,KAAK;AAClE,QAAM,eACJ,mBACA;AAAA,IACE,OAAO;AAAA,IACP,OAAO,SAAS,YAAY,IAAI,eAAe;AAAA,EACjD;AAIF,QAAM,aAAa,QAAQ,IAAI;AAC/B,QAAM,cAAc,QAAQ,IAAI;AAChC,MAAI;AACJ,MAAI,aAAa;AACf,eAAW;AAAA,EACb,WAAW,YAAY;AACrB,UAAMC,YAAgB;AAAA,MACf,WAAK,UAAU,QAAQ,2BAA2B,UAAU,EAAE;AAAA,IACrE;AACA,UAAM,eAAoB,cAAQ,UAAU,MAAM,IAAS;AAC3D,QAAI,CAACA,UAAS,WAAW,YAAY,GAAG;AACtC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,eAAWA;AAAA,EACb;AAIA,QAAM,cAAc,QAAQ,IAAI;AAChC,QAAM,WAAW,cACb;AAAA,IACE,SAAS;AAAA,IACT,oBAAoB,QAAQ,IAAI,oBAAoB;AAAA,IACpD,QAAQ;AAAA,IACR,cAAc;AAAA,EAChB,IACA,mBAAmB,OAAO,gBAAgB,QAAQ;AAEtD,QAAM,UAAU,SAAS;AACzB,QAAM,YACJ,QAAQ,IAAI,gBAAgB,KAAK,KACjC,QAAQ,IAAI,sBAAsB,KAAK,KACvC;AAGF,QAAM,aAAa,0BAA0B,QAAQ;AACrD,MAAI,CAAC,YAAY;AACf,UAAM,IAAI;AAAA,MACR,yCAAyC,QAAQ;AAAA;AAAA,IAEnD;AAAA,EACF;AAGA,QAAM,OAAiB,CAAC;AACxB,MAAI,SAAS,oBAAoB;AAC/B,SAAK,KAAK,4BAA4B;AAAA,EACxC;AACA,OAAK;AAAA,IACH,GAAG,sBAAsB,YAAY;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAGA,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,SAAU,MAAK,KAAK,eAAe,QAAQ,EAAE;AAEjD,QAAM,cAAc,QAAQ,IAAI;AAChC,MAAI,YAAa,MAAK,KAAK,kBAAkB,WAAW,EAAE;AAE1D,QAAM,mBAAmB,QAAQ,IAAI;AACrC,MAAI,iBAAkB,MAAK,KAAK,uBAAuB,gBAAgB,EAAE;AAEzE,QAAM,kBAAkB,QAAQ,IAAI;AACpC,MAAI;AACF,SAAK,KAAK,8BAA8B,eAAe,EAAE;AAE3D,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,SAAU,MAAK,KAAK,eAAe,QAAQ,EAAE;AAEjD,MAAI,QAAQ,IAAI,kBAAkB,OAAQ,MAAK,KAAK,aAAa;AACjE,MAAI,QAAQ,IAAI,yBAAyB;AACvC,SAAK,KAAK,6BAA6B;AAGzC,QAAM,aAAa,gBAAgB,QAAQ;AAE3C,QAAM,QAAQ,MAAM,SAAS,MAAM;AAAA,IACjC,KAAK;AAAA,IACL,KAAK;AAAA,IACL,OAAO;AAAA,EACT,CAAC;AAGD,yBAAuB,UAAU,UAAU,QAAQ;AAEnD,QAAM,GAAG,QAAQ,CAAC,MAAqB,WAAkC;AACvE,QAAI,QAAQ;AACV,cAAQ,KAAK,QAAQ,KAAK,MAAM;AAChC;AAAA,IACF;AACA,YAAQ,KAAK,QAAQ,CAAC;AAAA,EACxB,CAAC;AAED,QAAM,GAAG,SAAS,CAAC,UAAiB;AAClC,YAAQ,MAAM,OAAO,KAAK,CAAC;AAC3B,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;AAEA,SAAS,oBAA6B;AACpC,QAAM,QAAQ,QAAQ,KAAK,CAAC;AAC5B,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,YAAY,QAAQ,cAAmB,cAAQ,KAAK,CAAC,EAAE;AAChE;AAEA,IAAI,kBAAkB,GAAG;AACvB,OAAK,EAAE,MAAM,CAAC,UAAU;AACtB,YAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AACpE,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":["fs","fs","path","crypto","reviewFilePath","fs","path","log","fs","path","fs","path","fs","path","major","fs","path","DEFAULT_APP_SERVER_URL","DEFAULT_APP_SERVER_URL","createHeadlessLoop","resolved"]}
1
+ {"version":3,"sources":["../../src/engine/termination.ts","../../src/engine/review.ts","../../src/engine/headless-loop.ts","../../src/bridges/codex-bridge-runner.ts","../../src/config/resolve.ts","../../src/utils.ts","../../src/runtime/resolve-node.ts","../../src/engine/bridge-app-server-lifecycle.ts"],"sourcesContent":["/**\n * Termination engine — decides when a review session should stop.\n *\n * Strategies are evaluated in priority order. First non-\"continue\" verdict wins.\n * Default order: manual-stop → round-cap → repetition → quality → diff-insignificance\n */\nimport * as fs from \"node:fs\";\nimport * as crypto from \"node:crypto\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport type TerminationStrategy =\n | \"diff-insignificance\"\n | \"repetition-detection\"\n | \"quality-threshold\"\n | \"round-cap\"\n | \"manual-stop\";\n\nexport type TerminationVerdict = \"continue\" | \"stop\" | \"escalate\";\n\nexport type FindingSeverity =\n | \"critical\"\n | \"high\"\n | \"medium\"\n | \"low\"\n | \"nitpick\";\n\nexport interface ReviewFinding {\n severity: FindingSeverity;\n category: string;\n description: string;\n file?: string;\n line?: number;\n}\n\nexport interface ReviewRound {\n round: number;\n timestamp: string;\n findingCount: number;\n findings: ReviewFinding[];\n suggestedDiffLines: number;\n findingHash: string;\n}\n\nexport interface TerminationConfig {\n strategies: TerminationStrategy[];\n maxRounds: number;\n diffThreshold: number;\n repetitionThreshold: number;\n qualitySeverityFloor: FindingSeverity;\n}\n\nexport interface TerminationContext {\n round: number;\n rounds: ReviewRound[];\n stopSignalPath: string;\n config: TerminationConfig;\n}\n\nexport interface TerminationResult {\n verdict: TerminationVerdict;\n reason: string;\n strategy: TerminationStrategy;\n summary: string;\n}\n\n// ── Defaults ───────────────────────────────────────────────────────\n\nexport const DEFAULT_TERMINATION_CONFIG: TerminationConfig = {\n strategies: [\n \"manual-stop\",\n \"round-cap\",\n \"repetition-detection\",\n \"quality-threshold\",\n \"diff-insignificance\",\n ],\n maxRounds: 5,\n diffThreshold: 3,\n repetitionThreshold: 2,\n qualitySeverityFloor: \"high\",\n};\n\n// ── Severity ranking ───────────────────────────────────────────────\n\nconst SEVERITY_RANK: Record<FindingSeverity, number> = {\n critical: 5,\n high: 4,\n medium: 3,\n low: 2,\n nitpick: 1,\n};\n\nfunction isAtOrAbove(\n severity: FindingSeverity,\n floor: FindingSeverity,\n): boolean {\n return SEVERITY_RANK[severity] >= SEVERITY_RANK[floor];\n}\n\n// ── Finding hash ───────────────────────────────────────────────────\n\nexport function computeFindingHash(findings: ReviewFinding[]): string {\n const normalized = findings\n .filter((f) => isAtOrAbove(f.severity, \"high\"))\n .map((f) => `${f.category}:${f.description.slice(0, 100)}`)\n .sort()\n .join(\"|\");\n\n if (!normalized) return \"empty\";\n\n return crypto\n .createHash(\"sha256\")\n .update(normalized)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\n// ── Strategy evaluators ────────────────────────────────────────────\n\nfunction evalManualStop(ctx: TerminationContext): TerminationResult | null {\n if (fs.existsSync(ctx.stopSignalPath)) {\n return {\n verdict: \"stop\",\n reason: `Manual stop signal found at ${ctx.stopSignalPath}`,\n strategy: \"manual-stop\",\n summary: `Review stopped manually at round ${ctx.round}`,\n };\n }\n return null;\n}\n\nfunction evalRoundCap(ctx: TerminationContext): TerminationResult | null {\n if (ctx.round >= ctx.config.maxRounds) {\n return {\n verdict: \"stop\",\n reason: `Round cap reached (${ctx.round}/${ctx.config.maxRounds})`,\n strategy: \"round-cap\",\n summary: `Review stopped at round cap (${ctx.config.maxRounds})`,\n };\n }\n return null;\n}\n\nfunction evalRepetition(ctx: TerminationContext): TerminationResult | null {\n if (ctx.rounds.length < 2) return null;\n\n const latest = ctx.rounds[ctx.rounds.length - 1];\n if (!latest) return null;\n\n let count = 0;\n for (const round of ctx.rounds) {\n if (round.findingHash === latest.findingHash) count++;\n }\n\n if (count >= ctx.config.repetitionThreshold) {\n return {\n verdict: \"stop\",\n reason: `Same finding hash repeated ${count} times (threshold: ${ctx.config.repetitionThreshold})`,\n strategy: \"repetition-detection\",\n summary: `Review going in circles — same findings repeated ${count}x`,\n };\n }\n\n return null;\n}\n\nfunction evalQualityThreshold(\n ctx: TerminationContext,\n): TerminationResult | null {\n if (ctx.rounds.length === 0) return null;\n\n const latest = ctx.rounds[ctx.rounds.length - 1];\n if (!latest) return null;\n\n // Guard: if parser extracted nothing at all (0 findings + 0 diff lines),\n // treat as inconclusive — not \"clean\". The parser may have failed to\n // extract from malformed output.\n if (\n latest.findingCount === 0 &&\n latest.suggestedDiffLines === 0 &&\n latest.findings.length === 0\n ) {\n return null; // inconclusive — continue to next strategy\n }\n\n const significantFindings = latest.findings.filter((f) =>\n isAtOrAbove(f.severity, ctx.config.qualitySeverityFloor),\n );\n\n if (significantFindings.length === 0) {\n return {\n verdict: \"stop\",\n reason: `No findings at ${ctx.config.qualitySeverityFloor}+ severity in round ${ctx.round}`,\n strategy: \"quality-threshold\",\n summary: `Review clean — no ${ctx.config.qualitySeverityFloor}+ findings in round ${ctx.round}`,\n };\n }\n\n return null;\n}\n\nfunction evalDiffInsignificance(\n ctx: TerminationContext,\n): TerminationResult | null {\n if (ctx.rounds.length === 0) return null;\n\n const latest = ctx.rounds[ctx.rounds.length - 1];\n if (!latest) return null;\n\n // Guard: same as quality-threshold — empty output is inconclusive, not trivial\n if (\n latest.findingCount === 0 &&\n latest.suggestedDiffLines === 0 &&\n latest.findings.length === 0\n ) {\n return null;\n }\n\n if (latest.suggestedDiffLines < ctx.config.diffThreshold) {\n return {\n verdict: \"stop\",\n reason: `Suggested diff (${latest.suggestedDiffLines} lines) below threshold (${ctx.config.diffThreshold})`,\n strategy: \"diff-insignificance\",\n summary: `Review suggestions are trivial (${latest.suggestedDiffLines} lines)`,\n };\n }\n\n return null;\n}\n\nconst STRATEGY_EVALUATORS: Record<\n TerminationStrategy,\n (ctx: TerminationContext) => TerminationResult | null\n> = {\n \"manual-stop\": evalManualStop,\n \"round-cap\": evalRoundCap,\n \"repetition-detection\": evalRepetition,\n \"quality-threshold\": evalQualityThreshold,\n \"diff-insignificance\": evalDiffInsignificance,\n};\n\n// ── Main evaluator ─────────────────────────────────────────────────\n\nexport function evaluate(ctx: TerminationContext): TerminationResult {\n for (const strategy of ctx.config.strategies) {\n const evaluator = STRATEGY_EVALUATORS[strategy];\n if (!evaluator) continue;\n\n const result = evaluator(ctx);\n if (result) return result;\n }\n\n return {\n verdict: \"continue\",\n reason: \"All strategies passed — review continues\",\n strategy:\n ctx.config.strategies[ctx.config.strategies.length - 1] ?? \"round-cap\",\n summary: `Round ${ctx.round} complete, continuing`,\n };\n}\n","/**\n * Review engine — detects review requests, builds prompts, parses output.\n *\n * This module handles the \"what\" of review sessions.\n * The termination engine handles the \"when to stop.\"\n * The bridge handles the \"how to deliver.\"\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as crypto from \"node:crypto\";\nimport type {\n ReviewFinding,\n ReviewRound,\n FindingSeverity,\n TerminationConfig,\n} from \"./termination.js\";\nimport { computeFindingHash } from \"./termination.js\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport type AgentRole = \"reviewer\" | \"validator\" | \"long-running\";\n\nexport interface ReviewRequest {\n sourcePath: string;\n sender: string;\n recipient: string;\n prNumber: number;\n branch?: string;\n generation: string;\n isReReview: boolean;\n round: number;\n}\n\nexport interface ReviewSession {\n request: ReviewRequest;\n agentName: string;\n role: AgentRole;\n rounds: ReviewRound[];\n startedAt: string;\n terminatedAt?: string;\n reviewFilePath: string;\n}\n\nexport interface ReviewEngineConfig {\n role: AgentRole;\n generation: string;\n commsDir: string;\n repoRoot: string;\n agentName: string;\n termination: TerminationConfig;\n}\n\nexport interface HeadlessConfig {\n enabled: boolean;\n role: AgentRole;\n termination: TerminationConfig;\n}\n\n// ── Request Detection ──────────────────────────────────────────────\n\nconst REVIEW_KEYWORDS = [/리뷰\\s*요청/, /review[- ]?request/i];\n\nconst REREVIEW_KEYWORDS = [/재리뷰/, /re-?review/i];\n\nconst PR_NUMBER_PATTERNS = [\n /PR\\s*#?\\s*(\\d+)/i,\n /pull\\/(\\d+)/,\n /review[-_ ]?(\\d+)/i,\n];\n\nfunction trimAddress(value: string): string {\n return value.trim();\n}\n\nfunction canonicalizeAgentId(value: string): string {\n return trimAddress(value).replace(/-/g, \"_\").toLowerCase();\n}\n\nexport function isOwnMessageAddress(\n sender: string,\n agentId: string,\n agentName: string,\n): boolean {\n const normalizedSender = trimAddress(sender);\n if (!normalizedSender) return false;\n\n return (\n canonicalizeAgentId(normalizedSender) === canonicalizeAgentId(agentId) ||\n normalizedSender.toLowerCase() === trimAddress(agentName).toLowerCase()\n );\n}\n\n/**\n * Parse inbox filename to extract routing info.\n * Format: YYYYMMDD-sender-recipient-subject.md\n */\nexport function parseInboxFilename(filename: string): {\n date: string;\n sender: string;\n recipient: string;\n subject: string;\n} | null {\n const base = path.basename(filename, \".md\");\n const match = base.match(/^(\\d{8})-([^-]+)-([^-]+)-(.+)$/);\n if (!match) return null;\n\n return {\n date: match[1],\n sender: match[2],\n recipient: match[3],\n subject: match[4],\n };\n}\n\n/**\n * Extract PR number from text content.\n */\nexport function extractPrNumber(text: string): number | null {\n for (const pattern of PR_NUMBER_PATTERNS) {\n const match = text.match(pattern);\n if (match?.[1]) return parseInt(match[1], 10);\n }\n return null;\n}\n\n/**\n * Detect if a file represents a review request.\n * Returns a ReviewRequest if detected, null otherwise.\n */\nexport function detectReviewRequest(\n filePath: string,\n content: string,\n generation: string,\n): ReviewRequest | null {\n const parsed = parseInboxFilename(filePath);\n if (!parsed) return null;\n\n const fullText = `${parsed.subject} ${content}`;\n\n // Check for review keywords\n const isReview = REVIEW_KEYWORDS.some((re) => re.test(fullText));\n const isReReview = REREVIEW_KEYWORDS.some((re) => re.test(fullText));\n\n if (!isReview && !isReReview) return null;\n\n // Extract PR number\n const prNumber = extractPrNumber(fullText);\n if (!prNumber) return null;\n\n return {\n sourcePath: filePath,\n sender: parsed.sender,\n recipient: parsed.recipient,\n prNumber,\n generation,\n isReReview,\n round: isReReview ? 2 : 1, // Will be adjusted by session tracking\n };\n}\n\n// ── Review Prompt ──────────────────────────────────────────────────\n\nexport function buildReviewPrompt(\n request: ReviewRequest,\n agentName: string,\n round: number,\n): string {\n const roundLabel = round > 1 ? ` (re-review round ${round})` : \"\";\n\n return [\n `You are a code reviewer for the HUA Platform monorepo.`,\n ``,\n `## Task`,\n `Review PR #${request.prNumber}${roundLabel}.`,\n ``,\n `## Instructions`,\n `1. Run: gh pr diff ${request.prNumber}`,\n `2. Read changed files for understanding`,\n `3. Apply review checklist: security > data integrity > performance > error handling > code quality`,\n `4. Write structured findings`,\n ``,\n `## Output`,\n `Write review to: ${path.join(\"reviews\", request.generation, `review-PR${request.prNumber}-${agentName}.md`)}`,\n ``,\n `### Review File Format`,\n `\\`\\`\\`markdown`,\n `---`,\n `date: ${new Date().toISOString().split(\"T\")[0]}`,\n `reviewer: ${agentName}`,\n `pr: ${request.prNumber}`,\n `round: ${round}`,\n `status: clean | p1-Nitems | p2-Nitems`,\n `merge: merge | fix-then-merge | hold`,\n `---`,\n ``,\n `## Findings`,\n ``,\n `### Critical / High`,\n `- [severity] [category] file:line — description`,\n ``,\n `### Medium / Low`,\n `- [severity] [category] file:line — description`,\n ``,\n `## Checks`,\n `- [ ] Build verified`,\n `- [ ] Typecheck passed`,\n `- [ ] Scope check (only expected files changed)`,\n ``,\n `## Suggested Diff Lines`,\n `{number of lines the author should change to address findings}`,\n ``,\n `## Decision`,\n `{one-line merge recommendation}`,\n `\\`\\`\\``,\n ``,\n `## After Review`,\n `- Update reviews/INDEX.md`,\n `- Write inbox reply to ${request.sender}`,\n `- Commit and push comms changes`,\n ].join(\"\\n\");\n}\n\n// ── Review Output Parsing ──────────────────────────────────────────\n\nconst SEVERITY_PATTERNS: Record<FindingSeverity, RegExp> = {\n critical: /\\bcritical\\b/i,\n high: /\\bhigh\\b/i,\n medium: /\\bmedium\\b/i,\n low: /\\blow\\b/i,\n nitpick: /\\bnitpick\\b/i,\n};\n\nconst CATEGORY_PATTERNS = [\n \"security\",\n \"performance\",\n \"correctness\",\n \"data-integrity\",\n \"error-handling\",\n \"code-quality\",\n \"style\",\n];\n\n/**\n * Parse frontmatter from review file.\n */\nexport function parseFrontmatter(\n content: string,\n): Record<string, string> | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match?.[1]) return null;\n\n const fields: Record<string, string> = {};\n for (const line of match[1].split(\"\\n\")) {\n const kv = line.match(/^(\\w+):\\s*(.+)$/);\n if (kv?.[1] && kv[2]) {\n fields[kv[1]] = kv[2].trim();\n }\n }\n return fields;\n}\n\n/**\n * Extract suggested diff lines from review content.\n */\nexport function extractSuggestedDiffLines(content: string): number {\n const match = content.match(/## Suggested Diff Lines\\s*\\n\\s*(\\d+)/i);\n if (match?.[1]) return parseInt(match[1], 10);\n\n // Fallback: count lines in code blocks that look like suggestions\n const codeBlocks = content.match(/```[\\s\\S]*?```/g) ?? [];\n let totalLines = 0;\n for (const block of codeBlocks) {\n totalLines += block.split(\"\\n\").length - 2; // minus fences\n }\n return totalLines;\n}\n\n/**\n * Extract findings from review content.\n * Best-effort parsing — reviews may not follow exact format.\n */\nexport function extractFindings(content: string): ReviewFinding[] {\n const findings: ReviewFinding[] = [];\n\n // Match lines that look like finding entries\n const lines = content.split(\"\\n\");\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith(\"-\") && !trimmed.startsWith(\"*\")) continue;\n\n // Detect severity\n let severity: FindingSeverity = \"medium\";\n for (const [sev, pattern] of Object.entries(SEVERITY_PATTERNS)) {\n if (pattern.test(trimmed)) {\n severity = sev as FindingSeverity;\n break;\n }\n }\n\n // Detect category\n let category = \"general\";\n for (const cat of CATEGORY_PATTERNS) {\n if (trimmed.toLowerCase().includes(cat)) {\n category = cat;\n break;\n }\n }\n\n // Extract file:line if present\n const fileMatch = trimmed.match(/([a-zA-Z0-9_/.-]+\\.[a-zA-Z]+):(\\d+)/);\n\n // Only include if it looks like an actual finding (has severity keyword or file ref)\n const hasSeverityKeyword = Object.values(SEVERITY_PATTERNS).some((p) =>\n p.test(trimmed),\n );\n if (hasSeverityKeyword || fileMatch) {\n findings.push({\n severity,\n category,\n description: trimmed.replace(/^[-*]\\s*/, \"\").slice(0, 200),\n file: fileMatch?.[1],\n line: fileMatch?.[2] ? parseInt(fileMatch[2], 10) : undefined,\n });\n }\n }\n\n return findings;\n}\n\n/**\n * Parse a review output file into a ReviewRound.\n */\nexport function parseReviewOutput(\n reviewFilePath: string,\n round: number,\n): ReviewRound | null {\n if (!fs.existsSync(reviewFilePath)) return null;\n\n const content = fs.readFileSync(reviewFilePath, \"utf-8\");\n const findings = extractFindings(content);\n const suggestedDiffLines = extractSuggestedDiffLines(content);\n\n return {\n round,\n timestamp: new Date().toISOString(),\n findingCount: findings.length,\n findings,\n suggestedDiffLines,\n findingHash: computeFindingHash(findings),\n };\n}\n\n// ── Review File Path ───────────────────────────────────────────────\n\nexport function reviewFilePath(\n commsDir: string,\n generation: string,\n prNumber: number,\n agentName: string,\n): string {\n return path.join(\n commsDir,\n \"reviews\",\n generation,\n `review-PR${prNumber}-${agentName}.md`,\n );\n}\n\n// ── Stale Detection ────────────────────────────────────────────────\n\n/**\n * Check if a review request is stale (already handled).\n * Mirrors PS1 Test-IsStaleRequest logic.\n */\nexport function isStaleReviewRequest(\n request: ReviewRequest,\n commsDir: string,\n agentName: string,\n): boolean {\n // 1. Check if review file exists and is newer than request\n const revPath = reviewFilePath(\n commsDir,\n request.generation,\n request.prNumber,\n agentName,\n );\n if (fs.existsSync(revPath) && fs.existsSync(request.sourcePath)) {\n const reviewStat = fs.statSync(revPath);\n const requestStat = fs.statSync(request.sourcePath);\n if (reviewStat.mtimeMs > requestStat.mtimeMs) return true;\n }\n\n return false;\n}\n\n// ── Processed Marker ───────────────────────────────────────────────\n\nexport function computeRequestMarkerId(filePath: string): string {\n const stat = fs.statSync(filePath);\n const input = `${filePath}|${stat.mtimeMs}`;\n return crypto.createHash(\"sha1\").update(input).digest(\"hex\");\n}\n\nexport function isAlreadyProcessed(\n stateDir: string,\n filePath: string,\n): boolean {\n const markerId = computeRequestMarkerId(filePath);\n return fs.existsSync(path.join(stateDir, \"processed\", `${markerId}.done`));\n}\n\nexport function unmarkProcessed(\n stateDir: string,\n request: ReviewRequest,\n): void {\n const markerId = computeRequestMarkerId(request.sourcePath);\n const markerPath = path.join(stateDir, \"processed\", `${markerId}.done`);\n if (fs.existsSync(markerPath)) {\n fs.unlinkSync(markerPath);\n }\n}\n\nexport function markAsProcessed(\n stateDir: string,\n request: ReviewRequest,\n): void {\n const markerId = computeRequestMarkerId(request.sourcePath);\n const markerDir = path.join(stateDir, \"processed\");\n fs.mkdirSync(markerDir, { recursive: true });\n const markerPath = path.join(markerDir, `${markerId}.done`);\n const payload = {\n prNumber: request.prNumber,\n sourcePath: request.sourcePath,\n processedAt: new Date().toISOString(),\n };\n const tmp = `${markerPath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), \"utf-8\");\n fs.renameSync(tmp, markerPath);\n}\n\n// ── Bridge Receipt ─────────────────────────────────────────────────\n\n/**\n * Write immediate inbox acknowledgment before review starts.\n * Mirrors PS1 Write-BridgeReceipt pattern.\n */\nexport function writeReviewReceipt(\n commsDir: string,\n request: ReviewRequest,\n agentName: string,\n): string {\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n const filename = `${date}-${agentName}-${request.sender}-PR${request.prNumber}-ack.md`;\n const content = [\n `## ${agentName} > ${request.sender}`,\n ``,\n `- PR #${request.prNumber} review request received.`,\n `- headless reviewer processing.`,\n `- request: ${path.basename(request.sourcePath)}`,\n ].join(\"\\n\");\n\n const inboxDir = path.join(commsDir, \"inbox\");\n fs.mkdirSync(inboxDir, { recursive: true });\n const inboxPath = path.join(inboxDir, filename);\n const tmp = `${inboxPath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, content, \"utf-8\");\n fs.renameSync(tmp, inboxPath);\n return inboxPath;\n}\n\n// ── Orchestrator Entry Point ───────────────────────────────────\n\n/**\n * Check if the current bridge process is running in headless reviewer mode.\n * Reads from env vars set by engine/bridge.ts startBridge().\n */\nexport function isHeadlessReviewer(): boolean {\n return process.env.TAP_HEADLESS === \"true\";\n}\n\n/**\n * Get headless reviewer configuration from env vars.\n * Returns null if not in headless mode.\n */\nexport function getHeadlessEnvConfig(): {\n role: string;\n maxRounds: number;\n qualityFloor: string;\n} | null {\n if (!isHeadlessReviewer()) return null;\n return {\n role: process.env.TAP_AGENT_ROLE ?? \"reviewer\",\n maxRounds: parseInt(process.env.TAP_MAX_REVIEW_ROUNDS ?? \"5\", 10),\n qualityFloor: process.env.TAP_QUALITY_FLOOR ?? \"high\",\n };\n}\n\n/**\n * Scan inbox for pending review requests.\n * This is the entry point for the headless review loop.\n *\n * Phase 3 will wire this into the bridge runner's poll cycle:\n * 1. scanInboxForReviews() → detect pending requests\n * 2. For each: writeReviewReceipt() → dispatch to bridge → parseReviewOutput()\n * 3. evaluate() termination → continue or stop\n */\nexport function scanInboxForReviews(\n commsDir: string,\n stateDir: string,\n generation: string,\n agentName: string,\n agentId: string = agentName,\n): ReviewRequest[] {\n const inboxDir = path.join(commsDir, \"inbox\");\n if (!fs.existsSync(inboxDir)) return [];\n\n const files = fs.readdirSync(inboxDir).filter((f) => f.endsWith(\".md\"));\n const requests: ReviewRequest[] = [];\n\n for (const file of files) {\n const filePath = path.join(inboxDir, file);\n const content = fs.readFileSync(filePath, \"utf-8\");\n const request = detectReviewRequest(filePath, content, generation);\n\n if (!request) continue;\n\n // Only process requests addressed to this agent or broadcast (\"전체\"/\"all\")\n const to = request.recipient.toLowerCase();\n if (\n to !== agentName.toLowerCase() &&\n to !== \"전체\" &&\n to !== \"all\" &&\n to !== \"\"\n ) {\n continue;\n }\n\n if (isOwnMessageAddress(request.sender, agentId, agentName)) continue;\n\n if (isStaleReviewRequest(request, commsDir, agentName)) continue;\n if (isAlreadyProcessed(stateDir, filePath)) continue;\n\n requests.push(request);\n }\n\n return requests;\n}\n","/**\n * Headless review loop — poll-based review orchestrator for bridge processes.\n *\n * Runs alongside the bridge script. When TAP_HEADLESS=true:\n * 1. Periodically scans inbox for review requests\n * 2. Writes review dispatch files that the bridge picks up\n * 3. Monitors review output for completion\n * 4. Evaluates termination conditions\n * 5. Continues or stops the review session\n *\n * This is a control loop, not a WebSocket client — the bridge handles dispatch.\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport {\n scanInboxForReviews,\n isHeadlessReviewer,\n getHeadlessEnvConfig,\n buildReviewPrompt,\n writeReviewReceipt,\n parseReviewOutput,\n reviewFilePath,\n markAsProcessed,\n unmarkProcessed,\n type ReviewRequest,\n type ReviewSession,\n} from \"./review.js\";\nimport {\n evaluate,\n DEFAULT_TERMINATION_CONFIG,\n type TerminationContext,\n type TerminationConfig,\n type FindingSeverity,\n} from \"./termination.js\";\n\n// ── Types ──────────────────────────────────────────────────────────\n\nexport interface HeadlessLoopOptions {\n commsDir: string;\n stateDir: string;\n repoRoot: string;\n agentId?: string;\n agentName: string;\n generation: string;\n pollIntervalMs: number;\n}\n\nexport interface HeadlessLoopState {\n running: boolean;\n activeSession: ReviewSession | null;\n completedSessions: number;\n lastPollAt: string | null;\n}\n\n// ── Loop implementation ────────────────────────────────────────────\n\nexport function createHeadlessLoop(options: HeadlessLoopOptions): {\n start: () => void;\n stop: () => void;\n getState: () => HeadlessLoopState;\n} {\n const envConfig = getHeadlessEnvConfig();\n const terminationConfig: TerminationConfig = {\n ...DEFAULT_TERMINATION_CONFIG,\n maxRounds: envConfig?.maxRounds ?? DEFAULT_TERMINATION_CONFIG.maxRounds,\n qualitySeverityFloor:\n (envConfig?.qualityFloor as FindingSeverity) ??\n DEFAULT_TERMINATION_CONFIG.qualitySeverityFloor,\n };\n\n const state: HeadlessLoopState = {\n running: false,\n activeSession: null,\n completedSessions: 0,\n lastPollAt: null,\n };\n\n let timer: ReturnType<typeof setInterval> | null = null;\n\n function log(msg: string): void {\n const ts = new Date().toISOString();\n console.error(`[${ts}] [headless-loop] ${msg}`);\n }\n\n function writeStateFile(): void {\n try {\n const payload = {\n running: state.running,\n agentName: options.agentName,\n generation: options.generation,\n pollIntervalMs: options.pollIntervalMs,\n completedSessions: state.completedSessions,\n lastPollAt: state.lastPollAt,\n activeReview: state.activeSession\n ? {\n prNumber: state.activeSession.request.prNumber,\n round: state.activeSession.rounds.length + 1,\n startedAt: state.activeSession.startedAt,\n sender: state.activeSession.request.sender,\n }\n : null,\n terminationConfig: {\n maxRounds: terminationConfig.maxRounds,\n qualitySeverityFloor: terminationConfig.qualitySeverityFloor,\n },\n updatedAt: new Date().toISOString(),\n };\n const filePath = path.join(options.stateDir, \"headless-state.json\");\n const tmp = `${filePath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(payload, null, 2), \"utf-8\");\n fs.renameSync(tmp, filePath);\n } catch {\n // Non-critical — state dump is best-effort\n }\n }\n\n function pollOnce(): void {\n state.lastPollAt = new Date().toISOString();\n\n // Skip if already processing a review\n if (state.activeSession) {\n checkActiveSession();\n writeStateFile();\n return;\n }\n\n // Scan for new review requests\n const requests = scanInboxForReviews(\n options.commsDir,\n options.stateDir,\n options.generation,\n options.agentName,\n options.agentId,\n );\n\n if (requests.length === 0) {\n writeStateFile();\n return;\n }\n\n // Process first request (sequential — one at a time)\n const request = requests[0];\n startReviewSession(request);\n writeStateFile();\n }\n\n function startReviewSession(request: ReviewRequest): void {\n log(`Starting review for PR #${request.prNumber}`);\n\n // Mark as processed EAGERLY to prevent race with generic bridge.\n // If anything fails after this point, we roll back the marker.\n markAsProcessed(options.stateDir, request);\n\n try {\n // Write receipt\n writeReviewReceipt(options.commsDir, request, options.agentName);\n\n // Build review prompt\n const prompt = buildReviewPrompt(request, options.agentName, 1);\n\n // Write dispatch file to commsDir/inbox/ — the bridge watches this\n // directory and will inject it as a turn/start\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n const dispatchFilename = `${date}-headless-${options.agentName}-review-PR${request.prNumber}.md`;\n const inboxDir = path.join(options.commsDir, \"inbox\");\n fs.mkdirSync(inboxDir, { recursive: true });\n const dispatchFile = path.join(inboxDir, dispatchFilename);\n const tmp = `${dispatchFile}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, prompt, \"utf-8\");\n fs.renameSync(tmp, dispatchFile);\n\n state.activeSession = {\n request,\n agentName: options.agentName,\n role:\n (envConfig?.role as \"reviewer\" | \"validator\" | \"long-running\") ??\n \"reviewer\",\n rounds: [],\n startedAt: new Date().toISOString(),\n reviewFilePath: reviewFilePath(\n options.commsDir,\n request.generation,\n request.prNumber,\n options.agentName,\n ),\n };\n\n log(`Dispatched review prompt for PR #${request.prNumber} (round 1)`);\n } catch (err) {\n // Roll back processed marker so request can be retried on next poll\n log(\n `Failed to start review for PR #${request.prNumber}: ${err instanceof Error ? err.message : String(err)}`,\n );\n unmarkProcessed(options.stateDir, request);\n }\n }\n\n function checkActiveSession(): void {\n if (!state.activeSession) return;\n\n const session = state.activeSession;\n const revPath = session.reviewFilePath;\n\n // Check for new output FIRST — if output arrived, process it regardless\n // of elapsed time. Timeouts only apply when there's genuinely no output.\n // (덱 review: timeout before file check drops late-arriving valid output)\n let hasNewOutput = false;\n if (fs.existsSync(revPath)) {\n const stat = fs.statSync(revPath);\n const lastRound = session.rounds[session.rounds.length - 1];\n const lastCheck = lastRound?.timestamp ?? session.startedAt;\n hasNewOutput = stat.mtime.toISOString() > lastCheck;\n }\n\n if (hasNewOutput) {\n // New output arrived — parse and evaluate (skip timeout)\n const roundNum = session.rounds.length + 1;\n const round = parseReviewOutput(revPath, roundNum);\n if (!round) return;\n\n session.rounds.push(round);\n log(\n `PR #${session.request.prNumber} round ${roundNum}: ${round.findingCount} findings, ${round.suggestedDiffLines} suggested diff lines`,\n );\n\n // Evaluate termination\n const stopSignalPath = path.join(options.stateDir, \"stop-signal\");\n const ctx: TerminationContext = {\n round: roundNum,\n rounds: session.rounds,\n stopSignalPath,\n config: terminationConfig,\n };\n\n const result = evaluate(ctx);\n\n if (result.verdict === \"stop\") {\n log(\n `PR #${session.request.prNumber} terminated: ${result.reason} (${result.strategy})`,\n );\n completeSession(session);\n } else {\n log(\n `PR #${session.request.prNumber} continues to round ${roundNum + 1}`,\n );\n dispatchFollowUp(session, roundNum + 1);\n }\n return;\n }\n\n // No new output — apply timeout checks.\n\n // Session timeout: no output at all after SESSION_TIMEOUT_MS\n const SESSION_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes\n const elapsed = Date.now() - new Date(session.startedAt).getTime();\n if (elapsed > SESSION_TIMEOUT_MS && session.rounds.length === 0) {\n log(\n `PR #${session.request.prNumber} timed out — no output after ${Math.round(elapsed / 60000)}min. Releasing session.`,\n );\n state.activeSession = null;\n return;\n }\n\n // Round timeout: no new output between rounds for ROUND_TIMEOUT_MS\n const ROUND_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes between rounds\n if (session.rounds.length > 0) {\n const lastRoundTime = new Date(\n session.rounds[session.rounds.length - 1]!.timestamp,\n ).getTime();\n if (Date.now() - lastRoundTime > ROUND_TIMEOUT_MS) {\n log(\n `PR #${session.request.prNumber} round timeout — no new output after ${Math.round((Date.now() - lastRoundTime) / 60000)}min. Completing session.`,\n );\n completeSession(session);\n return;\n }\n }\n }\n\n function dispatchFollowUp(session: ReviewSession, round: number): void {\n const prompt = buildReviewPrompt(session.request, options.agentName, round);\n\n // Write follow-up dispatch to commsDir/inbox/ for bridge to steer\n const date = new Date().toISOString().split(\"T\")[0].replace(/-/g, \"\");\n const dispatchFilename = `${date}-headless-${options.agentName}-review-PR${session.request.prNumber}-r${round}.md`;\n const inboxDir = path.join(options.commsDir, \"inbox\");\n fs.mkdirSync(inboxDir, { recursive: true });\n const dispatchFile = path.join(inboxDir, dispatchFilename);\n const tmp = `${dispatchFile}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, prompt, \"utf-8\");\n fs.renameSync(tmp, dispatchFile);\n }\n\n function completeSession(session: ReviewSession): void {\n session.terminatedAt = new Date().toISOString();\n\n // Note: request was already marked as processed eagerly in startReviewSession()\n\n // Clean up dispatch files from inbox\n const inboxDir = path.join(options.commsDir, \"inbox\");\n if (fs.existsSync(inboxDir)) {\n const prefix = `headless-${options.agentName}-review-PR${session.request.prNumber}`;\n const files = fs.readdirSync(inboxDir).filter((f) => f.includes(prefix));\n for (const f of files) {\n fs.unlinkSync(path.join(inboxDir, f));\n }\n }\n\n state.activeSession = null;\n state.completedSessions++;\n log(\n `PR #${session.request.prNumber} review complete (${session.rounds.length} rounds)`,\n );\n }\n\n return {\n start() {\n if (!isHeadlessReviewer()) {\n log(\"Not in headless mode — loop not started\");\n return;\n }\n\n state.running = true;\n log(\n `Headless review loop started (${envConfig?.role ?? \"reviewer\"}, poll ${options.pollIntervalMs}ms, max ${terminationConfig.maxRounds} rounds)`,\n );\n\n // Write initial state\n writeStateFile();\n\n // Initial poll\n pollOnce();\n\n // Set up interval\n timer = setInterval(pollOnce, options.pollIntervalMs);\n },\n\n stop() {\n state.running = false;\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n writeStateFile();\n log(\"Headless review loop stopped\");\n },\n\n getState() {\n return { ...state };\n },\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { spawn } from \"node:child_process\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport {\n resolveConfig,\n SHARED_CONFIG_FILE,\n LOCAL_CONFIG_FILE,\n} from \"../config/index.js\";\nimport { resolveAppServerUrl } from \"../engine/bridge.js\";\nimport { resolveNodeRuntime, buildRuntimeEnv } from \"../runtime/index.js\";\n\n// ─── Repo root discovery (fallback for unbundled runs) ─────────\n\nfunction findRepoRootFromRunner(): string | null {\n let dir = path.resolve(path.dirname(fileURLToPath(import.meta.url)));\n\n while (true) {\n if (fs.existsSync(path.join(dir, SHARED_CONFIG_FILE))) return dir;\n if (fs.existsSync(path.join(dir, LOCAL_CONFIG_FILE))) return dir;\n if (fs.existsSync(path.join(dir, \"scripts\", \"codex-app-server-bridge.ts\")))\n return dir;\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n// ─── Headless review loop integration ──────────────────────────\n\nfunction maybeStartHeadlessLoop(\n repoRoot: string,\n commsDir: string,\n stateDir: string | undefined,\n): void {\n if (process.env.TAP_HEADLESS !== \"true\") return;\n\n // Dynamic import to avoid loading review/termination engines in non-headless mode\n import(\"../engine/headless-loop.js\")\n .then(({ createHeadlessLoop }) => {\n const agentName =\n process.env.TAP_AGENT_NAME ??\n process.env.CODEX_TAP_AGENT_NAME ??\n \"reviewer\";\n const agentId =\n process.env.TAP_AGENT_ID ??\n process.env.TAP_BRIDGE_INSTANCE_ID ??\n agentName;\n const generation = process.env.TAP_REVIEW_GENERATION ?? \"gen11\";\n const resolvedStateDir = stateDir ?? path.join(repoRoot, \".tap-comms\");\n\n const loop = createHeadlessLoop({\n commsDir,\n stateDir: resolvedStateDir,\n repoRoot,\n agentId,\n agentName,\n generation,\n pollIntervalMs: 3_000, // Poll faster than generic bridge (5s) for review priority\n });\n\n loop.start();\n\n // Clean shutdown\n process.on(\"SIGTERM\", () => loop.stop());\n process.on(\"SIGINT\", () => loop.stop());\n })\n .catch((err) => {\n console.error(\"[headless-loop] Failed to start:\", err);\n });\n}\n\n// ─── Main ──────────────────────────────────────────────────────\n\ninterface BridgeScriptArgsOptions {\n repoRoot: string;\n commsDir: string;\n appServerUrl: string;\n gatewayTokenFile?: string;\n stateDir?: string;\n agentName?: string;\n}\n\nexport function resolveBridgeDaemonScript(\n repoRoot: string,\n runnerUrl: string = import.meta.url,\n fileExists: (candidate: string) => boolean = fs.existsSync,\n): string | null {\n const moduleDir = path.dirname(fileURLToPath(runnerUrl));\n const candidates = [\n // 1. Bundled standalone/npm install\n path.join(moduleDir, \"codex-app-server-bridge.mjs\"),\n // 2. Source run from monorepo package\n path.join(moduleDir, \"codex-app-server-bridge.ts\"),\n // 3. Built monorepo package dist\n path.join(\n repoRoot,\n \"packages\",\n \"tap-comms\",\n \"dist\",\n \"bridges\",\n \"codex-app-server-bridge.mjs\",\n ),\n // 4. Monorepo source wrapper\n path.join(\n repoRoot,\n \"packages\",\n \"tap-comms\",\n \"src\",\n \"bridges\",\n \"codex-app-server-bridge.ts\",\n ),\n // 5. Legacy monorepo root script\n path.join(repoRoot, \"scripts\", \"codex-app-server-bridge.ts\"),\n ];\n\n for (const candidate of candidates) {\n if (fileExists(candidate)) {\n return candidate;\n }\n }\n\n return null;\n}\n\nexport function buildBridgeScriptArgs(\n scriptPath: string,\n options: BridgeScriptArgsOptions,\n): string[] {\n const args = [\n scriptPath,\n `--repo-root=${options.repoRoot}`,\n `--comms-dir=${options.commsDir}`,\n `--app-server-url=${options.appServerUrl}`,\n ];\n\n if (options.agentName) {\n args.push(`--agent-name=${options.agentName}`);\n }\n\n if (options.gatewayTokenFile) {\n args.push(`--gateway-token-file=${options.gatewayTokenFile}`);\n }\n\n if (options.stateDir) {\n args.push(`--state-dir=${options.stateDir}`);\n }\n\n return args;\n}\n\nexport function buildBridgeDaemonEnv(\n parentEnv: NodeJS.ProcessEnv,\n runtimeEnv: NodeJS.ProcessEnv,\n): NodeJS.ProcessEnv {\n return {\n ...parentEnv,\n ...runtimeEnv,\n };\n}\n\nasync function main(): Promise<void> {\n const repoRootHint = findRepoRootFromRunner() ?? undefined;\n const { config } = resolveConfig({}, repoRootHint);\n\n const repoRoot = config.repoRoot;\n const commsDir = config.commsDir;\n const instancePortRaw = process.env.TAP_BRIDGE_PORT;\n const instancePort = instancePortRaw\n ? Number.parseInt(instancePortRaw, 10)\n : undefined;\n const envAppServerUrl = process.env.CODEX_APP_SERVER_URL?.trim();\n const gatewayTokenFile = process.env.TAP_GATEWAY_TOKEN_FILE?.trim();\n const appServerUrl =\n envAppServerUrl ||\n resolveAppServerUrl(\n config.appServerUrl,\n Number.isFinite(instancePort) ? instancePort : undefined,\n );\n\n // Multi-instance: derive instance-specific runtime state dir.\n // TAP_STATE_DIR points to shared state.json for MCP bootstrap/rebind.\n // TAP_RUNTIME_STATE_DIR is the bridge-only heartbeat/thread directory.\n const instanceId = process.env.TAP_BRIDGE_INSTANCE_ID;\n const envStateDir = process.env.TAP_RUNTIME_STATE_DIR;\n let stateDir: string | undefined;\n if (envStateDir) {\n stateDir = envStateDir;\n } else if (instanceId) {\n const resolved = path.resolve(\n path.join(repoRoot, \".tmp\", `codex-app-server-bridge-${instanceId}`),\n );\n const expectedBase = path.resolve(repoRoot, \".tmp\") + path.sep;\n if (!resolved.startsWith(expectedBase)) {\n throw new Error(\n `Path traversal blocked: runtime state dir escapes .tmp/ directory`,\n );\n }\n stateDir = resolved;\n }\n\n // Honor pre-resolved node from parent (2-stage spawn: engine → runner → daemon)\n // TAP_STRIP_TYPES preserves metadata so bun doesn't get --experimental-strip-types.\n const preResolved = process.env.TAP_RESOLVED_NODE;\n const resolved = preResolved\n ? {\n command: preResolved,\n supportsStripTypes: process.env.TAP_STRIP_TYPES === \"1\",\n source: \"env\" as const,\n majorVersion: null,\n }\n : resolveNodeRuntime(config.runtimeCommand, repoRoot);\n\n const command = resolved.command;\n const agentName =\n process.env.TAP_AGENT_NAME?.trim() ||\n process.env.CODEX_TAP_AGENT_NAME?.trim() ||\n undefined;\n\n // Locate bridge script\n const scriptPath = resolveBridgeDaemonScript(repoRoot);\n if (!scriptPath) {\n throw new Error(\n `Bridge script not found for repo root ${repoRoot}.\\n` +\n `Expected a packaged dist/bridges/codex-app-server-bridge.mjs or monorepo bridge script.`,\n );\n }\n\n // Build args\n const args: string[] = [];\n if (resolved.supportsStripTypes) {\n args.push(\"--experimental-strip-types\");\n }\n args.push(\n ...buildBridgeScriptArgs(scriptPath, {\n repoRoot,\n commsDir,\n appServerUrl,\n gatewayTokenFile,\n stateDir,\n agentName,\n }),\n );\n\n // Forward bridge operational flags from env (set by engine/bridge.ts)\n const busyMode = process.env.TAP_BUSY_MODE;\n if (busyMode) args.push(`--busy-mode=${busyMode}`);\n\n const pollSeconds = process.env.TAP_POLL_SECONDS;\n if (pollSeconds) args.push(`--poll-seconds=${pollSeconds}`);\n\n const reconnectSeconds = process.env.TAP_RECONNECT_SECONDS;\n if (reconnectSeconds) args.push(`--reconnect-seconds=${reconnectSeconds}`);\n\n const lookbackMinutes = process.env.TAP_MESSAGE_LOOKBACK_MINUTES;\n if (lookbackMinutes)\n args.push(`--message-lookback-minutes=${lookbackMinutes}`);\n\n const threadId = process.env.TAP_THREAD_ID;\n if (threadId) args.push(`--thread-id=${threadId}`);\n\n if (process.env.TAP_EPHEMERAL === \"true\") args.push(\"--ephemeral\");\n if (process.env.TAP_PROCESS_EXISTING === \"true\")\n args.push(\"--process-existing-messages\");\n\n // Spawn with fnm-aware PATH so any further child spawns also find the right Node\n const runtimeEnv = buildRuntimeEnv(repoRoot);\n const daemonEnv = buildBridgeDaemonEnv(process.env, runtimeEnv);\n\n const child = spawn(command, args, {\n cwd: repoRoot,\n env: daemonEnv,\n stdio: \"inherit\",\n });\n\n // Start headless review loop if in headless mode\n maybeStartHeadlessLoop(repoRoot, commsDir, stateDir);\n\n child.on(\"exit\", (code: number | null, signal: NodeJS.Signals | null) => {\n if (signal) {\n process.kill(process.pid, signal);\n return;\n }\n process.exit(code ?? 0);\n });\n\n child.on(\"error\", (error: Error) => {\n console.error(String(error));\n process.exit(1);\n });\n}\n\nfunction isDirectExecution(): boolean {\n const entry = process.argv[1];\n if (!entry) return false;\n return import.meta.url === pathToFileURL(path.resolve(entry)).href;\n}\n\nif (isDirectExecution()) {\n main().catch((error) => {\n console.error(error instanceof Error ? error.message : String(error));\n process.exit(1);\n });\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n TapSharedConfig,\n TapLocalConfig,\n TapResolvedConfig,\n ConfigSource,\n ConfigResolution,\n TrackedConfigSource,\n TrackedValue,\n TapTrackedConfig,\n} from \"./types.js\";\nimport { computeConfigHash } from \"./config-hash.js\";\n\n// ─── File names ────────────────────────────────────────────────\n\nexport const SHARED_CONFIG_FILE = \"tap-config.json\";\nexport const LOCAL_CONFIG_FILE = \"tap-config.local.json\";\nexport const LEGACY_CONFIG_FILE = \".tap-config\";\n\n// ─── Defaults ──────────────────────────────────────────────────\n\nconst DEFAULT_RUNTIME_COMMAND = \"node\";\nconst DEFAULT_APP_SERVER_URL = \"ws://127.0.0.1:4501\";\n\n// ─── Repo root discovery ───────────────────────────────────────\n\nimport { _noGitWarned, _setNoGitWarned, log } from \"../utils.js\";\n\nexport function findRepoRoot(startDir: string = process.cwd()): string {\n let dir = path.resolve(startDir);\n while (true) {\n if (fs.existsSync(path.join(dir, \".git\"))) return dir;\n if (fs.existsSync(path.join(dir, \"package.json\"))) {\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No .git directory found. Resolved tap root via package.json. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No git repository or package.json found. Using the current directory as tap root. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return process.cwd();\n}\n\n// ─── JSON file loading ─────────────────────────────────────────\n\nfunction loadJsonFile<T>(filePath: string): T | null {\n if (!fs.existsSync(filePath)) return null;\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n return JSON.parse(raw) as T;\n } catch {\n return null;\n }\n}\n\nexport function loadSharedConfig(repoRoot: string): TapSharedConfig | null {\n return loadJsonFile<TapSharedConfig>(path.join(repoRoot, SHARED_CONFIG_FILE));\n}\n\nexport function loadLocalConfig(repoRoot: string): TapLocalConfig | null {\n return loadJsonFile<TapLocalConfig>(path.join(repoRoot, LOCAL_CONFIG_FILE));\n}\n\nfunction readLegacyShellValue(configText: string, key: string): string | null {\n const match = configText.match(new RegExp(`^${key}=\"?(.+?)\"?$`, \"m\"));\n return match?.[1]?.trim() || null;\n}\n\nfunction loadLegacyShellConfig(repoRoot: string): TapSharedConfig | null {\n const filePath = path.join(repoRoot, LEGACY_CONFIG_FILE);\n if (!fs.existsSync(filePath)) return null;\n\n try {\n const raw = fs.readFileSync(filePath, \"utf-8\");\n const commsDir = readLegacyShellValue(raw, \"TAP_COMMS_DIR\");\n if (!commsDir) return null;\n return { commsDir };\n } catch {\n return null;\n }\n}\n\n// ─── CLI overrides ─────────────────────────────────────────────\n\nexport interface ConfigOverrides {\n commsDir?: string;\n stateDir?: string;\n runtimeCommand?: string;\n appServerUrl?: string;\n}\n\n// ─── Resolution ────────────────────────────────────────────────\n\n/**\n * Resolve config with priority: CLI flag > env > local config > shared config > auto.\n */\nexport function resolveConfig(\n overrides: ConfigOverrides = {},\n startDir?: string,\n): ConfigResolution {\n const repoRoot = findRepoRoot(startDir);\n const shared = loadSharedConfig(repoRoot) ?? {};\n const local = loadLocalConfig(repoRoot) ?? {};\n const legacy = loadLegacyShellConfig(repoRoot) ?? {};\n\n const sources: Record<keyof TapResolvedConfig, ConfigSource> = {\n repoRoot: \"auto\",\n commsDir: \"auto\",\n stateDir: \"auto\",\n runtimeCommand: \"auto\",\n appServerUrl: \"auto\",\n towerName: \"auto\",\n };\n\n // ─── commsDir ──────────────────────────────────────────────\n let commsDir: string;\n if (overrides.commsDir) {\n commsDir = resolvePath(repoRoot, overrides.commsDir);\n sources.commsDir = \"cli-flag\";\n } else if (process.env.TAP_COMMS_DIR) {\n commsDir = resolvePath(repoRoot, process.env.TAP_COMMS_DIR);\n sources.commsDir = \"env\";\n } else if (local.commsDir) {\n commsDir = resolvePath(repoRoot, local.commsDir);\n sources.commsDir = \"local-config\";\n } else if (shared.commsDir) {\n commsDir = resolvePath(repoRoot, shared.commsDir);\n sources.commsDir = \"shared-config\";\n } else if (legacy.commsDir) {\n commsDir = resolvePath(repoRoot, legacy.commsDir);\n sources.commsDir = \"legacy-shell-config\";\n } else {\n commsDir = path.join(repoRoot, \"tap-comms\");\n }\n\n // ─── stateDir ──────────────────────────────────────────────\n let stateDir: string;\n if (overrides.stateDir) {\n stateDir = resolvePath(repoRoot, overrides.stateDir);\n sources.stateDir = \"cli-flag\";\n } else if (process.env.TAP_STATE_DIR) {\n stateDir = resolvePath(repoRoot, process.env.TAP_STATE_DIR);\n sources.stateDir = \"env\";\n } else if (local.stateDir) {\n stateDir = resolvePath(repoRoot, local.stateDir);\n sources.stateDir = \"local-config\";\n } else if (shared.stateDir) {\n stateDir = resolvePath(repoRoot, shared.stateDir);\n sources.stateDir = \"shared-config\";\n } else {\n stateDir = path.join(repoRoot, \".tap-comms\");\n }\n\n // ─── runtimeCommand ────────────────────────────────────────\n let runtimeCommand: string;\n if (overrides.runtimeCommand) {\n runtimeCommand = overrides.runtimeCommand;\n sources.runtimeCommand = \"cli-flag\";\n } else if (process.env.TAP_RUNTIME_COMMAND) {\n runtimeCommand = process.env.TAP_RUNTIME_COMMAND;\n sources.runtimeCommand = \"env\";\n } else if (local.runtimeCommand) {\n runtimeCommand = local.runtimeCommand;\n sources.runtimeCommand = \"local-config\";\n } else if (shared.runtimeCommand) {\n runtimeCommand = shared.runtimeCommand;\n sources.runtimeCommand = \"shared-config\";\n } else {\n runtimeCommand = DEFAULT_RUNTIME_COMMAND;\n }\n\n // ─── appServerUrl ──────────────────────────────────────────\n let appServerUrl: string;\n if (overrides.appServerUrl) {\n appServerUrl = overrides.appServerUrl;\n sources.appServerUrl = \"cli-flag\";\n } else if (process.env.TAP_APP_SERVER_URL) {\n appServerUrl = process.env.TAP_APP_SERVER_URL;\n sources.appServerUrl = \"env\";\n } else if (local.appServerUrl) {\n appServerUrl = local.appServerUrl;\n sources.appServerUrl = \"local-config\";\n } else if (shared.appServerUrl) {\n appServerUrl = shared.appServerUrl;\n sources.appServerUrl = \"shared-config\";\n } else {\n appServerUrl = DEFAULT_APP_SERVER_URL;\n }\n\n // ─── towerName ──────────────────────────────────────────────\n const towerName = local.towerName ?? shared.towerName ?? null;\n\n return {\n config: {\n repoRoot,\n commsDir,\n stateDir,\n runtimeCommand,\n appServerUrl,\n towerName,\n },\n sources,\n };\n}\n\n// ─── Save helpers ──────────────────────────────────────────────\n\nexport function saveSharedConfig(\n repoRoot: string,\n config: TapSharedConfig,\n): void {\n const filePath = path.join(repoRoot, SHARED_CONFIG_FILE);\n const tmp = `${filePath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, filePath);\n}\n\nexport function saveLocalConfig(\n repoRoot: string,\n config: TapLocalConfig,\n): void {\n const filePath = path.join(repoRoot, LOCAL_CONFIG_FILE);\n const tmp = `${filePath}.tmp.${process.pid}`;\n fs.writeFileSync(tmp, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n fs.renameSync(tmp, filePath);\n}\n\n// ─── Helpers ───────────────────────────────────────────────────\n\n/** Resolve a path relative to repoRoot, or keep absolute as-is. */\nfunction resolvePath(repoRoot: string, p: string): string {\n const normalized = normalizeTapPath(p);\n return path.isAbsolute(normalized)\n ? normalized\n : path.resolve(repoRoot, normalized);\n}\n\n// ─── Instance / Session Config Loading ────────────────────────\n\n/** Subset of config fields that can be overridden per-instance or per-session. */\ninterface ResolveOverrides {\n agentName?: string | null;\n port?: number | null;\n bridgeMode?: string | null;\n commsDir?: string;\n stateDir?: string;\n runtimeCommand?: string;\n appServerUrl?: string;\n towerName?: string;\n}\n\ntype SessionOverrides = ResolveOverrides;\n\n/**\n * Verify that a resolved path stays within the expected subdirectory.\n * Prevents crafted IDs from crossing source boundaries\n * (e.g., instanceId=\"../sessions/gen22\" reading a session file as instance).\n */\nfunction assertConfigPathContained(\n resolved: string,\n baseDir: string,\n subDir: string,\n): string {\n const expectedDir = path.resolve(baseDir, subDir) + path.sep;\n const normalizedResolved = path.resolve(resolved);\n if (!normalizedResolved.startsWith(expectedDir)) {\n throw new Error(\n `Config path traversal blocked: resolved path escapes \"${subDir}/\" directory`,\n );\n }\n return normalizedResolved;\n}\n\nexport function loadInstanceConfig(\n stateDir: string,\n instanceId: string,\n): ResolveOverrides | null {\n const filePath = path.join(stateDir, \"instances\", `${instanceId}.json`);\n assertConfigPathContained(filePath, stateDir, \"instances\");\n return loadJsonFile<ResolveOverrides>(filePath);\n}\n\nexport function loadSessionConfig(\n stateDir: string,\n sessionId: string,\n): SessionOverrides | null {\n const filePath = path.join(stateDir, \"sessions\", `${sessionId}.json`);\n assertConfigPathContained(filePath, stateDir, \"sessions\");\n return loadJsonFile<SessionOverrides>(filePath);\n}\n\n// ─── Tracked Config Resolution ────────────────────────────────\n\nfunction tracked<T>(\n value: T,\n source: TrackedConfigSource,\n sourceFile: string | null = null,\n): TrackedValue<T> {\n return { value, source, sourceFile };\n}\n\nexport interface TrackedResolveOpts extends ConfigOverrides {\n instanceId?: string;\n sessionId?: string;\n}\n\n/**\n * Resolve config with full source tracking.\n * 7-level priority: cli > env > instance > session > local > project > default\n */\nexport function resolveTrackedConfig(\n opts: TrackedResolveOpts = {},\n startDir?: string,\n): { tracked: TapTrackedConfig; hash: string } {\n const repoRoot = findRepoRoot(startDir);\n const shared = loadSharedConfig(repoRoot) ?? {};\n const local = loadLocalConfig(repoRoot) ?? {};\n const legacy = loadLegacyShellConfig(repoRoot) ?? {};\n\n // Resolve stateDir first (needed for instance/session paths)\n const rawStateDir =\n opts.stateDir ??\n process.env.TAP_STATE_DIR ??\n local.stateDir ??\n shared.stateDir ??\n null;\n const stateDir = rawStateDir\n ? resolvePath(repoRoot, rawStateDir)\n : path.join(repoRoot, \".tap-comms\");\n\n // Load instance/session configs (graceful fallback)\n const inst = opts.instanceId\n ? loadInstanceConfig(stateDir, opts.instanceId)\n : null;\n const instFile = opts.instanceId\n ? path.join(stateDir, \"instances\", `${opts.instanceId}.json`)\n : null;\n const sess = opts.sessionId\n ? loadSessionConfig(stateDir, opts.sessionId)\n : null;\n const sessFile = opts.sessionId\n ? path.join(stateDir, \"sessions\", `${opts.sessionId}.json`)\n : null;\n\n // Chain resolution helper — finds first defined value from highest priority\n function resolveField<T>(\n cliVal: T | undefined,\n envVal: T | undefined,\n instVal: T | undefined,\n sessVal: T | undefined,\n localVal: T | undefined,\n projectVal: T | undefined,\n defaultVal: T,\n ): TrackedValue<T> {\n if (cliVal !== undefined) return tracked(cliVal, \"cli\");\n if (envVal !== undefined) return tracked(envVal, \"env\");\n if (instVal !== undefined) return tracked(instVal, \"instance\", instFile);\n if (sessVal !== undefined) return tracked(sessVal, \"session\", sessFile);\n if (localVal !== undefined)\n return tracked(localVal, \"local\", path.join(repoRoot, LOCAL_CONFIG_FILE));\n if (projectVal !== undefined)\n return tracked(\n projectVal,\n \"project\",\n path.join(repoRoot, SHARED_CONFIG_FILE),\n );\n return tracked(defaultVal, \"default\");\n }\n\n const commsDirTracked = resolveField(\n opts.commsDir ? resolvePath(repoRoot, opts.commsDir) : undefined,\n process.env.TAP_COMMS_DIR\n ? resolvePath(repoRoot, process.env.TAP_COMMS_DIR)\n : undefined,\n inst?.commsDir ? resolvePath(repoRoot, inst.commsDir) : undefined,\n sess?.commsDir ? resolvePath(repoRoot, sess.commsDir) : undefined,\n local.commsDir ? resolvePath(repoRoot, local.commsDir) : undefined,\n (shared.commsDir ?? legacy.commsDir)\n ? resolvePath(repoRoot, (shared.commsDir ?? legacy.commsDir)!)\n : undefined,\n path.join(repoRoot, \"tap-comms\"),\n );\n\n const stateDirTracked = resolveField(\n opts.stateDir ? resolvePath(repoRoot, opts.stateDir) : undefined,\n process.env.TAP_STATE_DIR\n ? resolvePath(repoRoot, process.env.TAP_STATE_DIR)\n : undefined,\n inst?.stateDir ? resolvePath(repoRoot, inst.stateDir) : undefined,\n sess?.stateDir ? resolvePath(repoRoot, sess.stateDir) : undefined,\n local.stateDir ? resolvePath(repoRoot, local.stateDir) : undefined,\n shared.stateDir ? resolvePath(repoRoot, shared.stateDir) : undefined,\n stateDir,\n );\n\n const runtimeCommandTracked = resolveField(\n opts.runtimeCommand,\n process.env.TAP_RUNTIME_COMMAND,\n inst?.runtimeCommand,\n sess?.runtimeCommand,\n local.runtimeCommand,\n shared.runtimeCommand,\n DEFAULT_RUNTIME_COMMAND,\n );\n\n const appServerUrlTracked = resolveField(\n opts.appServerUrl,\n process.env.TAP_APP_SERVER_URL,\n inst?.appServerUrl,\n sess?.appServerUrl,\n local.appServerUrl,\n shared.appServerUrl,\n DEFAULT_APP_SERVER_URL,\n );\n\n const towerNameTracked = resolveField<string | null>(\n undefined, // no CLI flag for towerName\n undefined, // no env for towerName\n inst?.towerName ?? undefined,\n sess?.towerName ?? undefined,\n local.towerName ?? undefined,\n shared.towerName ?? undefined,\n null,\n );\n\n const agentNameTracked = resolveField<string | null>(\n undefined,\n undefined,\n inst?.agentName ?? undefined,\n sess?.agentName ?? undefined,\n undefined,\n undefined,\n null,\n );\n\n const portTracked = resolveField<number | null>(\n undefined,\n undefined,\n inst?.port ?? undefined,\n sess?.port ?? undefined,\n undefined,\n undefined,\n null,\n );\n\n const bridgeModeTracked = resolveField<string | null>(\n undefined,\n undefined,\n inst?.bridgeMode ?? undefined,\n sess?.bridgeMode ?? undefined,\n undefined,\n undefined,\n null,\n );\n\n const trackedConfig: TapTrackedConfig = {\n repoRoot: tracked(repoRoot, \"default\"),\n commsDir: commsDirTracked,\n stateDir: stateDirTracked,\n runtimeCommand: runtimeCommandTracked,\n appServerUrl: appServerUrlTracked,\n towerName: towerNameTracked,\n agentName: agentNameTracked,\n port: portTracked,\n bridgeMode: bridgeModeTracked,\n };\n\n return { tracked: trackedConfig, hash: computeConfigHash(trackedConfig) };\n}\n\nexport function normalizeTapPath(input: string): string {\n const trimmed = input.trim().replace(/^[\"'`]+|[\"'`]+$/g, \"\");\n if (/^[A-Za-z]:[\\\\/]/.test(trimmed)) {\n return trimmed;\n }\n\n // MSYS/Git Bash `/c/...` → `C:\\...` conversion — Windows only.\n // On POSIX, `/d/...` is a legitimate absolute path and must not be rewritten.\n if (process.platform === \"win32\") {\n const match = trimmed.match(/^\\/([A-Za-z])\\/(.*)$/);\n if (match) {\n return `${match[1].toUpperCase()}:\\\\${match[2].replace(/\\//g, \"\\\\\")}`;\n }\n }\n\n return trimmed;\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n AdapterContext,\n CommandCode,\n InstanceId,\n Platform,\n RuntimeName,\n TapState,\n} from \"./types.js\";\nimport { resolveConfig, normalizeTapPath } from \"./config/index.js\";\n\nconst VALID_RUNTIMES: RuntimeName[] = [\"claude\", \"codex\", \"gemini\"];\n\nexport function isValidRuntime(name: string): name is RuntimeName {\n return VALID_RUNTIMES.includes(name as RuntimeName);\n}\n\nexport function detectPlatform(): Platform {\n return process.platform as Platform;\n}\n\n/** Shared flag: suppress duplicate no-git warnings across modules. */\nexport let _noGitWarned = false;\nconst _loggedWarnings = new Set<string>();\n\nexport function _setNoGitWarned() {\n _noGitWarned = true;\n}\n\nexport function resetLoggedWarnings() {\n _loggedWarnings.clear();\n}\n\nexport function wasWarningLogged(message: string): boolean {\n return _loggedWarnings.has(message);\n}\n\nexport function findRepoRoot(startDir: string = process.cwd()): string {\n let dir = path.resolve(startDir);\n while (true) {\n if (fs.existsSync(path.join(dir, \".git\"))) return dir;\n if (fs.existsSync(path.join(dir, \"package.json\"))) {\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No .git directory found. Resolved tap root via package.json. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return dir;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n if (!_noGitWarned) {\n _setNoGitWarned();\n log(\n \"No git repository or package.json found. Using the current directory as tap root. \" +\n \"That's fine outside git; use --comms-dir to choose a different comms location.\",\n );\n }\n return process.cwd();\n}\n\nexport function resolveCommsDir(args: string[], repoRoot: string): string {\n // Check --comms-dir flag\n const idx = args.indexOf(\"--comms-dir\");\n if (idx !== -1 && args[idx + 1]) {\n return path.resolve(normalizeTapPath(args[idx + 1]));\n }\n\n // Delegate to config resolution (env > local > shared > auto)\n const { config } = resolveConfig({}, repoRoot);\n return config.commsDir;\n}\n\nexport function createAdapterContext(\n commsDir: string,\n repoRoot: string,\n): AdapterContext {\n // Use config-resolved stateDir if available\n const { config } = resolveConfig({}, repoRoot);\n return {\n commsDir: path.resolve(normalizeTapPath(commsDir)),\n repoRoot: path.resolve(normalizeTapPath(repoRoot)),\n stateDir: config.stateDir,\n platform: detectPlatform(),\n };\n}\n\nexport function parseArgs(args: string[]): {\n positional: string[];\n flags: Record<string, string | boolean>;\n} {\n const positional: string[] = [];\n const flags: Record<string, string | boolean> = {};\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg.startsWith(\"--\")) {\n const key = arg.slice(2);\n const next = args[i + 1];\n if (next && !next.startsWith(\"--\")) {\n flags[key] = next;\n i++;\n } else {\n flags[key] = true;\n }\n } else if (arg.startsWith(\"-\")) {\n flags[arg.slice(1)] = true;\n } else {\n positional.push(arg);\n }\n }\n\n return { positional, flags };\n}\n\n// ─── JSON mode suppression ──────────────────────────────────────\n\nlet _jsonMode = false;\n\nexport function setJsonMode(enabled: boolean): void {\n _jsonMode = enabled;\n}\n\nexport function log(message: string): void {\n if (!_jsonMode) console.log(` ${message}`);\n}\n\nexport function logSuccess(message: string): void {\n if (!_jsonMode) console.log(` + ${message}`);\n}\n\nexport function logWarn(message: string): void {\n if (_jsonMode) return;\n _loggedWarnings.add(message);\n console.log(` ! ${message}`);\n}\n\nexport function logError(message: string): void {\n if (!_jsonMode) console.error(` x ${message}`);\n}\n\nexport function logHeader(message: string): void {\n if (!_jsonMode) console.log(`\\n ${message}\\n`);\n}\n\n// ─── CLI argument validation ──────────────────────────────────\n\n/**\n * Parse and validate an integer CLI flag within a range.\n * Returns undefined if the flag is not provided, or the validated number.\n * Throws a descriptive error if invalid.\n */\nexport function parseIntFlag(\n value: string | undefined,\n name: string,\n min: number,\n max: number,\n): number | undefined {\n if (value === undefined) return undefined;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < min || parsed > max) {\n throw new RangeError(\n `Invalid ${name}: ${value}. Must be an integer between ${min} and ${max}.`,\n );\n }\n return parsed;\n}\n\n/**\n * Parse and validate a port number (1-65535).\n */\nexport function parsePortFlag(value: string | undefined): number | null {\n if (value === undefined) return null;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {\n throw new RangeError(\n `Invalid port: ${value}. Must be between 1 and 65535.`,\n );\n }\n return parsed;\n}\n\n// ─── Instance ID utilities ─────────────────────────────────────\n\nexport type ResolveResult =\n | { ok: true; instanceId: InstanceId }\n | { ok: false; code: CommandCode; message: string };\n\n/**\n * Resolve a user-provided identifier to an instance ID.\n * Accepts either an exact instance ID or a runtime name (if unambiguous).\n */\nexport function resolveInstanceId(\n identifier: string,\n state: TapState,\n): ResolveResult {\n // Exact match\n if (state.instances[identifier]) {\n return { ok: true, instanceId: identifier };\n }\n\n // Runtime name → find matching instances\n if (isValidRuntime(identifier)) {\n const matches = Object.values(state.instances).filter(\n (inst) => inst.runtime === identifier,\n );\n\n if (matches.length === 1) {\n return { ok: true, instanceId: matches[0].instanceId };\n }\n\n if (matches.length > 1) {\n const ids = matches.map((m) => m.instanceId).join(\", \");\n return {\n ok: false,\n code: \"TAP_INSTANCE_AMBIGUOUS\",\n message: `Multiple ${identifier} instances found: ${ids}. Specify one explicitly.`,\n };\n }\n }\n\n return {\n ok: false,\n code: \"TAP_INSTANCE_NOT_FOUND\",\n message: `Instance not found: ${identifier}`,\n };\n}\n\n/**\n * Reject instance names containing path-traversal sequences or separators.\n * Prevents directory escape when the ID is interpolated into file paths.\n */\nexport function validateInstanceName(name: string): void {\n if (/[/\\\\]/.test(name) || name.includes(\"..\")) {\n throw new Error(\n `Invalid instance name \"${name}\": must not contain path separators or \"..\" sequences`,\n );\n }\n}\n\n/** Build an instance ID from runtime + optional name. */\nexport function buildInstanceId(\n runtime: RuntimeName,\n name?: string,\n): InstanceId {\n if (name) {\n validateInstanceName(name);\n }\n return name ? `${runtime}-${name}` : runtime;\n}\n\n/** Extract the runtime name from an instance ID. */\nexport function extractRuntimeFromInstanceId(id: InstanceId): RuntimeName {\n for (const r of VALID_RUNTIMES) {\n if (id === r || id.startsWith(`${r}-`)) return r;\n }\n throw new Error(`Cannot extract runtime from instance ID: ${id}`);\n}\n\n/** Check if a port is already claimed by another instance. */\nexport function findPortConflict(\n state: TapState,\n port: number,\n excludeInstanceId?: InstanceId,\n): InstanceId | null {\n for (const [id, inst] of Object.entries(state.instances)) {\n if (id !== excludeInstanceId && inst.port === port) return id;\n }\n return null;\n}\n\n/** Find the next available port starting from basePort (default 4501). */\nexport function findNextAvailablePort(\n state: TapState,\n basePort: number = 4501,\n excludeInstanceId?: InstanceId,\n): number {\n let port = basePort;\n while (findPortConflict(state, port, excludeInstanceId) !== null) {\n port++;\n }\n return port;\n}\n","/**\n * Common Node.js runtime resolver for all tap-comms child processes.\n *\n * Resolution chain:\n * .node-version + fnm probe → configured command → tsx fallback\n *\n * Extracted from codex-bridge-runner.ts (M69) to share across:\n * - bridge engine spawn\n * - bridge runner spawn\n * - future CLI commands\n */\n\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { execSync } from \"node:child_process\";\n\n// ─── Types ─────────────────────────────────────────────────────\n\nexport type RuntimeSource = \"fnm\" | \"config\" | \"path\" | \"tsx-fallback\" | \"bun\";\n\nexport interface ResolvedRuntime {\n /** Absolute path or command name for the resolved runtime. */\n command: string;\n /** Whether --experimental-strip-types is supported and should be used. */\n supportsStripTypes: boolean;\n /** Where the runtime was resolved from (for diagnostics). */\n source: RuntimeSource;\n /** Detected major version, if available. */\n majorVersion: number | null;\n}\n\n// ─── .node-version ─────────────────────────────────────────────\n\nexport function readNodeVersion(repoRoot: string): string | null {\n const nvFile = path.join(repoRoot, \".node-version\");\n if (!fs.existsSync(nvFile)) return null;\n try {\n const raw = fs.readFileSync(nvFile, \"utf-8\").trim();\n return raw.length > 0 ? raw.replace(/^v/, \"\") : null;\n } catch {\n return null;\n }\n}\n\n// ─── fnm probe ─────────────────────────────────────────────────\n\nfunction fnmCandidateDirs(): string[] {\n if (process.platform === \"win32\") {\n return [\n process.env.FNM_DIR,\n process.env.APPDATA ? path.join(process.env.APPDATA, \"fnm\") : null,\n process.env.LOCALAPPDATA\n ? path.join(process.env.LOCALAPPDATA, \"fnm\")\n : null,\n process.env.USERPROFILE\n ? path.join(process.env.USERPROFILE, \"scoop\", \"persist\", \"fnm\")\n : null,\n ].filter(Boolean) as string[];\n }\n // macOS / Linux\n return [\n process.env.FNM_DIR,\n process.env.HOME\n ? path.join(process.env.HOME, \".local\", \"share\", \"fnm\")\n : null,\n process.env.HOME ? path.join(process.env.HOME, \".fnm\") : null,\n process.env.XDG_DATA_HOME\n ? path.join(process.env.XDG_DATA_HOME, \"fnm\")\n : null,\n ].filter(Boolean) as string[];\n}\n\nfunction nodeExecutableName(): string {\n return process.platform === \"win32\" ? \"node.exe\" : \"node\";\n}\n\nexport function probeFnmNode(desiredVersion: string): string | null {\n const dirs = fnmCandidateDirs();\n const exe = nodeExecutableName();\n\n for (const baseDir of dirs) {\n const candidate = path.join(\n baseDir,\n \"node-versions\",\n `v${desiredVersion}`,\n \"installation\",\n exe,\n );\n if (!fs.existsSync(candidate)) continue;\n\n try {\n const v = execSync(`\"${candidate}\" --version`, {\n encoding: \"utf-8\",\n timeout: 5000,\n }).trim();\n if (v.startsWith(`v${desiredVersion.split(\".\")[0]}.`)) {\n return candidate;\n }\n } catch {\n // candidate exists but doesn't work — skip\n }\n }\n\n return null;\n}\n\n// ─── Version detection ─────────────────────────────────────────\n\nexport function detectNodeMajorVersion(command: string): number | null {\n try {\n const version = execSync(`\"${command}\" --version`, {\n encoding: \"utf-8\",\n timeout: 5000,\n }).trim();\n const match = version.match(/^v?(\\d+)\\./);\n return match ? parseInt(match[1], 10) : null;\n } catch {\n return null;\n }\n}\n\nexport function checkStripTypesSupport(command: string): boolean {\n const major = detectNodeMajorVersion(command);\n if (major !== null && major >= 22) return true;\n try {\n execSync(`\"${command}\" --experimental-strip-types -e \"\"`, {\n timeout: 5000,\n stdio: \"pipe\",\n });\n return true;\n } catch {\n return false;\n }\n}\n\n// ─── tsx fallback ──────────────────────────────────────────────\n\nexport function findTsxFallback(repoRoot: string): string | null {\n const candidates = [\n path.join(repoRoot, \"node_modules\", \".bin\", \"tsx.exe\"),\n path.join(repoRoot, \"node_modules\", \".bin\", \"tsx.CMD\"),\n path.join(repoRoot, \"node_modules\", \".bin\", \"tsx\"),\n ];\n for (const c of candidates) {\n if (fs.existsSync(c)) return c;\n }\n return null;\n}\n\n// ─── fnm bin directory (for PATH prepending) ───────────────────\n\n/**\n * Returns the directory containing the fnm-managed node binary,\n * suitable for prepending to PATH in child processes.\n */\nexport function getFnmBinDir(repoRoot: string): string | null {\n const desiredVersion = readNodeVersion(repoRoot);\n if (!desiredVersion) return null;\n\n const nodePath = probeFnmNode(desiredVersion);\n if (!nodePath) return null;\n\n return path.dirname(nodePath);\n}\n\n// ─── Main resolver ─────────────────────────────────────────────\n\n/**\n * Resolve the Node.js runtime to use for spawning child processes.\n *\n * Priority: bun passthrough → .node-version + fnm → configured command → tsx fallback\n */\nexport function resolveNodeRuntime(\n configCommand: string,\n repoRoot: string,\n): ResolvedRuntime {\n // Bun: native TS support, no strip-types needed\n if (configCommand === \"bun\" || configCommand.endsWith(\"bun.exe\")) {\n return {\n command: configCommand,\n supportsStripTypes: false,\n source: \"bun\",\n majorVersion: null,\n };\n }\n\n // .node-version + fnm discovery\n const desiredVersion = readNodeVersion(repoRoot);\n if (desiredVersion) {\n const fnmNode = probeFnmNode(desiredVersion);\n if (fnmNode) {\n const major = detectNodeMajorVersion(fnmNode);\n return {\n command: fnmNode,\n supportsStripTypes: checkStripTypesSupport(fnmNode),\n source: \"fnm\",\n majorVersion: major,\n };\n }\n }\n\n // Configured command (from config or PATH)\n const major = detectNodeMajorVersion(configCommand);\n if (major !== null) {\n return {\n command: configCommand,\n supportsStripTypes: checkStripTypesSupport(configCommand),\n source: major === detectNodeMajorVersion(\"node\") ? \"path\" : \"config\",\n majorVersion: major,\n };\n }\n\n // tsx fallback\n const tsx = findTsxFallback(repoRoot);\n if (tsx) {\n return {\n command: tsx,\n supportsStripTypes: false,\n source: \"tsx-fallback\",\n majorVersion: null,\n };\n }\n\n // Last resort\n return {\n command: configCommand,\n supportsStripTypes: false,\n source: \"path\",\n majorVersion: null,\n };\n}\n\n// ─── Env builder for child processes ───────────────────────────\n\n/**\n * Build an env object with fnm Node prepended to PATH.\n * Use this when spawning child processes that need the correct Node.\n */\nexport function buildRuntimeEnv(\n repoRoot: string,\n baseEnv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n const fnmBin = getFnmBinDir(repoRoot);\n if (!fnmBin) return { ...baseEnv };\n\n const pathKey = process.platform === \"win32\" ? \"Path\" : \"PATH\";\n const currentPath = baseEnv[pathKey] ?? baseEnv.PATH ?? \"\";\n\n return {\n ...baseEnv,\n [pathKey]: `${fnmBin}${path.delimiter}${currentPath}`,\n };\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type {\n InstanceId,\n BridgeState,\n AppServerState,\n Platform,\n} from \"../types.js\";\n\nimport { appServerLogFilePath } from \"./bridge-paths.js\";\nimport { removeFileIfExists } from \"./bridge-file-io.js\";\nimport { isLoopbackHost } from \"./bridge-port-network.js\";\nimport { resolveCodexCommand } from \"./bridge-codex-command.js\";\nimport {\n startWindowsCodexAppServer,\n findListeningProcessId,\n} from \"./bridge-windows-spawn.js\";\nimport {\n startUnixCodexAppServer,\n findUnixListeningProcessId,\n} from \"./bridge-unix-spawn.js\";\nimport { terminateProcess, isProcessAlive } from \"./bridge-process-control.js\";\nimport {\n checkAppServerHealth,\n waitForManagedAppServerReady,\n markAppServerHealthy,\n} from \"./bridge-app-server-health.js\";\nimport {\n readGatewayToken,\n createManagedAppServerAuth,\n canReuseManagedAppServer,\n} from \"./bridge-app-server-auth.js\";\nimport { rotateLog } from \"./bridge-observability.js\";\n\nexport interface EnsureCodexAppServerOptions {\n instanceId: InstanceId;\n stateDir: string;\n runtimeStateDir: string;\n commsDir: string;\n repoRoot: string;\n platform: Platform;\n appServerUrl: string;\n agentName: string;\n existingAppServer?: AppServerState | null;\n noAuth?: boolean;\n}\n\nexport const DEFAULT_APP_SERVER_URL = \"ws://127.0.0.1:4501\";\nexport const APP_SERVER_START_TIMEOUT_MS = 20_000;\nexport const APP_SERVER_GATEWAY_START_TIMEOUT_MS = 5_000;\n\nfunction buildCodexAppServerEnv(\n options: EnsureCodexAppServerOptions,\n): NodeJS.ProcessEnv {\n return {\n ...process.env,\n TAP_COMMS_DIR: options.commsDir,\n TAP_STATE_DIR: options.stateDir,\n TAP_RUNTIME_STATE_DIR: options.runtimeStateDir,\n TAP_REPO_ROOT: options.repoRoot,\n TAP_BRIDGE_INSTANCE_ID: options.instanceId,\n TAP_AGENT_ID: options.instanceId,\n TAP_AGENT_NAME: options.agentName,\n CODEX_TAP_AGENT_NAME: options.agentName,\n };\n}\n\n/**\n * Check if any OTHER running bridge is using the same managed app-server.\n * Used to prevent killing a shared app-server when one bridge fails to start.\n */\nexport function isAppServerUsedByOtherBridge(\n stateDir: string,\n excludeInstanceId: InstanceId,\n appServer: AppServerState,\n): boolean {\n const pidDir = path.join(stateDir, \"pids\");\n if (!fs.existsSync(pidDir)) return false;\n\n for (const name of fs.readdirSync(pidDir)) {\n if (!name.startsWith(\"bridge-\") || !name.endsWith(\".json\")) continue;\n const otherId = name.slice(\"bridge-\".length, -\".json\".length);\n if (otherId === excludeInstanceId) continue;\n\n try {\n const raw = fs.readFileSync(path.join(pidDir, name), \"utf-8\");\n const state = JSON.parse(raw) as BridgeState;\n if (\n state.appServer?.url === appServer.url &&\n state.appServer?.pid === appServer.pid &&\n isProcessAlive(state.pid)\n ) {\n return true;\n }\n } catch {\n continue;\n }\n }\n return false;\n}\n\nexport function findReusableManagedAppServer(\n stateDir: string,\n publicUrl: string,\n): AppServerState | null {\n const pidDir = path.join(stateDir, \"pids\");\n if (!fs.existsSync(pidDir)) {\n return null;\n }\n\n for (const name of fs.readdirSync(pidDir)) {\n if (!name.startsWith(\"bridge-\") || !name.endsWith(\".json\")) {\n continue;\n }\n\n try {\n const raw = fs.readFileSync(path.join(pidDir, name), \"utf-8\");\n const parsed = JSON.parse(raw) as BridgeState;\n if (parsed.appServer?.url !== publicUrl) {\n continue;\n }\n if (canReuseManagedAppServer(parsed.appServer)) {\n return markAppServerHealthy(parsed.appServer!);\n }\n } catch {\n // Ignore stale or corrupted bridge state.\n }\n }\n\n return null;\n}\n\nexport function resolveAppServerUrl(\n baseUrl: string | undefined,\n port?: number,\n): string {\n const resolvedBase = (baseUrl ?? DEFAULT_APP_SERVER_URL).replace(/\\/$/, \"\");\n if (port == null) {\n return resolvedBase;\n }\n\n try {\n const parsed = new URL(resolvedBase);\n parsed.port = String(port);\n return parsed.toString().replace(/\\/$/, \"\");\n } catch {\n return resolvedBase;\n }\n}\n\nexport async function ensureCodexAppServer(\n options: EnsureCodexAppServerOptions,\n): Promise<AppServerState> {\n const effectiveUrl = resolveAppServerUrl(options.appServerUrl);\n const fallbackManualCommand = formatCodexAppServerCommand(\n \"codex\",\n effectiveUrl,\n );\n if (\n options.existingAppServer?.url === effectiveUrl &&\n canReuseManagedAppServer(options.existingAppServer)\n ) {\n return markAppServerHealthy(options.existingAppServer);\n }\n\n const sharedManaged = findReusableManagedAppServer(\n options.stateDir,\n effectiveUrl,\n );\n if (sharedManaged) {\n return sharedManaged;\n }\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(effectiveUrl);\n } catch {\n throw new Error(\n `Invalid app-server URL: ${effectiveUrl}\\nStart it manually:\\n ${fallbackManualCommand}`,\n );\n }\n\n if (!isLoopbackHost(parsedUrl.hostname)) {\n throw new Error(\n `Auto-start only supports loopback app-server URLs. Current URL: ${effectiveUrl}\\nStart it manually:\\n ${fallbackManualCommand}`,\n );\n }\n\n if (await checkAppServerHealth(effectiveUrl)) {\n const hint = options.noAuth\n ? \"Stop it first or use --no-server for an unmanaged external app-server.\"\n : \"A listener is already running, so tap cannot insert the auth gateway there.\\nStop it first or use --no-server for an unmanaged external app-server.\";\n throw new Error(`${effectiveUrl}: ${hint}`);\n }\n\n const resolvedCommand = resolveCodexCommand(options.platform);\n if (!resolvedCommand) {\n throw new Error(\n `Codex CLI not found in PATH.\\nStart the app-server manually:\\n ${fallbackManualCommand}`,\n );\n }\n\n const logPath = appServerLogFilePath(options.stateDir, options.instanceId);\n fs.mkdirSync(path.dirname(logPath), { recursive: true });\n rotateLog(logPath);\n const appServerEnv = buildCodexAppServerEnv(options);\n\n // --no-auth: start app-server directly on the public URL (no gateway).\n // TUI and bridge both connect to the same port without token auth.\n if (options.noAuth) {\n const manualCommand = formatCodexAppServerCommand(\"codex\", effectiveUrl);\n let pid: number | null;\n\n if (options.platform === \"win32\") {\n try {\n pid = startWindowsCodexAppServer(\n resolvedCommand,\n effectiveUrl,\n options.repoRoot,\n logPath,\n appServerEnv,\n );\n } catch (err) {\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n } else {\n try {\n pid = startUnixCodexAppServer(\n resolvedCommand,\n effectiveUrl,\n options.repoRoot,\n logPath,\n appServerEnv,\n options.platform,\n );\n } catch (err) {\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n }\n\n if (pid == null) {\n throw new Error(\n `Failed to spawn Codex app-server.\\nStart it manually:\\n ${manualCommand}`,\n );\n }\n\n // Managed startup uses HTTP /readyz (with TCP fallback for older servers)\n // so readiness is verified without opening an extra WebSocket session.\n const healthy = await waitForManagedAppServerReady(\n effectiveUrl,\n APP_SERVER_START_TIMEOUT_MS,\n );\n if (!healthy) {\n await terminateProcess(pid, options.platform);\n throw new Error(\n `Codex app-server did not become healthy at ${effectiveUrl}.\\nCheck ${logPath}\\nOr start it manually:\\n ${manualCommand}`,\n );\n }\n\n pid =\n (options.platform === \"win32\"\n ? findListeningProcessId(effectiveUrl, options.platform)\n : findUnixListeningProcessId(effectiveUrl, options.platform)) ?? pid;\n const healthyAt = new Date().toISOString();\n return {\n url: effectiveUrl,\n pid,\n managed: true,\n healthy: true,\n lastCheckedAt: healthyAt,\n lastHealthyAt: healthyAt,\n logPath,\n manualCommand,\n auth: null,\n };\n }\n\n // Default: auth gateway mode — gateway on publicUrl, app-server on random upstream port\n const auth = await createManagedAppServerAuth({\n instanceId: options.instanceId,\n stateDir: options.stateDir,\n repoRoot: options.repoRoot,\n platform: options.platform,\n publicUrl: effectiveUrl,\n });\n const manualCommand = formatCodexAppServerCommand(\"codex\", auth.upstreamUrl);\n\n let pid: number | null;\n\n if (options.platform === \"win32\") {\n try {\n pid = startWindowsCodexAppServer(\n resolvedCommand,\n auth.upstreamUrl,\n options.repoRoot,\n logPath,\n appServerEnv,\n );\n } catch (err) {\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n } else {\n try {\n pid = startUnixCodexAppServer(\n resolvedCommand,\n auth.upstreamUrl,\n options.repoRoot,\n logPath,\n appServerEnv,\n options.platform,\n );\n } catch (err) {\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Failed to spawn Codex app-server: ${err instanceof Error ? err.message : String(err)}\\nStart it manually:\\n ${manualCommand}`,\n { cause: err },\n );\n }\n }\n\n if (pid == null) {\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Failed to spawn Codex app-server.\\nStart it manually:\\n ${manualCommand}`,\n );\n }\n\n // Managed startup uses HTTP /readyz (with TCP fallback for older servers)\n // so readiness is verified without opening an extra WebSocket session.\n const healthy = await waitForManagedAppServerReady(\n auth.upstreamUrl,\n APP_SERVER_START_TIMEOUT_MS,\n );\n\n if (!healthy) {\n await terminateProcess(pid, options.platform);\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Codex app-server did not become healthy at ${auth.upstreamUrl}.\\nCheck ${logPath}\\nOr start it manually:\\n ${manualCommand}`,\n );\n }\n\n const gatewayToken = readGatewayToken(auth);\n if (!gatewayToken) {\n await terminateProcess(pid, options.platform);\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\"Tap auth gateway token is missing after startup.\");\n }\n\n // Gateway readiness hits /readyz on the public URL, which verifies the\n // gateway itself plus upstream readiness end-to-end without creating a\n // WebSocket session.\n const gatewayHealthy = await waitForManagedAppServerReady(\n effectiveUrl,\n APP_SERVER_GATEWAY_START_TIMEOUT_MS,\n );\n if (!gatewayHealthy) {\n await terminateProcess(pid, options.platform);\n if (auth.gatewayPid != null) {\n await terminateProcess(auth.gatewayPid, options.platform);\n }\n removeFileIfExists(auth.tokenPath);\n throw new Error(\n `Tap auth gateway did not become healthy at ${effectiveUrl}.\\nCheck ${auth.gatewayLogPath ?? \"the gateway log\"} and ${logPath}.`,\n );\n }\n\n const healthyAt = new Date().toISOString();\n pid =\n (options.platform === \"win32\"\n ? findListeningProcessId(auth.upstreamUrl, options.platform)\n : findUnixListeningProcessId(auth.upstreamUrl, options.platform)) ?? pid;\n return {\n url: effectiveUrl,\n pid,\n managed: true,\n healthy: true,\n lastCheckedAt: healthyAt,\n lastHealthyAt: healthyAt,\n logPath,\n manualCommand,\n auth,\n };\n}\n\nexport function formatCodexAppServerCommand(\n command: string,\n url: string,\n): string {\n return `${command} app-server --listen ${url}`;\n}\n"],"mappings":"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","names":["fs","fs","path","crypto","reviewFilePath","fs","path","log","fs","path","fs","path","fs","path","major","fs","path","DEFAULT_APP_SERVER_URL","DEFAULT_APP_SERVER_URL","createHeadlessLoop","resolved"]}
File without changes