@locusai/cli 0.10.5 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent/worker.js +249 -795
- package/bin/locus.js +22416 -23324
- package/package.json +2 -2
package/bin/agent/worker.js
CHANGED
|
@@ -14611,8 +14611,7 @@ var init_task = __esm(() => {
|
|
|
14611
14611
|
comments: exports_external.array(CommentSchema).optional(),
|
|
14612
14612
|
activityLog: exports_external.array(EventSchema).optional(),
|
|
14613
14613
|
docs: exports_external.array(DocSchema),
|
|
14614
|
-
order: exports_external.number().optional()
|
|
14615
|
-
tier: exports_external.number().int().min(0).optional()
|
|
14614
|
+
order: exports_external.number().optional()
|
|
14616
14615
|
});
|
|
14617
14616
|
CreateTaskSchema = exports_external.object({
|
|
14618
14617
|
title: exports_external.string().min(1, "Title is required").max(200),
|
|
@@ -14627,8 +14626,7 @@ var init_task = __esm(() => {
|
|
|
14627
14626
|
sprintId: exports_external.string().nullable().optional(),
|
|
14628
14627
|
acceptanceChecklist: exports_external.array(AcceptanceItemSchema).optional(),
|
|
14629
14628
|
docIds: exports_external.array(exports_external.string()).optional(),
|
|
14630
|
-
order: exports_external.number().optional()
|
|
14631
|
-
tier: exports_external.number().int().min(0).optional()
|
|
14629
|
+
order: exports_external.number().optional()
|
|
14632
14630
|
});
|
|
14633
14631
|
UpdateTaskSchema = TaskSchema.partial().omit({
|
|
14634
14632
|
id: true,
|
|
@@ -15532,7 +15530,7 @@ class CodexRunner {
|
|
|
15532
15530
|
type: "tool_use",
|
|
15533
15531
|
tool: line.replace(/^[→•✓]\s*/, "")
|
|
15534
15532
|
});
|
|
15535
|
-
} else
|
|
15533
|
+
} else {
|
|
15536
15534
|
enqueueChunk({ type: "text_delta", content: `${line}
|
|
15537
15535
|
` });
|
|
15538
15536
|
}
|
|
@@ -15672,18 +15670,11 @@ class CodexRunner {
|
|
|
15672
15670
|
}
|
|
15673
15671
|
buildArgs(outputPath) {
|
|
15674
15672
|
const args = [
|
|
15675
|
-
"--ask-for-approval",
|
|
15676
|
-
"never",
|
|
15677
15673
|
"exec",
|
|
15678
|
-
"--
|
|
15679
|
-
"workspace-write",
|
|
15674
|
+
"--full-auto",
|
|
15680
15675
|
"--skip-git-repo-check",
|
|
15681
15676
|
"--output-last-message",
|
|
15682
|
-
outputPath
|
|
15683
|
-
"-c",
|
|
15684
|
-
"sandbox_workspace_write.network_access=true",
|
|
15685
|
-
"-c",
|
|
15686
|
-
'sandbox.excludedCommands=["git", "gh"]'
|
|
15677
|
+
outputPath
|
|
15687
15678
|
];
|
|
15688
15679
|
if (this.model) {
|
|
15689
15680
|
args.push("--model", this.model);
|
|
@@ -31385,588 +31376,252 @@ var init_knowledge_base = __esm(() => {
|
|
|
31385
31376
|
init_config();
|
|
31386
31377
|
});
|
|
31387
31378
|
|
|
31388
|
-
// ../sdk/src/git
|
|
31379
|
+
// ../sdk/src/agent/git-workflow.ts
|
|
31389
31380
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
31390
31381
|
|
|
31391
|
-
class
|
|
31392
|
-
|
|
31382
|
+
class GitWorkflow {
|
|
31383
|
+
config;
|
|
31393
31384
|
log;
|
|
31394
|
-
|
|
31395
|
-
|
|
31385
|
+
projectPath;
|
|
31386
|
+
branchName = null;
|
|
31387
|
+
baseBranch = null;
|
|
31388
|
+
ghUsername;
|
|
31389
|
+
constructor(config2, log) {
|
|
31390
|
+
this.config = config2;
|
|
31396
31391
|
this.log = log;
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
branch,
|
|
31402
|
-
baseBranch: requestedBaseBranch,
|
|
31403
|
-
agentId,
|
|
31404
|
-
summary
|
|
31405
|
-
} = options;
|
|
31406
|
-
const provider = detectRemoteProvider(this.projectPath);
|
|
31407
|
-
if (provider !== "github") {
|
|
31408
|
-
throw new Error(`PR creation is only supported for GitHub repositories (detected: ${provider})`);
|
|
31409
|
-
}
|
|
31410
|
-
if (!isGhAvailable(this.projectPath)) {
|
|
31411
|
-
throw new Error("GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/");
|
|
31412
|
-
}
|
|
31413
|
-
const title = `[Locus] ${task2.title}`;
|
|
31414
|
-
const body = this.buildPrBody(task2, agentId, summary);
|
|
31415
|
-
const baseBranch = requestedBaseBranch ?? getDefaultBranch(this.projectPath);
|
|
31416
|
-
this.validateCreatePrInputs(baseBranch, branch);
|
|
31417
|
-
this.log(`Creating PR: ${title} (${branch} → ${baseBranch})`, "info");
|
|
31418
|
-
const output = execFileSync2("gh", [
|
|
31419
|
-
"pr",
|
|
31420
|
-
"create",
|
|
31421
|
-
"--title",
|
|
31422
|
-
title,
|
|
31423
|
-
"--body",
|
|
31424
|
-
body,
|
|
31425
|
-
"--base",
|
|
31426
|
-
baseBranch,
|
|
31427
|
-
"--head",
|
|
31428
|
-
branch
|
|
31429
|
-
], {
|
|
31430
|
-
cwd: this.projectPath,
|
|
31431
|
-
encoding: "utf-8",
|
|
31432
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31433
|
-
}).trim();
|
|
31434
|
-
const url3 = output;
|
|
31435
|
-
const prNumber = this.extractPrNumber(url3);
|
|
31436
|
-
this.log(`PR created: ${url3}`, "success");
|
|
31437
|
-
return { url: url3, number: prNumber };
|
|
31438
|
-
}
|
|
31439
|
-
validateCreatePrInputs(baseBranch, headBranch) {
|
|
31440
|
-
if (!this.hasRemoteBranch(baseBranch)) {
|
|
31441
|
-
throw new Error(`Base branch "${baseBranch}" does not exist on origin. Push/fetch refs and retry.`);
|
|
31442
|
-
}
|
|
31443
|
-
if (!this.hasRemoteBranch(headBranch)) {
|
|
31444
|
-
throw new Error(`Head branch "${headBranch}" is not available on origin. Ensure it is pushed before PR creation.`);
|
|
31445
|
-
}
|
|
31446
|
-
const baseRef = this.resolveBranchRef(baseBranch);
|
|
31447
|
-
const headRef = this.resolveBranchRef(headBranch);
|
|
31448
|
-
if (!baseRef) {
|
|
31449
|
-
throw new Error(`Could not resolve base branch "${baseBranch}" locally.`);
|
|
31450
|
-
}
|
|
31451
|
-
if (!headRef) {
|
|
31452
|
-
throw new Error(`Could not resolve head branch "${headBranch}" locally.`);
|
|
31392
|
+
this.projectPath = config2.projectPath || process.cwd();
|
|
31393
|
+
this.ghUsername = getGhUsername();
|
|
31394
|
+
if (this.ghUsername) {
|
|
31395
|
+
this.log(`GitHub user: ${this.ghUsername}`, "info");
|
|
31453
31396
|
}
|
|
31454
|
-
const commitsAhead = this.countCommitsAhead(baseRef, headRef);
|
|
31455
|
-
if (commitsAhead <= 0) {
|
|
31456
|
-
throw new Error(`No commits between "${baseBranch}" and "${headBranch}". Skipping PR creation.`);
|
|
31457
|
-
}
|
|
31458
|
-
}
|
|
31459
|
-
countCommitsAhead(baseRef, headRef) {
|
|
31460
|
-
const output = execFileSync2("git", ["rev-list", "--count", `${baseRef}..${headRef}`], {
|
|
31461
|
-
cwd: this.projectPath,
|
|
31462
|
-
encoding: "utf-8",
|
|
31463
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31464
|
-
}).trim();
|
|
31465
|
-
const value = Number.parseInt(output || "0", 10);
|
|
31466
|
-
return Number.isNaN(value) ? 0 : value;
|
|
31467
31397
|
}
|
|
31468
|
-
|
|
31469
|
-
|
|
31470
|
-
|
|
31471
|
-
}
|
|
31472
|
-
if (this.hasRemoteTrackingBranch(branch)) {
|
|
31473
|
-
return `origin/${branch}`;
|
|
31474
|
-
}
|
|
31475
|
-
return null;
|
|
31476
|
-
}
|
|
31477
|
-
hasLocalBranch(branch) {
|
|
31398
|
+
createBranch(sprintId) {
|
|
31399
|
+
const defaultBranch = getDefaultBranch(this.projectPath);
|
|
31400
|
+
this.baseBranch = defaultBranch;
|
|
31478
31401
|
try {
|
|
31479
|
-
|
|
31480
|
-
|
|
31481
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31482
|
-
});
|
|
31483
|
-
return true;
|
|
31402
|
+
this.gitExec(["checkout", defaultBranch]);
|
|
31403
|
+
this.gitExec(["pull", "origin", defaultBranch]);
|
|
31484
31404
|
} catch {
|
|
31485
|
-
|
|
31405
|
+
this.log(`Could not pull latest from ${defaultBranch}, continuing with current state`, "warn");
|
|
31486
31406
|
}
|
|
31407
|
+
const suffix = sprintId ? sprintId.slice(0, 8) : Date.now().toString(36);
|
|
31408
|
+
this.branchName = `locus/${suffix}`;
|
|
31409
|
+
try {
|
|
31410
|
+
this.gitExec(["branch", "-D", this.branchName]);
|
|
31411
|
+
} catch {}
|
|
31412
|
+
this.gitExec(["checkout", "-b", this.branchName]);
|
|
31413
|
+
this.log(`Created branch: ${this.branchName} (from ${defaultBranch})`, "success");
|
|
31414
|
+
return this.branchName;
|
|
31487
31415
|
}
|
|
31488
|
-
|
|
31416
|
+
commitAndPush(task2) {
|
|
31417
|
+
if (!this.branchName) {
|
|
31418
|
+
this.log("No branch created yet, skipping commit", "warn");
|
|
31419
|
+
return { branch: null, pushed: false, pushFailed: false };
|
|
31420
|
+
}
|
|
31489
31421
|
try {
|
|
31490
|
-
|
|
31491
|
-
|
|
31492
|
-
|
|
31493
|
-
|
|
31494
|
-
|
|
31495
|
-
|
|
31496
|
-
|
|
31422
|
+
const status = this.gitExec(["status", "--porcelain"]).trim();
|
|
31423
|
+
if (!status) {
|
|
31424
|
+
const baseBranchCommit = this.getBaseCommit();
|
|
31425
|
+
const headCommit = this.gitExec(["rev-parse", "HEAD"]).trim();
|
|
31426
|
+
if (baseBranchCommit && headCommit !== baseBranchCommit) {
|
|
31427
|
+
return this.pushBranch();
|
|
31428
|
+
}
|
|
31429
|
+
this.log("No changes to commit for this task", "info");
|
|
31430
|
+
return {
|
|
31431
|
+
branch: this.branchName,
|
|
31432
|
+
pushed: false,
|
|
31433
|
+
pushFailed: false,
|
|
31434
|
+
noChanges: true,
|
|
31435
|
+
skipReason: "No changes were made for this task."
|
|
31436
|
+
};
|
|
31437
|
+
}
|
|
31438
|
+
this.gitExec(["add", "-A"]);
|
|
31439
|
+
const staged = this.gitExec(["diff", "--cached", "--name-only"]).trim();
|
|
31440
|
+
if (!staged) {
|
|
31441
|
+
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
31442
|
+
return {
|
|
31443
|
+
branch: this.branchName,
|
|
31444
|
+
pushed: false,
|
|
31445
|
+
pushFailed: false,
|
|
31446
|
+
noChanges: true,
|
|
31447
|
+
skipReason: "All changes were ignored by .gitignore."
|
|
31448
|
+
};
|
|
31449
|
+
}
|
|
31450
|
+
this.log(`Staging ${staged.split(`
|
|
31451
|
+
`).length} file(s) for commit`, "info");
|
|
31452
|
+
const trailers = [
|
|
31453
|
+
`Task-ID: ${task2.id}`,
|
|
31454
|
+
`Agent: ${this.config.agentId}`,
|
|
31455
|
+
"Co-authored-by: LocusAI <agent@locusai.team>"
|
|
31456
|
+
];
|
|
31457
|
+
if (this.ghUsername) {
|
|
31458
|
+
trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
|
|
31459
|
+
}
|
|
31460
|
+
const commitMessage = `feat(agent): ${task2.title}
|
|
31461
|
+
|
|
31462
|
+
${trailers.join(`
|
|
31463
|
+
`)}`;
|
|
31464
|
+
this.gitExec(["commit", "-m", commitMessage]);
|
|
31465
|
+
const hash2 = this.gitExec(["rev-parse", "HEAD"]).trim();
|
|
31466
|
+
this.log(`Committed: ${hash2.slice(0, 8)}`, "success");
|
|
31467
|
+
return this.pushBranch();
|
|
31468
|
+
} catch (err) {
|
|
31469
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
31470
|
+
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
31471
|
+
return {
|
|
31472
|
+
branch: this.branchName,
|
|
31473
|
+
pushed: false,
|
|
31474
|
+
pushFailed: true,
|
|
31475
|
+
pushError: `Git commit/push failed: ${errorMessage}`
|
|
31476
|
+
};
|
|
31497
31477
|
}
|
|
31498
31478
|
}
|
|
31499
|
-
|
|
31479
|
+
pushBranch() {
|
|
31480
|
+
if (!this.branchName) {
|
|
31481
|
+
return { branch: null, pushed: false, pushFailed: false };
|
|
31482
|
+
}
|
|
31500
31483
|
try {
|
|
31501
|
-
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
|
|
31505
|
-
|
|
31506
|
-
|
|
31507
|
-
|
|
31484
|
+
this.gitExec(["push", "-u", "origin", this.branchName]);
|
|
31485
|
+
this.log(`Pushed ${this.branchName} to origin`, "success");
|
|
31486
|
+
return { branch: this.branchName, pushed: true, pushFailed: false };
|
|
31487
|
+
} catch (error48) {
|
|
31488
|
+
const msg = error48 instanceof Error ? error48.message : String(error48);
|
|
31489
|
+
if (msg.includes("non-fast-forward") || msg.includes("[rejected]") || msg.includes("fetch first")) {
|
|
31490
|
+
this.log(`Push rejected (non-fast-forward). Retrying with --force-with-lease.`, "warn");
|
|
31491
|
+
try {
|
|
31492
|
+
this.gitExec([
|
|
31493
|
+
"push",
|
|
31494
|
+
"--force-with-lease",
|
|
31495
|
+
"-u",
|
|
31496
|
+
"origin",
|
|
31497
|
+
this.branchName
|
|
31498
|
+
]);
|
|
31499
|
+
this.log(`Pushed ${this.branchName} to origin with --force-with-lease`, "success");
|
|
31500
|
+
return { branch: this.branchName, pushed: true, pushFailed: false };
|
|
31501
|
+
} catch (retryErr) {
|
|
31502
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
31503
|
+
this.log(`Git push retry failed: ${retryMsg}`, "error");
|
|
31504
|
+
return {
|
|
31505
|
+
branch: this.branchName,
|
|
31506
|
+
pushed: false,
|
|
31507
|
+
pushFailed: true,
|
|
31508
|
+
pushError: retryMsg
|
|
31509
|
+
};
|
|
31510
|
+
}
|
|
31511
|
+
}
|
|
31512
|
+
this.log(`Git push failed: ${msg}`, "error");
|
|
31513
|
+
return {
|
|
31514
|
+
branch: this.branchName,
|
|
31515
|
+
pushed: false,
|
|
31516
|
+
pushFailed: true,
|
|
31517
|
+
pushError: msg
|
|
31518
|
+
};
|
|
31508
31519
|
}
|
|
31509
31520
|
}
|
|
31510
|
-
|
|
31511
|
-
|
|
31512
|
-
|
|
31513
|
-
|
|
31514
|
-
|
|
31515
|
-
|
|
31516
|
-
|
|
31517
|
-
|
|
31518
|
-
|
|
31521
|
+
createPullRequest(completedTasks, summaries) {
|
|
31522
|
+
if (!this.branchName || !this.baseBranch) {
|
|
31523
|
+
return { url: null, error: "No branch or base branch available." };
|
|
31524
|
+
}
|
|
31525
|
+
const provider = detectRemoteProvider(this.projectPath);
|
|
31526
|
+
if (provider !== "github") {
|
|
31527
|
+
return {
|
|
31528
|
+
url: null,
|
|
31529
|
+
error: `PR creation is only supported for GitHub repositories (detected: ${provider})`
|
|
31530
|
+
};
|
|
31531
|
+
}
|
|
31532
|
+
if (!isGhAvailable(this.projectPath)) {
|
|
31533
|
+
return {
|
|
31534
|
+
url: null,
|
|
31535
|
+
error: "GitHub CLI (gh) is not installed or not authenticated. Install from https://cli.github.com/"
|
|
31536
|
+
};
|
|
31537
|
+
}
|
|
31538
|
+
const title = `[Locus] Sprint tasks (${completedTasks.length} task${completedTasks.length !== 1 ? "s" : ""})`;
|
|
31539
|
+
const body = this.buildPrBody(completedTasks, summaries);
|
|
31540
|
+
this.log(`Creating PR: ${title} (${this.branchName} → ${this.baseBranch})`, "info");
|
|
31519
31541
|
try {
|
|
31520
|
-
execFileSync2("gh", [
|
|
31542
|
+
const output = execFileSync2("gh", [
|
|
31521
31543
|
"pr",
|
|
31522
|
-
"
|
|
31523
|
-
|
|
31544
|
+
"create",
|
|
31545
|
+
"--title",
|
|
31546
|
+
title,
|
|
31524
31547
|
"--body",
|
|
31525
31548
|
body,
|
|
31526
|
-
|
|
31549
|
+
"--base",
|
|
31550
|
+
this.baseBranch,
|
|
31551
|
+
"--head",
|
|
31552
|
+
this.branchName
|
|
31527
31553
|
], {
|
|
31528
31554
|
cwd: this.projectPath,
|
|
31529
31555
|
encoding: "utf-8",
|
|
31530
31556
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31531
|
-
});
|
|
31557
|
+
}).trim();
|
|
31558
|
+
this.log(`PR created: ${output}`, "success");
|
|
31559
|
+
return { url: output };
|
|
31532
31560
|
} catch (err) {
|
|
31533
|
-
const
|
|
31534
|
-
|
|
31535
|
-
|
|
31536
|
-
cwd: this.projectPath,
|
|
31537
|
-
encoding: "utf-8",
|
|
31538
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31539
|
-
});
|
|
31540
|
-
return;
|
|
31541
|
-
}
|
|
31542
|
-
throw err;
|
|
31561
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
31562
|
+
this.log(`PR creation failed: ${errorMessage}`, "error");
|
|
31563
|
+
return { url: null, error: errorMessage };
|
|
31543
31564
|
}
|
|
31544
31565
|
}
|
|
31545
|
-
|
|
31566
|
+
checkoutBaseBranch() {
|
|
31567
|
+
if (!this.baseBranch)
|
|
31568
|
+
return;
|
|
31546
31569
|
try {
|
|
31547
|
-
|
|
31548
|
-
|
|
31549
|
-
|
|
31550
|
-
|
|
31551
|
-
"[Locus] in:title",
|
|
31552
|
-
"--state",
|
|
31553
|
-
"open",
|
|
31554
|
-
"--json",
|
|
31555
|
-
"number,title,url,headRefName"
|
|
31556
|
-
], {
|
|
31557
|
-
cwd: this.projectPath,
|
|
31558
|
-
encoding: "utf-8",
|
|
31559
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31560
|
-
}).trim();
|
|
31561
|
-
const prs = JSON.parse(output || "[]");
|
|
31562
|
-
return prs.map((pr) => ({
|
|
31563
|
-
number: pr.number,
|
|
31564
|
-
title: pr.title,
|
|
31565
|
-
url: pr.url,
|
|
31566
|
-
branch: pr.headRefName
|
|
31567
|
-
}));
|
|
31568
|
-
} catch {
|
|
31569
|
-
this.log("Failed to list Locus PRs", "warn");
|
|
31570
|
-
return [];
|
|
31570
|
+
this.gitExec(["checkout", this.baseBranch]);
|
|
31571
|
+
this.log(`Checked out base branch: ${this.baseBranch}`, "info");
|
|
31572
|
+
} catch (err) {
|
|
31573
|
+
this.log(`Could not checkout base branch: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
31571
31574
|
}
|
|
31572
31575
|
}
|
|
31573
|
-
|
|
31576
|
+
getBranchName() {
|
|
31577
|
+
return this.branchName;
|
|
31578
|
+
}
|
|
31579
|
+
getBaseBranch() {
|
|
31580
|
+
return this.baseBranch;
|
|
31581
|
+
}
|
|
31582
|
+
getBaseCommit() {
|
|
31583
|
+
if (!this.baseBranch)
|
|
31584
|
+
return null;
|
|
31574
31585
|
try {
|
|
31575
|
-
|
|
31576
|
-
cwd: this.projectPath,
|
|
31577
|
-
encoding: "utf-8",
|
|
31578
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31579
|
-
}).trim();
|
|
31580
|
-
const data = JSON.parse(output || "{}");
|
|
31581
|
-
return data.reviews?.some((r) => r.body?.includes("## Locus Agent Review")) ?? false;
|
|
31586
|
+
return this.gitExec(["rev-parse", this.baseBranch]).trim();
|
|
31582
31587
|
} catch {
|
|
31583
|
-
return
|
|
31588
|
+
return null;
|
|
31584
31589
|
}
|
|
31585
31590
|
}
|
|
31586
|
-
|
|
31587
|
-
const allPrs = this.listLocusPrs();
|
|
31588
|
-
return allPrs.filter((pr) => !this.hasLocusReview(String(pr.number)));
|
|
31589
|
-
}
|
|
31590
|
-
buildPrBody(task2, agentId, summary) {
|
|
31591
|
+
buildPrBody(completedTasks, summaries) {
|
|
31591
31592
|
const sections = [];
|
|
31592
|
-
sections.push(
|
|
31593
|
+
sections.push("## Completed Tasks");
|
|
31593
31594
|
sections.push("");
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
sections.push(
|
|
31597
|
-
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
sections.push(`- [ ] ${item.text}`);
|
|
31595
|
+
for (let i = 0;i < completedTasks.length; i++) {
|
|
31596
|
+
const task2 = completedTasks[i];
|
|
31597
|
+
sections.push(`### ${i + 1}. ${task2.title}`);
|
|
31598
|
+
sections.push(`Task ID: \`${task2.id}\``);
|
|
31599
|
+
if (summaries[i]) {
|
|
31600
|
+
sections.push("");
|
|
31601
|
+
sections.push(summaries[i]);
|
|
31602
31602
|
}
|
|
31603
31603
|
sections.push("");
|
|
31604
31604
|
}
|
|
31605
|
-
if (summary) {
|
|
31606
|
-
sections.push("## Agent Summary");
|
|
31607
|
-
sections.push(summary);
|
|
31608
|
-
sections.push("");
|
|
31609
|
-
}
|
|
31610
31605
|
sections.push("---");
|
|
31611
|
-
sections.push(`*Created by Locus Agent \`${agentId.slice(-8)}
|
|
31606
|
+
sections.push(`*Created by Locus Agent \`${this.config.agentId.slice(-8)}\`*`);
|
|
31612
31607
|
return sections.join(`
|
|
31613
31608
|
`);
|
|
31614
31609
|
}
|
|
31615
|
-
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
}
|
|
31619
|
-
}
|
|
31620
|
-
var init_pr_service = __esm(() => {
|
|
31621
|
-
init_git_utils();
|
|
31622
|
-
});
|
|
31623
|
-
|
|
31624
|
-
// ../sdk/src/worktree/worktree-config.ts
|
|
31625
|
-
var WORKTREE_ROOT_DIR = ".locus-worktrees", WORKTREE_BRANCH_PREFIX = "agent", DEFAULT_WORKTREE_CONFIG;
|
|
31626
|
-
var init_worktree_config = __esm(() => {
|
|
31627
|
-
DEFAULT_WORKTREE_CONFIG = {
|
|
31628
|
-
rootDir: WORKTREE_ROOT_DIR,
|
|
31629
|
-
branchPrefix: WORKTREE_BRANCH_PREFIX,
|
|
31630
|
-
cleanupPolicy: "retain-on-failure"
|
|
31631
|
-
};
|
|
31632
|
-
});
|
|
31633
|
-
|
|
31634
|
-
// ../sdk/src/worktree/worktree-manager.ts
|
|
31635
|
-
import { execFileSync as execFileSync3, execSync } from "node:child_process";
|
|
31636
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, rmSync, statSync } from "node:fs";
|
|
31637
|
-
import { join as join4, resolve as resolve2, sep } from "node:path";
|
|
31638
|
-
|
|
31639
|
-
class WorktreeManager {
|
|
31640
|
-
config;
|
|
31641
|
-
projectPath;
|
|
31642
|
-
log;
|
|
31643
|
-
constructor(projectPath, config2, log) {
|
|
31644
|
-
this.projectPath = resolve2(projectPath);
|
|
31645
|
-
this.config = { ...DEFAULT_WORKTREE_CONFIG, ...config2 };
|
|
31646
|
-
this.log = log ?? ((_msg) => {
|
|
31647
|
-
return;
|
|
31648
|
-
});
|
|
31649
|
-
}
|
|
31650
|
-
get rootPath() {
|
|
31651
|
-
return join4(this.projectPath, this.config.rootDir);
|
|
31652
|
-
}
|
|
31653
|
-
buildBranchName(taskId, taskSlug) {
|
|
31654
|
-
const sanitized = taskSlug.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 50);
|
|
31655
|
-
return `${this.config.branchPrefix}/${taskId}-${sanitized}`;
|
|
31656
|
-
}
|
|
31657
|
-
create(options) {
|
|
31658
|
-
const branch = this.buildBranchName(options.taskId, options.taskSlug);
|
|
31659
|
-
const worktreeDir = `${options.agentId}-${options.taskId}`;
|
|
31660
|
-
const worktreePath = join4(this.rootPath, worktreeDir);
|
|
31661
|
-
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
31662
|
-
const baseBranch = options.baseBranch ?? this.config.baseBranch ?? this.getCurrentBranch();
|
|
31663
|
-
if (!this.branchExists(baseBranch)) {
|
|
31664
|
-
this.log(`Base branch "${baseBranch}" not found locally, fetching from origin`, "info");
|
|
31665
|
-
try {
|
|
31666
|
-
this.gitExec(["fetch", "origin", baseBranch], this.projectPath);
|
|
31667
|
-
this.gitExec(["branch", baseBranch, `origin/${baseBranch}`], this.projectPath);
|
|
31668
|
-
} catch {
|
|
31669
|
-
this.log(`Could not fetch/create local branch for "${baseBranch}", falling back to current branch`, "warn");
|
|
31670
|
-
}
|
|
31671
|
-
}
|
|
31672
|
-
this.log(`Creating worktree: ${worktreeDir} (branch: ${branch}, base: ${baseBranch})`, "info");
|
|
31673
|
-
if (existsSync4(worktreePath)) {
|
|
31674
|
-
this.log(`Removing stale worktree directory: ${worktreePath}`, "warn");
|
|
31675
|
-
try {
|
|
31676
|
-
this.git(`worktree remove "${worktreePath}" --force`, this.projectPath);
|
|
31677
|
-
} catch {
|
|
31678
|
-
rmSync(worktreePath, { recursive: true, force: true });
|
|
31679
|
-
this.git("worktree prune", this.projectPath);
|
|
31680
|
-
}
|
|
31681
|
-
}
|
|
31682
|
-
if (this.branchExists(branch)) {
|
|
31683
|
-
this.log(`Deleting existing branch: ${branch}`, "warn");
|
|
31684
|
-
const branchWorktrees = this.list().filter((wt) => wt.branch === branch);
|
|
31685
|
-
for (const wt of branchWorktrees) {
|
|
31686
|
-
const worktreePath2 = resolve2(wt.path);
|
|
31687
|
-
if (wt.isMain || !this.isManagedWorktreePath(worktreePath2)) {
|
|
31688
|
-
throw new Error(`Branch "${branch}" is checked out at "${worktreePath2}". Remove or detach that worktree before retrying.`);
|
|
31689
|
-
}
|
|
31690
|
-
this.log(`Removing existing worktree for branch: ${branch} (${worktreePath2})`, "warn");
|
|
31691
|
-
this.remove(worktreePath2, false);
|
|
31692
|
-
}
|
|
31693
|
-
try {
|
|
31694
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
31695
|
-
} catch {
|
|
31696
|
-
this.git("worktree prune", this.projectPath);
|
|
31697
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
31698
|
-
}
|
|
31699
|
-
}
|
|
31700
|
-
const addWorktree = () => this.git(`worktree add "${worktreePath}" -b "${branch}" "${baseBranch}"`, this.projectPath);
|
|
31701
|
-
try {
|
|
31702
|
-
addWorktree();
|
|
31703
|
-
} catch (error48) {
|
|
31704
|
-
if (!this.isMissingDirectoryError(error48)) {
|
|
31705
|
-
throw error48;
|
|
31706
|
-
}
|
|
31707
|
-
this.log(`Worktree creation failed due to missing directories. Retrying after cleanup: ${worktreePath}`, "warn");
|
|
31708
|
-
this.cleanupFailedWorktree(worktreePath, branch);
|
|
31709
|
-
this.ensureDirectory(this.rootPath, "Worktree root");
|
|
31710
|
-
addWorktree();
|
|
31711
|
-
}
|
|
31712
|
-
const baseCommitHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31713
|
-
this.log(`Worktree created at ${worktreePath} (base: ${baseCommitHash.slice(0, 8)})`, "success");
|
|
31714
|
-
return { worktreePath, branch, baseBranch, baseCommitHash };
|
|
31715
|
-
}
|
|
31716
|
-
list() {
|
|
31717
|
-
const output = this.git("worktree list --porcelain", this.projectPath);
|
|
31718
|
-
const worktrees = [];
|
|
31719
|
-
const blocks = output.trim().split(`
|
|
31720
|
-
|
|
31721
|
-
`);
|
|
31722
|
-
for (const block of blocks) {
|
|
31723
|
-
if (!block.trim())
|
|
31724
|
-
continue;
|
|
31725
|
-
const lines = block.trim().split(`
|
|
31726
|
-
`);
|
|
31727
|
-
let path = "";
|
|
31728
|
-
let head = "";
|
|
31729
|
-
let branch = "";
|
|
31730
|
-
let isMain = false;
|
|
31731
|
-
let isPrunable = false;
|
|
31732
|
-
for (const line of lines) {
|
|
31733
|
-
if (line.startsWith("worktree ")) {
|
|
31734
|
-
path = line.slice("worktree ".length);
|
|
31735
|
-
} else if (line.startsWith("HEAD ")) {
|
|
31736
|
-
head = line.slice("HEAD ".length);
|
|
31737
|
-
} else if (line.startsWith("branch ")) {
|
|
31738
|
-
branch = line.slice("branch ".length).replace("refs/heads/", "");
|
|
31739
|
-
} else if (line === "bare" || path === this.projectPath) {
|
|
31740
|
-
isMain = true;
|
|
31741
|
-
} else if (line === "prunable") {
|
|
31742
|
-
isPrunable = true;
|
|
31743
|
-
} else if (line === "detached") {
|
|
31744
|
-
branch = "(detached)";
|
|
31745
|
-
}
|
|
31746
|
-
}
|
|
31747
|
-
if (resolve2(path) === this.projectPath) {
|
|
31748
|
-
isMain = true;
|
|
31749
|
-
}
|
|
31750
|
-
if (path) {
|
|
31751
|
-
worktrees.push({ path, branch, head, isMain, isPrunable });
|
|
31752
|
-
}
|
|
31753
|
-
}
|
|
31754
|
-
return worktrees;
|
|
31755
|
-
}
|
|
31756
|
-
listAgentWorktrees() {
|
|
31757
|
-
return this.list().filter((wt) => !wt.isMain);
|
|
31758
|
-
}
|
|
31759
|
-
remove(worktreePath, deleteBranch = true) {
|
|
31760
|
-
const absolutePath = resolve2(worktreePath);
|
|
31761
|
-
const worktrees = this.list();
|
|
31762
|
-
const worktree = worktrees.find((wt) => resolve2(wt.path) === absolutePath);
|
|
31763
|
-
const branchToDelete = worktree?.branch;
|
|
31764
|
-
this.log(`Removing worktree: ${absolutePath}`, "info");
|
|
31765
|
-
try {
|
|
31766
|
-
this.git(`worktree remove "${absolutePath}" --force`, this.projectPath);
|
|
31767
|
-
} catch {
|
|
31768
|
-
if (existsSync4(absolutePath)) {
|
|
31769
|
-
rmSync(absolutePath, { recursive: true, force: true });
|
|
31770
|
-
}
|
|
31771
|
-
this.git("worktree prune", this.projectPath);
|
|
31772
|
-
}
|
|
31773
|
-
if (deleteBranch && branchToDelete && !branchToDelete.startsWith("(")) {
|
|
31774
|
-
try {
|
|
31775
|
-
this.git(`branch -D "${branchToDelete}"`, this.projectPath);
|
|
31776
|
-
this.log(`Deleted branch: ${branchToDelete}`, "success");
|
|
31777
|
-
} catch {
|
|
31778
|
-
this.log(`Could not delete branch: ${branchToDelete} (may already be deleted)`, "warn");
|
|
31779
|
-
}
|
|
31780
|
-
}
|
|
31781
|
-
this.log("Worktree removed", "success");
|
|
31782
|
-
}
|
|
31783
|
-
prune() {
|
|
31784
|
-
const before = this.listAgentWorktrees().filter((wt) => wt.isPrunable).length;
|
|
31785
|
-
this.git("worktree prune", this.projectPath);
|
|
31786
|
-
const after = this.listAgentWorktrees().filter((wt) => wt.isPrunable).length;
|
|
31787
|
-
const pruned = before - after;
|
|
31788
|
-
if (pruned > 0) {
|
|
31789
|
-
this.log(`Pruned ${pruned} stale worktree(s)`, "success");
|
|
31790
|
-
}
|
|
31791
|
-
return pruned;
|
|
31792
|
-
}
|
|
31793
|
-
removeAll() {
|
|
31794
|
-
const agentWorktrees = this.listAgentWorktrees();
|
|
31795
|
-
let removed = 0;
|
|
31796
|
-
for (const wt of agentWorktrees) {
|
|
31797
|
-
try {
|
|
31798
|
-
this.remove(wt.path, true);
|
|
31799
|
-
removed++;
|
|
31800
|
-
} catch {
|
|
31801
|
-
this.log(`Failed to remove worktree: ${wt.path}`, "warn");
|
|
31802
|
-
}
|
|
31803
|
-
}
|
|
31804
|
-
if (existsSync4(this.rootPath)) {
|
|
31805
|
-
try {
|
|
31806
|
-
rmSync(this.rootPath, { recursive: true, force: true });
|
|
31807
|
-
} catch {}
|
|
31808
|
-
}
|
|
31809
|
-
return removed;
|
|
31810
|
-
}
|
|
31811
|
-
hasChanges(worktreePath) {
|
|
31812
|
-
const status = this.git("status --porcelain", worktreePath).trim();
|
|
31813
|
-
return status.length > 0;
|
|
31814
|
-
}
|
|
31815
|
-
hasCommitsAhead(worktreePath, baseBranch) {
|
|
31816
|
-
try {
|
|
31817
|
-
const count = this.git(`rev-list --count "${baseBranch}..HEAD"`, worktreePath).trim();
|
|
31818
|
-
return Number.parseInt(count, 10) > 0;
|
|
31819
|
-
} catch (err) {
|
|
31820
|
-
this.log(`Could not compare HEAD against base branch "${baseBranch}": ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
31821
|
-
return false;
|
|
31822
|
-
}
|
|
31823
|
-
}
|
|
31824
|
-
hasCommitsAheadOfHash(worktreePath, baseHash) {
|
|
31825
|
-
try {
|
|
31826
|
-
const headHash = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31827
|
-
return headHash !== baseHash;
|
|
31828
|
-
} catch {
|
|
31829
|
-
return false;
|
|
31830
|
-
}
|
|
31831
|
-
}
|
|
31832
|
-
commitChanges(worktreePath, message, baseBranch, baseCommitHash) {
|
|
31833
|
-
const hasUncommittedChanges = this.hasChanges(worktreePath);
|
|
31834
|
-
if (hasUncommittedChanges) {
|
|
31835
|
-
const statusOutput = this.git("status --porcelain", worktreePath).trim();
|
|
31836
|
-
this.log(`Detected uncommitted changes:
|
|
31837
|
-
${statusOutput.split(`
|
|
31838
|
-
`).slice(0, 10).join(`
|
|
31839
|
-
`)}${statusOutput.split(`
|
|
31840
|
-
`).length > 10 ? `
|
|
31841
|
-
... and ${statusOutput.split(`
|
|
31842
|
-
`).length - 10} more` : ""}`, "info");
|
|
31843
|
-
}
|
|
31844
|
-
if (!hasUncommittedChanges) {
|
|
31845
|
-
if (baseBranch && this.hasCommitsAhead(worktreePath, baseBranch)) {
|
|
31846
|
-
const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31847
|
-
this.log(`Agent already committed changes (${hash3.slice(0, 8)}); skipping additional commit`, "info");
|
|
31848
|
-
return hash3;
|
|
31849
|
-
}
|
|
31850
|
-
if (baseCommitHash && this.hasCommitsAheadOfHash(worktreePath, baseCommitHash)) {
|
|
31851
|
-
const hash3 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31852
|
-
this.log(`Agent already committed changes (${hash3.slice(0, 8)}, detected via base commit hash); skipping additional commit`, "info");
|
|
31853
|
-
return hash3;
|
|
31854
|
-
}
|
|
31855
|
-
const branch = this.getBranch(worktreePath);
|
|
31856
|
-
this.log(`No changes detected in worktree (branch: ${branch}, baseBranch: ${baseBranch ?? "none"}, baseCommitHash: ${baseCommitHash?.slice(0, 8) ?? "none"})`, "warn");
|
|
31857
|
-
return null;
|
|
31858
|
-
}
|
|
31859
|
-
this.git("add -A", worktreePath);
|
|
31860
|
-
const staged = this.git("diff --cached --name-only", worktreePath).trim();
|
|
31861
|
-
if (!staged) {
|
|
31862
|
-
this.log("All changes were ignored by .gitignore — nothing to commit", "warn");
|
|
31863
|
-
return null;
|
|
31864
|
-
}
|
|
31865
|
-
this.log(`Staging ${staged.split(`
|
|
31866
|
-
`).length} file(s) for commit`, "info");
|
|
31867
|
-
this.gitExec(["commit", "-m", message], worktreePath);
|
|
31868
|
-
const hash2 = this.git("rev-parse HEAD", worktreePath).trim();
|
|
31869
|
-
this.log(`Committed: ${hash2.slice(0, 8)}`, "success");
|
|
31870
|
-
return hash2;
|
|
31871
|
-
}
|
|
31872
|
-
pushBranch(worktreePath, remote = "origin") {
|
|
31873
|
-
const branch = this.getBranch(worktreePath);
|
|
31874
|
-
this.log(`Pushing branch ${branch} to ${remote}`, "info");
|
|
31875
|
-
try {
|
|
31876
|
-
this.gitExec(["push", "-u", remote, branch], worktreePath);
|
|
31877
|
-
this.log(`Pushed ${branch} to ${remote}`, "success");
|
|
31878
|
-
return branch;
|
|
31879
|
-
} catch (error48) {
|
|
31880
|
-
if (!this.isNonFastForwardPushError(error48)) {
|
|
31881
|
-
throw error48;
|
|
31882
|
-
}
|
|
31883
|
-
this.log(`Push rejected for ${branch} (non-fast-forward). Retrying with --force-with-lease.`, "warn");
|
|
31884
|
-
try {
|
|
31885
|
-
this.gitExec(["fetch", remote, branch], worktreePath);
|
|
31886
|
-
} catch {}
|
|
31887
|
-
this.gitExec(["push", "--force-with-lease", "-u", remote, branch], worktreePath);
|
|
31888
|
-
this.log(`Pushed ${branch} to ${remote} with --force-with-lease`, "success");
|
|
31889
|
-
return branch;
|
|
31890
|
-
}
|
|
31891
|
-
}
|
|
31892
|
-
getBranch(worktreePath) {
|
|
31893
|
-
return this.git("rev-parse --abbrev-ref HEAD", worktreePath).trim();
|
|
31894
|
-
}
|
|
31895
|
-
hasWorktreeForTask(taskId) {
|
|
31896
|
-
return this.listAgentWorktrees().some((wt) => wt.branch.includes(taskId) || wt.path.includes(taskId));
|
|
31897
|
-
}
|
|
31898
|
-
branchExists(branchName) {
|
|
31899
|
-
try {
|
|
31900
|
-
this.git(`rev-parse --verify "refs/heads/${branchName}"`, this.projectPath);
|
|
31901
|
-
return true;
|
|
31902
|
-
} catch {
|
|
31903
|
-
return false;
|
|
31904
|
-
}
|
|
31905
|
-
}
|
|
31906
|
-
getCurrentBranch() {
|
|
31907
|
-
return this.git("rev-parse --abbrev-ref HEAD", this.projectPath).trim();
|
|
31908
|
-
}
|
|
31909
|
-
isManagedWorktreePath(worktreePath) {
|
|
31910
|
-
const rootPath = resolve2(this.rootPath);
|
|
31911
|
-
const candidate = resolve2(worktreePath);
|
|
31912
|
-
const rootWithSep = rootPath.endsWith(sep) ? rootPath : `${rootPath}${sep}`;
|
|
31913
|
-
return candidate.startsWith(rootWithSep);
|
|
31914
|
-
}
|
|
31915
|
-
ensureDirectory(dirPath, label) {
|
|
31916
|
-
if (existsSync4(dirPath)) {
|
|
31917
|
-
if (!statSync(dirPath).isDirectory()) {
|
|
31918
|
-
throw new Error(`${label} exists but is not a directory: ${dirPath}`);
|
|
31919
|
-
}
|
|
31920
|
-
return;
|
|
31921
|
-
}
|
|
31922
|
-
mkdirSync2(dirPath, { recursive: true });
|
|
31923
|
-
}
|
|
31924
|
-
isMissingDirectoryError(error48) {
|
|
31925
|
-
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
31926
|
-
return message.includes("cannot create directory") || message.includes("No such file or directory");
|
|
31927
|
-
}
|
|
31928
|
-
cleanupFailedWorktree(worktreePath, branch) {
|
|
31929
|
-
try {
|
|
31930
|
-
this.git(`worktree remove "${worktreePath}" --force`, this.projectPath);
|
|
31931
|
-
} catch {}
|
|
31932
|
-
if (existsSync4(worktreePath)) {
|
|
31933
|
-
rmSync(worktreePath, { recursive: true, force: true });
|
|
31934
|
-
}
|
|
31935
|
-
try {
|
|
31936
|
-
this.git("worktree prune", this.projectPath);
|
|
31937
|
-
} catch {}
|
|
31938
|
-
if (this.branchExists(branch)) {
|
|
31939
|
-
try {
|
|
31940
|
-
this.git(`branch -D "${branch}"`, this.projectPath);
|
|
31941
|
-
} catch {}
|
|
31942
|
-
}
|
|
31943
|
-
}
|
|
31944
|
-
isNonFastForwardPushError(error48) {
|
|
31945
|
-
const message = error48 instanceof Error ? error48.message : String(error48);
|
|
31946
|
-
return message.includes("non-fast-forward") || message.includes("[rejected]") || message.includes("fetch first");
|
|
31947
|
-
}
|
|
31948
|
-
git(args, cwd) {
|
|
31949
|
-
return execSync(`git ${args}`, {
|
|
31950
|
-
cwd,
|
|
31951
|
-
encoding: "utf-8",
|
|
31952
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
31953
|
-
});
|
|
31954
|
-
}
|
|
31955
|
-
gitExec(args, cwd) {
|
|
31956
|
-
return execFileSync3("git", args, {
|
|
31957
|
-
cwd,
|
|
31610
|
+
gitExec(args) {
|
|
31611
|
+
return execFileSync2("git", args, {
|
|
31612
|
+
cwd: this.projectPath,
|
|
31958
31613
|
encoding: "utf-8",
|
|
31959
31614
|
stdio: ["pipe", "pipe", "pipe"]
|
|
31960
31615
|
});
|
|
31961
31616
|
}
|
|
31962
31617
|
}
|
|
31963
|
-
var
|
|
31964
|
-
|
|
31618
|
+
var init_git_workflow = __esm(() => {
|
|
31619
|
+
init_git_utils();
|
|
31965
31620
|
});
|
|
31966
31621
|
|
|
31967
31622
|
// ../sdk/src/core/prompt-builder.ts
|
|
31968
|
-
import { existsSync as
|
|
31969
|
-
import { join as
|
|
31623
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync } from "node:fs";
|
|
31624
|
+
import { join as join4 } from "node:path";
|
|
31970
31625
|
|
|
31971
31626
|
class PromptBuilder {
|
|
31972
31627
|
projectPath;
|
|
@@ -32008,7 +31663,7 @@ ${task2.description || "No description provided."}
|
|
|
32008
31663
|
}
|
|
32009
31664
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
32010
31665
|
let hasLocalContext = false;
|
|
32011
|
-
if (
|
|
31666
|
+
if (existsSync4(contextPath)) {
|
|
32012
31667
|
try {
|
|
32013
31668
|
const context = readFileSync4(contextPath, "utf-8");
|
|
32014
31669
|
if (context.trim().length > 20) {
|
|
@@ -32064,7 +31719,7 @@ ${serverContext.context}
|
|
|
32064
31719
|
|
|
32065
31720
|
`;
|
|
32066
31721
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
32067
|
-
if (
|
|
31722
|
+
if (existsSync4(indexPath)) {
|
|
32068
31723
|
prompt += `## Codebase Overview
|
|
32069
31724
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
32070
31725
|
|
|
@@ -32141,7 +31796,7 @@ ${query}
|
|
|
32141
31796
|
}
|
|
32142
31797
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
32143
31798
|
let hasLocalContext = false;
|
|
32144
|
-
if (
|
|
31799
|
+
if (existsSync4(contextPath)) {
|
|
32145
31800
|
try {
|
|
32146
31801
|
const context = readFileSync4(contextPath, "utf-8");
|
|
32147
31802
|
if (context.trim().length > 20) {
|
|
@@ -32177,7 +31832,7 @@ ${fallback}
|
|
|
32177
31832
|
|
|
32178
31833
|
`;
|
|
32179
31834
|
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
32180
|
-
if (
|
|
31835
|
+
if (existsSync4(indexPath)) {
|
|
32181
31836
|
prompt += `## Codebase Overview
|
|
32182
31837
|
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
32183
31838
|
|
|
@@ -32192,7 +31847,7 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
32192
31847
|
}
|
|
32193
31848
|
getProjectConfig() {
|
|
32194
31849
|
const configPath = getLocusPath(this.projectPath, "configFile");
|
|
32195
|
-
if (
|
|
31850
|
+
if (existsSync4(configPath)) {
|
|
32196
31851
|
try {
|
|
32197
31852
|
return JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
32198
31853
|
} catch {
|
|
@@ -32202,8 +31857,8 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
32202
31857
|
return null;
|
|
32203
31858
|
}
|
|
32204
31859
|
getFallbackContext() {
|
|
32205
|
-
const readmePath =
|
|
32206
|
-
if (
|
|
31860
|
+
const readmePath = join4(this.projectPath, "README.md");
|
|
31861
|
+
if (existsSync4(readmePath)) {
|
|
32207
31862
|
try {
|
|
32208
31863
|
const content = readFileSync4(readmePath, "utf-8");
|
|
32209
31864
|
const limit = 1000;
|
|
@@ -32222,7 +31877,7 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
32222
31877
|
if (e.startsWith(".") || e === "node_modules")
|
|
32223
31878
|
return false;
|
|
32224
31879
|
try {
|
|
32225
|
-
return
|
|
31880
|
+
return statSync(join4(this.projectPath, e)).isDirectory();
|
|
32226
31881
|
} catch {
|
|
32227
31882
|
return false;
|
|
32228
31883
|
}
|
|
@@ -32307,162 +31962,6 @@ var init_task_executor = __esm(() => {
|
|
|
32307
31962
|
init_prompt_builder();
|
|
32308
31963
|
});
|
|
32309
31964
|
|
|
32310
|
-
// ../sdk/src/agent/git-workflow.ts
|
|
32311
|
-
class GitWorkflow {
|
|
32312
|
-
config;
|
|
32313
|
-
log;
|
|
32314
|
-
ghUsername;
|
|
32315
|
-
worktreeManager;
|
|
32316
|
-
prService;
|
|
32317
|
-
constructor(config2, log, ghUsername) {
|
|
32318
|
-
this.config = config2;
|
|
32319
|
-
this.log = log;
|
|
32320
|
-
this.ghUsername = ghUsername;
|
|
32321
|
-
const projectPath = config2.projectPath || process.cwd();
|
|
32322
|
-
this.worktreeManager = config2.useWorktrees ? new WorktreeManager(projectPath, { cleanupPolicy: "auto" }, log) : null;
|
|
32323
|
-
this.prService = config2.autoPush ? new PrService(projectPath, log) : null;
|
|
32324
|
-
}
|
|
32325
|
-
createTaskWorktree(task2, defaultExecutor) {
|
|
32326
|
-
if (!this.worktreeManager) {
|
|
32327
|
-
return {
|
|
32328
|
-
worktreePath: null,
|
|
32329
|
-
baseBranch: null,
|
|
32330
|
-
baseCommitHash: null,
|
|
32331
|
-
executor: defaultExecutor
|
|
32332
|
-
};
|
|
32333
|
-
}
|
|
32334
|
-
const slug = task2.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
32335
|
-
const result = this.worktreeManager.create({
|
|
32336
|
-
taskId: task2.id,
|
|
32337
|
-
taskSlug: slug,
|
|
32338
|
-
agentId: this.config.agentId,
|
|
32339
|
-
baseBranch: this.config.baseBranch
|
|
32340
|
-
});
|
|
32341
|
-
this.log(`Worktree created: ${result.worktreePath} (${result.branch})`, "info");
|
|
32342
|
-
const provider = this.config.provider ?? PROVIDER.CLAUDE;
|
|
32343
|
-
const taskAiRunner = createAiRunner(provider, {
|
|
32344
|
-
projectPath: result.worktreePath,
|
|
32345
|
-
model: this.config.model,
|
|
32346
|
-
log: this.log
|
|
32347
|
-
});
|
|
32348
|
-
const taskExecutor = new TaskExecutor({
|
|
32349
|
-
aiRunner: taskAiRunner,
|
|
32350
|
-
projectPath: result.worktreePath,
|
|
32351
|
-
log: this.log
|
|
32352
|
-
});
|
|
32353
|
-
return {
|
|
32354
|
-
worktreePath: result.worktreePath,
|
|
32355
|
-
baseBranch: result.baseBranch,
|
|
32356
|
-
baseCommitHash: result.baseCommitHash,
|
|
32357
|
-
executor: taskExecutor
|
|
32358
|
-
};
|
|
32359
|
-
}
|
|
32360
|
-
commitAndPush(worktreePath, task2, baseBranch, baseCommitHash) {
|
|
32361
|
-
if (!this.worktreeManager) {
|
|
32362
|
-
return { branch: null, pushed: false, pushFailed: false };
|
|
32363
|
-
}
|
|
32364
|
-
try {
|
|
32365
|
-
const trailers = [
|
|
32366
|
-
`Task-ID: ${task2.id}`,
|
|
32367
|
-
`Agent: ${this.config.agentId}`,
|
|
32368
|
-
"Co-authored-by: LocusAI <agent@locusai.team>"
|
|
32369
|
-
];
|
|
32370
|
-
if (this.ghUsername) {
|
|
32371
|
-
trailers.push(`Co-authored-by: ${this.ghUsername} <${this.ghUsername}@users.noreply.github.com>`);
|
|
32372
|
-
}
|
|
32373
|
-
const commitMessage = `feat(agent): ${task2.title}
|
|
32374
|
-
|
|
32375
|
-
${trailers.join(`
|
|
32376
|
-
`)}`;
|
|
32377
|
-
const hash2 = this.worktreeManager.commitChanges(worktreePath, commitMessage, baseBranch, baseCommitHash);
|
|
32378
|
-
if (!hash2) {
|
|
32379
|
-
this.log("No changes to commit for this task", "info");
|
|
32380
|
-
return {
|
|
32381
|
-
branch: null,
|
|
32382
|
-
pushed: false,
|
|
32383
|
-
pushFailed: false,
|
|
32384
|
-
noChanges: true,
|
|
32385
|
-
skipReason: "No changes were committed, so no branch was pushed."
|
|
32386
|
-
};
|
|
32387
|
-
}
|
|
32388
|
-
const localBranch = this.worktreeManager.getBranch(worktreePath);
|
|
32389
|
-
if (this.config.autoPush) {
|
|
32390
|
-
try {
|
|
32391
|
-
return {
|
|
32392
|
-
branch: this.worktreeManager.pushBranch(worktreePath),
|
|
32393
|
-
pushed: true,
|
|
32394
|
-
pushFailed: false
|
|
32395
|
-
};
|
|
32396
|
-
} catch (err) {
|
|
32397
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32398
|
-
this.log(`Git push failed: ${errorMessage}`, "error");
|
|
32399
|
-
return {
|
|
32400
|
-
branch: localBranch,
|
|
32401
|
-
pushed: false,
|
|
32402
|
-
pushFailed: true,
|
|
32403
|
-
pushError: errorMessage
|
|
32404
|
-
};
|
|
32405
|
-
}
|
|
32406
|
-
}
|
|
32407
|
-
this.log("Auto-push disabled; skipping branch push", "info");
|
|
32408
|
-
return {
|
|
32409
|
-
branch: localBranch,
|
|
32410
|
-
pushed: false,
|
|
32411
|
-
pushFailed: false,
|
|
32412
|
-
skipReason: "Auto-push is disabled, so PR creation was skipped."
|
|
32413
|
-
};
|
|
32414
|
-
} catch (err) {
|
|
32415
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32416
|
-
this.log(`Git commit failed: ${errorMessage}`, "error");
|
|
32417
|
-
return {
|
|
32418
|
-
branch: null,
|
|
32419
|
-
pushed: false,
|
|
32420
|
-
pushFailed: true,
|
|
32421
|
-
pushError: `Git commit/push failed: ${errorMessage}`
|
|
32422
|
-
};
|
|
32423
|
-
}
|
|
32424
|
-
}
|
|
32425
|
-
createPullRequest(task2, branch, summary, baseBranch) {
|
|
32426
|
-
if (!this.prService) {
|
|
32427
|
-
const errorMessage = "PR service is not initialized. Enable auto-push to allow PR creation.";
|
|
32428
|
-
this.log(`PR creation skipped: ${errorMessage}`, "warn");
|
|
32429
|
-
return { url: null, error: errorMessage };
|
|
32430
|
-
}
|
|
32431
|
-
this.log(`Attempting PR creation from branch: ${branch}`, "info");
|
|
32432
|
-
try {
|
|
32433
|
-
const result = this.prService.createPr({
|
|
32434
|
-
task: task2,
|
|
32435
|
-
branch,
|
|
32436
|
-
baseBranch,
|
|
32437
|
-
agentId: this.config.agentId,
|
|
32438
|
-
summary
|
|
32439
|
-
});
|
|
32440
|
-
return { url: result.url };
|
|
32441
|
-
} catch (err) {
|
|
32442
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
32443
|
-
this.log(`PR creation failed: ${errorMessage}`, "error");
|
|
32444
|
-
return { url: null, error: errorMessage };
|
|
32445
|
-
}
|
|
32446
|
-
}
|
|
32447
|
-
cleanupWorktree(worktreePath, keepBranch) {
|
|
32448
|
-
if (!this.worktreeManager || !worktreePath)
|
|
32449
|
-
return;
|
|
32450
|
-
try {
|
|
32451
|
-
this.worktreeManager.remove(worktreePath, !keepBranch);
|
|
32452
|
-
this.log(keepBranch ? "Worktree cleaned up (branch preserved)" : "Worktree cleaned up", "info");
|
|
32453
|
-
} catch {
|
|
32454
|
-
this.log(`Could not clean up worktree: ${worktreePath}`, "warn");
|
|
32455
|
-
}
|
|
32456
|
-
}
|
|
32457
|
-
}
|
|
32458
|
-
var init_git_workflow = __esm(() => {
|
|
32459
|
-
init_factory();
|
|
32460
|
-
init_config();
|
|
32461
|
-
init_pr_service();
|
|
32462
|
-
init_worktree_manager();
|
|
32463
|
-
init_task_executor();
|
|
32464
|
-
});
|
|
32465
|
-
|
|
32466
31965
|
// ../sdk/src/agent/worker-cli.ts
|
|
32467
31966
|
var exports_worker_cli = {};
|
|
32468
31967
|
__export(exports_worker_cli, {
|
|
@@ -32495,16 +31994,8 @@ function parseWorkerArgs(argv) {
|
|
|
32495
31994
|
config2.apiKey = args[++i];
|
|
32496
31995
|
else if (arg === "--project-path")
|
|
32497
31996
|
config2.projectPath = args[++i];
|
|
32498
|
-
else if (arg === "--main-project-path")
|
|
32499
|
-
config2.mainProjectPath = args[++i];
|
|
32500
31997
|
else if (arg === "--model")
|
|
32501
31998
|
config2.model = args[++i];
|
|
32502
|
-
else if (arg === "--use-worktrees")
|
|
32503
|
-
config2.useWorktrees = true;
|
|
32504
|
-
else if (arg === "--auto-push")
|
|
32505
|
-
config2.autoPush = true;
|
|
32506
|
-
else if (arg === "--base-branch")
|
|
32507
|
-
config2.baseBranch = args[++i];
|
|
32508
31999
|
else if (arg === "--provider") {
|
|
32509
32000
|
const value = args[i + 1];
|
|
32510
32001
|
if (value && !value.startsWith("--"))
|
|
@@ -32546,8 +32037,8 @@ class AgentWorker {
|
|
|
32546
32037
|
tasksCompleted = 0;
|
|
32547
32038
|
heartbeatInterval = null;
|
|
32548
32039
|
currentTaskId = null;
|
|
32549
|
-
|
|
32550
|
-
|
|
32040
|
+
completedTaskList = [];
|
|
32041
|
+
taskSummaries = [];
|
|
32551
32042
|
constructor(config2) {
|
|
32552
32043
|
this.config = config2;
|
|
32553
32044
|
const projectPath = config2.projectPath || process.cwd();
|
|
@@ -32562,17 +32053,12 @@ class AgentWorker {
|
|
|
32562
32053
|
}
|
|
32563
32054
|
});
|
|
32564
32055
|
const log = this.log.bind(this);
|
|
32565
|
-
if (
|
|
32566
|
-
this.log("git is not installed —
|
|
32567
|
-
config2.useWorktrees = false;
|
|
32056
|
+
if (!isGitAvailable()) {
|
|
32057
|
+
this.log("git is not installed — branch management will not work", "error");
|
|
32568
32058
|
}
|
|
32569
|
-
if (
|
|
32059
|
+
if (!isGhAvailable(projectPath)) {
|
|
32570
32060
|
this.log("GitHub CLI (gh) not available or not authenticated. Branch push can continue, but automatic PR creation may fail until gh is configured. Install from https://cli.github.com/", "warn");
|
|
32571
32061
|
}
|
|
32572
|
-
const ghUsername = config2.autoPush ? getGhUsername() : null;
|
|
32573
|
-
if (ghUsername) {
|
|
32574
|
-
this.log(`GitHub user: ${ghUsername}`, "info");
|
|
32575
|
-
}
|
|
32576
32062
|
const provider = config2.provider ?? PROVIDER.CLAUDE;
|
|
32577
32063
|
this.aiRunner = createAiRunner(provider, {
|
|
32578
32064
|
projectPath,
|
|
@@ -32585,18 +32071,9 @@ class AgentWorker {
|
|
|
32585
32071
|
log
|
|
32586
32072
|
});
|
|
32587
32073
|
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
32588
|
-
this.gitWorkflow = new GitWorkflow(config2, log
|
|
32074
|
+
this.gitWorkflow = new GitWorkflow(config2, log);
|
|
32589
32075
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
32590
32076
|
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
32591
|
-
if (config2.useWorktrees) {
|
|
32592
|
-
this.log("Per-task worktree isolation enabled", "info");
|
|
32593
|
-
if (config2.baseBranch) {
|
|
32594
|
-
this.log(`Base branch for worktrees: ${config2.baseBranch}`, "info");
|
|
32595
|
-
}
|
|
32596
|
-
if (config2.autoPush) {
|
|
32597
|
-
this.log("Auto-push enabled: branches will be pushed to remote", "info");
|
|
32598
|
-
}
|
|
32599
|
-
}
|
|
32600
32077
|
}
|
|
32601
32078
|
log(message, level = "info") {
|
|
32602
32079
|
const timestamp = new Date().toISOString().split("T")[1]?.slice(0, 8) ?? "";
|
|
@@ -32645,56 +32122,26 @@ class AgentWorker {
|
|
|
32645
32122
|
}
|
|
32646
32123
|
async executeTask(task2) {
|
|
32647
32124
|
const fullTask = await this.client.tasks.getById(task2.id, this.config.workspaceId);
|
|
32648
|
-
const { worktreePath, baseBranch, baseCommitHash, executor } = this.gitWorkflow.createTaskWorktree(fullTask, this.taskExecutor);
|
|
32649
|
-
this.currentWorktreePath = worktreePath;
|
|
32650
|
-
let branchPushed = false;
|
|
32651
|
-
let keepBranch = false;
|
|
32652
|
-
let preserveWorktree = false;
|
|
32653
32125
|
try {
|
|
32654
|
-
const result = await
|
|
32655
|
-
let taskBranch = null;
|
|
32656
|
-
let prUrl = null;
|
|
32657
|
-
let prError = null;
|
|
32126
|
+
const result = await this.taskExecutor.execute(fullTask);
|
|
32658
32127
|
let noChanges = false;
|
|
32659
|
-
|
|
32660
|
-
|
|
32128
|
+
let taskBranch = null;
|
|
32129
|
+
if (result.success) {
|
|
32130
|
+
const commitResult = this.gitWorkflow.commitAndPush(fullTask);
|
|
32661
32131
|
taskBranch = commitResult.branch;
|
|
32662
|
-
branchPushed = commitResult.pushed;
|
|
32663
|
-
keepBranch = taskBranch !== null;
|
|
32664
32132
|
noChanges = Boolean(commitResult.noChanges);
|
|
32665
|
-
if (commitResult.pushFailed) {
|
|
32666
|
-
preserveWorktree = true;
|
|
32667
|
-
prError = commitResult.pushError ?? "Git push failed before PR creation. Please retry manually.";
|
|
32668
|
-
this.log(`Preserving worktree after push failure: ${worktreePath}`, "warn");
|
|
32669
|
-
}
|
|
32670
|
-
if (branchPushed && taskBranch) {
|
|
32671
|
-
const prResult = this.gitWorkflow.createPullRequest(fullTask, taskBranch, result.summary, baseBranch ?? undefined);
|
|
32672
|
-
prUrl = prResult.url;
|
|
32673
|
-
prError = prResult.error ?? null;
|
|
32674
|
-
if (!prUrl) {
|
|
32675
|
-
preserveWorktree = true;
|
|
32676
|
-
this.log(`Preserving worktree for manual follow-up: ${worktreePath}`, "warn");
|
|
32677
|
-
}
|
|
32678
|
-
} else if (commitResult.skipReason) {
|
|
32679
|
-
this.log(`Skipping PR creation: ${commitResult.skipReason}`, "info");
|
|
32680
|
-
}
|
|
32681
|
-
} else if (result.success && !worktreePath) {
|
|
32682
|
-
this.log("Skipping commit/push/PR flow because no task worktree is active.", "warn");
|
|
32683
32133
|
}
|
|
32684
32134
|
return {
|
|
32685
32135
|
...result,
|
|
32686
32136
|
branch: taskBranch ?? undefined,
|
|
32687
|
-
prUrl: prUrl ?? undefined,
|
|
32688
|
-
prError: prError ?? undefined,
|
|
32689
32137
|
noChanges: noChanges || undefined
|
|
32690
32138
|
};
|
|
32691
|
-
}
|
|
32692
|
-
|
|
32693
|
-
|
|
32694
|
-
|
|
32695
|
-
|
|
32696
|
-
|
|
32697
|
-
}
|
|
32139
|
+
} catch (err) {
|
|
32140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
32141
|
+
return {
|
|
32142
|
+
success: false,
|
|
32143
|
+
summary: `Execution error: ${msg}`
|
|
32144
|
+
};
|
|
32698
32145
|
}
|
|
32699
32146
|
}
|
|
32700
32147
|
updateProgress(task2, summary) {
|
|
@@ -32727,19 +32174,13 @@ class AgentWorker {
|
|
|
32727
32174
|
this.log(`Heartbeat failed: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
32728
32175
|
});
|
|
32729
32176
|
}
|
|
32730
|
-
async delayAfterCleanup() {
|
|
32731
|
-
if (!this.config.useWorktrees || this.postCleanupDelayMs <= 0)
|
|
32732
|
-
return;
|
|
32733
|
-
this.log(`Waiting ${Math.floor(this.postCleanupDelayMs / 1000)}s after worktree cleanup before next dispatch`, "info");
|
|
32734
|
-
await new Promise((resolve3) => setTimeout(resolve3, this.postCleanupDelayMs));
|
|
32735
|
-
}
|
|
32736
32177
|
async run() {
|
|
32737
32178
|
this.log(`Agent started in ${this.config.projectPath || process.cwd()}`, "success");
|
|
32738
32179
|
const handleShutdown = () => {
|
|
32739
32180
|
this.log("Received shutdown signal. Aborting...", "warn");
|
|
32740
32181
|
this.aiRunner.abort();
|
|
32741
32182
|
this.stopHeartbeat();
|
|
32742
|
-
this.gitWorkflow.
|
|
32183
|
+
this.gitWorkflow.checkoutBaseBranch();
|
|
32743
32184
|
process.exit(1);
|
|
32744
32185
|
};
|
|
32745
32186
|
process.on("SIGTERM", handleShutdown);
|
|
@@ -32751,10 +32192,12 @@ class AgentWorker {
|
|
|
32751
32192
|
} else {
|
|
32752
32193
|
this.log("No active sprint found.", "warn");
|
|
32753
32194
|
}
|
|
32195
|
+
const branchName = this.gitWorkflow.createBranch(this.config.sprintId);
|
|
32196
|
+
this.log(`Working on branch: ${branchName}`, "info");
|
|
32754
32197
|
while (this.tasksCompleted < this.maxTasks) {
|
|
32755
32198
|
const task2 = await this.getNextTask();
|
|
32756
32199
|
if (!task2) {
|
|
32757
|
-
this.log("No more tasks to process.
|
|
32200
|
+
this.log("No more tasks to process.", "info");
|
|
32758
32201
|
break;
|
|
32759
32202
|
}
|
|
32760
32203
|
this.log(`Claimed: ${task2.title}`, "success");
|
|
@@ -32770,7 +32213,7 @@ class AgentWorker {
|
|
|
32770
32213
|
});
|
|
32771
32214
|
await this.client.tasks.addComment(task2.id, this.config.workspaceId, {
|
|
32772
32215
|
author: this.config.agentId,
|
|
32773
|
-
text: `⚠️ Agent execution finished with no file changes, so no commit
|
|
32216
|
+
text: `⚠️ Agent execution finished with no file changes, so no commit was created.
|
|
32774
32217
|
|
|
32775
32218
|
${result.summary}`
|
|
32776
32219
|
});
|
|
@@ -32779,22 +32222,17 @@ ${result.summary}`
|
|
|
32779
32222
|
const updatePayload = {
|
|
32780
32223
|
status: "IN_REVIEW" /* IN_REVIEW */
|
|
32781
32224
|
};
|
|
32782
|
-
if (result.prUrl) {
|
|
32783
|
-
updatePayload.prUrl = result.prUrl;
|
|
32784
|
-
}
|
|
32785
32225
|
await this.client.tasks.update(task2.id, this.config.workspaceId, updatePayload);
|
|
32786
32226
|
const branchInfo = result.branch ? `
|
|
32787
32227
|
|
|
32788
32228
|
Branch: \`${result.branch}\`` : "";
|
|
32789
|
-
const prInfo = result.prUrl ? `
|
|
32790
|
-
PR: ${result.prUrl}` : "";
|
|
32791
|
-
const prErrorInfo = result.prError ? `
|
|
32792
|
-
PR automation error: ${result.prError}` : "";
|
|
32793
32229
|
await this.client.tasks.addComment(task2.id, this.config.workspaceId, {
|
|
32794
32230
|
author: this.config.agentId,
|
|
32795
|
-
text: `✅ ${result.summary}${branchInfo}
|
|
32231
|
+
text: `✅ ${result.summary}${branchInfo}`
|
|
32796
32232
|
});
|
|
32797
32233
|
this.tasksCompleted++;
|
|
32234
|
+
this.completedTaskList.push({ title: task2.title, id: task2.id });
|
|
32235
|
+
this.taskSummaries.push(result.summary);
|
|
32798
32236
|
this.updateProgress(task2, result.summary);
|
|
32799
32237
|
}
|
|
32800
32238
|
} else {
|
|
@@ -32810,8 +32248,24 @@ PR automation error: ${result.prError}` : "";
|
|
|
32810
32248
|
}
|
|
32811
32249
|
this.currentTaskId = null;
|
|
32812
32250
|
this.sendHeartbeat();
|
|
32813
|
-
await this.delayAfterCleanup();
|
|
32814
32251
|
}
|
|
32252
|
+
if (this.completedTaskList.length > 0) {
|
|
32253
|
+
this.log("All tasks done. Creating pull request...", "info");
|
|
32254
|
+
const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
|
|
32255
|
+
if (prResult.url) {
|
|
32256
|
+
this.log(`PR created: ${prResult.url}`, "success");
|
|
32257
|
+
for (const task2 of this.completedTaskList) {
|
|
32258
|
+
try {
|
|
32259
|
+
await this.client.tasks.update(task2.id, this.config.workspaceId, {
|
|
32260
|
+
prUrl: prResult.url
|
|
32261
|
+
});
|
|
32262
|
+
} catch {}
|
|
32263
|
+
}
|
|
32264
|
+
} else if (prResult.error) {
|
|
32265
|
+
this.log(`PR creation failed: ${prResult.error}`, "error");
|
|
32266
|
+
}
|
|
32267
|
+
}
|
|
32268
|
+
this.gitWorkflow.checkoutBaseBranch();
|
|
32815
32269
|
this.currentTaskId = null;
|
|
32816
32270
|
this.stopHeartbeat();
|
|
32817
32271
|
this.client.workspaces.heartbeat(this.config.workspaceId, this.config.agentId, null, "COMPLETED").catch(() => {});
|