@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.
Files changed (157) hide show
  1. package/CHANGELOG.md +2747 -40
  2. package/README.md +132 -1311
  3. package/dist/additional-roots-H7UNAEHS.js +31 -0
  4. package/dist/{app-MOXXWEOY.js → app-GE4OHJOC.js} +5740 -4045
  5. package/dist/app-GE4OHJOC.js.map +1 -0
  6. package/dist/{chunk-5GDZ22YP.js → chunk-224FJZLH.js} +99 -108
  7. package/dist/chunk-224FJZLH.js.map +1 -0
  8. package/dist/chunk-2AAOF5B5.js +28 -0
  9. package/dist/chunk-2AAOF5B5.js.map +1 -0
  10. package/dist/{chunk-CAGOB2N4.js → chunk-4AS7LKKQ.js} +6 -4
  11. package/dist/chunk-4AS7LKKQ.js.map +1 -0
  12. package/dist/chunk-525CAZPY.js +120 -0
  13. package/dist/chunk-525CAZPY.js.map +1 -0
  14. package/dist/{chunk-URLQAXTR.js → chunk-56SIEMDX.js} +2 -2
  15. package/dist/chunk-56SIEMDX.js.map +1 -0
  16. package/dist/{chunk-XIFFTATU.js → chunk-5YHTKWJN.js} +5420 -4031
  17. package/dist/chunk-5YHTKWJN.js.map +1 -0
  18. package/dist/chunk-62QL5QUP.js +283 -0
  19. package/dist/chunk-62QL5QUP.js.map +1 -0
  20. package/dist/{chunk-KWHQ2XXL.js → chunk-67UZEWWF.js} +3 -3
  21. package/dist/chunk-67UZEWWF.js.map +1 -0
  22. package/dist/chunk-6OQQOMZR.js +785 -0
  23. package/dist/chunk-6OQQOMZR.js.map +1 -0
  24. package/dist/{chunk-GTVB4HEM.js → chunk-AQEQX4FF.js} +344 -315
  25. package/dist/chunk-AQEQX4FF.js.map +1 -0
  26. package/dist/chunk-C7ADUNPR.js +273 -0
  27. package/dist/chunk-C7ADUNPR.js.map +1 -0
  28. package/dist/{chunk-E75IPST5.js → chunk-EFJ2NH6E.js} +12 -58
  29. package/dist/chunk-EFJ2NH6E.js.map +1 -0
  30. package/dist/{chunk-5KLVH3PY.js → chunk-HQI7NYGX.js} +5 -3
  31. package/dist/chunk-HQI7NYGX.js.map +1 -0
  32. package/dist/chunk-IME7R2UI.js +6541 -0
  33. package/dist/chunk-IME7R2UI.js.map +1 -0
  34. package/dist/chunk-J4F2QLM2.js +273 -0
  35. package/dist/chunk-J4F2QLM2.js.map +1 -0
  36. package/dist/{chunk-MAQXCUR4.js → chunk-J62VZYOB.js} +444 -112
  37. package/dist/chunk-J62VZYOB.js.map +1 -0
  38. package/dist/chunk-JTPOI3KU.js +826 -0
  39. package/dist/chunk-JTPOI3KU.js.map +1 -0
  40. package/dist/{chunk-WKBO5R4O.js → chunk-KBQS35OV.js} +2 -2
  41. package/dist/chunk-KBQS35OV.js.map +1 -0
  42. package/dist/chunk-KXAYUIKP.js +90 -0
  43. package/dist/chunk-KXAYUIKP.js.map +1 -0
  44. package/dist/{chunk-VZQXX3WW.js → chunk-QYBD22H5.js} +1 -1
  45. package/dist/chunk-QYBD22H5.js.map +1 -0
  46. package/dist/{chunk-V4BOYR52.js → chunk-RFJ6Q6LE.js} +763 -198
  47. package/dist/chunk-RFJ6Q6LE.js.map +1 -0
  48. package/dist/{chunk-7N4J557C.js → chunk-RT5SATF7.js} +1 -1
  49. package/dist/chunk-RT5SATF7.js.map +1 -0
  50. package/dist/{chunk-C7D4NJ2D.js → chunk-S4JMID4G.js} +68 -26
  51. package/dist/chunk-S4JMID4G.js.map +1 -0
  52. package/dist/{chunk-ANSJPYGI.js → chunk-SOW4WRQP.js} +3 -3
  53. package/dist/chunk-SOW4WRQP.js.map +1 -0
  54. package/dist/{chunk-J7MIZ5CA.js → chunk-TLX3H2CL.js} +518 -79
  55. package/dist/chunk-TLX3H2CL.js.map +1 -0
  56. package/dist/{chunk-DGMQEI6Y.js → chunk-VPNLCMVR.js} +22 -9
  57. package/dist/chunk-VPNLCMVR.js.map +1 -0
  58. package/dist/{chunk-BTZZUCLI.js → chunk-W3XFHEF4.js} +173 -29
  59. package/dist/chunk-W3XFHEF4.js.map +1 -0
  60. package/dist/{chunk-574C6MQR.js → chunk-X4NJT436.js} +130 -60
  61. package/dist/chunk-X4NJT436.js.map +1 -0
  62. package/dist/chunk-X66I2NQO.js +137 -0
  63. package/dist/chunk-X66I2NQO.js.map +1 -0
  64. package/dist/chunk-XHAO3K7I.js +18 -0
  65. package/dist/chunk-XHAO3K7I.js.map +1 -0
  66. package/dist/{chunk-3U5IBM24.js → chunk-Y2VHN7TC.js} +2 -2
  67. package/dist/chunk-Y2VHN7TC.js.map +1 -0
  68. package/dist/chunk-YC6W4EJH.js +947 -0
  69. package/dist/chunk-YC6W4EJH.js.map +1 -0
  70. package/dist/{chunk-24AXE6SP.js → chunk-ZPSZZUWF.js} +1 -1
  71. package/dist/chunk-ZPSZZUWF.js.map +1 -0
  72. package/dist/{command-approvals-6Y57VCWZ.js → command-approvals-VBIU6BLM.js} +6 -5
  73. package/dist/context-CBOBDY5Q.js +38 -0
  74. package/dist/{hook-approvals-CTUFKEJT.js → hook-approvals-ZVT4V7IM.js} +5 -5
  75. package/dist/index.js +996 -155
  76. package/dist/index.js.map +1 -1
  77. package/dist/{interactive-assets-CCAAWVKH.js → interactive-assets-T4I4SEHU.js} +13 -9
  78. package/dist/interactive-assets-T4I4SEHU.js.map +1 -0
  79. package/dist/job-runner.js +159 -111
  80. package/dist/job-runner.js.map +1 -1
  81. package/dist/job-supervisor.d.ts +2 -0
  82. package/dist/job-supervisor.js +112 -0
  83. package/dist/job-supervisor.js.map +1 -0
  84. package/dist/loader-S77OVTHH.js +18 -0
  85. package/dist/{mcp-approvals-WNPH65BK.js → mcp-approvals-HJ2VDYMC.js} +5 -5
  86. package/dist/{mcp-config-YIJIES2C.js → mcp-config-RNB5LEVE.js} +3 -3
  87. package/dist/memory-store-DZUT5HUH.js +75 -0
  88. package/dist/{model-window-store-YFVNXCIP.js → model-window-store-MTSQ5JDJ.js} +8 -4
  89. package/dist/openai-compatible-2OB54YST.js +14 -0
  90. package/dist/{permission-approvals-76PVREFD.js → permission-approvals-YLV6UC5W.js} +5 -5
  91. package/dist/permissions-DHOKF3W2.js +45 -0
  92. package/dist/sandbox-WRYTZH3D.js +41 -0
  93. package/dist/{scrollback-LUKLIBVI.js → scrollback-35ME2GFX.js} +32 -22
  94. package/dist/scrollback-35ME2GFX.js.map +1 -0
  95. package/dist/sdk.d.ts +2219 -2361
  96. package/dist/sdk.js +53 -38
  97. package/dist/sdk.js.map +1 -1
  98. package/dist/settings-loader-JUNPGAAM.js +35 -0
  99. package/dist/settings-loader-JUNPGAAM.js.map +1 -0
  100. package/dist/{shell-selection-V6QAYXPX.js → shell-selection-CDHGJ4GC.js} +2 -2
  101. package/dist/shell-selection-CDHGJ4GC.js.map +1 -0
  102. package/package.json +23 -22
  103. package/dist/app-MOXXWEOY.js.map +0 -1
  104. package/dist/chunk-24AXE6SP.js.map +0 -1
  105. package/dist/chunk-3U5IBM24.js.map +0 -1
  106. package/dist/chunk-574C6MQR.js.map +0 -1
  107. package/dist/chunk-5GDZ22YP.js.map +0 -1
  108. package/dist/chunk-5KLVH3PY.js.map +0 -1
  109. package/dist/chunk-7N4J557C.js.map +0 -1
  110. package/dist/chunk-A7JLBK2Y.js +0 -3506
  111. package/dist/chunk-A7JLBK2Y.js.map +0 -1
  112. package/dist/chunk-ANSJPYGI.js.map +0 -1
  113. package/dist/chunk-BTZZUCLI.js.map +0 -1
  114. package/dist/chunk-C7D4NJ2D.js.map +0 -1
  115. package/dist/chunk-CAGOB2N4.js.map +0 -1
  116. package/dist/chunk-DGMQEI6Y.js.map +0 -1
  117. package/dist/chunk-E75IPST5.js.map +0 -1
  118. package/dist/chunk-EYGD6X2T.js +0 -180
  119. package/dist/chunk-EYGD6X2T.js.map +0 -1
  120. package/dist/chunk-GTVB4HEM.js.map +0 -1
  121. package/dist/chunk-J7MIZ5CA.js.map +0 -1
  122. package/dist/chunk-KWHQ2XXL.js.map +0 -1
  123. package/dist/chunk-L6LK2QIQ.js +0 -657
  124. package/dist/chunk-L6LK2QIQ.js.map +0 -1
  125. package/dist/chunk-MAQXCUR4.js.map +0 -1
  126. package/dist/chunk-S4XL7HOM.js +0 -402
  127. package/dist/chunk-S4XL7HOM.js.map +0 -1
  128. package/dist/chunk-URLQAXTR.js.map +0 -1
  129. package/dist/chunk-V4BOYR52.js.map +0 -1
  130. package/dist/chunk-VZQXX3WW.js.map +0 -1
  131. package/dist/chunk-WKBO5R4O.js.map +0 -1
  132. package/dist/chunk-XIFFTATU.js.map +0 -1
  133. package/dist/context-ZC5IFEAX.js +0 -25
  134. package/dist/interactive-assets-CCAAWVKH.js.map +0 -1
  135. package/dist/loader-4IDS2RIE.js +0 -15
  136. package/dist/openai-compatible-RHZUBHV6.js +0 -10
  137. package/dist/permissions-CHCKTFQQ.js +0 -28
  138. package/dist/sandbox-CNVAQDA7.js +0 -23
  139. package/dist/scrollback-LUKLIBVI.js.map +0 -1
  140. package/dist/settings-loader-M2QP5TX2.js +0 -21
  141. package/patches/ink+6.8.0.patch +0 -13
  142. package/scripts/apply-ink-patch.mjs +0 -29
  143. package/scripts/ink-patch.mjs +0 -59
  144. package/scripts/verify-ink-patch.mjs +0 -11
  145. /package/dist/{command-approvals-6Y57VCWZ.js.map → additional-roots-H7UNAEHS.js.map} +0 -0
  146. /package/dist/{context-ZC5IFEAX.js.map → command-approvals-VBIU6BLM.js.map} +0 -0
  147. /package/dist/{hook-approvals-CTUFKEJT.js.map → context-CBOBDY5Q.js.map} +0 -0
  148. /package/dist/{loader-4IDS2RIE.js.map → hook-approvals-ZVT4V7IM.js.map} +0 -0
  149. /package/dist/{mcp-approvals-WNPH65BK.js.map → loader-S77OVTHH.js.map} +0 -0
  150. /package/dist/{mcp-config-YIJIES2C.js.map → mcp-approvals-HJ2VDYMC.js.map} +0 -0
  151. /package/dist/{model-window-store-YFVNXCIP.js.map → mcp-config-RNB5LEVE.js.map} +0 -0
  152. /package/dist/{openai-compatible-RHZUBHV6.js.map → memory-store-DZUT5HUH.js.map} +0 -0
  153. /package/dist/{permission-approvals-76PVREFD.js.map → model-window-store-MTSQ5JDJ.js.map} +0 -0
  154. /package/dist/{permissions-CHCKTFQQ.js.map → openai-compatible-2OB54YST.js.map} +0 -0
  155. /package/dist/{sandbox-CNVAQDA7.js.map → permission-approvals-YLV6UC5W.js.map} +0 -0
  156. /package/dist/{settings-loader-M2QP5TX2.js.map → permissions-DHOKF3W2.js.map} +0 -0
  157. /package/dist/{shell-selection-V6QAYXPX.js.map → sandbox-WRYTZH3D.js.map} +0 -0
