{"version":3,"file":"project-panes.d.ts","sourceRoot":"","sources":["../../../../src/inspectors/herdr/project-panes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAG/D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAkC,KAAK,WAAW,EAAyC,MAAM,aAAa,CAAC;AAEtH,eAAO,MAAM,0BAA0B,8DAA+D,CAAC;AACvG,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjF,MAAM,WAAW,uBAAuB;IACvC,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,oBAAoB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,iBAAiB;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,UAAU,eAAe;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;CACjB;AAoCD,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,GAAG,uBAAuB,GAAG,SAAS,CAMpG;AA0CD,wBAAsB,4BAA4B,CACjD,MAAM,EAAE,sBAAsB,EAC9B,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,eAAe,GACnB,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CA+DnC","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport type { AgentToolResult } from \"@lpb-work/pi-agent-core\";\nimport { getPiSpawnCommand } from \"../../runs/shared/pi-spawn.ts\";\nimport { writeAtomicJson } from \"../../shared/atomic-json.ts\";\nimport type { Details } from \"../../shared/types.ts\";\nimport { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode, type HerdrResult } from \"./client.ts\";\n\nexport const HERDR_PROJECT_PANE_ACTIONS = [\"project.open\", \"project.status\", \"project.close\"] as const;\nexport type HerdrProjectPaneAction = (typeof HERDR_PROJECT_PANE_ACTIONS)[number];\n\nexport interface HerdrProjectPaneBinding {\n\tschemaVersion: 1;\n\tkind: \"herdr-project-pane\";\n\tprojectRoot: string;\n\tpaneId: string;\n\topenedAt: string;\n\tlastFocusedAt?: string;\n\therdrVersion?: string;\n\tcommand: string;\n\tstartupMessage?: string;\n}\n\ninterface ProjectPaneParams {\n\tcwd?: string;\n\tmessage?: string;\n\tfocus?: boolean;\n}\n\ninterface ProjectPaneDeps {\n\tcwd: string;\n\tclient?: HerdrClient;\n\tsignal?: AbortSignal;\n\tnow?: () => Date;\n}\n\nfunction result(text: string, isError = false): AgentToolResult<Details> {\n\treturn {\n\t\tcontent: [{ type: \"text\", text }],\n\t\t...(isError ? { isError: true } : {}),\n\t\tdetails: { mode: \"management\", results: [] },\n\t};\n}\n\nfunction formatHerdrError(input: { code: HerdrErrorCode; message: string }): string {\n\treturn `Herdr project pane error (${input.code}): ${input.message}`;\n}\n\nfunction projectPaneDir(projectRoot: string): string {\n\treturn path.join(projectRoot, \".pi-subagents\", \"project-panes\");\n}\n\nfunction bindingPath(projectRoot: string): string {\n\treturn path.join(projectPaneDir(projectRoot), \"herdr.json\");\n}\n\nfunction parseBinding(value: unknown): HerdrProjectPaneBinding | undefined {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n\tconst input = value as Partial<HerdrProjectPaneBinding>;\n\tif (input.schemaVersion !== 1 || input.kind !== \"herdr-project-pane\") return undefined;\n\tif (\n\t\ttypeof input.projectRoot !== \"string\" ||\n\t\ttypeof input.paneId !== \"string\" ||\n\t\ttypeof input.openedAt !== \"string\" ||\n\t\ttypeof input.command !== \"string\"\n\t)\n\t\treturn undefined;\n\treturn input as HerdrProjectPaneBinding;\n}\n\nexport function readHerdrProjectPaneBinding(projectRoot: string): HerdrProjectPaneBinding | undefined {\n\ttry {\n\t\treturn parseBinding(JSON.parse(fs.readFileSync(bindingPath(projectRoot), \"utf-8\")));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction extractPaneId(value: unknown): string | undefined {\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n\tconst record = value as Record<string, unknown>;\n\tconst pane =\n\t\trecord.pane && typeof record.pane === \"object\" && !Array.isArray(record.pane)\n\t\t\t? (record.pane as Record<string, unknown>)\n\t\t\t: record;\n\tfor (const key of [\"pane_id\", \"paneId\", \"id\"]) if (typeof pane[key] === \"string\") return pane[key];\n\treturn undefined;\n}\n\nfunction shellQuote(value: string): string {\n\tif (process.platform === \"win32\") return `\"${value.replaceAll('\"', '\\\\\"')}\"`;\n\treturn `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nfunction resolveProjectRoot(params: ProjectPaneParams, deps: ProjectPaneDeps): string | { error: string } {\n\tconst requested = params.cwd?.trim() || deps.cwd;\n\tconst resolved = path.resolve(requested);\n\ttry {\n\t\tconst stat = fs.statSync(resolved);\n\t\tif (!stat.isDirectory()) return { error: `Project pane target '${resolved}' is not a directory.` };\n\t\treturn fs.realpathSync(resolved);\n\t} catch (cause) {\n\t\treturn {\n\t\t\terror: `Project pane target '${resolved}' is unavailable: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t};\n\t}\n}\n\nasync function paneExists(client: HerdrClient, paneId: string, signal?: AbortSignal): Promise<HerdrResult<unknown>> {\n\treturn client.run([\"pane\", \"get\", paneId], { timeoutMs: 5_000, signal });\n}\n\nfunction projectPaneCommand(message: string | undefined): string {\n\tconst args = message?.trim() ? [message.trim()] : [];\n\tconst command = getPiSpawnCommand(args);\n\treturn [command.command, ...command.args].map(shellQuote).join(\" \");\n}\n\nexport async function handleHerdrProjectPaneAction(\n\taction: HerdrProjectPaneAction,\n\tparams: ProjectPaneParams,\n\tdeps: ProjectPaneDeps,\n): Promise<AgentToolResult<Details>> {\n\tconst projectRoot = resolveProjectRoot(params, deps);\n\tif (typeof projectRoot !== \"string\") return result(projectRoot.error, true);\n\tconst client = deps.client ?? createHerdrClient();\n\tconst existing = readHerdrProjectPaneBinding(projectRoot);\n\n\tif (action === \"project.status\") {\n\t\tif (!existing) return result(`No Herdr project pane binding exists for ${projectRoot}.`);\n\t\tconst live = await paneExists(client, existing.paneId, deps.signal);\n\t\tif (live.ok === false)\n\t\t\treturn result(`${formatHerdrError(live.error)}\\nBinding: ${bindingPath(projectRoot)}`, true);\n\t\treturn result(\n\t\t\t`Herdr project pane ${existing.paneId} is open for ${projectRoot}.\\nBinding: ${bindingPath(projectRoot)}`,\n\t\t);\n\t}\n\n\tif (action === \"project.close\") {\n\t\tif (!existing) return result(`No Herdr project pane binding exists for ${projectRoot}.`);\n\t\tconst closed = await client.run([\"pane\", \"close\", existing.paneId], { timeoutMs: 10_000, signal: deps.signal });\n\t\tif (closed.ok === false && closed.error.code !== \"NOT_FOUND\" && closed.error.code !== \"PANE_GONE\")\n\t\t\treturn result(formatHerdrError(closed.error), true);\n\t\tfs.rmSync(bindingPath(projectRoot), { force: true });\n\t\treturn result(`Closed Herdr project pane ${existing.paneId} for ${projectRoot}.`);\n\t}\n\n\tconst detected = await detectHerdr(client, deps.signal);\n\tif (detected.ok === false) return result(formatHerdrError(detected.error), true);\n\tif (existing) {\n\t\tconst live = await paneExists(client, existing.paneId, deps.signal);\n\t\tif (live.ok)\n\t\t\treturn result(\n\t\t\t\t`Herdr project pane ${existing.paneId} is already open for ${projectRoot}.${params.focus ? \" Herdr cannot refocus an arbitrary raw pane id; select it in the Herdr UI.\" : \"\"}`,\n\t\t\t);\n\t}\n\tconst splitArgs = [\"pane\", \"split\", \"--current\", \"--direction\", \"right\", \"--cwd\", projectRoot];\n\tif (params.focus !== false) splitArgs.push(\"--focus\");\n\tconst split = await client.run(splitArgs, { timeoutMs: 15_000, signal: deps.signal });\n\tif (split.ok === false) return result(formatHerdrError(split.error), true);\n\tconst paneId = extractPaneId(split.data);\n\tif (!paneId) return result(\"Herdr project pane error (PANE_GONE): pane split returned no pane id.\", true);\n\tconst startupMessage = params.message?.trim();\n\tconst command = projectPaneCommand(startupMessage);\n\tconst started = await client.run([\"pane\", \"run\", paneId, command], { timeoutMs: 15_000, signal: deps.signal });\n\tif (started.ok === false) {\n\t\tawait client.run([\"pane\", \"close\", paneId], { timeoutMs: 5_000 });\n\t\treturn result(formatHerdrError(started.error), true);\n\t}\n\tconst now = (deps.now?.() ?? new Date()).toISOString();\n\tconst binding: HerdrProjectPaneBinding = {\n\t\tschemaVersion: 1,\n\t\tkind: \"herdr-project-pane\",\n\t\tprojectRoot,\n\t\tpaneId,\n\t\topenedAt: now,\n\t\t...(params.focus !== false ? { lastFocusedAt: now } : {}),\n\t\therdrVersion: detected.data.versionText,\n\t\tcommand,\n\t\t...(startupMessage ? { startupMessage } : {}),\n\t};\n\twriteAtomicJson(bindingPath(projectRoot), binding);\n\treturn result(\n\t\t`Opened Herdr project pane ${paneId} for ${projectRoot}. The pane runs its own Pi session; subagents launched there belong to that project.`,\n\t);\n}\n"]}