automata-cli 0.1.0-feature-030-do-work.210 → 0.1.0-feature-030-do-work.212
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.js +208 -51
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2431,22 +2431,24 @@ query($owner:String!,$repo:String!,$cursor:String){
|
|
|
2431
2431
|
pullRequests(states:OPEN, first:100, after:$cursor, orderBy:{field:UPDATED_AT, direction:DESC}){
|
|
2432
2432
|
pageInfo{ hasNextPage endCursor }
|
|
2433
2433
|
nodes{
|
|
2434
|
-
number url title headRefName isDraft updatedAt
|
|
2434
|
+
number url title headRefName baseRefName isCrossRepository isDraft updatedAt
|
|
2435
2435
|
closingIssuesReferences(first:50){
|
|
2436
2436
|
pageInfo{ hasNextPage }
|
|
2437
|
-
nodes{ number }
|
|
2437
|
+
nodes{ number repository{ nameWithOwner } }
|
|
2438
2438
|
}
|
|
2439
2439
|
}
|
|
2440
2440
|
}
|
|
2441
2441
|
}
|
|
2442
2442
|
}`.trim();
|
|
2443
2443
|
var MAX_LINK_MAP_PAGES = 50;
|
|
2444
|
-
function indexPullRequest(map, node) {
|
|
2444
|
+
function indexPullRequest(map, node, nameWithOwner) {
|
|
2445
2445
|
const ref = {
|
|
2446
2446
|
number: node.number,
|
|
2447
2447
|
url: node.url,
|
|
2448
2448
|
title: node.title,
|
|
2449
2449
|
headRefName: node.headRefName,
|
|
2450
|
+
baseRefName: node.baseRefName,
|
|
2451
|
+
isCrossRepository: node.isCrossRepository,
|
|
2450
2452
|
state: "OPEN",
|
|
2451
2453
|
isDraft: node.isDraft,
|
|
2452
2454
|
updatedAt: node.updatedAt
|
|
@@ -2457,6 +2459,7 @@ function indexPullRequest(map, node) {
|
|
|
2457
2459
|
);
|
|
2458
2460
|
}
|
|
2459
2461
|
for (const issue of node.closingIssuesReferences.nodes) {
|
|
2462
|
+
if (issue.repository.nameWithOwner !== nameWithOwner) continue;
|
|
2460
2463
|
const existing = map.get(issue.number);
|
|
2461
2464
|
if (existing) {
|
|
2462
2465
|
existing.push(ref);
|
|
@@ -2475,7 +2478,7 @@ function getOpenPrLinkMap() {
|
|
|
2475
2478
|
const response = ghJson(args, "query open pull requests");
|
|
2476
2479
|
const connection = response.data.repository.pullRequests;
|
|
2477
2480
|
for (const node of connection.nodes) {
|
|
2478
|
-
indexPullRequest(map, node);
|
|
2481
|
+
indexPullRequest(map, node, `${owner}/${repo}`);
|
|
2479
2482
|
}
|
|
2480
2483
|
if (!connection.pageInfo?.hasNextPage || connection.pageInfo.endCursor === null) {
|
|
2481
2484
|
return map;
|
|
@@ -2566,7 +2569,7 @@ function getPrSurface(prNumber) {
|
|
|
2566
2569
|
"view",
|
|
2567
2570
|
String(prNumber),
|
|
2568
2571
|
"--json",
|
|
2569
|
-
"number,title,url,headRefName,state,isDraft,body,author,createdAt,updatedAt,comments,reviews"
|
|
2572
|
+
"number,title,url,headRefName,baseRefName,isCrossRepository,state,isDraft,body,author,createdAt,updatedAt,comments,reviews"
|
|
2570
2573
|
],
|
|
2571
2574
|
`read pull request #${String(prNumber)}`
|
|
2572
2575
|
);
|
|
@@ -2594,6 +2597,8 @@ function getPrSurface(prNumber) {
|
|
|
2594
2597
|
url: raw.url,
|
|
2595
2598
|
title: raw.title,
|
|
2596
2599
|
headRefName: raw.headRefName,
|
|
2600
|
+
baseRefName: raw.baseRefName ?? "",
|
|
2601
|
+
isCrossRepository: raw.isCrossRepository ?? false,
|
|
2597
2602
|
state,
|
|
2598
2603
|
isDraft: raw.isDraft ?? false,
|
|
2599
2604
|
updatedAt: raw.updatedAt ?? raw.createdAt
|
|
@@ -2719,7 +2724,24 @@ function decideWork(state, p, baseBranch) {
|
|
|
2719
2724
|
};
|
|
2720
2725
|
}
|
|
2721
2726
|
const surface = prSurface;
|
|
2722
|
-
|
|
2727
|
+
if (surface.pr.isCrossRepository) {
|
|
2728
|
+
return {
|
|
2729
|
+
kind: "skip",
|
|
2730
|
+
issue,
|
|
2731
|
+
reason: "unsafe-pr-branch",
|
|
2732
|
+
detail: `pull request #${String(surface.pr.number)} comes from a fork; its head branch is not in this repository`
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
if (surface.pr.headRefName === baseBranch) {
|
|
2736
|
+
return {
|
|
2737
|
+
kind: "skip",
|
|
2738
|
+
issue,
|
|
2739
|
+
reason: "unsafe-pr-branch",
|
|
2740
|
+
detail: `pull request #${String(surface.pr.number)} has the base branch (${baseBranch}) as its head`
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
const agentThreadMessages = surface.threads.flatMap((thread) => thread.comments).filter((comment) => comment.author.toLowerCase() === p.agentUser.toLowerCase());
|
|
2744
|
+
const prAnalysis = analyzeSurface([...surface.messages, ...agentThreadMessages], p);
|
|
2723
2745
|
const actionableThreads = findActionableThreads(surface.threads, p);
|
|
2724
2746
|
const hasPrWork = prAnalysis.hasNewMessage || actionableThreads.length > 0;
|
|
2725
2747
|
if (!hasPrWork && !issueAnalysis.hasNewMessage) {
|
|
@@ -2850,6 +2872,16 @@ var RUN_LOCK_RELATIVE_PATH = `${LOCK_DIR}/${LOCK_FILE}`;
|
|
|
2850
2872
|
function lockPath() {
|
|
2851
2873
|
return join2(process.cwd(), LOCK_DIR, LOCK_FILE);
|
|
2852
2874
|
}
|
|
2875
|
+
function processStartedAt(pid) {
|
|
2876
|
+
try {
|
|
2877
|
+
const stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
|
|
2878
|
+
const afterComm = stat.slice(stat.lastIndexOf(")") + 2);
|
|
2879
|
+
const field = afterComm.split(" ")[19];
|
|
2880
|
+
return field === void 0 || field.length === 0 ? null : field;
|
|
2881
|
+
} catch {
|
|
2882
|
+
return null;
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2853
2885
|
function isAlive(pid) {
|
|
2854
2886
|
try {
|
|
2855
2887
|
process.kill(pid, 0);
|
|
@@ -2867,7 +2899,8 @@ function readOwner(path) {
|
|
|
2867
2899
|
startedAt: parsed.startedAt,
|
|
2868
2900
|
host: parsed.host ?? "unknown",
|
|
2869
2901
|
command: parsed.command ?? "unknown",
|
|
2870
|
-
token: parsed.token ?? ""
|
|
2902
|
+
token: parsed.token ?? "",
|
|
2903
|
+
pidStartedAt: parsed.pidStartedAt
|
|
2871
2904
|
};
|
|
2872
2905
|
} catch {
|
|
2873
2906
|
return null;
|
|
@@ -2876,12 +2909,24 @@ function readOwner(path) {
|
|
|
2876
2909
|
function isStale(owner, staleMinutes) {
|
|
2877
2910
|
if (owner === null) return true;
|
|
2878
2911
|
if (owner.host === hostname()) {
|
|
2879
|
-
|
|
2912
|
+
if (!isAlive(owner.pid)) return true;
|
|
2913
|
+
const startedAt2 = processStartedAt(owner.pid);
|
|
2914
|
+
if (owner.pidStartedAt !== void 0 && startedAt2 !== null && startedAt2 !== owner.pidStartedAt) {
|
|
2915
|
+
return true;
|
|
2916
|
+
}
|
|
2917
|
+
return false;
|
|
2880
2918
|
}
|
|
2881
2919
|
const startedAt = Date.parse(owner.startedAt);
|
|
2882
2920
|
if (Number.isNaN(startedAt)) return true;
|
|
2883
2921
|
return Date.now() - startedAt > staleMinutes * 60 * 1e3;
|
|
2884
2922
|
}
|
|
2923
|
+
function heldTooLong(owner, staleMinutes) {
|
|
2924
|
+
if (owner.host !== hostname()) return false;
|
|
2925
|
+
if (owner.pidStartedAt !== void 0 && processStartedAt(owner.pid) !== null) return false;
|
|
2926
|
+
const startedAt = Date.parse(owner.startedAt);
|
|
2927
|
+
if (Number.isNaN(startedAt)) return true;
|
|
2928
|
+
return Date.now() - startedAt > staleMinutes * 60 * 1e3;
|
|
2929
|
+
}
|
|
2885
2930
|
function makeHandle(path, token) {
|
|
2886
2931
|
let released = false;
|
|
2887
2932
|
return {
|
|
@@ -2923,7 +2968,8 @@ function write(path, command, token, flag = "wx") {
|
|
|
2923
2968
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2924
2969
|
host: hostname(),
|
|
2925
2970
|
command,
|
|
2926
|
-
token
|
|
2971
|
+
token,
|
|
2972
|
+
pidStartedAt: processStartedAt(process.pid) ?? void 0
|
|
2927
2973
|
};
|
|
2928
2974
|
writeFileSync(path, JSON.stringify(owner, null, 2) + "\n", { encoding: "utf8", flag });
|
|
2929
2975
|
}
|
|
@@ -2939,9 +2985,10 @@ function acquireRunLock(command, staleMinutes) {
|
|
|
2939
2985
|
}
|
|
2940
2986
|
const owner = readOwner(path);
|
|
2941
2987
|
if (!isStale(owner, staleMinutes)) {
|
|
2942
|
-
|
|
2988
|
+
const held = owner;
|
|
2989
|
+
return { ok: false, heldBy: held, suspect: heldTooLong(held, staleMinutes) };
|
|
2943
2990
|
}
|
|
2944
|
-
return reclaim(path, command, token);
|
|
2991
|
+
return reclaim(path, command, token, owner);
|
|
2945
2992
|
}
|
|
2946
2993
|
var UNKNOWN_OWNER = {
|
|
2947
2994
|
pid: 0,
|
|
@@ -2950,18 +2997,34 @@ var UNKNOWN_OWNER = {
|
|
|
2950
2997
|
command: "unknown",
|
|
2951
2998
|
token: ""
|
|
2952
2999
|
};
|
|
2953
|
-
function claimStaleLock(path, token) {
|
|
3000
|
+
function claimStaleLock(path, token, expected) {
|
|
3001
|
+
const claimed = `${path}.stale.${token}`;
|
|
2954
3002
|
try {
|
|
2955
|
-
renameSync(path,
|
|
2956
|
-
return true;
|
|
3003
|
+
renameSync(path, claimed);
|
|
2957
3004
|
} catch (err) {
|
|
2958
3005
|
if (err.code === "ENOENT") return false;
|
|
2959
3006
|
throw err;
|
|
2960
3007
|
}
|
|
3008
|
+
if (expected !== void 0) {
|
|
3009
|
+
const taken = readOwner(claimed);
|
|
3010
|
+
const sameFile = expected === null && taken === null || expected !== null && taken !== null && taken.token === expected.token;
|
|
3011
|
+
if (!sameFile) {
|
|
3012
|
+
try {
|
|
3013
|
+
linkSync(claimed, path);
|
|
3014
|
+
} catch {
|
|
3015
|
+
}
|
|
3016
|
+
try {
|
|
3017
|
+
unlinkSync(claimed);
|
|
3018
|
+
} catch {
|
|
3019
|
+
}
|
|
3020
|
+
return false;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
return true;
|
|
2961
3024
|
}
|
|
2962
|
-
function reclaim(path, command, token) {
|
|
2963
|
-
if (!claimStaleLock(path, token)) {
|
|
2964
|
-
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER };
|
|
3025
|
+
function reclaim(path, command, token, expected) {
|
|
3026
|
+
if (!claimStaleLock(path, token, expected)) {
|
|
3027
|
+
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
|
|
2965
3028
|
}
|
|
2966
3029
|
const claimed = `${path}.stale.${token}`;
|
|
2967
3030
|
try {
|
|
@@ -2969,7 +3032,7 @@ function reclaim(path, command, token) {
|
|
|
2969
3032
|
return { ok: true, handle: makeHandle(path, token) };
|
|
2970
3033
|
} catch (err) {
|
|
2971
3034
|
if (err.code !== "EEXIST") throw err;
|
|
2972
|
-
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER };
|
|
3035
|
+
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
|
|
2973
3036
|
} finally {
|
|
2974
3037
|
try {
|
|
2975
3038
|
unlinkSync(claimed);
|
|
@@ -3020,6 +3083,7 @@ function preparePrBranch(headRefName) {
|
|
|
3020
3083
|
}
|
|
3021
3084
|
|
|
3022
3085
|
// src/commands/doWork.ts
|
|
3086
|
+
var inFlightMarker = null;
|
|
3023
3087
|
function out(message) {
|
|
3024
3088
|
process.stdout.write(message);
|
|
3025
3089
|
}
|
|
@@ -3261,6 +3325,40 @@ function describePlan(decisions) {
|
|
|
3261
3325
|
});
|
|
3262
3326
|
return lines.length === 0 ? " (no issues matched the discovery filter)\n" : lines.join("\n") + "\n";
|
|
3263
3327
|
}
|
|
3328
|
+
function claimIssue(item, settings) {
|
|
3329
|
+
if (!item.needsAssignment) return;
|
|
3330
|
+
try {
|
|
3331
|
+
assignIssueToAgent(item.issue.number, settings.participants.agentUser);
|
|
3332
|
+
progress(` assigned issue #${String(item.issue.number)} to ${settings.participants.agentUser}.
|
|
3333
|
+
`);
|
|
3334
|
+
} catch (err) {
|
|
3335
|
+
progress(` warning: could not assign issue #${String(item.issue.number)}: ${err.message}
|
|
3336
|
+
`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
function notePickupOnIssue(item, marker) {
|
|
3340
|
+
if (item.turn !== "pr-work" || item.pr === null || !item.issueAnalysis.hasNewMessage) return true;
|
|
3341
|
+
try {
|
|
3342
|
+
postMarker(
|
|
3343
|
+
"issue",
|
|
3344
|
+
item.issue.number,
|
|
3345
|
+
`automata do-work: picked this up on pull request #${String(item.pr.number)} \u2014 ${item.pr.url}`
|
|
3346
|
+
);
|
|
3347
|
+
progress(` noted on issue #${String(item.issue.number)} that the work is on pull request #${String(item.pr.number)}.
|
|
3348
|
+
`);
|
|
3349
|
+
return true;
|
|
3350
|
+
} catch (err) {
|
|
3351
|
+
progress(` skipped: could not note the pickup on issue #${String(item.issue.number)} \u2014 ${err.message}
|
|
3352
|
+
`);
|
|
3353
|
+
try {
|
|
3354
|
+
deleteMarker(marker);
|
|
3355
|
+
} catch (deleteErr) {
|
|
3356
|
+
progress(` warning: could not withdraw the working marker: ${deleteErr.message}
|
|
3357
|
+
`);
|
|
3358
|
+
}
|
|
3359
|
+
return false;
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3264
3362
|
function refreshItem(item, settings) {
|
|
3265
3363
|
const linkMap = getOpenPrLinkMap();
|
|
3266
3364
|
return decideWork(
|
|
@@ -3269,6 +3367,14 @@ function refreshItem(item, settings) {
|
|
|
3269
3367
|
settings.baseBranch
|
|
3270
3368
|
);
|
|
3271
3369
|
}
|
|
3370
|
+
function authorizedMessagesSince(messages, participants, marker) {
|
|
3371
|
+
const allowed = participants.allowedUsers.map((user) => user.toLowerCase());
|
|
3372
|
+
const agent = participants.agentUser.toLowerCase();
|
|
3373
|
+
return messages.filter((message) => {
|
|
3374
|
+
const author = message.author.toLowerCase();
|
|
3375
|
+
return author !== agent && allowed.includes(author) && message.createdAt > marker.createdAt;
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3272
3378
|
function readAnsweringSurface(item) {
|
|
3273
3379
|
if (item.turn === "issue-discuss" || item.pr === null) {
|
|
3274
3380
|
return getIssueSurface(item.issue.number).messages;
|
|
@@ -3279,10 +3385,13 @@ function readAnsweringSurface(item) {
|
|
|
3279
3385
|
function markerSurfaceLabel(item) {
|
|
3280
3386
|
return item.turn === "pr-work" && item.pr ? `pull request #${String(item.pr.number)}` : `issue #${String(item.issue.number)}`;
|
|
3281
3387
|
}
|
|
3282
|
-
function reconcileMarker(item, marker,
|
|
3388
|
+
function reconcileMarker(item, marker, participants, runError) {
|
|
3389
|
+
const agentUser = participants.agentUser;
|
|
3390
|
+
let surfaceMessages = [];
|
|
3283
3391
|
let answered;
|
|
3284
3392
|
try {
|
|
3285
|
-
|
|
3393
|
+
surfaceMessages = readAnsweringSurface(item);
|
|
3394
|
+
answered = agentAnsweredAfter(surfaceMessages, agentUser, marker);
|
|
3286
3395
|
} catch (err) {
|
|
3287
3396
|
progress(
|
|
3288
3397
|
` warning: could not re-read issue #${String(item.issue.number)} to check for an answer: ${err.message}
|
|
@@ -3304,12 +3413,30 @@ function reconcileMarker(item, marker, agentUser, runError) {
|
|
|
3304
3413
|
return { outcome: "answered-no-reply", detail: "could not verify whether an answer was posted" };
|
|
3305
3414
|
}
|
|
3306
3415
|
if (answered) {
|
|
3416
|
+
const interleaved = authorizedMessagesSince(surfaceMessages, participants, marker);
|
|
3417
|
+
if (interleaved.length > 0) {
|
|
3418
|
+
const authors = [...new Set(interleaved.map((message) => message.author))].join(", ");
|
|
3419
|
+
try {
|
|
3420
|
+
postMarker(
|
|
3421
|
+
item.turn === "pr-work" ? "pr" : "issue",
|
|
3422
|
+
item.turn === "pr-work" && item.pr ? item.pr.number : item.issue.number,
|
|
3423
|
+
`automata do-work: ${authors} posted here while this run was already in progress, so ${interleaved.length === 1 ? "that message was" : "those messages were"} not included in it. Please post again to have them acted on.`
|
|
3424
|
+
);
|
|
3425
|
+
} catch (err) {
|
|
3426
|
+
progress(` warning: could not report the interleaved message(s): ${err.message}
|
|
3427
|
+
`);
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3307
3430
|
try {
|
|
3308
3431
|
deleteMarker(marker);
|
|
3309
3432
|
} catch (err) {
|
|
3310
3433
|
progress(` warning: could not delete the marker comment: ${err.message}
|
|
3311
3434
|
`);
|
|
3312
3435
|
}
|
|
3436
|
+
const noted = interleaved.length > 0 ? ` (${String(interleaved.length)} message(s) arrived mid-run and were flagged)` : "";
|
|
3437
|
+
if (interleaved.length > 0) {
|
|
3438
|
+
return { outcome: "answered-no-reply", detail: `answered${noted}` };
|
|
3439
|
+
}
|
|
3313
3440
|
return runError === null ? { outcome: "answered", detail: "answered" } : { outcome: "answered", detail: `answered, but the run reported: ${runError.message}` };
|
|
3314
3441
|
}
|
|
3315
3442
|
const surface = markerSurfaceLabel(item);
|
|
@@ -3346,7 +3473,8 @@ function repairIssueLink(item, baseBranch) {
|
|
|
3346
3473
|
`);
|
|
3347
3474
|
return;
|
|
3348
3475
|
}
|
|
3349
|
-
|
|
3476
|
+
const closesRef = new RegExp(String.raw`\bcloses\s+#` + String(item.issue.number) + String.raw`\b`, "i");
|
|
3477
|
+
if (closesRef.test(pr.body)) {
|
|
3350
3478
|
progress(` pull request #${String(pr.number)} already closes issue #${String(item.issue.number)}.
|
|
3351
3479
|
`);
|
|
3352
3480
|
return;
|
|
@@ -3391,31 +3519,7 @@ async function processItem(planned, settings, silent) {
|
|
|
3391
3519
|
`);
|
|
3392
3520
|
return { ...base, outcome: "skipped", detail: `${prepared.reason}: ${prepared.detail}` };
|
|
3393
3521
|
}
|
|
3394
|
-
|
|
3395
|
-
try {
|
|
3396
|
-
assignIssueToAgent(item.issue.number, settings.participants.agentUser);
|
|
3397
|
-
progress(` assigned issue #${String(item.issue.number)} to ${settings.participants.agentUser}.
|
|
3398
|
-
`);
|
|
3399
|
-
} catch (err) {
|
|
3400
|
-
progress(` warning: could not assign issue #${String(item.issue.number)}: ${err.message}
|
|
3401
|
-
`);
|
|
3402
|
-
}
|
|
3403
|
-
}
|
|
3404
|
-
if (item.turn === "pr-work" && item.pr && item.issueAnalysis.hasNewMessage) {
|
|
3405
|
-
try {
|
|
3406
|
-
postMarker(
|
|
3407
|
-
"issue",
|
|
3408
|
-
item.issue.number,
|
|
3409
|
-
`automata do-work: picked this up on pull request #${String(item.pr.number)} \u2014 ${item.pr.url}`
|
|
3410
|
-
);
|
|
3411
|
-
progress(` noted on issue #${String(item.issue.number)} that the work is on pull request #${String(item.pr.number)}.
|
|
3412
|
-
`);
|
|
3413
|
-
} catch (err) {
|
|
3414
|
-
progress(` skipped: could not note the pickup on issue #${String(item.issue.number)} \u2014 ${err.message}
|
|
3415
|
-
`);
|
|
3416
|
-
return { ...base, outcome: "skipped", detail: `issue pickup note failed: ${err.message}` };
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3522
|
+
claimIssue(item, settings);
|
|
3419
3523
|
let marker;
|
|
3420
3524
|
const markerSurface = item.turn === "pr-work" && item.pr ? item.pr.number : item.issue.number;
|
|
3421
3525
|
try {
|
|
@@ -3425,6 +3529,11 @@ async function processItem(planned, settings, silent) {
|
|
|
3425
3529
|
`);
|
|
3426
3530
|
return { ...base, outcome: "skipped", detail: `marker failed: ${err.message}` };
|
|
3427
3531
|
}
|
|
3532
|
+
inFlightMarker = { marker, item };
|
|
3533
|
+
if (!notePickupOnIssue(item, marker)) {
|
|
3534
|
+
inFlightMarker = null;
|
|
3535
|
+
return { ...base, outcome: "skipped", detail: "issue pickup note failed" };
|
|
3536
|
+
}
|
|
3428
3537
|
const prompt = composePrompt({
|
|
3429
3538
|
item,
|
|
3430
3539
|
repo: getRepoSlug(),
|
|
@@ -3432,19 +3541,39 @@ async function processItem(planned, settings, silent) {
|
|
|
3432
3541
|
baseBranch: settings.baseBranch,
|
|
3433
3542
|
frame: settings.prompts[item.turn]
|
|
3434
3543
|
});
|
|
3544
|
+
const MAX_PROMPT_BYTES = 96 * 1024;
|
|
3545
|
+
const promptBytes = Buffer.byteLength(prompt, "utf8");
|
|
3546
|
+
if (promptBytes > MAX_PROMPT_BYTES) {
|
|
3547
|
+
const detail = `the composed prompt is ${String(Math.round(promptBytes / 1024))} KiB, over the ${String(MAX_PROMPT_BYTES / 1024)} KiB limit for a single command-line argument. The conversation is too long to hand to the executor this way.`;
|
|
3548
|
+
progress(` failed: ${detail}
|
|
3549
|
+
`);
|
|
3550
|
+
inFlightMarker = null;
|
|
3551
|
+
try {
|
|
3552
|
+
updateMarker(
|
|
3553
|
+
marker,
|
|
3554
|
+
`automata do-work: could not start a run because ${detail} Summarise the discussion in a new issue, or shorten the thread, and try again.`
|
|
3555
|
+
);
|
|
3556
|
+
} catch (err) {
|
|
3557
|
+
progress(` warning: could not update the marker comment: ${err.message}
|
|
3558
|
+
`);
|
|
3559
|
+
}
|
|
3560
|
+
return { ...base, outcome: "failed", detail };
|
|
3561
|
+
}
|
|
3435
3562
|
let runError = null;
|
|
3436
3563
|
try {
|
|
3437
3564
|
await invokeExecutor(prompt, settings, silent);
|
|
3438
3565
|
} catch (err) {
|
|
3439
3566
|
runError = err;
|
|
3440
3567
|
}
|
|
3441
|
-
const
|
|
3568
|
+
const ranExecutor = true;
|
|
3569
|
+
inFlightMarker = null;
|
|
3570
|
+
const reconciled = reconcileMarker(item, marker, settings.participants, runError);
|
|
3442
3571
|
progress(` ${reconciled.detail}
|
|
3443
3572
|
`);
|
|
3444
3573
|
if (item.turn === "issue-discuss") {
|
|
3445
3574
|
repairIssueLink(item, settings.baseBranch);
|
|
3446
3575
|
}
|
|
3447
|
-
return { ...base, outcome: reconciled.outcome, detail: reconciled.detail };
|
|
3576
|
+
return { ...base, outcome: reconciled.outcome, detail: reconciled.detail, ranExecutor };
|
|
3448
3577
|
}
|
|
3449
3578
|
function summarize(reports) {
|
|
3450
3579
|
out("\nTick summary:\n");
|
|
@@ -3464,17 +3593,33 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3464
3593
|
"Print the work plan, plus a summary and the exact command that would be launched for each item, and exit without changing anything"
|
|
3465
3594
|
).option("--json", "Emit the work plan and outcomes as JSON on stdout").option("--silent", "Suppress step-by-step Claude output; show only the final summary").action(async (options) => {
|
|
3466
3595
|
const settings = resolveSettings(options);
|
|
3596
|
+
if (options.dryRun === true) {
|
|
3597
|
+
const exitCode2 = await runTick(settings, options);
|
|
3598
|
+
if (exitCode2 !== 0) process.exit(exitCode2);
|
|
3599
|
+
return;
|
|
3600
|
+
}
|
|
3467
3601
|
const lock = acquireRunLock("do-work", settings.lockStaleMinutes);
|
|
3468
3602
|
if (!lock.ok) {
|
|
3469
3603
|
const held = lock.heldBy;
|
|
3470
3604
|
const sentence = `Another automata instance is already running here (pid ${String(held.pid)} on ${held.host}, started ${held.startedAt}, command ${held.command}). Doing nothing.
|
|
3471
3605
|
`;
|
|
3606
|
+
const suspectSentence = lock.suspect ? `Warning: that lock has been held longer than ${String(settings.lockStaleMinutes)} minutes. If no tick is really running, its process id was probably reused; remove ${RUN_LOCK_RELATIVE_PATH} once you have confirmed that.
|
|
3607
|
+
` : "";
|
|
3608
|
+
const exitCode2 = lock.suspect ? 2 : 0;
|
|
3472
3609
|
if (options.json === true) {
|
|
3473
|
-
progress(sentence);
|
|
3474
|
-
out(
|
|
3610
|
+
progress(sentence + suspectSentence);
|
|
3611
|
+
out(
|
|
3612
|
+
JSON.stringify(
|
|
3613
|
+
{ lockHeld: true, suspect: lock.suspect, heldBy: held, plan: [], items: [], exitCode: exitCode2 },
|
|
3614
|
+
null,
|
|
3615
|
+
2
|
|
3616
|
+
) + "\n"
|
|
3617
|
+
);
|
|
3475
3618
|
} else {
|
|
3476
3619
|
out(sentence);
|
|
3620
|
+
if (suspectSentence) progress(suspectSentence);
|
|
3477
3621
|
}
|
|
3622
|
+
if (exitCode2 !== 0) process.exit(exitCode2);
|
|
3478
3623
|
return;
|
|
3479
3624
|
}
|
|
3480
3625
|
const handle = lock.handle;
|
|
@@ -3483,6 +3628,18 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3483
3628
|
if (shuttingDown) return;
|
|
3484
3629
|
shuttingDown = true;
|
|
3485
3630
|
progress("\nInterrupted: stopping the executor before releasing the run lock\u2026\n");
|
|
3631
|
+
const pending = inFlightMarker;
|
|
3632
|
+
if (pending !== null) {
|
|
3633
|
+
try {
|
|
3634
|
+
updateMarker(
|
|
3635
|
+
pending.marker,
|
|
3636
|
+
`automata do-work: this run was interrupted before it finished, so no answer was produced. The branch \`${pending.item.branch}\` may have been changed. Reply here to have another attempt made.`
|
|
3637
|
+
);
|
|
3638
|
+
} catch (err) {
|
|
3639
|
+
progress(`Warning: could not update the in-flight marker: ${err.message}
|
|
3640
|
+
`);
|
|
3641
|
+
}
|
|
3642
|
+
}
|
|
3486
3643
|
void terminateTrackedChildren().then((allExited) => {
|
|
3487
3644
|
if (allExited) {
|
|
3488
3645
|
handle.release();
|
|
@@ -3548,7 +3705,7 @@ ${describePlan(decisions)}`;
|
|
|
3548
3705
|
detail: err.message
|
|
3549
3706
|
};
|
|
3550
3707
|
}
|
|
3551
|
-
if (report.
|
|
3708
|
+
if (report.ranExecutor === true) runsUsed++;
|
|
3552
3709
|
reports.push(report);
|
|
3553
3710
|
}
|
|
3554
3711
|
for (const item of deferred) {
|