@hoilab/ada-cli 0.84.19 → 0.84.21
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.
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +9 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/memory-engine/audit.d.ts +2 -1
- package/dist/core/memory-engine/audit.d.ts.map +1 -1
- package/dist/core/memory-engine/audit.js +2 -0
- package/dist/core/memory-engine/audit.js.map +1 -1
- package/dist/core/memory-engine/engine.d.ts +2 -0
- package/dist/core/memory-engine/engine.d.ts.map +1 -1
- package/dist/core/memory-engine/engine.js +5 -0
- package/dist/core/memory-engine/engine.js.map +1 -1
- package/dist/core/memory-engine/keychain.d.ts +18 -0
- package/dist/core/memory-engine/keychain.d.ts.map +1 -0
- package/dist/core/memory-engine/keychain.js +93 -0
- package/dist/core/memory-engine/keychain.js.map +1 -0
- package/dist/core/memory-engine/vault.d.ts +17 -2
- package/dist/core/memory-engine/vault.d.ts.map +1 -1
- package/dist/core/memory-engine/vault.js +57 -3
- package/dist/core/memory-engine/vault.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +1 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../../src/core/memory-engine/vault.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ada Secure Vault — encrypted secret storage.\n *\n * - Project vault: AES-256-GCM encrypted at rest under\n * <agentDir>/vault/<project-hash>/vault.json; the key is derived from the\n * user passphrase with scrypt (never stored).\n * - Session vault: per-conversation secrets kept ONLY in process memory\n * (never written to disk).\n * - Values are exposed to the LLM only through @vault:<name> expansion in\n * the user's prompt, and the persisted session file stores the redacted\n * form (the expansion is stripped before persistence).\n *\n * Zero native dependencies: node:crypto works on Node and Bun.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface VaultSecret {\n\tname: string;\n\tvalue: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n}\n\nexport interface VaultListEntry {\n\tname: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n\tsource: \"project\" | \"session\";\n}\n\nconst KDF_N = 32768;\nconst KDF_R = 8;\nconst KDF_P = 1;\nconst KEY_LENGTH = 32;\n\ninterface VaultFilePayload {\n\tversion: 1;\n\tkdf: \"scrypt\";\n\tN: number;\n\tr: number;\n\tp: number;\n\tsalt: string; // base64\n\tiv: string; // base64\n\ttag: string; // base64 (GCM auth tag)\n\tdata: string; // base64 (AES-256-GCM ciphertext of the JSON entries)\n}\n\nfunction deriveKey(passphrase: string, salt: Buffer, N = KDF_N, r = KDF_R, p = KDF_P): Buffer {\n\treturn scryptSync(passphrase, salt, KEY_LENGTH, { N, r, p, maxmem: 128 * 1024 * 1024 });\n}\n\nfunction encryptPayload(payload: VaultSecret[], key: Buffer): { iv: Buffer; tag: Buffer; data: Buffer } {\n\tconst iv = randomBytes(12);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst data = Buffer.concat([cipher.update(JSON.stringify(payload), \"utf-8\"), cipher.final()]);\n\tconst tag = cipher.getAuthTag();\n\treturn { iv, tag, data };\n}\n\nfunction decryptPayload(file: VaultFilePayload, key: Buffer): VaultSecret[] {\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, Buffer.from(file.iv, \"base64\"));\n\tdecipher.setAuthTag(Buffer.from(file.tag, \"base64\"));\n\tconst plain = Buffer.concat([\n\t\tdecipher.update(Buffer.from(file.data, \"base64\")),\n\t\tdecipher.final(), // throws on wrong passphrase (auth failure)\n\t]);\n\treturn JSON.parse(plain.toString(\"utf-8\")) as VaultSecret[];\n}\n\n/** Reference syntax in user prompts: @vault:<name>. */\nexport const VAULT_REF_PATTERN = /@vault:([A-Za-z0-9_.-]+)/g;\n\nexport interface VaultExpansion {\n\ttext: string;\n\t/** Names that were expanded (empty when none). */\n\texpanded: string[];\n\t/** Names referenced but missing (not expanded). */\n\tmissing: string[];\n}\n\n/**\n * Expand @vault:<name> references in a prompt. The returned text is what the\n * LLM receives; the ORIGINAL input (redacted) is what gets persisted.\n */\nexport function expandVaultRefs(text: string, vault: VaultManager | null): VaultExpansion {\n\tconst expanded: string[] = [];\n\tconst missing: string[] = [];\n\tif (!vault) {\n\t\tfor (const match of text.matchAll(VAULT_REF_PATTERN)) {\n\t\t\tmissing.push(match[1]);\n\t\t}\n\t\treturn { text, expanded, missing };\n\t}\n\tconst out = text.replace(VAULT_REF_PATTERN, (_full, name: string) => {\n\t\tconst value = vault.getSecret(name);\n\t\tif (value !== null) {\n\t\t\texpanded.push(name);\n\t\t\treturn value;\n\t\t}\n\t\tmissing.push(name);\n\t\treturn _full; // leave the reference intact so the user sees it failed\n\t});\n\treturn { text: out, expanded, missing };\n}\n\nexport class SessionVault {\n\tprivate entries = new Map<string, string>();\n\n\tadd(name: string, value: string): void {\n\t\tthis.entries.set(name, value);\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.entries.get(name) ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.entries.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.entries.has(name);\n\t}\n\n\tlist(): string[] {\n\t\treturn [...this.entries.keys()].sort();\n\t}\n\n\tclear(): void {\n\t\tthis.entries.clear();\n\t}\n}\n\nexport class ProjectVault {\n\tprivate secrets: Map<string, VaultSecret>;\n\tprivate key: Buffer;\n\n\tprivate constructor(key: Buffer, secrets: Map<string, VaultSecret>) {\n\t\tthis.key = key;\n\t\tthis.secrets = secrets;\n\t}\n\n\t/** Create a new empty project vault (writes the encrypted file). */\n\tstatic async create(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst salt = randomBytes(16);\n\t\tconst key = deriveKey(passphrase, salt);\n\t\tconst vault = new ProjectVault(key, new Map());\n\t\tawait vault.save(filePath, salt);\n\t\treturn vault;\n\t}\n\n\t/** Open an existing vault. Throws on wrong passphrase. */\n\tstatic async open(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\tconst file = JSON.parse(raw) as VaultFilePayload;\n\t\tconst key = deriveKey(passphrase, Buffer.from(file.salt, \"base64\"), file.N, file.r, file.p);\n\t\tconst secrets = decryptPayload(file, key);\n\t\treturn new ProjectVault(\n\t\t\tkey,\n\t\t\tnew Map(secrets.map((s) => [s.name, s])),\n\t\t);\n\t}\n\n\tadd(name: string, value: string, note?: string, category?: string): void {\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, { name, value, note, category, created: now, updated: now });\n\t}\n\n\tupdate(name: string, value: string, note?: string, category?: string): boolean {\n\t\tconst existing = this.secrets.get(name);\n\t\tif (!existing) return false;\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, {\n\t\t\t...existing,\n\t\t\tvalue,\n\t\t\tnote: note ?? existing.note,\n\t\t\tcategory: category ?? existing.category,\n\t\t\tupdated: now,\n\t\t});\n\t\treturn true;\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.secrets.get(name)?.value ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.secrets.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.secrets.has(name);\n\t}\n\n\tlist(): VaultListEntry[] {\n\t\treturn [...this.secrets.values()]\n\t\t\t.map((s) => ({\n\t\t\t\tname: s.name,\n\t\t\t\tnote: s.note,\n\t\t\t\tcategory: s.category,\n\t\t\t\tcreated: s.created,\n\t\t\t\tupdated: s.updated,\n\t\t\t\tsource: \"project\" as const,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tget entryCount(): number {\n\t\treturn this.secrets.size;\n\t}\n\n\tasync save(filePath: string, salt?: Buffer): Promise<void> {\n\t\tconst { iv, tag, data } = encryptPayload([...this.secrets.values()], this.key);\n\t\tconst payload: VaultFilePayload = {\n\t\t\tversion: 1,\n\t\t\tkdf: \"scrypt\",\n\t\t\tN: KDF_N,\n\t\t\tr: KDF_R,\n\t\t\tp: KDF_P,\n\t\t\tsalt: (salt ?? randomBytes(16)).toString(\"base64\"),\n\t\t\tiv: iv.toString(\"base64\"),\n\t\t\ttag: tag.toString(\"base64\"),\n\t\t\tdata: data.toString(\"base64\"),\n\t\t};\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tawait writeFile(filePath, JSON.stringify(payload, null, 1), \"utf-8\");\n\t}\n}\n\nexport class VaultManager {\n\tprivate projectVault: ProjectVault | null = null;\n\treadonly sessionVault = new SessionVault();\n\tprivate vaultDir: string;\n\tprivate projectHash: string;\n\n\tconstructor(agentDir: string, projectHash: string | null) {\n\t\tthis.vaultDir = join(agentDir, \"vault\");\n\t\tthis.projectHash = projectHash ?? \"global\";\n\t}\n\n\tget vaultPath(): string {\n\t\treturn join(this.vaultDir, this.projectHash, \"vault.json\");\n\t}\n\n\tget isUnlocked(): boolean {\n\t\treturn this.projectVault !== null;\n\t}\n\n\t/** True when a project vault file already exists. */\n\texists(): boolean {\n\t\treturn existsSync(this.vaultPath);\n\t}\n\n\t/**\n\t * Unlock (or create) the project vault with the user passphrase.\n\t * Returns false on wrong passphrase.\n\t */\n\tasync unlock(passphrase: string): Promise<boolean> {\n\t\ttry {\n\t\t\tif (this.exists()) {\n\t\t\t\tthis.projectVault = await ProjectVault.open(this.vaultPath, passphrase);\n\t\t\t} else {\n\t\t\t\tthis.projectVault = await ProjectVault.create(this.vaultPath, passphrase);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch {\n\t\t\tthis.projectVault = null;\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tlock(): void {\n\t\tthis.projectVault = null;\n\t}\n\n\t// ─── Project vault operations (require unlock) ───\n\n\tadd(name: string, value: string, note?: string, category?: string): boolean {\n\t\tif (!this.projectVault || !name.trim() || !value) return false;\n\t\tthis.projectVault.add(name.trim(), value, note, category);\n\t\tvoid this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn true;\n\t}\n\n\tupdate(name: string, value: string, note?: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.update(name, value, note);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn ok;\n\t}\n\n\tremove(name: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.remove(name);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn ok;\n\t}\n\n\t// ─── Lookup (project first, then session) ───\n\n\tgetSecret(name: string): string | null {\n\t\tconst fromProject = this.projectVault?.get(name) ?? null;\n\t\tif (fromProject !== null) return fromProject;\n\t\treturn this.sessionVault.get(name);\n\t}\n\n\t// ─── Session vault (memory only) ───\n\n\tsessionAdd(name: string, value: string): void {\n\t\tthis.sessionVault.add(name, value);\n\t}\n\n\tsessionRemove(name: string): boolean {\n\t\treturn this.sessionVault.remove(name);\n\t}\n\n\t// ─── Listing ───\n\n\tlist(): VaultListEntry[] {\n\t\tconst entries: VaultListEntry[] = [];\n\t\tif (this.projectVault) entries.push(...this.projectVault.list());\n\t\tfor (const name of this.sessionVault.list()) {\n\t\t\tentries.push({\n\t\t\t\tname,\n\t\t\t\tcreated: \"\",\n\t\t\t\tupdated: \"\",\n\t\t\t\tsource: \"session\",\n\t\t\t});\n\t\t}\n\t\treturn entries.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tstatus(): {\n\t\tunlocked: boolean;\n\t\tvaultPath: string;\n\t\texists: boolean;\n\t\tprojectCount: number;\n\t\tsessionCount: number;\n\t} {\n\t\treturn {\n\t\t\tunlocked: this.isUnlocked,\n\t\t\tvaultPath: this.vaultPath,\n\t\t\texists: this.exists(),\n\t\t\tprojectCount: this.projectVault?.entryCount ?? 0,\n\t\t\tsessionCount: this.sessionVault.list().length,\n\t\t};\n\t}\n}\n\nexport { existsSync };\n"]}
|
|
1
|
+
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../../src/core/memory-engine/vault.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ada Secure Vault — encrypted secret storage.\n *\n * - Project vault: AES-256-GCM encrypted at rest under\n * <agentDir>/vault/<project-hash>/vault.json; the key is derived from the\n * user passphrase with scrypt (never stored).\n * - Session vault: per-conversation secrets kept ONLY in process memory\n * (never written to disk).\n * - Values are exposed to the LLM only through @vault:<name> expansion in\n * the user's prompt, and the persisted session file stores the redacted\n * form (the expansion is stripped before persistence).\n *\n * Zero native dependencies: node:crypto works on Node and Bun.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport {\n\tisKeychainAvailable,\n\tkeychainHasPassphrase,\n\tkeychainLoadPassphrase,\n\tkeychainSavePassphrase,\n\tkeychainClearPassphrase,\n} from \"./keychain.ts\";\nimport type { AuditEntry } from \"./audit.ts\";\n\nexport interface VaultSecret {\n\tname: string;\n\tvalue: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n}\n\nexport interface VaultListEntry {\n\tname: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n\tsource: \"project\" | \"session\";\n}\n\nconst KDF_N = 32768;\nconst KDF_R = 8;\nconst KDF_P = 1;\nconst KEY_LENGTH = 32;\n\ninterface VaultFilePayload {\n\tversion: 1;\n\tkdf: \"scrypt\";\n\tN: number;\n\tr: number;\n\tp: number;\n\tsalt: string; // base64\n\tiv: string; // base64\n\ttag: string; // base64 (GCM auth tag)\n\tdata: string; // base64 (AES-256-GCM ciphertext of the JSON entries)\n}\n\nfunction deriveKey(passphrase: string, salt: Buffer, N = KDF_N, r = KDF_R, p = KDF_P): Buffer {\n\treturn scryptSync(passphrase, salt, KEY_LENGTH, { N, r, p, maxmem: 128 * 1024 * 1024 });\n}\n\nfunction encryptPayload(payload: VaultSecret[], key: Buffer): { iv: Buffer; tag: Buffer; data: Buffer } {\n\tconst iv = randomBytes(12);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst data = Buffer.concat([cipher.update(JSON.stringify(payload), \"utf-8\"), cipher.final()]);\n\tconst tag = cipher.getAuthTag();\n\treturn { iv, tag, data };\n}\n\nfunction decryptPayload(file: VaultFilePayload, key: Buffer): VaultSecret[] {\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, Buffer.from(file.iv, \"base64\"));\n\tdecipher.setAuthTag(Buffer.from(file.tag, \"base64\"));\n\tconst plain = Buffer.concat([\n\t\tdecipher.update(Buffer.from(file.data, \"base64\")),\n\t\tdecipher.final(), // throws on wrong passphrase (auth failure)\n\t]);\n\treturn JSON.parse(plain.toString(\"utf-8\")) as VaultSecret[];\n}\n\n/** Reference syntax in user prompts: @vault:<name>. */\nexport const VAULT_REF_PATTERN = /@vault:([A-Za-z0-9_.-]+)/g;\n\nexport interface VaultExpansion {\n\ttext: string;\n\t/** Names that were expanded (empty when none). */\n\texpanded: string[];\n\t/** Names referenced but missing (not expanded). */\n\tmissing: string[];\n}\n\n/**\n * Expand @vault:<name> references in a prompt. The returned text is what the\n * LLM receives; the ORIGINAL input (redacted) is what gets persisted.\n */\nexport function expandVaultRefs(text: string, vault: VaultManager | null): VaultExpansion {\n\tconst expanded: string[] = [];\n\tconst missing: string[] = [];\n\tif (!vault) {\n\t\tfor (const match of text.matchAll(VAULT_REF_PATTERN)) {\n\t\t\tmissing.push(match[1]);\n\t\t}\n\t\treturn { text, expanded, missing };\n\t}\n\tconst out = text.replace(VAULT_REF_PATTERN, (_full, name: string) => {\n\t\tconst value = vault.getSecret(name);\n\t\tif (value !== null) {\n\t\t\texpanded.push(name);\n\t\t\treturn value;\n\t\t}\n\t\tmissing.push(name);\n\t\treturn _full; // leave the reference intact so the user sees it failed\n\t});\n\treturn { text: out, expanded, missing };\n}\n\nexport class SessionVault {\n\tprivate entries = new Map<string, string>();\n\n\tadd(name: string, value: string): void {\n\t\tthis.entries.set(name, value);\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.entries.get(name) ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.entries.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.entries.has(name);\n\t}\n\n\tlist(): string[] {\n\t\treturn [...this.entries.keys()].sort();\n\t}\n\n\tclear(): void {\n\t\tthis.entries.clear();\n\t}\n}\n\nexport class ProjectVault {\n\tprivate secrets: Map<string, VaultSecret>;\n\tprivate key: Buffer;\n\n\tprivate constructor(key: Buffer, secrets: Map<string, VaultSecret>) {\n\t\tthis.key = key;\n\t\tthis.secrets = secrets;\n\t}\n\n\t/** Create a new empty project vault (writes the encrypted file). */\n\tstatic async create(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst salt = randomBytes(16);\n\t\tconst key = deriveKey(passphrase, salt);\n\t\tconst vault = new ProjectVault(key, new Map());\n\t\tawait vault.save(filePath, salt);\n\t\treturn vault;\n\t}\n\n\t/** Open an existing vault. Throws on wrong passphrase. */\n\tstatic async open(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\tconst file = JSON.parse(raw) as VaultFilePayload;\n\t\tconst key = deriveKey(passphrase, Buffer.from(file.salt, \"base64\"), file.N, file.r, file.p);\n\t\tconst secrets = decryptPayload(file, key);\n\t\treturn new ProjectVault(\n\t\t\tkey,\n\t\t\tnew Map(secrets.map((s) => [s.name, s])),\n\t\t);\n\t}\n\n\tadd(name: string, value: string, note?: string, category?: string): void {\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, { name, value, note, category, created: now, updated: now });\n\t}\n\n\tupdate(name: string, value: string, note?: string, category?: string): boolean {\n\t\tconst existing = this.secrets.get(name);\n\t\tif (!existing) return false;\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, {\n\t\t\t...existing,\n\t\t\tvalue,\n\t\t\tnote: note ?? existing.note,\n\t\t\tcategory: category ?? existing.category,\n\t\t\tupdated: now,\n\t\t});\n\t\treturn true;\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.secrets.get(name)?.value ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.secrets.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.secrets.has(name);\n\t}\n\n\tlist(): VaultListEntry[] {\n\t\treturn [...this.secrets.values()]\n\t\t\t.map((s) => ({\n\t\t\t\tname: s.name,\n\t\t\t\tnote: s.note,\n\t\t\t\tcategory: s.category,\n\t\t\t\tcreated: s.created,\n\t\t\t\tupdated: s.updated,\n\t\t\t\tsource: \"project\" as const,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tget entryCount(): number {\n\t\treturn this.secrets.size;\n\t}\n\n\tasync save(filePath: string, salt?: Buffer): Promise<void> {\n\t\tconst { iv, tag, data } = encryptPayload([...this.secrets.values()], this.key);\n\t\tconst payload: VaultFilePayload = {\n\t\t\tversion: 1,\n\t\t\tkdf: \"scrypt\",\n\t\t\tN: KDF_N,\n\t\t\tr: KDF_R,\n\t\t\tp: KDF_P,\n\t\t\tsalt: (salt ?? randomBytes(16)).toString(\"base64\"),\n\t\t\tiv: iv.toString(\"base64\"),\n\t\t\ttag: tag.toString(\"base64\"),\n\t\t\tdata: data.toString(\"base64\"),\n\t\t};\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tawait writeFile(filePath, JSON.stringify(payload, null, 1), \"utf-8\");\n\t}\n}\n\nexport class VaultManager {\n\tprivate projectVault: ProjectVault | null = null;\n\treadonly sessionVault = new SessionVault();\n\tprivate vaultDir: string;\n\tprivate projectHash: string;\n\tprivate auditFn: ((entry: Omit<AuditEntry, \"seq\" | \"ts\" | \"hash\" | \"prevHash\">) => void) | null = null;\n\n\tconstructor(agentDir: string, projectHash: string | null) {\n\t\tthis.vaultDir = join(agentDir, \"vault\");\n\t\tthis.projectHash = projectHash ?? \"global\";\n\t}\n\n\t/** Attach the audit logger (enterprise accountability, never the value). */\n\tsetAudit(fn: ((entry: Omit<AuditEntry, \"seq\" | \"ts\" | \"hash\" | \"prevHash\">) => void) | null): void {\n\t\tthis.auditFn = fn;\n\t}\n\n\tprivate audit(action: AuditEntry[\"action\"], ok: boolean, reason?: string): void {\n\t\tthis.auditFn?.({\n\t\t\taction,\n\t\t\tsource: \"user\",\n\t\t\ttarget: \"vault\",\n\t\t\tscope: this.projectHash,\n\t\t\tok,\n\t\t\treason,\n\t\t});\n\t}\n\n\tget keychainAvailable(): boolean {\n\t\treturn isKeychainAvailable();\n\t}\n\n\tget vaultPath(): string {\n\t\treturn join(this.vaultDir, this.projectHash, \"vault.json\");\n\t}\n\n\tget isUnlocked(): boolean {\n\t\treturn this.projectVault !== null;\n\t}\n\n\t/** True when a project vault file already exists. */\n\texists(): boolean {\n\t\treturn existsSync(this.vaultPath);\n\t}\n\n\t/**\n\t * Unlock (or create) the project vault with the user passphrase.\n\t * Returns false on wrong passphrase. When `remember` is set and the\n\t * Keychain is available, the passphrase is stored there (macOS).\n\t */\n\tasync unlock(passphrase: string, options: { remember?: boolean } = {}): Promise<boolean> {\n\t\ttry {\n\t\t\tif (this.exists()) {\n\t\t\t\tthis.projectVault = await ProjectVault.open(this.vaultPath, passphrase);\n\t\t\t} else {\n\t\t\t\tthis.projectVault = await ProjectVault.create(this.vaultPath, passphrase);\n\t\t\t}\n\t\t\tif (options.remember && isKeychainAvailable()) {\n\t\t\t\tconst saved = await keychainSavePassphrase(this.projectHash, passphrase);\n\t\t\t\tthis.audit(\"add\", saved, saved ? \"keychain\" : \"keychain save failed\");\n\t\t\t}\n\t\t\tthis.audit(\"add\", true, this.exists() ? \"unlock\" : \"create\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\tthis.projectVault = null;\n\t\t\tthis.audit(\"rejected\", false, \"wrong passphrase or corrupted vault\");\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Try to unlock using the passphrase stored in the Keychain (macOS). */\n\tasync unlockFromKeychain(): Promise<boolean> {\n\t\tif (!isKeychainAvailable()) return false;\n\t\tconst passphrase = await keychainLoadPassphrase(this.projectHash);\n\t\tif (!passphrase) return false;\n\t\treturn this.unlock(passphrase, { remember: false });\n\t}\n\n\t/** True when a passphrase for this vault is stored in the Keychain. */\n\tasync keychainRemembered(): Promise<boolean> {\n\t\tif (!isKeychainAvailable()) return false;\n\t\treturn keychainHasPassphrase(this.projectHash);\n\t}\n\n\t/** Forget the Keychain-stored passphrase. */\n\tasync keychainForget(): Promise<boolean> {\n\t\tif (!isKeychainAvailable()) return false;\n\t\tconst ok = await keychainClearPassphrase(this.projectHash);\n\t\tthis.audit(\"remove\", ok, \"keychain forget\");\n\t\treturn ok;\n\t}\n\n\tlock(): void {\n\t\tthis.projectVault = null;\n\t}\n\n\t// ─── Project vault operations (require unlock) ───\n\n\tadd(name: string, value: string, note?: string, category?: string): boolean {\n\t\tif (!this.projectVault || !name.trim() || !value) return false;\n\t\tthis.projectVault.add(name.trim(), value, note, category);\n\t\tvoid this.projectVault.save(this.vaultPath).catch(() => {});\n\t\tthis.audit(\"add\", true, name);\n\t\treturn true;\n\t}\n\n\tupdate(name: string, value: string, note?: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.update(name, value, note);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn ok;\n\t}\n\n\tremove(name: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.remove(name);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\tthis.audit(\"remove\", ok, name);\n\t\treturn ok;\n\t}\n\n\t// ─── Lookup (project first, then session) ───\n\n\tgetSecret(name: string): string | null {\n\t\tconst fromProject = this.projectVault?.get(name) ?? null;\n\t\tif (fromProject !== null) return fromProject;\n\t\treturn this.sessionVault.get(name);\n\t}\n\n\t// ─── Session vault (memory only) ───\n\n\tsessionAdd(name: string, value: string): void {\n\t\tthis.sessionVault.add(name, value);\n\t\tthis.audit(\"add\", true, `session:${name}`);\n\t}\n\n\tsessionRemove(name: string): boolean {\n\t\tconst ok = this.sessionVault.remove(name);\n\t\tthis.audit(\"remove\", ok, `session:${name}`);\n\t\treturn ok;\n\t}\n\n\t// ─── Listing ───\n\n\tlist(): VaultListEntry[] {\n\t\tconst entries: VaultListEntry[] = [];\n\t\tif (this.projectVault) entries.push(...this.projectVault.list());\n\t\tfor (const name of this.sessionVault.list()) {\n\t\t\tentries.push({\n\t\t\t\tname,\n\t\t\t\tcreated: \"\",\n\t\t\t\tupdated: \"\",\n\t\t\t\tsource: \"session\",\n\t\t\t});\n\t\t}\n\t\treturn entries.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tstatus(): {\n\t\tunlocked: boolean;\n\t\tvaultPath: string;\n\t\texists: boolean;\n\t\tprojectCount: number;\n\t\tsessionCount: number;\n\t} {\n\t\treturn {\n\t\t\tunlocked: this.isUnlocked,\n\t\t\tvaultPath: this.vaultPath,\n\t\t\texists: this.exists(),\n\t\t\tprojectCount: this.projectVault?.entryCount ?? 0,\n\t\t\tsessionCount: this.sessionVault.list().length,\n\t\t};\n\t}\n}\n\nexport { existsSync };\n"]}
|
|
@@ -16,6 +16,7 @@ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:
|
|
|
16
16
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
17
17
|
import { existsSync } from "node:fs";
|
|
18
18
|
import { dirname, join } from "node:path";
|
|
19
|
+
import { isKeychainAvailable, keychainHasPassphrase, keychainLoadPassphrase, keychainSavePassphrase, keychainClearPassphrase, } from "./keychain.js";
|
|
19
20
|
const KDF_N = 32768;
|
|
20
21
|
const KDF_R = 8;
|
|
21
22
|
const KDF_P = 1;
|
|
@@ -173,10 +174,28 @@ export class VaultManager {
|
|
|
173
174
|
sessionVault = new SessionVault();
|
|
174
175
|
vaultDir;
|
|
175
176
|
projectHash;
|
|
177
|
+
auditFn = null;
|
|
176
178
|
constructor(agentDir, projectHash) {
|
|
177
179
|
this.vaultDir = join(agentDir, "vault");
|
|
178
180
|
this.projectHash = projectHash ?? "global";
|
|
179
181
|
}
|
|
182
|
+
/** Attach the audit logger (enterprise accountability, never the value). */
|
|
183
|
+
setAudit(fn) {
|
|
184
|
+
this.auditFn = fn;
|
|
185
|
+
}
|
|
186
|
+
audit(action, ok, reason) {
|
|
187
|
+
this.auditFn?.({
|
|
188
|
+
action,
|
|
189
|
+
source: "user",
|
|
190
|
+
target: "vault",
|
|
191
|
+
scope: this.projectHash,
|
|
192
|
+
ok,
|
|
193
|
+
reason,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
get keychainAvailable() {
|
|
197
|
+
return isKeychainAvailable();
|
|
198
|
+
}
|
|
180
199
|
get vaultPath() {
|
|
181
200
|
return join(this.vaultDir, this.projectHash, "vault.json");
|
|
182
201
|
}
|
|
@@ -189,9 +208,10 @@ export class VaultManager {
|
|
|
189
208
|
}
|
|
190
209
|
/**
|
|
191
210
|
* Unlock (or create) the project vault with the user passphrase.
|
|
192
|
-
* Returns false on wrong passphrase.
|
|
211
|
+
* Returns false on wrong passphrase. When `remember` is set and the
|
|
212
|
+
* Keychain is available, the passphrase is stored there (macOS).
|
|
193
213
|
*/
|
|
194
|
-
async unlock(passphrase) {
|
|
214
|
+
async unlock(passphrase, options = {}) {
|
|
195
215
|
try {
|
|
196
216
|
if (this.exists()) {
|
|
197
217
|
this.projectVault = await ProjectVault.open(this.vaultPath, passphrase);
|
|
@@ -199,13 +219,42 @@ export class VaultManager {
|
|
|
199
219
|
else {
|
|
200
220
|
this.projectVault = await ProjectVault.create(this.vaultPath, passphrase);
|
|
201
221
|
}
|
|
222
|
+
if (options.remember && isKeychainAvailable()) {
|
|
223
|
+
const saved = await keychainSavePassphrase(this.projectHash, passphrase);
|
|
224
|
+
this.audit("add", saved, saved ? "keychain" : "keychain save failed");
|
|
225
|
+
}
|
|
226
|
+
this.audit("add", true, this.exists() ? "unlock" : "create");
|
|
202
227
|
return true;
|
|
203
228
|
}
|
|
204
229
|
catch {
|
|
205
230
|
this.projectVault = null;
|
|
231
|
+
this.audit("rejected", false, "wrong passphrase or corrupted vault");
|
|
206
232
|
return false;
|
|
207
233
|
}
|
|
208
234
|
}
|
|
235
|
+
/** Try to unlock using the passphrase stored in the Keychain (macOS). */
|
|
236
|
+
async unlockFromKeychain() {
|
|
237
|
+
if (!isKeychainAvailable())
|
|
238
|
+
return false;
|
|
239
|
+
const passphrase = await keychainLoadPassphrase(this.projectHash);
|
|
240
|
+
if (!passphrase)
|
|
241
|
+
return false;
|
|
242
|
+
return this.unlock(passphrase, { remember: false });
|
|
243
|
+
}
|
|
244
|
+
/** True when a passphrase for this vault is stored in the Keychain. */
|
|
245
|
+
async keychainRemembered() {
|
|
246
|
+
if (!isKeychainAvailable())
|
|
247
|
+
return false;
|
|
248
|
+
return keychainHasPassphrase(this.projectHash);
|
|
249
|
+
}
|
|
250
|
+
/** Forget the Keychain-stored passphrase. */
|
|
251
|
+
async keychainForget() {
|
|
252
|
+
if (!isKeychainAvailable())
|
|
253
|
+
return false;
|
|
254
|
+
const ok = await keychainClearPassphrase(this.projectHash);
|
|
255
|
+
this.audit("remove", ok, "keychain forget");
|
|
256
|
+
return ok;
|
|
257
|
+
}
|
|
209
258
|
lock() {
|
|
210
259
|
this.projectVault = null;
|
|
211
260
|
}
|
|
@@ -215,6 +264,7 @@ export class VaultManager {
|
|
|
215
264
|
return false;
|
|
216
265
|
this.projectVault.add(name.trim(), value, note, category);
|
|
217
266
|
void this.projectVault.save(this.vaultPath).catch(() => { });
|
|
267
|
+
this.audit("add", true, name);
|
|
218
268
|
return true;
|
|
219
269
|
}
|
|
220
270
|
update(name, value, note) {
|
|
@@ -231,6 +281,7 @@ export class VaultManager {
|
|
|
231
281
|
const ok = this.projectVault.remove(name);
|
|
232
282
|
if (ok)
|
|
233
283
|
void this.projectVault.save(this.vaultPath).catch(() => { });
|
|
284
|
+
this.audit("remove", ok, name);
|
|
234
285
|
return ok;
|
|
235
286
|
}
|
|
236
287
|
// ─── Lookup (project first, then session) ───
|
|
@@ -243,9 +294,12 @@ export class VaultManager {
|
|
|
243
294
|
// ─── Session vault (memory only) ───
|
|
244
295
|
sessionAdd(name, value) {
|
|
245
296
|
this.sessionVault.add(name, value);
|
|
297
|
+
this.audit("add", true, `session:${name}`);
|
|
246
298
|
}
|
|
247
299
|
sessionRemove(name) {
|
|
248
|
-
|
|
300
|
+
const ok = this.sessionVault.remove(name);
|
|
301
|
+
this.audit("remove", ok, `session:${name}`);
|
|
302
|
+
return ok;
|
|
249
303
|
}
|
|
250
304
|
// ─── Listing ───
|
|
251
305
|
list() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vault.js","sourceRoot":"","sources":["../../../src/core/memory-engine/vault.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ada Secure Vault — encrypted secret storage.\n *\n * - Project vault: AES-256-GCM encrypted at rest under\n * <agentDir>/vault/<project-hash>/vault.json; the key is derived from the\n * user passphrase with scrypt (never stored).\n * - Session vault: per-conversation secrets kept ONLY in process memory\n * (never written to disk).\n * - Values are exposed to the LLM only through @vault:<name> expansion in\n * the user's prompt, and the persisted session file stores the redacted\n * form (the expansion is stripped before persistence).\n *\n * Zero native dependencies: node:crypto works on Node and Bun.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface VaultSecret {\n\tname: string;\n\tvalue: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n}\n\nexport interface VaultListEntry {\n\tname: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n\tsource: \"project\" | \"session\";\n}\n\nconst KDF_N = 32768;\nconst KDF_R = 8;\nconst KDF_P = 1;\nconst KEY_LENGTH = 32;\n\ninterface VaultFilePayload {\n\tversion: 1;\n\tkdf: \"scrypt\";\n\tN: number;\n\tr: number;\n\tp: number;\n\tsalt: string; // base64\n\tiv: string; // base64\n\ttag: string; // base64 (GCM auth tag)\n\tdata: string; // base64 (AES-256-GCM ciphertext of the JSON entries)\n}\n\nfunction deriveKey(passphrase: string, salt: Buffer, N = KDF_N, r = KDF_R, p = KDF_P): Buffer {\n\treturn scryptSync(passphrase, salt, KEY_LENGTH, { N, r, p, maxmem: 128 * 1024 * 1024 });\n}\n\nfunction encryptPayload(payload: VaultSecret[], key: Buffer): { iv: Buffer; tag: Buffer; data: Buffer } {\n\tconst iv = randomBytes(12);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst data = Buffer.concat([cipher.update(JSON.stringify(payload), \"utf-8\"), cipher.final()]);\n\tconst tag = cipher.getAuthTag();\n\treturn { iv, tag, data };\n}\n\nfunction decryptPayload(file: VaultFilePayload, key: Buffer): VaultSecret[] {\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, Buffer.from(file.iv, \"base64\"));\n\tdecipher.setAuthTag(Buffer.from(file.tag, \"base64\"));\n\tconst plain = Buffer.concat([\n\t\tdecipher.update(Buffer.from(file.data, \"base64\")),\n\t\tdecipher.final(), // throws on wrong passphrase (auth failure)\n\t]);\n\treturn JSON.parse(plain.toString(\"utf-8\")) as VaultSecret[];\n}\n\n/** Reference syntax in user prompts: @vault:<name>. */\nexport const VAULT_REF_PATTERN = /@vault:([A-Za-z0-9_.-]+)/g;\n\nexport interface VaultExpansion {\n\ttext: string;\n\t/** Names that were expanded (empty when none). */\n\texpanded: string[];\n\t/** Names referenced but missing (not expanded). */\n\tmissing: string[];\n}\n\n/**\n * Expand @vault:<name> references in a prompt. The returned text is what the\n * LLM receives; the ORIGINAL input (redacted) is what gets persisted.\n */\nexport function expandVaultRefs(text: string, vault: VaultManager | null): VaultExpansion {\n\tconst expanded: string[] = [];\n\tconst missing: string[] = [];\n\tif (!vault) {\n\t\tfor (const match of text.matchAll(VAULT_REF_PATTERN)) {\n\t\t\tmissing.push(match[1]);\n\t\t}\n\t\treturn { text, expanded, missing };\n\t}\n\tconst out = text.replace(VAULT_REF_PATTERN, (_full, name: string) => {\n\t\tconst value = vault.getSecret(name);\n\t\tif (value !== null) {\n\t\t\texpanded.push(name);\n\t\t\treturn value;\n\t\t}\n\t\tmissing.push(name);\n\t\treturn _full; // leave the reference intact so the user sees it failed\n\t});\n\treturn { text: out, expanded, missing };\n}\n\nexport class SessionVault {\n\tprivate entries = new Map<string, string>();\n\n\tadd(name: string, value: string): void {\n\t\tthis.entries.set(name, value);\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.entries.get(name) ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.entries.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.entries.has(name);\n\t}\n\n\tlist(): string[] {\n\t\treturn [...this.entries.keys()].sort();\n\t}\n\n\tclear(): void {\n\t\tthis.entries.clear();\n\t}\n}\n\nexport class ProjectVault {\n\tprivate secrets: Map<string, VaultSecret>;\n\tprivate key: Buffer;\n\n\tprivate constructor(key: Buffer, secrets: Map<string, VaultSecret>) {\n\t\tthis.key = key;\n\t\tthis.secrets = secrets;\n\t}\n\n\t/** Create a new empty project vault (writes the encrypted file). */\n\tstatic async create(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst salt = randomBytes(16);\n\t\tconst key = deriveKey(passphrase, salt);\n\t\tconst vault = new ProjectVault(key, new Map());\n\t\tawait vault.save(filePath, salt);\n\t\treturn vault;\n\t}\n\n\t/** Open an existing vault. Throws on wrong passphrase. */\n\tstatic async open(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\tconst file = JSON.parse(raw) as VaultFilePayload;\n\t\tconst key = deriveKey(passphrase, Buffer.from(file.salt, \"base64\"), file.N, file.r, file.p);\n\t\tconst secrets = decryptPayload(file, key);\n\t\treturn new ProjectVault(\n\t\t\tkey,\n\t\t\tnew Map(secrets.map((s) => [s.name, s])),\n\t\t);\n\t}\n\n\tadd(name: string, value: string, note?: string, category?: string): void {\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, { name, value, note, category, created: now, updated: now });\n\t}\n\n\tupdate(name: string, value: string, note?: string, category?: string): boolean {\n\t\tconst existing = this.secrets.get(name);\n\t\tif (!existing) return false;\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, {\n\t\t\t...existing,\n\t\t\tvalue,\n\t\t\tnote: note ?? existing.note,\n\t\t\tcategory: category ?? existing.category,\n\t\t\tupdated: now,\n\t\t});\n\t\treturn true;\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.secrets.get(name)?.value ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.secrets.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.secrets.has(name);\n\t}\n\n\tlist(): VaultListEntry[] {\n\t\treturn [...this.secrets.values()]\n\t\t\t.map((s) => ({\n\t\t\t\tname: s.name,\n\t\t\t\tnote: s.note,\n\t\t\t\tcategory: s.category,\n\t\t\t\tcreated: s.created,\n\t\t\t\tupdated: s.updated,\n\t\t\t\tsource: \"project\" as const,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tget entryCount(): number {\n\t\treturn this.secrets.size;\n\t}\n\n\tasync save(filePath: string, salt?: Buffer): Promise<void> {\n\t\tconst { iv, tag, data } = encryptPayload([...this.secrets.values()], this.key);\n\t\tconst payload: VaultFilePayload = {\n\t\t\tversion: 1,\n\t\t\tkdf: \"scrypt\",\n\t\t\tN: KDF_N,\n\t\t\tr: KDF_R,\n\t\t\tp: KDF_P,\n\t\t\tsalt: (salt ?? randomBytes(16)).toString(\"base64\"),\n\t\t\tiv: iv.toString(\"base64\"),\n\t\t\ttag: tag.toString(\"base64\"),\n\t\t\tdata: data.toString(\"base64\"),\n\t\t};\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tawait writeFile(filePath, JSON.stringify(payload, null, 1), \"utf-8\");\n\t}\n}\n\nexport class VaultManager {\n\tprivate projectVault: ProjectVault | null = null;\n\treadonly sessionVault = new SessionVault();\n\tprivate vaultDir: string;\n\tprivate projectHash: string;\n\n\tconstructor(agentDir: string, projectHash: string | null) {\n\t\tthis.vaultDir = join(agentDir, \"vault\");\n\t\tthis.projectHash = projectHash ?? \"global\";\n\t}\n\n\tget vaultPath(): string {\n\t\treturn join(this.vaultDir, this.projectHash, \"vault.json\");\n\t}\n\n\tget isUnlocked(): boolean {\n\t\treturn this.projectVault !== null;\n\t}\n\n\t/** True when a project vault file already exists. */\n\texists(): boolean {\n\t\treturn existsSync(this.vaultPath);\n\t}\n\n\t/**\n\t * Unlock (or create) the project vault with the user passphrase.\n\t * Returns false on wrong passphrase.\n\t */\n\tasync unlock(passphrase: string): Promise<boolean> {\n\t\ttry {\n\t\t\tif (this.exists()) {\n\t\t\t\tthis.projectVault = await ProjectVault.open(this.vaultPath, passphrase);\n\t\t\t} else {\n\t\t\t\tthis.projectVault = await ProjectVault.create(this.vaultPath, passphrase);\n\t\t\t}\n\t\t\treturn true;\n\t\t} catch {\n\t\t\tthis.projectVault = null;\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tlock(): void {\n\t\tthis.projectVault = null;\n\t}\n\n\t// ─── Project vault operations (require unlock) ───\n\n\tadd(name: string, value: string, note?: string, category?: string): boolean {\n\t\tif (!this.projectVault || !name.trim() || !value) return false;\n\t\tthis.projectVault.add(name.trim(), value, note, category);\n\t\tvoid this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn true;\n\t}\n\n\tupdate(name: string, value: string, note?: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.update(name, value, note);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn ok;\n\t}\n\n\tremove(name: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.remove(name);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn ok;\n\t}\n\n\t// ─── Lookup (project first, then session) ───\n\n\tgetSecret(name: string): string | null {\n\t\tconst fromProject = this.projectVault?.get(name) ?? null;\n\t\tif (fromProject !== null) return fromProject;\n\t\treturn this.sessionVault.get(name);\n\t}\n\n\t// ─── Session vault (memory only) ───\n\n\tsessionAdd(name: string, value: string): void {\n\t\tthis.sessionVault.add(name, value);\n\t}\n\n\tsessionRemove(name: string): boolean {\n\t\treturn this.sessionVault.remove(name);\n\t}\n\n\t// ─── Listing ───\n\n\tlist(): VaultListEntry[] {\n\t\tconst entries: VaultListEntry[] = [];\n\t\tif (this.projectVault) entries.push(...this.projectVault.list());\n\t\tfor (const name of this.sessionVault.list()) {\n\t\t\tentries.push({\n\t\t\t\tname,\n\t\t\t\tcreated: \"\",\n\t\t\t\tupdated: \"\",\n\t\t\t\tsource: \"session\",\n\t\t\t});\n\t\t}\n\t\treturn entries.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tstatus(): {\n\t\tunlocked: boolean;\n\t\tvaultPath: string;\n\t\texists: boolean;\n\t\tprojectCount: number;\n\t\tsessionCount: number;\n\t} {\n\t\treturn {\n\t\t\tunlocked: this.isUnlocked,\n\t\t\tvaultPath: this.vaultPath,\n\t\t\texists: this.exists(),\n\t\t\tprojectCount: this.projectVault?.entryCount ?? 0,\n\t\t\tsessionCount: this.sessionVault.list().length,\n\t\t};\n\t}\n}\n\nexport { existsSync };\n"]}
|
|
1
|
+
{"version":3,"file":"vault.js","sourceRoot":"","sources":["../../../src/core/memory-engine/vault.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Ada Secure Vault — encrypted secret storage.\n *\n * - Project vault: AES-256-GCM encrypted at rest under\n * <agentDir>/vault/<project-hash>/vault.json; the key is derived from the\n * user passphrase with scrypt (never stored).\n * - Session vault: per-conversation secrets kept ONLY in process memory\n * (never written to disk).\n * - Values are exposed to the LLM only through @vault:<name> expansion in\n * the user's prompt, and the persisted session file stores the redacted\n * form (the expansion is stripped before persistence).\n *\n * Zero native dependencies: node:crypto works on Node and Bun.\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport {\n\tisKeychainAvailable,\n\tkeychainHasPassphrase,\n\tkeychainLoadPassphrase,\n\tkeychainSavePassphrase,\n\tkeychainClearPassphrase,\n} from \"./keychain.ts\";\nimport type { AuditEntry } from \"./audit.ts\";\n\nexport interface VaultSecret {\n\tname: string;\n\tvalue: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n}\n\nexport interface VaultListEntry {\n\tname: string;\n\tnote?: string;\n\tcategory?: string;\n\tcreated: string;\n\tupdated: string;\n\tsource: \"project\" | \"session\";\n}\n\nconst KDF_N = 32768;\nconst KDF_R = 8;\nconst KDF_P = 1;\nconst KEY_LENGTH = 32;\n\ninterface VaultFilePayload {\n\tversion: 1;\n\tkdf: \"scrypt\";\n\tN: number;\n\tr: number;\n\tp: number;\n\tsalt: string; // base64\n\tiv: string; // base64\n\ttag: string; // base64 (GCM auth tag)\n\tdata: string; // base64 (AES-256-GCM ciphertext of the JSON entries)\n}\n\nfunction deriveKey(passphrase: string, salt: Buffer, N = KDF_N, r = KDF_R, p = KDF_P): Buffer {\n\treturn scryptSync(passphrase, salt, KEY_LENGTH, { N, r, p, maxmem: 128 * 1024 * 1024 });\n}\n\nfunction encryptPayload(payload: VaultSecret[], key: Buffer): { iv: Buffer; tag: Buffer; data: Buffer } {\n\tconst iv = randomBytes(12);\n\tconst cipher = createCipheriv(\"aes-256-gcm\", key, iv);\n\tconst data = Buffer.concat([cipher.update(JSON.stringify(payload), \"utf-8\"), cipher.final()]);\n\tconst tag = cipher.getAuthTag();\n\treturn { iv, tag, data };\n}\n\nfunction decryptPayload(file: VaultFilePayload, key: Buffer): VaultSecret[] {\n\tconst decipher = createDecipheriv(\"aes-256-gcm\", key, Buffer.from(file.iv, \"base64\"));\n\tdecipher.setAuthTag(Buffer.from(file.tag, \"base64\"));\n\tconst plain = Buffer.concat([\n\t\tdecipher.update(Buffer.from(file.data, \"base64\")),\n\t\tdecipher.final(), // throws on wrong passphrase (auth failure)\n\t]);\n\treturn JSON.parse(plain.toString(\"utf-8\")) as VaultSecret[];\n}\n\n/** Reference syntax in user prompts: @vault:<name>. */\nexport const VAULT_REF_PATTERN = /@vault:([A-Za-z0-9_.-]+)/g;\n\nexport interface VaultExpansion {\n\ttext: string;\n\t/** Names that were expanded (empty when none). */\n\texpanded: string[];\n\t/** Names referenced but missing (not expanded). */\n\tmissing: string[];\n}\n\n/**\n * Expand @vault:<name> references in a prompt. The returned text is what the\n * LLM receives; the ORIGINAL input (redacted) is what gets persisted.\n */\nexport function expandVaultRefs(text: string, vault: VaultManager | null): VaultExpansion {\n\tconst expanded: string[] = [];\n\tconst missing: string[] = [];\n\tif (!vault) {\n\t\tfor (const match of text.matchAll(VAULT_REF_PATTERN)) {\n\t\t\tmissing.push(match[1]);\n\t\t}\n\t\treturn { text, expanded, missing };\n\t}\n\tconst out = text.replace(VAULT_REF_PATTERN, (_full, name: string) => {\n\t\tconst value = vault.getSecret(name);\n\t\tif (value !== null) {\n\t\t\texpanded.push(name);\n\t\t\treturn value;\n\t\t}\n\t\tmissing.push(name);\n\t\treturn _full; // leave the reference intact so the user sees it failed\n\t});\n\treturn { text: out, expanded, missing };\n}\n\nexport class SessionVault {\n\tprivate entries = new Map<string, string>();\n\n\tadd(name: string, value: string): void {\n\t\tthis.entries.set(name, value);\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.entries.get(name) ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.entries.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.entries.has(name);\n\t}\n\n\tlist(): string[] {\n\t\treturn [...this.entries.keys()].sort();\n\t}\n\n\tclear(): void {\n\t\tthis.entries.clear();\n\t}\n}\n\nexport class ProjectVault {\n\tprivate secrets: Map<string, VaultSecret>;\n\tprivate key: Buffer;\n\n\tprivate constructor(key: Buffer, secrets: Map<string, VaultSecret>) {\n\t\tthis.key = key;\n\t\tthis.secrets = secrets;\n\t}\n\n\t/** Create a new empty project vault (writes the encrypted file). */\n\tstatic async create(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst salt = randomBytes(16);\n\t\tconst key = deriveKey(passphrase, salt);\n\t\tconst vault = new ProjectVault(key, new Map());\n\t\tawait vault.save(filePath, salt);\n\t\treturn vault;\n\t}\n\n\t/** Open an existing vault. Throws on wrong passphrase. */\n\tstatic async open(filePath: string, passphrase: string): Promise<ProjectVault> {\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\tconst file = JSON.parse(raw) as VaultFilePayload;\n\t\tconst key = deriveKey(passphrase, Buffer.from(file.salt, \"base64\"), file.N, file.r, file.p);\n\t\tconst secrets = decryptPayload(file, key);\n\t\treturn new ProjectVault(\n\t\t\tkey,\n\t\t\tnew Map(secrets.map((s) => [s.name, s])),\n\t\t);\n\t}\n\n\tadd(name: string, value: string, note?: string, category?: string): void {\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, { name, value, note, category, created: now, updated: now });\n\t}\n\n\tupdate(name: string, value: string, note?: string, category?: string): boolean {\n\t\tconst existing = this.secrets.get(name);\n\t\tif (!existing) return false;\n\t\tconst now = new Date().toISOString();\n\t\tthis.secrets.set(name, {\n\t\t\t...existing,\n\t\t\tvalue,\n\t\t\tnote: note ?? existing.note,\n\t\t\tcategory: category ?? existing.category,\n\t\t\tupdated: now,\n\t\t});\n\t\treturn true;\n\t}\n\n\tget(name: string): string | null {\n\t\treturn this.secrets.get(name)?.value ?? null;\n\t}\n\n\tremove(name: string): boolean {\n\t\treturn this.secrets.delete(name);\n\t}\n\n\thas(name: string): boolean {\n\t\treturn this.secrets.has(name);\n\t}\n\n\tlist(): VaultListEntry[] {\n\t\treturn [...this.secrets.values()]\n\t\t\t.map((s) => ({\n\t\t\t\tname: s.name,\n\t\t\t\tnote: s.note,\n\t\t\t\tcategory: s.category,\n\t\t\t\tcreated: s.created,\n\t\t\t\tupdated: s.updated,\n\t\t\t\tsource: \"project\" as const,\n\t\t\t}))\n\t\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tget entryCount(): number {\n\t\treturn this.secrets.size;\n\t}\n\n\tasync save(filePath: string, salt?: Buffer): Promise<void> {\n\t\tconst { iv, tag, data } = encryptPayload([...this.secrets.values()], this.key);\n\t\tconst payload: VaultFilePayload = {\n\t\t\tversion: 1,\n\t\t\tkdf: \"scrypt\",\n\t\t\tN: KDF_N,\n\t\t\tr: KDF_R,\n\t\t\tp: KDF_P,\n\t\t\tsalt: (salt ?? randomBytes(16)).toString(\"base64\"),\n\t\t\tiv: iv.toString(\"base64\"),\n\t\t\ttag: tag.toString(\"base64\"),\n\t\t\tdata: data.toString(\"base64\"),\n\t\t};\n\t\tawait mkdir(dirname(filePath), { recursive: true });\n\t\tawait writeFile(filePath, JSON.stringify(payload, null, 1), \"utf-8\");\n\t}\n}\n\nexport class VaultManager {\n\tprivate projectVault: ProjectVault | null = null;\n\treadonly sessionVault = new SessionVault();\n\tprivate vaultDir: string;\n\tprivate projectHash: string;\n\tprivate auditFn: ((entry: Omit<AuditEntry, \"seq\" | \"ts\" | \"hash\" | \"prevHash\">) => void) | null = null;\n\n\tconstructor(agentDir: string, projectHash: string | null) {\n\t\tthis.vaultDir = join(agentDir, \"vault\");\n\t\tthis.projectHash = projectHash ?? \"global\";\n\t}\n\n\t/** Attach the audit logger (enterprise accountability, never the value). */\n\tsetAudit(fn: ((entry: Omit<AuditEntry, \"seq\" | \"ts\" | \"hash\" | \"prevHash\">) => void) | null): void {\n\t\tthis.auditFn = fn;\n\t}\n\n\tprivate audit(action: AuditEntry[\"action\"], ok: boolean, reason?: string): void {\n\t\tthis.auditFn?.({\n\t\t\taction,\n\t\t\tsource: \"user\",\n\t\t\ttarget: \"vault\",\n\t\t\tscope: this.projectHash,\n\t\t\tok,\n\t\t\treason,\n\t\t});\n\t}\n\n\tget keychainAvailable(): boolean {\n\t\treturn isKeychainAvailable();\n\t}\n\n\tget vaultPath(): string {\n\t\treturn join(this.vaultDir, this.projectHash, \"vault.json\");\n\t}\n\n\tget isUnlocked(): boolean {\n\t\treturn this.projectVault !== null;\n\t}\n\n\t/** True when a project vault file already exists. */\n\texists(): boolean {\n\t\treturn existsSync(this.vaultPath);\n\t}\n\n\t/**\n\t * Unlock (or create) the project vault with the user passphrase.\n\t * Returns false on wrong passphrase. When `remember` is set and the\n\t * Keychain is available, the passphrase is stored there (macOS).\n\t */\n\tasync unlock(passphrase: string, options: { remember?: boolean } = {}): Promise<boolean> {\n\t\ttry {\n\t\t\tif (this.exists()) {\n\t\t\t\tthis.projectVault = await ProjectVault.open(this.vaultPath, passphrase);\n\t\t\t} else {\n\t\t\t\tthis.projectVault = await ProjectVault.create(this.vaultPath, passphrase);\n\t\t\t}\n\t\t\tif (options.remember && isKeychainAvailable()) {\n\t\t\t\tconst saved = await keychainSavePassphrase(this.projectHash, passphrase);\n\t\t\t\tthis.audit(\"add\", saved, saved ? \"keychain\" : \"keychain save failed\");\n\t\t\t}\n\t\t\tthis.audit(\"add\", true, this.exists() ? \"unlock\" : \"create\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\tthis.projectVault = null;\n\t\t\tthis.audit(\"rejected\", false, \"wrong passphrase or corrupted vault\");\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Try to unlock using the passphrase stored in the Keychain (macOS). */\n\tasync unlockFromKeychain(): Promise<boolean> {\n\t\tif (!isKeychainAvailable()) return false;\n\t\tconst passphrase = await keychainLoadPassphrase(this.projectHash);\n\t\tif (!passphrase) return false;\n\t\treturn this.unlock(passphrase, { remember: false });\n\t}\n\n\t/** True when a passphrase for this vault is stored in the Keychain. */\n\tasync keychainRemembered(): Promise<boolean> {\n\t\tif (!isKeychainAvailable()) return false;\n\t\treturn keychainHasPassphrase(this.projectHash);\n\t}\n\n\t/** Forget the Keychain-stored passphrase. */\n\tasync keychainForget(): Promise<boolean> {\n\t\tif (!isKeychainAvailable()) return false;\n\t\tconst ok = await keychainClearPassphrase(this.projectHash);\n\t\tthis.audit(\"remove\", ok, \"keychain forget\");\n\t\treturn ok;\n\t}\n\n\tlock(): void {\n\t\tthis.projectVault = null;\n\t}\n\n\t// ─── Project vault operations (require unlock) ───\n\n\tadd(name: string, value: string, note?: string, category?: string): boolean {\n\t\tif (!this.projectVault || !name.trim() || !value) return false;\n\t\tthis.projectVault.add(name.trim(), value, note, category);\n\t\tvoid this.projectVault.save(this.vaultPath).catch(() => {});\n\t\tthis.audit(\"add\", true, name);\n\t\treturn true;\n\t}\n\n\tupdate(name: string, value: string, note?: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.update(name, value, note);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\treturn ok;\n\t}\n\n\tremove(name: string): boolean {\n\t\tif (!this.projectVault) return false;\n\t\tconst ok = this.projectVault.remove(name);\n\t\tif (ok) void this.projectVault.save(this.vaultPath).catch(() => {});\n\t\tthis.audit(\"remove\", ok, name);\n\t\treturn ok;\n\t}\n\n\t// ─── Lookup (project first, then session) ───\n\n\tgetSecret(name: string): string | null {\n\t\tconst fromProject = this.projectVault?.get(name) ?? null;\n\t\tif (fromProject !== null) return fromProject;\n\t\treturn this.sessionVault.get(name);\n\t}\n\n\t// ─── Session vault (memory only) ───\n\n\tsessionAdd(name: string, value: string): void {\n\t\tthis.sessionVault.add(name, value);\n\t\tthis.audit(\"add\", true, `session:${name}`);\n\t}\n\n\tsessionRemove(name: string): boolean {\n\t\tconst ok = this.sessionVault.remove(name);\n\t\tthis.audit(\"remove\", ok, `session:${name}`);\n\t\treturn ok;\n\t}\n\n\t// ─── Listing ───\n\n\tlist(): VaultListEntry[] {\n\t\tconst entries: VaultListEntry[] = [];\n\t\tif (this.projectVault) entries.push(...this.projectVault.list());\n\t\tfor (const name of this.sessionVault.list()) {\n\t\t\tentries.push({\n\t\t\t\tname,\n\t\t\t\tcreated: \"\",\n\t\t\t\tupdated: \"\",\n\t\t\t\tsource: \"session\",\n\t\t\t});\n\t\t}\n\t\treturn entries.sort((a, b) => a.name.localeCompare(b.name));\n\t}\n\n\tstatus(): {\n\t\tunlocked: boolean;\n\t\tvaultPath: string;\n\t\texists: boolean;\n\t\tprojectCount: number;\n\t\tsessionCount: number;\n\t} {\n\t\treturn {\n\t\t\tunlocked: this.isUnlocked,\n\t\t\tvaultPath: this.vaultPath,\n\t\t\texists: this.exists(),\n\t\t\tprojectCount: this.projectVault?.entryCount ?? 0,\n\t\t\tsessionCount: this.sessionVault.list().length,\n\t\t};\n\t}\n}\n\nexport { existsSync };\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-loader.d.ts","sourceRoot":"","sources":["../../src/core/resource-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, dirname, join, resolve, sep } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { loadThemeFromPath, type Theme } from \"../modes/interactive/theme/theme.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\n\nexport type { ResourceCollision, ResourceDiagnostic } from \"./diagnostics.ts\";\n\nimport { canonicalizePath, isLocalPath, resolvePath } from \"../utils/paths.ts\";\nimport { createEventBus, type EventBus } from \"./event-bus.ts\";\nimport {\n\tclearExtensionCache,\n\tcreateExtensionRuntime,\n\tloadExtensionFromFactory,\n\tloadExtensionsCached,\n} from \"./extensions/loader.ts\";\nimport type { Extension, ExtensionRuntime, InlineExtension, LoadExtensionsResult } from \"./extensions/types.ts\";\nimport { findGitPaths } from \"./footer-data-provider.ts\";\nimport { DefaultPackageManager, type PathMetadata, type ResolvedResource } from \"./package-manager.ts\";\nimport { loadProjectMemorySync } from \"./project-memory.ts\";\nimport { MemoryEngine } from \"./memory-engine/engine.ts\";\nimport { VaultManager } from \"./memory-engine/vault.ts\";\nimport type { PromptTemplate } from \"./prompt-templates.ts\";\nimport { loadPromptTemplates } from \"./prompt-templates.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { loadSkills } from \"./skills.ts\";\nimport { createSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport { resetTimings } from \"./timings.ts\";\n\nexport interface ResourceExtensionPaths {\n\tskillPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tpromptPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tthemePaths?: Array<{ path: string; metadata: PathMetadata }>;\n}\n\nexport interface ResourceLoaderReloadOptions {\n\tresolveProjectTrust?: (input: { extensionsResult: LoadExtensionsResult }) => Promise<boolean>;\n}\n\nexport interface ResourceLoader {\n\tgetCwd(): string;\n\tgetAgentDir(): string;\n\tgetExtensions(): LoadExtensionsResult;\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\tgetAgentsFiles(): { agentsFiles: Array<{ path: string; content: string }> };\n\tgetSystemPrompt(): string | undefined;\n\tgetMemoryEngine(): import(\"./memory-engine/engine.ts\").MemoryEngine | null;\n\tgetVaultManager(): import(\"./memory-engine/vault.ts\").VaultManager | null;\n\tgetSystemPromptSource(): { path: string } | undefined;\n\tgetAppendSystemPrompt(): string[];\n\tgetAppendSystemPromptSources(): Array<{ path: string }>;\n\textendResources(paths: ResourceExtensionPaths): void;\n\treload(options?: ResourceLoaderReloadOptions): Promise<void>;\n}\n\nfunction resolvePromptInput(input: string | undefined, description: string): string | undefined {\n\tif (!input) {\n\t\treturn undefined;\n\t}\n\n\tif (existsSync(input)) {\n\t\ttry {\n\t\t\treturn readFileSync(input, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));\n\t\t\treturn input;\n\t\t}\n\t}\n\n\treturn input;\n}\n\nfunction loadContextFileFromDir(dir: string): { path: string; content: string } | null {\n\tconst candidates = [\"AGENTS.override.md\", \"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\tif (!statSync(filePath).isFile()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * The main repo's context file that a nested linked worktree's own copy shadows: both\n * occupy the same logical repository scope, so loading both applies that context twice. Returns\n * undefined when nothing is shadowed, leaving normal ancestor inheritance alone.\n *\n * Returned canonicalized (realpath), because `git worktree add` writes the `.git`\n * file's `gitdir:` target in realpath form while cwd may still be symlinked\n * (macOS `/tmp` -> `/private/tmp`).\n */\nfunction findShadowedContextFile(cwd: string): string | undefined {\n\tconst gitPaths = findGitPaths(cwd);\n\tif (!gitPaths) return undefined;\n\tconst commonGitDir = canonicalizePath(gitPaths.commonGitDir);\n\tconst worktreeRoot = canonicalizePath(gitPaths.repoDir);\n\tconst mainRepoRoot = dirname(commonGitDir);\n\t// False for an ordinary repo, where the two are the same dir, and for a sibling\n\t// worktree (`git worktree add ../feat`), whose main repo is not an ancestor.\n\tif (!worktreeRoot.startsWith(`${mainRepoRoot}${sep}`)) return undefined;\n\t// dirname of the common git dir is the main worktree root only when that dir is\n\t// itself checked out from the same repo. In a bare layout (`proj/.bare` +\n\t// `proj/main`) it is just the directory holding `.bare`, which tracks nothing; a\n\t// submodule's gitdir has no `commondir`, so it lands under `.git/modules`.\n\tif (canonicalizePath(join(mainRepoRoot, \".git\")) !== commonGitDir) return undefined;\n\tconst worktreeContextFile = loadContextFileFromDir(worktreeRoot);\n\treturn worktreeContextFile ? join(mainRepoRoot, basename(worktreeContextFile.path)) : undefined;\n}\n\nexport function loadProjectContextFiles(options: {\n\tcwd: string;\n\tagentDir: string;\n\tprojectMemoryEnabled?: boolean;\n\tmemoryEngine?: import(\"./memory-engine/engine.ts\").MemoryEngine | null;\n}): Array<{ path: string; content: string }> {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\n\tconst contextFiles: Array<{ path: string; content: string }> = [];\n\tconst seenPaths = new Set<string>();\n\n\tconst globalContext = loadContextFileFromDir(resolvedAgentDir);\n\tif (globalContext) {\n\t\tcontextFiles.push(globalContext);\n\t\tseenPaths.add(globalContext.path);\n\t}\n\n\tconst ancestorContextFiles: Array<{ path: string; content: string }> = [];\n\n\tconst shadowedContextFile = findShadowedContextFile(resolvedCwd);\n\tlet currentDir = resolvedCwd;\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tconst isShadowed =\n\t\t\tshadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? \"\") === shadowedContextFile;\n\t\tif (contextFile && !isShadowed && !seenPaths.has(contextFile.path)) {\n\t\t\tancestorContextFiles.unshift(contextFile);\n\t\t\tseenPaths.add(contextFile.path);\n\t\t}\n\n\t\tconst parentDir = dirname(currentDir);\n\t\tif (parentDir === currentDir) break;\n\t\tcurrentDir = parentDir;\n\t}\n\n\tcontextFiles.push(...ancestorContextFiles);\n\n\t// Shared project memory: the Ada Memory Engine block (policy or inject\n\t// mode). Falls back to the legacy project-memory file when the engine is\n\t// unavailable or disabled. Included last so it reads as the freshest\n\t// project context.\n\tconst memoryBlock = options.memoryEngine?.getPromptBlockSync() ?? \"\";\n\tif (memoryBlock) {\n\t\tcontextFiles.push({ path: \"<ada-memory>\", content: memoryBlock });\n\t} else {\n\t\tconst memoryFile = (options.projectMemoryEnabled ?? true)\n\t\t\t? loadProjectMemorySync(resolvedCwd, resolvedAgentDir)\n\t\t\t: null;\n\t\tif (memoryFile) {\n\t\t\tcontextFiles.push(memoryFile);\n\t\t}\n\t}\n\n\treturn contextFiles;\n}\n\nexport interface DefaultResourceLoaderOptions {\n\tcwd: string;\n\tagentDir: string;\n\tsettingsManager?: SettingsManager;\n\teventBus?: EventBus;\n\tadditionalExtensionPaths?: string[];\n\tadditionalSkillPaths?: string[];\n\tadditionalPromptTemplatePaths?: string[];\n\tadditionalThemePaths?: string[];\n\textensionFactories?: InlineExtension[];\n\tnoExtensions?: boolean;\n\tnoSkills?: boolean;\n\tnoPromptTemplates?: boolean;\n\tnoThemes?: boolean;\n\tnoContextFiles?: boolean;\n\tsystemPrompt?: string;\n\tappendSystemPrompt?: string[];\n\textensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tskillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tpromptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tthemesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tagentsFilesOverride?: (base: { agentsFiles: Array<{ path: string; content: string }> }) => {\n\t\tagentsFiles: Array<{ path: string; content: string }>;\n\t};\n\tsystemPromptOverride?: (base: string | undefined) => string | undefined;\n\tappendSystemPromptOverride?: (base: string[]) => string[];\n}\n\nexport class DefaultResourceLoader implements ResourceLoader {\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\tprivate settingsManager: SettingsManager;\n\n\tgetCwd(): string {\n\t\treturn this.cwd;\n\t}\n\n\tgetAgentDir(): string {\n\t\treturn this.agentDir;\n\t}\n\tprivate eventBus: EventBus;\n\tprivate packageManager: DefaultPackageManager;\tprivate additionalExtensionPaths: string[];\n\tprivate additionalSkillPaths: string[];\n\tprivate additionalPromptTemplatePaths: string[];\n\tprivate additionalThemePaths: string[];\n\tprivate extensionFactories: InlineExtension[];\n\tprivate noExtensions: boolean;\n\tprivate noSkills: boolean;\n\tprivate noPromptTemplates: boolean;\n\tprivate noThemes: boolean;\n\tprivate noContextFiles: boolean;\n\tprivate memoryEngine: import(\"./memory-engine/engine.ts\").MemoryEngine | null = null;\n\tprivate vaultManager: import(\"./memory-engine/vault.ts\").VaultManager | null = null;\n\tprivate systemPromptSource?: string;\n\tprivate appendSystemPromptSource?: string[];\n\tprivate extensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tprivate skillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate promptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate themesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate agentsFilesOverride?: (base: { agentsFiles: Array<{ path: string; content: string }> }) => {\n\t\tagentsFiles: Array<{ path: string; content: string }>;\n\t};\n\tprivate systemPromptOverride?: (base: string | undefined) => string | undefined;\n\tprivate appendSystemPromptOverride?: (base: string[]) => string[];\n\n\tprivate extensionsResult: LoadExtensionsResult;\n\tprivate skills: Skill[];\n\tprivate skillDiagnostics: ResourceDiagnostic[];\n\tprivate prompts: PromptTemplate[];\n\tprivate promptDiagnostics: ResourceDiagnostic[];\n\tprivate themes: Theme[];\n\tprivate themeDiagnostics: ResourceDiagnostic[];\n\tprivate agentsFiles: Array<{ path: string; content: string }>;\n\tprivate systemPrompt?: string;\n\tprivate systemPromptSourcePath?: string;\n\tprivate appendSystemPrompt: string[];\n\tprivate appendSystemPromptSourcePaths: string[];\n\tprivate lastSkillPaths: string[];\n\tprivate extensionSkillSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionPromptSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionThemeSourceInfos: Map<string, SourceInfo>;\n\tprivate resourceMetadataByPath: Map<string, PathMetadata>;\n\tprivate lastPromptPaths: string[];\n\tprivate lastThemePaths: string[];\n\tprivate loaded: boolean;\n\n\tconstructor(options: DefaultResourceLoaderOptions) {\n\t\tthis.cwd = resolvePath(options.cwd);\n\t\tthis.agentDir = resolvePath(options.agentDir);\n\t\tthis.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);\n\t\t// Ada Memory Engine — shared across CLI and desktop; lazy-initialized.\n\t\tthis.memoryEngine = new MemoryEngine({\n\t\t\tagentDir: this.agentDir,\n\t\t\tcwd: this.cwd,\n\t\t\tgetConfig: () => this.settingsManager.getMemoryEngineConfig(),\n\t\t});\n\t\t// Ada Secure Vault — encrypted secrets (project + session scopes).\n\t\tthis.vaultManager = new VaultManager(this.agentDir, this.memoryEngine.projectHash);\n\t\tthis.eventBus = options.eventBus ?? createEventBus();\n\t\tthis.packageManager = new DefaultPackageManager({\n\t\t\tcwd: this.cwd,\n\t\t\tagentDir: this.agentDir,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t});\n\t\tthis.additionalExtensionPaths = options.additionalExtensionPaths ?? [];\n\t\tthis.additionalSkillPaths = options.additionalSkillPaths ?? [];\n\t\tthis.additionalPromptTemplatePaths = options.additionalPromptTemplatePaths ?? [];\n\t\tthis.additionalThemePaths = options.additionalThemePaths ?? [];\n\t\tthis.extensionFactories = options.extensionFactories ?? [];\n\t\tthis.noExtensions = options.noExtensions ?? false;\n\t\tthis.noSkills = options.noSkills ?? false;\n\t\tthis.noPromptTemplates = options.noPromptTemplates ?? false;\n\t\tthis.noThemes = options.noThemes ?? false;\n\t\tthis.noContextFiles = options.noContextFiles ?? false;\n\t\tthis.systemPromptSource = options.systemPrompt;\n\t\tthis.appendSystemPromptSource = options.appendSystemPrompt;\n\t\tthis.extensionsOverride = options.extensionsOverride;\n\t\tthis.skillsOverride = options.skillsOverride;\n\t\tthis.promptsOverride = options.promptsOverride;\n\t\tthis.themesOverride = options.themesOverride;\n\t\tthis.agentsFilesOverride = options.agentsFilesOverride;\n\t\tthis.systemPromptOverride = options.systemPromptOverride;\n\t\tthis.appendSystemPromptOverride = options.appendSystemPromptOverride;\n\n\t\tthis.extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };\n\t\tthis.skills = [];\n\t\tthis.skillDiagnostics = [];\n\t\tthis.prompts = [];\n\t\tthis.promptDiagnostics = [];\n\t\tthis.themes = [];\n\t\tthis.themeDiagnostics = [];\n\t\tthis.agentsFiles = [];\n\t\tthis.appendSystemPrompt = [];\n\t\tthis.appendSystemPromptSourcePaths = [];\n\t\tthis.lastSkillPaths = [];\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\t\tthis.resourceMetadataByPath = new Map();\n\t\tthis.lastPromptPaths = [];\n\t\tthis.lastThemePaths = [];\n\t\tthis.loaded = false;\n\t}\n\n\tgetExtensions(): LoadExtensionsResult {\n\t\treturn this.extensionsResult;\n\t}\n\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { skills: this.skills, diagnostics: this.skillDiagnostics };\n\t}\n\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { prompts: this.prompts, diagnostics: this.promptDiagnostics };\n\t}\n\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { themes: this.themes, diagnostics: this.themeDiagnostics };\n\t}\n\n\tgetAgentsFiles(): { agentsFiles: Array<{ path: string; content: string }> } {\n\t\treturn { agentsFiles: this.agentsFiles };\n\t}\n\n\tgetMemoryEngine(): import(\"./memory-engine/engine.ts\").MemoryEngine | null {\n\t\treturn this.memoryEngine;\n\t}\n\n\tgetVaultManager(): import(\"./memory-engine/vault.ts\").VaultManager | null {\n\t\treturn this.vaultManager;\n\t}\n\n\tgetSystemPrompt(): string | undefined {\n\t\treturn this.systemPrompt;\n\t}\n\n\tgetSystemPromptSource(): { path: string } | undefined {\n\t\treturn this.systemPromptSourcePath ? { path: this.systemPromptSourcePath } : undefined;\n\t}\n\n\tgetAppendSystemPrompt(): string[] {\n\t\treturn this.appendSystemPrompt;\n\t}\n\n\tgetAppendSystemPromptSources(): Array<{ path: string }> {\n\t\treturn this.appendSystemPromptSourcePaths.map((path) => ({ path }));\n\t}\n\n\textendResources(paths: ResourceExtensionPaths): void {\n\t\tconst skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);\n\t\tconst promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);\n\t\tconst themePaths = this.normalizeExtensionPaths(paths.themePaths ?? []);\n\n\t\tfor (const entry of skillPaths) {\n\t\t\tthis.extensionSkillSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of promptPaths) {\n\t\t\tthis.extensionPromptSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of themePaths) {\n\t\t\tthis.extensionThemeSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\n\t\tif (skillPaths.length > 0) {\n\t\t\tthis.lastSkillPaths = this.mergePaths(\n\t\t\t\tthis.lastSkillPaths,\n\t\t\t\tskillPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateSkillsFromPaths(this.lastSkillPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (promptPaths.length > 0) {\n\t\t\tthis.lastPromptPaths = this.mergePaths(\n\t\t\t\tthis.lastPromptPaths,\n\t\t\t\tpromptPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updatePromptsFromPaths(this.lastPromptPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (themePaths.length > 0) {\n\t\t\tthis.lastThemePaths = this.mergePaths(\n\t\t\t\tthis.lastThemePaths,\n\t\t\t\tthemePaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateThemesFromPaths(this.lastThemePaths, this.resourceMetadataByPath);\n\t\t}\n\t}\n\n\tasync loadProjectTrustExtensions(): Promise<LoadExtensionsResult> {\n\t\t// Force untrusted project settings for the bootstrap pass. This keeps project-local\n\t\t// extensions/packages out while still loading user/global and temporary CLI extensions.\n\t\tthis.settingsManager.setProjectTrusted(false);\n\t\tawait this.settingsManager.reload();\n\t\treturn this.loadCurrentExtensionSet({ includeInlineFactories: true });\n\t}\n\n\tasync reload(options?: ResourceLoaderReloadOptions): Promise<void> {\n\t\tresetTimings(\"extensions\");\n\n\t\tif (this.loaded) {\n\t\t\tclearExtensionCache();\n\t\t}\n\n\t\tlet preTrustExtensions: LoadExtensionsResult | undefined;\n\t\tif (options?.resolveProjectTrust) {\n\t\t\tpreTrustExtensions = await this.loadProjectTrustExtensions();\n\t\t\tconst projectTrusted = await options.resolveProjectTrust({ extensionsResult: preTrustExtensions });\n\t\t\tthis.settingsManager.setProjectTrusted(projectTrusted);\n\t\t}\n\n\t\t// reload() preserves SettingsManager.projectTrusted and reloads settings for that trust state.\n\t\tawait this.settingsManager.reload();\n\t\tconst resolvedPaths = await this.packageManager.resolve();\n\t\tconst cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {\n\t\t\ttemporary: true,\n\t\t});\n\t\t// Kept on the instance so post-reload passes (extendResources) can still resolve package metadata.\n\t\tthis.resourceMetadataByPath = new Map();\n\t\tconst metadataByPath = this.resourceMetadataByPath;\n\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\n\t\t// Helper to extract enabled paths and store metadata\n\t\tconst getEnabledResources = (resources: ResolvedResource[]): ResolvedResource[] => {\n\t\t\tfor (const r of resources) {\n\t\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\t\tmetadataByPath.set(r.path, r.metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resources.filter((r) => r.enabled);\n\t\t};\n\n\t\tconst getEnabledPaths = (resources: ResolvedResource[]): string[] =>\n\t\t\tgetEnabledResources(resources).map((r) => r.path);\n\t\tconst enabledExtensions = getEnabledPaths(resolvedPaths.extensions);\n\t\tconst enabledSkillResources = getEnabledResources(resolvedPaths.skills);\n\t\tconst enabledPrompts = getEnabledPaths(resolvedPaths.prompts);\n\t\tconst enabledThemes = getEnabledPaths(resolvedPaths.themes);\n\n\t\tconst enabledSkills = enabledSkillResources.map((resource) => this.mapSkillPath(resource, metadataByPath));\n\n\t\t// Add CLI paths metadata\n\t\tfor (const r of cliExtensionPaths.extensions) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\t\tfor (const r of cliExtensionPaths.skills) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\n\t\tconst cliEnabledExtensions = getEnabledPaths(cliExtensionPaths.extensions);\n\t\tconst cliEnabledSkills = getEnabledPaths(cliExtensionPaths.skills);\n\t\tconst cliEnabledPrompts = getEnabledPaths(cliExtensionPaths.prompts);\n\t\tconst cliEnabledThemes = getEnabledPaths(cliExtensionPaths.themes);\n\n\t\tconst extensionPaths = this.noExtensions\n\t\t\t? cliEnabledExtensions\n\t\t\t: this.mergePaths(cliEnabledExtensions, enabledExtensions);\n\n\t\tconst extensionsResult = await this.loadFinalExtensionSet(extensionPaths, preTrustExtensions);\n\t\tfor (const p of this.additionalExtensionPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved)) {\n\t\t\t\t\textensionsResult.errors.push({ path: resolved, error: `Extension path does not exist: ${resolved}` });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;\n\t\tthis.applyExtensionSourceInfo(this.extensionsResult.extensions, metadataByPath);\n\n\t\tconst skillPaths = this.noSkills\n\t\t\t? this.mergePaths(cliEnabledSkills, this.additionalSkillPaths)\n\t\t\t: this.mergePaths([...cliEnabledSkills, ...enabledSkills], this.additionalSkillPaths);\n\n\t\tthis.lastSkillPaths = skillPaths;\n\t\tthis.updateSkillsFromPaths(skillPaths, metadataByPath);\n\t\tfor (const p of this.additionalSkillPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.skillDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.skillDiagnostics.push({ type: \"error\", message: \"Skill path does not exist\", path: resolved });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst promptPaths = this.noPromptTemplates\n\t\t\t? this.mergePaths(cliEnabledPrompts, this.additionalPromptTemplatePaths)\n\t\t\t: this.mergePaths([...cliEnabledPrompts, ...enabledPrompts], this.additionalPromptTemplatePaths);\n\n\t\tthis.lastPromptPaths = promptPaths;\n\t\tthis.updatePromptsFromPaths(promptPaths, metadataByPath);\n\t\tfor (const p of this.additionalPromptTemplatePaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.promptDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.promptDiagnostics.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\tmessage: \"Prompt template path does not exist\",\n\t\t\t\t\t\tpath: resolved,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst themePaths = this.noThemes\n\t\t\t? this.mergePaths(cliEnabledThemes, this.additionalThemePaths)\n\t\t\t: this.mergePaths([...cliEnabledThemes, ...enabledThemes], this.additionalThemePaths);\n\n\t\tthis.lastThemePaths = themePaths;\n\t\tthis.updateThemesFromPaths(themePaths, metadataByPath);\n\t\tfor (const p of this.additionalThemePaths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved) && !this.themeDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\tthis.themeDiagnostics.push({ type: \"error\", message: \"Theme path does not exist\", path: resolved });\n\t\t\t}\n\t\t}\n\n\t\tconst agentsFiles = {\n\t\t\tagentsFiles: this.noContextFiles\n\t\t\t\t? []\n\t\t\t\t: loadProjectContextFiles({\n\t\t\t\t\t\tcwd: this.cwd,\n\t\t\t\t\t\tagentDir: this.agentDir,\n\t\t\t\t\t\tprojectMemoryEnabled: this.settingsManager.getProjectMemoryEnabled(),\n\t\t\t\t\t\tmemoryEngine: this.memoryEngine,\n\t\t\t\t\t}),\n\t\t};\n\t\tconst resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;\n\t\tthis.agentsFiles = resolvedAgentsFiles.agentsFiles;\n\n\t\tconst systemPromptSource = this.systemPromptSource ?? this.discoverSystemPromptFile();\n\t\tconst baseSystemPrompt = resolvePromptInput(systemPromptSource, \"system prompt\");\n\t\tthis.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;\n\t\tthis.systemPromptSourcePath =\n\t\t\tsystemPromptSource && existsSync(systemPromptSource) ? resolvePath(systemPromptSource) : undefined;\n\n\t\tlet appendSources = this.appendSystemPromptSource;\n\t\tif (!appendSources) {\n\t\t\tconst discoveredAppendSystemPromptFile = this.discoverAppendSystemPromptFile();\n\t\t\tappendSources = discoveredAppendSystemPromptFile ? [discoveredAppendSystemPromptFile] : [];\n\t\t}\n\t\tconst baseAppend = appendSources\n\t\t\t.map((s) => resolvePromptInput(s, \"append system prompt\"))\n\t\t\t.filter((s): s is string => s !== undefined);\n\t\tthis.appendSystemPrompt = this.appendSystemPromptOverride\n\t\t\t? this.appendSystemPromptOverride(baseAppend)\n\t\t\t: baseAppend;\n\t\tthis.appendSystemPromptSourcePaths = appendSources\n\t\t\t.filter((source) => existsSync(source))\n\t\t\t.map((source) => resolvePath(source));\n\t\tthis.loaded = true;\n\t}\n\n\tprivate async loadCurrentExtensionSet(options: { includeInlineFactories: boolean }): Promise<LoadExtensionsResult> {\n\t\tconst resolvedPaths = await this.packageManager.resolve();\n\t\tconst cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {\n\t\t\ttemporary: true,\n\t\t});\n\t\tconst enabledExtensions = resolvedPaths.extensions.filter((r) => r.enabled).map((r) => r.path);\n\t\tconst cliEnabledExtensions = cliExtensionPaths.extensions.filter((r) => r.enabled).map((r) => r.path);\n\t\tconst extensionPaths = this.noExtensions\n\t\t\t? cliEnabledExtensions\n\t\t\t: this.mergePaths(cliEnabledExtensions, enabledExtensions);\n\t\tconst extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);\n\t\tif (!options.includeInlineFactories) {\n\t\t\treturn extensionsResult;\n\t\t}\n\n\t\tconst inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);\n\t\textensionsResult.extensions.push(...inlineExtensions.extensions);\n\t\textensionsResult.errors.push(...inlineExtensions.errors);\n\t\treturn extensionsResult;\n\t}\n\n\tprivate resolveExtensionLoadPath(path: string): string {\n\t\treturn resolvePath(path, this.cwd, { normalizeUnicodeSpaces: true });\n\t}\n\n\tprivate async loadFinalExtensionSet(\n\t\textensionPaths: string[],\n\t\tpreTrustExtensions: LoadExtensionsResult | undefined,\n\t): Promise<LoadExtensionsResult> {\n\t\tif (!preTrustExtensions) {\n\t\t\tconst extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);\n\t\t\tconst inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);\n\t\t\textensionsResult.extensions.push(...inlineExtensions.extensions);\n\t\t\textensionsResult.errors.push(...inlineExtensions.errors);\n\t\t\tthis.addExtensionConflictDiagnostics(extensionsResult);\n\t\t\treturn extensionsResult;\n\t\t}\n\n\t\tconst preloadedByPath = new Map(\n\t\t\tpreTrustExtensions.extensions\n\t\t\t\t.filter((extension) => !extension.path.startsWith(\"<inline:\"))\n\t\t\t\t.map((extension) => [extension.resolvedPath, extension]),\n\t\t);\n\t\tconst failedPreloadPaths = new Set(\n\t\t\tpreTrustExtensions.errors.map((error) => this.resolveExtensionLoadPath(error.path)),\n\t\t);\n\t\tconst remainingPaths = extensionPaths.filter((path) => {\n\t\t\tconst resolvedPath = this.resolveExtensionLoadPath(path);\n\t\t\treturn !preloadedByPath.has(resolvedPath) && !failedPreloadPaths.has(resolvedPath);\n\t\t});\n\t\tconst remainingExtensions = await loadExtensionsCached(\n\t\t\tremainingPaths,\n\t\t\tthis.cwd,\n\t\t\tthis.eventBus,\n\t\t\tpreTrustExtensions.runtime,\n\t\t);\n\t\tconst loadedByPath = new Map(preloadedByPath);\n\t\tfor (const extension of remainingExtensions.extensions) {\n\t\t\tloadedByPath.set(extension.resolvedPath, extension);\n\t\t}\n\n\t\tconst inlineExtensions = preTrustExtensions.extensions.filter((extension) =>\n\t\t\textension.path.startsWith(\"<inline:\"),\n\t\t);\n\t\tconst orderedExtensions = extensionPaths\n\t\t\t.map((path) => loadedByPath.get(this.resolveExtensionLoadPath(path)))\n\t\t\t.filter((extension): extension is Extension => extension !== undefined);\n\t\torderedExtensions.push(...inlineExtensions);\n\n\t\tconst extensionsResult: LoadExtensionsResult = {\n\t\t\textensions: orderedExtensions,\n\t\t\terrors: [...preTrustExtensions.errors, ...remainingExtensions.errors],\n\t\t\truntime: preTrustExtensions.runtime,\n\t\t};\n\t\tthis.addExtensionConflictDiagnostics(extensionsResult);\n\t\treturn extensionsResult;\n\t}\n\n\tprivate addExtensionConflictDiagnostics(extensionsResult: LoadExtensionsResult): void {\n\t\t// Detect extension conflicts (tools, commands, flags with same names from different extensions)\n\t\t// Keep all extensions loaded. Conflicts are reported as diagnostics, and precedence is handled by load order.\n\t\tconst conflicts = this.detectExtensionConflicts(extensionsResult.extensions);\n\t\tfor (const conflict of conflicts) {\n\t\t\textensionsResult.errors.push({ path: conflict.path, error: conflict.message });\n\t\t}\n\t}\n\n\tprivate mapSkillPath(resource: ResolvedResource, metadataByPath: Map<string, PathMetadata>): string {\n\t\tif (resource.metadata.source !== \"auto\" && resource.metadata.origin !== \"package\") {\n\t\t\treturn resource.path;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = statSync(resource.path);\n\t\t\tif (!stats.isDirectory()) {\n\t\t\t\treturn resource.path;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn resource.path;\n\t\t}\n\t\tconst skillFile = join(resource.path, \"SKILL.md\");\n\t\tif (existsSync(skillFile)) {\n\t\t\tif (!metadataByPath.has(skillFile)) {\n\t\t\t\tmetadataByPath.set(skillFile, resource.metadata);\n\t\t\t}\n\t\t\treturn skillFile;\n\t\t}\n\t\treturn resource.path;\n\t}\n\n\tprivate normalizeExtensionPaths(\n\t\tentries: Array<{ path: string; metadata: PathMetadata }>,\n\t): Array<{ path: string; metadata: PathMetadata }> {\n\t\treturn entries.map((entry) => {\n\t\t\tconst metadata = entry.metadata.baseDir\n\t\t\t\t? { ...entry.metadata, baseDir: this.resolveResourcePath(entry.metadata.baseDir) }\n\t\t\t\t: entry.metadata;\n\t\t\treturn {\n\t\t\t\tpath: this.resolveResourcePath(entry.path),\n\t\t\t\tmetadata,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate updateSkillsFromPaths(skillPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet skillsResult: { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noSkills && skillPaths.length === 0) {\n\t\t\tskillsResult = { skills: [], diagnostics: [] };\n\t\t} else {\n\t\t\tskillsResult = loadSkills({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tskillPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t});\n\t\t}\n\t\tconst resolvedSkills = this.skillsOverride ? this.skillsOverride(skillsResult) : skillsResult;\n\t\tthis.skills = resolvedSkills.skills.map((skill) => ({\n\t\t\t...skill,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(skill.filePath, this.extensionSkillSourceInfos, metadataByPath) ??\n\t\t\t\tskill.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(skill.filePath),\n\t\t}));\n\t\tthis.skillDiagnostics = resolvedSkills.diagnostics;\n\t}\n\n\tprivate updatePromptsFromPaths(promptPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet promptsResult: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noPromptTemplates && promptPaths.length === 0) {\n\t\t\tpromptsResult = { prompts: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst allPrompts = loadPromptTemplates({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tpromptPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t});\n\t\t\tpromptsResult = this.dedupePrompts(allPrompts);\n\t\t}\n\t\tconst resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;\n\t\tthis.prompts = resolvedPrompts.prompts.map((prompt) => ({\n\t\t\t...prompt,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(prompt.filePath, this.extensionPromptSourceInfos, metadataByPath) ??\n\t\t\t\tprompt.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(prompt.filePath),\n\t\t}));\n\t\tthis.promptDiagnostics = resolvedPrompts.diagnostics;\n\t}\n\n\tprivate updateThemesFromPaths(themePaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet themesResult: { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noThemes && themePaths.length === 0) {\n\t\t\tthemesResult = { themes: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst loaded = this.loadThemes(themePaths, false);\n\t\t\tconst deduped = this.dedupeThemes(loaded.themes);\n\t\t\tthemesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };\n\t\t}\n\t\tconst resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;\n\t\tthis.themes = resolvedThemes.themes.map((theme) => {\n\t\t\tconst sourcePath = theme.sourcePath;\n\t\t\ttheme.sourceInfo = sourcePath\n\t\t\t\t? (this.findSourceInfoForPath(sourcePath, this.extensionThemeSourceInfos, metadataByPath) ??\n\t\t\t\t\ttheme.sourceInfo ??\n\t\t\t\t\tthis.getDefaultSourceInfoForPath(sourcePath))\n\t\t\t\t: theme.sourceInfo;\n\t\t\treturn theme;\n\t\t});\n\t\tthis.themeDiagnostics = resolvedThemes.diagnostics;\n\t}\n\n\tprivate applyExtensionSourceInfo(extensions: Extension[], metadataByPath: Map<string, PathMetadata>): void {\n\t\tfor (const extension of extensions) {\n\t\t\textension.sourceInfo =\n\t\t\t\tthis.findSourceInfoForPath(extension.path, undefined, metadataByPath) ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(extension.path);\n\t\t\tfor (const command of extension.commands.values()) {\n\t\t\t\tcommand.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t\tfor (const tool of extension.tools.values()) {\n\t\t\t\ttool.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate findSourceInfoForPath(\n\t\tresourcePath: string,\n\t\textraSourceInfos?: Map<string, SourceInfo>,\n\t\tmetadataByPath?: Map<string, PathMetadata>,\n\t): SourceInfo | undefined {\n\t\tif (!resourcePath) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (resourcePath.startsWith(\"<\")) {\n\t\t\treturn this.getDefaultSourceInfoForPath(resourcePath);\n\t\t}\n\n\t\tconst normalizedResourcePath = resolve(resourcePath);\n\t\tif (extraSourceInfos) {\n\t\t\tfor (const [sourcePath, sourceInfo] of extraSourceInfos.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn { ...sourceInfo, path: resourcePath };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (metadataByPath) {\n\t\t\tconst exact = metadataByPath.get(normalizedResourcePath) ?? metadataByPath.get(resourcePath);\n\t\t\tif (exact) {\n\t\t\t\treturn createSourceInfo(resourcePath, exact);\n\t\t\t}\n\n\t\t\tfor (const [sourcePath, metadata] of metadataByPath.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn createSourceInfo(resourcePath, metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate getDefaultSourceInfoForPath(filePath: string): SourceInfo {\n\t\tif (filePath.startsWith(\"<\") && filePath.endsWith(\">\")) {\n\t\t\treturn {\n\t\t\t\tpath: filePath,\n\t\t\t\tsource: filePath.slice(1, -1).split(\":\")[0] || \"temporary\",\n\t\t\t\tscope: \"temporary\",\n\t\t\t\torigin: \"top-level\",\n\t\t\t};\n\t\t}\n\n\t\tconst normalizedPath = resolve(filePath);\n\t\tconst agentRoots = [\n\t\t\tjoin(this.agentDir, \"skills\"),\n\t\t\tjoin(this.agentDir, \"prompts\"),\n\t\t\tjoin(this.agentDir, \"themes\"),\n\t\t\tjoin(this.agentDir, \"extensions\"),\n\t\t];\n\t\tconst projectRoots = [\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"skills\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"prompts\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"themes\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"extensions\"),\n\t\t];\n\n\t\tfor (const root of agentRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"user\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\tfor (const root of projectRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"project\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tsource: \"local\",\n\t\t\tscope: \"temporary\",\n\t\t\torigin: \"top-level\",\n\t\t\tbaseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, \"..\"),\n\t\t};\n\t}\n\n\tprivate mergePaths(primary: string[], additional: string[]): string[] {\n\t\tconst merged: string[] = [];\n\t\tconst seen = new Set<string>();\n\n\t\tfor (const p of [...primary, ...additional]) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tconst canonicalPath = canonicalizePath(resolved);\n\t\t\tif (seen.has(canonicalPath)) continue;\n\t\t\tseen.add(canonicalPath);\n\t\t\tmerged.push(resolved);\n\t\t}\n\n\t\treturn merged;\n\t}\n\n\tprivate resolveResourcePath(p: string): string {\n\t\treturn resolvePath(p, this.cwd, { trim: true });\n\t}\n\n\tprivate loadThemes(\n\t\tpaths: string[],\n\t\tincludeDefaults: boolean = true,\n\t): {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t} {\n\t\tconst themes: Theme[] = [];\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\t\tif (includeDefaults) {\n\t\t\tconst defaultDirs = [join(this.agentDir, \"themes\"), join(this.cwd, CONFIG_DIR_NAME, \"themes\")];\n\n\t\t\tfor (const dir of defaultDirs) {\n\t\t\t\tthis.loadThemesFromDir(dir, themes, diagnostics);\n\t\t\t}\n\t\t}\n\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path does not exist\", path: resolved });\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(resolved);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.loadThemesFromDir(resolved, themes, diagnostics);\n\t\t\t\t} else if (stats.isFile() && resolved.endsWith(\".json\")) {\n\t\t\t\t\tthis.loadThemeFromFile(resolved, themes, diagnostics);\n\t\t\t\t} else {\n\t\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path is not a json file\", path: resolved });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme path\";\n\t\t\t\tdiagnostics.push({ type: \"warning\", message, path: resolved });\n\t\t\t}\n\t\t}\n\n\t\treturn { themes, diagnostics };\n\t}\n\n\tprivate loadThemesFromDir(dir: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\tif (!existsSync(dir)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\t\tfor (const entry of entries) {\n\t\t\t\tlet isFile = entry.isFile();\n\t\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tisFile = statSync(join(dir, entry.name)).isFile();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!isFile) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!entry.name.endsWith(\".json\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.loadThemeFromFile(join(dir, entry.name), themes, diagnostics);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme directory\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: dir });\n\t\t}\n\t}\n\n\tprivate loadThemeFromFile(filePath: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\ttry {\n\t\t\tthemes.push(loadThemeFromPath(filePath));\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to load theme\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\t}\n\t}\n\n\tprivate async loadExtensionFactories(runtime: ExtensionRuntime): Promise<{\n\t\textensions: Extension[];\n\t\terrors: Array<{ path: string; error: string }>;\n\t}> {\n\t\tconst extensions: Extension[] = [];\n\t\tconst errors: Array<{ path: string; error: string }> = [];\n\n\t\tfor (const [index, input] of this.extensionFactories.entries()) {\n\t\t\tconst isNamed = typeof input !== \"function\";\n\t\t\tconst factory = isNamed ? input.factory : input;\n\t\t\tconst extensionPath = `<inline:${isNamed ? input.name : index + 1}>`;\n\t\t\ttry {\n\t\t\t\tconst extension = await loadExtensionFromFactory(factory, this.cwd, this.eventBus, runtime, extensionPath);\n\t\t\t\textension.hidden = isNamed && input.hidden;\n\t\t\t\textensions.push(extension);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to load extension\";\n\t\t\t\terrors.push({ path: extensionPath, error: message });\n\t\t\t}\n\t\t}\n\n\t\treturn { extensions, errors };\n\t}\n\n\tprivate dedupePrompts(prompts: PromptTemplate[]): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, PromptTemplate>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const prompt of prompts) {\n\t\t\tconst existing = seen.get(prompt.name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"/${prompt.name}\" collision`,\n\t\t\t\t\tpath: prompt.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"prompt\",\n\t\t\t\t\t\tname: prompt.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: prompt.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(prompt.name, prompt);\n\t\t\t}\n\t\t}\n\n\t\treturn { prompts: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate dedupeThemes(themes: Theme[]): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, Theme>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const t of themes) {\n\t\t\tconst name = t.name ?? \"unnamed\";\n\t\t\tconst existing = seen.get(name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${name}\" collision`,\n\t\t\t\t\tpath: t.sourcePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"theme\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t\twinnerPath: existing.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t\tloserPath: t.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(name, t);\n\t\t\t}\n\t\t}\n\n\t\treturn { themes: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate discoverSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"SYSTEM.md\");\n\t\tif (this.settingsManager.isProjectTrusted() && existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate discoverAppendSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"APPEND_SYSTEM.md\");\n\t\tif (this.settingsManager.isProjectTrusted() && existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"APPEND_SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate isUnderPath(target: string, root: string): boolean {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t}\n\n\tprivate detectExtensionConflicts(extensions: Extension[]): Array<{ path: string; message: string }> {\n\t\tconst conflicts: Array<{ path: string; message: string }> = [];\n\n\t\t// Track which extension registered each tool and flag\n\t\tconst toolOwners = new Map<string, string>();\n\t\tconst flagOwners = new Map<string, string>();\n\n\t\tfor (const ext of extensions) {\n\t\t\t// Check tools\n\t\t\tfor (const toolName of ext.tools.keys()) {\n\t\t\t\tconst existingOwner = toolOwners.get(toolName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Tool \"${toolName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\ttoolOwners.set(toolName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check flags\n\t\t\tfor (const flagName of ext.flags.keys()) {\n\t\t\t\tconst existingOwner = flagOwners.get(flagName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Flag \"--${flagName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tflagOwners.set(flagName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn conflicts;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"resource-loader.d.ts","sourceRoot":"","sources":["../../src/core/resource-loader.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { basename, dirname, join, resolve, sep } from \"node:path\";\nimport chalk from \"chalk\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { loadThemeFromPath, type Theme } from \"../modes/interactive/theme/theme.ts\";\nimport type { ResourceDiagnostic } from \"./diagnostics.ts\";\n\nexport type { ResourceCollision, ResourceDiagnostic } from \"./diagnostics.ts\";\n\nimport { canonicalizePath, isLocalPath, resolvePath } from \"../utils/paths.ts\";\nimport { createEventBus, type EventBus } from \"./event-bus.ts\";\nimport {\n\tclearExtensionCache,\n\tcreateExtensionRuntime,\n\tloadExtensionFromFactory,\n\tloadExtensionsCached,\n} from \"./extensions/loader.ts\";\nimport type { Extension, ExtensionRuntime, InlineExtension, LoadExtensionsResult } from \"./extensions/types.ts\";\nimport { findGitPaths } from \"./footer-data-provider.ts\";\nimport { DefaultPackageManager, type PathMetadata, type ResolvedResource } from \"./package-manager.ts\";\nimport { loadProjectMemorySync } from \"./project-memory.ts\";\nimport { MemoryEngine } from \"./memory-engine/engine.ts\";\nimport { VaultManager } from \"./memory-engine/vault.ts\";\nimport type { PromptTemplate } from \"./prompt-templates.ts\";\nimport { loadPromptTemplates } from \"./prompt-templates.ts\";\nimport { SettingsManager } from \"./settings-manager.ts\";\nimport type { Skill } from \"./skills.ts\";\nimport { loadSkills } from \"./skills.ts\";\nimport { createSourceInfo, type SourceInfo } from \"./source-info.ts\";\nimport { resetTimings } from \"./timings.ts\";\n\nexport interface ResourceExtensionPaths {\n\tskillPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tpromptPaths?: Array<{ path: string; metadata: PathMetadata }>;\n\tthemePaths?: Array<{ path: string; metadata: PathMetadata }>;\n}\n\nexport interface ResourceLoaderReloadOptions {\n\tresolveProjectTrust?: (input: { extensionsResult: LoadExtensionsResult }) => Promise<boolean>;\n}\n\nexport interface ResourceLoader {\n\tgetCwd(): string;\n\tgetAgentDir(): string;\n\tgetExtensions(): LoadExtensionsResult;\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\tgetAgentsFiles(): { agentsFiles: Array<{ path: string; content: string }> };\n\tgetSystemPrompt(): string | undefined;\n\tgetMemoryEngine(): import(\"./memory-engine/engine.ts\").MemoryEngine | null;\n\tgetVaultManager(): import(\"./memory-engine/vault.ts\").VaultManager | null;\n\tgetSystemPromptSource(): { path: string } | undefined;\n\tgetAppendSystemPrompt(): string[];\n\tgetAppendSystemPromptSources(): Array<{ path: string }>;\n\textendResources(paths: ResourceExtensionPaths): void;\n\treload(options?: ResourceLoaderReloadOptions): Promise<void>;\n}\n\nfunction resolvePromptInput(input: string | undefined, description: string): string | undefined {\n\tif (!input) {\n\t\treturn undefined;\n\t}\n\n\tif (existsSync(input)) {\n\t\ttry {\n\t\t\treturn readFileSync(input, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));\n\t\t\treturn input;\n\t\t}\n\t}\n\n\treturn input;\n}\n\nfunction loadContextFileFromDir(dir: string): { path: string; content: string } | null {\n\tconst candidates = [\"AGENTS.override.md\", \"AGENTS.md\", \"AGENTS.MD\", \"CLAUDE.md\", \"CLAUDE.MD\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\tif (!statSync(filePath).isFile()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * The main repo's context file that a nested linked worktree's own copy shadows: both\n * occupy the same logical repository scope, so loading both applies that context twice. Returns\n * undefined when nothing is shadowed, leaving normal ancestor inheritance alone.\n *\n * Returned canonicalized (realpath), because `git worktree add` writes the `.git`\n * file's `gitdir:` target in realpath form while cwd may still be symlinked\n * (macOS `/tmp` -> `/private/tmp`).\n */\nfunction findShadowedContextFile(cwd: string): string | undefined {\n\tconst gitPaths = findGitPaths(cwd);\n\tif (!gitPaths) return undefined;\n\tconst commonGitDir = canonicalizePath(gitPaths.commonGitDir);\n\tconst worktreeRoot = canonicalizePath(gitPaths.repoDir);\n\tconst mainRepoRoot = dirname(commonGitDir);\n\t// False for an ordinary repo, where the two are the same dir, and for a sibling\n\t// worktree (`git worktree add ../feat`), whose main repo is not an ancestor.\n\tif (!worktreeRoot.startsWith(`${mainRepoRoot}${sep}`)) return undefined;\n\t// dirname of the common git dir is the main worktree root only when that dir is\n\t// itself checked out from the same repo. In a bare layout (`proj/.bare` +\n\t// `proj/main`) it is just the directory holding `.bare`, which tracks nothing; a\n\t// submodule's gitdir has no `commondir`, so it lands under `.git/modules`.\n\tif (canonicalizePath(join(mainRepoRoot, \".git\")) !== commonGitDir) return undefined;\n\tconst worktreeContextFile = loadContextFileFromDir(worktreeRoot);\n\treturn worktreeContextFile ? join(mainRepoRoot, basename(worktreeContextFile.path)) : undefined;\n}\n\nexport function loadProjectContextFiles(options: {\n\tcwd: string;\n\tagentDir: string;\n\tprojectMemoryEnabled?: boolean;\n\tmemoryEngine?: import(\"./memory-engine/engine.ts\").MemoryEngine | null;\n}): Array<{ path: string; content: string }> {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\n\tconst contextFiles: Array<{ path: string; content: string }> = [];\n\tconst seenPaths = new Set<string>();\n\n\tconst globalContext = loadContextFileFromDir(resolvedAgentDir);\n\tif (globalContext) {\n\t\tcontextFiles.push(globalContext);\n\t\tseenPaths.add(globalContext.path);\n\t}\n\n\tconst ancestorContextFiles: Array<{ path: string; content: string }> = [];\n\n\tconst shadowedContextFile = findShadowedContextFile(resolvedCwd);\n\tlet currentDir = resolvedCwd;\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tconst isShadowed =\n\t\t\tshadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? \"\") === shadowedContextFile;\n\t\tif (contextFile && !isShadowed && !seenPaths.has(contextFile.path)) {\n\t\t\tancestorContextFiles.unshift(contextFile);\n\t\t\tseenPaths.add(contextFile.path);\n\t\t}\n\n\t\tconst parentDir = dirname(currentDir);\n\t\tif (parentDir === currentDir) break;\n\t\tcurrentDir = parentDir;\n\t}\n\n\tcontextFiles.push(...ancestorContextFiles);\n\n\t// Shared project memory: the Ada Memory Engine block (policy or inject\n\t// mode). Falls back to the legacy project-memory file when the engine is\n\t// unavailable or disabled. Included last so it reads as the freshest\n\t// project context.\n\tconst memoryBlock = options.memoryEngine?.getPromptBlockSync() ?? \"\";\n\tif (memoryBlock) {\n\t\tcontextFiles.push({ path: \"<ada-memory>\", content: memoryBlock });\n\t} else {\n\t\tconst memoryFile = (options.projectMemoryEnabled ?? true)\n\t\t\t? loadProjectMemorySync(resolvedCwd, resolvedAgentDir)\n\t\t\t: null;\n\t\tif (memoryFile) {\n\t\t\tcontextFiles.push(memoryFile);\n\t\t}\n\t}\n\n\treturn contextFiles;\n}\n\nexport interface DefaultResourceLoaderOptions {\n\tcwd: string;\n\tagentDir: string;\n\tsettingsManager?: SettingsManager;\n\teventBus?: EventBus;\n\tadditionalExtensionPaths?: string[];\n\tadditionalSkillPaths?: string[];\n\tadditionalPromptTemplatePaths?: string[];\n\tadditionalThemePaths?: string[];\n\textensionFactories?: InlineExtension[];\n\tnoExtensions?: boolean;\n\tnoSkills?: boolean;\n\tnoPromptTemplates?: boolean;\n\tnoThemes?: boolean;\n\tnoContextFiles?: boolean;\n\tsystemPrompt?: string;\n\tappendSystemPrompt?: string[];\n\textensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tskillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tpromptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tthemesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tagentsFilesOverride?: (base: { agentsFiles: Array<{ path: string; content: string }> }) => {\n\t\tagentsFiles: Array<{ path: string; content: string }>;\n\t};\n\tsystemPromptOverride?: (base: string | undefined) => string | undefined;\n\tappendSystemPromptOverride?: (base: string[]) => string[];\n}\n\nexport class DefaultResourceLoader implements ResourceLoader {\n\tprivate cwd: string;\n\tprivate agentDir: string;\n\tprivate settingsManager: SettingsManager;\n\n\tgetCwd(): string {\n\t\treturn this.cwd;\n\t}\n\n\tgetAgentDir(): string {\n\t\treturn this.agentDir;\n\t}\n\tprivate eventBus: EventBus;\n\tprivate packageManager: DefaultPackageManager;\tprivate additionalExtensionPaths: string[];\n\tprivate additionalSkillPaths: string[];\n\tprivate additionalPromptTemplatePaths: string[];\n\tprivate additionalThemePaths: string[];\n\tprivate extensionFactories: InlineExtension[];\n\tprivate noExtensions: boolean;\n\tprivate noSkills: boolean;\n\tprivate noPromptTemplates: boolean;\n\tprivate noThemes: boolean;\n\tprivate noContextFiles: boolean;\n\tprivate memoryEngine: import(\"./memory-engine/engine.ts\").MemoryEngine | null = null;\n\tprivate vaultManager: import(\"./memory-engine/vault.ts\").VaultManager | null = null;\n\tprivate systemPromptSource?: string;\n\tprivate appendSystemPromptSource?: string[];\n\tprivate extensionsOverride?: (base: LoadExtensionsResult) => LoadExtensionsResult;\n\tprivate skillsOverride?: (base: { skills: Skill[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tskills: Skill[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate promptsOverride?: (base: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tprompts: PromptTemplate[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate themesOverride?: (base: { themes: Theme[]; diagnostics: ResourceDiagnostic[] }) => {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t};\n\tprivate agentsFilesOverride?: (base: { agentsFiles: Array<{ path: string; content: string }> }) => {\n\t\tagentsFiles: Array<{ path: string; content: string }>;\n\t};\n\tprivate systemPromptOverride?: (base: string | undefined) => string | undefined;\n\tprivate appendSystemPromptOverride?: (base: string[]) => string[];\n\n\tprivate extensionsResult: LoadExtensionsResult;\n\tprivate skills: Skill[];\n\tprivate skillDiagnostics: ResourceDiagnostic[];\n\tprivate prompts: PromptTemplate[];\n\tprivate promptDiagnostics: ResourceDiagnostic[];\n\tprivate themes: Theme[];\n\tprivate themeDiagnostics: ResourceDiagnostic[];\n\tprivate agentsFiles: Array<{ path: string; content: string }>;\n\tprivate systemPrompt?: string;\n\tprivate systemPromptSourcePath?: string;\n\tprivate appendSystemPrompt: string[];\n\tprivate appendSystemPromptSourcePaths: string[];\n\tprivate lastSkillPaths: string[];\n\tprivate extensionSkillSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionPromptSourceInfos: Map<string, SourceInfo>;\n\tprivate extensionThemeSourceInfos: Map<string, SourceInfo>;\n\tprivate resourceMetadataByPath: Map<string, PathMetadata>;\n\tprivate lastPromptPaths: string[];\n\tprivate lastThemePaths: string[];\n\tprivate loaded: boolean;\n\n\tconstructor(options: DefaultResourceLoaderOptions) {\n\t\tthis.cwd = resolvePath(options.cwd);\n\t\tthis.agentDir = resolvePath(options.agentDir);\n\t\tthis.settingsManager = options.settingsManager ?? SettingsManager.create(this.cwd, this.agentDir);\n\t\t// Ada Memory Engine — shared across CLI and desktop; lazy-initialized.\n\t\tthis.memoryEngine = new MemoryEngine({\n\t\t\tagentDir: this.agentDir,\n\t\t\tcwd: this.cwd,\n\t\t\tgetConfig: () => this.settingsManager.getMemoryEngineConfig(),\n\t\t});\n\t\t// Ada Secure Vault — encrypted secrets (project + session scopes).\n\t\tthis.vaultManager = new VaultManager(this.agentDir, this.memoryEngine.projectHash);\n\t\tthis.vaultManager.setAudit((entry) => this.memoryEngine?.recordAudit(entry));\n\t\tthis.eventBus = options.eventBus ?? createEventBus();\n\t\tthis.packageManager = new DefaultPackageManager({\n\t\t\tcwd: this.cwd,\n\t\t\tagentDir: this.agentDir,\n\t\t\tsettingsManager: this.settingsManager,\n\t\t});\n\t\tthis.additionalExtensionPaths = options.additionalExtensionPaths ?? [];\n\t\tthis.additionalSkillPaths = options.additionalSkillPaths ?? [];\n\t\tthis.additionalPromptTemplatePaths = options.additionalPromptTemplatePaths ?? [];\n\t\tthis.additionalThemePaths = options.additionalThemePaths ?? [];\n\t\tthis.extensionFactories = options.extensionFactories ?? [];\n\t\tthis.noExtensions = options.noExtensions ?? false;\n\t\tthis.noSkills = options.noSkills ?? false;\n\t\tthis.noPromptTemplates = options.noPromptTemplates ?? false;\n\t\tthis.noThemes = options.noThemes ?? false;\n\t\tthis.noContextFiles = options.noContextFiles ?? false;\n\t\tthis.systemPromptSource = options.systemPrompt;\n\t\tthis.appendSystemPromptSource = options.appendSystemPrompt;\n\t\tthis.extensionsOverride = options.extensionsOverride;\n\t\tthis.skillsOverride = options.skillsOverride;\n\t\tthis.promptsOverride = options.promptsOverride;\n\t\tthis.themesOverride = options.themesOverride;\n\t\tthis.agentsFilesOverride = options.agentsFilesOverride;\n\t\tthis.systemPromptOverride = options.systemPromptOverride;\n\t\tthis.appendSystemPromptOverride = options.appendSystemPromptOverride;\n\n\t\tthis.extensionsResult = { extensions: [], errors: [], runtime: createExtensionRuntime() };\n\t\tthis.skills = [];\n\t\tthis.skillDiagnostics = [];\n\t\tthis.prompts = [];\n\t\tthis.promptDiagnostics = [];\n\t\tthis.themes = [];\n\t\tthis.themeDiagnostics = [];\n\t\tthis.agentsFiles = [];\n\t\tthis.appendSystemPrompt = [];\n\t\tthis.appendSystemPromptSourcePaths = [];\n\t\tthis.lastSkillPaths = [];\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\t\tthis.resourceMetadataByPath = new Map();\n\t\tthis.lastPromptPaths = [];\n\t\tthis.lastThemePaths = [];\n\t\tthis.loaded = false;\n\t}\n\n\tgetExtensions(): LoadExtensionsResult {\n\t\treturn this.extensionsResult;\n\t}\n\n\tgetSkills(): { skills: Skill[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { skills: this.skills, diagnostics: this.skillDiagnostics };\n\t}\n\n\tgetPrompts(): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { prompts: this.prompts, diagnostics: this.promptDiagnostics };\n\t}\n\n\tgetThemes(): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\treturn { themes: this.themes, diagnostics: this.themeDiagnostics };\n\t}\n\n\tgetAgentsFiles(): { agentsFiles: Array<{ path: string; content: string }> } {\n\t\treturn { agentsFiles: this.agentsFiles };\n\t}\n\n\tgetMemoryEngine(): import(\"./memory-engine/engine.ts\").MemoryEngine | null {\n\t\treturn this.memoryEngine;\n\t}\n\n\tgetVaultManager(): import(\"./memory-engine/vault.ts\").VaultManager | null {\n\t\treturn this.vaultManager;\n\t}\n\n\tgetSystemPrompt(): string | undefined {\n\t\treturn this.systemPrompt;\n\t}\n\n\tgetSystemPromptSource(): { path: string } | undefined {\n\t\treturn this.systemPromptSourcePath ? { path: this.systemPromptSourcePath } : undefined;\n\t}\n\n\tgetAppendSystemPrompt(): string[] {\n\t\treturn this.appendSystemPrompt;\n\t}\n\n\tgetAppendSystemPromptSources(): Array<{ path: string }> {\n\t\treturn this.appendSystemPromptSourcePaths.map((path) => ({ path }));\n\t}\n\n\textendResources(paths: ResourceExtensionPaths): void {\n\t\tconst skillPaths = this.normalizeExtensionPaths(paths.skillPaths ?? []);\n\t\tconst promptPaths = this.normalizeExtensionPaths(paths.promptPaths ?? []);\n\t\tconst themePaths = this.normalizeExtensionPaths(paths.themePaths ?? []);\n\n\t\tfor (const entry of skillPaths) {\n\t\t\tthis.extensionSkillSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of promptPaths) {\n\t\t\tthis.extensionPromptSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\t\tfor (const entry of themePaths) {\n\t\t\tthis.extensionThemeSourceInfos.set(entry.path, createSourceInfo(entry.path, entry.metadata));\n\t\t}\n\n\t\tif (skillPaths.length > 0) {\n\t\t\tthis.lastSkillPaths = this.mergePaths(\n\t\t\t\tthis.lastSkillPaths,\n\t\t\t\tskillPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateSkillsFromPaths(this.lastSkillPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (promptPaths.length > 0) {\n\t\t\tthis.lastPromptPaths = this.mergePaths(\n\t\t\t\tthis.lastPromptPaths,\n\t\t\t\tpromptPaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updatePromptsFromPaths(this.lastPromptPaths, this.resourceMetadataByPath);\n\t\t}\n\n\t\tif (themePaths.length > 0) {\n\t\t\tthis.lastThemePaths = this.mergePaths(\n\t\t\t\tthis.lastThemePaths,\n\t\t\t\tthemePaths.map((entry) => entry.path),\n\t\t\t);\n\t\t\tthis.updateThemesFromPaths(this.lastThemePaths, this.resourceMetadataByPath);\n\t\t}\n\t}\n\n\tasync loadProjectTrustExtensions(): Promise<LoadExtensionsResult> {\n\t\t// Force untrusted project settings for the bootstrap pass. This keeps project-local\n\t\t// extensions/packages out while still loading user/global and temporary CLI extensions.\n\t\tthis.settingsManager.setProjectTrusted(false);\n\t\tawait this.settingsManager.reload();\n\t\treturn this.loadCurrentExtensionSet({ includeInlineFactories: true });\n\t}\n\n\tasync reload(options?: ResourceLoaderReloadOptions): Promise<void> {\n\t\tresetTimings(\"extensions\");\n\n\t\tif (this.loaded) {\n\t\t\tclearExtensionCache();\n\t\t}\n\n\t\tlet preTrustExtensions: LoadExtensionsResult | undefined;\n\t\tif (options?.resolveProjectTrust) {\n\t\t\tpreTrustExtensions = await this.loadProjectTrustExtensions();\n\t\t\tconst projectTrusted = await options.resolveProjectTrust({ extensionsResult: preTrustExtensions });\n\t\t\tthis.settingsManager.setProjectTrusted(projectTrusted);\n\t\t}\n\n\t\t// reload() preserves SettingsManager.projectTrusted and reloads settings for that trust state.\n\t\tawait this.settingsManager.reload();\n\t\tconst resolvedPaths = await this.packageManager.resolve();\n\t\tconst cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {\n\t\t\ttemporary: true,\n\t\t});\n\t\t// Kept on the instance so post-reload passes (extendResources) can still resolve package metadata.\n\t\tthis.resourceMetadataByPath = new Map();\n\t\tconst metadataByPath = this.resourceMetadataByPath;\n\n\t\tthis.extensionSkillSourceInfos = new Map();\n\t\tthis.extensionPromptSourceInfos = new Map();\n\t\tthis.extensionThemeSourceInfos = new Map();\n\n\t\t// Helper to extract enabled paths and store metadata\n\t\tconst getEnabledResources = (resources: ResolvedResource[]): ResolvedResource[] => {\n\t\t\tfor (const r of resources) {\n\t\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\t\tmetadataByPath.set(r.path, r.metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn resources.filter((r) => r.enabled);\n\t\t};\n\n\t\tconst getEnabledPaths = (resources: ResolvedResource[]): string[] =>\n\t\t\tgetEnabledResources(resources).map((r) => r.path);\n\t\tconst enabledExtensions = getEnabledPaths(resolvedPaths.extensions);\n\t\tconst enabledSkillResources = getEnabledResources(resolvedPaths.skills);\n\t\tconst enabledPrompts = getEnabledPaths(resolvedPaths.prompts);\n\t\tconst enabledThemes = getEnabledPaths(resolvedPaths.themes);\n\n\t\tconst enabledSkills = enabledSkillResources.map((resource) => this.mapSkillPath(resource, metadataByPath));\n\n\t\t// Add CLI paths metadata\n\t\tfor (const r of cliExtensionPaths.extensions) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\t\tfor (const r of cliExtensionPaths.skills) {\n\t\t\tif (!metadataByPath.has(r.path)) {\n\t\t\t\tmetadataByPath.set(r.path, { source: \"cli\", scope: \"temporary\", origin: \"top-level\" });\n\t\t\t}\n\t\t}\n\n\t\tconst cliEnabledExtensions = getEnabledPaths(cliExtensionPaths.extensions);\n\t\tconst cliEnabledSkills = getEnabledPaths(cliExtensionPaths.skills);\n\t\tconst cliEnabledPrompts = getEnabledPaths(cliExtensionPaths.prompts);\n\t\tconst cliEnabledThemes = getEnabledPaths(cliExtensionPaths.themes);\n\n\t\tconst extensionPaths = this.noExtensions\n\t\t\t? cliEnabledExtensions\n\t\t\t: this.mergePaths(cliEnabledExtensions, enabledExtensions);\n\n\t\tconst extensionsResult = await this.loadFinalExtensionSet(extensionPaths, preTrustExtensions);\n\t\tfor (const p of this.additionalExtensionPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved)) {\n\t\t\t\t\textensionsResult.errors.push({ path: resolved, error: `Extension path does not exist: ${resolved}` });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;\n\t\tthis.applyExtensionSourceInfo(this.extensionsResult.extensions, metadataByPath);\n\n\t\tconst skillPaths = this.noSkills\n\t\t\t? this.mergePaths(cliEnabledSkills, this.additionalSkillPaths)\n\t\t\t: this.mergePaths([...cliEnabledSkills, ...enabledSkills], this.additionalSkillPaths);\n\n\t\tthis.lastSkillPaths = skillPaths;\n\t\tthis.updateSkillsFromPaths(skillPaths, metadataByPath);\n\t\tfor (const p of this.additionalSkillPaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.skillDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.skillDiagnostics.push({ type: \"error\", message: \"Skill path does not exist\", path: resolved });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst promptPaths = this.noPromptTemplates\n\t\t\t? this.mergePaths(cliEnabledPrompts, this.additionalPromptTemplatePaths)\n\t\t\t: this.mergePaths([...cliEnabledPrompts, ...enabledPrompts], this.additionalPromptTemplatePaths);\n\n\t\tthis.lastPromptPaths = promptPaths;\n\t\tthis.updatePromptsFromPaths(promptPaths, metadataByPath);\n\t\tfor (const p of this.additionalPromptTemplatePaths) {\n\t\t\tif (isLocalPath(p)) {\n\t\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\t\tif (!existsSync(resolved) && !this.promptDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\t\tthis.promptDiagnostics.push({\n\t\t\t\t\t\ttype: \"error\",\n\t\t\t\t\t\tmessage: \"Prompt template path does not exist\",\n\t\t\t\t\t\tpath: resolved,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst themePaths = this.noThemes\n\t\t\t? this.mergePaths(cliEnabledThemes, this.additionalThemePaths)\n\t\t\t: this.mergePaths([...cliEnabledThemes, ...enabledThemes], this.additionalThemePaths);\n\n\t\tthis.lastThemePaths = themePaths;\n\t\tthis.updateThemesFromPaths(themePaths, metadataByPath);\n\t\tfor (const p of this.additionalThemePaths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved) && !this.themeDiagnostics.some((d) => d.path === resolved)) {\n\t\t\t\tthis.themeDiagnostics.push({ type: \"error\", message: \"Theme path does not exist\", path: resolved });\n\t\t\t}\n\t\t}\n\n\t\tconst agentsFiles = {\n\t\t\tagentsFiles: this.noContextFiles\n\t\t\t\t? []\n\t\t\t\t: loadProjectContextFiles({\n\t\t\t\t\t\tcwd: this.cwd,\n\t\t\t\t\t\tagentDir: this.agentDir,\n\t\t\t\t\t\tprojectMemoryEnabled: this.settingsManager.getProjectMemoryEnabled(),\n\t\t\t\t\t\tmemoryEngine: this.memoryEngine,\n\t\t\t\t\t}),\n\t\t};\n\t\tconst resolvedAgentsFiles = this.agentsFilesOverride ? this.agentsFilesOverride(agentsFiles) : agentsFiles;\n\t\tthis.agentsFiles = resolvedAgentsFiles.agentsFiles;\n\n\t\tconst systemPromptSource = this.systemPromptSource ?? this.discoverSystemPromptFile();\n\t\tconst baseSystemPrompt = resolvePromptInput(systemPromptSource, \"system prompt\");\n\t\tthis.systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(baseSystemPrompt) : baseSystemPrompt;\n\t\tthis.systemPromptSourcePath =\n\t\t\tsystemPromptSource && existsSync(systemPromptSource) ? resolvePath(systemPromptSource) : undefined;\n\n\t\tlet appendSources = this.appendSystemPromptSource;\n\t\tif (!appendSources) {\n\t\t\tconst discoveredAppendSystemPromptFile = this.discoverAppendSystemPromptFile();\n\t\t\tappendSources = discoveredAppendSystemPromptFile ? [discoveredAppendSystemPromptFile] : [];\n\t\t}\n\t\tconst baseAppend = appendSources\n\t\t\t.map((s) => resolvePromptInput(s, \"append system prompt\"))\n\t\t\t.filter((s): s is string => s !== undefined);\n\t\tthis.appendSystemPrompt = this.appendSystemPromptOverride\n\t\t\t? this.appendSystemPromptOverride(baseAppend)\n\t\t\t: baseAppend;\n\t\tthis.appendSystemPromptSourcePaths = appendSources\n\t\t\t.filter((source) => existsSync(source))\n\t\t\t.map((source) => resolvePath(source));\n\t\tthis.loaded = true;\n\t}\n\n\tprivate async loadCurrentExtensionSet(options: { includeInlineFactories: boolean }): Promise<LoadExtensionsResult> {\n\t\tconst resolvedPaths = await this.packageManager.resolve();\n\t\tconst cliExtensionPaths = await this.packageManager.resolveExtensionSources(this.additionalExtensionPaths, {\n\t\t\ttemporary: true,\n\t\t});\n\t\tconst enabledExtensions = resolvedPaths.extensions.filter((r) => r.enabled).map((r) => r.path);\n\t\tconst cliEnabledExtensions = cliExtensionPaths.extensions.filter((r) => r.enabled).map((r) => r.path);\n\t\tconst extensionPaths = this.noExtensions\n\t\t\t? cliEnabledExtensions\n\t\t\t: this.mergePaths(cliEnabledExtensions, enabledExtensions);\n\t\tconst extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);\n\t\tif (!options.includeInlineFactories) {\n\t\t\treturn extensionsResult;\n\t\t}\n\n\t\tconst inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);\n\t\textensionsResult.extensions.push(...inlineExtensions.extensions);\n\t\textensionsResult.errors.push(...inlineExtensions.errors);\n\t\treturn extensionsResult;\n\t}\n\n\tprivate resolveExtensionLoadPath(path: string): string {\n\t\treturn resolvePath(path, this.cwd, { normalizeUnicodeSpaces: true });\n\t}\n\n\tprivate async loadFinalExtensionSet(\n\t\textensionPaths: string[],\n\t\tpreTrustExtensions: LoadExtensionsResult | undefined,\n\t): Promise<LoadExtensionsResult> {\n\t\tif (!preTrustExtensions) {\n\t\t\tconst extensionsResult = await loadExtensionsCached(extensionPaths, this.cwd, this.eventBus);\n\t\t\tconst inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime);\n\t\t\textensionsResult.extensions.push(...inlineExtensions.extensions);\n\t\t\textensionsResult.errors.push(...inlineExtensions.errors);\n\t\t\tthis.addExtensionConflictDiagnostics(extensionsResult);\n\t\t\treturn extensionsResult;\n\t\t}\n\n\t\tconst preloadedByPath = new Map(\n\t\t\tpreTrustExtensions.extensions\n\t\t\t\t.filter((extension) => !extension.path.startsWith(\"<inline:\"))\n\t\t\t\t.map((extension) => [extension.resolvedPath, extension]),\n\t\t);\n\t\tconst failedPreloadPaths = new Set(\n\t\t\tpreTrustExtensions.errors.map((error) => this.resolveExtensionLoadPath(error.path)),\n\t\t);\n\t\tconst remainingPaths = extensionPaths.filter((path) => {\n\t\t\tconst resolvedPath = this.resolveExtensionLoadPath(path);\n\t\t\treturn !preloadedByPath.has(resolvedPath) && !failedPreloadPaths.has(resolvedPath);\n\t\t});\n\t\tconst remainingExtensions = await loadExtensionsCached(\n\t\t\tremainingPaths,\n\t\t\tthis.cwd,\n\t\t\tthis.eventBus,\n\t\t\tpreTrustExtensions.runtime,\n\t\t);\n\t\tconst loadedByPath = new Map(preloadedByPath);\n\t\tfor (const extension of remainingExtensions.extensions) {\n\t\t\tloadedByPath.set(extension.resolvedPath, extension);\n\t\t}\n\n\t\tconst inlineExtensions = preTrustExtensions.extensions.filter((extension) =>\n\t\t\textension.path.startsWith(\"<inline:\"),\n\t\t);\n\t\tconst orderedExtensions = extensionPaths\n\t\t\t.map((path) => loadedByPath.get(this.resolveExtensionLoadPath(path)))\n\t\t\t.filter((extension): extension is Extension => extension !== undefined);\n\t\torderedExtensions.push(...inlineExtensions);\n\n\t\tconst extensionsResult: LoadExtensionsResult = {\n\t\t\textensions: orderedExtensions,\n\t\t\terrors: [...preTrustExtensions.errors, ...remainingExtensions.errors],\n\t\t\truntime: preTrustExtensions.runtime,\n\t\t};\n\t\tthis.addExtensionConflictDiagnostics(extensionsResult);\n\t\treturn extensionsResult;\n\t}\n\n\tprivate addExtensionConflictDiagnostics(extensionsResult: LoadExtensionsResult): void {\n\t\t// Detect extension conflicts (tools, commands, flags with same names from different extensions)\n\t\t// Keep all extensions loaded. Conflicts are reported as diagnostics, and precedence is handled by load order.\n\t\tconst conflicts = this.detectExtensionConflicts(extensionsResult.extensions);\n\t\tfor (const conflict of conflicts) {\n\t\t\textensionsResult.errors.push({ path: conflict.path, error: conflict.message });\n\t\t}\n\t}\n\n\tprivate mapSkillPath(resource: ResolvedResource, metadataByPath: Map<string, PathMetadata>): string {\n\t\tif (resource.metadata.source !== \"auto\" && resource.metadata.origin !== \"package\") {\n\t\t\treturn resource.path;\n\t\t}\n\t\ttry {\n\t\t\tconst stats = statSync(resource.path);\n\t\t\tif (!stats.isDirectory()) {\n\t\t\t\treturn resource.path;\n\t\t\t}\n\t\t} catch {\n\t\t\treturn resource.path;\n\t\t}\n\t\tconst skillFile = join(resource.path, \"SKILL.md\");\n\t\tif (existsSync(skillFile)) {\n\t\t\tif (!metadataByPath.has(skillFile)) {\n\t\t\t\tmetadataByPath.set(skillFile, resource.metadata);\n\t\t\t}\n\t\t\treturn skillFile;\n\t\t}\n\t\treturn resource.path;\n\t}\n\n\tprivate normalizeExtensionPaths(\n\t\tentries: Array<{ path: string; metadata: PathMetadata }>,\n\t): Array<{ path: string; metadata: PathMetadata }> {\n\t\treturn entries.map((entry) => {\n\t\t\tconst metadata = entry.metadata.baseDir\n\t\t\t\t? { ...entry.metadata, baseDir: this.resolveResourcePath(entry.metadata.baseDir) }\n\t\t\t\t: entry.metadata;\n\t\t\treturn {\n\t\t\t\tpath: this.resolveResourcePath(entry.path),\n\t\t\t\tmetadata,\n\t\t\t};\n\t\t});\n\t}\n\n\tprivate updateSkillsFromPaths(skillPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet skillsResult: { skills: Skill[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noSkills && skillPaths.length === 0) {\n\t\t\tskillsResult = { skills: [], diagnostics: [] };\n\t\t} else {\n\t\t\tskillsResult = loadSkills({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tskillPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t});\n\t\t}\n\t\tconst resolvedSkills = this.skillsOverride ? this.skillsOverride(skillsResult) : skillsResult;\n\t\tthis.skills = resolvedSkills.skills.map((skill) => ({\n\t\t\t...skill,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(skill.filePath, this.extensionSkillSourceInfos, metadataByPath) ??\n\t\t\t\tskill.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(skill.filePath),\n\t\t}));\n\t\tthis.skillDiagnostics = resolvedSkills.diagnostics;\n\t}\n\n\tprivate updatePromptsFromPaths(promptPaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet promptsResult: { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noPromptTemplates && promptPaths.length === 0) {\n\t\t\tpromptsResult = { prompts: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst allPrompts = loadPromptTemplates({\n\t\t\t\tcwd: this.cwd,\n\t\t\t\tagentDir: this.agentDir,\n\t\t\t\tpromptPaths,\n\t\t\t\tincludeDefaults: false,\n\t\t\t});\n\t\t\tpromptsResult = this.dedupePrompts(allPrompts);\n\t\t}\n\t\tconst resolvedPrompts = this.promptsOverride ? this.promptsOverride(promptsResult) : promptsResult;\n\t\tthis.prompts = resolvedPrompts.prompts.map((prompt) => ({\n\t\t\t...prompt,\n\t\t\tsourceInfo:\n\t\t\t\tthis.findSourceInfoForPath(prompt.filePath, this.extensionPromptSourceInfos, metadataByPath) ??\n\t\t\t\tprompt.sourceInfo ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(prompt.filePath),\n\t\t}));\n\t\tthis.promptDiagnostics = resolvedPrompts.diagnostics;\n\t}\n\n\tprivate updateThemesFromPaths(themePaths: string[], metadataByPath?: Map<string, PathMetadata>): void {\n\t\tlet themesResult: { themes: Theme[]; diagnostics: ResourceDiagnostic[] };\n\t\tif (this.noThemes && themePaths.length === 0) {\n\t\t\tthemesResult = { themes: [], diagnostics: [] };\n\t\t} else {\n\t\t\tconst loaded = this.loadThemes(themePaths, false);\n\t\t\tconst deduped = this.dedupeThemes(loaded.themes);\n\t\t\tthemesResult = { themes: deduped.themes, diagnostics: [...loaded.diagnostics, ...deduped.diagnostics] };\n\t\t}\n\t\tconst resolvedThemes = this.themesOverride ? this.themesOverride(themesResult) : themesResult;\n\t\tthis.themes = resolvedThemes.themes.map((theme) => {\n\t\t\tconst sourcePath = theme.sourcePath;\n\t\t\ttheme.sourceInfo = sourcePath\n\t\t\t\t? (this.findSourceInfoForPath(sourcePath, this.extensionThemeSourceInfos, metadataByPath) ??\n\t\t\t\t\ttheme.sourceInfo ??\n\t\t\t\t\tthis.getDefaultSourceInfoForPath(sourcePath))\n\t\t\t\t: theme.sourceInfo;\n\t\t\treturn theme;\n\t\t});\n\t\tthis.themeDiagnostics = resolvedThemes.diagnostics;\n\t}\n\n\tprivate applyExtensionSourceInfo(extensions: Extension[], metadataByPath: Map<string, PathMetadata>): void {\n\t\tfor (const extension of extensions) {\n\t\t\textension.sourceInfo =\n\t\t\t\tthis.findSourceInfoForPath(extension.path, undefined, metadataByPath) ??\n\t\t\t\tthis.getDefaultSourceInfoForPath(extension.path);\n\t\t\tfor (const command of extension.commands.values()) {\n\t\t\t\tcommand.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t\tfor (const tool of extension.tools.values()) {\n\t\t\t\ttool.sourceInfo = extension.sourceInfo;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate findSourceInfoForPath(\n\t\tresourcePath: string,\n\t\textraSourceInfos?: Map<string, SourceInfo>,\n\t\tmetadataByPath?: Map<string, PathMetadata>,\n\t): SourceInfo | undefined {\n\t\tif (!resourcePath) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tif (resourcePath.startsWith(\"<\")) {\n\t\t\treturn this.getDefaultSourceInfoForPath(resourcePath);\n\t\t}\n\n\t\tconst normalizedResourcePath = resolve(resourcePath);\n\t\tif (extraSourceInfos) {\n\t\t\tfor (const [sourcePath, sourceInfo] of extraSourceInfos.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn { ...sourceInfo, path: resourcePath };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (metadataByPath) {\n\t\t\tconst exact = metadataByPath.get(normalizedResourcePath) ?? metadataByPath.get(resourcePath);\n\t\t\tif (exact) {\n\t\t\t\treturn createSourceInfo(resourcePath, exact);\n\t\t\t}\n\n\t\t\tfor (const [sourcePath, metadata] of metadataByPath.entries()) {\n\t\t\t\tconst normalizedSourcePath = resolve(sourcePath);\n\t\t\t\tif (\n\t\t\t\t\tnormalizedResourcePath === normalizedSourcePath ||\n\t\t\t\t\tnormalizedResourcePath.startsWith(`${normalizedSourcePath}${sep}`)\n\t\t\t\t) {\n\t\t\t\t\treturn createSourceInfo(resourcePath, metadata);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate getDefaultSourceInfoForPath(filePath: string): SourceInfo {\n\t\tif (filePath.startsWith(\"<\") && filePath.endsWith(\">\")) {\n\t\t\treturn {\n\t\t\t\tpath: filePath,\n\t\t\t\tsource: filePath.slice(1, -1).split(\":\")[0] || \"temporary\",\n\t\t\t\tscope: \"temporary\",\n\t\t\t\torigin: \"top-level\",\n\t\t\t};\n\t\t}\n\n\t\tconst normalizedPath = resolve(filePath);\n\t\tconst agentRoots = [\n\t\t\tjoin(this.agentDir, \"skills\"),\n\t\t\tjoin(this.agentDir, \"prompts\"),\n\t\t\tjoin(this.agentDir, \"themes\"),\n\t\t\tjoin(this.agentDir, \"extensions\"),\n\t\t];\n\t\tconst projectRoots = [\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"skills\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"prompts\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"themes\"),\n\t\t\tjoin(this.cwd, CONFIG_DIR_NAME, \"extensions\"),\n\t\t];\n\n\t\tfor (const root of agentRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"user\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\tfor (const root of projectRoots) {\n\t\t\tif (this.isUnderPath(normalizedPath, root)) {\n\t\t\t\treturn { path: filePath, source: \"local\", scope: \"project\", origin: \"top-level\", baseDir: root };\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tpath: filePath,\n\t\t\tsource: \"local\",\n\t\t\tscope: \"temporary\",\n\t\t\torigin: \"top-level\",\n\t\t\tbaseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, \"..\"),\n\t\t};\n\t}\n\n\tprivate mergePaths(primary: string[], additional: string[]): string[] {\n\t\tconst merged: string[] = [];\n\t\tconst seen = new Set<string>();\n\n\t\tfor (const p of [...primary, ...additional]) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tconst canonicalPath = canonicalizePath(resolved);\n\t\t\tif (seen.has(canonicalPath)) continue;\n\t\t\tseen.add(canonicalPath);\n\t\t\tmerged.push(resolved);\n\t\t}\n\n\t\treturn merged;\n\t}\n\n\tprivate resolveResourcePath(p: string): string {\n\t\treturn resolvePath(p, this.cwd, { trim: true });\n\t}\n\n\tprivate loadThemes(\n\t\tpaths: string[],\n\t\tincludeDefaults: boolean = true,\n\t): {\n\t\tthemes: Theme[];\n\t\tdiagnostics: ResourceDiagnostic[];\n\t} {\n\t\tconst themes: Theme[] = [];\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\t\tif (includeDefaults) {\n\t\t\tconst defaultDirs = [join(this.agentDir, \"themes\"), join(this.cwd, CONFIG_DIR_NAME, \"themes\")];\n\n\t\t\tfor (const dir of defaultDirs) {\n\t\t\t\tthis.loadThemesFromDir(dir, themes, diagnostics);\n\t\t\t}\n\t\t}\n\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = this.resolveResourcePath(p);\n\t\t\tif (!existsSync(resolved)) {\n\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path does not exist\", path: resolved });\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst stats = statSync(resolved);\n\t\t\t\tif (stats.isDirectory()) {\n\t\t\t\t\tthis.loadThemesFromDir(resolved, themes, diagnostics);\n\t\t\t\t} else if (stats.isFile() && resolved.endsWith(\".json\")) {\n\t\t\t\t\tthis.loadThemeFromFile(resolved, themes, diagnostics);\n\t\t\t\t} else {\n\t\t\t\t\tdiagnostics.push({ type: \"warning\", message: \"theme path is not a json file\", path: resolved });\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme path\";\n\t\t\t\tdiagnostics.push({ type: \"warning\", message, path: resolved });\n\t\t\t}\n\t\t}\n\n\t\treturn { themes, diagnostics };\n\t}\n\n\tprivate loadThemesFromDir(dir: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\tif (!existsSync(dir)) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\t\t\tfor (const entry of entries) {\n\t\t\t\tlet isFile = entry.isFile();\n\t\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tisFile = statSync(join(dir, entry.name)).isFile();\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!isFile) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (!entry.name.endsWith(\".json\")) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthis.loadThemeFromFile(join(dir, entry.name), themes, diagnostics);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to read theme directory\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: dir });\n\t\t}\n\t}\n\n\tprivate loadThemeFromFile(filePath: string, themes: Theme[], diagnostics: ResourceDiagnostic[]): void {\n\t\ttry {\n\t\t\tthemes.push(loadThemeFromPath(filePath));\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : \"failed to load theme\";\n\t\t\tdiagnostics.push({ type: \"warning\", message, path: filePath });\n\t\t}\n\t}\n\n\tprivate async loadExtensionFactories(runtime: ExtensionRuntime): Promise<{\n\t\textensions: Extension[];\n\t\terrors: Array<{ path: string; error: string }>;\n\t}> {\n\t\tconst extensions: Extension[] = [];\n\t\tconst errors: Array<{ path: string; error: string }> = [];\n\n\t\tfor (const [index, input] of this.extensionFactories.entries()) {\n\t\t\tconst isNamed = typeof input !== \"function\";\n\t\t\tconst factory = isNamed ? input.factory : input;\n\t\t\tconst extensionPath = `<inline:${isNamed ? input.name : index + 1}>`;\n\t\t\ttry {\n\t\t\t\tconst extension = await loadExtensionFromFactory(factory, this.cwd, this.eventBus, runtime, extensionPath);\n\t\t\t\textension.hidden = isNamed && input.hidden;\n\t\t\t\textensions.push(extension);\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : \"failed to load extension\";\n\t\t\t\terrors.push({ path: extensionPath, error: message });\n\t\t\t}\n\t\t}\n\n\t\treturn { extensions, errors };\n\t}\n\n\tprivate dedupePrompts(prompts: PromptTemplate[]): { prompts: PromptTemplate[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, PromptTemplate>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const prompt of prompts) {\n\t\t\tconst existing = seen.get(prompt.name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"/${prompt.name}\" collision`,\n\t\t\t\t\tpath: prompt.filePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"prompt\",\n\t\t\t\t\t\tname: prompt.name,\n\t\t\t\t\t\twinnerPath: existing.filePath,\n\t\t\t\t\t\tloserPath: prompt.filePath,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(prompt.name, prompt);\n\t\t\t}\n\t\t}\n\n\t\treturn { prompts: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate dedupeThemes(themes: Theme[]): { themes: Theme[]; diagnostics: ResourceDiagnostic[] } {\n\t\tconst seen = new Map<string, Theme>();\n\t\tconst diagnostics: ResourceDiagnostic[] = [];\n\n\t\tfor (const t of themes) {\n\t\t\tconst name = t.name ?? \"unnamed\";\n\t\t\tconst existing = seen.get(name);\n\t\t\tif (existing) {\n\t\t\t\tdiagnostics.push({\n\t\t\t\t\ttype: \"collision\",\n\t\t\t\t\tmessage: `name \"${name}\" collision`,\n\t\t\t\t\tpath: t.sourcePath,\n\t\t\t\t\tcollision: {\n\t\t\t\t\t\tresourceType: \"theme\",\n\t\t\t\t\t\tname,\n\t\t\t\t\t\twinnerPath: existing.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t\tloserPath: t.sourcePath ?? \"<builtin>\",\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tseen.set(name, t);\n\t\t\t}\n\t\t}\n\n\t\treturn { themes: Array.from(seen.values()), diagnostics };\n\t}\n\n\tprivate discoverSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"SYSTEM.md\");\n\t\tif (this.settingsManager.isProjectTrusted() && existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate discoverAppendSystemPromptFile(): string | undefined {\n\t\tconst projectPath = join(this.cwd, CONFIG_DIR_NAME, \"APPEND_SYSTEM.md\");\n\t\tif (this.settingsManager.isProjectTrusted() && existsSync(projectPath)) {\n\t\t\treturn projectPath;\n\t\t}\n\n\t\tconst globalPath = join(this.agentDir, \"APPEND_SYSTEM.md\");\n\t\tif (existsSync(globalPath)) {\n\t\t\treturn globalPath;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate isUnderPath(target: string, root: string): boolean {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t}\n\n\tprivate detectExtensionConflicts(extensions: Extension[]): Array<{ path: string; message: string }> {\n\t\tconst conflicts: Array<{ path: string; message: string }> = [];\n\n\t\t// Track which extension registered each tool and flag\n\t\tconst toolOwners = new Map<string, string>();\n\t\tconst flagOwners = new Map<string, string>();\n\n\t\tfor (const ext of extensions) {\n\t\t\t// Check tools\n\t\t\tfor (const toolName of ext.tools.keys()) {\n\t\t\t\tconst existingOwner = toolOwners.get(toolName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Tool \"${toolName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\ttoolOwners.set(toolName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check flags\n\t\t\tfor (const flagName of ext.flags.keys()) {\n\t\t\t\tconst existingOwner = flagOwners.get(flagName);\n\t\t\t\tif (existingOwner && existingOwner !== ext.path) {\n\t\t\t\t\tconflicts.push({\n\t\t\t\t\t\tpath: ext.path,\n\t\t\t\t\t\tmessage: `Flag \"--${flagName}\" conflicts with ${existingOwner}`,\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tflagOwners.set(flagName, ext.path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn conflicts;\n\t}\n}\n"]}
|
|
@@ -190,6 +190,7 @@ export class DefaultResourceLoader {
|
|
|
190
190
|
});
|
|
191
191
|
// Ada Secure Vault — encrypted secrets (project + session scopes).
|
|
192
192
|
this.vaultManager = new VaultManager(this.agentDir, this.memoryEngine.projectHash);
|
|
193
|
+
this.vaultManager.setAudit((entry) => this.memoryEngine?.recordAudit(entry));
|
|
193
194
|
this.eventBus = options.eventBus ?? createEventBus();
|
|
194
195
|
this.packageManager = new DefaultPackageManager({
|
|
195
196
|
cwd: this.cwd,
|