@@ -1,402 +0,0 @@
1
- import {
2
- systemClock
3
- } from "./chunk-7N4J557C.js";
4
- import {
5
- resolveBookHome
6
- } from "./chunk-24AXE6SP.js";
7
-
8
- // src/jobs/persistent-store.ts
9
- import {
10
- existsSync as existsSync2,
11
- mkdirSync as mkdirSync2,
12
- readFileSync,
13
- readdirSync,
14
- renameSync,
15
- rmSync as rmSync2,
16
- writeFileSync
17
- } from "fs";
18
- import { randomUUID as randomUUID2 } from "crypto";
19
- import { dirname as dirname2, join as join2 } from "path";
20
-
21
- // src/agents/git-isolation.ts
22
- import { createHash, randomUUID } from "crypto";
23
- import { execFile, spawn } from "child_process";
24
- import { existsSync, mkdirSync, mkdtempSync, rmSync } from "fs";
25
- import { tmpdir } from "os";
26
- import { dirname, join, resolve } from "path";
27
- function git(cwd, args, options) {
28
- if (options?.input !== void 0) {
29
- return new Promise((resolvePromise, reject) => {
30
- const child = spawn("git", args, {
31
- cwd,
32
- env: { ...process.env, ...options.env },
33
- stdio: ["pipe", "pipe", "pipe"]
34
- });
35
- let stdout = "";
36
- let stderr = "";
37
- child.stdout.on("data", (chunk) => stdout += chunk.toString());
38
- child.stderr.on("data", (chunk) => stderr += chunk.toString());
39
- child.on("error", reject);
40
- child.on("close", (codeValue) => {
41
- const code = codeValue ?? 1;
42
- if (code === 0 || options.allowExitCodes?.includes(code)) {
43
- resolvePromise({ stdout, stderr, code });
44
- } else {
45
- reject(new Error(stderr.trim() || stdout.trim() || `git ${args[0]} failed (${code})`));
46
- }
47
- });
48
- child.stdin.end(options.input);
49
- });
50
- }
51
- return new Promise((resolvePromise, reject) => {
52
- execFile(
53
- "git",
54
- args,
55
- {
56
- cwd,
57
- env: { ...process.env, ...options?.env },
58
- encoding: "utf8",
59
- maxBuffer: 50 * 1024 * 1024
60
- },
61
- (error, stdout, stderr) => {
62
- const code = typeof error?.code === "number" ? error.code : error ? 1 : 0;
63
- if (!error || options?.allowExitCodes?.includes(code)) {
64
- resolvePromise({ stdout, stderr, code });
65
- return;
66
- }
67
- reject(new Error(stderr.trim() || stdout.trim() || error.message));
68
- }
69
- );
70
- });
71
- }
72
- function gitIdentityEnv() {
73
- return {
74
- GIT_AUTHOR_NAME: "Book Agent",
75
- GIT_AUTHOR_EMAIL: "agents@book.local",
76
- GIT_COMMITTER_NAME: "Book Agent",
77
- GIT_COMMITTER_EMAIL: "agents@book.local"
78
- };
79
- }
80
- function tempIndex() {
81
- const dir = mkdtempSync(join(tmpdir(), "book-index-"));
82
- return { dir, path: join(dir, "index") };
83
- }
84
- async function findGitRoot(workspace) {
85
- try {
86
- return (await git(workspace, ["rev-parse", "--show-toplevel"])).stdout.trim();
87
- } catch {
88
- return void 0;
89
- }
90
- }
91
- function repositoryHash(repoRoot) {
92
- return createHash("sha256").update(resolve(repoRoot).toLowerCase()).digest("hex").slice(0, 20);
93
- }
94
- async function writeWorkspaceTree(repoRoot, includeUntracked) {
95
- const index = tempIndex();
96
- try {
97
- const env = { GIT_INDEX_FILE: index.path };
98
- const head = (await git(repoRoot, ["rev-parse", "HEAD"])).stdout.trim();
99
- await git(repoRoot, ["read-tree", head], { env });
100
- await git(repoRoot, includeUntracked ? ["add", "-A", "--", "."] : ["add", "-u", "--", "."], {
101
- env
102
- });
103
- const tree = (await git(repoRoot, ["write-tree"], { env })).stdout.trim();
104
- const status = (await git(repoRoot, ["status", "--porcelain=v1", "--untracked-files=all"])).stdout;
105
- return { head, tree, status };
106
- } finally {
107
- rmSync(index.dir, { recursive: true, force: true });
108
- }
109
- }
110
- async function currentWorkspaceFingerprint(repoRoot, includeUntracked) {
111
- const { head, tree } = await writeWorkspaceTree(repoRoot, includeUntracked);
112
- return `${head}:${tree}`;
113
- }
114
- async function createSyntheticSnapshot(workspace, includeUntracked = true) {
115
- const repoRoot = await findGitRoot(workspace);
116
- if (!repoRoot) {
117
- throw new Error(
118
- "Managed agents require a Git workspace with at least one commit. Use --agents off for this workspace."
119
- );
120
- }
121
- const id = randomUUID();
122
- const { head, tree, status } = await writeWorkspaceTree(repoRoot, includeUntracked);
123
- const commit = (await git(repoRoot, ["commit-tree", tree, "-p", head, "-m", `book synthetic snapshot ${id}`], {
124
- env: gitIdentityEnv()
125
- })).stdout.trim();
126
- const repoHash = repositoryHash(repoRoot);
127
- const ref = `refs/book/snapshots/${repoHash}/${id}`;
128
- await git(repoRoot, ["update-ref", ref, commit]);
129
- const manifestOutput = (await git(repoRoot, [
130
- "-c",
131
- "core.quotepath=false",
132
- "diff-tree",
133
- "--no-commit-id",
134
- "--name-status",
135
- "-r",
136
- "-M",
137
- head,
138
- commit
139
- ])).stdout;
140
- const manifest = manifestOutput.split(/\r?\n/).filter(Boolean).map((line) => {
141
- const [statusCode, ...paths] = line.split(" ");
142
- return { status: statusCode, path: paths.join(" -> ") };
143
- });
144
- return {
145
- id,
146
- repoRoot,
147
- repoHash,
148
- baseHead: head,
149
- commit,
150
- tree,
151
- ref,
152
- fingerprint: `${head}:${tree}`,
153
- dirty: status.trim().length > 0,
154
- includeUntracked,
155
- manifest,
156
- createdAt: Date.now()
157
- };
158
- }
159
- function defaultWorktreeRoot() {
160
- return join(resolveBookHome(), "worktrees");
161
- }
162
- async function createAgentWorktree(snapshot, agentId, worktreeRoot = defaultWorktreeRoot(), startCommit = snapshot.commit) {
163
- const path = join(worktreeRoot, snapshot.repoHash, agentId);
164
- const branch = `book-agent/${snapshot.repoHash}/${agentId}`;
165
- if (existsSync(path)) return { path, branch };
166
- mkdirSync(dirname(path), { recursive: true });
167
- await git(snapshot.repoRoot, ["worktree", "add", "-b", branch, path, startCommit]);
168
- return { path, branch };
169
- }
170
- async function removeAgentWorktree(record, repoRoot = record.worktree) {
171
- if (!record.worktree || !record.branch) return;
172
- const root = repoRoot ?? record.worktree;
173
- await git(root, ["worktree", "remove", "--force", record.worktree], {
174
- allowExitCodes: [128]
175
- }).catch(() => {
176
- });
177
- await git(root, ["branch", "-D", record.branch], { allowExitCodes: [1, 128] }).catch(() => {
178
- });
179
- }
180
- async function removeSnapshotRef(snapshot) {
181
- await git(snapshot.repoRoot, ["update-ref", "-d", snapshot.ref], { allowExitCodes: [1] }).catch(
182
- () => {
183
- }
184
- );
185
- }
186
- async function checkoutAgentCommit(worktree, commit) {
187
- await git(worktree, ["reset", "--hard", commit]);
188
- }
189
- async function commitAgentWork(record, snapshot) {
190
- if (!record.worktree || !record.branch) return void 0;
191
- await git(record.worktree, ["add", "-A", "--", "."]);
192
- const diff = await git(record.worktree, ["diff", "--cached", "--quiet"], { allowExitCodes: [1] });
193
- if (diff.code === 0) return void 0;
194
- await git(record.worktree, ["commit", "-m", `book agent ${record.id}: ${record.name}`], {
195
- env: gitIdentityEnv()
196
- });
197
- const headCommit = (await git(record.worktree, ["rev-parse", "HEAD"])).stdout.trim();
198
- return {
199
- baseCommit: snapshot.commit,
200
- headCommit,
201
- branch: record.branch,
202
- agentId: record.id
203
- };
204
- }
205
- async function candidateDelta(snapshot, candidate) {
206
- return (await git(snapshot.repoRoot, [
207
- "diff",
208
- "--binary",
209
- "--full-index",
210
- candidate.baseCommit,
211
- candidate.headCommit,
212
- "--"
213
- ])).stdout;
214
- }
215
- async function precheckDelta(snapshot, patch) {
216
- const index = tempIndex();
217
- try {
218
- const env = { GIT_INDEX_FILE: index.path };
219
- await git(snapshot.repoRoot, ["read-tree", snapshot.commit], { env });
220
- await git(snapshot.repoRoot, ["apply", "--3way", "--check", "--cached", "-"], {
221
- env,
222
- input: patch
223
- });
224
- await git(snapshot.repoRoot, ["apply", "--check", "-"], { input: patch });
225
- } finally {
226
- rmSync(index.dir, { recursive: true, force: true });
227
- }
228
- }
229
- async function applyVerifiedCandidate(snapshot, candidate) {
230
- if (candidate.baseCommit !== snapshot.commit) {
231
- return {
232
- status: "conflicted",
233
- error: "Patch candidate base does not match the task snapshot."
234
- };
235
- }
236
- const actualHead = (await git(snapshot.repoRoot, ["rev-parse", candidate.headCommit])).stdout.trim();
237
- if (actualHead !== candidate.headCommit) {
238
- return { status: "conflicted", error: "Patch candidate commit no longer resolves exactly." };
239
- }
240
- const current = await currentWorkspaceFingerprint(snapshot.repoRoot, snapshot.includeUntracked);
241
- if (current !== snapshot.fingerprint) {
242
- return {
243
- status: "conflicted",
244
- error: "Parent workspace drifted after the agent snapshot; no changes were applied."
245
- };
246
- }
247
- const patch = await candidateDelta(snapshot, candidate);
248
- try {
249
- await precheckDelta(snapshot, patch);
250
- } catch (error) {
251
- return {
252
- status: "conflicted",
253
- error: `Patch pre-check failed; no changes were applied: ${error instanceof Error ? error.message : String(error)}`
254
- };
255
- }
256
- if (!snapshot.dirty) {
257
- try {
258
- await git(snapshot.repoRoot, ["cherry-pick", candidate.headCommit]);
259
- const appliedCommit = (await git(snapshot.repoRoot, ["rev-parse", "HEAD"])).stdout.trim();
260
- return { status: "applied", commit: appliedCommit };
261
- } catch (error) {
262
- await git(snapshot.repoRoot, ["cherry-pick", "--abort"], { allowExitCodes: [128] }).catch(
263
- () => {
264
- }
265
- );
266
- return {
267
- status: "conflicted",
268
- error: `Cherry-pick failed and was rolled back: ${error instanceof Error ? error.message : String(error)}`
269
- };
270
- }
271
- }
272
- try {
273
- await git(snapshot.repoRoot, ["apply", "-"], { input: patch });
274
- return { status: "applied", commit: candidate.headCommit };
275
- } catch (error) {
276
- return {
277
- status: "conflicted",
278
- error: `Patch application failed: ${error instanceof Error ? error.message : String(error)}`
279
- };
280
- }
281
- }
282
-
283
- // src/jobs/persistent-store.ts
284
- function persistentJobPaths(workspace, root = join2(resolveBookHome(), "jobs")) {
285
- const repositoryRoot = join2(root, repositoryHash(workspace));
286
- return {
287
- root: repositoryRoot,
288
- records: join2(repositoryRoot, "records"),
289
- controls: join2(repositoryRoot, "controls"),
290
- logs: join2(repositoryRoot, "logs"),
291
- specs: join2(repositoryRoot, "specs")
292
- };
293
- }
294
- function ensurePersistentJobPaths(paths) {
295
- for (const path of [paths.root, paths.records, paths.controls, paths.logs, paths.specs]) {
296
- mkdirSync2(path, { recursive: true });
297
- }
298
- }
299
- function writeJsonAtomic(path, value) {
300
- mkdirSync2(dirname2(path), { recursive: true });
301
- const temporary = `${path}.${process.pid}.${randomUUID2()}.tmp`;
302
- writeFileSync(temporary, JSON.stringify(value, null, 2), { encoding: "utf8", mode: 384 });
303
- renameSync(temporary, path);
304
- }
305
- function readJsonFile(path) {
306
- try {
307
- return JSON.parse(readFileSync(path, "utf8"));
308
- } catch {
309
- return void 0;
310
- }
311
- }
312
- function listPersistentStates(paths) {
313
- if (!existsSync2(paths.records)) return [];
314
- return readdirSync(paths.records).filter((name) => name.endsWith(".json")).map((name) => readJsonFile(join2(paths.records, name))).filter((state) => state?.version === 1);
315
- }
316
- function removePersistentJobFiles(paths, id) {
317
- for (const path of [
318
- join2(paths.records, `${id}.json`),
319
- join2(paths.controls, `${id}.json`),
320
- join2(paths.logs, `${id}.log`),
321
- join2(paths.specs, `${id}.json`)
322
- ]) {
323
- rmSync2(path, { force: true });
324
- }
325
- }
326
- function removePersistentRunnerFiles(paths, id) {
327
- for (const path of [join2(paths.controls, `${id}.json`), join2(paths.specs, `${id}.json`)]) {
328
- rmSync2(path, { force: true });
329
- }
330
- }
331
-
332
- // src/jobs/process-tree.ts
333
- var GROUP_POLL_INTERVAL_MS = 25;
334
- function signalTargetExists(target) {
335
- try {
336
- process.kill(target, 0);
337
- return true;
338
- } catch (error) {
339
- return error.code === "EPERM";
340
- }
341
- }
342
- function isProcessAlive(pid) {
343
- return pid === void 0 ? false : signalTargetExists(pid);
344
- }
345
- function isProcessGroupAlive(pgid) {
346
- if (pgid === void 0 || process.platform === "win32") return false;
347
- return signalTargetExists(-pgid);
348
- }
349
- function signalProcessGroup(proc, pgid, signal) {
350
- try {
351
- process.kill(-pgid, signal);
352
- } catch {
353
- try {
354
- proc?.kill(signal);
355
- } catch {
356
- }
357
- }
358
- }
359
- async function waitForProcessGroupExit(pgid, timeoutMs, clock = systemClock) {
360
- const deadline = clock.monotonicNowMs() + timeoutMs;
361
- while (isProcessGroupAlive(pgid)) {
362
- if (clock.monotonicNowMs() >= deadline) return false;
363
- await new Promise((resolve2) => setTimeout(resolve2, GROUP_POLL_INTERVAL_MS));
364
- }
365
- return true;
366
- }
367
-
368
- // src/system32.ts
369
- import { existsSync as existsSync3 } from "fs";
370
- import { join as join3 } from "path";
371
- function system32Executable(name, platform = process.platform, env = process.env) {
372
- if (platform !== "win32") return name;
373
- const fileName = name.toLowerCase().endsWith(".exe") ? name : `${name}.exe`;
374
- const systemRoot = env.SystemRoot || env.windir || "C:\\Windows";
375
- const targetPath = join3(systemRoot, "System32", fileName);
376
- return existsSync3(targetPath) ? targetPath : name;
377
- }
378
-
379
- export {
380
- findGitRoot,
381
- repositoryHash,
382
- createSyntheticSnapshot,
383
- defaultWorktreeRoot,
384
- createAgentWorktree,
385
- removeAgentWorktree,
386
- removeSnapshotRef,
387
- checkoutAgentCommit,
388
- commitAgentWork,
389
- applyVerifiedCandidate,
390
- persistentJobPaths,
391
- ensurePersistentJobPaths,
392
- writeJsonAtomic,
393
- readJsonFile,
394
- listPersistentStates,
395
- removePersistentJobFiles,
396
- removePersistentRunnerFiles,
397
- isProcessAlive,
398
- signalProcessGroup,
399
- waitForProcessGroupExit,
400
- system32Executable
401
- };
402
- //# sourceMappingURL=chunk-S4XL7HOM.js.map
@@ -1 +0,0 @@
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"]}
@@ -1 +0,0 @@
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":[]}