{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../../src/extension/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAoB,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAoB,MAAM,qBAAqB,CAAC;AAChF,OAAO,EAAE,sBAAsB,EAAiC,MAAM,gCAAgC,CAAC;AAGvG,OAAO,EAAwB,KAAK,eAAe,EAAE,KAAK,aAAa,EAAiB,MAAM,oBAAoB,CAAC;AAEnH,UAAU,WAAW;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,UAAU;IACnB,gBAAgB,EAAE,MAAM,OAAO,CAAC;IAChC,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,uBAAuB,EAAE,OAAO,uBAAuB,CAAC;IACxD,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;CACtD;AAED,UAAU,iBAAiB;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACxC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAC3B;AAuKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CAwClE","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { type AgentSource, discoverAgentsAll } from \"../agents/agents.ts\";\nimport { discoverAvailableSkills, type SkillSource } from \"../agents/skills.ts\";\nimport { diagnoseIntercomBridge, type IntercomBridgeDiagnostic } from \"../intercom/intercom-bridge.ts\";\nimport { isAsyncAvailable } from \"../runs/background/async-execution.ts\";\nimport { formatSpawnBudgetSummary, getSpawnBudgetSnapshot } from \"../runs/shared/spawn-budget.ts\";\nimport { CHAIN_RUNS_DIR, DIRS, type ExtensionConfig, type SubagentState, TEMP_ROOT_DIR } from \"../shared/types.ts\";\n\ninterface DoctorPaths {\n\ttempRootDir: string;\n\tasyncDir: string;\n\tresultsDir: string;\n\tchainRunsDir: string;\n}\n\ninterface DoctorDeps {\n\tisAsyncAvailable: () => boolean;\n\tdiscoverAgentsAll: typeof discoverAgentsAll;\n\tdiscoverAvailableSkills: typeof discoverAvailableSkills;\n\tdiagnoseIntercomBridge: typeof diagnoseIntercomBridge;\n}\n\ninterface DoctorReportInput {\n\tcwd: string;\n\tconfig: ExtensionConfig;\n\tstate: SubagentState;\n\tcontext?: \"fresh\" | \"fork\";\n\trequestedSessionDir?: string;\n\tcurrentSessionFile?: string | null;\n\tcurrentSessionId?: string | null;\n\torchestratorTarget?: string;\n\tsessionError?: string;\n\texpandTilde?: (value: string) => string;\n\tpaths?: DoctorPaths;\n\tdeps?: Partial<DoctorDeps>;\n}\n\nfunction defaultPaths(): DoctorPaths {\n\treturn {\n\t\ttempRootDir: TEMP_ROOT_DIR,\n\t\tasyncDir: DIRS.async,\n\t\tresultsDir: DIRS.results,\n\t\tchainRunsDir: CHAIN_RUNS_DIR,\n\t};\n}\n\nconst DEFAULT_DEPS: DoctorDeps = {\n\tisAsyncAvailable,\n\tdiscoverAgentsAll,\n\tdiscoverAvailableSkills,\n\tdiagnoseIntercomBridge,\n};\n\nfunction errorText(error: unknown): string {\n\treturn error instanceof Error ? `${error.name}: ${error.message}` : String(error);\n}\n\nfunction lineFromCheck(label: string, check: () => string): string {\n\ttry {\n\t\treturn check();\n\t} catch (error) {\n\t\treturn `- ${label}: failed — ${errorText(error)}`;\n\t}\n}\n\nfunction formatExistingDirectory(label: string, dirPath: string): string {\n\ttry {\n\t\tif (!fs.existsSync(dirPath)) return `- ${label}: missing (${dirPath})`;\n\t\tconst stats = fs.statSync(dirPath);\n\t\tif (!stats.isDirectory()) throw new Error(`not a directory: ${dirPath}`);\n\t\tfs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);\n\t\treturn `- ${label}: ok (${dirPath})`;\n\t} catch (error) {\n\t\treturn `- ${label}: failed (${dirPath}) — ${errorText(error)}`;\n\t}\n}\n\nfunction formatSourceCounts(counts: Record<AgentSource, number>): string {\n\treturn `builtin ${counts.builtin}, package ${counts.package}, user ${counts.user}, project ${counts.project}`;\n}\n\nfunction formatSkillSourceCounts(skills: Array<{ source: SkillSource }>): string {\n\tconst counts = new Map<SkillSource, number>();\n\tfor (const skill of skills) counts.set(skill.source, (counts.get(skill.source) ?? 0) + 1);\n\tconst ordered: SkillSource[] = [\n\t\t\"project\",\n\t\t\"project-settings\",\n\t\t\"project-package\",\n\t\t\"user\",\n\t\t\"user-settings\",\n\t\t\"user-package\",\n\t\t\"extension\",\n\t\t\"builtin\",\n\t\t\"unknown\",\n\t];\n\tconst parts = ordered.map((source) => `${source} ${counts.get(source) ?? 0}`).filter((part) => !part.endsWith(\" 0\"));\n\treturn parts.length > 0 ? parts.join(\", \") : \"none\";\n}\n\nfunction formatConfiguredSessionDir(input: DoctorReportInput): string {\n\tif (input.requestedSessionDir) {\n\t\treturn path.resolve(input.expandTilde?.(input.requestedSessionDir) ?? input.requestedSessionDir);\n\t}\n\tif (input.config.defaultSessionDir) {\n\t\treturn path.resolve(input.expandTilde?.(input.config.defaultSessionDir) ?? input.config.defaultSessionDir);\n\t}\n\treturn \"not configured\";\n}\n\nfunction formatSessionLines(input: DoctorReportInput): string[] {\n\tconst sessionFile = input.currentSessionFile ?? null;\n\tconst lines = [\n\t\tlineFromCheck(\"configured session dir\", () => `- configured session dir: ${formatConfiguredSessionDir(input)}`),\n\t\t`- current session file: ${sessionFile ?? \"not available\"}`,\n\t\t`- current session dir: ${sessionFile ? path.dirname(sessionFile) : \"not available\"}`,\n\t\t`- current session id: ${input.currentSessionId ?? input.state.currentSessionId ?? \"not available\"}`,\n\t];\n\tif (input.sessionError) lines.push(`- session manager: failed — ${input.sessionError}`);\n\treturn lines;\n}\n\nfunction formatDiscovery(input: DoctorReportInput, deps: DoctorDeps): string[] {\n\treturn [\n\t\tlineFromCheck(\"agents/chains\", () => {\n\t\t\tconst discovered = deps.discoverAgentsAll(input.cwd);\n\t\t\tconst agentCounts = {\n\t\t\t\tbuiltin: discovered.builtin.length,\n\t\t\t\tpackage: discovered.package?.length ?? 0,\n\t\t\t\tuser: discovered.user.length,\n\t\t\t\tproject: discovered.project.length,\n\t\t\t};\n\t\t\tconst chainCounts = discovered.chains.reduce<Record<AgentSource, number>>(\n\t\t\t\t(counts, chain) => {\n\t\t\t\t\tcounts[chain.source] += 1;\n\t\t\t\t\treturn counts;\n\t\t\t\t},\n\t\t\t\t{ builtin: 0, package: 0, user: 0, project: 0 },\n\t\t\t);\n\t\t\treturn [\n\t\t\t\t`- agents: total ${agentCounts.builtin + agentCounts.package + agentCounts.user + agentCounts.project} (${formatSourceCounts(agentCounts)})`,\n\t\t\t\t`- chains: total ${discovered.chains.length} (${formatSourceCounts(chainCounts)})`,\n\t\t\t].join(\"\\n\");\n\t\t}),\n\t\tlineFromCheck(\"skills\", () => {\n\t\t\tconst skills = deps.discoverAvailableSkills(input.cwd);\n\t\t\treturn `- skills: total ${skills.length} (${formatSkillSourceCounts(skills)})`;\n\t\t}),\n\t];\n}\n\nfunction formatIntercomDiagnostic(\n\tdiagnostic: IntercomBridgeDiagnostic,\n\tcontext: \"fresh\" | \"fork\" | undefined,\n): string[] {\n\tconst lines = [\n\t\t`- bridge: ${diagnostic.active ? \"active\" : \"inactive\"}${diagnostic.reason ? ` (${diagnostic.reason})` : \"\"}`,\n\t\t`- mode: ${diagnostic.mode}; context: ${context ?? \"unspecified\"}`,\n\t\t`- orchestrator target: ${diagnostic.orchestratorTarget ?? \"not available\"}`,\n\t\t`- supervisor channel: ${diagnostic.supervisorChannelAvailable ? \"available\" : \"unavailable\"} (${diagnostic.extensionDir})`,\n\t];\n\treturn lines;\n}\n\nfunction formatSpawnBudgetSection(input: DoctorReportInput): string[] {\n\tconst snapshot = getSpawnBudgetSnapshot(\n\t\tinput.state,\n\t\tinput.config,\n\t\tinput.currentSessionId ?? input.state.currentSessionId,\n\t);\n\treturn [\n\t\t`- usage: ${formatSpawnBudgetSummary(snapshot)}`,\n\t\t`- recent grants: ${\n\t\t\tsnapshot.grantHistory.length === 0\n\t\t\t\t? \"none\"\n\t\t\t\t: snapshot.grantHistory\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t(grant) =>\n\t\t\t\t\t\t\t\t`+${grant.amount} at ${new Date(grant.grantedAt).toISOString()} (${grant.previousLimit} → ${grant.limit})`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join(\"; \")\n\t\t}`,\n\t\t\"- reset boundary: a new parent session resets usage and grants; compaction does not\",\n\t];\n}\n\nfunction formatPermissionSystemSection(): string[] {\n\tconst lines: string[] = [];\n\tconst parentSession = process.env.PI_SUBAGENT_PARENT_SESSION ?? \"\";\n\tconst trimmed = parentSession.trim();\n\tif (trimmed) {\n\t\tlines.push(`- parent session: set (${trimmed})`);\n\t} else {\n\t\tlines.push(\"- parent session: not set — ask forwarding from subprocess children will not reach a parent UI\");\n\t}\n\tconst isChild = process.env.PI_SUBAGENT_CHILD === \"1\";\n\tlines.push(`- subagent process: ${isChild ? \"yes (PI_SUBAGENT_CHILD=1)\" : \"no\"}`);\n\t// Whether pi-permission-system is installed and where it stores config is\n\t// outside pi-subagents' control, so we only report the forwarding signal we\n\t// own. Run `pi list` to confirm the permission extension is installed.\n\treturn lines;\n}\n\nexport function buildDoctorReport(input: DoctorReportInput): string {\n\tconst paths = input.paths ?? defaultPaths();\n\tconst deps = { ...DEFAULT_DEPS, ...input.deps };\n\tconst lines = [\n\t\t\"Subagents doctor report\",\n\t\t\"\",\n\t\t\"Runtime\",\n\t\t`- cwd: ${input.cwd}`,\n\t\tlineFromCheck(\"async support\", () => `- async support: ${deps.isAsyncAvailable() ? \"available\" : \"unavailable\"}`),\n\t\t...formatSessionLines(input),\n\t\t\"\",\n\t\t\"Filesystem\",\n\t\tformatExistingDirectory(\"temp root\", paths.tempRootDir),\n\t\tformatExistingDirectory(\"async runs\", paths.asyncDir),\n\t\tformatExistingDirectory(\"results\", paths.resultsDir),\n\t\tformatExistingDirectory(\"chain runs\", paths.chainRunsDir),\n\t\t\"\",\n\t\t\"Discovery\",\n\t\t...formatDiscovery(input, deps),\n\t\t\"\",\n\t\t\"Spawn budget\",\n\t\t...formatSpawnBudgetSection(input),\n\t\t\"\",\n\t\t\"Permission system\",\n\t\t...formatPermissionSystemSection(),\n\t\t\"\",\n\t\t\"Intercom bridge\",\n\t\t...lineFromCheck(\"intercom bridge\", () =>\n\t\t\tformatIntercomDiagnostic(\n\t\t\t\tdeps.diagnoseIntercomBridge({\n\t\t\t\t\tconfig: input.config.intercomBridge,\n\t\t\t\t\tcontext: input.context,\n\t\t\t\t\torchestratorTarget: input.orchestratorTarget,\n\t\t\t\t\tcwd: input.cwd,\n\t\t\t\t}),\n\t\t\t\tinput.context,\n\t\t\t).join(\"\\n\"),\n\t\t).split(\"\\n\"),\n\t];\n\treturn lines.join(\"\\n\");\n}\n"]}