automata-cli 0.1.0-feature-030-do-work.212 → 0.1.0-feature-030-do-work.214
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.
|
@@ -13,7 +13,7 @@ var DEFAULT_DO_WORK = {
|
|
|
13
13
|
maxRunsPerTick: 0,
|
|
14
14
|
lockStaleMinutes: 120
|
|
15
15
|
};
|
|
16
|
-
var DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT = "You are the agent named in the context below, working on a GitHub issue together with the people allowed to instruct you. Answer the messages marked NEW; the earlier messages are context only.\n\nDo not modify, create or delete any file, and do not create a branch or a pull request, UNLESS a message marked NEW explicitly asks you to implement the work. If it does: create a branch off the base branch named below, implement the change following the project's existing conventions, run the tests and the linter, and open a pull request whose body contains `Closes #<issue number>`.\n\nOtherwise do not touch the code at all: reply on the issue with the specification, the plan, or the open questions you need answered. Keep the reply short and concrete,
|
|
16
|
+
var DEFAULT_DO_WORK_ISSUE_DISCUSS_PROMPT = "You are the agent named in the context below, working on a GitHub issue together with the people allowed to instruct you. Answer the messages marked NEW; the earlier messages are context only.\n\nDo not modify, create or delete any file, and do not create a branch or a pull request, UNLESS a message marked NEW explicitly asks you to implement the work. If it does: create a branch off the base branch named below, implement the change following the project's existing conventions, run the tests and the linter, and open a pull request whose body contains `Closes #<issue number>`.\n\nOtherwise do not touch the code at all: reply on the issue with the specification, the plan, or the open questions you need answered. Keep the reply short and concrete.\n\nEither way, always post a reply on the issue before you finish \u2014 including when you implemented and opened a pull request. Silence is indistinguishable from a crash, and the run will be reported as having produced no answer.";
|
|
17
17
|
var DEFAULT_DO_WORK_PR_WORK_PROMPT = "You are the agent named in the context below, working on the pull request for a GitHub issue together with the people allowed to instruct you. Work on the branch named below, which is already checked out and up to date.\n\nAddress every message marked NEW and every unresolved review thread listed. Follow the project's existing conventions, run the tests and the linter, then commit and push to that branch. Do not merge the pull request and do not push to the base branch.\n\nReply on the pull request with a short summary of what you changed, or reply in the review thread when your answer belongs to a specific comment. Always post a reply \u2014 silence looks like a crash.";
|
|
18
18
|
var CONFIG_DIR = ".automata";
|
|
19
19
|
var CONFIG_FILE = "config.json";
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
readConfig,
|
|
11
11
|
readRawConfig,
|
|
12
12
|
writeConfig
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-5ZRROOCC.js";
|
|
14
14
|
|
|
15
15
|
// src/index.ts
|
|
16
16
|
import { Command as Command7 } from "commander";
|
|
@@ -184,7 +184,7 @@ var configCommand = new Command("config").description("Configure automata settin
|
|
|
184
184
|
const [{ render }, React, { ConfigWizard }] = await Promise.all([
|
|
185
185
|
import("ink"),
|
|
186
186
|
import("react"),
|
|
187
|
-
import("./ConfigWizard-
|
|
187
|
+
import("./ConfigWizard-5MOWNA7O.js")
|
|
188
188
|
]);
|
|
189
189
|
const { waitUntilExit } = render(React.createElement(ConfigWizard));
|
|
190
190
|
await waitUntilExit();
|
|
@@ -2428,6 +2428,7 @@ function getIssueSurface(issueNumber) {
|
|
|
2428
2428
|
var LINK_MAP_QUERY = `
|
|
2429
2429
|
query($owner:String!,$repo:String!,$cursor:String){
|
|
2430
2430
|
repository(owner:$owner,name:$repo){
|
|
2431
|
+
defaultBranchRef{ name }
|
|
2431
2432
|
pullRequests(states:OPEN, first:100, after:$cursor, orderBy:{field:UPDATED_AT, direction:DESC}){
|
|
2432
2433
|
pageInfo{ hasNextPage endCursor }
|
|
2433
2434
|
nodes{
|
|
@@ -2471,17 +2472,19 @@ function indexPullRequest(map, node, nameWithOwner) {
|
|
|
2471
2472
|
function getOpenPrLinkMap() {
|
|
2472
2473
|
const { owner, repo } = getRepoSlug();
|
|
2473
2474
|
const map = /* @__PURE__ */ new Map();
|
|
2475
|
+
let defaultBranch = null;
|
|
2474
2476
|
let cursor = null;
|
|
2475
2477
|
for (let page = 0; page < MAX_LINK_MAP_PAGES; page++) {
|
|
2476
2478
|
const args = ["api", "graphql", "-f", `query=${LINK_MAP_QUERY}`, "-f", `owner=${owner}`, "-f", `repo=${repo}`];
|
|
2477
2479
|
if (cursor !== null) args.push("-f", `cursor=${cursor}`);
|
|
2478
2480
|
const response = ghJson(args, "query open pull requests");
|
|
2481
|
+
defaultBranch = response.data.repository.defaultBranchRef?.name ?? defaultBranch;
|
|
2479
2482
|
const connection = response.data.repository.pullRequests;
|
|
2480
2483
|
for (const node of connection.nodes) {
|
|
2481
2484
|
indexPullRequest(map, node, `${owner}/${repo}`);
|
|
2482
2485
|
}
|
|
2483
2486
|
if (!connection.pageInfo?.hasNextPage || connection.pageInfo.endCursor === null) {
|
|
2484
|
-
return map;
|
|
2487
|
+
return { byIssue: map, defaultBranch };
|
|
2485
2488
|
}
|
|
2486
2489
|
cursor = connection.pageInfo.endCursor;
|
|
2487
2490
|
}
|
|
@@ -2499,7 +2502,7 @@ query($owner:String!,$repo:String!,$prNumber:Int!,$cursor:String){
|
|
|
2499
2502
|
isResolved isOutdated path line
|
|
2500
2503
|
comments(last:100){
|
|
2501
2504
|
pageInfo{ hasPreviousPage }
|
|
2502
|
-
nodes{ author{login} body createdAt }
|
|
2505
|
+
nodes{ author{login} body createdAt url }
|
|
2503
2506
|
}
|
|
2504
2507
|
}
|
|
2505
2508
|
}
|
|
@@ -2545,6 +2548,7 @@ function getReviewThreads(prNumber) {
|
|
|
2545
2548
|
path: node.path,
|
|
2546
2549
|
line: node.line ?? null,
|
|
2547
2550
|
isResolved: node.isResolved,
|
|
2551
|
+
url: node.comments.nodes.at(-1)?.url ?? null,
|
|
2548
2552
|
comments: node.comments.nodes.map((comment) => ({
|
|
2549
2553
|
kind: "thread-comment",
|
|
2550
2554
|
author: login(comment.author),
|
|
@@ -2667,12 +2671,16 @@ function isAssignedToAgent(assignees, agentUser) {
|
|
|
2667
2671
|
const agent = agentUser.toLowerCase();
|
|
2668
2672
|
return assignees.some((name) => name.toLowerCase() === agent);
|
|
2669
2673
|
}
|
|
2670
|
-
function findActionableThreads(threads, p) {
|
|
2674
|
+
function findActionableThreads(threads, p, prLastAgentAt) {
|
|
2671
2675
|
const actionable = [];
|
|
2672
2676
|
for (const thread of threads) {
|
|
2673
2677
|
if (thread.isResolved) continue;
|
|
2674
2678
|
const comments = thread.comments.filter((comment) => classifyForThread(comment.author, p) !== "other");
|
|
2675
2679
|
if (lastAuthorClass(comments, p) !== "authorized") continue;
|
|
2680
|
+
const newestAuthorized = comments.at(-1)?.createdAt;
|
|
2681
|
+
if (newestAuthorized !== void 0 && prLastAgentAt !== null && prLastAgentAt > newestAuthorized) {
|
|
2682
|
+
continue;
|
|
2683
|
+
}
|
|
2676
2684
|
actionable.push({ ...thread, comments });
|
|
2677
2685
|
}
|
|
2678
2686
|
return actionable;
|
|
@@ -2688,7 +2696,7 @@ function newestPr(prs) {
|
|
|
2688
2696
|
function plural(count, noun) {
|
|
2689
2697
|
return `${String(count)} ${noun}${count === 1 ? "" : "s"}`;
|
|
2690
2698
|
}
|
|
2691
|
-
function decideWork(state, p, baseBranch) {
|
|
2699
|
+
function decideWork(state, p, baseBranch, defaultBranch = null) {
|
|
2692
2700
|
const { issueSurface, prSurface } = state;
|
|
2693
2701
|
const issue = issueSurface.issue;
|
|
2694
2702
|
if (issueSurface.state === "CLOSED") {
|
|
@@ -2732,17 +2740,20 @@ function decideWork(state, p, baseBranch) {
|
|
|
2732
2740
|
detail: `pull request #${String(surface.pr.number)} comes from a fork; its head branch is not in this repository`
|
|
2733
2741
|
};
|
|
2734
2742
|
}
|
|
2735
|
-
|
|
2743
|
+
const protectedHead = [baseBranch, defaultBranch].filter(
|
|
2744
|
+
(branch) => branch !== null && branch.length > 0
|
|
2745
|
+
);
|
|
2746
|
+
if (protectedHead.includes(surface.pr.headRefName)) {
|
|
2736
2747
|
return {
|
|
2737
2748
|
kind: "skip",
|
|
2738
2749
|
issue,
|
|
2739
2750
|
reason: "unsafe-pr-branch",
|
|
2740
|
-
detail: `pull request #${String(surface.pr.number)} has
|
|
2751
|
+
detail: `pull request #${String(surface.pr.number)} has a protected branch (${surface.pr.headRefName}) as its head, so a turn would have to push to it`
|
|
2741
2752
|
};
|
|
2742
2753
|
}
|
|
2743
2754
|
const agentThreadMessages = surface.threads.flatMap((thread) => thread.comments).filter((comment) => comment.author.toLowerCase() === p.agentUser.toLowerCase());
|
|
2744
2755
|
const prAnalysis = analyzeSurface([...surface.messages, ...agentThreadMessages], p);
|
|
2745
|
-
const actionableThreads = findActionableThreads(surface.threads, p);
|
|
2756
|
+
const actionableThreads = findActionableThreads(surface.threads, p, prAnalysis.lastAgentAt);
|
|
2746
2757
|
const hasPrWork = prAnalysis.hasNewMessage || actionableThreads.length > 0;
|
|
2747
2758
|
if (!hasPrWork && !issueAnalysis.hasNewMessage) {
|
|
2748
2759
|
return {
|
|
@@ -2784,12 +2795,6 @@ function selectLinkedPr(linkedPrs) {
|
|
|
2784
2795
|
const open = linkedPrs.filter((pr) => pr.state === "OPEN");
|
|
2785
2796
|
return open.length === 0 ? null : newestPr(open);
|
|
2786
2797
|
}
|
|
2787
|
-
function agentAnsweredAfter(messages, agentUser, marker) {
|
|
2788
|
-
const agent = agentUser.toLowerCase();
|
|
2789
|
-
return messages.some(
|
|
2790
|
-
(message) => message.author.toLowerCase() === agent && message.kind !== "issue-body" && message.createdAt > marker.createdAt
|
|
2791
|
-
);
|
|
2792
|
-
}
|
|
2793
2798
|
|
|
2794
2799
|
// src/github/workPrompt.ts
|
|
2795
2800
|
function formatThreads(threads) {
|
|
@@ -2798,7 +2803,9 @@ function formatThreads(threads) {
|
|
|
2798
2803
|
const newest = thread.comments.at(-1);
|
|
2799
2804
|
const author = newest?.author ?? "unknown";
|
|
2800
2805
|
const body = newest?.body ?? "";
|
|
2801
|
-
|
|
2806
|
+
const link = thread.url === null ? "" : `
|
|
2807
|
+
${thread.url}`;
|
|
2808
|
+
return `[${author}] ${location}${link}
|
|
2802
2809
|
${body}`;
|
|
2803
2810
|
}).join("\n\n");
|
|
2804
2811
|
}
|
|
@@ -2861,6 +2868,36 @@ function composePrompt(input) {
|
|
|
2861
2868
|
return lines.join("\n");
|
|
2862
2869
|
}
|
|
2863
2870
|
|
|
2871
|
+
// src/github/markerReconciliation.ts
|
|
2872
|
+
function isAuthorized(author, p) {
|
|
2873
|
+
const login2 = author.toLowerCase();
|
|
2874
|
+
return login2 !== p.agentUser.toLowerCase() && p.allowedUsers.some((user) => user.toLowerCase() === login2);
|
|
2875
|
+
}
|
|
2876
|
+
function promptWatermark(analysed) {
|
|
2877
|
+
let watermark = null;
|
|
2878
|
+
for (const surface of analysed) {
|
|
2879
|
+
for (const message of surface.messages) {
|
|
2880
|
+
if (watermark === null || message.createdAt > watermark) watermark = message.createdAt;
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
return watermark;
|
|
2884
|
+
}
|
|
2885
|
+
function analyseAnswer(messages, p, marker, watermark) {
|
|
2886
|
+
let answeredAt = null;
|
|
2887
|
+
for (const message of messages) {
|
|
2888
|
+
if (message.kind === "issue-body") continue;
|
|
2889
|
+
if (message.author.toLowerCase() !== p.agentUser.toLowerCase()) continue;
|
|
2890
|
+
if (message.createdAt <= marker.createdAt) continue;
|
|
2891
|
+
if (answeredAt === null || message.createdAt > answeredAt) answeredAt = message.createdAt;
|
|
2892
|
+
}
|
|
2893
|
+
const since = watermark ?? marker.createdAt;
|
|
2894
|
+
const toReport = messages.filter(
|
|
2895
|
+
(message) => isAuthorized(message.author, p) && message.createdAt > since
|
|
2896
|
+
);
|
|
2897
|
+
const missed = answeredAt === null ? [] : toReport.filter((message) => message.createdAt < answeredAt);
|
|
2898
|
+
return { answeredAt, missed, toReport };
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2864
2901
|
// src/run/runLock.ts
|
|
2865
2902
|
import { writeFileSync, readFileSync as readFileSync3, unlinkSync, mkdirSync, renameSync, linkSync } from "fs";
|
|
2866
2903
|
import { randomUUID } from "crypto";
|
|
@@ -3022,20 +3059,60 @@ function claimStaleLock(path, token, expected) {
|
|
|
3022
3059
|
}
|
|
3023
3060
|
return true;
|
|
3024
3061
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
}
|
|
3029
|
-
const claimed = `${path}.stale.${token}`;
|
|
3062
|
+
var CLAIM_STALE_MS = 6e4;
|
|
3063
|
+
function acquireClaim(path) {
|
|
3064
|
+
const claimPath = `${path}.claim`;
|
|
3030
3065
|
try {
|
|
3031
|
-
|
|
3032
|
-
|
|
3066
|
+
writeFileSync(claimPath, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }), {
|
|
3067
|
+
encoding: "utf8",
|
|
3068
|
+
flag: "wx"
|
|
3069
|
+
});
|
|
3070
|
+
return claimPath;
|
|
3033
3071
|
} catch (err) {
|
|
3034
3072
|
if (err.code !== "EEXIST") throw err;
|
|
3073
|
+
}
|
|
3074
|
+
try {
|
|
3075
|
+
const raw = JSON.parse(readFileSync3(claimPath, "utf8"));
|
|
3076
|
+
const at = raw.at === void 0 ? Number.NaN : Date.parse(raw.at);
|
|
3077
|
+
if (!Number.isNaN(at) && Date.now() - at < CLAIM_STALE_MS) return null;
|
|
3078
|
+
unlinkSync(claimPath);
|
|
3079
|
+
} catch {
|
|
3080
|
+
}
|
|
3081
|
+
try {
|
|
3082
|
+
writeFileSync(claimPath, JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }), {
|
|
3083
|
+
encoding: "utf8",
|
|
3084
|
+
flag: "wx"
|
|
3085
|
+
});
|
|
3086
|
+
return claimPath;
|
|
3087
|
+
} catch {
|
|
3088
|
+
return null;
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
function reclaim(path, command, token, expected) {
|
|
3092
|
+
const claimPath = acquireClaim(path);
|
|
3093
|
+
if (claimPath === null) {
|
|
3035
3094
|
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
|
|
3095
|
+
}
|
|
3096
|
+
try {
|
|
3097
|
+
if (!claimStaleLock(path, token, expected)) {
|
|
3098
|
+
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
|
|
3099
|
+
}
|
|
3100
|
+
const claimed = `${path}.stale.${token}`;
|
|
3101
|
+
try {
|
|
3102
|
+
write(path, command, token, "wx");
|
|
3103
|
+
return { ok: true, handle: makeHandle(path, token) };
|
|
3104
|
+
} catch (err) {
|
|
3105
|
+
if (err.code !== "EEXIST") throw err;
|
|
3106
|
+
return { ok: false, heldBy: readOwner(path) ?? UNKNOWN_OWNER, suspect: false };
|
|
3107
|
+
} finally {
|
|
3108
|
+
try {
|
|
3109
|
+
unlinkSync(claimed);
|
|
3110
|
+
} catch {
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3036
3113
|
} finally {
|
|
3037
3114
|
try {
|
|
3038
|
-
unlinkSync(
|
|
3115
|
+
unlinkSync(claimPath);
|
|
3039
3116
|
} catch {
|
|
3040
3117
|
}
|
|
3041
3118
|
}
|
|
@@ -3306,7 +3383,7 @@ function issueMatchesFilter(surface, settings) {
|
|
|
3306
3383
|
}
|
|
3307
3384
|
function buildIssueState(issue, linkMap) {
|
|
3308
3385
|
const issueSurface = getIssueSurface(issue.number);
|
|
3309
|
-
const linkedPrs = linkMap.get(issue.number) ?? [];
|
|
3386
|
+
const linkedPrs = linkMap.byIssue.get(issue.number) ?? [];
|
|
3310
3387
|
const selected = selectLinkedPr(linkedPrs);
|
|
3311
3388
|
return {
|
|
3312
3389
|
issueSurface,
|
|
@@ -3364,17 +3441,10 @@ function refreshItem(item, settings) {
|
|
|
3364
3441
|
return decideWork(
|
|
3365
3442
|
buildIssueState(item.issue, linkMap),
|
|
3366
3443
|
settings.participants,
|
|
3367
|
-
settings.baseBranch
|
|
3444
|
+
settings.baseBranch,
|
|
3445
|
+
linkMap.defaultBranch
|
|
3368
3446
|
);
|
|
3369
3447
|
}
|
|
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
|
-
}
|
|
3378
3448
|
function readAnsweringSurface(item) {
|
|
3379
3449
|
if (item.turn === "issue-discuss" || item.pr === null) {
|
|
3380
3450
|
return getIssueSurface(item.issue.number).messages;
|
|
@@ -3385,62 +3455,71 @@ function readAnsweringSurface(item) {
|
|
|
3385
3455
|
function markerSurfaceLabel(item) {
|
|
3386
3456
|
return item.turn === "pr-work" && item.pr ? `pull request #${String(item.pr.number)}` : `issue #${String(item.issue.number)}`;
|
|
3387
3457
|
}
|
|
3388
|
-
function
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3458
|
+
function markerSurfaceTarget(item) {
|
|
3459
|
+
return item.turn === "pr-work" && item.pr ? { surface: "pr", number: item.pr.number } : { surface: "issue", number: item.issue.number };
|
|
3460
|
+
}
|
|
3461
|
+
function reportOvertakenMessages(item, analysis) {
|
|
3462
|
+
if (analysis.missed.length === 0) return;
|
|
3463
|
+
const authors = [...new Set(analysis.toReport.map((message) => message.author))].join(", ");
|
|
3464
|
+
const count = analysis.toReport.length;
|
|
3465
|
+
const target = markerSurfaceTarget(item);
|
|
3392
3466
|
try {
|
|
3393
|
-
|
|
3394
|
-
|
|
3467
|
+
postMarker(
|
|
3468
|
+
target.surface,
|
|
3469
|
+
target.number,
|
|
3470
|
+
`automata do-work: ${authors} posted here while this run was already in progress, so ${count === 1 ? "that message was" : "those messages were"} not included in it. Please post again to have them acted on.`
|
|
3471
|
+
);
|
|
3472
|
+
} catch (err) {
|
|
3473
|
+
progress(` warning: could not report the overtaken message(s): ${err.message}
|
|
3474
|
+
`);
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
function reconcileMarker(item, marker, participants, watermark, runError) {
|
|
3478
|
+
let analysis;
|
|
3479
|
+
try {
|
|
3480
|
+
analysis = analyseAnswer(readAnsweringSurface(item), participants, marker, watermark);
|
|
3395
3481
|
} catch (err) {
|
|
3396
3482
|
progress(
|
|
3397
3483
|
` warning: could not re-read issue #${String(item.issue.number)} to check for an answer: ${err.message}
|
|
3398
3484
|
`
|
|
3399
3485
|
);
|
|
3400
|
-
|
|
3401
|
-
}
|
|
3402
|
-
if (answered === "unknown") {
|
|
3403
|
-
const surface2 = markerSurfaceLabel(item);
|
|
3404
|
-
try {
|
|
3405
|
-
updateMarker(
|
|
3406
|
-
marker,
|
|
3407
|
-
`automata do-work: the agent run finished, but automata could not read ${surface2} afterwards to confirm whether an answer was posted. Check this thread and the branch before assuming either. Reply here to have another attempt made.`
|
|
3408
|
-
);
|
|
3409
|
-
} catch (updateErr) {
|
|
3410
|
-
progress(` warning: could not update the marker comment: ${updateErr.message}
|
|
3411
|
-
`);
|
|
3412
|
-
}
|
|
3413
|
-
return { outcome: "answered-no-reply", detail: "could not verify whether an answer was posted" };
|
|
3486
|
+
return reportUnverified(item, marker);
|
|
3414
3487
|
}
|
|
3415
|
-
if (
|
|
3416
|
-
|
|
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
|
-
}
|
|
3488
|
+
if (analysis.answeredAt !== null) {
|
|
3489
|
+
reportOvertakenMessages(item, analysis);
|
|
3430
3490
|
try {
|
|
3431
3491
|
deleteMarker(marker);
|
|
3432
3492
|
} catch (err) {
|
|
3433
3493
|
progress(` warning: could not delete the marker comment: ${err.message}
|
|
3434
3494
|
`);
|
|
3435
3495
|
}
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3496
|
+
if (analysis.missed.length > 0) {
|
|
3497
|
+
return {
|
|
3498
|
+
outcome: "answered-no-reply",
|
|
3499
|
+
detail: `answered (${String(analysis.toReport.length)} message(s) arrived mid-run and were flagged)`
|
|
3500
|
+
};
|
|
3439
3501
|
}
|
|
3440
3502
|
return runError === null ? { outcome: "answered", detail: "answered" } : { outcome: "answered", detail: `answered, but the run reported: ${runError.message}` };
|
|
3441
3503
|
}
|
|
3504
|
+
return reportNoAnswer(item, marker, runError);
|
|
3505
|
+
}
|
|
3506
|
+
function reportUnverified(item, marker) {
|
|
3442
3507
|
const surface = markerSurfaceLabel(item);
|
|
3443
|
-
|
|
3508
|
+
try {
|
|
3509
|
+
updateMarker(
|
|
3510
|
+
marker,
|
|
3511
|
+
`automata do-work: the agent run finished, but automata could not read ${surface} afterwards to confirm whether an answer was posted. Check this thread and the branch before assuming either. Reply here to have another attempt made.`
|
|
3512
|
+
);
|
|
3513
|
+
} catch (err) {
|
|
3514
|
+
progress(` warning: could not update the marker comment: ${err.message}
|
|
3515
|
+
`);
|
|
3516
|
+
}
|
|
3517
|
+
return { outcome: "answered-no-reply", detail: "could not verify whether an answer was posted" };
|
|
3518
|
+
}
|
|
3519
|
+
function reportNoAnswer(item, marker, runError) {
|
|
3520
|
+
const surface = markerSurfaceLabel(item);
|
|
3521
|
+
const sideEffects = item.turn === "issue-discuss" ? "It may still have created a branch or opened a pull request \u2014 check before assuming otherwise." : `It may still have changed the branch \`${item.branch}\` \u2014 check it before assuming otherwise.`;
|
|
3522
|
+
const explanation = runError === null ? `automata do-work: the agent run finished without posting an answer on ${surface}. ${sideEffects} Reply on ${surface} to have another attempt made.` : `automata do-work: the agent run failed before posting an answer (${runError.message}). ${sideEffects} Reply on ${surface} to have another attempt made.`;
|
|
3444
3523
|
try {
|
|
3445
3524
|
updateMarker(marker, explanation);
|
|
3446
3525
|
} catch (err) {
|
|
@@ -3465,26 +3544,28 @@ function repairIssueLink(item, baseBranch) {
|
|
|
3465
3544
|
if (branch === baseBranch) {
|
|
3466
3545
|
progress(` issue #${String(item.issue.number)} is still in discussion (no branch was created).
|
|
3467
3546
|
`);
|
|
3468
|
-
return;
|
|
3547
|
+
return false;
|
|
3469
3548
|
}
|
|
3470
3549
|
const pr = getCurrentBranchPr();
|
|
3471
3550
|
if (!pr) {
|
|
3472
3551
|
progress(` issue #${String(item.issue.number)} is still in discussion (no pull request).
|
|
3473
3552
|
`);
|
|
3474
|
-
return;
|
|
3553
|
+
return false;
|
|
3475
3554
|
}
|
|
3476
3555
|
const closesRef = new RegExp(String.raw`\bcloses\s+#` + String(item.issue.number) + String.raw`\b`, "i");
|
|
3477
3556
|
if (closesRef.test(pr.body)) {
|
|
3478
3557
|
progress(` pull request #${String(pr.number)} already closes issue #${String(item.issue.number)}.
|
|
3479
3558
|
`);
|
|
3480
|
-
return;
|
|
3559
|
+
return true;
|
|
3481
3560
|
}
|
|
3482
3561
|
addClosesRefToPr(pr.number, item.issue.number);
|
|
3483
3562
|
progress(` linked pull request #${String(pr.number)} to issue #${String(item.issue.number)}.
|
|
3484
3563
|
`);
|
|
3564
|
+
return true;
|
|
3485
3565
|
} catch (err) {
|
|
3486
3566
|
progress(` warning: could not link a pull request to issue #${String(item.issue.number)}: ${err.message}
|
|
3487
3567
|
`);
|
|
3568
|
+
return false;
|
|
3488
3569
|
}
|
|
3489
3570
|
}
|
|
3490
3571
|
async function processItem(planned, settings, silent) {
|
|
@@ -3534,6 +3615,10 @@ async function processItem(planned, settings, silent) {
|
|
|
3534
3615
|
inFlightMarker = null;
|
|
3535
3616
|
return { ...base, outcome: "skipped", detail: "issue pickup note failed" };
|
|
3536
3617
|
}
|
|
3618
|
+
const watermark = promptWatermark([
|
|
3619
|
+
item.issueAnalysis,
|
|
3620
|
+
...item.prAnalysis === null ? [] : [item.prAnalysis]
|
|
3621
|
+
]);
|
|
3537
3622
|
const prompt = composePrompt({
|
|
3538
3623
|
item,
|
|
3539
3624
|
repo: getRepoSlug(),
|
|
@@ -3567,13 +3652,18 @@ async function processItem(planned, settings, silent) {
|
|
|
3567
3652
|
}
|
|
3568
3653
|
const ranExecutor = true;
|
|
3569
3654
|
inFlightMarker = null;
|
|
3570
|
-
const reconciled = reconcileMarker(item, marker, settings.participants, runError);
|
|
3655
|
+
const reconciled = reconcileMarker(item, marker, settings.participants, watermark, runError);
|
|
3571
3656
|
progress(` ${reconciled.detail}
|
|
3572
3657
|
`);
|
|
3658
|
+
let outcome = reconciled;
|
|
3573
3659
|
if (item.turn === "issue-discuss") {
|
|
3574
|
-
repairIssueLink(item, settings.baseBranch);
|
|
3660
|
+
const linked = repairIssueLink(item, settings.baseBranch);
|
|
3661
|
+
if (linked && outcome.outcome === "answered-no-reply") {
|
|
3662
|
+
outcome = { outcome: "answered", detail: "opened a pull request (no issue comment)" };
|
|
3663
|
+
progress(" a pull request was opened, so the turn is counted as answered.\n");
|
|
3664
|
+
}
|
|
3575
3665
|
}
|
|
3576
|
-
return { ...base, outcome:
|
|
3666
|
+
return { ...base, outcome: outcome.outcome, detail: outcome.detail, ranExecutor };
|
|
3577
3667
|
}
|
|
3578
3668
|
function summarize(reports) {
|
|
3579
3669
|
out("\nTick summary:\n");
|
|
@@ -3670,7 +3760,14 @@ var doWorkCommand = new Command6("do-work").description(
|
|
|
3670
3760
|
async function runTick(settings, options) {
|
|
3671
3761
|
const issues = discoverIssues(settings);
|
|
3672
3762
|
const linkMap = getOpenPrLinkMap();
|
|
3673
|
-
const decisions = issues.map(
|
|
3763
|
+
const decisions = issues.map(
|
|
3764
|
+
(issue) => decideWork(
|
|
3765
|
+
buildIssueState(issue, linkMap),
|
|
3766
|
+
settings.participants,
|
|
3767
|
+
settings.baseBranch,
|
|
3768
|
+
linkMap.defaultBranch
|
|
3769
|
+
)
|
|
3770
|
+
);
|
|
3674
3771
|
const items = decisions.flatMap((decision) => decision.kind === "work" ? [decision.item] : []);
|
|
3675
3772
|
const planText = `Work plan (${String(items.length)} of ${String(issues.length)} issues need an answer):
|
|
3676
3773
|
${describePlan(decisions)}`;
|