@saeed42/worktree-worker 1.0.0 → 1.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 (3) hide show
  1. package/README.md +28 -7
  2. package/dist/main.js +210 -70
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,15 +1,19 @@
1
- # @orgn/worktree-worker
1
+ # @saeed42/worktree-worker
2
2
 
3
3
  Git worktree management service for AI agent trials. Runs as an npm package in CodeSandbox or any Node.js environment.
4
4
 
5
+ ## Security
6
+
7
+ **GitHub tokens are NEVER persisted to disk.** All authenticated operations require the token to be passed in the request body. This is critical for sandbox environments where credentials should not be stored.
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
8
12
  # Install globally
9
- npm install -g @orgn/worktree-worker
13
+ npm install -g @saeed42/worktree-worker
10
14
 
11
15
  # Or add to your project
12
- npm install @orgn/worktree-worker
16
+ npm install @saeed42/worktree-worker
13
17
  ```
14
18
 
15
19
  ## Quick Start
@@ -32,7 +36,7 @@ Add to your `package.json`:
32
36
  ```json
33
37
  {
34
38
  "dependencies": {
35
- "@orgn/worktree-worker": "^1.0.0"
39
+ "@saeed42/worktree-worker": "^1.0.0"
36
40
  },
37
41
  "scripts": {
38
42
  "worktree-worker": "worktree-worker"
@@ -43,13 +47,13 @@ Add to your `package.json`:
43
47
  Or run directly:
44
48
 
45
49
  ```bash
46
- npx @orgn/worktree-worker
50
+ npx @saeed42/worktree-worker
47
51
  ```
48
52
 
49
53
  ### Run Programmatically
50
54
 
51
55
  ```typescript
52
- import { app } from '@orgn/worktree-worker';
56
+ import { app } from '@saeed42/worktree-worker';
53
57
 
54
58
  // The app is a Hono instance
55
59
  // You can extend it or use it as middleware
@@ -133,10 +137,13 @@ curl -X POST http://localhost:8787/v1/trials/abc123/worktree/reset \
133
137
  -H "Content-Type: application/json" \
134
138
  -d '{
135
139
  "baseBranch": "main",
136
- "trialBranch": "feature/my-feature"
140
+ "trialBranch": "feature/my-feature",
141
+ "githubToken": "ghp_xxx"
137
142
  }'
138
143
  ```
139
144
 
145
+ > **Note:** `githubToken` is required for private repositories. The token is used inline and **never stored on disk**.
146
+
140
147
  Response:
141
148
 
142
149
  ```json
@@ -165,6 +172,20 @@ curl -X POST http://localhost:8787/v1/trials/abc123/worktree/commit \
165
172
  }'
166
173
  ```
167
174
 
175
+ ### Push Changes
176
+
177
+ ```bash
178
+ curl -X POST http://localhost:8787/v1/trials/abc123/worktree/push \
179
+ -H "Authorization: Bearer your-token" \
180
+ -H "Content-Type: application/json" \
181
+ -d '{
182
+ "githubToken": "ghp_xxx",
183
+ "force": false
184
+ }'
185
+ ```
186
+
187
+ > **Note:** `githubToken` is **required** for push operations. The token is used inline and **never stored on disk**.
188
+
168
189
  ## Development
169
190
 
170
191
  ```bash
package/dist/main.js CHANGED
@@ -188,13 +188,36 @@ var GitService = class {
188
188
  return result.stdout.trim();
189
189
  }
190
190
  /**
191
- * Fetch from remote
191
+ * Build authenticated URL (token is NEVER persisted)
192
192
  */
193
- async fetch(remote = "origin", cwd) {
193
+ buildAuthUrl(repoUrl, githubToken) {
194
+ if (!githubToken) return repoUrl;
195
+ const urlWithoutProtocol = repoUrl.replace(/^https?:\/\//, "");
196
+ return `https://x-access-token:${githubToken}@${urlWithoutProtocol}`;
197
+ }
198
+ /**
199
+ * Get remote URL and inject auth if token provided
200
+ */
201
+ async getAuthenticatedRemoteUrl(remote, cwd, githubToken) {
202
+ const url = await this.getRemoteUrl(remote, cwd);
203
+ if (!url) return null;
204
+ return githubToken ? this.buildAuthUrl(url, githubToken) : url;
205
+ }
206
+ /**
207
+ * Fetch from remote (token used inline, never persisted)
208
+ */
209
+ async fetch(remote = "origin", cwd, auth) {
210
+ if (auth?.githubToken) {
211
+ const authUrl = await this.getAuthenticatedRemoteUrl(remote, cwd || env.BASE_WORKSPACE_DIR, auth.githubToken);
212
+ if (authUrl) {
213
+ await this.execOrThrow(["fetch", authUrl], cwd);
214
+ return;
215
+ }
216
+ }
194
217
  await this.execOrThrow(["fetch", remote], cwd);
195
218
  }
196
219
  /**
197
- * Clone a repository
220
+ * Clone a repository (token used inline, never persisted)
198
221
  */
199
222
  async cloneRepo(repoUrl, targetDir, options) {
200
223
  const args = ["clone"];
@@ -206,7 +229,8 @@ var GitService = class {
206
229
  if (options?.branch) {
207
230
  args.push("--branch", options.branch);
208
231
  }
209
- args.push(repoUrl, targetDir);
232
+ const cloneUrl = options?.githubToken ? this.buildAuthUrl(repoUrl, options.githubToken) : repoUrl;
233
+ args.push(cloneUrl, targetDir);
210
234
  const parentDir = targetDir.split("/").slice(0, -1).join("/") || "/";
211
235
  await this.execOrThrow(args, parentDir);
212
236
  }
@@ -290,9 +314,14 @@ var GitService = class {
290
314
  return result.stdout.trim();
291
315
  }
292
316
  /**
293
- * Check if branch exists
317
+ * Check if branch exists (local or remote)
318
+ * For remote branches, pass "origin/branch-name"
294
319
  */
295
320
  async branchExists(branch, cwd) {
321
+ if (branch.includes("/")) {
322
+ const result2 = await this.exec(["show-ref", "--verify", "--quiet", `refs/remotes/${branch}`], cwd);
323
+ return result2.code === 0;
324
+ }
296
325
  const result = await this.exec(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], cwd);
297
326
  return result.code === 0;
298
327
  }
@@ -329,9 +358,18 @@ var GitService = class {
329
358
  return this.getHeadSha(cwd);
330
359
  }
331
360
  /**
332
- * Push
361
+ * Push (token used inline, never persisted)
333
362
  */
334
- async push(remote, branch, force = false, cwd) {
363
+ async push(remote, branch, force = false, cwd, auth) {
364
+ if (auth?.githubToken) {
365
+ const authUrl = await this.getAuthenticatedRemoteUrl(remote, cwd || env.BASE_WORKSPACE_DIR, auth.githubToken);
366
+ if (authUrl) {
367
+ const args2 = ["push", authUrl, branch];
368
+ if (force) args2.push("--force");
369
+ await this.execOrThrow(args2, cwd);
370
+ return;
371
+ }
372
+ }
335
373
  const args = ["push", remote, branch];
336
374
  if (force) args.push("--force");
337
375
  await this.execOrThrow(args, cwd);
@@ -417,7 +455,7 @@ import { Hono as Hono2 } from "hono";
417
455
  import { z } from "zod";
418
456
 
419
457
  // src/services/repo.service.ts
420
- import { mkdir as mkdir2, rm, stat as stat2, writeFile } from "fs/promises";
458
+ import { mkdir as mkdir2, readdir, rm, stat as stat2 } from "fs/promises";
421
459
  var RepoService = class {
422
460
  /**
423
461
  * Check if the repository is initialized
@@ -467,71 +505,117 @@ var RepoService = class {
467
505
  }
468
506
  /**
469
507
  * Initialize/clone the repository
508
+ * SECURITY: githubToken is used inline and NEVER persisted
509
+ *
510
+ * Smart init logic:
511
+ * - No repo exists → Clone it
512
+ * - Repo exists, same URL → Fetch latest (idempotent)
513
+ * - Repo exists, different URL, force=false → Error
514
+ * - Repo exists, different URL, force=true → Clean all worktrees → Delete repo → Re-clone
470
515
  */
471
516
  async initRepo(options) {
472
517
  const log = logger.child({ service: "repo", action: "init" });
473
518
  const repoRoot = env.BASE_WORKSPACE_DIR;
519
+ const auth = options.githubToken ? { githubToken: options.githubToken } : void 0;
520
+ const normalizeUrl = (url) => {
521
+ return url.replace(/^https:\/\/[^@]+@/, "https://").replace(/\.git$/, "").replace(/\/$/, "").toLowerCase();
522
+ };
523
+ const requestedUrl = normalizeUrl(options.repoUrl);
474
524
  log.info("Initializing repository", {
475
525
  repoUrl: options.repoUrl.replace(/ghp_[a-zA-Z0-9]+/, "ghp_***"),
476
526
  branch: options.branch,
477
- force: options.force
527
+ force: options.force,
528
+ hasToken: !!options.githubToken
478
529
  });
479
530
  const status = await this.getStatus();
480
- if (status.initialized && !options.force) {
481
- log.info("Repository already initialized");
482
- return {
483
- path: repoRoot,
484
- branch: status.branch || env.DEFAULT_BRANCH,
485
- headSha: status.headSha || "",
486
- remote: status.remote
487
- };
488
- }
489
- if (options.force && status.initialized) {
490
- log.info("Force flag set, removing existing repo");
531
+ if (status.initialized) {
532
+ const currentUrl = normalizeUrl(status.remote || "");
533
+ const isSameRepo = currentUrl === requestedUrl;
534
+ if (isSameRepo) {
535
+ log.info("Repository already initialized with same URL, fetching latest");
536
+ await gitService.fetch("origin", repoRoot, auth);
537
+ const targetBranch = options.branch || env.DEFAULT_BRANCH;
538
+ const currentBranch = status.branch || "";
539
+ if (targetBranch !== currentBranch) {
540
+ log.info("Switching to requested branch", { from: currentBranch, to: targetBranch });
541
+ const checkoutResult = await gitService.exec(["checkout", targetBranch], repoRoot);
542
+ if (checkoutResult.code !== 0) {
543
+ await gitService.exec(["checkout", "-b", targetBranch, `origin/${targetBranch}`], repoRoot);
544
+ }
545
+ }
546
+ const headSha2 = await gitService.getHeadSha(repoRoot);
547
+ const branch2 = await gitService.getCurrentBranch(repoRoot);
548
+ return { path: repoRoot, branch: branch2, headSha: headSha2, remote: status.remote };
549
+ }
550
+ if (!options.force) {
551
+ throw new Error(
552
+ `Repository already initialized with different URL. Current: ${currentUrl}, Requested: ${requestedUrl}. Use force=true to re-initialize (this will delete all worktrees).`
553
+ );
554
+ }
555
+ log.warn("Force re-init: cleaning all worktrees and repo");
556
+ await this.cleanAllWorktrees();
491
557
  await rm(repoRoot, { recursive: true, force: true });
492
558
  }
493
559
  const parentDir = repoRoot.split("/").slice(0, -1).join("/");
494
560
  await mkdir2(parentDir, { recursive: true });
495
561
  await mkdir2(env.TRIALS_WORKSPACE_DIR, { recursive: true });
496
- let cloneUrl = options.repoUrl;
497
- if (options.githubToken) {
498
- const urlWithoutProtocol = options.repoUrl.replace(/^https?:\/\//, "");
499
- cloneUrl = `https://x-access-token:${options.githubToken}@${urlWithoutProtocol}`;
500
- }
501
562
  const branch = options.branch || env.DEFAULT_BRANCH;
502
563
  log.info("Cloning repository", { branch });
503
- await gitService.cloneRepo(cloneUrl, repoRoot, {
564
+ await gitService.cloneRepo(options.repoUrl, repoRoot, {
504
565
  branch,
505
- blobless: true
566
+ blobless: true,
567
+ githubToken: options.githubToken
506
568
  });
507
569
  await gitService.exec(["config", "--local", "user.name", "origin-agent[bot]"], repoRoot);
508
570
  await gitService.exec(["config", "--local", "user.email", "origin-agent[bot]@users.noreply.github.com"], repoRoot);
509
571
  await gitService.exec(["config", "--local", "safe.directory", repoRoot], repoRoot);
510
572
  const cleanUrl = options.repoUrl.replace(/^https:\/\/[^@]+@/, "https://");
511
573
  await gitService.exec(["remote", "set-url", "origin", cleanUrl], repoRoot);
512
- if (options.githubToken) {
513
- await this.updateCredentials(options.githubToken);
514
- }
574
+ log.info("Fetching all remote refs");
575
+ await gitService.fetch("origin", repoRoot, auth);
576
+ await gitService.exec(["branch", `--set-upstream-to=origin/${branch}`, branch], repoRoot).catch(() => {
577
+ });
515
578
  const headSha = await gitService.getHeadSha(repoRoot);
516
579
  const remote = await gitService.getRemoteUrl("origin", repoRoot);
517
580
  log.info("Repository initialized", { branch, headSha });
518
581
  return { path: repoRoot, branch, headSha, remote };
519
582
  }
583
+ /**
584
+ * Clean all worktrees (used before force re-init)
585
+ */
586
+ async cleanAllWorktrees() {
587
+ const log = logger.child({ service: "repo", action: "cleanAllWorktrees" });
588
+ try {
589
+ const entries = await readdir(env.TRIALS_WORKSPACE_DIR, { withFileTypes: true });
590
+ for (const entry of entries) {
591
+ if (entry.isDirectory()) {
592
+ const worktreePath = `${env.TRIALS_WORKSPACE_DIR}/${entry.name}`;
593
+ log.info("Removing worktree", { path: worktreePath });
594
+ await rm(worktreePath, { recursive: true, force: true });
595
+ }
596
+ }
597
+ await gitService.pruneWorktrees(env.BASE_WORKSPACE_DIR).catch(() => {
598
+ });
599
+ log.info("All worktrees cleaned");
600
+ } catch (err) {
601
+ log.debug("No worktrees to clean", { error: err.message });
602
+ }
603
+ }
520
604
  /**
521
605
  * Update/pull the repository
606
+ * SECURITY: githubToken is used inline and NEVER persisted
522
607
  */
523
608
  async updateRepo(options) {
524
609
  const log = logger.child({ service: "repo", action: "update" });
525
610
  const repoRoot = env.BASE_WORKSPACE_DIR;
611
+ const auth = options?.githubToken ? { githubToken: options.githubToken } : void 0;
526
612
  const status = await this.getStatus();
527
613
  if (!status.initialized) {
528
614
  throw new Error("Repository not initialized");
529
615
  }
530
- if (options?.githubToken) {
531
- await this.updateCredentials(options.githubToken);
532
- }
616
+ log.info("Updating repository", { hasToken: !!options?.githubToken });
533
617
  log.info("Fetching from remote");
534
- await gitService.fetch("origin", repoRoot);
618
+ await gitService.fetch("origin", repoRoot, auth);
535
619
  if (options?.branch && options.branch !== status.branch) {
536
620
  log.info("Checking out branch", { branch: options.branch });
537
621
  await gitService.execOrThrow(["checkout", options.branch], repoRoot);
@@ -545,19 +629,6 @@ var RepoService = class {
545
629
  const remote = await gitService.getRemoteUrl("origin", repoRoot);
546
630
  return { path: repoRoot, branch, headSha, remote };
547
631
  }
548
- /**
549
- * Update git credentials
550
- */
551
- async updateCredentials(githubToken) {
552
- const log = logger.child({ service: "repo", action: "updateCredentials" });
553
- const repoRoot = env.BASE_WORKSPACE_DIR;
554
- await gitService.exec(["config", "--global", "credential.helper", "store --file=/tmp/.git-credentials"], repoRoot);
555
- await gitService.exec(["config", "--global", "core.askPass", ""], repoRoot);
556
- const credentialsContent = `https://x-access-token:${githubToken}@github.com
557
- `;
558
- await writeFile("/tmp/.git-credentials", credentialsContent, { mode: 384 });
559
- log.info("Credentials updated");
560
- }
561
632
  /**
562
633
  * List branches
563
634
  */
@@ -651,7 +722,7 @@ import { Hono as Hono3 } from "hono";
651
722
  import { z as z2 } from "zod";
652
723
 
653
724
  // src/services/worktree.service.ts
654
- import { mkdir as mkdir3, rm as rm2, stat as stat3, readdir } from "fs/promises";
725
+ import { mkdir as mkdir3, rm as rm2, stat as stat3, readdir as readdir2 } from "fs/promises";
655
726
  var WorktreeService = class {
656
727
  /**
657
728
  * Get the isolated workspace directory for a specific trial.
@@ -674,6 +745,7 @@ var WorktreeService = class {
674
745
  }
675
746
  /**
676
747
  * Reset or create a worktree for a trial
748
+ * SECURITY: githubToken is used inline and NEVER persisted
677
749
  */
678
750
  async resetWorktree(trialId, options) {
679
751
  const log = logger.child({ trialId, service: "worktree" });
@@ -681,20 +753,37 @@ var WorktreeService = class {
681
753
  const worktreePath = this.getWorktreePath(trialId, branchName);
682
754
  const baseBranch = options.baseBranch || env.DEFAULT_BRANCH;
683
755
  const baseRepoDir = env.BASE_WORKSPACE_DIR;
756
+ const auth = options.githubToken ? { githubToken: options.githubToken } : void 0;
684
757
  log.info("Starting worktree reset (v3 architecture)", {
685
758
  worktreePath,
686
759
  branchName,
687
760
  baseBranch,
688
- baseRepoDir
761
+ baseRepoDir,
762
+ hasToken: !!options.githubToken,
763
+ hasRepoUrl: !!options.repoUrl
689
764
  });
690
765
  await mkdir3(env.TRIALS_WORKSPACE_DIR, { recursive: true });
766
+ const repoStatus = await repoService.getStatus();
767
+ if (!repoStatus.initialized) {
768
+ if (!options.repoUrl) {
769
+ throw new Error(
770
+ "Main repository not initialized. Provide repoUrl to auto-initialize, or call /v1/repo/init first."
771
+ );
772
+ }
773
+ log.info("Main repo not initialized, auto-initializing", { repoUrl: options.repoUrl.replace(/ghp_[a-zA-Z0-9]+/, "ghp_***") });
774
+ await repoService.initRepo({
775
+ repoUrl: options.repoUrl,
776
+ branch: baseBranch,
777
+ githubToken: options.githubToken
778
+ });
779
+ }
691
780
  try {
692
781
  const stats = await stat3(worktreePath);
693
782
  if (stats.isDirectory()) {
694
783
  const gitFile = await stat3(`${worktreePath}/.git`).catch(() => null);
695
784
  if (gitFile) {
696
785
  log.info("Worktree already exists, fetching latest");
697
- await gitService.fetch("origin", worktreePath).catch(() => {
786
+ await gitService.fetch("origin", worktreePath, auth).catch(() => {
698
787
  });
699
788
  const headSha2 = await gitService.getHeadSha(worktreePath);
700
789
  return { worktreePath, branch: branchName, headSha: headSha2 };
@@ -703,7 +792,7 @@ var WorktreeService = class {
703
792
  } catch {
704
793
  }
705
794
  log.info("Fetching from remote");
706
- await gitService.fetch("origin", baseRepoDir);
795
+ await gitService.fetch("origin", baseRepoDir, auth);
707
796
  try {
708
797
  await gitService.removeWorktree(worktreePath, true, baseRepoDir);
709
798
  } catch {
@@ -727,7 +816,33 @@ var WorktreeService = class {
727
816
  await gitService.exec(["fetch", "origin", `${branchName}:${branchName}`], baseRepoDir);
728
817
  await gitService.execOrThrow(["worktree", "add", worktreePath, branchName], baseRepoDir);
729
818
  } else {
730
- const baseRef = `origin/${baseBranch}`;
819
+ let baseRef = `origin/${baseBranch}`;
820
+ const baseBranchExistsOnRemote = await gitService.branchExists(baseRef, baseRepoDir);
821
+ if (!baseBranchExistsOnRemote) {
822
+ log.warn("Base branch not found, detecting default branch", { tried: baseRef });
823
+ const fallbackBranches = ["origin/main", "origin/master", "origin/dev", "origin/develop"];
824
+ let found = false;
825
+ for (const fallback of fallbackBranches) {
826
+ const exists = await gitService.branchExists(fallback, baseRepoDir);
827
+ if (exists) {
828
+ baseRef = fallback;
829
+ found = true;
830
+ log.info("Found fallback base branch", { baseRef });
831
+ break;
832
+ }
833
+ }
834
+ if (!found) {
835
+ const currentBranch = await gitService.getCurrentBranch(baseRepoDir);
836
+ if (currentBranch && currentBranch !== "HEAD") {
837
+ baseRef = currentBranch;
838
+ log.info("Using current branch as base", { baseRef });
839
+ } else {
840
+ throw new Error(
841
+ `Cannot find base branch. Tried: origin/${baseBranch}, ${fallbackBranches.join(", ")}. Ensure the repository has been fetched properly.`
842
+ );
843
+ }
844
+ }
845
+ }
731
846
  log.info("Creating worktree with new branch from base", { baseRef, branchName });
732
847
  await gitService.addWorktreeWithNewBranch(worktreePath, branchName, baseRef, baseRepoDir);
733
848
  }
@@ -848,12 +963,14 @@ var WorktreeService = class {
848
963
  }
849
964
  /**
850
965
  * Push branch
966
+ * SECURITY: githubToken is used inline and NEVER persisted
851
967
  */
852
- async pushBranch(trialId, remote = "origin", force = false, trialBranch) {
968
+ async pushBranch(trialId, remote = "origin", force = false, trialBranch, githubToken) {
853
969
  const branchName = trialBranch || this.getBranchName(trialId);
854
970
  const worktreePath = this.getWorktreePath(trialId, branchName);
971
+ const auth = githubToken ? { githubToken } : void 0;
855
972
  const branch = await gitService.getCurrentBranch(worktreePath);
856
- await gitService.push(remote, branch, force, worktreePath);
973
+ await gitService.push(remote, branch, force, worktreePath, auth);
857
974
  return { branch, pushed: true };
858
975
  }
859
976
  /**
@@ -865,7 +982,7 @@ var WorktreeService = class {
865
982
  const errors = [];
866
983
  const cutoffTime = Date.now() - env.CLEANUP_AFTER_HOURS * 60 * 60 * 1e3;
867
984
  try {
868
- const entries = await readdir(env.TRIALS_WORKSPACE_DIR, { withFileTypes: true });
985
+ const entries = await readdir2(env.TRIALS_WORKSPACE_DIR, { withFileTypes: true });
869
986
  for (const entry of entries) {
870
987
  if (!entry.isDirectory()) continue;
871
988
  const worktreePath = `${env.TRIALS_WORKSPACE_DIR}/${entry.name}`;
@@ -897,18 +1014,25 @@ var worktree = new Hono3();
897
1014
  var resetWorktreeSchema = z2.object({
898
1015
  baseBranch: z2.string().default("main"),
899
1016
  trialBranch: z2.string().optional(),
900
- force: z2.boolean().default(false)
1017
+ force: z2.boolean().default(false),
1018
+ githubToken: z2.string().optional(),
1019
+ // Required for private repos
1020
+ repoUrl: z2.string().url().optional()
1021
+ // Required if repo not yet initialized
901
1022
  });
902
1023
  var commitSchema = z2.object({
903
1024
  message: z2.string().min(1),
904
1025
  author: z2.object({
905
1026
  name: z2.string(),
906
1027
  email: z2.string().email()
907
- }).optional()
1028
+ }).optional(),
1029
+ trialBranch: z2.string().optional()
908
1030
  });
909
1031
  var pushSchema = z2.object({
910
1032
  remote: z2.string().default("origin"),
911
- force: z2.boolean().default(false)
1033
+ force: z2.boolean().default(false),
1034
+ githubToken: z2.string().min(1, "GitHub token is required for push operations"),
1035
+ trialBranch: z2.string().optional()
912
1036
  });
913
1037
  worktree.post("/trials/:trialId/worktree/reset", async (c) => {
914
1038
  const trialId = c.req.param("trialId");
@@ -926,12 +1050,15 @@ worktree.post("/trials/:trialId/worktree/reset", async (c) => {
926
1050
  log.info("Creating/resetting worktree", {
927
1051
  baseBranch: input.baseBranch,
928
1052
  trialBranch: input.trialBranch,
929
- force: input.force
1053
+ force: input.force,
1054
+ hasToken: !!input.githubToken
930
1055
  });
931
1056
  const result = await worktreeService.resetWorktree(trialId, {
932
1057
  baseBranch: input.baseBranch,
933
1058
  trialBranch: input.trialBranch,
934
- force: input.force
1059
+ force: input.force,
1060
+ githubToken: input.githubToken,
1061
+ repoUrl: input.repoUrl
935
1062
  });
936
1063
  return c.json({ success: true, data: result });
937
1064
  } catch (err) {
@@ -942,10 +1069,11 @@ worktree.post("/trials/:trialId/worktree/reset", async (c) => {
942
1069
  });
943
1070
  worktree.delete("/trials/:trialId/worktree", async (c) => {
944
1071
  const trialId = c.req.param("trialId");
945
- const log = logger.child({ route: "worktree/delete", trialId });
1072
+ const trialBranch = c.req.query("trialBranch");
1073
+ const log = logger.child({ route: "worktree/delete", trialId, trialBranch });
946
1074
  try {
947
1075
  log.info("Deleting worktree");
948
- await worktreeService.deleteWorktree(trialId);
1076
+ await worktreeService.deleteWorktree(trialId, trialBranch);
949
1077
  return c.json({ success: true, data: { deleted: true } });
950
1078
  } catch (err) {
951
1079
  const error = err;
@@ -955,9 +1083,10 @@ worktree.delete("/trials/:trialId/worktree", async (c) => {
955
1083
  });
956
1084
  worktree.get("/trials/:trialId/worktree/status", async (c) => {
957
1085
  const trialId = c.req.param("trialId");
958
- const log = logger.child({ route: "worktree/status", trialId });
1086
+ const trialBranch = c.req.query("trialBranch");
1087
+ const log = logger.child({ route: "worktree/status", trialId, trialBranch });
959
1088
  try {
960
- const status = await worktreeService.getWorktreeStatus(trialId);
1089
+ const status = await worktreeService.getWorktreeStatus(trialId, trialBranch);
961
1090
  return c.json({ success: true, data: status });
962
1091
  } catch (err) {
963
1092
  const error = err;
@@ -967,9 +1096,10 @@ worktree.get("/trials/:trialId/worktree/status", async (c) => {
967
1096
  });
968
1097
  worktree.get("/trials/:trialId/worktree/diff", async (c) => {
969
1098
  const trialId = c.req.param("trialId");
970
- const log = logger.child({ route: "worktree/diff", trialId });
1099
+ const trialBranch = c.req.query("trialBranch");
1100
+ const log = logger.child({ route: "worktree/diff", trialId, trialBranch });
971
1101
  try {
972
- const diff = await worktreeService.getWorktreeDiff(trialId);
1102
+ const diff = await worktreeService.getWorktreeDiff(trialId, trialBranch);
973
1103
  return c.json({ success: true, data: diff });
974
1104
  } catch (err) {
975
1105
  const error = err;
@@ -989,11 +1119,15 @@ worktree.post("/trials/:trialId/worktree/commit", async (c) => {
989
1119
  400
990
1120
  );
991
1121
  }
992
- log.info("Committing changes", { message: parsed.data.message.slice(0, 50) });
1122
+ log.info("Committing changes", {
1123
+ message: parsed.data.message.slice(0, 50),
1124
+ trialBranch: parsed.data.trialBranch
1125
+ });
993
1126
  const result = await worktreeService.commitChanges(
994
1127
  trialId,
995
1128
  parsed.data.message,
996
- parsed.data.author
1129
+ parsed.data.author,
1130
+ parsed.data.trialBranch
997
1131
  );
998
1132
  return c.json({ success: true, data: result });
999
1133
  } catch (err) {
@@ -1014,11 +1148,17 @@ worktree.post("/trials/:trialId/worktree/push", async (c) => {
1014
1148
  400
1015
1149
  );
1016
1150
  }
1017
- log.info("Pushing to remote", { remote: parsed.data.remote, force: parsed.data.force });
1151
+ log.info("Pushing to remote", {
1152
+ remote: parsed.data.remote,
1153
+ force: parsed.data.force,
1154
+ trialBranch: parsed.data.trialBranch
1155
+ });
1018
1156
  const result = await worktreeService.pushBranch(
1019
1157
  trialId,
1020
1158
  parsed.data.remote,
1021
- parsed.data.force
1159
+ parsed.data.force,
1160
+ parsed.data.trialBranch,
1161
+ parsed.data.githubToken
1022
1162
  );
1023
1163
  return c.json({ success: true, data: result });
1024
1164
  } catch (err) {
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@saeed42/worktree-worker",
3
- "version": "1.0.0",
3
+ "version": "1.3.0",
4
4
  "description": "Git worktree management service for AI agent trials",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
7
7
  "bin": {
8
- "opencode-ai": "./dist/main.js"
8
+ "worktree-worker": "./dist/main.js"
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsup",