automata-cli 0.12.0-develop.533 → 0.12.0-develop.562
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 +412 -109
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2633,7 +2633,7 @@ function addLabel(target, name, description) {
|
|
|
2633
2633
|
const edit = target.kind === "pr" ? "pr" : "issue";
|
|
2634
2634
|
const args = [edit, "edit", String(target.number), "--add-label", name];
|
|
2635
2635
|
let result = run4("gh", args);
|
|
2636
|
-
if (result.status !== 0 && isMissingLabelError(result.stderr)) {
|
|
2636
|
+
if (result.status !== 0 && isMissingLabelError(result.stderr, name)) {
|
|
2637
2637
|
const created = run4("gh", ["label", "create", name, "--description", description]);
|
|
2638
2638
|
if (created.status !== 0 && !/already exists/i.test(created.stderr)) {
|
|
2639
2639
|
throw new Error(created.stderr.trim() || `Failed to create the label ${name}.`);
|
|
@@ -2732,8 +2732,11 @@ var MISSING_LABEL_PATTERNS = [
|
|
|
2732
2732
|
/could not resolve to a label/i,
|
|
2733
2733
|
/\blabels?\b[^\n]*\b(?:not found|does not exist)\b/i
|
|
2734
2734
|
];
|
|
2735
|
-
function isMissingLabelError(stderr) {
|
|
2736
|
-
|
|
2735
|
+
function isMissingLabelError(stderr, name) {
|
|
2736
|
+
if (MISSING_LABEL_PATTERNS.some((pattern) => pattern.test(stderr))) {
|
|
2737
|
+
return true;
|
|
2738
|
+
}
|
|
2739
|
+
return name !== void 0 && stderr.includes(`'${name}' not found`);
|
|
2737
2740
|
}
|
|
2738
2741
|
function createDraftPullRequest(input) {
|
|
2739
2742
|
const base = [
|
|
@@ -2752,7 +2755,7 @@ function createDraftPullRequest(input) {
|
|
|
2752
2755
|
if (input.label !== void 0 && input.label.length > 0) {
|
|
2753
2756
|
const labelled = run4("gh", [...base, "--label", input.label]);
|
|
2754
2757
|
if (labelled.status === 0) return parseCreatedPrUrl(labelled.stdout, input.head);
|
|
2755
|
-
if (!isMissingLabelError(labelled.stderr)) {
|
|
2758
|
+
if (!isMissingLabelError(labelled.stderr, input.label)) {
|
|
2756
2759
|
throw new Error(
|
|
2757
2760
|
labelled.stderr.trim() || `Failed to open a draft pull request for ${input.head}.`
|
|
2758
2761
|
);
|
|
@@ -2966,7 +2969,7 @@ function runClaude(prompt, options = {}) {
|
|
|
2966
2969
|
if (options.printSteps !== true) return;
|
|
2967
2970
|
try {
|
|
2968
2971
|
const event = JSON.parse(line);
|
|
2969
|
-
formatEvent(event, turnCount);
|
|
2972
|
+
formatEvent(event, turnCount, options.echoResult !== false);
|
|
2970
2973
|
if (event["type"] === "assistant") turnCount++;
|
|
2971
2974
|
} catch {
|
|
2972
2975
|
}
|
|
@@ -3056,7 +3059,7 @@ function formatContentBlock(block) {
|
|
|
3056
3059
|
}
|
|
3057
3060
|
return null;
|
|
3058
3061
|
}
|
|
3059
|
-
function formatResultEvent(event) {
|
|
3062
|
+
function formatResultEvent(event, echoResult) {
|
|
3060
3063
|
const result = event["result"];
|
|
3061
3064
|
const cost = event["cost_usd"];
|
|
3062
3065
|
const duration = event["duration_ms"];
|
|
@@ -3070,16 +3073,16 @@ function formatResultEvent(event) {
|
|
|
3070
3073
|
process.stderr.write(` [info] ${parts.join(" | ")}
|
|
3071
3074
|
`);
|
|
3072
3075
|
}
|
|
3073
|
-
if (result) {
|
|
3076
|
+
if (result && echoResult) {
|
|
3074
3077
|
process.stdout.write(result + "\n");
|
|
3075
3078
|
}
|
|
3076
3079
|
}
|
|
3077
|
-
function formatEvent(event, turnCount) {
|
|
3080
|
+
function formatEvent(event, turnCount, echoResult = true) {
|
|
3078
3081
|
const type = event["type"];
|
|
3079
3082
|
if (type === "assistant") {
|
|
3080
3083
|
formatAssistantEvent(event, turnCount);
|
|
3081
3084
|
} else if (type === "result") {
|
|
3082
|
-
formatResultEvent(event);
|
|
3085
|
+
formatResultEvent(event, echoResult);
|
|
3083
3086
|
}
|
|
3084
3087
|
}
|
|
3085
3088
|
function asText(value, fallback) {
|
|
@@ -5432,6 +5435,44 @@ async function scrubExcerpt(execution, excerpt, timeoutMs = SECOND_OPINION_TIMEO
|
|
|
5432
5435
|
return result.ok ? interpretScrubOutput(result.stdout, excerpt) : result;
|
|
5433
5436
|
}
|
|
5434
5437
|
|
|
5438
|
+
// src/run/claudeAuth.ts
|
|
5439
|
+
var CLAUDE_AUTH_PATTERNS = [
|
|
5440
|
+
/Failed to authenticate/i,
|
|
5441
|
+
/OAuth (?:session|token) (?:has )?expired/i,
|
|
5442
|
+
/Invalid API key\s*[·\-:]\s*Please run \/login/i,
|
|
5443
|
+
/Please run \/login/i
|
|
5444
|
+
];
|
|
5445
|
+
var CLAUDE_AUTH_MARKER = "<!-- automata:claude-auth -->";
|
|
5446
|
+
function detectClaudeAuthFailure(text) {
|
|
5447
|
+
return CLAUDE_AUTH_PATTERNS.some((pattern) => pattern.test(text));
|
|
5448
|
+
}
|
|
5449
|
+
var CLAUDE_AUTH_TAIL_MAX = 8192;
|
|
5450
|
+
var ClaudeAuthTail = class {
|
|
5451
|
+
text = "";
|
|
5452
|
+
output(_stream, chunk) {
|
|
5453
|
+
this.text = (this.text + chunk).slice(-CLAUDE_AUTH_TAIL_MAX);
|
|
5454
|
+
}
|
|
5455
|
+
exited() {
|
|
5456
|
+
}
|
|
5457
|
+
get tail() {
|
|
5458
|
+
return this.text;
|
|
5459
|
+
}
|
|
5460
|
+
};
|
|
5461
|
+
var CLAUDE_AUTH_REASON = "Claude Code is not signed in (its login expired and could not be refreshed)";
|
|
5462
|
+
var CLAUDE_AUTH_REMEDY = "Run `claude /login` again on the machine that runs automata (inside the devcontainer, if it runs there), then reply to have another attempt made.";
|
|
5463
|
+
function renderClaudeAuthComment(surface, excerpt) {
|
|
5464
|
+
const lines = [
|
|
5465
|
+
`automata do-work: the agent run stopped before it posted an answer on ${surface}. ${CLAUDE_AUTH_REASON}.`,
|
|
5466
|
+
CLAUDE_AUTH_REMEDY,
|
|
5467
|
+
"This comment is posted once. Automata does not post it again while the problem remains."
|
|
5468
|
+
];
|
|
5469
|
+
if (excerpt.trim() !== "") {
|
|
5470
|
+
lines.push("", "Last lines of the agent's output:", "```text", excerpt.replaceAll("```", "'''"), "```");
|
|
5471
|
+
}
|
|
5472
|
+
return `${lines.join("\n")}
|
|
5473
|
+
${CLAUDE_AUTH_MARKER}`;
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5435
5476
|
// src/run/answerRecovery.ts
|
|
5436
5477
|
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
5437
5478
|
var RECOVERY_TIMEOUT_MS = 3e5;
|
|
@@ -6256,6 +6297,14 @@ function describeTickHistory(ticks, cadence, now, logPath, lines, problems) {
|
|
|
6256
6297
|
)
|
|
6257
6298
|
);
|
|
6258
6299
|
}
|
|
6300
|
+
if (ticks[0].note === "claude-auth") {
|
|
6301
|
+
problems.push(
|
|
6302
|
+
finding(
|
|
6303
|
+
`${CLAUDE_AUTH_REASON}, so the last tick stopped before it answered; ${CLAUDE_AUTH_REMEDY}`,
|
|
6304
|
+
"claude /login"
|
|
6305
|
+
)
|
|
6306
|
+
);
|
|
6307
|
+
}
|
|
6259
6308
|
const lockHeld = ticks.filter((tick) => tick.note === "lock-held").length;
|
|
6260
6309
|
const withRuns = ticks.filter((tick) => tick.runs > 0).length;
|
|
6261
6310
|
lines.push(
|
|
@@ -6635,14 +6684,20 @@ function requireParticipants(config) {
|
|
|
6635
6684
|
);
|
|
6636
6685
|
}
|
|
6637
6686
|
if (!config.issueDiscoveryTechnique) {
|
|
6638
|
-
failSettings(
|
|
6687
|
+
failSettings(
|
|
6688
|
+
"No issue discovery technique configured. Run `automata config set issue-discovery-technique <value>`."
|
|
6689
|
+
);
|
|
6639
6690
|
}
|
|
6640
6691
|
if (!config.issueDiscoveryValue) {
|
|
6641
|
-
failSettings(
|
|
6692
|
+
failSettings(
|
|
6693
|
+
"No issue discovery value configured. Run `automata config set issue-discovery-value <value>`."
|
|
6694
|
+
);
|
|
6642
6695
|
}
|
|
6643
6696
|
const allowedUsers = (config.allowedUsers ?? []).filter((user) => user.trim().length > 0);
|
|
6644
6697
|
if (allowedUsers.length === 0) {
|
|
6645
|
-
failSettings(
|
|
6698
|
+
failSettings(
|
|
6699
|
+
"No allowed users configured. Run `automata config set allowed-users <user1,user2>`."
|
|
6700
|
+
);
|
|
6646
6701
|
}
|
|
6647
6702
|
const agentUser = (config.agentUser ?? "").trim();
|
|
6648
6703
|
if (agentUser.length === 0) {
|
|
@@ -6861,8 +6916,20 @@ function validateDoWorkConfig(section2) {
|
|
|
6861
6916
|
failSettings(`doWork.executor must be 'claude' or 'codex', got ${JSON.stringify(executor)}.`);
|
|
6862
6917
|
}
|
|
6863
6918
|
validateOptionalString(section2, "baseBranch", "doWork.baseBranch");
|
|
6864
|
-
validateOptionalInt(
|
|
6865
|
-
|
|
6919
|
+
validateOptionalInt(
|
|
6920
|
+
section2,
|
|
6921
|
+
"maxRunsPerTick",
|
|
6922
|
+
"doWork.maxRunsPerTick",
|
|
6923
|
+
0,
|
|
6924
|
+
"a non-negative integer (0 = unlimited)"
|
|
6925
|
+
);
|
|
6926
|
+
validateOptionalInt(
|
|
6927
|
+
section2,
|
|
6928
|
+
"lockStaleMinutes",
|
|
6929
|
+
"doWork.lockStaleMinutes",
|
|
6930
|
+
1,
|
|
6931
|
+
"a positive integer"
|
|
6932
|
+
);
|
|
6866
6933
|
validateOptionalBoolean(section2, "dumpOnBlock", "doWork.dumpOnBlock");
|
|
6867
6934
|
validateOptionalBoolean(section2, "postRunLog", "doWork.postRunLog");
|
|
6868
6935
|
validateProtectedBranches(section2["protectedBranches"]);
|
|
@@ -6894,7 +6961,9 @@ function validateSettingContainer(value, container, keys) {
|
|
|
6894
6961
|
}
|
|
6895
6962
|
for (const key2 of Object.keys(value)) {
|
|
6896
6963
|
if (!keys.includes(key2)) {
|
|
6897
|
-
failSettings(
|
|
6964
|
+
failSettings(
|
|
6965
|
+
`doWork.${container}.${key2} is not a recognised setting; expected one of: ${keys.join(", ")}.`
|
|
6966
|
+
);
|
|
6898
6967
|
}
|
|
6899
6968
|
}
|
|
6900
6969
|
}
|
|
@@ -6906,7 +6975,11 @@ function orphanPassEnabled(settings) {
|
|
|
6906
6975
|
}
|
|
6907
6976
|
function discoverIssues(settings) {
|
|
6908
6977
|
if (!issuePassEnabled(settings)) return [];
|
|
6909
|
-
const candidates = listCandidateIssues(
|
|
6978
|
+
const candidates = listCandidateIssues(
|
|
6979
|
+
settings.technique,
|
|
6980
|
+
settings.discoveryValue,
|
|
6981
|
+
settings.limit
|
|
6982
|
+
);
|
|
6910
6983
|
if (settings.onlyIssue === void 0) {
|
|
6911
6984
|
if (candidates.length === settings.limit) {
|
|
6912
6985
|
progress(
|
|
@@ -7003,11 +7076,15 @@ function claimIssue(item, settings) {
|
|
|
7003
7076
|
if (!item.needsAssignment || item.issue === null) return;
|
|
7004
7077
|
try {
|
|
7005
7078
|
assignIssueToAgent(item.issue.number, settings.participants.agentUser);
|
|
7006
|
-
progress(
|
|
7007
|
-
`)
|
|
7079
|
+
progress(
|
|
7080
|
+
` assigned issue #${String(item.issue.number)} to ${settings.participants.agentUser}.
|
|
7081
|
+
`
|
|
7082
|
+
);
|
|
7008
7083
|
} catch (err) {
|
|
7009
|
-
progress(
|
|
7010
|
-
`)
|
|
7084
|
+
progress(
|
|
7085
|
+
` warning: could not assign issue #${String(item.issue.number)}: ${err.message}
|
|
7086
|
+
`
|
|
7087
|
+
);
|
|
7011
7088
|
}
|
|
7012
7089
|
}
|
|
7013
7090
|
function claimPr(prNumber, agentUser) {
|
|
@@ -7016,8 +7093,10 @@ function claimPr(prNumber, agentUser) {
|
|
|
7016
7093
|
progress(` assigned pull request #${String(prNumber)} to ${agentUser}.
|
|
7017
7094
|
`);
|
|
7018
7095
|
} catch (err) {
|
|
7019
|
-
progress(
|
|
7020
|
-
`)
|
|
7096
|
+
progress(
|
|
7097
|
+
` warning: could not assign pull request #${String(prNumber)}: ${err.message}
|
|
7098
|
+
`
|
|
7099
|
+
);
|
|
7021
7100
|
}
|
|
7022
7101
|
}
|
|
7023
7102
|
function notePickupOnIssue(item, marker) {
|
|
@@ -7030,13 +7109,17 @@ function notePickupOnIssue(item, marker) {
|
|
|
7030
7109
|
`automata do-work: picked this up on pull request #${String(item.pr.number)} \u2014 ${item.pr.url}`
|
|
7031
7110
|
);
|
|
7032
7111
|
} catch (err) {
|
|
7033
|
-
progress(
|
|
7034
|
-
`)
|
|
7112
|
+
progress(
|
|
7113
|
+
` skipped: could not note the pickup on issue #${String(item.issue.number)} \u2014 ${err.message}
|
|
7114
|
+
`
|
|
7115
|
+
);
|
|
7035
7116
|
try {
|
|
7036
7117
|
deleteMarker(marker);
|
|
7037
7118
|
} catch (deleteErr) {
|
|
7038
|
-
progress(
|
|
7039
|
-
`
|
|
7119
|
+
progress(
|
|
7120
|
+
` warning: could not withdraw the working marker: ${deleteErr.message}
|
|
7121
|
+
`
|
|
7122
|
+
);
|
|
7040
7123
|
}
|
|
7041
7124
|
return false;
|
|
7042
7125
|
}
|
|
@@ -7049,8 +7132,10 @@ function reportIssueMessagesBuriedByNote(item, note, participants) {
|
|
|
7049
7132
|
const messages = getIssueSurface(item.issue.number).messages;
|
|
7050
7133
|
buried = messagesBetween(messages, participants, watermark, note.createdAt);
|
|
7051
7134
|
} catch (err) {
|
|
7052
|
-
progress(
|
|
7053
|
-
`)
|
|
7135
|
+
progress(
|
|
7136
|
+
` warning: could not re-read issue #${String(item.issue.number)}: ${err.message}
|
|
7137
|
+
`
|
|
7138
|
+
);
|
|
7054
7139
|
return 0;
|
|
7055
7140
|
}
|
|
7056
7141
|
if (buried.length === 0) return 0;
|
|
@@ -7062,8 +7147,10 @@ function reportIssueMessagesBuriedByNote(item, note, participants) {
|
|
|
7062
7147
|
`automata do-work: ${authors} posted here while this issue was being picked up, so ${buried.length === 1 ? "that message was" : "those messages were"} not included in the run. Please post again to have them acted on.`
|
|
7063
7148
|
);
|
|
7064
7149
|
} catch (err) {
|
|
7065
|
-
progress(
|
|
7066
|
-
`)
|
|
7150
|
+
progress(
|
|
7151
|
+
` warning: could not report the buried issue message(s): ${err.message}
|
|
7152
|
+
`
|
|
7153
|
+
);
|
|
7067
7154
|
}
|
|
7068
7155
|
return buried.length;
|
|
7069
7156
|
}
|
|
@@ -7081,7 +7168,9 @@ function refreshOrphanItem(item, linkMap, settings, policy) {
|
|
|
7081
7168
|
const number = item.pr?.number ?? 0;
|
|
7082
7169
|
const stillOrphan = linkMap.orphans.some((candidate) => candidate.pr.number === number);
|
|
7083
7170
|
if (!stillOrphan) {
|
|
7084
|
-
const linkedTo = issuesClosedBy(linkMap, number).map(
|
|
7171
|
+
const linkedTo = issuesClosedBy(linkMap, number).map(
|
|
7172
|
+
(issueNumber) => `#${String(issueNumber)}`
|
|
7173
|
+
);
|
|
7085
7174
|
return {
|
|
7086
7175
|
kind: "skip",
|
|
7087
7176
|
issue: null,
|
|
@@ -7123,7 +7212,8 @@ function reportOvertakenMessages(item, analysis) {
|
|
|
7123
7212
|
`);
|
|
7124
7213
|
}
|
|
7125
7214
|
}
|
|
7126
|
-
async function reconcileMarker(item, marker, participants, watermark,
|
|
7215
|
+
async function reconcileMarker(item, marker, participants, watermark, run5) {
|
|
7216
|
+
const { runError, diagnostics, recovery, claudeAuth } = run5;
|
|
7127
7217
|
let analysis;
|
|
7128
7218
|
try {
|
|
7129
7219
|
analysis = analyseAnswer(readAnsweringSurface(item), participants, marker, watermark);
|
|
@@ -7136,7 +7226,8 @@ async function reconcileMarker(item, marker, participants, watermark, runError,
|
|
|
7136
7226
|
}
|
|
7137
7227
|
let recoveryNote = null;
|
|
7138
7228
|
let recovered = false;
|
|
7139
|
-
|
|
7229
|
+
const canRecover = runError === null && recovery !== null && claudeAuth === null;
|
|
7230
|
+
if (analysis.answeredAt === null && canRecover) {
|
|
7140
7231
|
const attempt = await attemptRecovery(item, marker, participants, watermark, recovery);
|
|
7141
7232
|
if ("analysis" in attempt) {
|
|
7142
7233
|
analysis = attempt.analysis;
|
|
@@ -7146,31 +7237,74 @@ async function reconcileMarker(item, marker, participants, watermark, runError,
|
|
|
7146
7237
|
}
|
|
7147
7238
|
}
|
|
7148
7239
|
if (analysis.answeredAt !== null) {
|
|
7149
|
-
|
|
7150
|
-
try {
|
|
7151
|
-
deleteMarker(marker);
|
|
7152
|
-
} catch (err) {
|
|
7153
|
-
progress(` warning: could not delete the marker comment: ${err.message}
|
|
7154
|
-
`);
|
|
7155
|
-
}
|
|
7156
|
-
if (analysis.missed.length > 0) {
|
|
7157
|
-
return {
|
|
7158
|
-
outcome: "answered-no-reply",
|
|
7159
|
-
detail: `answered (${String(analysis.toReport.length)} message(s) arrived mid-run and were flagged)`,
|
|
7160
|
-
reason: "flagged"
|
|
7161
|
-
};
|
|
7162
|
-
}
|
|
7163
|
-
if (recovered) {
|
|
7164
|
-
return { outcome: "answered", detail: "answered (recovered from the run transcript)", reason: "answered" };
|
|
7165
|
-
}
|
|
7166
|
-
return runError === null ? { outcome: "answered", detail: "answered", reason: "answered" } : { outcome: "answered", detail: `answered, but the run reported: ${runError.message}`, reason: "answered" };
|
|
7240
|
+
return reportAnswered(item, marker, analysis, runError, recovered);
|
|
7167
7241
|
}
|
|
7242
|
+
if (claudeAuth !== null) return reportClaudeAuth(item, marker, participants, claudeAuth);
|
|
7168
7243
|
return reportNoAnswer(item, marker, runError, diagnostics, recoveryNote);
|
|
7169
7244
|
}
|
|
7245
|
+
function reportAnswered(item, marker, analysis, runError, recovered) {
|
|
7246
|
+
reportOvertakenMessages(item, analysis);
|
|
7247
|
+
try {
|
|
7248
|
+
deleteMarker(marker);
|
|
7249
|
+
} catch (err) {
|
|
7250
|
+
progress(` warning: could not delete the marker comment: ${err.message}
|
|
7251
|
+
`);
|
|
7252
|
+
}
|
|
7253
|
+
if (analysis.missed.length > 0) {
|
|
7254
|
+
return {
|
|
7255
|
+
outcome: "answered-no-reply",
|
|
7256
|
+
detail: `answered (${String(analysis.toReport.length)} message(s) arrived mid-run and were flagged)`,
|
|
7257
|
+
reason: "flagged"
|
|
7258
|
+
};
|
|
7259
|
+
}
|
|
7260
|
+
if (recovered) {
|
|
7261
|
+
return {
|
|
7262
|
+
outcome: "answered",
|
|
7263
|
+
detail: "answered (recovered from the run transcript)",
|
|
7264
|
+
reason: "answered"
|
|
7265
|
+
};
|
|
7266
|
+
}
|
|
7267
|
+
return runError === null ? { outcome: "answered", detail: "answered", reason: "answered" } : {
|
|
7268
|
+
outcome: "answered",
|
|
7269
|
+
detail: `answered, but the run reported: ${runError.message}`,
|
|
7270
|
+
reason: "answered"
|
|
7271
|
+
};
|
|
7272
|
+
}
|
|
7273
|
+
function hasClaudeAuthComment(item, marker, participants) {
|
|
7274
|
+
try {
|
|
7275
|
+
const before = new Date(marker.createdAt).getTime();
|
|
7276
|
+
const agent = participants.agentUser.toLowerCase();
|
|
7277
|
+
const last = readAnsweringSurface(item).findLast(
|
|
7278
|
+
(message) => message.author.toLowerCase() === agent && new Date(message.createdAt).getTime() < before
|
|
7279
|
+
);
|
|
7280
|
+
return last?.body.includes(CLAUDE_AUTH_MARKER) === true;
|
|
7281
|
+
} catch {
|
|
7282
|
+
return false;
|
|
7283
|
+
}
|
|
7284
|
+
}
|
|
7285
|
+
function reportClaudeAuth(item, marker, participants, excerpt) {
|
|
7286
|
+
const surface = markerSurfaceLabel(item);
|
|
7287
|
+
const result = {
|
|
7288
|
+
outcome: "failed",
|
|
7289
|
+
detail: `${CLAUDE_AUTH_REASON}. ${CLAUDE_AUTH_REMEDY}`,
|
|
7290
|
+
reason: "no-answer",
|
|
7291
|
+
claudeAuth: true
|
|
7292
|
+
};
|
|
7293
|
+
const alreadyTold = hasClaudeAuthComment(item, marker, participants);
|
|
7294
|
+
try {
|
|
7295
|
+
if (alreadyTold) deleteMarker(marker);
|
|
7296
|
+
else updateMarker(marker, renderClaudeAuthComment(surface, excerpt));
|
|
7297
|
+
} catch (err) {
|
|
7298
|
+
warnMarkerNotUpdated(surface, err, "that Claude Code is not signed in");
|
|
7299
|
+
}
|
|
7300
|
+
return result;
|
|
7301
|
+
}
|
|
7170
7302
|
async function attemptRecovery(item, marker, participants, watermark, recovery) {
|
|
7171
7303
|
const surface = markerSurfaceLabel(item);
|
|
7172
|
-
progress(
|
|
7173
|
-
`
|
|
7304
|
+
progress(
|
|
7305
|
+
` recovery: the run posted no answer on ${surface}; asking the model to write it from the transcript.
|
|
7306
|
+
`
|
|
7307
|
+
);
|
|
7174
7308
|
const reread = () => analyseAnswer(readAnsweringSurface(item), participants, marker, watermark);
|
|
7175
7309
|
const fail3 = (reason) => {
|
|
7176
7310
|
progress(` recovery: failed \u2014 ${reason}
|
|
@@ -7202,12 +7336,15 @@ async function attemptRecovery(item, marker, participants, watermark, recovery)
|
|
|
7202
7336
|
}
|
|
7203
7337
|
try {
|
|
7204
7338
|
const verified2 = reread();
|
|
7205
|
-
if (verified2.answeredAt === null)
|
|
7339
|
+
if (verified2.answeredAt === null)
|
|
7340
|
+
return fail3(`the recovered answer is not visible on ${surface} after posting`);
|
|
7206
7341
|
progress(` recovery: posted the answer recovered from the run transcript on ${surface}.
|
|
7207
7342
|
`);
|
|
7208
7343
|
return { analysis: verified2, posted: true };
|
|
7209
7344
|
} catch (err) {
|
|
7210
|
-
return fail3(
|
|
7345
|
+
return fail3(
|
|
7346
|
+
`could not re-read ${surface} to verify the recovered answer: ${err.message}`
|
|
7347
|
+
);
|
|
7211
7348
|
}
|
|
7212
7349
|
}
|
|
7213
7350
|
function reportUnverified(item, marker) {
|
|
@@ -7227,6 +7364,13 @@ function reportUnverified(item, marker) {
|
|
|
7227
7364
|
reason: "unverified"
|
|
7228
7365
|
};
|
|
7229
7366
|
}
|
|
7367
|
+
function warnMarkerNotUpdated(surface, err, what) {
|
|
7368
|
+
progress(
|
|
7369
|
+
` warning: could not update the marker comment on ${surface}: ${err.message}
|
|
7370
|
+
the humans have not been told ${what}.
|
|
7371
|
+
`
|
|
7372
|
+
);
|
|
7373
|
+
}
|
|
7230
7374
|
async function reportNoAnswer(item, marker, runError, diagnostics, recoveryNote = null) {
|
|
7231
7375
|
const surface = markerSurfaceLabel(item);
|
|
7232
7376
|
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.`;
|
|
@@ -7235,13 +7379,13 @@ async function reportNoAnswer(item, marker, runError, diagnostics, recoveryNote
|
|
|
7235
7379
|
try {
|
|
7236
7380
|
updateMarker(marker, explanation + recovery + (await diagnostics?.() ?? ""));
|
|
7237
7381
|
} catch (err) {
|
|
7238
|
-
|
|
7239
|
-
` warning: could not update the marker comment on ${surface}: ${err.message}
|
|
7240
|
-
the humans have not been told that this run produced no answer.
|
|
7241
|
-
`
|
|
7242
|
-
);
|
|
7382
|
+
warnMarkerNotUpdated(surface, err, "that this run produced no answer");
|
|
7243
7383
|
}
|
|
7244
|
-
return runError === null ? {
|
|
7384
|
+
return runError === null ? {
|
|
7385
|
+
outcome: "answered-no-reply",
|
|
7386
|
+
detail: "run finished but posted no answer",
|
|
7387
|
+
reason: "no-answer"
|
|
7388
|
+
} : { outcome: "failed", detail: `run failed: ${runError.message}`, reason: "no-answer" };
|
|
7245
7389
|
}
|
|
7246
7390
|
async function invokeExecutor(prompt, execution, silent, sink2) {
|
|
7247
7391
|
beat({
|
|
@@ -7253,7 +7397,12 @@ async function invokeExecutor(prompt, execution, silent, sink2) {
|
|
|
7253
7397
|
await runCodex(prompt, { model: execution.model, effort: execution.effort, sink: sink2 });
|
|
7254
7398
|
return;
|
|
7255
7399
|
}
|
|
7256
|
-
await runClaude(prompt, {
|
|
7400
|
+
await runClaude(prompt, {
|
|
7401
|
+
model: execution.model,
|
|
7402
|
+
effort: execution.effort,
|
|
7403
|
+
printSteps: !silent,
|
|
7404
|
+
sink: sink2
|
|
7405
|
+
});
|
|
7257
7406
|
}
|
|
7258
7407
|
function repairIssueLink(item, baseBranch, agentUser) {
|
|
7259
7408
|
const issue = item.issue;
|
|
@@ -7261,8 +7410,10 @@ function repairIssueLink(item, baseBranch, agentUser) {
|
|
|
7261
7410
|
try {
|
|
7262
7411
|
const branch = getCurrentBranch();
|
|
7263
7412
|
if (branch === baseBranch) {
|
|
7264
|
-
progress(
|
|
7265
|
-
`)
|
|
7413
|
+
progress(
|
|
7414
|
+
` issue #${String(issue.number)} is still in discussion (no branch was created).
|
|
7415
|
+
`
|
|
7416
|
+
);
|
|
7266
7417
|
return false;
|
|
7267
7418
|
}
|
|
7268
7419
|
const pr = getCurrentBranchPr();
|
|
@@ -7274,10 +7425,15 @@ function repairIssueLink(item, baseBranch, agentUser) {
|
|
|
7274
7425
|
if (pr.assignees.length === 0) {
|
|
7275
7426
|
claimPr(pr.number, agentUser);
|
|
7276
7427
|
}
|
|
7277
|
-
const closesRef = new RegExp(
|
|
7428
|
+
const closesRef = new RegExp(
|
|
7429
|
+
String.raw`\bcloses\s+#` + String(issue.number) + String.raw`\b`,
|
|
7430
|
+
"i"
|
|
7431
|
+
);
|
|
7278
7432
|
if (closesRef.test(pr.body)) {
|
|
7279
|
-
progress(
|
|
7280
|
-
`)
|
|
7433
|
+
progress(
|
|
7434
|
+
` pull request #${String(pr.number)} already closes issue #${String(issue.number)}.
|
|
7435
|
+
`
|
|
7436
|
+
);
|
|
7281
7437
|
return true;
|
|
7282
7438
|
}
|
|
7283
7439
|
addClosesRefToPr(pr.number, issue.number);
|
|
@@ -7285,8 +7441,10 @@ function repairIssueLink(item, baseBranch, agentUser) {
|
|
|
7285
7441
|
`);
|
|
7286
7442
|
return true;
|
|
7287
7443
|
} catch (err) {
|
|
7288
|
-
progress(
|
|
7289
|
-
`)
|
|
7444
|
+
progress(
|
|
7445
|
+
` warning: could not link a pull request to issue #${String(issue.number)}: ${err.message}
|
|
7446
|
+
`
|
|
7447
|
+
);
|
|
7290
7448
|
return false;
|
|
7291
7449
|
}
|
|
7292
7450
|
}
|
|
@@ -7375,8 +7533,10 @@ ${itemLabel(planned)} ${planned.turn}: ${planned.reason}
|
|
|
7375
7533
|
}
|
|
7376
7534
|
let buriedByNote = 0;
|
|
7377
7535
|
if (note !== null) {
|
|
7378
|
-
progress(
|
|
7379
|
-
`)
|
|
7536
|
+
progress(
|
|
7537
|
+
` noted on issue #${String(item.issue?.number ?? 0)} that the work is on pull request #${String(item.pr?.number ?? 0)}.
|
|
7538
|
+
`
|
|
7539
|
+
);
|
|
7380
7540
|
buriedByNote = reportIssueMessagesBuriedByNote(item, note, settings.participants);
|
|
7381
7541
|
}
|
|
7382
7542
|
const watermark = promptWatermark([
|
|
@@ -7411,43 +7571,64 @@ ${itemLabel(planned)} ${planned.turn}: ${planned.reason}
|
|
|
7411
7571
|
);
|
|
7412
7572
|
}
|
|
7413
7573
|
const execution = toExecution(resolved);
|
|
7414
|
-
const { runError, diagnostics, recovery } = await runRecorded(
|
|
7574
|
+
const { runError, diagnostics, recovery, claudeAuth } = await runRecorded(
|
|
7575
|
+
item,
|
|
7576
|
+
prompt,
|
|
7577
|
+
execution,
|
|
7578
|
+
settings,
|
|
7579
|
+
silent
|
|
7580
|
+
);
|
|
7415
7581
|
const ranExecutor = true;
|
|
7416
7582
|
let reconciled;
|
|
7417
7583
|
try {
|
|
7418
|
-
reconciled = await reconcileMarker(
|
|
7419
|
-
item,
|
|
7420
|
-
marker,
|
|
7421
|
-
settings.participants,
|
|
7422
|
-
watermark,
|
|
7584
|
+
reconciled = await reconcileMarker(item, marker, settings.participants, watermark, {
|
|
7423
7585
|
runError,
|
|
7424
7586
|
diagnostics,
|
|
7425
|
-
recovery
|
|
7426
|
-
|
|
7587
|
+
recovery,
|
|
7588
|
+
claudeAuth
|
|
7589
|
+
});
|
|
7427
7590
|
} finally {
|
|
7428
7591
|
inFlightMarker = null;
|
|
7429
7592
|
}
|
|
7430
7593
|
progress(` ${reconciled.detail}
|
|
7431
7594
|
`);
|
|
7432
7595
|
const outcome = adjustOutcome(reconciled, item, settings, buriedByNote);
|
|
7433
|
-
return {
|
|
7596
|
+
return {
|
|
7597
|
+
...base,
|
|
7598
|
+
outcome: outcome.outcome,
|
|
7599
|
+
detail: outcome.detail,
|
|
7600
|
+
ranExecutor,
|
|
7601
|
+
execution,
|
|
7602
|
+
claudeAuth: outcome.claudeAuth
|
|
7603
|
+
};
|
|
7434
7604
|
}
|
|
7435
7605
|
async function runRecorded(item, prompt, execution, settings, silent) {
|
|
7436
7606
|
const transcript = settings.postRunLog ? new RunTranscript(transcriptLabel(item)) : null;
|
|
7437
7607
|
const before = transcript === null ? null : snapshotCheckout();
|
|
7608
|
+
const authTail = transcript === null && execution.executor === "claude" ? new ClaudeAuthTail() : null;
|
|
7438
7609
|
let runError = null;
|
|
7439
7610
|
try {
|
|
7440
|
-
await invokeExecutor(prompt, execution, silent, transcript ?? void 0);
|
|
7611
|
+
await invokeExecutor(prompt, execution, silent, transcript ?? authTail ?? void 0);
|
|
7441
7612
|
} catch (err) {
|
|
7442
7613
|
runError = err;
|
|
7443
7614
|
}
|
|
7444
|
-
if (transcript === null)
|
|
7615
|
+
if (transcript === null) {
|
|
7616
|
+
const claudeAuth2 = runError !== null && authTail !== null && detectClaudeAuthFailure(authTail.tail) ? "" : null;
|
|
7617
|
+
return { runError, diagnostics: null, recovery: null, claudeAuth: claudeAuth2 };
|
|
7618
|
+
}
|
|
7619
|
+
const tail = transcript.excerpt();
|
|
7620
|
+
const claudeAuth = runError !== null && execution.executor === "claude" && detectClaudeAuthFailure(tail) ? tail : null;
|
|
7445
7621
|
return {
|
|
7446
7622
|
runError,
|
|
7623
|
+
claudeAuth,
|
|
7447
7624
|
recovery: async () => {
|
|
7448
7625
|
const text = (transcript.saved ? readTranscriptTail(transcript.path) : null) ?? transcript.excerpt();
|
|
7449
7626
|
if (text.trim() === "") return { ok: false, reason: "the run transcript is empty" };
|
|
7450
|
-
return recoverAnswer(execution, {
|
|
7627
|
+
return recoverAnswer(execution, {
|
|
7628
|
+
subject: markerSurfaceLabel(item),
|
|
7629
|
+
transcript: text,
|
|
7630
|
+
conversation: prompt
|
|
7631
|
+
});
|
|
7451
7632
|
},
|
|
7452
7633
|
diagnostics: async () => {
|
|
7453
7634
|
const excerpt = transcript.excerpt();
|
|
@@ -7527,8 +7708,10 @@ function summarize(reports) {
|
|
|
7527
7708
|
}
|
|
7528
7709
|
for (const report of reports) {
|
|
7529
7710
|
const ran = report.execution === void 0 ? "" : ` \xB7 ${describeExecution(report.execution)}`;
|
|
7530
|
-
out(
|
|
7531
|
-
`)
|
|
7711
|
+
out(
|
|
7712
|
+
` ${reportLabel(report)} ${report.turn ?? "-"} ${report.outcome} \u2014 ${report.detail}${ran}
|
|
7713
|
+
`
|
|
7714
|
+
);
|
|
7532
7715
|
}
|
|
7533
7716
|
}
|
|
7534
7717
|
function toItemJson(report) {
|
|
@@ -7550,7 +7733,13 @@ function toItemJson(report) {
|
|
|
7550
7733
|
}
|
|
7551
7734
|
var doWorkCommand = new Command6("do-work").description(
|
|
7552
7735
|
"Run one tick of the autonomous loop: answer the issues whose newest authorized message the agent has not answered, then the open pull requests that close no issue of this repository and have one"
|
|
7553
|
-
).option(
|
|
7736
|
+
).option(
|
|
7737
|
+
"--with <executor>",
|
|
7738
|
+
"Executor to use: claude or codex (default: from config, else claude)"
|
|
7739
|
+
).option(
|
|
7740
|
+
"--model <string>",
|
|
7741
|
+
"Model identifier to pass to the executor, overriding the configured default for it"
|
|
7742
|
+
).option(
|
|
7554
7743
|
"--effort <level>",
|
|
7555
7744
|
"Reasoning effort to pass to the executor, overriding the configured default for it"
|
|
7556
7745
|
).option("--issue <number>", "Restrict the tick to a single issue").option(
|
|
@@ -7616,7 +7805,12 @@ async function runDoWorkTraced(options) {
|
|
|
7616
7805
|
return exitCode2;
|
|
7617
7806
|
}
|
|
7618
7807
|
const { exitCode, reports } = await runTickUnderLock(lock.handle, settings, options);
|
|
7619
|
-
logTick(
|
|
7808
|
+
logTick(
|
|
7809
|
+
reports,
|
|
7810
|
+
exitCode,
|
|
7811
|
+
startedAt,
|
|
7812
|
+
reports.some((report) => report.claudeAuth === true) ? "claude-auth" : void 0
|
|
7813
|
+
);
|
|
7620
7814
|
return exitCode;
|
|
7621
7815
|
}
|
|
7622
7816
|
async function runTickUnderLock(handle, settings, options) {
|
|
@@ -7682,6 +7876,20 @@ function buildSelectionEvidence(settings, issues, linkMap, decisions, verbose) {
|
|
|
7682
7876
|
})) : null
|
|
7683
7877
|
};
|
|
7684
7878
|
}
|
|
7879
|
+
function decideAll(settings, issues, linkMap, policy) {
|
|
7880
|
+
return skipDuplicateHeadBranches([
|
|
7881
|
+
...issues.map(
|
|
7882
|
+
(issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, policy)
|
|
7883
|
+
),
|
|
7884
|
+
...discoverOrphanPrs(settings, linkMap).map(
|
|
7885
|
+
(candidate) => decideOrphanPrWork(
|
|
7886
|
+
{ prSurface: getPrSurface(candidate.pr.number) },
|
|
7887
|
+
settings.participants,
|
|
7888
|
+
policy
|
|
7889
|
+
)
|
|
7890
|
+
)
|
|
7891
|
+
]);
|
|
7892
|
+
}
|
|
7685
7893
|
function collectSelection(settings, verbose) {
|
|
7686
7894
|
const issues = discoverIssues(settings);
|
|
7687
7895
|
const linkMap = getOpenPrLinkMap();
|
|
@@ -7690,12 +7898,7 @@ function collectSelection(settings, verbose) {
|
|
|
7690
7898
|
defaultBranch: linkMap.defaultBranch,
|
|
7691
7899
|
protectedBranches: settings.protectedBranches
|
|
7692
7900
|
};
|
|
7693
|
-
const decisions =
|
|
7694
|
-
...issues.map((issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, policy)),
|
|
7695
|
-
...discoverOrphanPrs(settings, linkMap).map(
|
|
7696
|
-
(candidate) => decideOrphanPrWork({ prSurface: getPrSurface(candidate.pr.number) }, settings.participants, policy)
|
|
7697
|
-
)
|
|
7698
|
-
]);
|
|
7901
|
+
const decisions = decideAll(settings, issues, linkMap, policy);
|
|
7699
7902
|
return buildSelectionEvidence(settings, issues, linkMap, decisions, verbose);
|
|
7700
7903
|
}
|
|
7701
7904
|
function makeSelectionSection(lines, problems, data) {
|
|
@@ -7727,7 +7930,12 @@ function describeIssueEvidence(settings, evidence) {
|
|
|
7727
7930
|
`discovery query: ${settings.technique} = ${settings.discoveryValue}, limit ${String(settings.limit)}${restriction}`
|
|
7728
7931
|
];
|
|
7729
7932
|
if (evidence.issues === null) return lines;
|
|
7730
|
-
lines.push(
|
|
7933
|
+
lines.push(
|
|
7934
|
+
heading(
|
|
7935
|
+
`discovery returned ${String(evidence.issues.length)} issue(s)`,
|
|
7936
|
+
evidence.issues.length
|
|
7937
|
+
)
|
|
7938
|
+
);
|
|
7731
7939
|
for (const issue of evidence.issues) {
|
|
7732
7940
|
lines.push(` #${String(issue.number)} ${issue.title}`);
|
|
7733
7941
|
}
|
|
@@ -7809,11 +8017,11 @@ function selectionData(evidence) {
|
|
|
7809
8017
|
}
|
|
7810
8018
|
function selectionSectionFromEvidence(settings, evidence, verbose) {
|
|
7811
8019
|
if (evidence === null) {
|
|
7812
|
-
return makeSelectionSection(
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
);
|
|
8020
|
+
return makeSelectionSection(["not run: the tick was blocked before discovery"], [], {
|
|
8021
|
+
ran: false,
|
|
8022
|
+
detail: "blocked before discovery",
|
|
8023
|
+
plan: []
|
|
8024
|
+
});
|
|
7817
8025
|
}
|
|
7818
8026
|
const lines = [];
|
|
7819
8027
|
if (verbose && settings !== null) describeSelectionEvidence(settings, evidence, lines);
|
|
@@ -8048,6 +8256,9 @@ function dumpBlocked(options, context) {
|
|
|
8048
8256
|
}
|
|
8049
8257
|
}
|
|
8050
8258
|
function classifyBlocked(hygiene, decisions, items, reports) {
|
|
8259
|
+
if (reports.some((report) => report.claudeAuth === true)) {
|
|
8260
|
+
return { reason: "claude-auth", trigger: `${CLAUDE_AUTH_REASON}; run \`claude /login\` again` };
|
|
8261
|
+
}
|
|
8051
8262
|
const ran = reports.some((report) => report.ranExecutor === true);
|
|
8052
8263
|
if (ran) return null;
|
|
8053
8264
|
if (hygiene.degraded) {
|
|
@@ -8165,12 +8376,7 @@ async function runTick(settings, options) {
|
|
|
8165
8376
|
defaultBranch: linkMap.defaultBranch,
|
|
8166
8377
|
protectedBranches: settings.protectedBranches
|
|
8167
8378
|
};
|
|
8168
|
-
const decisions =
|
|
8169
|
-
...issues.map((issue) => decideWork(buildIssueState(issue, linkMap), settings.participants, policy)),
|
|
8170
|
-
...discoverOrphanPrs(settings, linkMap).map(
|
|
8171
|
-
(candidate) => decideOrphanPrWork({ prSurface: getPrSurface(candidate.pr.number) }, settings.participants, policy)
|
|
8172
|
-
)
|
|
8173
|
-
]);
|
|
8379
|
+
const decisions = decideAll(settings, issues, linkMap, policy);
|
|
8174
8380
|
const items = decisions.flatMap((decision) => decision.kind === "work" ? [decision.item] : []);
|
|
8175
8381
|
const planText = `Work plan (${String(items.length)} of ${String(decisions.length)} candidates need an answer):
|
|
8176
8382
|
${describePlan(decisions)}`;
|
|
@@ -8219,9 +8425,23 @@ async function processItems(items, settings, options, hygiene) {
|
|
|
8219
8425
|
const report = await reportForItem(item, settings, options, preflightCauses);
|
|
8220
8426
|
if (report.ranExecutor === true) runsUsed++;
|
|
8221
8427
|
reports.push(report);
|
|
8428
|
+
if (report.claudeAuth === true) {
|
|
8429
|
+
reports.push(...items.slice(index + 1).map((rest) => skipForClaudeAuth(rest)));
|
|
8430
|
+
break;
|
|
8431
|
+
}
|
|
8222
8432
|
}
|
|
8223
8433
|
return { reports, deferred };
|
|
8224
8434
|
}
|
|
8435
|
+
function skipForClaudeAuth(item) {
|
|
8436
|
+
return {
|
|
8437
|
+
issue: item.issue?.number ?? null,
|
|
8438
|
+
pr: item.pr?.number ?? null,
|
|
8439
|
+
title: itemTitle(item),
|
|
8440
|
+
turn: item.turn,
|
|
8441
|
+
outcome: "skipped",
|
|
8442
|
+
detail: `${CLAUDE_AUTH_REASON}; run \`claude /login\` again`
|
|
8443
|
+
};
|
|
8444
|
+
}
|
|
8225
8445
|
async function reportForItem(item, settings, options, preflightCauses) {
|
|
8226
8446
|
try {
|
|
8227
8447
|
return await processItem(item, settings, options.silent === true, preflightCauses);
|
|
@@ -8892,6 +9112,48 @@ function withoutWatched(list, id) {
|
|
|
8892
9112
|
return list.filter((entry) => entry !== id);
|
|
8893
9113
|
}
|
|
8894
9114
|
|
|
9115
|
+
// src/conductor/permissionDenials.ts
|
|
9116
|
+
var MAX_TARGET_LENGTH = 300;
|
|
9117
|
+
function targetOf(input) {
|
|
9118
|
+
if (typeof input !== "object" || input === null) return "";
|
|
9119
|
+
const record = input;
|
|
9120
|
+
for (const key2 of ["command", "file_path", "path", "pattern"]) {
|
|
9121
|
+
const value = record[key2];
|
|
9122
|
+
if (typeof value === "string") return value;
|
|
9123
|
+
}
|
|
9124
|
+
return JSON.stringify(input);
|
|
9125
|
+
}
|
|
9126
|
+
function denialOf(entry) {
|
|
9127
|
+
if (typeof entry !== "object" || entry === null) return null;
|
|
9128
|
+
const record = entry;
|
|
9129
|
+
const tool = record["tool_name"];
|
|
9130
|
+
if (typeof tool !== "string") return null;
|
|
9131
|
+
return { tool, target: targetOf(record["tool_input"]) };
|
|
9132
|
+
}
|
|
9133
|
+
function parsePermissionDenials(stdout) {
|
|
9134
|
+
const denials = [];
|
|
9135
|
+
for (const line of stdout.split("\n")) {
|
|
9136
|
+
if (!line.trim().startsWith("{")) continue;
|
|
9137
|
+
try {
|
|
9138
|
+
const event = JSON.parse(line);
|
|
9139
|
+
const listed = event["type"] === "result" ? event["permission_denials"] : void 0;
|
|
9140
|
+
if (!Array.isArray(listed)) continue;
|
|
9141
|
+
for (const entry of listed) {
|
|
9142
|
+
const denial = denialOf(entry);
|
|
9143
|
+
if (denial !== null) denials.push(denial);
|
|
9144
|
+
}
|
|
9145
|
+
} catch {
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
return denials;
|
|
9149
|
+
}
|
|
9150
|
+
function describePermissionDenials(denials) {
|
|
9151
|
+
return denials.map((denial) => {
|
|
9152
|
+
const flat = `${denial.tool} ${denial.target}`.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
9153
|
+
return flat.length <= MAX_TARGET_LENGTH ? flat : `${flat.slice(0, MAX_TARGET_LENGTH)}\u2026`;
|
|
9154
|
+
});
|
|
9155
|
+
}
|
|
9156
|
+
|
|
8895
9157
|
// src/commands/conductor.ts
|
|
8896
9158
|
function fail2(message) {
|
|
8897
9159
|
process.stderr.write(`Error: ${message}
|
|
@@ -8956,7 +9218,29 @@ function readTargetMessages(target) {
|
|
|
8956
9218
|
}
|
|
8957
9219
|
function runModel(prompt, execution, replyTo, sink2) {
|
|
8958
9220
|
const { owner, repo } = getRepoSlug();
|
|
8959
|
-
return runClaude(prompt, { model: execution.model, effort: execution.effort, readOnly: true, replyTo, ghRepo: `${owner}/${repo}`, sink: sink2 });
|
|
9221
|
+
return runClaude(prompt, { model: execution.model, effort: execution.effort, readOnly: true, replyTo, ghRepo: `${owner}/${repo}`, sink: sink2, printSteps: verboseRun, echoResult: false });
|
|
9222
|
+
}
|
|
9223
|
+
var verboseRun = false;
|
|
9224
|
+
function reportDenials(stdout, label2) {
|
|
9225
|
+
if (!verboseRun || stdout.length === 0) return;
|
|
9226
|
+
const lines = describePermissionDenials(parsePermissionDenials(stdout));
|
|
9227
|
+
if (lines.length === 0) {
|
|
9228
|
+
process.stderr.write(`Conductor: the run for ${label2} reported no denied tool call.
|
|
9229
|
+
`);
|
|
9230
|
+
return;
|
|
9231
|
+
}
|
|
9232
|
+
process.stderr.write(`Conductor: the run for ${label2} was denied ${String(lines.length)} tool call(s):
|
|
9233
|
+
`);
|
|
9234
|
+
for (const line of lines) process.stderr.write(` - ${line}
|
|
9235
|
+
`);
|
|
9236
|
+
}
|
|
9237
|
+
function reportCommandTrace() {
|
|
9238
|
+
const trace = takeCommandTrace();
|
|
9239
|
+
if (trace === null) return;
|
|
9240
|
+
process.stderr.write(`Commands (${String(trace.length)})
|
|
9241
|
+
`);
|
|
9242
|
+
for (const traced of trace) process.stderr.write(` ${describeTracedCommand(traced)}
|
|
9243
|
+
`);
|
|
8960
9244
|
}
|
|
8961
9245
|
function captureStdout() {
|
|
8962
9246
|
let stdout = "";
|
|
@@ -8971,8 +9255,12 @@ function captureStdout() {
|
|
|
8971
9255
|
async function runModelForText(prompt, execution) {
|
|
8972
9256
|
const { owner, repo } = getRepoSlug();
|
|
8973
9257
|
const captured = captureStdout();
|
|
8974
|
-
const options = { model: execution.model, effort: execution.effort, readOnly: true, ghRepo: `${owner}/${repo}`, sink: captured.sink };
|
|
8975
|
-
|
|
9258
|
+
const options = { model: execution.model, effort: execution.effort, readOnly: true, ghRepo: `${owner}/${repo}`, sink: captured.sink, printSteps: verboseRun, echoResult: false };
|
|
9259
|
+
try {
|
|
9260
|
+
await runClaude(prompt, options);
|
|
9261
|
+
} finally {
|
|
9262
|
+
reportDenials(captured.text(), "the dry run");
|
|
9263
|
+
}
|
|
8976
9264
|
return extractDryRunReply(execution.executor, captured.text());
|
|
8977
9265
|
}
|
|
8978
9266
|
function coveredOf(watch, linkMap) {
|
|
@@ -9052,6 +9340,7 @@ async function conductItem(id, config, login2, participants, execution, dryRun,
|
|
|
9052
9340
|
read: () => readTargetMessages(replyTo),
|
|
9053
9341
|
run: () => runModel(prompt, execution, replyTo, captured.sink)
|
|
9054
9342
|
});
|
|
9343
|
+
reportDenials(captured.text(), label2);
|
|
9055
9344
|
const needsHuman = parseNeedsHuman(extractDryRunReply(execution.executor, captured.text()) ?? "");
|
|
9056
9345
|
if (needsHuman !== null && outcome.kind !== "run-failed" && outcome.kind !== "unverified") {
|
|
9057
9346
|
return blockItem(item, label2, needsHuman);
|
|
@@ -9300,6 +9589,17 @@ function reportHeldLock(held, suspect, staleMinutes) {
|
|
|
9300
9589
|
return 2;
|
|
9301
9590
|
}
|
|
9302
9591
|
async function runConductor(options = {}) {
|
|
9592
|
+
verboseRun = options.verbose === true;
|
|
9593
|
+
if (verboseRun) startCommandTrace();
|
|
9594
|
+
try {
|
|
9595
|
+
return await runConductorTick(options);
|
|
9596
|
+
} finally {
|
|
9597
|
+
reportCommandTrace();
|
|
9598
|
+
verboseRun = false;
|
|
9599
|
+
stopCommandTrace();
|
|
9600
|
+
}
|
|
9601
|
+
}
|
|
9602
|
+
async function runConductorTick(options) {
|
|
9303
9603
|
if (options.check === true && options.dryRun === true) {
|
|
9304
9604
|
return fail2("--check and --dry-run cannot be used together.");
|
|
9305
9605
|
}
|
|
@@ -9340,7 +9640,10 @@ async function runConductor(options = {}) {
|
|
|
9340
9640
|
}
|
|
9341
9641
|
var conductorCommand = new Command7("conductor").description(
|
|
9342
9642
|
"Run one conductor tick as an allowed (human) account: verify the identity, take the conductor's own run lock, then act"
|
|
9343
|
-
).option("--check", "Report what a tick would do, including pending prunes, and change nothing; exit 1 on a problem").option("--dry-run", "Run the model for each owed reply and print it instead of posting; no lock, no prune").
|
|
9643
|
+
).option("--check", "Report what a tick would do, including pending prunes, and change nothing; exit 1 on a problem").option("--dry-run", "Run the model for each owed reply and print it instead of posting; no lock, no prune").option(
|
|
9644
|
+
"--verbose",
|
|
9645
|
+
"Trace every `git` and `gh` call with its duration and exit code, show the model's steps, and list the tool calls the run was denied"
|
|
9646
|
+
).action(async (options) => {
|
|
9344
9647
|
exitWith(await runConductor(options));
|
|
9345
9648
|
});
|
|
9346
9649
|
conductorCommand.command("add <id>").description("Watch an issue or pull request and make do-work pick it up (an issue also follows its linked PR)").action((id) => {
|