{"version":3,"file":"managed-sources.d.ts","sourceRoot":"","sources":["../../src/bundle/managed-sources.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,KAAK,qBAAqB,EAI1B,KAAK,KAAK,EACV,MAAM,2BAA2B,CAAC;AAEnC,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,WAAW,uBAAuB;IACvC,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACxC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CACrC;AAiED,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAOzG;AAMD,wBAAsB,8BAA8B,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,uBAAuB,CAAC,CA+B7G;AA2BD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE;IACpD,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,cAAc,GAAG,qBAAqB,CAAC,CAAC;IAC3E,MAAM,EAAE,cAAc,CAAC;IACvB,SAAS,EAAE,uBAAuB,CAAC;CACnC,GAAG,wBAAwB,CAoE3B","sourcesContent":["import { constants, realpathSync } from \"node:fs\";\nimport { lstat, open, readFile, realpath } from \"node:fs/promises\";\nimport { join, relative, resolve, sep } from \"node:path\";\nimport {\n\ttype BeforeAgentStartEvent,\n\ttype BuildSystemPromptOptions,\n\tbuildSystemPrompt,\n\tloadSkills,\n\ttype Skill,\n} from \"@ch1nyzzz/pi-coding-agent\";\nimport { sha256 } from \"../storage.ts\";\nimport type { BundleManagedSource, CompiledBundle } from \"../types.ts\";\n\nexport interface ManagedRuntimeResources {\n\ttargetContents: ReadonlyMap<string, string>;\n\tskills: readonly Skill[];\n}\n\nexport interface ManagedPromptReplacement {\n\tsystemPrompt: string;\n\texcludedTargets: ReadonlySet<string>;\n}\n\nfunction errorCode(error: unknown): string | undefined {\n\tif (typeof error !== \"object\" || error === null || !(\"code\" in error)) return undefined;\n\treturn typeof error.code === \"string\" ? error.code : undefined;\n}\n\nfunction managedSourcePath(source: BundleManagedSource): string {\n\treturn join(source.sourceRoot, ...source.relativePath.split(\"/\"));\n}\n\nasync function readManagedSourceIfPresent(source: BundleManagedSource): Promise<string | undefined> {\n\tlet canonicalRoot: string;\n\ttry {\n\t\tcanonicalRoot = await realpath(source.sourceRoot);\n\t} catch (error) {\n\t\tif (errorCode(error) === \"ENOENT\" || errorCode(error) === \"ENOTDIR\") return undefined;\n\t\tthrow error;\n\t}\n\tif (canonicalRoot !== source.sourceRoot) {\n\t\tthrow new Error(`Evo-Pi managed source root is no longer canonical: ${source.sourceRoot}`);\n\t}\n\n\tconst path = managedSourcePath(source);\n\tlet pathStat: Awaited<ReturnType<typeof lstat>>;\n\ttry {\n\t\tpathStat = await lstat(path);\n\t} catch (error) {\n\t\tif (errorCode(error) === \"ENOENT\" || errorCode(error) === \"ENOTDIR\") return undefined;\n\t\tthrow error;\n\t}\n\tif (pathStat.isSymbolicLink() || !pathStat.isFile()) {\n\t\tthrow new Error(`Evo-Pi managed source is no longer a regular file: ${path}`);\n\t}\n\tif ((pathStat.mode & 0o111) !== 0) {\n\t\tthrow new Error(`Evo-Pi managed source became executable: ${path}`);\n\t}\n\tif ((await realpath(path)) !== path) {\n\t\tthrow new Error(`Evo-Pi managed source now traverses a symbolic link: ${path}`);\n\t}\n\n\tconst handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);\n\ttry {\n\t\tconst before = await handle.stat();\n\t\tif (!before.isFile() || before.dev !== pathStat.dev || before.ino !== pathStat.ino) {\n\t\t\tthrow new Error(`Evo-Pi managed source changed during validation: ${path}`);\n\t\t}\n\t\tconst bytes = await handle.readFile();\n\t\tconst after = await handle.stat();\n\t\tif (before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {\n\t\t\tthrow new Error(`Evo-Pi managed source changed while it was read: ${path}`);\n\t\t}\n\t\ttry {\n\t\t\treturn new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n\t\t} catch (error) {\n\t\t\tif (error instanceof TypeError) {\n\t\t\t\tthrow new Error(`Evo-Pi managed source is not valid UTF-8: ${path}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t} finally {\n\t\tawait handle.close();\n\t}\n}\n\nexport async function verifyManagedSourceSnapshots(sources: readonly BundleManagedSource[]): Promise<void> {\n\tfor (const source of sources) {\n\t\tconst content = await readManagedSourceIfPresent(source);\n\t\tif (content !== undefined && sha256(content) !== source.sourceSha256) {\n\t\t\tthrow new Error(`Evo-Pi managed source drifted outside the registry: ${managedSourcePath(source)}`);\n\t\t}\n\t}\n}\n\nfunction normalizeRelativePath(path: string): string {\n\treturn path.split(sep).join(\"/\");\n}\n\nexport async function prepareManagedRuntimeResources(bundle: CompiledBundle): Promise<ManagedRuntimeResources> {\n\tconst targetContents = new Map<string, string>();\n\tfor (const source of bundle.policy.managedSources ?? []) {\n\t\tif (targetContents.has(source.targetPath)) continue;\n\t\ttargetContents.set(source.targetPath, await readFile(join(bundle.directory, source.targetPath), \"utf8\"));\n\t}\n\n\tconst expectedSkillPaths = bundle.manifest.files\n\t\t.map((file) => file.path)\n\t\t.filter((path) => path.startsWith(\"skills/\"))\n\t\t.sort();\n\tif (expectedSkillPaths.length === 0) return { targetContents, skills: [] };\n\n\tconst skillDirectory = join(bundle.directory, \"skills\");\n\tconst loaded = loadSkills({\n\t\tcwd: bundle.directory,\n\t\tagentDir: bundle.directory,\n\t\tskillPaths: [skillDirectory],\n\t\tincludeDefaults: false,\n\t});\n\tif (loaded.diagnostics.length > 0) {\n\t\tconst detail = loaded.diagnostics.map((diagnostic) => `${diagnostic.path}: ${diagnostic.message}`).join(\"; \");\n\t\tthrow new Error(`Evo-Pi bundle contains invalid or ambiguous skills: ${detail}`);\n\t}\n\tconst actualSkillPaths = loaded.skills\n\t\t.map((skill) => normalizeRelativePath(relative(bundle.directory, resolve(skill.filePath))))\n\t\t.sort();\n\tif (actualSkillPaths.join(\"\\n\") !== expectedSkillPaths.join(\"\\n\")) {\n\t\tthrow new Error(\"Evo-Pi bundle skill discovery did not match its manifest\");\n\t}\n\treturn { targetContents, skills: loaded.skills };\n}\n\nfunction targetContent(resources: ManagedRuntimeResources, source: BundleManagedSource): string {\n\tconst content = resources.targetContents.get(source.targetPath);\n\tif (content === undefined) {\n\t\tthrow new Error(`Evo-Pi managed target was not prepared: ${source.targetPath}`);\n\t}\n\treturn content;\n}\n\nfunction pathMatchesSource(path: string, source: BundleManagedSource): boolean {\n\tconst expected = managedSourcePath(source);\n\tconst resolved = resolve(path);\n\tif (resolved === expected) return true;\n\ttry {\n\t\treturn realpathSync(resolved) === expected;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction replaceAssembledBase(current: string, original: string, replacement: string): string {\n\tconst index = current.indexOf(original);\n\tif (index === -1 || index !== current.lastIndexOf(original)) return replacement;\n\treturn current.slice(0, index) + replacement + current.slice(index + original.length);\n}\n\nexport function replaceManagedHostResources(options: {\n\tevent: Pick<BeforeAgentStartEvent, \"systemPrompt\" | \"systemPromptOptions\">;\n\tbundle: CompiledBundle;\n\tresources: ManagedRuntimeResources;\n}): ManagedPromptReplacement {\n\tconst sources = options.bundle.policy.managedSources ?? [];\n\tif (sources.length === 0) {\n\t\treturn { systemPrompt: options.event.systemPrompt, excludedTargets: new Set() };\n\t}\n\n\tconst excludedTargets = new Set<string>();\n\tconst originalOptions = options.event.systemPromptOptions;\n\tconst managedOptions: BuildSystemPromptOptions = { ...originalOptions };\n\tconst customPrompt = sources.find((source) => source.kind === \"custom-prompt\");\n\tif (\n\t\tcustomPrompt &&\n\t\t(originalOptions.customPrompt === undefined || sha256(originalOptions.customPrompt) === customPrompt.sourceSha256)\n\t) {\n\t\tmanagedOptions.customPrompt = targetContent(options.resources, customPrompt);\n\t\texcludedTargets.add(customPrompt.targetPath);\n\t}\n\tconst appendPrompt = sources.find((source) => source.kind === \"append-prompt\");\n\tif (\n\t\tappendPrompt &&\n\t\t(originalOptions.appendSystemPrompt === undefined ||\n\t\t\tsha256(originalOptions.appendSystemPrompt) === appendPrompt.sourceSha256)\n\t) {\n\t\tmanagedOptions.appendSystemPrompt = targetContent(options.resources, appendPrompt);\n\t\texcludedTargets.add(appendPrompt.targetPath);\n\t}\n\n\tconst contextSources = sources.filter((source) => source.kind === \"context\");\n\tconst usedContextTargets = new Set<string>();\n\tconst contextFiles = (originalOptions.contextFiles ?? []).map((file) => {\n\t\tconst source = contextSources.find((candidate) => pathMatchesSource(file.path, candidate));\n\t\tif (!source) return file;\n\t\tusedContextTargets.add(source.targetPath);\n\t\texcludedTargets.add(source.targetPath);\n\t\treturn {\n\t\t\tpath: join(options.bundle.directory, source.targetPath),\n\t\t\tcontent: targetContent(options.resources, source),\n\t\t};\n\t});\n\tfor (const source of contextSources) {\n\t\tif (usedContextTargets.has(source.targetPath)) continue;\n\t\texcludedTargets.add(source.targetPath);\n\t\tcontextFiles.push({\n\t\t\tpath: join(options.bundle.directory, source.targetPath),\n\t\t\tcontent: targetContent(options.resources, source),\n\t\t});\n\t}\n\tmanagedOptions.contextFiles = contextFiles;\n\n\tconst managedSkillSources = sources.filter((source) => source.kind === \"skill\");\n\tconst bundleSkillPaths = new Set(options.resources.skills.map((skill) => resolve(skill.filePath)));\n\tconst bundleSkillNames = new Set(options.resources.skills.map((skill) => skill.name));\n\tmanagedOptions.skills = [\n\t\t...(originalOptions.skills ?? []).filter(\n\t\t\t(skill) =>\n\t\t\t\t!managedSkillSources.some((source) => pathMatchesSource(skill.filePath, source)) &&\n\t\t\t\t!bundleSkillPaths.has(resolve(skill.filePath)) &&\n\t\t\t\t!bundleSkillNames.has(skill.name),\n\t\t),\n\t\t...options.resources.skills,\n\t];\n\n\tconst originalBase = buildSystemPrompt(originalOptions);\n\tconst managedBase = buildSystemPrompt(managedOptions);\n\treturn {\n\t\tsystemPrompt: replaceAssembledBase(options.event.systemPrompt, originalBase, managedBase),\n\t\texcludedTargets,\n\t};\n}\n"]}