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.
Files changed (2) hide show
  1. package/dist/index.js +271 -71
  2. 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 trimmed = value.trim();
38
- const parsed = Number.parseInt(trimmed, 10);
39
- if (Number.isNaN(parsed) || parsed < 0 || String(parsed) !== trimmed || !Number.isSafeInteger(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 trimmed = value.trim();
1563
- if (trimmed.length === 0) {
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 trimmed;
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 newest = [...messages].sort(byCreatedAt).at(-1);
2136
- return newest === void 0 ? "none" : classify(newest.author, p);
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 newest = thread.comments.at(-1);
2912
- const author = newest?.author ?? "unknown";
2913
- const body = newest?.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 trimmed = value.trim();
3325
- if (!/^\d+$/.test(trimmed)) {
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(trimmed);
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 executor = doWork.executor ?? DEFAULT_DO_WORK.executor;
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
- executor = requested;
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
- executor,
3377
- // --model wins; otherwise take the default for the executor in use.
3378
- model: options.model ?? doWork.models?.[executor],
3379
- // Same precedence for the reasoning effort, but both candidates go through
3380
- // the one normaliser: a config value is validated as non-empty when the
3381
- // file is read, yet nothing trims it, so `" high "` would otherwise reach
3382
- // the executor with its padding and be silently ignored as an unknown
3383
- // level. `??` keeps the flag winning an empty `--effort` is not nullish,
3384
- // so it is still rejected rather than falling through to the default.
3385
- effort: resolveEffortOption(options.effort ?? doWork.effort?.[executor]),
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 planRun(item, settings) {
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(settings.executor === "codex" ? "codex" : "claude");
3427
- const args = settings.executor === "codex" ? buildCodexArgs(prompt, { yolo: true, model: settings.model, effort: settings.effort }) : buildClaudeArgs(prompt, {
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: settings.model,
3431
- effort: settings.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 ${settings.executor}${modelNote}${effortNote}`,
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, settings, silent) {
3732
- if (settings.executor === "codex") {
3733
- await runCodex(prompt, { model: settings.model, effort: settings.effort });
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: settings.model, effort: settings.effort, printSteps: !silent });
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
- const detail = oversized;
3837
- progress(` failed: ${detail}
3838
- `);
3839
- inFlightMarker = null;
3840
- try {
3841
- updateMarker(
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, settings, silent);
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
- out(` #${String(report.issue)} ${report.turn ?? "-"} ${report.outcome} \u2014 ${report.detail}
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(JSON.stringify({ dryRun: false, plan: decisions.map(toPlanJson), items: reports, exitCode }, null, 2) + "\n");
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) => planRun(item, settings));
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((run5, index) => ({
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 [index, run5] of planned.entries()) {
4085
- out("\n" + describePlannedRun(describable[index], settings, run5));
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automata-cli",
3
- "version": "0.6.0-develop.257",
3
+ "version": "0.6.0-develop.273",
4
4
  "description": "Automata CLI tool",
5
5
  "type": "module",
6
6
  "engines": {