@astroanywhere/agent 0.3.2 → 0.3.4
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/lib/git-pr.d.ts +59 -10
- package/dist/lib/git-pr.d.ts.map +1 -1
- package/dist/lib/git-pr.js +140 -30
- package/dist/lib/git-pr.js.map +1 -1
- package/dist/lib/local-merge.d.ts +3 -1
- package/dist/lib/local-merge.d.ts.map +1 -1
- package/dist/lib/local-merge.js +29 -7
- package/dist/lib/local-merge.js.map +1 -1
- package/dist/lib/task-executor.d.ts.map +1 -1
- package/dist/lib/task-executor.js +74 -38
- package/dist/lib/task-executor.js.map +1 -1
- package/dist/lib/websocket-client.d.ts +2 -2
- package/dist/lib/websocket-client.d.ts.map +1 -1
- package/dist/lib/websocket-client.js +4 -4
- package/dist/lib/websocket-client.js.map +1 -1
- package/dist/lib/worktree.js +4 -4
- package/dist/lib/worktree.js.map +1 -1
- package/dist/providers/base-adapter.d.ts +2 -2
- package/dist/providers/base-adapter.d.ts.map +1 -1
- package/dist/providers/claude-sdk-adapter.d.ts.map +1 -1
- package/dist/providers/claude-sdk-adapter.js +13 -5
- package/dist/providers/claude-sdk-adapter.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/lib/task-executor.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAOH,OAAO,KAAK,EAAE,IAAI,EAAwC,aAAa,EAAE,MAAM,aAAa,CAAC;AAC7F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"task-executor.d.ts","sourceRoot":"","sources":["../../src/lib/task-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@ import { SlurmJobMonitor } from './slurm-job-monitor.js';
|
|
|
16
16
|
import { createWorktree, syncProjectWorktree } from './worktree.js';
|
|
17
17
|
import { ensureProjectWorkspace } from './workspace-root.js';
|
|
18
18
|
import { BranchLockManager } from './branch-lock.js';
|
|
19
|
-
import { pushAndCreatePR, mergePullRequest, getRemoteBranchSha, isGhAvailable } from './git-pr.js';
|
|
19
|
+
import { pushAndCreatePR, mergePullRequest, getRemoteBranchSha, isGhAvailable, getRepoSlug } from './git-pr.js';
|
|
20
20
|
import { localMergeIntoProjectBranch } from './local-merge.js';
|
|
21
21
|
import { checkWorkdirSafety, isGitAvailable, isGitRepo, isUntrackedInParentRepo, createSandbox, WorkdirSafetyTier, } from './workdir-safety.js';
|
|
22
22
|
import { initializeGit } from './git-bootstrap.js';
|
|
@@ -94,22 +94,45 @@ After you force-push, I will automatically retry the GitHub merge.`;
|
|
|
94
94
|
* existing merge retry loop handles real conflicts.
|
|
95
95
|
*/
|
|
96
96
|
async function tryPreMergeRebase(workdir, targetBranch, isRemote) {
|
|
97
|
+
// `git rebase` always operates on the currently checked-out branch (HEAD).
|
|
98
|
+
// It cannot rebase a detached branch ref. When the worktree is gone, HEAD
|
|
99
|
+
// in gitRoot is the user's main checkout (e.g., main) — NOT the task branch.
|
|
100
|
+
// Rebasing from gitRoot would corrupt the user's working directory.
|
|
101
|
+
// Skip the rebase entirely; the merge/PR flow handles conflicts anyway.
|
|
102
|
+
//
|
|
103
|
+
// Best-effort check — worktree could still be deleted after this (TOCTOU),
|
|
104
|
+
// but the git commands will fail safely and be caught by the outer catch.
|
|
105
|
+
try {
|
|
106
|
+
statSync(workdir);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
console.log(`[git] Worktree at ${workdir} gone — skipping rebase (cannot rebase non-checked-out branch)`);
|
|
110
|
+
return { rebased: false, skipped: true };
|
|
111
|
+
}
|
|
97
112
|
try {
|
|
98
113
|
const rebaseTarget = isRemote ? `origin/${targetBranch}` : targetBranch;
|
|
114
|
+
const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' };
|
|
99
115
|
if (isRemote) {
|
|
100
|
-
|
|
116
|
+
console.log(`[git] fetch origin ${targetBranch} (cwd: ${workdir})`);
|
|
117
|
+
await execFileAsync('git', ['fetch', 'origin', targetBranch], { cwd: workdir, env: gitEnv, timeout: 30_000 });
|
|
101
118
|
}
|
|
102
119
|
// Check if rebase is needed (target branch moved since we branched)
|
|
103
|
-
|
|
104
|
-
const { stdout:
|
|
120
|
+
console.log(`[git] merge-base HEAD ${rebaseTarget} (cwd: ${workdir})`);
|
|
121
|
+
const { stdout: mergeBase } = await execFileAsync('git', ['merge-base', 'HEAD', rebaseTarget], { cwd: workdir, env: gitEnv });
|
|
122
|
+
const { stdout: targetTip } = await execFileAsync('git', ['rev-parse', rebaseTarget], { cwd: workdir, env: gitEnv });
|
|
105
123
|
if (mergeBase.trim() === targetTip.trim()) {
|
|
106
|
-
|
|
124
|
+
console.log(`[git] Already up to date (merge-base = target tip: ${targetTip.trim().slice(0, 7)})`);
|
|
125
|
+
return { rebased: false, skipped: true };
|
|
107
126
|
}
|
|
108
127
|
// Try automatic rebase — timeout after 60s (should be fast for non-conflicting changes)
|
|
109
|
-
|
|
128
|
+
console.log(`[git] rebase ${rebaseTarget} (cwd: ${workdir})`);
|
|
129
|
+
await execFileAsync('git', ['rebase', rebaseTarget], { cwd: workdir, env: gitEnv, timeout: 60_000 });
|
|
130
|
+
console.log(`[git] Rebase onto ${rebaseTarget} succeeded`);
|
|
110
131
|
return { rebased: true };
|
|
111
132
|
}
|
|
112
|
-
catch {
|
|
133
|
+
catch (err) {
|
|
134
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
135
|
+
console.warn(`[git] Rebase onto ${targetBranch} failed: ${msg}`);
|
|
113
136
|
// Abort on failure — existing retry loop will handle conflicts
|
|
114
137
|
await execFileAsync('git', ['rebase', '--abort'], { cwd: workdir }).catch(() => { });
|
|
115
138
|
return { rebased: false };
|
|
@@ -571,11 +594,11 @@ export class TaskExecutor {
|
|
|
571
594
|
text: (data) => {
|
|
572
595
|
this.wsClient.sendTaskText(taskId, data, textSequence++);
|
|
573
596
|
},
|
|
574
|
-
toolUse: (toolName, toolInput) => {
|
|
575
|
-
this.wsClient.sendTaskToolUse(taskId, toolName, toolInput);
|
|
597
|
+
toolUse: (toolName, toolInput, toolUseId) => {
|
|
598
|
+
this.wsClient.sendTaskToolUse(taskId, toolName, toolInput, toolUseId);
|
|
576
599
|
},
|
|
577
|
-
toolResult: (toolName, result, success) => {
|
|
578
|
-
this.wsClient.sendTaskToolResult(taskId, toolName, result, success);
|
|
600
|
+
toolResult: (toolName, result, success, toolUseId) => {
|
|
601
|
+
this.wsClient.sendTaskToolResult(taskId, toolName, result, success, toolUseId);
|
|
579
602
|
},
|
|
580
603
|
fileChange: (path, action, linesAdded, linesRemoved, diff) => {
|
|
581
604
|
this.wsClient.sendTaskFileChange(taskId, path, action, linesAdded, linesRemoved, diff);
|
|
@@ -902,13 +925,13 @@ export class TaskExecutor {
|
|
|
902
925
|
resetIdleTimeout();
|
|
903
926
|
this.wsClient.sendTaskText(normalizedTask.id, data, textSequence++);
|
|
904
927
|
},
|
|
905
|
-
toolUse: (toolName, toolInput) => {
|
|
928
|
+
toolUse: (toolName, toolInput, toolUseId) => {
|
|
906
929
|
resetIdleTimeout();
|
|
907
|
-
this.wsClient.sendTaskToolUse(normalizedTask.id, toolName, toolInput);
|
|
930
|
+
this.wsClient.sendTaskToolUse(normalizedTask.id, toolName, toolInput, toolUseId);
|
|
908
931
|
},
|
|
909
|
-
toolResult: (toolName, result, success) => {
|
|
932
|
+
toolResult: (toolName, result, success, toolUseId) => {
|
|
910
933
|
resetIdleTimeout();
|
|
911
|
-
this.wsClient.sendTaskToolResult(normalizedTask.id, toolName, result, success);
|
|
934
|
+
this.wsClient.sendTaskToolResult(normalizedTask.id, toolName, result, success, toolUseId);
|
|
912
935
|
},
|
|
913
936
|
fileChange: (path, action, linesAdded, linesRemoved, diff) => {
|
|
914
937
|
resetIdleTimeout();
|
|
@@ -947,14 +970,15 @@ export class TaskExecutor {
|
|
|
947
970
|
// 30s aligns with the server's heartbeat check interval and is well under
|
|
948
971
|
// the 3-minute startup timeout (STARTUP_TIMEOUT_MS in dispatch.ts).
|
|
949
972
|
const TASK_HEARTBEAT_INTERVAL_MS = 30_000;
|
|
950
|
-
let taskHeartbeatPhase = 'preparing';
|
|
951
973
|
let heartbeatSeq = 0;
|
|
952
974
|
const taskHeartbeatTimer = setInterval(() => {
|
|
953
975
|
// Send directly via wsClient to keep the server's activity timer alive
|
|
954
976
|
// WITHOUT resetting the agent-side idle timeout. stream.text() calls
|
|
955
977
|
// resetIdleTimeout(), which would make a hung agent run until hard cap
|
|
956
978
|
// instead of idle-timing out after 15 minutes of no real activity.
|
|
957
|
-
|
|
979
|
+
// Heartbeat text is intentionally empty — it exists only to keep the
|
|
980
|
+
// server's activity timer alive, not to display anything to the user.
|
|
981
|
+
this.wsClient.sendTaskText(normalizedTask.id, '', -(++heartbeatSeq));
|
|
958
982
|
}, TASK_HEARTBEAT_INTERVAL_MS);
|
|
959
983
|
// Text-only tasks (plan/chat/summarize) without a working directory skip workspace prep
|
|
960
984
|
const isTextOnly = normalizedTask.type === 'summarize' || normalizedTask.type === 'chat' || normalizedTask.type === 'plan';
|
|
@@ -975,7 +999,6 @@ export class TaskExecutor {
|
|
|
975
999
|
}
|
|
976
1000
|
const taskWithWorkspace = { ...normalizedTask, workingDirectory: prepared.workingDirectory };
|
|
977
1001
|
runningTask.task = taskWithWorkspace;
|
|
978
|
-
taskHeartbeatPhase = 'executing';
|
|
979
1002
|
console.log(`[executor] Task ${task.id}: workspace prepared, cwd=${prepared.workingDirectory}`);
|
|
980
1003
|
// Execute with idle timeout + hard cap.
|
|
981
1004
|
// Idle timeout resets on every stream activity (text, tool, file, etc.).
|
|
@@ -1078,9 +1101,12 @@ export class TaskExecutor {
|
|
|
1078
1101
|
? `[${task.shortProjectId}/${task.shortNodeId}] ${rawTitle}`
|
|
1079
1102
|
: rawTitle;
|
|
1080
1103
|
if (prepared.branchName && result.status === 'completed') {
|
|
1081
|
-
|
|
1082
|
-
stream.text?.(`\n[Astro] Delivering changes (mode: ${deliveryMode})...\n`);
|
|
1104
|
+
stream.text?.(`\n── [Astro] Delivering changes (mode: ${deliveryMode})...\n`);
|
|
1083
1105
|
this.wsClient.sendTaskStatus(task.id, 'running', 90, 'Delivering changes...');
|
|
1106
|
+
// Resolve GitHub repo slug (OWNER/REPO) for explicit gh --repo targeting.
|
|
1107
|
+
// This makes gh pr create/merge independent of local filesystem state
|
|
1108
|
+
// (worktree may be cleaned up by the agent during execution).
|
|
1109
|
+
const repoSlug = prepared.gitRoot ? await getRepoSlug(prepared.gitRoot) : undefined;
|
|
1084
1110
|
// Build PR body: enrich with summary data when available
|
|
1085
1111
|
const prBodyParts = [];
|
|
1086
1112
|
if (summary) {
|
|
@@ -1113,14 +1139,16 @@ export class TaskExecutor {
|
|
|
1113
1139
|
try {
|
|
1114
1140
|
if (deliveryMode === 'direct') {
|
|
1115
1141
|
// No git delivery — files modified in-place
|
|
1142
|
+
stream.text?.(`── [Delivery] Changes applied in-place (direct mode)\n`);
|
|
1116
1143
|
console.log(`[executor] Task ${task.id}: direct mode, skipping git delivery`);
|
|
1117
1144
|
}
|
|
1118
1145
|
else if (deliveryMode === 'copy') {
|
|
1119
1146
|
// Copy mode: worktree preserved, no git operations
|
|
1147
|
+
stream.text?.(`── [Delivery] Worktree preserved (copy mode)\n`);
|
|
1120
1148
|
console.log(`[executor] Task ${task.id}: copy mode, worktree preserved at ${prepared.workingDirectory}`);
|
|
1121
1149
|
}
|
|
1122
1150
|
else if (deliveryMode === 'branch') {
|
|
1123
|
-
stream.text?.(
|
|
1151
|
+
stream.text?.(`── [Git] Merging into project branch ${prepared.projectBranch ?? 'local'}...\n`);
|
|
1124
1152
|
// Branch mode: commit locally, merge into project branch if available.
|
|
1125
1153
|
// The merge lock is held only during the squash-merge (seconds, not minutes),
|
|
1126
1154
|
// allowing tasks to execute in parallel. The squash merge naturally handles
|
|
@@ -1138,9 +1166,11 @@ export class TaskExecutor {
|
|
|
1138
1166
|
// don't overlap, and saves one retry cycle when they do.
|
|
1139
1167
|
const preRebase = await tryPreMergeRebase(prepared.workingDirectory, prepared.projectBranch, false);
|
|
1140
1168
|
if (preRebase.rebased) {
|
|
1169
|
+
stream.text?.(`── [Git] Rebased onto ${prepared.projectBranch}\n`);
|
|
1141
1170
|
console.log(`[executor] Task ${task.id}: pre-merge rebase onto ${prepared.projectBranch} succeeded`);
|
|
1142
1171
|
}
|
|
1143
1172
|
else if (!preRebase.skipped) {
|
|
1173
|
+
stream.text?.(`── [Git] Rebase skipped (conflicts), retrying via merge\n`);
|
|
1144
1174
|
console.log(`[executor] Task ${task.id}: pre-merge rebase had conflicts, falling back to merge retry loop`);
|
|
1145
1175
|
}
|
|
1146
1176
|
const mergeLockKey = BranchLockManager.computeLockKey(prepared.gitRoot, task.shortProjectId, task.shortNodeId, task.id);
|
|
@@ -1153,7 +1183,7 @@ export class TaskExecutor {
|
|
|
1153
1183
|
let mergeResult;
|
|
1154
1184
|
try {
|
|
1155
1185
|
this.wsClient.sendTaskStatus(task.id, 'running', 96, 'Merging into project branch...');
|
|
1156
|
-
mergeResult = await localMergeIntoProjectBranch(prepared.gitRoot, prepared.branchName, prepared.projectBranch, commitMessage);
|
|
1186
|
+
mergeResult = await localMergeIntoProjectBranch(prepared.gitRoot, prepared.branchName, prepared.projectBranch, commitMessage, (msg) => stream.text?.(`── [Git] ${msg}\n`));
|
|
1157
1187
|
}
|
|
1158
1188
|
finally {
|
|
1159
1189
|
mergeLock.release();
|
|
@@ -1162,7 +1192,7 @@ export class TaskExecutor {
|
|
|
1162
1192
|
if (mergeResult.merged) {
|
|
1163
1193
|
result.deliveryStatus = 'success';
|
|
1164
1194
|
result.commitAfterSha = mergeResult.commitSha;
|
|
1165
|
-
stream.text?.(
|
|
1195
|
+
stream.text?.(`── [Git] Merged into ${prepared.projectBranch} (${mergeResult.commitSha?.slice(0, 7)})\n`);
|
|
1166
1196
|
console.log(`[executor] Task ${task.id}: merged into ${prepared.projectBranch} (${mergeResult.commitSha})`);
|
|
1167
1197
|
// Sync project worktree to reflect the merged changes on disk
|
|
1168
1198
|
if (prepared.projectWorktreePath && prepared.projectBranch && prepared.gitRoot) {
|
|
@@ -1178,7 +1208,7 @@ export class TaskExecutor {
|
|
|
1178
1208
|
: null;
|
|
1179
1209
|
if (taskContext?.sessionId && this.isResumableAdapter(adapter) && attempt < MAX_MERGE_ATTEMPTS) {
|
|
1180
1210
|
const conflictFiles = mergeResult.conflictFiles?.join(', ') ?? 'unknown files';
|
|
1181
|
-
stream.text?.(
|
|
1211
|
+
stream.text?.(`── [Git] Merge conflict: ${conflictFiles} — agent resolving (attempt ${attempt})...\n`);
|
|
1182
1212
|
console.log(`[executor] Task ${task.id}: merge conflict (attempt ${attempt}), resuming ${adapter.name} to resolve: ${conflictFiles}`);
|
|
1183
1213
|
this.wsClient.sendTaskStatus(task.id, 'running', 97, `Merge conflict — agent resolving (attempt ${attempt})...`);
|
|
1184
1214
|
// Resume agent session with conflict resolution instructions.
|
|
@@ -1227,7 +1257,7 @@ export class TaskExecutor {
|
|
|
1227
1257
|
}
|
|
1228
1258
|
else if (deliveryMode === 'push') {
|
|
1229
1259
|
// Push branch to remote, but don't create a PR — user creates PR manually
|
|
1230
|
-
stream.text?.(
|
|
1260
|
+
stream.text?.(`── [Git] Pushing ${prepared.branchName} to origin...\n`);
|
|
1231
1261
|
this.wsClient.sendTaskStatus(task.id, 'running', 95, 'Pushing branch...');
|
|
1232
1262
|
console.log(`[executor] Task ${task.id}: push mode, pushing branch ${prepared.branchName}`);
|
|
1233
1263
|
const prResult = await pushAndCreatePR(prepared.workingDirectory, {
|
|
@@ -1236,30 +1266,31 @@ export class TaskExecutor {
|
|
|
1236
1266
|
taskDescription: task.description || task.prompt.slice(0, 500),
|
|
1237
1267
|
skipPR: true,
|
|
1238
1268
|
baseBranch: prepared.baseBranch,
|
|
1269
|
+
gitRoot: prepared.gitRoot,
|
|
1239
1270
|
});
|
|
1240
1271
|
result.branchName = prResult.branchName;
|
|
1241
1272
|
if (prResult.error) {
|
|
1242
1273
|
// Delivery failure — don't override execution status
|
|
1243
1274
|
result.deliveryStatus = 'failed';
|
|
1244
1275
|
result.deliveryError = `Push delivery failed: ${prResult.error}`;
|
|
1245
|
-
stream.text?.(
|
|
1276
|
+
stream.text?.(`── [Git] Push failed: ${prResult.error}\n`);
|
|
1246
1277
|
console.error(`[executor] Task ${task.id}: push delivery failed: ${prResult.error}`);
|
|
1247
1278
|
}
|
|
1248
1279
|
else if (prResult.pushed) {
|
|
1249
1280
|
result.deliveryStatus = 'success';
|
|
1250
1281
|
keepBranch = true;
|
|
1251
|
-
stream.text?.(
|
|
1282
|
+
stream.text?.(`── [Git] Pushed to origin: ${prepared.branchName}\n`);
|
|
1252
1283
|
console.log(`[executor] Task ${task.id}: branch pushed (${prepared.branchName})`);
|
|
1253
1284
|
}
|
|
1254
1285
|
else {
|
|
1255
1286
|
result.deliveryStatus = 'skipped';
|
|
1256
|
-
stream.text?.(
|
|
1287
|
+
stream.text?.(`── [Git] No changes to push\n`);
|
|
1257
1288
|
console.log(`[executor] Task ${task.id}: no changes to push`);
|
|
1258
1289
|
}
|
|
1259
1290
|
}
|
|
1260
1291
|
else {
|
|
1261
1292
|
// 'pr' — push + create PR, auto-merge into project branch if applicable
|
|
1262
|
-
stream.text?.(
|
|
1293
|
+
stream.text?.(`── [Git] Creating PR: ${prepared.branchName} → ${prepared.baseBranch ?? 'main'}...\n`);
|
|
1263
1294
|
this.wsClient.sendTaskStatus(task.id, 'running', 94, 'Rebasing before push...');
|
|
1264
1295
|
// Pre-push rebase: if the target branch moved forward, rebase our task
|
|
1265
1296
|
// branch so the PR will be cleanly mergeable. The branch hasn't been
|
|
@@ -1267,9 +1298,11 @@ export class TaskExecutor {
|
|
|
1267
1298
|
if (prepared.baseBranch) {
|
|
1268
1299
|
const preRebase = await tryPreMergeRebase(prepared.workingDirectory, prepared.baseBranch, true);
|
|
1269
1300
|
if (preRebase.rebased) {
|
|
1301
|
+
stream.text?.(`── [Git] Rebased onto origin/${prepared.baseBranch}\n`);
|
|
1270
1302
|
console.log(`[executor] Task ${task.id}: pre-push rebase onto origin/${prepared.baseBranch} succeeded`);
|
|
1271
1303
|
}
|
|
1272
1304
|
else if (!preRebase.skipped) {
|
|
1305
|
+
stream.text?.(`── [Git] Rebase skipped (conflicts), proceeding without rebase\n`);
|
|
1273
1306
|
console.log(`[executor] Task ${task.id}: pre-push rebase had conflicts, proceeding without rebase`);
|
|
1274
1307
|
}
|
|
1275
1308
|
}
|
|
@@ -1284,6 +1317,7 @@ export class TaskExecutor {
|
|
|
1284
1317
|
baseBranch: prepared.baseBranch,
|
|
1285
1318
|
autoMerge: hasProjectBranch,
|
|
1286
1319
|
commitBeforeSha: prepared.commitBeforeSha,
|
|
1320
|
+
gitRoot: prepared.gitRoot,
|
|
1287
1321
|
});
|
|
1288
1322
|
result.branchName = prResult.branchName;
|
|
1289
1323
|
if (prResult.prUrl) {
|
|
@@ -1292,7 +1326,7 @@ export class TaskExecutor {
|
|
|
1292
1326
|
result.commitBeforeSha = prResult.commitBeforeSha;
|
|
1293
1327
|
result.commitAfterSha = prResult.commitAfterSha;
|
|
1294
1328
|
keepBranch = true;
|
|
1295
|
-
stream.text?.(
|
|
1329
|
+
stream.text?.(`── [Git] PR created: ${prResult.prUrl}\n`);
|
|
1296
1330
|
if (prResult.autoMergeFailed) {
|
|
1297
1331
|
// PR was created but auto-merge failed (likely conflict).
|
|
1298
1332
|
// If the adapter supports session resume, ask the agent to
|
|
@@ -1304,7 +1338,7 @@ export class TaskExecutor {
|
|
|
1304
1338
|
: null;
|
|
1305
1339
|
if (prTaskContext?.sessionId && this.isResumableAdapter(adapter) && hasProjectBranch && prepared.branchName && prepared.baseBranch && prResult.prNumber && prepared.gitRoot) {
|
|
1306
1340
|
for (let attempt = 1; attempt <= MAX_PR_MERGE_ATTEMPTS; attempt++) {
|
|
1307
|
-
stream.text?.(
|
|
1341
|
+
stream.text?.(`── [Git] Auto-merge failed — agent resolving conflict (attempt ${attempt})...\n`);
|
|
1308
1342
|
console.log(`[executor] Task ${task.id}: PR auto-merge failed (attempt ${attempt}), resuming ${adapter.name} to resolve`);
|
|
1309
1343
|
this.wsClient.sendTaskStatus(task.id, 'running', 97, `PR merge conflict — agent resolving (attempt ${attempt})...`);
|
|
1310
1344
|
// Resume agent session — agent rebases and force-pushes.
|
|
@@ -1318,11 +1352,14 @@ export class TaskExecutor {
|
|
|
1318
1352
|
result.deliveryError = `PR created but auto-merge failed. Agent resolution failed: ${resumeErr instanceof Error ? resumeErr.message : resumeErr}`;
|
|
1319
1353
|
break;
|
|
1320
1354
|
}
|
|
1321
|
-
// Retry the GitHub merge
|
|
1355
|
+
// Retry the GitHub merge.
|
|
1356
|
+
// Use gitRoot as cwd (always valid, guarded at L1518) instead of
|
|
1357
|
+
// worktree path which may have been deleted during resolution.
|
|
1322
1358
|
this.wsClient.sendTaskStatus(task.id, 'running', 98, `Retrying PR merge (attempt ${attempt})...`);
|
|
1323
|
-
const retryMerge = await mergePullRequest(prepared.
|
|
1359
|
+
const retryMerge = await mergePullRequest(prepared.gitRoot, prResult.prNumber, {
|
|
1324
1360
|
method: 'squash',
|
|
1325
1361
|
deleteBranch: true,
|
|
1362
|
+
repoSlug: repoSlug ?? undefined,
|
|
1326
1363
|
});
|
|
1327
1364
|
if (retryMerge.ok) {
|
|
1328
1365
|
result.commitAfterSha = await getRemoteBranchSha(prepared.gitRoot, prepared.baseBranch) ?? undefined;
|
|
@@ -1362,18 +1399,18 @@ export class TaskExecutor {
|
|
|
1362
1399
|
result.deliveryStatus = 'failed';
|
|
1363
1400
|
result.deliveryError = `PR delivery failed: ${prResult.error}`;
|
|
1364
1401
|
keepBranch = prResult.pushed ?? false; // Keep branch if it was pushed
|
|
1365
|
-
stream.text?.(
|
|
1402
|
+
stream.text?.(`── [Git] PR delivery failed: ${prResult.error}\n`);
|
|
1366
1403
|
console.error(`[executor] Task ${task.id}: PR delivery failed: ${prResult.error}`);
|
|
1367
1404
|
}
|
|
1368
1405
|
else {
|
|
1369
|
-
stream.text?.(
|
|
1406
|
+
stream.text?.(`── [Git] No changes to deliver\n`);
|
|
1370
1407
|
console.log(`[executor] Task ${task.id}: no changes to push`);
|
|
1371
1408
|
}
|
|
1372
1409
|
}
|
|
1373
1410
|
}
|
|
1374
1411
|
catch (prError) {
|
|
1375
1412
|
const prMsg = prError instanceof Error ? prError.message : String(prError);
|
|
1376
|
-
stream.text?.(
|
|
1413
|
+
stream.text?.(`── [Delivery] Failed: ${prMsg}\n`);
|
|
1377
1414
|
console.error(`[executor] Task ${task.id}: delivery (${deliveryMode}) failed: ${prMsg}`);
|
|
1378
1415
|
// Delivery failure — don't override execution status
|
|
1379
1416
|
result.deliveryStatus = 'failed';
|
|
@@ -1430,14 +1467,13 @@ export class TaskExecutor {
|
|
|
1430
1467
|
clearTimeout(hardCapTimeoutId);
|
|
1431
1468
|
if (idleTimerId !== undefined)
|
|
1432
1469
|
clearTimeout(idleTimerId);
|
|
1433
|
-
taskHeartbeatPhase = 'cleaning up';
|
|
1434
1470
|
// Always cleanup the local worktree directory to reclaim disk space
|
|
1435
1471
|
// (node_modules alone is ~680MB per worktree). When keepBranch is true
|
|
1436
1472
|
// (PR created or branch pushed), we preserve the git branch but still
|
|
1437
1473
|
// remove the working copy — the branch lives on remote/local refs,
|
|
1438
1474
|
// and re-execution will create a fresh worktree if needed.
|
|
1439
1475
|
if (prepared.branchName) {
|
|
1440
|
-
stream.text?.(`\n[Astro] Cleaning up worktree (branch: ${prepared.branchName})...\n`);
|
|
1476
|
+
stream.text?.(`\n── [Astro] Cleaning up worktree (branch: ${prepared.branchName})...\n`);
|
|
1441
1477
|
}
|
|
1442
1478
|
if (this.preserveWorktrees) {
|
|
1443
1479
|
console.log(`[executor] Task ${task.id}: worktree preserved (debug mode)`);
|