{"version":3,"file":"trust.d.ts","sourceRoot":"","sources":["../../../../src/core/extensions/plugins/trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAOH,qCAAqC;AACrC,MAAM,WAAW,gBAAgB;IAChC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,iFAA+E;IAC/E,EAAE,EAAE,MAAM,CAAC;CACX;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,GAAE,MAAsB,GAAG,MAAM,CAEvE;AAmBD,mDAAmD;AACnD,wBAAgB,qBAAqB,CAAC,QAAQ,GAAE,MAAsB,GAAG,gBAAgB,EAAE,CAE1F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,OAAO,CAGzF;AAED,gFAA8E;AAC9E,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,gBAAgB,CAK9F;AAED,mFAAmF;AACnF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAsB,GAAG,OAAO,CAOvF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,OAAO,CAEzF;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC/C,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,iBAAiB,EAAE,MAAM,EAAE,EAC3B,QAAQ,GAAE,MAAsB,GAC9B,OAAO,CAET","sourcesContent":["/**\n * Workspace trust — the per-machine record of which working directories the user\n * has agreed to run repository-supplied plugin code from.\n *\n * The problem it solves (docs/plugin-system-architecture.md §5.9): a plugin\n * committed to a repository is code that runs for whoever clones it next. Its\n * skills and commands are text the model reads, which is no worse than reading\n * the repository itself, but its **hooks and MCP servers are processes** that\n * start on session load. Nothing about a plugin's location can distinguish \"I\n * installed this here\" from \"this arrived in the clone\", because everything in\n * the repository travels with it — including any marker a plugin might carry to\n * claim otherwise.\n *\n * So the record lives **outside the repository**, in the agent dir, keyed by\n * absolute path. That is the whole design: trust cannot be forged by repository\n * content, because repository content cannot write here. It is the same shape\n * Claude Code's workspace trust dialog and VS Code's trusted folders use, and it\n * carries the same known consequence — once a directory is trusted, code pulled\n * into it later is trusted too. Trust is a statement about a *place you work*,\n * not about a specific commit.\n *\n * Granting is a human act (`/plugin trust`, or an explicit `/plugin install\n * --scope project`, where the person is demonstrably operating in the directory\n * on purpose). The autonomous install path never grants it: a model deciding\n * that a workspace should execute repository code is exactly the decision this\n * record exists to keep with a person.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { getAgentDir } from \"../../../config.js\";\nimport { isPathInside } from \"../../../utils/paths.js\";\n\n/** One trusted working directory. */\nexport interface TrustedWorkspace {\n\t/** Absolute, resolved path. */\n\tpath: string;\n\t/** When trust was granted, ISO-8601. Informational — nothing expires today. */\n\tat: string;\n}\n\ninterface TrustStoreFile {\n\tworkspaces?: TrustedWorkspace[];\n}\n\n/**\n * `~/.agents/trusted-workspaces.json`.\n *\n * Beside the marketplace registry rather than inside the repo, for the reason in\n * the module docstring: a file the repository can write is not a trust record.\n */\nexport function trustStorePath(agentDir: string = getAgentDir()): string {\n\treturn path.join(path.dirname(agentDir), \".agents\", \"trusted-workspaces.json\");\n}\n\nfunction readStore(agentDir: string): TrustedWorkspace[] {\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(trustStorePath(agentDir), \"utf8\")) as TrustStoreFile;\n\t\treturn Array.isArray(parsed?.workspaces)\n\t\t\t? parsed.workspaces.filter((w): w is TrustedWorkspace => typeof w?.path === \"string\")\n\t\t\t: [];\n\t} catch {\n\t\treturn [];\n\t}\n}\n\nfunction writeStore(agentDir: string, workspaces: TrustedWorkspace[]): void {\n\tconst file = trustStorePath(agentDir);\n\tmkdirSync(path.dirname(file), { recursive: true });\n\twriteFileSync(file, `${JSON.stringify({ workspaces }, null, 2)}\\n`, \"utf8\");\n}\n\n/** Every trusted workspace, newest grant first. */\nexport function listTrustedWorkspaces(agentDir: string = getAgentDir()): TrustedWorkspace[] {\n\treturn [...readStore(agentDir)].sort((a, b) => (a.at < b.at ? 1 : -1));\n}\n\n/**\n * Whether `cwd` is trusted.\n *\n * Exact-path only, deliberately: trusting `~/src` must not silently trust every\n * repository ever cloned beneath it, which is what a prefix match would do the\n * first time someone trusts a directory one level too high.\n */\nexport function isWorkspaceTrusted(cwd: string, agentDir: string = getAgentDir()): boolean {\n\tconst target = path.resolve(cwd);\n\treturn readStore(agentDir).some((w) => path.resolve(w.path) === target);\n}\n\n/** Grant trust to `cwd`. Idempotent — re-granting refreshes the timestamp. */\nexport function trustWorkspace(cwd: string, agentDir: string = getAgentDir()): TrustedWorkspace {\n\tconst target = path.resolve(cwd);\n\tconst record: TrustedWorkspace = { path: target, at: new Date().toISOString() };\n\twriteStore(agentDir, [...readStore(agentDir).filter((w) => path.resolve(w.path) !== target), record]);\n\treturn record;\n}\n\n/** Revoke trust for `cwd`. Returns false when it was not trusted to begin with. */\nexport function untrustWorkspace(cwd: string, agentDir: string = getAgentDir()): boolean {\n\tconst target = path.resolve(cwd);\n\tconst before = readStore(agentDir);\n\tconst after = before.filter((w) => path.resolve(w.path) !== target);\n\tif (after.length === before.length) return false;\n\twriteStore(agentDir, after);\n\treturn true;\n}\n\n/**\n * Whether `target` sits under any of `projectScopeRoots`, and therefore came with\n * the repository as far as anyone but its installer can tell.\n *\n * Callers supply their own roots because each capability has its own project-scope\n * homes — plugins live in `.claude/skills` and `.agents/plugins`, canvas extensions\n * in `.agents/extensions` and `.github/extensions`. What does not vary is the\n * reasoning: no location can distinguish \"I put this here\" from \"this arrived in\n * the clone\", so location only decides *whether* to ask about trust. It never\n * answers the question.\n */\nexport function isRepositorySupplied(target: string, projectScopeRoots: string[]): boolean {\n\treturn projectScopeRoots.some((root) => isPathInside(target, root));\n}\n\n/**\n * Whether repository-supplied code at `target` must be withheld: it lives in the\n * working tree and this machine has not trusted the workspace.\n *\n * What \"withheld\" means is the caller's to decide, and it differs by capability. A\n * plugin keeps its passive capabilities and loses only its processes; a canvas has\n * no passive half, so it is withheld whole.\n */\nexport function shouldWithholdRepositorySupplied(\n\ttarget: string,\n\tcwd: string,\n\tprojectScopeRoots: string[],\n\tagentDir: string = getAgentDir(),\n): boolean {\n\treturn isRepositorySupplied(target, projectScopeRoots) && !isWorkspaceTrusted(cwd, agentDir);\n}\n"]}