{"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../../src/shared/artifacts.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,qBAAqB,EAAE,KAAK,aAAa,EAAsC,MAAM,YAAY,CAAC;AA6GhH,0FAA0F;AAC1F,wBAAgB,kCAAkC,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAqBlF;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,GAAE,qBAAiC,GAAG,MAAM,CAY5G;AAED,wBAAgB,eAAe,CAC9B,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,UAAU,CAAC,EAAE,MAAM,EACnB,aAAa,GAAE,qBAAiC,GAC9C,MAAM,CAsBR;AAED,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,aAAa,CAWlH;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEpD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGrE;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAMT;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAGtE;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CA6BzE;AAED,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAwB/D","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { type ArtifactDirPreference, type ArtifactPaths, CHAIN_RUNS_DIR, TEMP_ARTIFACTS_DIR } from \"./types.ts\";\nimport { getAgentDir } from \"./utils.ts\";\n\nconst CLEANUP_MARKER_FILE = \".last-cleanup\";\nconst PROJECT_ARTIFACT_ROOT = \".pi-subagents\";\n\nconst PROJECT_ARTIFACT_PATHS = [\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/output.md`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_input.md`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_output.md`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker.jsonl`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_transcript.jsonl`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/run_worker_meta.json`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/progress/run/progress.md`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/outputs/output.md`,\n\t`${PROJECT_ARTIFACT_ROOT}/artifacts/outputs/run/output.md`,\n\t`${PROJECT_ARTIFACT_ROOT}/chain-runs/run.json`,\n];\n\nfunction globMatchesPath(pattern: string, filePath: string): boolean {\n\tlet expression = \"^\";\n\tfor (let index = 0; index < pattern.length; index += 1) {\n\t\tconst character = pattern[index];\n\t\tif (character === undefined) break;\n\t\tif (character === \"*\" && pattern[index + 1] === \"*\") {\n\t\t\tindex += 1;\n\t\t\tif (pattern[index + 1] === \"/\") {\n\t\t\t\tindex += 1;\n\t\t\t\texpression += \"(?:.*/)?\";\n\t\t\t} else {\n\t\t\t\texpression += \".*\";\n\t\t\t}\n\t\t} else if (character === \"*\") {\n\t\t\texpression += \"[^/]*\";\n\t\t} else if (character === \"?\") {\n\t\t\texpression += \"[^/]\";\n\t\t} else if (character === \"[\") {\n\t\t\tconst end = pattern.indexOf(\"]\", index + 1);\n\t\t\tif (end === -1) expression += \"\\\\[\";\n\t\t\telse {\n\t\t\t\texpression += pattern.slice(index, end + 1);\n\t\t\t\tindex = end;\n\t\t\t}\n\t\t} else {\n\t\t\texpression += character.replace(/[|\\\\{}()[\\]^$+?.]/g, \"\\\\$&\");\n\t\t}\n\t}\n\ttry {\n\t\treturn new RegExp(`${expression}$`).test(filePath);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction normalizePattern(pattern: string): string {\n\treturn pattern.replace(/^\\.\\//, \"\").replace(/^\\//, \"\").replace(/\\/+$/, \"\");\n}\n\nfunction patternMatchesArtifactPath(pattern: string, artifactPath: string): boolean {\n\tconst normalized = normalizePattern(pattern);\n\treturn (\n\t\tnormalized === PROJECT_ARTIFACT_ROOT ||\n\t\tnormalized === \"*\" ||\n\t\tartifactPath.startsWith(`${normalized}/`) ||\n\t\tglobMatchesPath(normalized, artifactPath)\n\t);\n}\n\nfunction _patternMatchesProjectArtifacts(pattern: string): boolean {\n\treturn PROJECT_ARTIFACT_PATHS.some((artifactPath) => patternMatchesArtifactPath(pattern, artifactPath));\n}\n\nfunction ignoreFileExcludesProjectArtifacts(filePath: string): boolean {\n\tif (!fs.existsSync(filePath)) return false;\n\ttry {\n\t\tconst excluded = new Set<string>();\n\t\tfor (const rawLine of fs.readFileSync(filePath, \"utf-8\").split(/\\r?\\n/)) {\n\t\t\tconst line = rawLine.trim();\n\t\t\tif (!line || line.startsWith(\"#\")) continue;\n\t\t\tconst negated = line.startsWith(\"!\");\n\t\t\tconst pattern = negated ? line.slice(1) : line;\n\t\t\tfor (const artifactPath of PROJECT_ARTIFACT_PATHS) {\n\t\t\t\tif (!patternMatchesArtifactPath(pattern, artifactPath)) continue;\n\t\t\t\tif (negated) excluded.delete(artifactPath);\n\t\t\t\telse excluded.add(artifactPath);\n\t\t\t}\n\t\t}\n\t\treturn excluded.size === PROJECT_ARTIFACT_PATHS.length;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction filesIncludeProjectArtifacts(files: unknown): boolean | undefined {\n\tif (!Array.isArray(files)) return undefined;\n\tconst included = new Set<string>();\n\tfor (const entry of files) {\n\t\tif (typeof entry !== \"string\") continue;\n\t\tconst negated = entry.startsWith(\"!\");\n\t\tconst pattern = negated ? entry.slice(1) : entry;\n\t\tfor (const artifactPath of PROJECT_ARTIFACT_PATHS) {\n\t\t\tif (!patternMatchesArtifactPath(pattern, artifactPath)) continue;\n\t\t\tif (negated) included.delete(artifactPath);\n\t\t\telse included.add(artifactPath);\n\t\t}\n\t}\n\treturn included.size > 0;\n}\n\n/** Returns a package-publishing warning when project artifacts can enter npm packages. */\nexport function getProjectArtifactPackagingWarning(cwd: string): string | undefined {\n\tconst packagePath = path.join(cwd, \"package.json\");\n\tif (!fs.existsSync(packagePath)) return undefined;\n\n\tlet packageJson: Record<string, unknown>;\n\ttry {\n\t\tconst parsed: unknown = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n\t\tif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) return undefined;\n\t\tpackageJson = parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst filesIncludeArtifacts = filesIncludeProjectArtifacts(packageJson.files);\n\tif (filesIncludeArtifacts === false) return undefined;\n\n\tconst npmIgnorePath = path.join(cwd, \".npmignore\");\n\tconst ignorePath = fs.existsSync(npmIgnorePath) ? npmIgnorePath : path.join(cwd, \".gitignore\");\n\tif (filesIncludeArtifacts === undefined && ignoreFileExcludesProjectArtifacts(ignorePath)) return undefined;\n\n\treturn \"Project-scoped subagent artifacts can be included when this package is published. Add '.pi-subagents/' to .npmignore, restrict package.json files, or set artifactDir to 'session' or 'temp'.\";\n}\n\nexport function getProjectSubagentsDir(cwd: string): string {\n\treturn path.join(cwd, PROJECT_ARTIFACT_ROOT);\n}\n\nexport function getProjectArtifactsDir(cwd: string): string {\n\treturn path.join(getProjectSubagentsDir(cwd), \"artifacts\");\n}\n\nexport function getProjectChainRunsDir(cwd: string): string {\n\treturn path.join(getProjectSubagentsDir(cwd), \"chain-runs\");\n}\n\nexport function getChainRunsDir(projectCwd: string, dirPreference: ArtifactDirPreference = \"project\"): string {\n\tswitch (dirPreference) {\n\t\tcase \"project\":\n\t\t\treturn getProjectChainRunsDir(projectCwd);\n\t\tcase \"session\":\n\t\tcase \"temp\":\n\t\t\treturn CHAIN_RUNS_DIR;\n\t\tdefault:\n\t\t\tthrow new Error(\n\t\t\t\t`Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected \"project\", \"session\", or \"temp\".`,\n\t\t\t);\n\t}\n}\n\nexport function getArtifactsDir(\n\tsessionFile: string | null,\n\tprojectCwd?: string,\n\tdirPreference: ArtifactDirPreference = \"project\",\n): string {\n\tswitch (dirPreference) {\n\t\tcase \"session\":\n\t\t\tif (sessionFile) {\n\t\t\t\tconst sessionDir = path.dirname(sessionFile);\n\t\t\t\treturn path.join(sessionDir, \"subagent-artifacts\");\n\t\t\t}\n\t\t\treturn TEMP_ARTIFACTS_DIR;\n\t\tcase \"temp\":\n\t\t\treturn TEMP_ARTIFACTS_DIR;\n\t\tcase \"project\":\n\t\t\tif (projectCwd) return getProjectArtifactsDir(projectCwd);\n\t\t\tif (sessionFile) {\n\t\t\t\tconst sessionDir = path.dirname(sessionFile);\n\t\t\t\treturn path.join(sessionDir, \"subagent-artifacts\");\n\t\t\t}\n\t\t\treturn TEMP_ARTIFACTS_DIR;\n\t\tdefault:\n\t\t\tthrow new Error(\n\t\t\t\t`Unsupported artifactDir ${JSON.stringify(dirPreference)}; expected \"project\", \"session\", or \"temp\".`,\n\t\t\t);\n\t}\n}\n\nexport function getArtifactPaths(artifactsDir: string, runId: string, agent: string, index?: number): ArtifactPaths {\n\tconst suffix = index !== undefined ? `_${index}` : \"\";\n\tconst safeAgent = agent.replace(/[^\\w.-]/g, \"_\");\n\tconst base = `${runId}_${safeAgent}${suffix}`;\n\treturn {\n\t\tinputPath: path.join(artifactsDir, `${base}_input.md`),\n\t\toutputPath: path.join(artifactsDir, `${base}_output.md`),\n\t\tjsonlPath: path.join(artifactsDir, `${base}.jsonl`),\n\t\ttranscriptPath: path.join(artifactsDir, `${base}_transcript.jsonl`),\n\t\tmetadataPath: path.join(artifactsDir, `${base}_meta.json`),\n\t};\n}\n\nexport function ensureArtifactsDir(dir: string): void {\n\tfs.mkdirSync(dir, { recursive: true });\n}\n\nexport function writeArtifact(filePath: string, content: string): void {\n\tfs.mkdirSync(path.dirname(filePath), { recursive: true });\n\tfs.writeFileSync(filePath, content, \"utf-8\");\n}\n\nexport function formatOutputArtifactContent(input: {\n\toutput: string;\n\terror?: string;\n\ttranscriptPath?: string;\n\tmetadataPath?: string;\n}): string {\n\tif (input.output.trim() || !input.error) return input.output;\n\tconst lines = [\"Subagent run failed before producing output.\", \"\", \"Error:\", input.error];\n\tif (input.transcriptPath) lines.push(\"\", `Transcript: ${input.transcriptPath}`);\n\tif (input.metadataPath) lines.push(`Metadata: ${input.metadataPath}`);\n\treturn lines.join(\"\\n\");\n}\n\nexport function writeMetadata(filePath: string, metadata: object): void {\n\tfs.mkdirSync(path.dirname(filePath), { recursive: true });\n\tfs.writeFileSync(filePath, JSON.stringify(metadata, null, 2), \"utf-8\");\n}\n\nexport function appendJsonl(filePath: string, line: string): void {\n\tfs.appendFileSync(filePath, `${line}\\n`);\n}\n\nexport function cleanupOldArtifacts(dir: string, maxAgeDays: number): void {\n\tif (!fs.existsSync(dir)) return;\n\n\tconst markerPath = path.join(dir, CLEANUP_MARKER_FILE);\n\tconst now = Date.now();\n\n\tif (fs.existsSync(markerPath)) {\n\t\tconst stat = fs.statSync(markerPath);\n\t\tif (now - stat.mtimeMs < 24 * 60 * 60 * 1000) return;\n\t}\n\n\tconst maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;\n\tconst cutoff = now - maxAgeMs;\n\n\tfor (const file of fs.readdirSync(dir)) {\n\t\tif (file === CLEANUP_MARKER_FILE) continue;\n\t\tconst filePath = path.join(dir, file);\n\t\ttry {\n\t\t\tconst stat = fs.statSync(filePath);\n\t\t\tif (stat.mtimeMs < cutoff) {\n\t\t\t\tfs.unlinkSync(filePath);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Artifact cleanup is best-effort housekeeping. Skip files that disappear\n\t\t\t// or become unreadable while scanning so one bad entry does not block the rest.\n\t\t}\n\t}\n\n\tfs.writeFileSync(markerPath, String(now));\n}\n\nexport function cleanupAllArtifactDirs(maxAgeDays: number): void {\n\tcleanupOldArtifacts(TEMP_ARTIFACTS_DIR, maxAgeDays);\n\n\tconst sessionsBase = path.join(getAgentDir(), \"sessions\");\n\tif (!fs.existsSync(sessionsBase)) return;\n\n\tlet dirs: string[];\n\ttry {\n\t\tdirs = fs.readdirSync(sessionsBase);\n\t} catch {\n\t\t// Session artifact cleanup is best-effort. If the sessions root cannot be read,\n\t\t// skip cleanup instead of failing extension startup.\n\t\treturn;\n\t}\n\n\tfor (const dir of dirs) {\n\t\tconst artifactsDir = path.join(sessionsBase, dir, \"subagent-artifacts\");\n\t\ttry {\n\t\t\tcleanupOldArtifacts(artifactsDir, maxAgeDays);\n\t\t} catch {\n\t\t\t// Session cleanup is best-effort. Keep going so one unreadable session dir\n\t\t\t// does not block cleanup for the rest.\n\t\t}\n\t}\n}\n"]}