pi-forge 1.3.4 → 1.3.6

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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/client/assets/{CodeMirrorEditor-B4s5rJXF.js → CodeMirrorEditor-BH6J6WhM.js} +2 -2
  3. package/dist/client/assets/{CodeMirrorEditor-B4s5rJXF.js.map → CodeMirrorEditor-BH6J6WhM.js.map} +1 -1
  4. package/dist/client/assets/index-BszHzUYE.css +1 -0
  5. package/dist/client/assets/{index-x-u_kklU.js → index-bJJrpg1R.js} +84 -84
  6. package/dist/client/assets/index-bJJrpg1R.js.map +1 -0
  7. package/dist/client/index.html +2 -2
  8. package/dist/client/sw.js +1 -1
  9. package/dist/client/sw.js.map +1 -1
  10. package/dist/server/git-runner.js +57 -0
  11. package/dist/server/git-runner.js.map +1 -1
  12. package/dist/server/mcp/manager.js +40 -5
  13. package/dist/server/mcp/manager.js.map +1 -1
  14. package/dist/server/orchestration/event-bridge.js +11 -0
  15. package/dist/server/orchestration/event-bridge.js.map +1 -1
  16. package/dist/server/orchestration/tools.js +16 -14
  17. package/dist/server/orchestration/tools.js.map +1 -1
  18. package/dist/server/orchestration/worker-lifecycle.js +68 -0
  19. package/dist/server/orchestration/worker-lifecycle.js.map +1 -0
  20. package/dist/server/routes/git.js +227 -216
  21. package/dist/server/routes/git.js.map +1 -1
  22. package/dist/server/routes/orchestration.js +10 -8
  23. package/dist/server/routes/orchestration.js.map +1 -1
  24. package/dist/server/routes/projects.js +13 -8
  25. package/dist/server/routes/projects.js.map +1 -1
  26. package/dist/server/routes/sessions.js +32 -13
  27. package/dist/server/routes/sessions.js.map +1 -1
  28. package/dist/server/routes/terminal.js +18 -7
  29. package/dist/server/routes/terminal.js.map +1 -1
  30. package/dist/server/session-registry.js +34 -4
  31. package/dist/server/session-registry.js.map +1 -1
  32. package/dist/server/sse-bridge.js +19 -0
  33. package/dist/server/sse-bridge.js.map +1 -1
  34. package/package.json +4 -4
  35. package/dist/client/assets/index-VyHgL4FY.css +0 -1
  36. package/dist/client/assets/index-x-u_kklU.js.map +0 -1
@@ -1,6 +1,7 @@
1
- import { GitCommandError, GitNotInstalledError, InvalidBranchNameError, checkoutBranch, commit, createBranch, deleteBranch, fetch, addRemote, getBranches, getRemotes, removeRemote, getDiff, getFileDiff, getLog, getStagedDiff, getStatus, initRepo, isGitRepo, pull, push, revertPaths, stagePaths, unstagePaths, } from "../git-runner.js";
1
+ import { GitCommandError, GitNotInstalledError, InvalidBranchNameError, checkoutBranch, commit, createBranch, deleteBranch, fetch, addRemote, getBranches, getRemotes, removeRemote, getDiff, getFileDiff, getLog, getStagedDiff, getStatus, getWorktrees, initRepo, isGitRepo, pull, push, revertPaths, stagePaths, unstagePaths, } from "../git-runner.js";
2
2
  import { applyHunks, HunkStagingError } from "../git-hunk-stager.js";
3
3
  import { config } from "../config.js";
4
+ import { PathOutsideRootError } from "../file-manager.js";
4
5
  import { getProject } from "../project-manager.js";
5
6
  import { errorSchema } from "./_schemas.js";
6
7
  /* ----------------------------- schemas ----------------------------- */
@@ -107,6 +108,34 @@ const remotesSchema = {
107
108
  },
108
109
  },
109
110
  };
