automata-cli 0.6.0-develop.257 → 0.6.0-develop.273
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 +271 -71
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,9 +34,9 @@ function writeDoWork(patch) {
|
|
|
34
34
|
writeConfig({ ...current, doWork: { ...current.doWork, ...patch } });
|
|
35
35
|
}
|
|
36
36
|
function parseNonNegativeInt(value, label) {
|
|
37
|
-
const
|
|
38
|
-
const parsed = Number.parseInt(
|
|
39
|
-
if (Number.isNaN(parsed) || parsed < 0 || String(parsed) !==
|
|
37
|
+
const trimmed2 = value.trim();
|
|
38
|
+
const parsed = Number.parseInt(trimmed2, 10);
|
|
39
|
+
if (Number.isNaN(parsed) || parsed < 0 || String(parsed) !== trimmed2 || !Number.isSafeInteger(parsed)) {
|
|
40
40
|
process.stderr.write(`Error: ${label} must be a non-negative integer (got "${value}").
|
|
41
41
|
`);
|
|
42
42
|
process.exit(1);
|
|
@@ -1559,12 +1559,12 @@ function handleExitCode(status, toolName) {
|
|
|
1559
1559
|
}
|
|
1560
1560
|
function resolveEffortOption(value) {
|
|
1561
1561
|
if (value === void 0) return void 0;
|
|
1562
|
-
const
|
|
1563
|
-
if (
|
|
1562
|
+
const trimmed2 = value.trim();
|
|
1563
|
+
if (trimmed2.length === 0) {
|
|
1564
1564
|
process.stderr.write("Error: --effort must be a non-empty level.\n");
|
|
1565
1565
|
process.exit(1);
|
|
1566
1566
|
}
|
|
1567
|
-
return
|
|
1567
|
+
return trimmed2;
|
|
1568
1568
|
}
|
|
1569
1569
|
|
|
1570
1570
|
// src/cli/childRegistry.ts
|
|
@@ -2132,8 +2132,8 @@ function analyzeSurface(messages, p) {
|
|
|
2132
2132
|
}
|
|
2133
2133
|
function lastAuthorClass(messages, p) {
|
|
2134
2134
|
if (messages.length === 0) return "none";
|
|
2135
|
-
const
|
|
2136
|
-
return
|
|
2135
|
+
const newest2 = [...messages].sort(byCreatedAt).at(-1);
|
|
2136
|
+
return newest2 === void 0 ? "none" : classify(newest2.author, p);
|
|
2137
2137
|
}
|
|
2138
2138
|
function formatMessages(messages) {
|
|
2139
2139
|
return messages.map((message) => {
|
|
@@ -2908,9 +2908,9 @@ function selectLinkedPr(linkedPrs) {
|
|
|
2908
2908
|
function formatThreads(threads) {
|
|
2909
2909
|
return threads.map((thread) => {
|
|
2910
2910
|
const location = thread.line === null ? `${thread.path}:(file)` : `${thread.path}:${String(thread.line)}`;
|
|
2911
|
-
const
|
|
2912
|
-
const author =
|
|
2913
|
-
const body =
|
|
2911
|
+
const newest2 = thread.comments.at(-1);
|
|
2912
|
+
const author = newest2?.author ?? "unknown";
|
|
2913
|
+
const body = newest2?.body ?? "";
|
|
2914
2914
|
const link = thread.url === null ? "" : `
|
|
2915
2915
|
${thread.url}`;
|
|
2916
2916
|
return `[${author}] ${location}${link}
|
|
@@ -2976,6 +2976,101 @@ function composePrompt(input) {
|
|
|
2976
2976
|
return lines.join("\n");
|
|
2977
2977
|
}
|
|
2978
2978
|
|
|
2979
|
+
// src/github/runDirective.ts
|
|
2980
|
+
var VALID_TOOLS = ["claude", "codex"];
|
|
2981
|
+
var TOOL_PATTERN = /(?<![a-z0-9_:-])tool:([a-z0-9._-]+)/gi;
|
|
2982
|
+
var MODEL_PATTERN = /(?<![a-z0-9_:-])model:([a-z0-9._/+@-]+)/gi;
|
|
2983
|
+
function lastCapture(body, pattern) {
|
|
2984
|
+
const matches = [...body.matchAll(new RegExp(pattern.source, pattern.flags))];
|
|
2985
|
+
return matches.at(-1)?.[1];
|
|
2986
|
+
}
|
|
2987
|
+
function parseRunDirective(body) {
|
|
2988
|
+
const tool = lastCapture(body, TOOL_PATTERN);
|
|
2989
|
+
return {
|
|
2990
|
+
tool: tool === void 0 ? void 0 : tool.toLowerCase(),
|
|
2991
|
+
model: lastCapture(body, MODEL_PATTERN)
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
function newest(messages) {
|
|
2995
|
+
let found = null;
|
|
2996
|
+
for (const message of messages) {
|
|
2997
|
+
if (found === null || message.createdAt >= found.createdAt) found = message;
|
|
2998
|
+
}
|
|
2999
|
+
return found;
|
|
3000
|
+
}
|
|
3001
|
+
function triggeringMessage(item) {
|
|
3002
|
+
if (item.turn === "issue-discuss") return newest(item.issueAnalysis.newMessages);
|
|
3003
|
+
const prLastAgentAt = item.prAnalysis?.lastAgentAt ?? null;
|
|
3004
|
+
const threadComments = item.actionableThreads.flatMap(
|
|
3005
|
+
(thread) => (
|
|
3006
|
+
// The thread's comments are already filtered to authorized and agent
|
|
3007
|
+
// accounts; the agent's own are not a trigger, and anything the agent has
|
|
3008
|
+
// since answered is not either.
|
|
3009
|
+
thread.comments.filter(
|
|
3010
|
+
(comment) => prLastAgentAt === null || comment.createdAt > prLastAgentAt
|
|
3011
|
+
)
|
|
3012
|
+
)
|
|
3013
|
+
);
|
|
3014
|
+
return newest([
|
|
3015
|
+
...item.issueAnalysis.newMessages,
|
|
3016
|
+
...item.prAnalysis?.newMessages ?? [],
|
|
3017
|
+
...threadComments
|
|
3018
|
+
]);
|
|
3019
|
+
}
|
|
3020
|
+
function isExecutor(value) {
|
|
3021
|
+
return VALID_TOOLS.includes(value);
|
|
3022
|
+
}
|
|
3023
|
+
function baselineExecutorSource(withOption, configExecutor) {
|
|
3024
|
+
if (withOption !== void 0) return "option";
|
|
3025
|
+
if (configExecutor !== void 0) return "config";
|
|
3026
|
+
return "default";
|
|
3027
|
+
}
|
|
3028
|
+
function trimmed(value) {
|
|
3029
|
+
if (value === void 0) return void 0;
|
|
3030
|
+
const cleaned = value.trim();
|
|
3031
|
+
return cleaned.length === 0 ? void 0 : cleaned;
|
|
3032
|
+
}
|
|
3033
|
+
function resolvePerExecutor(option, configured, switched) {
|
|
3034
|
+
if (!switched && option !== void 0) return { value: option, source: "option" };
|
|
3035
|
+
const fromConfig = trimmed(configured);
|
|
3036
|
+
if (fromConfig !== void 0) return { value: fromConfig, source: "config" };
|
|
3037
|
+
return { value: void 0, source: "none" };
|
|
3038
|
+
}
|
|
3039
|
+
function resolveExecution(input) {
|
|
3040
|
+
const { directive, withOption, modelOption, configExecutor, configModels } = input;
|
|
3041
|
+
if (directive.tool !== void 0 && !isExecutor(directive.tool)) {
|
|
3042
|
+
return { ok: false, invalidTool: directive.tool };
|
|
3043
|
+
}
|
|
3044
|
+
const baseline = withOption ?? configExecutor ?? input.defaultExecutor;
|
|
3045
|
+
const baselineSource = baselineExecutorSource(withOption, configExecutor);
|
|
3046
|
+
const executor = directive.tool ?? baseline;
|
|
3047
|
+
const executorSource = directive.tool === void 0 ? baselineSource : "message";
|
|
3048
|
+
const switched = directive.tool !== void 0 && directive.tool !== baseline;
|
|
3049
|
+
const effort = resolvePerExecutor(input.effortOption, input.configEfforts?.[executor], switched);
|
|
3050
|
+
const common = {
|
|
3051
|
+
ok: true,
|
|
3052
|
+
executor,
|
|
3053
|
+
executorSource,
|
|
3054
|
+
effort: effort.value,
|
|
3055
|
+
effortSource: effort.source
|
|
3056
|
+
};
|
|
3057
|
+
if (directive.model !== void 0) {
|
|
3058
|
+
return { ...common, model: directive.model, modelSource: "message" };
|
|
3059
|
+
}
|
|
3060
|
+
const model = resolvePerExecutor(modelOption, configModels?.[executor], switched);
|
|
3061
|
+
return { ...common, model: model.value, modelSource: model.source };
|
|
3062
|
+
}
|
|
3063
|
+
function describeExecution(execution) {
|
|
3064
|
+
const model = execution.model === void 0 ? " (no model override)" : ` \xB7 model ${execution.model}`;
|
|
3065
|
+
const effort = execution.effort === void 0 ? "" : ` \xB7 effort ${execution.effort}`;
|
|
3066
|
+
const fromMessage = execution.executorSource === "message" || execution.modelSource === "message" ? " \u2014 from the message" : "";
|
|
3067
|
+
return `${execution.executor}${model}${effort}${fromMessage}`;
|
|
3068
|
+
}
|
|
3069
|
+
function describeInvalidTool(invalidTool) {
|
|
3070
|
+
const valid = VALID_TOOLS.map((tool) => `\`${tool}\``).join(" and ");
|
|
3071
|
+
return `the newest message asks for \`tool:${invalidTool}\`, which is not an executor automata knows (valid values are ${valid})`;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
2979
3074
|
// src/github/markerReconciliation.ts
|
|
2980
3075
|
function isAuthorized(author, p) {
|
|
2981
3076
|
const login2 = author.toLowerCase();
|
|
@@ -3321,11 +3416,11 @@ function fail(message) {
|
|
|
3321
3416
|
process.exit(1);
|
|
3322
3417
|
}
|
|
3323
3418
|
function parsePositiveInt(value, label) {
|
|
3324
|
-
const
|
|
3325
|
-
if (!/^\d+$/.test(
|
|
3419
|
+
const trimmed2 = value.trim();
|
|
3420
|
+
if (!/^\d+$/.test(trimmed2)) {
|
|
3326
3421
|
fail(`${label} must be a positive integer (got "${value}").`);
|
|
3327
3422
|
}
|
|
3328
|
-
const parsed = Number(
|
|
3423
|
+
const parsed = Number(trimmed2);
|
|
3329
3424
|
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
3330
3425
|
fail(`${label} must be a positive integer within the safe range (got "${value}").`);
|
|
3331
3426
|
}
|
|
@@ -3359,13 +3454,13 @@ function resolveSettings(options) {
|
|
|
3359
3454
|
}
|
|
3360
3455
|
validateDoWorkConfig(config.doWork);
|
|
3361
3456
|
const doWork = config.doWork ?? {};
|
|
3362
|
-
let
|
|
3457
|
+
let withOption;
|
|
3363
3458
|
if (options.with !== void 0) {
|
|
3364
3459
|
const requested = options.with.toLowerCase();
|
|
3365
3460
|
if (requested !== "claude" && requested !== "codex") {
|
|
3366
3461
|
fail(`--with must be 'claude' or 'codex', got '${options.with}'.`);
|
|
3367
3462
|
}
|
|
3368
|
-
|
|
3463
|
+
withOption = requested;
|
|
3369
3464
|
}
|
|
3370
3465
|
if (options.dryRun !== true) {
|
|
3371
3466
|
checkAuthenticatedIdentity(agentUser, allowedUsers);
|
|
@@ -3373,16 +3468,16 @@ function resolveSettings(options) {
|
|
|
3373
3468
|
return {
|
|
3374
3469
|
baseBranch: doWork.baseBranch ?? DEFAULT_DO_WORK.baseBranch,
|
|
3375
3470
|
protectedBranches: doWork.protectedBranches ?? DEFAULT_DO_WORK.protectedBranches,
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
//
|
|
3381
|
-
//
|
|
3382
|
-
//
|
|
3383
|
-
//
|
|
3384
|
-
|
|
3385
|
-
|
|
3471
|
+
withOption,
|
|
3472
|
+
modelOption: options.model,
|
|
3473
|
+
configExecutor: doWork.executor,
|
|
3474
|
+
configModels: doWork.models,
|
|
3475
|
+
// Rejected here rather than per item: an empty `--effort` is an operator
|
|
3476
|
+
// mistake on this invocation, not a property of any one work item. The
|
|
3477
|
+
// configured per-executor defaults are trimmed inside `resolveExecution`,
|
|
3478
|
+
// which is where the executor in use is finally known.
|
|
3479
|
+
effortOption: resolveEffortOption(options.effort),
|
|
3480
|
+
configEfforts: doWork.effort,
|
|
3386
3481
|
maxRuns: options.maxRuns !== void 0 ? parsePositiveInt(options.maxRuns, "--max-runs") : doWork.maxRunsPerTick ?? DEFAULT_DO_WORK.maxRunsPerTick,
|
|
3387
3482
|
lockStaleMinutes: doWork.lockStaleMinutes ?? DEFAULT_DO_WORK.lockStaleMinutes,
|
|
3388
3483
|
limit: parsePositiveInt(options.limit, "--limit"),
|
|
@@ -3415,7 +3510,30 @@ function checkAuthenticatedIdentity(agentUser, allowedUsers) {
|
|
|
3415
3510
|
`\`gh\` is authenticated as "${login2}" but agentUser is "${agentUser}". Comments posted under that identity are neither the agent's nor an authorized user's, so they are filtered out of the conversation: the answer boundary would never advance and the same message would start a run on every tick. Authenticate \`gh\` as the agent account (${agentUser}) in this environment, or correct \`agentUser\`.`
|
|
3416
3511
|
);
|
|
3417
3512
|
}
|
|
3418
|
-
function
|
|
3513
|
+
function resolveItemExecution(item, settings) {
|
|
3514
|
+
const trigger = triggeringMessage(item);
|
|
3515
|
+
return resolveExecution({
|
|
3516
|
+
directive: trigger === null ? { tool: void 0, model: void 0 } : parseRunDirective(trigger.body),
|
|
3517
|
+
withOption: settings.withOption,
|
|
3518
|
+
modelOption: settings.modelOption,
|
|
3519
|
+
configExecutor: settings.configExecutor,
|
|
3520
|
+
configModels: settings.configModels,
|
|
3521
|
+
effortOption: settings.effortOption,
|
|
3522
|
+
configEfforts: settings.configEfforts,
|
|
3523
|
+
defaultExecutor: DEFAULT_DO_WORK.executor
|
|
3524
|
+
});
|
|
3525
|
+
}
|
|
3526
|
+
function toExecution(resolved) {
|
|
3527
|
+
return {
|
|
3528
|
+
executor: resolved.executor,
|
|
3529
|
+
executorSource: resolved.executorSource,
|
|
3530
|
+
model: resolved.model,
|
|
3531
|
+
modelSource: resolved.modelSource,
|
|
3532
|
+
effort: resolved.effort,
|
|
3533
|
+
effortSource: resolved.effortSource
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
function planRun(item, settings, execution) {
|
|
3419
3537
|
const prompt = composePrompt({
|
|
3420
3538
|
item,
|
|
3421
3539
|
repo: getRepoSlug(),
|
|
@@ -3423,22 +3541,20 @@ function planRun(item, settings) {
|
|
|
3423
3541
|
baseBranch: settings.baseBranch,
|
|
3424
3542
|
frame: settings.prompts[item.turn]
|
|
3425
3543
|
});
|
|
3426
|
-
const bin = resolveCommand(
|
|
3427
|
-
const args =
|
|
3544
|
+
const bin = resolveCommand(execution.executor === "codex" ? "codex" : "claude");
|
|
3545
|
+
const args = execution.executor === "codex" ? buildCodexArgs(prompt, { yolo: true, model: execution.model, effort: execution.effort }) : buildClaudeArgs(prompt, {
|
|
3428
3546
|
yolo: true,
|
|
3429
3547
|
verbose: true,
|
|
3430
|
-
model:
|
|
3431
|
-
effort:
|
|
3548
|
+
model: execution.model,
|
|
3549
|
+
effort: execution.effort
|
|
3432
3550
|
});
|
|
3433
3551
|
return { prompt, bin, args, command: [bin, ...args].map(shellQuote).join(" ") };
|
|
3434
3552
|
}
|
|
3435
|
-
function describePlannedRun(item, settings, run5) {
|
|
3553
|
+
function describePlannedRun(item, settings, run5, execution) {
|
|
3436
3554
|
const rule = "\u2500".repeat(72);
|
|
3437
3555
|
const branchAction = item.turn === "pr-work" ? " and fast-forward" : " and pull";
|
|
3438
3556
|
const assignment = item.needsAssignment ? `would assign to ${settings.participants.agentUser}` : "already assigned";
|
|
3439
3557
|
const markerTarget = item.turn === "pr-work" && item.pr ? `pull request #${String(item.pr.number)}` : `issue #${String(item.issue.number)}`;
|
|
3440
|
-
const modelNote = settings.model === void 0 ? " (no model override)" : ` \xB7 model ${settings.model}`;
|
|
3441
|
-
const effortNote = settings.effort === void 0 ? "" : ` \xB7 effort ${settings.effort}`;
|
|
3442
3558
|
const lines = [
|
|
3443
3559
|
rule,
|
|
3444
3560
|
`Issue #${String(item.issue.number)} \u2014 ${item.issue.title}`,
|
|
@@ -3448,7 +3564,7 @@ function describePlannedRun(item, settings, run5) {
|
|
|
3448
3564
|
` Branch ${item.branch} (would check out${branchAction})`,
|
|
3449
3565
|
` Assign ${assignment}`,
|
|
3450
3566
|
` Marker would post on ${markerTarget}`,
|
|
3451
|
-
` Executor ${
|
|
3567
|
+
` Executor ${describeExecution(execution)}`,
|
|
3452
3568
|
" Permissions bypassed (do-work always runs unattended)",
|
|
3453
3569
|
` Prompt ${String(run5.prompt.length)} chars \u2014 frame + assembled context`,
|
|
3454
3570
|
"",
|
|
@@ -3464,6 +3580,19 @@ function describePlannedRun(item, settings, run5) {
|
|
|
3464
3580
|
];
|
|
3465
3581
|
return lines.join("\n") + "\n";
|
|
3466
3582
|
}
|
|
3583
|
+
function describeRefusedRun(item, refusal) {
|
|
3584
|
+
const rule = "\u2500".repeat(72);
|
|
3585
|
+
return [
|
|
3586
|
+
rule,
|
|
3587
|
+
`Issue #${String(item.issue.number)} \u2014 ${item.issue.title}`,
|
|
3588
|
+
rule,
|
|
3589
|
+
` Turn ${item.turn}`,
|
|
3590
|
+
` Why ${item.reason}`,
|
|
3591
|
+
` Executor refused \u2014 ${refusal}`,
|
|
3592
|
+
" Command none; a real tick would post the working marker and then replace it with this refusal",
|
|
3593
|
+
""
|
|
3594
|
+
].join("\n") + "\n";
|
|
3595
|
+
}
|
|
3467
3596
|
function isPlainObject(value) {
|
|
3468
3597
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3469
3598
|
}
|
|
@@ -3728,12 +3857,12 @@ function reportNoAnswer(item, marker, runError) {
|
|
|
3728
3857
|
}
|
|
3729
3858
|
return runError === null ? { outcome: "answered-no-reply", detail: "run finished but posted no answer", reason: "no-answer" } : { outcome: "failed", detail: `run failed: ${runError.message}`, reason: "no-answer" };
|
|
3730
3859
|
}
|
|
3731
|
-
async function invokeExecutor(prompt,
|
|
3732
|
-
if (
|
|
3733
|
-
await runCodex(prompt, { model:
|
|
3860
|
+
async function invokeExecutor(prompt, execution, silent) {
|
|
3861
|
+
if (execution.executor === "codex") {
|
|
3862
|
+
await runCodex(prompt, { model: execution.model, effort: execution.effort });
|
|
3734
3863
|
return;
|
|
3735
3864
|
}
|
|
3736
|
-
await runClaude(prompt, { model:
|
|
3865
|
+
await runClaude(prompt, { model: execution.model, effort: execution.effort, printSteps: !silent });
|
|
3737
3866
|
}
|
|
3738
3867
|
function repairIssueLink(item, baseBranch) {
|
|
3739
3868
|
try {
|
|
@@ -3765,6 +3894,18 @@ function repairIssueLink(item, baseBranch) {
|
|
|
3765
3894
|
return false;
|
|
3766
3895
|
}
|
|
3767
3896
|
}
|
|
3897
|
+
function refuseBeforeRun(base, marker, detail, markerText) {
|
|
3898
|
+
progress(` failed: ${detail}
|
|
3899
|
+
`);
|
|
3900
|
+
inFlightMarker = null;
|
|
3901
|
+
try {
|
|
3902
|
+
updateMarker(marker, markerText);
|
|
3903
|
+
} catch (err) {
|
|
3904
|
+
progress(` warning: could not update the marker comment: ${err.message}
|
|
3905
|
+
`);
|
|
3906
|
+
}
|
|
3907
|
+
return { ...base, outcome: "failed", detail };
|
|
3908
|
+
}
|
|
3768
3909
|
async function processItem(planned, settings, silent) {
|
|
3769
3910
|
progress(`
|
|
3770
3911
|
#${String(planned.issue.number)} ${planned.turn}: ${planned.reason}
|
|
@@ -3833,24 +3974,27 @@ async function processItem(planned, settings, silent) {
|
|
|
3833
3974
|
});
|
|
3834
3975
|
const oversized = describeOversizedPrompt(prompt);
|
|
3835
3976
|
if (oversized !== null) {
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
marker,
|
|
3843
|
-
`automata do-work: could not start a run because ${detail} Summarise the discussion in a new issue, or shorten the thread, and try again.`
|
|
3844
|
-
);
|
|
3845
|
-
} catch (err) {
|
|
3846
|
-
progress(` warning: could not update the marker comment: ${err.message}
|
|
3847
|
-
`);
|
|
3848
|
-
}
|
|
3849
|
-
return { ...base, outcome: "failed", detail };
|
|
3977
|
+
return refuseBeforeRun(
|
|
3978
|
+
base,
|
|
3979
|
+
marker,
|
|
3980
|
+
oversized,
|
|
3981
|
+
`automata do-work: could not start a run because ${oversized} Summarise the discussion in a new issue, or shorten the thread, and try again.`
|
|
3982
|
+
);
|
|
3850
3983
|
}
|
|
3984
|
+
const resolved = resolveItemExecution(item, settings);
|
|
3985
|
+
if (!resolved.ok) {
|
|
3986
|
+
const detail = describeInvalidTool(resolved.invalidTool);
|
|
3987
|
+
return refuseBeforeRun(
|
|
3988
|
+
base,
|
|
3989
|
+
marker,
|
|
3990
|
+
detail,
|
|
3991
|
+
`automata do-work: ${detail}. No run was started. Reply here with a corrected directive, or none at all, to have another attempt made.`
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
const execution = toExecution(resolved);
|
|
3851
3995
|
let runError = null;
|
|
3852
3996
|
try {
|
|
3853
|
-
await invokeExecutor(prompt,
|
|
3997
|
+
await invokeExecutor(prompt, execution, silent);
|
|
3854
3998
|
} catch (err) {
|
|
3855
3999
|
runError = err;
|
|
3856
4000
|
}
|
|
@@ -3860,7 +4004,7 @@ async function processItem(planned, settings, silent) {
|
|
|
3860
4004
|
progress(` ${reconciled.detail}
|
|
3861
4005
|
`);
|
|
3862
4006
|
const outcome = adjustOutcome(reconciled, item, settings, buriedByNote);
|
|
3863
|
-
return { ...base, outcome: outcome.outcome, detail: outcome.detail, ranExecutor };
|
|
4007
|
+
return { ...base, outcome: outcome.outcome, detail: outcome.detail, ranExecutor, execution };
|
|
3864
4008
|
}
|
|
3865
4009
|
function describeOversizedPrompt(prompt) {
|
|
3866
4010
|
const MAX_PROMPT_BYTES = 96 * 1024;
|
|
@@ -3896,10 +4040,27 @@ function summarize(reports) {
|
|
|
3896
4040
|
return;
|
|
3897
4041
|
}
|
|
3898
4042
|
for (const report of reports) {
|
|
3899
|
-
|
|
4043
|
+
const ran = report.execution === void 0 ? "" : ` \xB7 ${describeExecution(report.execution)}`;
|
|
4044
|
+
out(` #${String(report.issue)} ${report.turn ?? "-"} ${report.outcome} \u2014 ${report.detail}${ran}
|
|
3900
4045
|
`);
|
|
3901
4046
|
}
|
|
3902
4047
|
}
|
|
4048
|
+
function toItemJson(report) {
|
|
4049
|
+
return {
|
|
4050
|
+
issue: report.issue,
|
|
4051
|
+
title: report.title,
|
|
4052
|
+
turn: report.turn,
|
|
4053
|
+
outcome: report.outcome,
|
|
4054
|
+
detail: report.detail,
|
|
4055
|
+
ranExecutor: report.ranExecutor ?? false,
|
|
4056
|
+
executor: report.execution?.executor ?? null,
|
|
4057
|
+
model: report.execution?.model ?? null,
|
|
4058
|
+
effort: report.execution?.effort ?? null,
|
|
4059
|
+
executorSource: report.execution?.executorSource ?? null,
|
|
4060
|
+
modelSource: report.execution?.modelSource ?? null,
|
|
4061
|
+
effortSource: report.execution?.effortSource ?? null
|
|
4062
|
+
};
|
|
4063
|
+
}
|
|
3903
4064
|
var doWorkCommand = new Command6("do-work").description(
|
|
3904
4065
|
"Run one tick of the autonomous loop: find the issues whose newest authorized message the agent has not answered, and answer them"
|
|
3905
4066
|
).option("--with <executor>", "Executor to use: claude or codex (default: from config, else claude)").option("--model <string>", "Model identifier to pass to the executor, overriding the configured default for it").option(
|
|
@@ -4048,32 +4209,69 @@ ${describePlan(decisions)}`;
|
|
|
4048
4209
|
const degraded = reports.some((report) => report.outcome !== "answered");
|
|
4049
4210
|
const exitCode = degraded ? 2 : 0;
|
|
4050
4211
|
if (options.json) {
|
|
4051
|
-
out(
|
|
4212
|
+
out(
|
|
4213
|
+
JSON.stringify(
|
|
4214
|
+
{ dryRun: false, plan: decisions.map(toPlanJson), items: reports.map(toItemJson), exitCode },
|
|
4215
|
+
null,
|
|
4216
|
+
2
|
|
4217
|
+
) + "\n"
|
|
4218
|
+
);
|
|
4052
4219
|
} else {
|
|
4053
4220
|
summarize(reports);
|
|
4054
4221
|
}
|
|
4055
4222
|
return exitCode;
|
|
4056
4223
|
}
|
|
4224
|
+
function toRunJson(entry) {
|
|
4225
|
+
if (entry.kind === "refused") {
|
|
4226
|
+
return {
|
|
4227
|
+
issue: entry.item.issue.number,
|
|
4228
|
+
turn: entry.item.turn,
|
|
4229
|
+
executor: null,
|
|
4230
|
+
model: null,
|
|
4231
|
+
effort: null,
|
|
4232
|
+
executorSource: null,
|
|
4233
|
+
modelSource: null,
|
|
4234
|
+
effortSource: null,
|
|
4235
|
+
refusal: entry.refusal,
|
|
4236
|
+
bin: null,
|
|
4237
|
+
args: null,
|
|
4238
|
+
command: null,
|
|
4239
|
+
prompt: null
|
|
4240
|
+
};
|
|
4241
|
+
}
|
|
4242
|
+
return {
|
|
4243
|
+
issue: entry.item.issue.number,
|
|
4244
|
+
turn: entry.item.turn,
|
|
4245
|
+
executor: entry.execution.executor,
|
|
4246
|
+
model: entry.execution.model ?? null,
|
|
4247
|
+
effort: entry.execution.effort ?? null,
|
|
4248
|
+
executorSource: entry.execution.executorSource,
|
|
4249
|
+
modelSource: entry.execution.modelSource,
|
|
4250
|
+
effortSource: entry.execution.effortSource,
|
|
4251
|
+
refusal: null,
|
|
4252
|
+
bin: entry.run.bin,
|
|
4253
|
+
args: entry.run.args,
|
|
4254
|
+
command: entry.run.command,
|
|
4255
|
+
prompt: entry.run.prompt
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4057
4258
|
function reportDryRun(items, decisions, settings, options) {
|
|
4058
4259
|
const describable = settings.maxRuns > 0 ? items.slice(0, settings.maxRuns) : items;
|
|
4059
|
-
const planned = describable.map((item) =>
|
|
4260
|
+
const planned = describable.map((item) => {
|
|
4261
|
+
const resolved = resolveItemExecution(item, settings);
|
|
4262
|
+
if (!resolved.ok) {
|
|
4263
|
+
return { kind: "refused", item, refusal: describeInvalidTool(resolved.invalidTool) };
|
|
4264
|
+
}
|
|
4265
|
+
const execution = toExecution(resolved);
|
|
4266
|
+
return { kind: "run", item, execution, run: planRun(item, settings, execution) };
|
|
4267
|
+
});
|
|
4060
4268
|
if (options.json) {
|
|
4061
4269
|
out(
|
|
4062
4270
|
JSON.stringify(
|
|
4063
4271
|
{
|
|
4064
4272
|
dryRun: true,
|
|
4065
4273
|
plan: decisions.map(toPlanJson),
|
|
4066
|
-
runs: planned.map(
|
|
4067
|
-
issue: describable[index].issue.number,
|
|
4068
|
-
turn: describable[index].turn,
|
|
4069
|
-
executor: settings.executor,
|
|
4070
|
-
model: settings.model ?? null,
|
|
4071
|
-
effort: settings.effort ?? null,
|
|
4072
|
-
bin: run5.bin,
|
|
4073
|
-
args: run5.args,
|
|
4074
|
-
command: run5.command,
|
|
4075
|
-
prompt: run5.prompt
|
|
4076
|
-
}))
|
|
4274
|
+
runs: planned.map(toRunJson)
|
|
4077
4275
|
},
|
|
4078
4276
|
null,
|
|
4079
4277
|
2
|
|
@@ -4081,8 +4279,10 @@ function reportDryRun(items, decisions, settings, options) {
|
|
|
4081
4279
|
);
|
|
4082
4280
|
return;
|
|
4083
4281
|
}
|
|
4084
|
-
for (const
|
|
4085
|
-
out(
|
|
4282
|
+
for (const entry of planned) {
|
|
4283
|
+
out(
|
|
4284
|
+
"\n" + (entry.kind === "refused" ? describeRefusedRun(entry.item, entry.refusal) : describePlannedRun(entry.item, settings, entry.run, entry.execution))
|
|
4285
|
+
);
|
|
4086
4286
|
}
|
|
4087
4287
|
const deferred = items.length - describable.length;
|
|
4088
4288
|
if (deferred > 0) {
|