git-workspace-service 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -312,9 +312,11 @@ interface Workspace {
312
312
  */
313
313
  branch: BranchInfo;
314
314
  /**
315
- * Credential for this workspace
315
+ * Credential for this workspace.
316
+ * Optional for public repositories (read-only operations).
317
+ * Required for write operations (push, PR creation).
316
318
  */
317
- credential: GitCredential;
319
+ credential?: GitCredential;
318
320
  /**
319
321
  * When the workspace was provisioned
320
322
  */
@@ -1233,6 +1235,10 @@ declare class WorkspaceService {
1233
1235
  * Clean up all workspaces for an execution
1234
1236
  */
1235
1237
  cleanupForExecution(executionId: string): Promise<void>;
1238
+ /**
1239
+ * Try to clone a public repository without authentication
1240
+ */
1241
+ private tryUnauthenticatedClone;
1236
1242
  private cloneRepo;
1237
1243
  private createBranch;
1238
1244
  private addWorktreeFromParent;
package/dist/index.d.ts CHANGED
@@ -312,9 +312,11 @@ interface Workspace {
312
312
  */
313
313
  branch: BranchInfo;
314
314
  /**
315
- * Credential for this workspace
315
+ * Credential for this workspace.
316
+ * Optional for public repositories (read-only operations).
317
+ * Required for write operations (push, PR creation).
316
318
  */
317
- credential: GitCredential;
319
+ credential?: GitCredential;
318
320
  /**
319
321
  * When the workspace was provisioned
320
322
  */
@@ -1233,6 +1235,10 @@ declare class WorkspaceService {
1233
1235
  * Clean up all workspaces for an execution
1234
1236
  */
1235
1237
  cleanupForExecution(executionId: string): Promise<void>;
1238
+ /**
1239
+ * Try to clone a public repository without authentication
1240
+ */
1241
+ private tryUnauthenticatedClone;
1236
1242
  private cloneRepo;
1237
1243
  private createBranch;
1238
1244
  private addWorktreeFromParent;
package/dist/index.js CHANGED
@@ -299,24 +299,28 @@ var WorkspaceService = class {
299
299
  credential = parent.credential;
300
300
  } else {
301
301
  await fs3.mkdir(workspacePath, { recursive: true });
302
- credential = await this.credentialService.getCredentials({
303
- repo: config.repo,
304
- access: "write",
305
- context: {
302
+ if (config.userCredentials) {
303
+ credential = await this.credentialService.getCredentials({
304
+ repo: config.repo,
305
+ access: "write",
306
+ context: {
307
+ executionId: config.execution.id,
308
+ taskId: config.task.id,
309
+ userId: config.user?.id,
310
+ reason: `Workspace for ${config.task.role} in ${config.execution.patternName}`
311
+ },
312
+ userProvided: config.userCredentials
313
+ });
314
+ }
315
+ if (credential) {
316
+ await this.emitEvent({
317
+ type: "credential:granted",
318
+ workspaceId,
319
+ credentialId: credential.id,
306
320
  executionId: config.execution.id,
307
- taskId: config.task.id,
308
- userId: config.user?.id,
309
- reason: `Workspace for ${config.task.role} in ${config.execution.patternName}`
310
- },
311
- userProvided: config.userCredentials
312
- });
313
- await this.emitEvent({
314
- type: "credential:granted",
315
- workspaceId,
316
- credentialId: credential.id,
317
- executionId: config.execution.id,
318
- timestamp: /* @__PURE__ */ new Date()
319
- });
321
+ timestamp: /* @__PURE__ */ new Date()
322
+ });
323
+ }
320
324
  }
321
325
  const branchInfo = createBranchInfo(
322
326
  {
@@ -333,6 +337,7 @@ var WorkspaceService = class {
333
337
  repo: config.repo,
334
338
  branch: branchInfo,
335
339
  credential,
340
+ // Will be set before any write operations
336
341
  provisionedAt: /* @__PURE__ */ new Date(),
337
342
  status: "provisioning",
338
343
  strategy,
@@ -348,7 +353,38 @@ var WorkspaceService = class {
348
353
  try {
349
354
  if (strategy === "clone") {
350
355
  this.updateProgress(workspace, "cloning", "Cloning repository");
351
- await this.cloneRepo(workspace, credential.token);
356
+ if (!credential) {
357
+ const cloneResult = await this.tryUnauthenticatedClone(workspace);
358
+ if (!cloneResult.success) {
359
+ this.log(
360
+ "info",
361
+ { workspaceId, error: cloneResult.error },
362
+ "Unauthenticated clone failed, requesting credentials"
363
+ );
364
+ credential = await this.credentialService.getCredentials({
365
+ repo: config.repo,
366
+ access: "write",
367
+ context: {
368
+ executionId: config.execution.id,
369
+ taskId: config.task.id,
370
+ userId: config.user?.id,
371
+ reason: `Workspace for ${config.task.role} in ${config.execution.patternName}`
372
+ }
373
+ });
374
+ workspace.credential = credential;
375
+ this.workspaces.set(workspaceId, workspace);
376
+ await this.emitEvent({
377
+ type: "credential:granted",
378
+ workspaceId,
379
+ credentialId: credential.id,
380
+ executionId: config.execution.id,
381
+ timestamp: /* @__PURE__ */ new Date()
382
+ });
383
+ await this.cloneRepo(workspace, credential.token);
384
+ }
385
+ } else {
386
+ await this.cloneRepo(workspace, credential.token);
387
+ }
352
388
  this.updateProgress(workspace, "creating_branch", "Creating branch");
353
389
  await this.createBranch(workspace);
354
390
  } else {
@@ -569,7 +605,7 @@ var WorkspaceService = class {
569
605
  });
570
606
  }
571
607
  }
572
- if (workspace.strategy === "clone") {
608
+ if (workspace.strategy === "clone" && workspace.credential) {
573
609
  await this.credentialService.revokeCredential(workspace.credential.id);
574
610
  await this.emitEvent({
575
611
  type: "credential:revoked",
@@ -605,6 +641,36 @@ var WorkspaceService = class {
605
641
  // ─────────────────────────────────────────────────────────────
606
642
  // Private Methods
607
643
  // ─────────────────────────────────────────────────────────────
644
+ /**
645
+ * Try to clone a public repository without authentication
646
+ */
647
+ async tryUnauthenticatedClone(workspace) {
648
+ let cloneUrl = workspace.repo;
649
+ if (cloneUrl.startsWith("git@github.com:")) {
650
+ cloneUrl = cloneUrl.replace("git@github.com:", "https://github.com/");
651
+ }
652
+ if (!cloneUrl.endsWith(".git")) {
653
+ cloneUrl = `${cloneUrl}.git`;
654
+ }
655
+ if (!cloneUrl.startsWith("https://")) {
656
+ cloneUrl = `https://${cloneUrl}`;
657
+ }
658
+ try {
659
+ await this.execInDir(
660
+ workspace.path,
661
+ `git clone --depth 1 --branch ${workspace.branch.baseBranch} ${cloneUrl} .`
662
+ );
663
+ this.log("info", { workspaceId: workspace.id }, "Public repository cloned without authentication");
664
+ return { success: true };
665
+ } catch (error) {
666
+ const errorMessage = error instanceof Error ? error.message : String(error);
667
+ const isAuthError = errorMessage.includes("401") || errorMessage.includes("403") || errorMessage.includes("Authentication failed") || errorMessage.includes("could not read Username") || errorMessage.includes("terminal prompts disabled");
668
+ if (isAuthError) {
669
+ return { success: false, error: "Authentication required" };
670
+ }
671
+ throw error;
672
+ }
673
+ }
608
674
  async cloneRepo(workspace, token) {
609
675
  const cloneUrl = token ? this.buildAuthenticatedUrl(workspace.repo, token) : workspace.repo;
610
676
  await this.execInDir(
@@ -628,11 +694,11 @@ var WorkspaceService = class {
628
694
  async configureGit(workspace) {
629
695
  await this.execInDir(workspace.path, 'git config user.name "Workspace Agent"');
630
696
  await this.execInDir(workspace.path, 'git config user.email "agent@workspace.local"');
631
- if (!workspace.credential.token) {
697
+ if (!workspace.credential || !workspace.credential.token) {
632
698
  this.log(
633
699
  "debug",
634
700
  { workspaceId: workspace.id },
635
- "Using SSH authentication, skipping credential helper"
701
+ workspace.credential ? "Using SSH authentication, skipping credential helper" : "No credentials (public repo), skipping credential helper"
636
702
  );
637
703
  return;
638
704
  }
@@ -658,9 +724,19 @@ var WorkspaceService = class {
658
724
  );
659
725
  }
660
726
  async pushBranch(workspace) {
727
+ if (!workspace.credential) {
728
+ throw new Error(
729
+ "Push requires authentication. This workspace was cloned from a public repository without credentials."
730
+ );
731
+ }
661
732
  await this.execInDir(workspace.path, `git push -u origin ${workspace.branch.name}`);
662
733
  }
663
734
  async createPullRequest(workspace, config) {
735
+ if (!workspace.credential) {
736
+ throw new Error(
737
+ "Pull request creation requires authentication. This workspace was cloned from a public repository without credentials."
738
+ );
739
+ }
664
740
  const repoInfo = this.parseRepo(workspace.repo);
665
741
  if (!repoInfo) {
666
742
  throw new Error(`Invalid repository format: ${workspace.repo}`);