@letrquan/book 0.2.0 → 0.3.0
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/CHANGELOG.md +2747 -40
- package/README.md +132 -1311
- package/dist/additional-roots-H7UNAEHS.js +31 -0
- package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
- package/dist/app-GE4OHJOC.js.map +1 -0
- package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
- package/dist/chunk-224FJZLH.js.map +1 -0
- package/dist/chunk-2AAOF5B5.js +28 -0
- package/dist/chunk-2AAOF5B5.js.map +1 -0
- package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
- package/dist/chunk-4AS7LKKQ.js.map +1 -0
- package/dist/chunk-525CAZPY.js +120 -0
- package/dist/chunk-525CAZPY.js.map +1 -0
- package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
- package/dist/chunk-56SIEMDX.js.map +1 -0
- package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
- package/dist/chunk-5YHTKWJN.js.map +1 -0
- package/dist/chunk-62QL5QUP.js +283 -0
- package/dist/chunk-62QL5QUP.js.map +1 -0
- package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
- package/dist/chunk-67UZEWWF.js.map +1 -0
- package/dist/chunk-6OQQOMZR.js +785 -0
- package/dist/chunk-6OQQOMZR.js.map +1 -0
- package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
- package/dist/chunk-AQEQX4FF.js.map +1 -0
- package/dist/chunk-C7ADUNPR.js +273 -0
- package/dist/chunk-C7ADUNPR.js.map +1 -0
- package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
- package/dist/chunk-EFJ2NH6E.js.map +1 -0
- package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
- package/dist/chunk-HQI7NYGX.js.map +1 -0
- package/dist/chunk-IME7R2UI.js +6541 -0
- package/dist/chunk-IME7R2UI.js.map +1 -0
- package/dist/chunk-J4F2QLM2.js +273 -0
- package/dist/chunk-J4F2QLM2.js.map +1 -0
- package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
- package/dist/chunk-J62VZYOB.js.map +1 -0
- package/dist/chunk-JTPOI3KU.js +826 -0
- package/dist/chunk-JTPOI3KU.js.map +1 -0
- package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
- package/dist/chunk-KBQS35OV.js.map +1 -0
- package/dist/chunk-KXAYUIKP.js +90 -0
- package/dist/chunk-KXAYUIKP.js.map +1 -0
- package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
- package/dist/chunk-QYBD22H5.js.map +1 -0
- package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
- package/dist/chunk-RFJ6Q6LE.js.map +1 -0
- package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
- package/dist/chunk-RT5SATF7.js.map +1 -0
- package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
- package/dist/chunk-S4JMID4G.js.map +1 -0
- package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
- package/dist/chunk-SOW4WRQP.js.map +1 -0
- package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
- package/dist/chunk-TLX3H2CL.js.map +1 -0
- package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
- package/dist/chunk-VPNLCMVR.js.map +1 -0
- package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
- package/dist/chunk-W3XFHEF4.js.map +1 -0
- package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
- package/dist/chunk-X4NJT436.js.map +1 -0
- package/dist/chunk-X66I2NQO.js +137 -0
- package/dist/chunk-X66I2NQO.js.map +1 -0
- package/dist/chunk-XHAO3K7I.js +18 -0
- package/dist/chunk-XHAO3K7I.js.map +1 -0
- package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
- package/dist/chunk-Y2VHN7TC.js.map +1 -0
- package/dist/chunk-YC6W4EJH.js +947 -0
- package/dist/chunk-YC6W4EJH.js.map +1 -0
- package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
- package/dist/chunk-ZPSZZUWF.js.map +1 -0
- package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
- package/dist/context-CBOBDY5Q.js +38 -0
- package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
- package/dist/index.js +996 -155
- package/dist/index.js.map +1 -1
- package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
- package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
- package/dist/job-runner.js +159 -111
- package/dist/job-runner.js.map +1 -1
- package/dist/job-supervisor.d.ts +2 -0
- package/dist/job-supervisor.js +112 -0
- package/dist/job-supervisor.js.map +1 -0
- package/dist/loader-S77OVTHH.js +18 -0
- package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
- package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
- package/dist/memory-store-DZUT5HUH.js +75 -0
- package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
- package/dist/openai-compatible-2OB54YST.js +14 -0
- package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
- package/dist/permissions-DHOKF3W2.js +45 -0
- package/dist/sandbox-WRYTZH3D.js +41 -0
- package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
- package/dist/scrollback-35ME2GFX.js.map +1 -0
- package/dist/sdk.d.ts +2219 -2361
- package/dist/sdk.js +53 -38
- package/dist/sdk.js.map +1 -1
- package/dist/settings-loader-JUNPGAAM.js +35 -0
- package/dist/settings-loader-JUNPGAAM.js.map +1 -0
- package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
- package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
- package/package.json +23 -22
- package/dist/app-MOXXWEOY.js.map +0 -1
- package/dist/chunk-24AXE6SP.js.map +0 -1
- package/dist/chunk-3U5IBM24.js.map +0 -1
- package/dist/chunk-574C6MQR.js.map +0 -1
- package/dist/chunk-5GDZ22YP.js.map +0 -1
- package/dist/chunk-5KLVH3PY.js.map +0 -1
- package/dist/chunk-7N4J557C.js.map +0 -1
- package/dist/chunk-A7JLBK2Y.js +0 -3506
- package/dist/chunk-A7JLBK2Y.js.map +0 -1
- package/dist/chunk-ANSJPYGI.js.map +0 -1
- package/dist/chunk-BTZZUCLI.js.map +0 -1
- package/dist/chunk-C7D4NJ2D.js.map +0 -1
- package/dist/chunk-CAGOB2N4.js.map +0 -1
- package/dist/chunk-DGMQEI6Y.js.map +0 -1
- package/dist/chunk-E75IPST5.js.map +0 -1
- package/dist/chunk-EYGD6X2T.js +0 -180
- package/dist/chunk-EYGD6X2T.js.map +0 -1
- package/dist/chunk-GTVB4HEM.js.map +0 -1
- package/dist/chunk-J7MIZ5CA.js.map +0 -1
- package/dist/chunk-KWHQ2XXL.js.map +0 -1
- package/dist/chunk-L6LK2QIQ.js +0 -657
- package/dist/chunk-L6LK2QIQ.js.map +0 -1
- package/dist/chunk-MAQXCUR4.js.map +0 -1
- package/dist/chunk-S4XL7HOM.js +0 -402
- package/dist/chunk-S4XL7HOM.js.map +0 -1
- package/dist/chunk-URLQAXTR.js.map +0 -1
- package/dist/chunk-V4BOYR52.js.map +0 -1
- package/dist/chunk-VZQXX3WW.js.map +0 -1
- package/dist/chunk-WKBO5R4O.js.map +0 -1
- package/dist/chunk-XIFFTATU.js.map +0 -1
- package/dist/context-ZC5IFEAX.js +0 -25
- package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
- package/dist/loader-4IDS2RIE.js +0 -15
- package/dist/openai-compatible-RHZUBHV6.js +0 -10
- package/dist/permissions-CHCKTFQQ.js +0 -28
- package/dist/sandbox-CNVAQDA7.js +0 -23
- package/dist/scrollback-LUKLIBVI.js.map +0 -1
- package/dist/settings-loader-M2QP5TX2.js +0 -21
- package/patches/ink+6.8.0.patch +0 -13
- package/scripts/apply-ink-patch.mjs +0 -29
- package/scripts/ink-patch.mjs +0 -59
- package/scripts/verify-ink-patch.mjs +0 -11
- /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
- /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
- /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
- /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
- /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
- /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
- /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
- /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
- /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
- /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
- /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
- /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
- /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/additional-roots.ts"],"sourcesContent":["/**\n * The directories `additionalDirectories` actually adds, and the decisions about them.\n *\n * The setting names directories the file tools may serve alongside the workspace: Read, Glob and\n * Grep reach them without a prompt, and the write tools' root check accepts them (a write there\n * still goes through the ordinary permission flow for its mode). It is honoured from the layers\n * the user controls — the user-global file, `.book/settings.local.json`, and `--settings` — and\n * withheld from the checked-in project layer until the user approves it in the user-global trust\n * store, exactly as a project-declared `permissions.allow` rule is (see `permission-approvals.ts`\n * for why, and `workspace-trust.ts` for where the decision lives).\n *\n * A decision is keyed by the directory's **real path**, never by the text the repository wrote.\n * A repository controls that text and could declare `./link` for a symlink pointing at `/etc`; a\n * user who approved the string would have approved a path they never saw. Resolving first also\n * makes the key stable: `./shared`, `shared` and `<workspace>/shared/` are one directory, and\n * retargeting a link makes the key change, so the earlier decision no longer covers it and the\n * directory is pending again.\n */\nimport { existsSync, realpathSync } from 'fs';\nimport { homedir } from 'os';\nimport { isAbsolute, relative, resolve, sep } from 'path';\nimport { createDebugLogger } from './debug-log.js';\nimport { resolveBookHome } from './book-home.js';\nimport { realWorkspaceRoot, canonicalizePath } from './tools/path-utils.js';\nimport type { ProjectDirectoryChoice } from './settings.js';\nimport { updateWorkspaceTrust } from './workspace-trust.js';\n\nconst log = createDebugLogger('additional-roots');\n\n/** Recorded decisions, keyed by the directory's real path. */\nexport type ProjectDirectoryStore = Record<string, ProjectDirectoryChoice>;\n\nexport type ProjectDirectoryState = ProjectDirectoryChoice | 'unknown';\n\n/** A project-declared directory, as the file tools will see it and as the repository wrote it. */\nexport interface DeclaredDirectory {\n /** The text the project layer declared, kept for display. */\n declared: string;\n /** The real path, which is also the key a decision is recorded under. */\n realPath: string;\n}\n\n/**\n * The real path a declared directory resolves to, or `undefined` when it is not usable: a path\n * that does not exist is ignored (with a debug log, since a checkout may not carry it yet), and\n * one that cannot be resolved is ignored the same way rather than trusted as written.\n *\n * Resolution follows links, so a declaration that points somewhere other than where it reads is\n * the thing the user is asked about.\n */\nexport function realPathOfDirectory(workspace: string, declared: string): string | undefined {\n const absolute = isAbsolute(declared) ? resolve(declared) : resolve(workspace, declared);\n if (!existsSync(absolute)) {\n log.debug('ignoring declared directory that does not exist', { declared, resolved: absolute });\n return undefined;\n }\n try {\n return realpathSync.native(absolute);\n } catch (error) {\n log.debug('ignoring declared directory that cannot be resolved', {\n declared,\n resolved: absolute,\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n }\n}\n\n/**\n * The project layer's declared directories, as `{declared, realPath}` pairs, deduplicated by\n * real path (two spellings of one directory are one decision) and in declaration order.\n */\nexport function collectDeclaredDirectories(\n workspace: string,\n declared: readonly string[] | undefined,\n): DeclaredDirectory[] {\n const seen = new Set<string>();\n const out: DeclaredDirectory[] = [];\n for (const entry of declared ?? []) {\n const realPath = realPathOfDirectory(workspace, entry);\n if (!realPath || seen.has(realPath)) continue;\n seen.add(realPath);\n out.push({ declared: entry, realPath });\n }\n return out;\n}\n\nexport function evaluateProjectDirectory(\n store: ProjectDirectoryStore | undefined,\n realPath: string,\n): ProjectDirectoryState {\n return store?.[realPath] ?? 'unknown';\n}\n\nexport interface ProjectDirectoryPartition {\n /** Approved by the user: these become honored roots. */\n approved: DeclaredDirectory[];\n /** No decision recorded: withheld until the user makes one. */\n pending: DeclaredDirectory[];\n /** Explicitly refused: withheld, and not re-offered. */\n rejected: DeclaredDirectory[];\n}\n\nexport function partitionProjectDirectories(\n directories: readonly DeclaredDirectory[],\n store: ProjectDirectoryStore | undefined,\n): ProjectDirectoryPartition {\n const partition: ProjectDirectoryPartition = { approved: [], pending: [], rejected: [] };\n for (const directory of directories) {\n const state = evaluateProjectDirectory(store, directory.realPath);\n if (state === 'approved') partition.approved.push(directory);\n else if (state === 'rejected') partition.rejected.push(directory);\n else partition.pending.push(directory);\n }\n return partition;\n}\n\n/**\n * Record an approve/reject decision for one project-declared directory, keyed by its real path,\n * leaving every other recorded decision untouched.\n */\nexport function persistProjectDirectoryChoice(\n workspace: string,\n realPath: string,\n choice: ProjectDirectoryChoice,\n options: { trustStorePath?: string } = {},\n): { ok: boolean; error?: string } {\n return updateWorkspaceTrust(\n workspace,\n (trust) => {\n trust.projectDirectories[realPath] = choice;\n },\n options.trustStorePath,\n );\n}\n\n/**\n * The honored roots the file tools and the permission scope use, for one evaluation.\n *\n * `declared` is the resolved settings list, which already carries every layer's entries the\n * resolver decided to release; nothing here re-reads a file. A declared directory inside the\n * workspace adds nothing — the workspace already serves it — so it is dropped rather than\n * resolved twice, and a path that vanished between resolution and now is dropped too.\n *\n * Resolved once per run and passed down through `ToolContext` and `WorkspaceScope`, so a managed\n * child and a subagent serve exactly the roots their parent does.\n */\nexport function resolveAdditionalRoots(\n workspace: string,\n declared: readonly string[] | undefined,\n): string[] {\n const realWorkspace = realWorkspaceRoot(workspace);\n const roots: string[] = [];\n const seen = new Set<string>();\n for (const entry of collectDeclaredDirectories(workspace, declared)) {\n // Inside the workspace — not merely equal to it: the workspace already serves everything under\n // it, so listing a subdirectory adds a root that widens nothing. The test is `..` followed by\n // a separator rather than a bare prefix, because a sibling whose name starts with two dots\n // (`/home/u/..foo`) is *outside* the workspace and `startsWith('..')` read it as inside. An\n // empty result means equal, and equal is inside.\n const fromWorkspace = relative(realWorkspace, entry.realPath);\n if (\n !isAbsolute(fromWorkspace) &&\n fromWorkspace !== '..' &&\n !fromWorkspace.startsWith(`..${sep}`)\n ) {\n log.debug('dropping declared directory the workspace already serves', {\n declared: entry.declared,\n realPath: entry.realPath,\n });\n continue;\n }\n if (seen.has(entry.realPath)) continue;\n seen.add(entry.realPath);\n roots.push(entry.realPath);\n }\n return roots;\n}\n\n/**\n * Whether a root is, or contains, a home directory — the OS home or Book's own `BOOK_HOME`.\n *\n * A home carries SSH and provider keys and Book's trust store, so a session rooted at one keeps\n * prompting for reads. Compared as written and after following links, so a root that is a link\n * to a home, or a home reached through a link, still counts. One function for the workspace and\n * for each honored directory, so the rule reads the same wherever it applies.\n */\nexport function rootHoldsHome(root: string, homes: readonly string[] = homeGuards()): boolean {\n return homes.some((home) => containsPath(root, home));\n}\n\n/**\n * The home directories a read is never auto-allowed in: Book's own `BOOK_HOME` and the OS home,\n * each resolved through links once, deduplicated.\n *\n * Resolved once per run matters — a guard is consulted for every Read, Glob and Grep, and the\n * alternative is a `realpath` walk per call — and it is what lets {@link pathHoldsHome} compare\n * against a short fixed list.\n */\nexport function homeGuards(): string[] {\n const homes: string[] = [];\n for (const home of [resolveBookHome(), homedir()]) {\n const real = canonicalizePath(home);\n if (real && !homes.includes(real)) homes.push(real);\n }\n return homes;\n}\n\n/**\n * Whether a canonical path is a home directory or is inside one.\n *\n * Deliberately a test of the path rather than of the root that authorized it: a root may hold a\n * home as a subdirectory, or reach one through a symlink, and either way the read lands on\n * `~/.ssh/id_rsa` whatever the operator approved. Checking the resolved target catches both.\n */\nexport function pathHoldsHome(\n canonicalPath: string,\n homes: readonly string[] = homeGuards(),\n): boolean {\n return homes.some((home) => containsPath(home, canonicalPath));\n}\n\n/**\n * Whether `root` is `target` or contains it, both sides compared in one canonical form.\n *\n * The whole of that rule: a root as the caller spelled it and a path from `realpath` are two\n * spellings of one directory at best, and two unrelated ones when the root was typed in 8.3 short\n * form — so the root is put through the same canonicalization as the path before the two are\n * compared. Both are canonicalized here rather than one of them assumed canonical, because a home\n * list, a root list and a target all arrive from different places.\n *\n * Exported because a Grep or Glob *scope* has to be compared against a home the same way a file\n * is (PR #334 finding 4).\n */\nexport function containsPath(root: string, target: string): boolean {\n const fromRoot = relative(canonicalizePath(root), canonicalizePath(target));\n return !(fromRoot === '..' || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot));\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
updateWorkspaceTrust
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-VPNLCMVR.js";
|
|
4
4
|
|
|
5
5
|
// src/mcp-approvals.ts
|
|
6
6
|
import { createHash } from "crypto";
|
|
@@ -57,4 +57,4 @@ export {
|
|
|
57
57
|
mcpServersToRecord,
|
|
58
58
|
persistMcpProjectServerChoice
|
|
59
59
|
};
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
//# sourceMappingURL=chunk-56SIEMDX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp-approvals.ts"],"sourcesContent":["/**\n * Trust decisions for MCP servers.\n *\n * Servers declared in a workspace `.mcp.json` are repository-controlled input:\n * connecting one executes an arbitrary command. Hosts therefore require a\n * one-time per-project approval, recorded against a fingerprint of the full\n * connection configuration in the user-global trust store, outside the\n * workspace, where nothing the repository ships can reach it (see\n * `workspace-trust.ts`).\n * A config change after approval invalidates the decision and re-prompts.\n * User-global servers (<BOOK_HOME>/.book/mcp.json) were written by the user\n * and never require approval.\n */\nimport { createHash } from 'crypto';\nimport type { McpProjectServerChoice, McpSettings } from './settings.js';\nimport type { McpServerConfig, ResolvedMcpServer } from './mcp-config.js';\nimport { updateWorkspaceTrust } from './workspace-trust.js';\n\n/** Stable digest of everything the user is asked to trust about a server. */\nexport function mcpServerFingerprint(config: McpServerConfig): string {\n const sortedRecord = (record: Record<string, string> | undefined) =>\n Object.fromEntries(Object.entries(record ?? {}).sort(([a], [b]) => a.localeCompare(b)));\n const canonical = JSON.stringify({\n type: config.type ?? (config.url ? 'http' : 'stdio'),\n command: config.command,\n args: config.args ?? [],\n env: sortedRecord(config.env),\n cwd: config.cwd,\n url: config.url,\n // Values intentionally participate in the digest: changing an auth token\n // or any other header changes what the user has approved. The digest is\n // one-way and neither the UI nor diagnostics render these values.\n headers: sortedRecord(config.headers),\n });\n return createHash('sha256').update(canonical).digest('hex').slice(0, 16);\n}\n\nexport type McpServerApprovalState = 'approved' | 'rejected' | 'unknown';\n\ninterface McpApprovalSettings {\n mcp?: Partial<McpSettings>;\n}\n\nexport function evaluateMcpServerApproval(\n settings: McpApprovalSettings,\n server: ResolvedMcpServer,\n): McpServerApprovalState {\n if (server.source === 'user') return 'approved';\n const choice: McpProjectServerChoice | undefined = settings.mcp?.projectServers?.[server.name];\n if (!choice) return 'unknown';\n if (choice.fingerprint !== mcpServerFingerprint(server.config)) return 'unknown';\n return choice.choice;\n}\n\nexport interface McpApprovalPartition {\n /** User-global servers plus approved project servers — safe to connect. */\n allowed: ResolvedMcpServer[];\n /** Project servers with no (valid) recorded decision — prompt or skip. */\n pending: ResolvedMcpServer[];\n /** Project servers the user rejected. */\n rejected: ResolvedMcpServer[];\n}\n\nexport function partitionMcpServersByApproval(\n servers: ResolvedMcpServer[],\n settings: McpApprovalSettings,\n): McpApprovalPartition {\n const partition: McpApprovalPartition = { allowed: [], pending: [], rejected: [] };\n for (const server of servers) {\n const state = evaluateMcpServerApproval(settings, server);\n if (state === 'approved') partition.allowed.push(server);\n else if (state === 'rejected') partition.rejected.push(server);\n else partition.pending.push(server);\n }\n return partition;\n}\n\nexport function mcpServersToRecord(servers: ResolvedMcpServer[]): Record<string, McpServerConfig> {\n return Object.fromEntries(servers.map((server) => [server.name, server.config]));\n}\n\n/**\n * Record an approve/reject decision for one server, leaving every other\n * recorded decision — in this workspace and in every other — untouched.\n */\nexport function persistMcpProjectServerChoice(\n workspace: string,\n name: string,\n fingerprint: string,\n choice: McpProjectServerChoice['choice'],\n options: { trustStorePath?: string } = {},\n): { ok: boolean; error?: string } {\n return updateWorkspaceTrust(\n workspace,\n (trust) => {\n trust.mcpServers[name] = { fingerprint, choice } satisfies McpProjectServerChoice;\n },\n options.trustStorePath,\n );\n}\n"],"mappings":";;;;;AAaA,SAAS,kBAAkB;AAMpB,SAAS,qBAAqB,QAAiC;AACpE,QAAM,eAAe,CAAC,WACpB,OAAO,YAAY,OAAO,QAAQ,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;AACxF,QAAM,YAAY,KAAK,UAAU;AAAA,IAC/B,MAAM,OAAO,SAAS,OAAO,MAAM,SAAS;AAAA,IAC5C,SAAS,OAAO;AAAA,IAChB,MAAM,OAAO,QAAQ,CAAC;AAAA,IACtB,KAAK,aAAa,OAAO,GAAG;AAAA,IAC5B,KAAK,OAAO;AAAA,IACZ,KAAK,OAAO;AAAA;AAAA;AAAA;AAAA,IAIZ,SAAS,aAAa,OAAO,OAAO;AAAA,EACtC,CAAC;AACD,SAAO,WAAW,QAAQ,EAAE,OAAO,SAAS,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACzE;AAQO,SAAS,0BACd,UACA,QACwB;AACxB,MAAI,OAAO,WAAW,OAAQ,QAAO;AACrC,QAAM,SAA6C,SAAS,KAAK,iBAAiB,OAAO,IAAI;AAC7F,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,OAAO,gBAAgB,qBAAqB,OAAO,MAAM,EAAG,QAAO;AACvE,SAAO,OAAO;AAChB;AAWO,SAAS,8BACd,SACA,UACsB;AACtB,QAAM,YAAkC,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,UAAU,CAAC,EAAE;AACjF,aAAW,UAAU,SAAS;AAC5B,UAAM,QAAQ,0BAA0B,UAAU,MAAM;AACxD,QAAI,UAAU,WAAY,WAAU,QAAQ,KAAK,MAAM;AAAA,aAC9C,UAAU,WAAY,WAAU,SAAS,KAAK,MAAM;AAAA,QACxD,WAAU,QAAQ,KAAK,MAAM;AAAA,EACpC;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,SAA+D;AAChG,SAAO,OAAO,YAAY,QAAQ,IAAI,CAAC,WAAW,CAAC,OAAO,MAAM,OAAO,MAAM,CAAC,CAAC;AACjF;AAMO,SAAS,8BACd,WACA,MACA,aACA,QACA,UAAuC,CAAC,GACP;AACjC,SAAO;AAAA,IACL;AAAA,IACA,CAAC,UAAU;AACT,YAAM,WAAW,IAAI,IAAI,EAAE,aAAa,OAAO;AAAA,IACjD;AAAA,IACA,QAAQ;AAAA,EACV;AACF;","names":[]}
|