111
+ const worktreesSchema = {
112
+ type: "object",
113
+ required: ["isGitRepo", "worktrees"],
114
+ properties: {
115
+ isGitRepo: { type: "boolean" },
116
+ worktrees: {
117
+ type: "array",
118
+ items: {
119
+ type: "object",
120
+ required: ["path", "bare", "detached", "current"],
121
+ properties: {
122
+ path: { type: "string" },
123
+ head: { type: "string" },
124
+ branch: { type: "string" },
125
+ bare: { type: "boolean" },
126
+ detached: { type: "boolean" },
127
+ current: { type: "boolean" },
128
+ },
129
+ },
130
+ },
131
+ },
132
+ };
133
+ class InvalidWorktreePathError extends Error {
134
+ constructor(path) {
135
+ super(`invalid worktree path: ${path}`);
136
+ this.name = "InvalidWorktreePathError";
137
+ }
138
+ }
110
139
  /* ----------------------------- error mapping ----------------------------- */
111
140
  function mapError(reply, err) {
112
141
  if (err instanceof GitNotInstalledError) {
@@ -118,6 +147,12 @@ function mapError(reply, err) {
118
147
  if (err instanceof InvalidBranchNameError) {
119
148
  return reply.code(400).send({ error: "invalid_branch_name", message: err.message });
120
149
  }
150
+ if (err instanceof InvalidWorktreePathError) {
151
+ return reply.code(400).send({ error: "invalid_worktree_path", message: err.message });
152
+ }
153
+ if (err instanceof PathOutsideRootError) {
154
+ return reply.code(403).send({ error: "path_not_allowed", message: "path outside workspace" });
155
+ }
121
156
  if (err instanceof GitCommandError) {
122
157
  // Git "rejected" / "non-fast-forward" / commit hook failures /
123
158
  // missing upstream are user-actionable, not server bugs. 400
@@ -173,6 +208,36 @@ async function withProject(projectId, reply, fn) {
173
208
  return mapError(reply, err);
174
209
  }
175
210
  }
211
+ async function resolveGitCwd(project, worktreePath) {
212
+ if (worktreePath === undefined)
213
+ return project.path;
214
+ const listed = await getWorktrees(project.path);
215
+ const match = listed.worktrees.find((w) => w.path === worktreePath);
216
+ if (match === undefined)
217
+ throw new InvalidWorktreePathError(worktreePath);
218
+ return match.path;
219
+ }
220
+ async function withGitCwd(projectId, worktreePath, reply, fn) {
221
+ const project = await resolveProject(projectId, reply);
222
+ if (project === undefined)
223
+ return reply;
224
+ try {
225
+ const cwd = await resolveGitCwd(project, worktreePath);
226
+ return await fn(cwd);
227
+ }
228
+ catch (err) {
229
+ return mapError(reply, err);
230
+ }
231
+ }
232
+ const projectWorktreeQuerySchema = {
233
+ type: "object",
234
+ required: ["projectId"],
235
+ properties: {
236
+ projectId: { type: "string", minLength: 1 },
237
+ worktreePath: { type: "string", minLength: 1 },
238
+ },
239
+ };
240
+ const worktreeBodyProperty = { worktreePath: { type: "string", minLength: 1 } };
176
241
  /* ----------------------------- routes ----------------------------- */
177
242
  export const gitRoutes = async (fastify) => {
178
243
  fastify.post("/git/init", {
@@ -230,48 +295,65 @@ export const gitRoutes = async (fastify) => {
230
295
  "`{ isGitRepo: false, files: [] }` (NOT 500) so the panel can sit " +
231
296
  "quiet on plain folders.",
232
297
  tags: ["git"],
298
+ querystring: projectWorktreeQuerySchema,
299
+ response: {
300
+ 200: statusSchema,
301
+ 400: errorSchema,
302
+ 403: errorSchema,
303
+ 404: errorSchema,
304
+ 500: errorSchema,
305
+ },
306
+ },
307
+ }, async (req, reply) => withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getStatus(cwd)));
308
+ fastify.get("/git/worktrees", {
309
+ schema: {
310
+ description: "List registered git worktrees for the project's repository. Returned absolute paths " +
311
+ "can be passed back as `worktreePath` to git routes and are accepted only if still registered.",
312
+ tags: ["git"],
233
313
  querystring: {
234
314
  type: "object",
235
315
  required: ["projectId"],
236
316
  properties: { projectId: { type: "string", minLength: 1 } },
237
317
  },
238
- response: { 200: statusSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
318
+ response: {
319
+ 200: worktreesSchema,
320
+ 400: errorSchema,
321
+ 403: errorSchema,
322
+ 404: errorSchema,
323
+ 500: errorSchema,
324
+ },
239
325
  },
240
- }, async (req, reply) => {
241
- const project = await resolveProject(req.query.projectId, reply);
242
- if (project === undefined)
243
- return reply;
244
- try {
245
- return await getStatus(project.path);
246
- }
247
- catch (err) {
248
- return mapError(reply, err);
249
- }
250
- });
326
+ }, async (req, reply) => withProject(req.query.projectId, reply, async (p) => {
327
+ return getWorktrees(p.path);
328
+ }));
251
329
  fastify.get("/git/diff", {
252
330
  schema: {
253
331
  description: "Unstaged unified diff for the project (working tree vs index).",
254
332
  tags: ["git"],
255
- querystring: {
256
- type: "object",
257
- required: ["projectId"],
258
- properties: { projectId: { type: "string", minLength: 1 } },
333
+ querystring: projectWorktreeQuerySchema,
334
+ response: {
335
+ 200: diffSchema,
336
+ 400: errorSchema,
337
+ 403: errorSchema,
338
+ 404: errorSchema,
339
+ 500: errorSchema,
259
340
  },
260
- response: { 200: diffSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
261
341
  },
262
- }, async (req, reply) => withProject(req.query.projectId, reply, (p) => getDiff(p.path)));
342
+ }, async (req, reply) => withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getDiff(cwd)));
263
343
  fastify.get("/git/diff/staged", {
264
344
  schema: {
265
345
  description: "Staged unified diff (index vs HEAD).",
266
346
  tags: ["git"],
267
- querystring: {
268
- type: "object",
269
- required: ["projectId"],
270
- properties: { projectId: { type: "string", minLength: 1 } },
347
+ querystring: projectWorktreeQuerySchema,
348
+ response: {
349
+ 200: diffSchema,
350
+ 400: errorSchema,
351
+ 403: errorSchema,
352
+ 404: errorSchema,
353
+ 500: errorSchema,
271
354
  },
272
- response: { 200: diffSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
273
355
  },
274
- }, async (req, reply) => withProject(req.query.projectId, reply, (p) => getStagedDiff(p.path)));
356
+ }, async (req, reply) => withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getStagedDiff(cwd)));
275
357
  fastify.get("/git/diff/file", {
276
358
  schema: {
277
359
  description: "Unified diff for a single file. `?staged=1` for the index↔HEAD diff; " +
@@ -284,13 +366,20 @@ export const gitRoutes = async (fastify) => {
284
366
  projectId: { type: "string", minLength: 1 },
285
367
  path: { type: "string", minLength: 1 },
286
368
  staged: { type: "string", enum: ["0", "1", "true", "false"] },
369
+ worktreePath: { type: "string", minLength: 1 },
287
370
  },
288
371
  },
289
- response: { 200: diffSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
372
+ response: {
373
+ 200: diffSchema,
374
+ 400: errorSchema,
375
+ 403: errorSchema,
376
+ 404: errorSchema,
377
+ 500: errorSchema,
378
+ },
290
379
  },
291
380
  }, async (req, reply) => {
292
381
  const staged = req.query.staged === "1" || req.query.staged === "true";
293
- return withProject(req.query.projectId, reply, (p) => getFileDiff(p.path, req.query.path, staged));
382
+ return withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getFileDiff(cwd, req.query.path, staged));
294
383
  });
