@letrquan/book 0.2.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 +2504 -0
- package/LICENSE +123 -0
- package/README.md +1383 -0
- package/dist/app-MOXXWEOY.js +18945 -0
- package/dist/app-MOXXWEOY.js.map +1 -0
- package/dist/chunk-24AXE6SP.js +12 -0
- package/dist/chunk-24AXE6SP.js.map +1 -0
- package/dist/chunk-3U5IBM24.js +213 -0
- package/dist/chunk-3U5IBM24.js.map +1 -0
- package/dist/chunk-574C6MQR.js +743 -0
- package/dist/chunk-574C6MQR.js.map +1 -0
- package/dist/chunk-5GDZ22YP.js +449 -0
- package/dist/chunk-5GDZ22YP.js.map +1 -0
- package/dist/chunk-5KLVH3PY.js +167 -0
- package/dist/chunk-5KLVH3PY.js.map +1 -0
- package/dist/chunk-5RDRAO4B.js +92 -0
- package/dist/chunk-5RDRAO4B.js.map +1 -0
- package/dist/chunk-7N4J557C.js +10 -0
- package/dist/chunk-7N4J557C.js.map +1 -0
- package/dist/chunk-A7JLBK2Y.js +3506 -0
- package/dist/chunk-A7JLBK2Y.js.map +1 -0
- package/dist/chunk-ANSJPYGI.js +84 -0
- package/dist/chunk-ANSJPYGI.js.map +1 -0
- package/dist/chunk-BTZZUCLI.js +264 -0
- package/dist/chunk-BTZZUCLI.js.map +1 -0
- package/dist/chunk-C7D4NJ2D.js +217 -0
- package/dist/chunk-C7D4NJ2D.js.map +1 -0
- package/dist/chunk-CAGOB2N4.js +208 -0
- package/dist/chunk-CAGOB2N4.js.map +1 -0
- package/dist/chunk-DGMQEI6Y.js +98 -0
- package/dist/chunk-DGMQEI6Y.js.map +1 -0
- package/dist/chunk-E75IPST5.js +289 -0
- package/dist/chunk-E75IPST5.js.map +1 -0
- package/dist/chunk-EYGD6X2T.js +180 -0
- package/dist/chunk-EYGD6X2T.js.map +1 -0
- package/dist/chunk-GTVB4HEM.js +765 -0
- package/dist/chunk-GTVB4HEM.js.map +1 -0
- package/dist/chunk-J7MIZ5CA.js +1907 -0
- package/dist/chunk-J7MIZ5CA.js.map +1 -0
- package/dist/chunk-KWHQ2XXL.js +626 -0
- package/dist/chunk-KWHQ2XXL.js.map +1 -0
- package/dist/chunk-L6LK2QIQ.js +657 -0
- package/dist/chunk-L6LK2QIQ.js.map +1 -0
- package/dist/chunk-MAQXCUR4.js +1007 -0
- package/dist/chunk-MAQXCUR4.js.map +1 -0
- package/dist/chunk-S4XL7HOM.js +402 -0
- package/dist/chunk-S4XL7HOM.js.map +1 -0
- package/dist/chunk-URLQAXTR.js +60 -0
- package/dist/chunk-URLQAXTR.js.map +1 -0
- package/dist/chunk-V4BOYR52.js +3504 -0
- package/dist/chunk-V4BOYR52.js.map +1 -0
- package/dist/chunk-VZQXX3WW.js +210 -0
- package/dist/chunk-VZQXX3WW.js.map +1 -0
- package/dist/chunk-WKBO5R4O.js +34 -0
- package/dist/chunk-WKBO5R4O.js.map +1 -0
- package/dist/chunk-XIFFTATU.js +15753 -0
- package/dist/chunk-XIFFTATU.js.map +1 -0
- package/dist/command-approvals-6Y57VCWZ.js +28 -0
- package/dist/command-approvals-6Y57VCWZ.js.map +1 -0
- package/dist/context-ZC5IFEAX.js +25 -0
- package/dist/context-ZC5IFEAX.js.map +1 -0
- package/dist/hook-approvals-CTUFKEJT.js +22 -0
- package/dist/hook-approvals-CTUFKEJT.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2582 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive-assets-CCAAWVKH.js +31 -0
- package/dist/interactive-assets-CCAAWVKH.js.map +1 -0
- package/dist/job-runner.d.ts +2 -0
- package/dist/job-runner.js +235 -0
- package/dist/job-runner.js.map +1 -0
- package/dist/loader-4IDS2RIE.js +15 -0
- package/dist/loader-4IDS2RIE.js.map +1 -0
- package/dist/mcp-approvals-WNPH65BK.js +18 -0
- package/dist/mcp-approvals-WNPH65BK.js.map +1 -0
- package/dist/mcp-config-YIJIES2C.js +16 -0
- package/dist/mcp-config-YIJIES2C.js.map +1 -0
- package/dist/model-window-store-YFVNXCIP.js +27 -0
- package/dist/model-window-store-YFVNXCIP.js.map +1 -0
- package/dist/openai-compatible-RHZUBHV6.js +10 -0
- package/dist/openai-compatible-RHZUBHV6.js.map +1 -0
- package/dist/permission-approvals-76PVREFD.js +14 -0
- package/dist/permission-approvals-76PVREFD.js.map +1 -0
- package/dist/permissions-CHCKTFQQ.js +28 -0
- package/dist/permissions-CHCKTFQQ.js.map +1 -0
- package/dist/sandbox-CNVAQDA7.js +23 -0
- package/dist/sandbox-CNVAQDA7.js.map +1 -0
- package/dist/scrollback-LUKLIBVI.js +136 -0
- package/dist/scrollback-LUKLIBVI.js.map +1 -0
- package/dist/sdk.d.ts +4574 -0
- package/dist/sdk.js +665 -0
- package/dist/sdk.js.map +1 -0
- package/dist/settings-loader-M2QP5TX2.js +21 -0
- package/dist/settings-loader-M2QP5TX2.js.map +1 -0
- package/dist/settings-removed-JWWPT4WA.js +19 -0
- package/dist/settings-removed-JWWPT4WA.js.map +1 -0
- package/dist/shell-selection-V6QAYXPX.js +17 -0
- package/dist/shell-selection-V6QAYXPX.js.map +1 -0
- package/package.json +143 -0
- package/patches/ink+6.8.0.patch +13 -0
- package/scripts/apply-ink-patch.mjs +29 -0
- package/scripts/ink-patch.mjs +59 -0
- package/scripts/verify-ink-patch.mjs +11 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/jobs/persistent-store.ts","../src/agents/git-isolation.ts","../src/jobs/process-tree.ts","../src/system32.ts"],"sourcesContent":["import {\r\n existsSync,\r\n mkdirSync,\r\n readFileSync,\r\n readdirSync,\r\n renameSync,\r\n rmSync,\r\n writeFileSync,\r\n} from 'node:fs';\r\nimport { randomUUID } from 'node:crypto';\r\nimport { dirname, join } from 'node:path';\r\nimport { repositoryHash } from '../agents/git-isolation.js';\r\nimport type {\r\n BackgroundShellNotify,\r\n BackgroundShellStatus,\r\n CommandExecution,\r\n} from '../types/runtime.js';\r\nimport { resolveBookHome } from '../book-home.js';\r\n\r\nexport interface PersistentShellSpec {\r\n version: 1;\r\n id: string;\r\n command: string;\r\n effectiveCommand: string;\r\n /**\r\n * Present only for sandboxed commands. Specs written before this field\r\n * existed simply lack it and fall back to the shell path, which is what they\r\n * were already doing.\r\n */\r\n exec?: CommandExecution;\r\n title: string;\r\n workdir: string;\r\n env: Record<string, string>;\r\n sandboxed: boolean;\r\n notify: BackgroundShellNotify;\r\n timeoutMs?: number;\r\n parentSessionId?: string;\r\n rootRunId?: string;\r\n parentRunId?: string;\r\n token: string;\r\n tokenHash: string;\r\n recordPath: string;\r\n controlPath: string;\r\n outputPath: string;\r\n maxLogBytes: number;\r\n}\r\n\r\nexport interface PersistentShellState {\r\n version: 1;\r\n revision: number;\r\n id: string;\r\n command: string;\r\n title: string;\r\n workdir: string;\r\n status: BackgroundShellStatus;\r\n notify: BackgroundShellNotify;\r\n sandboxed: boolean;\r\n runnerPid: number;\r\n childPid?: number;\r\n tokenHash: string;\r\n startedAt: number;\r\n finishedAt?: number;\r\n heartbeatAt: number;\r\n /** Incremented whenever the bounded log is rewritten to its tail. */\r\n outputRotationSequence?: number;\r\n truncatedBytes?: number;\r\n timeoutMs?: number;\r\n deadlineAt?: number;\r\n exitCode?: number | null;\r\n signal?: NodeJS.Signals | string | null;\r\n stopReason?: string;\r\n parentSessionId?: string;\r\n rootRunId?: string;\r\n parentRunId?: string;\r\n outputPath: string;\r\n controlPath: string;\r\n completionSequence: number;\r\n completionDeliveredSequence: number;\r\n completionAcknowledgedSequence: number;\r\n}\r\n\r\nexport interface PersistentJobPaths {\r\n root: string;\r\n records: string;\r\n controls: string;\r\n logs: string;\r\n specs: string;\r\n}\r\n\r\nexport function persistentJobPaths(\r\n workspace: string,\r\n root = join(resolveBookHome(), 'jobs'),\r\n): PersistentJobPaths {\r\n const repositoryRoot = join(root, repositoryHash(workspace));\r\n return {\r\n root: repositoryRoot,\r\n records: join(repositoryRoot, 'records'),\r\n controls: join(repositoryRoot, 'controls'),\r\n logs: join(repositoryRoot, 'logs'),\r\n specs: join(repositoryRoot, 'specs'),\r\n };\r\n}\r\n\r\nexport function ensurePersistentJobPaths(paths: PersistentJobPaths): void {\r\n for (const path of [paths.root, paths.records, paths.controls, paths.logs, paths.specs]) {\r\n mkdirSync(path, { recursive: true });\r\n }\r\n}\r\n\r\nexport function writeJsonAtomic(path: string, value: unknown): void {\r\n mkdirSync(dirname(path), { recursive: true });\r\n const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;\r\n writeFileSync(temporary, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 });\r\n renameSync(temporary, path);\r\n}\r\n\r\nexport function readJsonFile<T>(path: string): T | undefined {\r\n try {\r\n return JSON.parse(readFileSync(path, 'utf8')) as T;\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nexport function listPersistentStates(paths: PersistentJobPaths): PersistentShellState[] {\r\n if (!existsSync(paths.records)) return [];\r\n return readdirSync(paths.records)\r\n .filter((name) => name.endsWith('.json'))\r\n .map((name) => readJsonFile<PersistentShellState>(join(paths.records, name)))\r\n .filter((state): state is PersistentShellState => state?.version === 1);\r\n}\r\n\r\nexport function removePersistentJobFiles(paths: PersistentJobPaths, id: string): void {\r\n for (const path of [\r\n join(paths.records, `${id}.json`),\r\n join(paths.controls, `${id}.json`),\r\n join(paths.logs, `${id}.log`),\r\n join(paths.specs, `${id}.json`),\r\n ]) {\r\n rmSync(path, { force: true });\r\n }\r\n}\r\n\r\nexport function removePersistentRunnerFiles(paths: PersistentJobPaths, id: string): void {\r\n for (const path of [join(paths.controls, `${id}.json`), join(paths.specs, `${id}.json`)]) {\r\n rmSync(path, { force: true });\r\n }\r\n}\r\n","import { createHash, randomUUID } from 'crypto';\r\nimport { execFile, spawn } from 'child_process';\r\nimport { existsSync, mkdirSync, mkdtempSync, rmSync } from 'fs';\r\nimport { tmpdir } from 'os';\r\nimport { dirname, join, resolve } from 'path';\r\nimport type { AgentApplyResult, AgentRecord, AgentSnapshot, PatchCandidate } from './types.js';\r\nimport { resolveBookHome } from '../book-home.js';\r\n\r\ninterface GitResult {\r\n stdout: string;\r\n stderr: string;\r\n code: number;\r\n}\r\n\r\nfunction git(\r\n cwd: string,\r\n args: string[],\r\n options?: { env?: NodeJS.ProcessEnv; allowExitCodes?: number[]; input?: string },\r\n): Promise<GitResult> {\r\n if (options?.input !== undefined) {\r\n return new Promise((resolvePromise, reject) => {\r\n const child = spawn('git', args, {\r\n cwd,\r\n env: { ...process.env, ...options.env },\r\n stdio: ['pipe', 'pipe', 'pipe'],\r\n });\r\n let stdout = '';\r\n let stderr = '';\r\n child.stdout.on('data', (chunk) => (stdout += chunk.toString()));\r\n child.stderr.on('data', (chunk) => (stderr += chunk.toString()));\r\n child.on('error', reject);\r\n child.on('close', (codeValue) => {\r\n const code = codeValue ?? 1;\r\n if (code === 0 || options.allowExitCodes?.includes(code)) {\r\n resolvePromise({ stdout, stderr, code });\r\n } else {\r\n reject(new Error(stderr.trim() || stdout.trim() || `git ${args[0]} failed (${code})`));\r\n }\r\n });\r\n child.stdin.end(options.input);\r\n });\r\n }\r\n\r\n return new Promise((resolvePromise, reject) => {\r\n execFile(\r\n 'git',\r\n args,\r\n {\r\n cwd,\r\n env: { ...process.env, ...options?.env },\r\n encoding: 'utf8',\r\n maxBuffer: 50 * 1024 * 1024,\r\n },\r\n (error, stdout, stderr) => {\r\n const code = typeof error?.code === 'number' ? error.code : error ? 1 : 0;\r\n if (!error || options?.allowExitCodes?.includes(code)) {\r\n resolvePromise({ stdout, stderr, code });\r\n return;\r\n }\r\n reject(new Error(stderr.trim() || stdout.trim() || error.message));\r\n },\r\n );\r\n });\r\n}\r\n\r\nfunction gitIdentityEnv(): NodeJS.ProcessEnv {\r\n return {\r\n GIT_AUTHOR_NAME: 'Book Agent',\r\n GIT_AUTHOR_EMAIL: 'agents@book.local',\r\n GIT_COMMITTER_NAME: 'Book Agent',\r\n GIT_COMMITTER_EMAIL: 'agents@book.local',\r\n };\r\n}\r\n\r\nfunction tempIndex(): { dir: string; path: string } {\r\n const dir = mkdtempSync(join(tmpdir(), 'book-index-'));\r\n return { dir, path: join(dir, 'index') };\r\n}\r\n\r\nexport async function findGitRoot(workspace: string): Promise<string | undefined> {\r\n try {\r\n return (await git(workspace, ['rev-parse', '--show-toplevel'])).stdout.trim();\r\n } catch {\r\n return undefined;\r\n }\r\n}\r\n\r\nexport function repositoryHash(repoRoot: string): string {\r\n return createHash('sha256').update(resolve(repoRoot).toLowerCase()).digest('hex').slice(0, 20);\r\n}\r\n\r\nasync function writeWorkspaceTree(\r\n repoRoot: string,\r\n includeUntracked: boolean,\r\n): Promise<{ head: string; tree: string; status: string }> {\r\n const index = tempIndex();\r\n try {\r\n const env = { GIT_INDEX_FILE: index.path };\r\n const head = (await git(repoRoot, ['rev-parse', 'HEAD'])).stdout.trim();\r\n await git(repoRoot, ['read-tree', head], { env });\r\n await git(repoRoot, includeUntracked ? ['add', '-A', '--', '.'] : ['add', '-u', '--', '.'], {\r\n env,\r\n });\r\n const tree = (await git(repoRoot, ['write-tree'], { env })).stdout.trim();\r\n const status = (await git(repoRoot, ['status', '--porcelain=v1', '--untracked-files=all']))\r\n .stdout;\r\n return { head, tree, status };\r\n } finally {\r\n rmSync(index.dir, { recursive: true, force: true });\r\n }\r\n}\r\n\r\nexport async function currentWorkspaceFingerprint(\r\n repoRoot: string,\r\n includeUntracked: boolean,\r\n): Promise<string> {\r\n const { head, tree } = await writeWorkspaceTree(repoRoot, includeUntracked);\r\n return `${head}:${tree}`;\r\n}\r\n\r\nexport async function createSyntheticSnapshot(\r\n workspace: string,\r\n includeUntracked = true,\r\n): Promise<AgentSnapshot> {\r\n const repoRoot = await findGitRoot(workspace);\r\n if (!repoRoot) {\r\n throw new Error(\r\n 'Managed agents require a Git workspace with at least one commit. Use --agents off for this workspace.',\r\n );\r\n }\r\n const id = randomUUID();\r\n const { head, tree, status } = await writeWorkspaceTree(repoRoot, includeUntracked);\r\n const commit = (\r\n await git(repoRoot, ['commit-tree', tree, '-p', head, '-m', `book synthetic snapshot ${id}`], {\r\n env: gitIdentityEnv(),\r\n })\r\n ).stdout.trim();\r\n const repoHash = repositoryHash(repoRoot);\r\n const ref = `refs/book/snapshots/${repoHash}/${id}`;\r\n await git(repoRoot, ['update-ref', ref, commit]);\r\n\r\n const manifestOutput = (\r\n await git(repoRoot, [\r\n '-c',\r\n 'core.quotepath=false',\r\n 'diff-tree',\r\n '--no-commit-id',\r\n '--name-status',\r\n '-r',\r\n '-M',\r\n head,\r\n commit,\r\n ])\r\n ).stdout;\r\n const manifest = manifestOutput\r\n .split(/\\r?\\n/)\r\n .filter(Boolean)\r\n .map((line) => {\r\n const [statusCode, ...paths] = line.split('\\t');\r\n return { status: statusCode, path: paths.join(' -> ') };\r\n });\r\n\r\n return {\r\n id,\r\n repoRoot,\r\n repoHash,\r\n baseHead: head,\r\n commit,\r\n tree,\r\n ref,\r\n fingerprint: `${head}:${tree}`,\r\n dirty: status.trim().length > 0,\r\n includeUntracked,\r\n manifest,\r\n createdAt: Date.now(),\r\n };\r\n}\r\n\r\n/** Where agent worktrees live when a host does not override the root. */\r\nexport function defaultWorktreeRoot(): string {\r\n return join(resolveBookHome(), 'worktrees');\r\n}\r\n\r\nexport async function createAgentWorktree(\r\n snapshot: AgentSnapshot,\r\n agentId: string,\r\n worktreeRoot = defaultWorktreeRoot(),\r\n startCommit = snapshot.commit,\r\n): Promise<{ path: string; branch: string }> {\r\n const path = join(worktreeRoot, snapshot.repoHash, agentId);\r\n const branch = `book-agent/${snapshot.repoHash}/${agentId}`;\r\n if (existsSync(path)) return { path, branch };\r\n mkdirSync(dirname(path), { recursive: true });\r\n await git(snapshot.repoRoot, ['worktree', 'add', '-b', branch, path, startCommit]);\r\n return { path, branch };\r\n}\r\n\r\nexport async function removeAgentWorktree(\r\n record: AgentRecord,\r\n repoRoot = record.worktree,\r\n): Promise<void> {\r\n if (!record.worktree || !record.branch) return;\r\n const root = repoRoot ?? record.worktree;\r\n await git(root, ['worktree', 'remove', '--force', record.worktree], {\r\n allowExitCodes: [128],\r\n }).catch(() => {});\r\n await git(root, ['branch', '-D', record.branch], { allowExitCodes: [1, 128] }).catch(() => {});\r\n}\r\n\r\nexport async function removeSnapshotRef(snapshot: AgentSnapshot): Promise<void> {\r\n await git(snapshot.repoRoot, ['update-ref', '-d', snapshot.ref], { allowExitCodes: [1] }).catch(\r\n () => {},\r\n );\r\n}\r\n\r\nexport async function checkoutAgentCommit(worktree: string, commit: string): Promise<void> {\r\n await git(worktree, ['reset', '--hard', commit]);\r\n}\r\n\r\nexport async function commitAgentWork(\r\n record: AgentRecord,\r\n snapshot: AgentSnapshot,\r\n): Promise<PatchCandidate | undefined> {\r\n if (!record.worktree || !record.branch) return undefined;\r\n await git(record.worktree, ['add', '-A', '--', '.']);\r\n const diff = await git(record.worktree, ['diff', '--cached', '--quiet'], { allowExitCodes: [1] });\r\n if (diff.code === 0) return undefined;\r\n await git(record.worktree, ['commit', '-m', `book agent ${record.id}: ${record.name}`], {\r\n env: gitIdentityEnv(),\r\n });\r\n const headCommit = (await git(record.worktree, ['rev-parse', 'HEAD'])).stdout.trim();\r\n return {\r\n baseCommit: snapshot.commit,\r\n headCommit,\r\n branch: record.branch,\r\n agentId: record.id,\r\n };\r\n}\r\n\r\nasync function candidateDelta(snapshot: AgentSnapshot, candidate: PatchCandidate): Promise<string> {\r\n return (\r\n await git(snapshot.repoRoot, [\r\n 'diff',\r\n '--binary',\r\n '--full-index',\r\n candidate.baseCommit,\r\n candidate.headCommit,\r\n '--',\r\n ])\r\n ).stdout;\r\n}\r\n\r\nasync function precheckDelta(snapshot: AgentSnapshot, patch: string): Promise<void> {\r\n const index = tempIndex();\r\n try {\r\n const env = { GIT_INDEX_FILE: index.path };\r\n await git(snapshot.repoRoot, ['read-tree', snapshot.commit], { env });\r\n await git(snapshot.repoRoot, ['apply', '--3way', '--check', '--cached', '-'], {\r\n env,\r\n input: patch,\r\n });\r\n await git(snapshot.repoRoot, ['apply', '--check', '-'], { input: patch });\r\n } finally {\r\n rmSync(index.dir, { recursive: true, force: true });\r\n }\r\n}\r\n\r\nexport async function applyVerifiedCandidate(\r\n snapshot: AgentSnapshot,\r\n candidate: PatchCandidate,\r\n): Promise<AgentApplyResult> {\r\n if (candidate.baseCommit !== snapshot.commit) {\r\n return {\r\n status: 'conflicted',\r\n error: 'Patch candidate base does not match the task snapshot.',\r\n };\r\n }\r\n const actualHead = (\r\n await git(snapshot.repoRoot, ['rev-parse', candidate.headCommit])\r\n ).stdout.trim();\r\n if (actualHead !== candidate.headCommit) {\r\n return { status: 'conflicted', error: 'Patch candidate commit no longer resolves exactly.' };\r\n }\r\n\r\n const current = await currentWorkspaceFingerprint(snapshot.repoRoot, snapshot.includeUntracked);\r\n if (current !== snapshot.fingerprint) {\r\n return {\r\n status: 'conflicted',\r\n error: 'Parent workspace drifted after the agent snapshot; no changes were applied.',\r\n };\r\n }\r\n\r\n const patch = await candidateDelta(snapshot, candidate);\r\n try {\r\n await precheckDelta(snapshot, patch);\r\n } catch (error) {\r\n return {\r\n status: 'conflicted',\r\n error: `Patch pre-check failed; no changes were applied: ${error instanceof Error ? error.message : String(error)}`,\r\n };\r\n }\r\n\r\n if (!snapshot.dirty) {\r\n try {\r\n await git(snapshot.repoRoot, ['cherry-pick', candidate.headCommit]);\r\n const appliedCommit = (await git(snapshot.repoRoot, ['rev-parse', 'HEAD'])).stdout.trim();\r\n return { status: 'applied', commit: appliedCommit };\r\n } catch (error) {\r\n await git(snapshot.repoRoot, ['cherry-pick', '--abort'], { allowExitCodes: [128] }).catch(\r\n () => {},\r\n );\r\n return {\r\n status: 'conflicted',\r\n error: `Cherry-pick failed and was rolled back: ${error instanceof Error ? error.message : String(error)}`,\r\n };\r\n }\r\n }\r\n\r\n try {\r\n await git(snapshot.repoRoot, ['apply', '-'], { input: patch });\r\n return { status: 'applied', commit: candidate.headCommit };\r\n } catch (error) {\r\n return {\r\n status: 'conflicted',\r\n error: `Patch application failed: ${error instanceof Error ? error.message : String(error)}`,\r\n };\r\n }\r\n}\r\n","/**\r\n * Process-liveness helpers shared by the shell manager and the detached job runner.\r\n *\r\n * A POSIX process group outlives its leader. Background commands run through `sh -c`, and the\r\n * shell forks the real worker instead of exec'ing it whenever it cannot hand its own process\r\n * over, so the direct child can exit — for instance by taking the SIGTERM a worker ignores —\r\n * while that worker keeps running in the same group. Termination therefore has to be judged on\r\n * group membership; the direct child's own exit proves nothing about the tree behind it.\r\n */\r\n\r\nimport { systemClock, type Clock } from '../clock.js';\r\n\r\nconst GROUP_POLL_INTERVAL_MS = 25;\r\n\r\n/** A signal target exists when it accepts signal 0, or rejects it as another user's. */\r\nfunction signalTargetExists(target: number): boolean {\r\n try {\r\n process.kill(target, 0);\r\n return true;\r\n } catch (error) {\r\n return (error as NodeJS.ErrnoException).code === 'EPERM';\r\n }\r\n}\r\n\r\nexport function isProcessAlive(pid: number | undefined): boolean {\r\n return pid === undefined ? false : signalTargetExists(pid);\r\n}\r\n\r\n/** True while any process remains in the group, whether or not the leader is one of them. */\r\nexport function isProcessGroupAlive(pgid: number | undefined): boolean {\r\n if (pgid === undefined || process.platform === 'win32') return false;\r\n return signalTargetExists(-pgid);\r\n}\r\n\r\n/** Signal every process in the group, falling back to the direct child when it has no group. */\r\nexport function signalProcessGroup(\r\n proc: { kill(signal: NodeJS.Signals): boolean } | undefined,\r\n pgid: number,\r\n signal: NodeJS.Signals,\r\n): void {\r\n try {\r\n process.kill(-pgid, signal);\r\n } catch {\r\n try {\r\n proc?.kill(signal);\r\n } catch {\r\n // The tree may have exited between the group signal and the direct-child fallback.\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Resolve `true` once the group holds no processes, or `false` when the bound elapses first.\r\n *\r\n * `kill(-pgid, 0)` also succeeds for a member that has exited but has not been reaped yet, so a\r\n * group reads as alive until its reaper runs. That bound is deliberate: a zombie holds no ports,\r\n * file handles, or CPU, and reporting \"not stopped\" for the moments before it is reaped is\r\n * honest, where trusting the direct child's exit is not.\r\n */\r\nexport async function waitForProcessGroupExit(\r\n pgid: number,\r\n timeoutMs: number,\r\n // A kill bound is a duration: on the wall clock, a backwards step here waits\r\n // past the bound and a forwards one abandons a group that was about to exit.\r\n clock: Clock = systemClock,\r\n): Promise<boolean> {\r\n const deadline = clock.monotonicNowMs() + timeoutMs;\r\n while (isProcessGroupAlive(pgid)) {\r\n if (clock.monotonicNowMs() >= deadline) return false;\r\n await new Promise((resolve) => setTimeout(resolve, GROUP_POLL_INTERVAL_MS));\r\n }\r\n return true;\r\n}\r\n","import { existsSync } from 'node:fs';\r\nimport { join } from 'node:path';\r\n\r\n/**\r\n * Resolve an executable from %SystemRoot%\\System32 on Windows.\r\n *\r\n * When System32 is missing from PATH (e.g. Git Bash, MSYS, or sanitized subprocess environments),\r\n * bare executable names fail with ENOENT under execFile / spawn with shell: false.\r\n * Off Windows, or if the executable is not found in System32, falls back to the bare name.\r\n */\r\nexport function system32Executable(\r\n name: string,\r\n platform: NodeJS.Platform = process.platform,\r\n env: NodeJS.ProcessEnv = process.env,\r\n): string {\r\n if (platform !== 'win32') return name;\r\n const fileName = name.toLowerCase().endsWith('.exe') ? name : `${name}.exe`;\r\n const systemRoot = env.SystemRoot || env.windir || 'C:\\\\Windows';\r\n const targetPath = join(systemRoot, 'System32', fileName);\r\n return existsSync(targetPath) ? targetPath : name;\r\n}\r\n"],"mappings":"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","names":["existsSync","mkdirSync","rmSync","randomUUID","dirname","join","join","mkdirSync","dirname","randomUUID","existsSync","rmSync","resolve","existsSync","join"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
updateWorkspaceTrust
|
|
3
|
+
} from "./chunk-DGMQEI6Y.js";
|
|
4
|
+
|
|
5
|
+
// src/mcp-approvals.ts
|
|
6
|
+
import { createHash } from "crypto";
|
|
7
|
+
function mcpServerFingerprint(config) {
|
|
8
|
+
const sortedRecord = (record) => Object.fromEntries(Object.entries(record ?? {}).sort(([a], [b]) => a.localeCompare(b)));
|
|
9
|
+
const canonical = JSON.stringify({
|
|
10
|
+
type: config.type ?? (config.url ? "http" : "stdio"),
|
|
11
|
+
command: config.command,
|
|
12
|
+
args: config.args ?? [],
|
|
13
|
+
env: sortedRecord(config.env),
|
|
14
|
+
cwd: config.cwd,
|
|
15
|
+
url: config.url,
|
|
16
|
+
// Values intentionally participate in the digest: changing an auth token
|
|
17
|
+
// or any other header changes what the user has approved. The digest is
|
|
18
|
+
// one-way and neither the UI nor diagnostics render these values.
|
|
19
|
+
headers: sortedRecord(config.headers)
|
|
20
|
+
});
|
|
21
|
+
return createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
22
|
+
}
|
|
23
|
+
function evaluateMcpServerApproval(settings, server) {
|
|
24
|
+
if (server.source === "user") return "approved";
|
|
25
|
+
const choice = settings.mcp?.projectServers?.[server.name];
|
|
26
|
+
if (!choice) return "unknown";
|
|
27
|
+
if (choice.fingerprint !== mcpServerFingerprint(server.config)) return "unknown";
|
|
28
|
+
return choice.choice;
|
|
29
|
+
}
|
|
30
|
+
function partitionMcpServersByApproval(servers, settings) {
|
|
31
|
+
const partition = { allowed: [], pending: [], rejected: [] };
|
|
32
|
+
for (const server of servers) {
|
|
33
|
+
const state = evaluateMcpServerApproval(settings, server);
|
|
34
|
+
if (state === "approved") partition.allowed.push(server);
|
|
35
|
+
else if (state === "rejected") partition.rejected.push(server);
|
|
36
|
+
else partition.pending.push(server);
|
|
37
|
+
}
|
|
38
|
+
return partition;
|
|
39
|
+
}
|
|
40
|
+
function mcpServersToRecord(servers) {
|
|
41
|
+
return Object.fromEntries(servers.map((server) => [server.name, server.config]));
|
|
42
|
+
}
|
|
43
|
+
function persistMcpProjectServerChoice(workspace, name, fingerprint, choice, options = {}) {
|
|
44
|
+
return updateWorkspaceTrust(
|
|
45
|
+
workspace,
|
|
46
|
+
(trust) => {
|
|
47
|
+
trust.mcpServers[name] = { fingerprint, choice };
|
|
48
|
+
},
|
|
49
|
+
options.trustStorePath
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
mcpServerFingerprint,
|
|
55
|
+
evaluateMcpServerApproval,
|
|
56
|
+
partitionMcpServersByApproval,
|
|
57
|
+
mcpServersToRecord,
|
|
58
|
+
persistMcpProjectServerChoice
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=chunk-URLQAXTR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/mcp-approvals.ts"],"sourcesContent":["/**\r\n * Trust decisions for MCP servers.\r\n *\r\n * Servers declared in a workspace `.mcp.json` are repository-controlled input:\r\n * connecting one executes an arbitrary command. Hosts therefore require a\r\n * one-time per-project approval, recorded against a fingerprint of the full\r\n * connection configuration in the user-global trust store, outside the\r\n * workspace, where nothing the repository ships can reach it (see\r\n * `workspace-trust.ts`).\r\n * A config change after approval invalidates the decision and re-prompts.\r\n * User-global servers (<BOOK_HOME>/.book/mcp.json) were written by the user\r\n * and never require approval.\r\n */\r\nimport { createHash } from 'crypto';\r\nimport type { McpProjectServerChoice, McpSettings } from './settings.js';\r\nimport type { McpServerConfig, ResolvedMcpServer } from './mcp-config.js';\r\nimport { updateWorkspaceTrust } from './workspace-trust.js';\r\n\r\n/** Stable digest of everything the user is asked to trust about a server. */\r\nexport function mcpServerFingerprint(config: McpServerConfig): string {\r\n const sortedRecord = (record: Record<string, string> | undefined) =>\r\n Object.fromEntries(Object.entries(record ?? {}).sort(([a], [b]) => a.localeCompare(b)));\r\n const canonical = JSON.stringify({\r\n type: config.type ?? (config.url ? 'http' : 'stdio'),\r\n command: config.command,\r\n args: config.args ?? [],\r\n env: sortedRecord(config.env),\r\n cwd: config.cwd,\r\n url: config.url,\r\n // Values intentionally participate in the digest: changing an auth token\r\n // or any other header changes what the user has approved. The digest is\r\n // one-way and neither the UI nor diagnostics render these values.\r\n headers: sortedRecord(config.headers),\r\n });\r\n return createHash('sha256').update(canonical).digest('hex').slice(0, 16);\r\n}\r\n\r\nexport type McpServerApprovalState = 'approved' | 'rejected' | 'unknown';\r\n\r\ninterface McpApprovalSettings {\r\n mcp?: Partial<McpSettings>;\r\n}\r\n\r\nexport function evaluateMcpServerApproval(\r\n settings: McpApprovalSettings,\r\n server: ResolvedMcpServer,\r\n): McpServerApprovalState {\r\n if (server.source === 'user') return 'approved';\r\n const choice: McpProjectServerChoice | undefined = settings.mcp?.projectServers?.[server.name];\r\n if (!choice) return 'unknown';\r\n if (choice.fingerprint !== mcpServerFingerprint(server.config)) return 'unknown';\r\n return choice.choice;\r\n}\r\n\r\nexport interface McpApprovalPartition {\r\n /** User-global servers plus approved project servers — safe to connect. */\r\n allowed: ResolvedMcpServer[];\r\n /** Project servers with no (valid) recorded decision — prompt or skip. */\r\n pending: ResolvedMcpServer[];\r\n /** Project servers the user rejected. */\r\n rejected: ResolvedMcpServer[];\r\n}\r\n\r\nexport function partitionMcpServersByApproval(\r\n servers: ResolvedMcpServer[],\r\n settings: McpApprovalSettings,\r\n): McpApprovalPartition {\r\n const partition: McpApprovalPartition = { allowed: [], pending: [], rejected: [] };\r\n for (const server of servers) {\r\n const state = evaluateMcpServerApproval(settings, server);\r\n if (state === 'approved') partition.allowed.push(server);\r\n else if (state === 'rejected') partition.rejected.push(server);\r\n else partition.pending.push(server);\r\n }\r\n return partition;\r\n}\r\n\r\nexport function mcpServersToRecord(servers: ResolvedMcpServer[]): Record<string, McpServerConfig> {\r\n return Object.fromEntries(servers.map((server) => [server.name, server.config]));\r\n}\r\n\r\n/**\r\n * Record an approve/reject decision for one server, leaving every other\r\n * recorded decision — in this workspace and in every other — untouched.\r\n */\r\nexport function persistMcpProjectServerChoice(\r\n workspace: string,\r\n name: string,\r\n fingerprint: string,\r\n choice: McpProjectServerChoice['choice'],\r\n options: { trustStorePath?: string } = {},\r\n): { ok: boolean; error?: string } {\r\n return updateWorkspaceTrust(\r\n workspace,\r\n (trust) => {\r\n trust.mcpServers[name] = { fingerprint, choice } satisfies McpProjectServerChoice;\r\n },\r\n options.trustStorePath,\r\n );\r\n}\r\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":[]}
|