295
384
  fastify.get("/git/log", {
296
385
  schema: {
@@ -302,41 +391,52 @@ export const gitRoutes = async (fastify) => {
302
391
  properties: {
303
392
  projectId: { type: "string", minLength: 1 },
304
393
  limit: { type: "string", pattern: "^[0-9]+$" },
394
+ worktreePath: { type: "string", minLength: 1 },
305
395
  },
306
396
  },
307
- response: { 200: logSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
397
+ response: {
398
+ 200: logSchema,
399
+ 400: errorSchema,
400
+ 403: errorSchema,
401
+ 404: errorSchema,
402
+ 500: errorSchema,
403
+ },
308
404
  },
309
405
  }, async (req, reply) => {
310
406
  const limit = req.query.limit !== undefined
311
407
  ? Math.min(1000, Math.max(1, Number.parseInt(req.query.limit, 10)))
312
408
  : 30;
313
- return withProject(req.query.projectId, reply, (p) => getLog(p.path, limit));
409
+ return withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getLog(cwd, limit));
314
410
  });
315
411
  fastify.get("/git/branches", {
316
412
  schema: {
317
413
  description: "Local + remote branch list with `current` flag.",
318
414
  tags: ["git"],
319
- querystring: {
320
- type: "object",
321
- required: ["projectId"],
322
- properties: { projectId: { type: "string", minLength: 1 } },
415
+ querystring: projectWorktreeQuerySchema,
416
+ response: {
417
+ 200: branchesSchema,
418
+ 400: errorSchema,
419
+ 403: errorSchema,
420
+ 404: errorSchema,
421
+ 500: errorSchema,
323
422
  },
324
- response: { 200: branchesSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
325
423
  },
326
- }, async (req, reply) => withProject(req.query.projectId, reply, (p) => getBranches(p.path)));
424
+ }, async (req, reply) => withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getBranches(cwd)));
327
425
  fastify.get("/git/remotes", {
328
426
  schema: {
329
427
  description: "Configured git remotes with their fetch + push URLs. " +
330
428
  "Empty array for non-git projects or repos with no remotes.",
331
429
  tags: ["git"],
332
- querystring: {
333
- type: "object",
334
- required: ["projectId"],
335
- properties: { projectId: { type: "string", minLength: 1 } },
430
+ querystring: projectWorktreeQuerySchema,
431
+ response: {
432
+ 200: remotesSchema,
433
+ 400: errorSchema,
434
+ 403: errorSchema,
435
+ 404: errorSchema,
436
+ 500: errorSchema,
336
437
  },
337
- response: { 200: remotesSchema, 400: errorSchema, 404: errorSchema, 500: errorSchema },
338
438
  },
339
- }, async (req, reply) => withProject(req.query.projectId, reply, (p) => getRemotes(p.path)));
439
+ }, async (req, reply) => withGitCwd(req.query.projectId, req.query.worktreePath, reply, (cwd) => getRemotes(cwd)));
340
440
  fastify.post("/git/remote/add", {
341
441
  schema: {
342
442
  description: "Add a git remote (`git remote add <name> <url>`). Name is " +
@@ -352,6 +452,7 @@ export const gitRoutes = async (fastify) => {
352
452
  projectId: { type: "string", minLength: 1 },
353
453
  name: { type: "string", minLength: 1 },
354
454
  url: { type: "string", minLength: 1, maxLength: 1024 },
455
+ ...worktreeBodyProperty,
355
456
  },
356
457
  },
357
458
  response: {
@@ -361,18 +462,10 @@ export const gitRoutes = async (fastify) => {
361
462
  500: errorSchema,
362
463
  },
363
464
  },
364
- }, async (req, reply) => {
365
- const project = await resolveProject(req.body.projectId, reply);
366
- if (project === undefined)
367
- return reply;
368
- try {
369
- await addRemote(project.path, req.body.name, req.body.url);
370
- return { ok: true };
371
- }
372
- catch (err) {
373
- return mapError(reply, err);
374
- }
375
- });
465
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
466
+ await addRemote(cwd, req.body.name, req.body.url);
467
+ return { ok: true };
468
+ }));
376
469
  fastify.delete("/git/remote/:name", {
377
470
  schema: {
378
471
  description: "Remove a git remote (`git remote remove <name>`). 400 " +
@@ -383,11 +476,7 @@ export const gitRoutes = async (fastify) => {
383
476
  required: ["name"],
384
477
  properties: { name: { type: "string", minLength: 1 } },
385
478
  },
386
- querystring: {
387
- type: "object",
388
- required: ["projectId"],
389
- properties: { projectId: { type: "string", minLength: 1 } },
390
- },
479
+ querystring: projectWorktreeQuerySchema,
391
480
  response: {
392
481
  200: { type: "object", properties: { ok: { type: "boolean" } }, required: ["ok"] },
393
482
  400: errorSchema,
@@ -395,18 +484,10 @@ export const gitRoutes = async (fastify) => {
395
484
  500: errorSchema,
396
485
  },
397
486
  },
398
- }, async (req, reply) => {
399
- const project = await resolveProject(req.query.projectId, reply);
400
- if (project === undefined)
401
- return reply;
402
- try {
403
- await removeRemote(project.path, req.params.name);
404
- return { ok: true };
405
- }
406
- catch (err) {
407
- return mapError(reply, err);
408
- }
409
- });
487
+ }, async (req, reply) => withGitCwd(req.query.projectId, req.query.worktreePath, reply, async (cwd) => {
488
+ await removeRemote(cwd, req.params.name);
489
+ return { ok: true };
490
+ }));
410
491
  fastify.post("/git/checkout", {
411
492
  schema: {
412
493
  description: "Switch the working tree to `branch`. Refuses on a dirty tree (git's " +
@@ -421,6 +502,7 @@ export const gitRoutes = async (fastify) => {
421
502
  properties: {
422
503
  projectId: { type: "string", minLength: 1 },
423
504
  branch: { type: "string", minLength: 1 },
505
+ ...worktreeBodyProperty,
424
506
  },
425
507
  },
426
508
  response: {
@@ -430,18 +512,10 @@ export const gitRoutes = async (fastify) => {
430
512
  500: errorSchema,
431
513
  },
432
514
  },
433
- }, async (req, reply) => {
434
- const project = await resolveProject(req.body.projectId, reply);
435
- if (project === undefined)
436
- return reply;
437
- try {
438
- await checkoutBranch(project.path, req.body.branch);
439
- return { ok: true };
440
- }
441
- catch (err) {
442
- return mapError(reply, err);
443
- }
444
- });
515
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
516
+ await checkoutBranch(cwd, req.body.branch);
517
+ return { ok: true };
518
+ }));
445
519
  fastify.post("/git/branch/create", {
446
520
  schema: {
447
521
  description: "Create a local branch. `startPoint` (defaults to HEAD) accepts any ref " +
@@ -457,6 +531,7 @@ export const gitRoutes = async (fastify) => {
457
531
  name: { type: "string", minLength: 1 },
458
532
  startPoint: { type: "string", minLength: 1 },
459
533
  checkout: { type: "boolean" },
534
+ ...worktreeBodyProperty,
460
535
  },
461
536
  },
462
537
  response: {
@@ -466,23 +541,15 @@ export const gitRoutes = async (fastify) => {
466
541
  500: errorSchema,
467
542
  },
468
543
  },
469
- }, async (req, reply) => {
470
- const project = await resolveProject(req.body.projectId, reply);
471
- if (project === undefined)
472
- return reply;
473
- try {
474
- const opts = {};
475
- if (req.body.startPoint !== undefined)
476
- opts.startPoint = req.body.startPoint;
477
- if (req.body.checkout !== undefined)
478
- opts.checkout = req.body.checkout;
479
- await createBranch(project.path, req.body.name, opts);
480
- return { ok: true };
481
- }
482
- catch (err) {
483
- return mapError(reply, err);
484
- }
485
- });
544
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
545
+ const opts = {};
546
+ if (req.body.startPoint !== undefined)
547
+ opts.startPoint = req.body.startPoint;
548
+ if (req.body.checkout !== undefined)
549
+ opts.checkout = req.body.checkout;
550
+ await createBranch(cwd, req.body.name, opts);
551
+ return { ok: true };
552
+ }));
486
553
  fastify.delete("/git/branch/:name", {
487
554
  schema: {
488
555
  description: "Delete a local branch via `git branch -d <name>`. `?force=1` switches " +
@@ -500,6 +567,7 @@ export const gitRoutes = async (fastify) => {
500
567
  properties: {
501
568
  projectId: { type: "string", minLength: 1 },
502
569
  force: { type: "string", enum: ["0", "1", "true", "false"] },
570
+ worktreePath: { type: "string", minLength: 1 },
503
571
  },
504
572
  },
505
573
  response: {
@@ -510,17 +578,11 @@ export const gitRoutes = async (fastify) => {
510
578
  },
511
579
  },
512
580
  }, async (req, reply) => {
513
- const project = await resolveProject(req.query.projectId, reply);
514
- if (project === undefined)
515
- return reply;
516
- try {
517
- const force = req.query.force === "1" || req.query.force === "true";
518
- await deleteBranch(project.path, req.params.name, { force });
581
+ const force = req.query.force === "1" || req.query.force === "true";
582
+ return withGitCwd(req.query.projectId, req.query.worktreePath, reply, async (cwd) => {
583
+ await deleteBranch(cwd, req.params.name, { force });
519
584
  return { ok: true };
520
- }
521
- catch (err) {
522
- return mapError(reply, err);
523
- }
585
+ });
524
586
  });
525
587
  fastify.post("/git/stage", {
526
588
  schema: {
@@ -543,6 +605,7 @@ export const gitRoutes = async (fastify) => {
543
605
  // even a very wide repo land well under 1000 paths.
544
606
  maxItems: 1000,
545
607
  },
608
+ ...worktreeBodyProperty,
546
609
  },
547
610
  },
548
611
  response: {
@@ -552,18 +615,10 @@ export const gitRoutes = async (fastify) => {
552
615
  500: errorSchema,
553
616
  },
554
617
  },
555
- }, async (req, reply) => {
556
- const project = await resolveProject(req.body.projectId, reply);
557
- if (project === undefined)
558
- return reply;
559
- try {
560
- await stagePaths(project.path, req.body.paths);
561
- return { ok: true };
562
- }
563
- catch (err) {
564
- return mapError(reply, err);
565
- }
566
- });
618
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
619
+ await stagePaths(cwd, req.body.paths);
620
+ return { ok: true };
621
+ }));
567
622
  fastify.post("/git/unstage", {
568
623
  schema: {
569
624
  description: "Unstage one or more files (`git restore --staged -- <paths>`).",
@@ -585,6 +640,7 @@ export const gitRoutes = async (fastify) => {
585
640
  // even a very wide repo land well under 1000 paths.
586
641
  maxItems: 1000,
587
642
  },
643
+ ...worktreeBodyProperty,
588
644
  },
589
645
  },
590
646
  response: {
@@ -594,18 +650,10 @@ export const gitRoutes = async (fastify) => {
594
650
  500: errorSchema,
595
651
  },
596
652
  },
597
- }, async (req, reply) => {
598
- const project = await resolveProject(req.body.projectId, reply);
599
- if (project === undefined)
600
- return reply;
601
- try {
602
- await unstagePaths(project.path, req.body.paths);
603
- return { ok: true };
604
- }
605
- catch (err) {
606
- return mapError(reply, err);
607
- }
608
- });
653
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
654
+ await unstagePaths(cwd, req.body.paths);
655
+ return { ok: true };
656
+ }));
609
657
  fastify.post("/git/apply-hunks", {
610
658
  schema: {
611
659
  description: "Stage or unstage selected hunks of a single file. Builds a " +
@@ -630,6 +678,7 @@ export const gitRoutes = async (fastify) => {
630
678
  minItems: 1,
631
679
  maxItems: 1_000,
632
680
  },
681
+ ...worktreeBodyProperty,
633
682
  },
634
683
  },
635
684
  response: {
@@ -647,12 +696,9 @@ export const gitRoutes = async (fastify) => {
647
696
  500: errorSchema,
648
697
  },
649
698
  },
650
- }, async (req, reply) => {
651
- const project = await resolveProject(req.body.projectId, reply);
652
- if (project === undefined)
653
- return reply;
699
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
654
700
  try {
655
- const { totalHunks } = await applyHunks(project.path, req.body.path, req.body.hunkIndices, req.body.mode);
701
+ const { totalHunks } = await applyHunks(cwd, req.body.path, req.body.hunkIndices, req.body.mode);
656
702
  return { ok: true, totalHunks };
657
703
  }
658
704
  catch (err) {
@@ -663,9 +709,9 @@ export const gitRoutes = async (fastify) => {
663
709
  if (err instanceof HunkStagingError) {
664
710
  return { ok: false, error: err.code, totalHunks: 0 };
665
711
  }
666
- return mapError(reply, err);
712
+ throw err;
667
713
  }
668
- });
714
+ }));
669
715
  fastify.post("/git/revert", {
670
716
  schema: {
671
717
  description: "Discard local changes for the given files via `git restore " +
@@ -692,6 +738,7 @@ export const gitRoutes = async (fastify) => {
692
738
  // even a very wide repo land well under 1000 paths.
693
739
  maxItems: 1000,
694
740
  },
741
+ ...worktreeBodyProperty,
695
742
  },
696
743
  },
697
744
  response: {
@@ -701,18 +748,10 @@ export const gitRoutes = async (fastify) => {
701
748
  500: errorSchema,
702
749
  },
703
750
  },
704
- }, async (req, reply) => {
705
- const project = await resolveProject(req.body.projectId, reply);
706
- if (project === undefined)
707
- return reply;
708
- try {
709
- await revertPaths(project.path, req.body.paths);
710
- return { ok: true };
711
- }
712
- catch (err) {
713
- return mapError(reply, err);
714
- }
715
- });
751
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
752
+ await revertPaths(cwd, req.body.paths);
753
+ return { ok: true };
754
+ }));
716
755
  fastify.post("/git/commit", {
717
756
  schema: {
718
757
  description: "Commit the currently-staged changes. Pre-commit hooks fire as " +
@@ -726,6 +765,7 @@ export const gitRoutes = async (fastify) => {
726
765
  properties: {
727
766
  projectId: { type: "string", minLength: 1 },
728
767
  message: { type: "string", minLength: 1 },
768
+ ...worktreeBodyProperty,
729
769
  },
730
770
  },
731
771
  response: {
@@ -740,19 +780,11 @@ export const gitRoutes = async (fastify) => {
740
780
  },
741
781
  },
742
782
  }, async (req, reply) => {
743
- const project = await resolveProject(req.body.projectId, reply);
744
- if (project === undefined)
745
- return reply;
746
783
  const message = req.body.message.trim();
747
784
  if (message.length === 0) {
748
785
  return reply.code(400).send({ error: "empty_message" });
749
786
  }
750
- try {
751
- return await commit(project.path, message);
752
- }
753
- catch (err) {
754
- return mapError(reply, err);
755
- }
787
+ return withGitCwd(req.body.projectId, req.body.worktreePath, reply, (cwd) => commit(cwd, message));
756
788
  });
757
789
  fastify.post("/git/fetch", {
758
790
  schema: {
@@ -768,6 +800,7 @@ export const gitRoutes = async (fastify) => {
768
800
  projectId: { type: "string", minLength: 1 },
769
801
  remote: { type: "string", minLength: 1 },
770
802
  prune: { type: "boolean" },
803
+ ...worktreeBodyProperty,
771
804
  },
772
805
  },
773
806
  response: {
@@ -777,23 +810,15 @@ export const gitRoutes = async (fastify) => {
777
810
  500: errorSchema,
778
811
  },
779
812
  },
780
- }, async (req, reply) => {
781
- const project = await resolveProject(req.body.projectId, reply);
782
- if (project === undefined)
783
- return reply;
784
- try {
785
- const opts = {};
786
- if (req.body.remote !== undefined)
787
- opts.remote = req.body.remote;
788
- if (req.body.prune !== undefined)
789
- opts.prune = req.body.prune;
790
- const { stdout } = await fetch(project.path, opts);
791
- return { output: stdout };
792
- }
793
- catch (err) {
794
- return mapError(reply, err);
795
- }
796
- });
813
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
814
+ const opts = {};
815
+ if (req.body.remote !== undefined)
816
+ opts.remote = req.body.remote;
817
+ if (req.body.prune !== undefined)
818
+ opts.prune = req.body.prune;
819
+ const { stdout } = await fetch(cwd, opts);
820
+ return { output: stdout };
821
+ }));
797
822
  fastify.post("/git/pull", {
798
823
  schema: {
799
824
  description: "git pull — fetches AND merges (or rebases with `rebase: true`). " +
@@ -810,6 +835,7 @@ export const gitRoutes = async (fastify) => {
810
835
  remote: { type: "string", minLength: 1 },
811
836
  branch: { type: "string", minLength: 1 },
812
837
  rebase: { type: "boolean" },
838
+ ...worktreeBodyProperty,
813
839
  },
814
840
  },
815
841
  response: {
@@ -819,25 +845,17 @@ export const gitRoutes = async (fastify) => {
819
845
  500: errorSchema,
820
846
  },
821
847
  },
822
- }, async (req, reply) => {
823
- const project = await resolveProject(req.body.projectId, reply);
824
- if (project === undefined)
825
- return reply;
826
- try {
827
- const opts = {};
828
- if (req.body.remote !== undefined)
829
- opts.remote = req.body.remote;
830
- if (req.body.branch !== undefined)
831
- opts.branch = req.body.branch;
832
- if (req.body.rebase !== undefined)
833
- opts.rebase = req.body.rebase;
834
- const { stdout } = await pull(project.path, opts);
835
- return { output: stdout };
836
- }
837
- catch (err) {
838
- return mapError(reply, err);
839
- }
840
- });
848
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
849
+ const opts = {};
850
+ if (req.body.remote !== undefined)
851
+ opts.remote = req.body.remote;
852
+ if (req.body.branch !== undefined)
853
+ opts.branch = req.body.branch;
854
+ if (req.body.rebase !== undefined)
855
+ opts.rebase = req.body.rebase;
856
+ const { stdout } = await pull(cwd, opts);
857
+ return { output: stdout };
858
+ }));
841
859
  fastify.post("/git/push", {
842
860
  config: {
843
861
  rateLimit: {
@@ -862,6 +880,7 @@ export const gitRoutes = async (fastify) => {
862
880
  remote: { type: "string", minLength: 1 },
863
881
  branch: { type: "string", minLength: 1 },
864
882
  setUpstream: { type: "boolean" },
883
+ ...worktreeBodyProperty,
865
884
  },
866
885
  },
867
886
  response: {
@@ -875,24 +894,16 @@ export const gitRoutes = async (fastify) => {
875
894
  500: errorSchema,
876
895
  },
877
896
  },
878
- }, async (req, reply) => {
879
- const project = await resolveProject(req.body.projectId, reply);
880
- if (project === undefined)
881
- return reply;
882
- try {
883
- const opts = {};
884
- if (req.body.remote !== undefined)
885
- opts.remote = req.body.remote;
886
- if (req.body.branch !== undefined)
887
- opts.branch = req.body.branch;
888
- if (req.body.setUpstream !== undefined)
889
- opts.setUpstream = req.body.setUpstream;
890
- const { stdout } = await push(project.path, opts);
891
- return { output: stdout };
892
- }
893
- catch (err) {
894
- return mapError(reply, err);
895
- }
896
- });
897
+ }, async (req, reply) => withGitCwd(req.body.projectId, req.body.worktreePath, reply, async (cwd) => {
898
+ const opts = {};
899
+ if (req.body.remote !== undefined)
900
+ opts.remote = req.body.remote;
901
+ if (req.body.branch !== undefined)
902
+ opts.branch = req.body.branch;
903
+ if (req.body.setUpstream !== undefined)
904
+ opts.setUpstream = req.body.setUpstream;
905
+ const { stdout } = await push(cwd, opts);
906
+ return { output: stdout };
907
+ }));
897
908
  };
898
909
  //# sourceMappingURL=git.js.map