executant 1.14.0 → 1.16.0

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 CHANGED
@@ -52,7 +52,7 @@ var init_update = __esm({
52
52
  // src/index.ts
53
53
  import React3 from "react";
54
54
  import { render } from "ink";
55
- import { readFileSync as readFileSync6 } from "node:fs";
55
+ import { readFileSync as readFileSync7 } from "node:fs";
56
56
  import { dirname as dirname5, join as join5 } from "node:path";
57
57
  import { fileURLToPath as fileURLToPath2 } from "node:url";
58
58
 
@@ -406,12 +406,11 @@ import { execSync, spawn as spawn2 } from "node:child_process";
406
406
  import { zodToJsonSchema } from "zod-to-json-schema";
407
407
  var METHODOLOGY = loadPrompt("development-methodology");
408
408
  var DEFAULT_TOOLS = ["Read", "Edit", "Write", "Bash", "Glob", "Grep"];
409
- function buildClaudeArgs(task) {
409
+ function buildClaudeArgs(task, interactive = false) {
410
410
  const allowedTools = task.allowedTools ?? DEFAULT_TOOLS;
411
411
  const permissionMode = task.permissionMode ?? "bypassPermissions";
412
412
  return [
413
- "--print",
414
- task.prompt,
413
+ ...interactive ? [] : ["--print", task.prompt],
415
414
  "--output-format",
416
415
  "stream-json",
417
416
  "--verbose",
@@ -433,7 +432,7 @@ function resolveClaudePath() {
433
432
  );
434
433
  }
435
434
  }
436
- async function* runClaude(task) {
435
+ async function* runClaude(task, _channel) {
437
436
  yield {
438
437
  type: "log",
439
438
  level: "info",
@@ -562,7 +561,7 @@ function shouldSkipStep(stepNumber, name, options2) {
562
561
  }
563
562
  return options2.fromStep !== void 0 && stepNumber < options2.fromStep[0];
564
563
  }
565
- async function* runWorkflow(workflow2, options2 = {}) {
564
+ async function* runWorkflow(workflow2, options2 = {}, channel2) {
566
565
  const workflowStart = Date.now();
567
566
  yield { type: "workflow:start", workflow: workflow2 };
568
567
  for (const [i, task] of workflow2.tasks.entries()) {
@@ -575,7 +574,7 @@ async function* runWorkflow(workflow2, options2 = {}) {
575
574
  yield { type: "step:start", index: i, name: task.name };
576
575
  const from = options2.fromStep && options2.fromStep[0] === stepNumber ? options2.fromStep.slice(1) : void 0;
577
576
  try {
578
- for await (const event of runStep(task, from)) {
577
+ for await (const event of runStep(task, from, channel2)) {
579
578
  if (event.type === "step:iteration" || event.type === "step:inner" || event.type === "output:text" || event.type === "output:tool") {
580
579
  yield { ...event, index: i };
581
580
  } else {
@@ -600,7 +599,7 @@ async function* runWorkflow(workflow2, options2 = {}) {
600
599
  durationMs: Date.now() - workflowStart
601
600
  };
602
601
  }
603
- async function* runStep(task, from) {
602
+ async function* runStep(task, from, channel2) {
604
603
  switch (task.type) {
605
604
  case "log":
606
605
  yield* runLog(task);
@@ -619,11 +618,20 @@ async function* runStep(task, from) {
619
618
  }
620
619
  case "claude": {
621
620
  const expanded = expandContext(task);
622
- yield* expanded.llmAsJudge ? runClaudeWithJudge(expanded) : runClaude(expanded);
621
+ const queued = channel2?.consumeQueue() ?? [];
622
+ const enriched = queued.length > 0 ? {
623
+ ...expanded,
624
+ prompt: `[User correction from a previous step]
625
+ ${queued.join("\n")}
626
+
627
+ ---
628
+ ${expanded.prompt}`
629
+ } : expanded;
630
+ yield* enriched.llmAsJudge ? runClaudeWithJudge(enriched, channel2) : runClaude(enriched, channel2);
623
631
  break;
624
632
  }
625
633
  case "forEach":
626
- yield* runForEach(task, from);
634
+ yield* runForEach(task, from, channel2);
627
635
  break;
628
636
  default: {
629
637
  const _ = task;
@@ -634,7 +642,7 @@ async function* runStep(task, from) {
634
642
  async function* runLog(task) {
635
643
  yield { type: "output:text", index: -1, text: task.message };
636
644
  }
637
- async function* runForEach(task, from) {
645
+ async function* runForEach(task, from, channel2) {
638
646
  const items = await resolveItems(task.forEach);
639
647
  const total = items.length;
640
648
  const innerTotal = task.inner.length;
@@ -669,7 +677,7 @@ async function* runForEach(task, from) {
669
677
  }
670
678
  const childFrom = childIdx === startChild ? iterFrom?.slice(1) : void 0;
671
679
  try {
672
- for await (const event of runStep(substituted, childFrom)) {
680
+ for await (const event of runStep(substituted, childFrom, channel2)) {
673
681
  if (event.type !== "step:iteration" && event.type !== "step:inner") {
674
682
  yield event;
675
683
  }
@@ -803,14 +811,14 @@ async function* runCommandWithHealing(task) {
803
811
  }
804
812
  }
805
813
  }
806
- async function* runClaudeWithJudge(task) {
814
+ async function* runClaudeWithJudge(task, channel2) {
807
815
  let judgeContext = "";
808
816
  for (let attempt = 0; attempt < MAX_JUDGE_RETRIES; attempt++) {
809
817
  const prompt = attempt === 0 ? task.prompt : `${task.prompt}
810
818
 
811
819
  ${fillTemplate(JUDGE_RETRY_CONTEXT, { FEEDBACK: judgeContext })}`;
812
820
  const lines = [];
813
- yield* collectLines(runClaude({ ...task, prompt }), lines);
821
+ yield* collectLines(runClaude({ ...task, prompt }, channel2), lines);
814
822
  yield {
815
823
  type: "log",
816
824
  level: "info",
@@ -918,18 +926,86 @@ ${blocks.join("\n\n")}`;
918
926
  init_update();
919
927
 
920
928
  // src/ui/App.tsx
921
- import { useEffect as useEffect2, useReducer, useState } from "react";
922
- import { Box as Box5, Text as Text5, useApp, useStdin, useStdout } from "ink";
929
+ import { useEffect as useEffect2, useReducer, useState as useState2 } from "react";
930
+ import { Box as Box6, Text as Text6, useApp, useStdin, useStdout } from "ink";
923
931
 
924
932
  // src/ui/KeyboardHandler.tsx
925
933
  import { useInput } from "ink";
926
- function KeyboardHandler({ onExit }) {
934
+ function KeyboardHandler({
935
+ onExit,
936
+ onInterject,
937
+ isInterjecting
938
+ }) {
927
939
  useInput((input, key) => {
940
+ if (isInterjecting) return;
928
941
  if (input === "q" || key.ctrl && input === "c") onExit();
942
+ if (input === "i" && onInterject) onInterject();
929
943
  });
930
944
  return null;
931
945
  }
932
946
 
947
+ // src/ui/InterjectInput.tsx
948
+ import { useState } from "react";
949
+ import { Box, Text, useInput as useInput2 } from "ink";
950
+
951
+ // src/ui/theme.ts
952
+ import { createRequire } from "node:module";
953
+ import { oklchToHex } from "@coston/design-tokens";
954
+ var THEME_NAME = "purple-dark";
955
+ var _require = createRequire(import.meta.url);
956
+ var { themes } = _require("@coston/design-tokens/tokens.json");
957
+ function hex(key) {
958
+ return oklchToHex(themes[THEME_NAME][key]);
959
+ }
960
+ var theme = {
961
+ foreground: hex("foreground"),
962
+ // primary text
963
+ muted: hex("muted-foreground"),
964
+ // dimmed / inactive text and borders
965
+ primary: hex("primary"),
966
+ // tool calls, cursor, active
967
+ primaryLight: hex("secondary-foreground"),
968
+ // lighter tint of primary (same hue, higher lightness)
969
+ success: hex("success"),
970
+ // completed steps
971
+ error: hex("destructive"),
972
+ // errors
973
+ warning: hex("warning"),
974
+ // warnings, retries, updates
975
+ border: hex("border")
976
+ // log pane border
977
+ };
978
+
979
+ // src/ui/InterjectInput.tsx
980
+ import { jsx, jsxs } from "react/jsx-runtime";
981
+ function InterjectInput({ onSubmit, onCancel }) {
982
+ const [value, setValue] = useState("");
983
+ useInput2((input, key) => {
984
+ if (key.escape) {
985
+ onCancel();
986
+ return;
987
+ }
988
+ if (key.return) {
989
+ if (value.trim()) onSubmit(value.trim());
990
+ else onCancel();
991
+ return;
992
+ }
993
+ if (key.backspace || key.delete) {
994
+ setValue((v) => v.slice(0, -1));
995
+ return;
996
+ }
997
+ if (input && !key.ctrl && !key.meta) {
998
+ setValue((v) => v + input);
999
+ }
1000
+ });
1001
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", children: [
1002
+ /* @__PURE__ */ jsx(Text, { color: theme.primary, bold: true, children: "\u25B7 " }),
1003
+ /* @__PURE__ */ jsx(Text, { children: value }),
1004
+ /* @__PURE__ */ jsx(Text, { color: theme.primary, children: "\u258C" }),
1005
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: " esc to cancel" })
1006
+ ] });
1007
+ }
1008
+
933
1009
  // src/ui/formatTool.ts
934
1010
  function formatToolCall2(tool, input) {
935
1011
  switch (tool) {
@@ -1081,6 +1157,11 @@ function reducer(state, event) {
1081
1157
  if (idx >= state.tasks.length) return state;
1082
1158
  return appendLines(state, idx, `[${event.level}] ${event.text}`);
1083
1159
  }
1160
+ case "step:interjection": {
1161
+ const idx = event.index;
1162
+ if (idx >= state.tasks.length) return state;
1163
+ return appendLines(state, idx, `[interjection] ${event.message}`);
1164
+ }
1084
1165
  default: {
1085
1166
  const _ = event;
1086
1167
  void _;
@@ -1116,35 +1197,7 @@ function appendLines(state, index, text) {
1116
1197
  }
1117
1198
 
1118
1199
  // src/ui/TaskRow.tsx
1119
- import { Box, Text } from "ink";
1120
-
1121
- // src/ui/theme.ts
1122
- import { createRequire } from "node:module";
1123
- import { oklchToHex } from "@coston/design-tokens";
1124
- var THEME_NAME = "purple-dark";
1125
- var _require = createRequire(import.meta.url);
1126
- var { themes } = _require("@coston/design-tokens/tokens.json");
1127
- function hex(key) {
1128
- return oklchToHex(themes[THEME_NAME][key]);
1129
- }
1130
- var theme = {
1131
- foreground: hex("foreground"),
1132
- // primary text
1133
- muted: hex("muted-foreground"),
1134
- // dimmed / inactive text and borders
1135
- primary: hex("primary"),
1136
- // tool calls, cursor, active
1137
- primaryLight: hex("secondary-foreground"),
1138
- // lighter tint of primary (same hue, higher lightness)
1139
- success: hex("success"),
1140
- // completed steps
1141
- error: hex("destructive"),
1142
- // errors
1143
- warning: hex("warning"),
1144
- // warnings, retries, updates
1145
- border: hex("border")
1146
- // log pane border
1147
- };
1200
+ import { Box as Box2, Text as Text2 } from "ink";
1148
1201
 
1149
1202
  // src/ui/utils.ts
1150
1203
  var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -1182,7 +1235,7 @@ function formatTaskElapsed(start, end, status) {
1182
1235
  }
1183
1236
 
1184
1237
  // src/ui/TaskRow.tsx
1185
- import { jsx, jsxs } from "react/jsx-runtime";
1238
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1186
1239
  function TaskRow({ taskState, isActive, index, tick }) {
1187
1240
  const { task, status, startTime, endTime } = taskState;
1188
1241
  const icon = statusIcon(status, tick);
@@ -1190,13 +1243,13 @@ function TaskRow({ taskState, isActive, index, tick }) {
1190
1243
  const elapsed = formatTaskElapsed(startTime, endTime, status);
1191
1244
  const iterInfo = formatIterCount(taskState.iterationHistory);
1192
1245
  const label = `${index + 1}. ${task.name}${iterInfo}`;
1193
- return /* @__PURE__ */ jsxs(Box, { children: [
1194
- /* @__PURE__ */ jsxs(Text, { color, children: [
1246
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
1247
+ /* @__PURE__ */ jsxs2(Text2, { color, children: [
1195
1248
  icon,
1196
1249
  " "
1197
1250
  ] }),
1198
- /* @__PURE__ */ jsx(Text, { color: isActive ? theme.foreground : theme.muted, bold: isActive, children: label }),
1199
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
1251
+ /* @__PURE__ */ jsx2(Text2, { color: isActive ? theme.foreground : theme.muted, bold: isActive, children: label }),
1252
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1200
1253
  " ",
1201
1254
  elapsed
1202
1255
  ] })
@@ -1215,22 +1268,22 @@ function formatIterCount(history) {
1215
1268
  }
1216
1269
 
1217
1270
  // src/ui/IterationRow.tsx
1218
- import { Box as Box2, Text as Text2 } from "ink";
1219
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1271
+ import { Box as Box3, Text as Text3 } from "ink";
1272
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1220
1273
  function IterationRow({ record, tick }) {
1221
1274
  const icon = statusIcon(record.status, tick);
1222
1275
  const color = STATUS_COLOR[record.status] ?? theme.primary;
1223
1276
  const innerText = record.inner ? ` \u2014 ${stripItem(record.inner.name, record.item)} [${record.inner.index + 1}/${record.inner.total}]` : "";
1224
1277
  const ms = (record.endTime ?? Date.now()) - record.startTime;
1225
1278
  const elapsed = `${(ms / 1e3).toFixed(1)}s`;
1226
- return /* @__PURE__ */ jsxs2(Box2, { children: [
1227
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " " }),
1228
- /* @__PURE__ */ jsx2(Text2, { color, children: icon }),
1229
- /* @__PURE__ */ jsx2(Text2, { children: " " }),
1230
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: `[${record.iteration}/${record.total}]` }),
1231
- /* @__PURE__ */ jsx2(Text2, { children: " " }),
1232
- /* @__PURE__ */ jsxs2(
1233
- Text2,
1279
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
1280
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " " }),
1281
+ /* @__PURE__ */ jsx3(Text3, { color, children: icon }),
1282
+ /* @__PURE__ */ jsx3(Text3, { children: " " }),
1283
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `[${record.iteration}/${record.total}]` }),
1284
+ /* @__PURE__ */ jsx3(Text3, { children: " " }),
1285
+ /* @__PURE__ */ jsxs3(
1286
+ Text3,
1234
1287
  {
1235
1288
  color: record.status === "running" ? theme.foreground : theme.muted,
1236
1289
  children: [
@@ -1239,7 +1292,7 @@ function IterationRow({ record, tick }) {
1239
1292
  ]
1240
1293
  }
1241
1294
  ),
1242
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1295
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
1243
1296
  " ",
1244
1297
  elapsed
1245
1298
  ] })
@@ -1261,29 +1314,29 @@ function IterationList({
1261
1314
  if (isRepeatStyle(iterationHistory)) return null;
1262
1315
  const hidden = iterationHistory.length - maxVisible;
1263
1316
  const visible = iterationHistory.slice(-maxVisible);
1264
- return /* @__PURE__ */ jsxs2(Fragment, { children: [
1265
- hidden > 0 && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: ` \u2026 ${hidden} earlier` }),
1266
- visible.map((record) => /* @__PURE__ */ jsx2(IterationRow, { record, tick }, record.iteration))
1317
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
1318
+ hidden > 0 && /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: ` \u2026 ${hidden} earlier` }),
1319
+ visible.map((record) => /* @__PURE__ */ jsx3(IterationRow, { record, tick }, record.iteration))
1267
1320
  ] });
1268
1321
  }
1269
1322
 
1270
1323
  // src/ui/LogPane.tsx
1271
- import { Box as Box3, Text as Text3 } from "ink";
1272
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1324
+ import { Box as Box4, Text as Text4 } from "ink";
1325
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1273
1326
  function LogPane({ lines, isActive = false, maxLines = 15 }) {
1274
1327
  const visible = lines.slice(-maxLines);
1275
1328
  if (visible.length === 0) {
1276
- return /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isActive ? "\u2838 waiting for output\u2026" : "\u2014 no output yet \u2014" }) });
1329
+ return /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: isActive ? "\u2838 waiting for output\u2026" : "\u2014 no output yet \u2014" }) });
1277
1330
  }
1278
- return /* @__PURE__ */ jsx3(
1279
- Box3,
1331
+ return /* @__PURE__ */ jsx4(
1332
+ Box4,
1280
1333
  {
1281
1334
  flexDirection: "column",
1282
1335
  marginTop: 1,
1283
1336
  borderStyle: "single",
1284
1337
  borderColor: theme.border,
1285
1338
  paddingX: 1,
1286
- children: visible.map((line, i) => /* @__PURE__ */ jsx3(
1339
+ children: visible.map((line, i) => /* @__PURE__ */ jsx4(
1287
1340
  LogLine,
1288
1341
  {
1289
1342
  text: line,
@@ -1295,52 +1348,52 @@ function LogPane({ lines, isActive = false, maxLines = 15 }) {
1295
1348
  );
1296
1349
  }
1297
1350
  function LogLine({ text, cursor }) {
1298
- const suffix = cursor ? /* @__PURE__ */ jsx3(Text3, { color: theme.primary, children: " \u258C" }) : null;
1351
+ const suffix = cursor ? /* @__PURE__ */ jsx4(Text4, { color: theme.primary, children: " \u258C" }) : null;
1299
1352
  if (/^\[[\w:]+\]/.test(text)) {
1300
1353
  const bracket = text.match(/^\[[\w:]+\]/)?.[0] ?? "";
1301
1354
  const rest = text.slice(bracket.length);
1302
- return /* @__PURE__ */ jsxs3(Text3, { children: [
1303
- /* @__PURE__ */ jsx3(Text3, { color: theme.primary, children: bracket }),
1304
- /* @__PURE__ */ jsx3(Text3, { children: rest }),
1355
+ return /* @__PURE__ */ jsxs4(Text4, { children: [
1356
+ /* @__PURE__ */ jsx4(Text4, { color: theme.primary, children: bracket }),
1357
+ /* @__PURE__ */ jsx4(Text4, { children: rest }),
1305
1358
  suffix
1306
1359
  ] });
1307
1360
  }
1308
1361
  if (/^\s*\$\s/.test(text))
1309
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1362
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.warning, children: [
1310
1363
  text,
1311
1364
  suffix
1312
1365
  ] });
1313
1366
  if (text.startsWith("[warn]"))
1314
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1367
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.warning, children: [
1315
1368
  text,
1316
1369
  suffix
1317
1370
  ] });
1318
1371
  if (text.startsWith("[error]"))
1319
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.error, children: [
1372
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.error, children: [
1320
1373
  text,
1321
1374
  suffix
1322
1375
  ] });
1323
1376
  if (/^[\s]*(✓|✔|✅|done|success|compiled|built|passed)/i.test(text) && !/\b(error|fail|failed|warn|warning)\b/i.test(text))
1324
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.success, children: [
1377
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.success, children: [
1325
1378
  text,
1326
1379
  suffix
1327
1380
  ] });
1328
1381
  if (/\b(error|failed|fail)\b/i.test(text))
1329
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.error, children: [
1382
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.error, children: [
1330
1383
  text,
1331
1384
  suffix
1332
1385
  ] });
1333
1386
  if (/\b(warn|warning)\b/i.test(text))
1334
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.warning, children: [
1387
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.warning, children: [
1335
1388
  text,
1336
1389
  suffix
1337
1390
  ] });
1338
1391
  if (/^[·…⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/.test(text))
1339
- return /* @__PURE__ */ jsxs3(Text3, { color: theme.muted, children: [
1392
+ return /* @__PURE__ */ jsxs4(Text4, { color: theme.muted, children: [
1340
1393
  text,
1341
1394
  suffix
1342
1395
  ] });
1343
- return /* @__PURE__ */ jsxs3(Text3, { children: [
1396
+ return /* @__PURE__ */ jsxs4(Text4, { children: [
1344
1397
  text,
1345
1398
  suffix
1346
1399
  ] });
@@ -1360,8 +1413,8 @@ function useInterval(callback, delayMs) {
1360
1413
  }
1361
1414
 
1362
1415
  // src/ui/BrandMark.tsx
1363
- import { Box as Box4, Text as Text4 } from "ink";
1364
- import { jsx as jsx4 } from "react/jsx-runtime";
1416
+ import { Box as Box5, Text as Text5 } from "ink";
1417
+ import { jsx as jsx5 } from "react/jsx-runtime";
1365
1418
  var BRAND = "Executant";
1366
1419
  var SWEEP_TICKS = BRAND.length * 2;
1367
1420
  var GAP_TICKS = 30;
@@ -1374,15 +1427,22 @@ function charColor(charIndex, tick, isActive) {
1374
1427
  return charIndex === charPos ? theme.primaryLight : theme.primary;
1375
1428
  }
1376
1429
  function BrandMark({ tick, isActive }) {
1377
- return /* @__PURE__ */ jsx4(Box4, { children: [...BRAND].map((char, i) => /* @__PURE__ */ jsx4(Text4, { color: charColor(i, tick, isActive), bold: true, children: char }, i)) });
1430
+ return /* @__PURE__ */ jsx5(Box5, { children: [...BRAND].map((char, i) => /* @__PURE__ */ jsx5(Text5, { color: charColor(i, tick, isActive), bold: true, children: char }, i)) });
1378
1431
  }
1379
1432
 
1380
1433
  // src/ui/App.tsx
1381
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1434
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1382
1435
  var MAX_VISIBLE_ITERATIONS = 8;
1383
- function App({ workflow: workflow2, events: events2, options: options2, updateCheck: updateCheck2 }) {
1436
+ function App({
1437
+ workflow: workflow2,
1438
+ events: events2,
1439
+ options: options2,
1440
+ updateCheck: updateCheck2,
1441
+ interjectChannel
1442
+ }) {
1384
1443
  const { exit } = useApp();
1385
1444
  const [state, dispatch] = useReducer(reducer, buildInitialState(workflow2));
1445
+ const [isInterjecting, setIsInterjecting] = useState2(false);
1386
1446
  useEffect2(() => {
1387
1447
  let active = true;
1388
1448
  (async () => {
@@ -1411,11 +1471,11 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1411
1471
  }, [events2, exit]);
1412
1472
  const { isRawModeSupported } = useStdin();
1413
1473
  const { stdout } = useStdout();
1414
- const [updateVersion, setUpdateVersion] = useState(null);
1474
+ const [updateVersion, setUpdateVersion] = useState2(null);
1415
1475
  useEffect2(() => {
1416
1476
  updateCheck2.then(setUpdateVersion);
1417
1477
  }, [updateCheck2]);
1418
- const [tick, setTick] = useState(0);
1478
+ const [tick, setTick] = useState2(0);
1419
1479
  useInterval(() => {
1420
1480
  if (!state.endTime) setTick((t) => t + 1);
1421
1481
  }, 100);
@@ -1426,7 +1486,7 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1426
1486
  runningTask?.iterationHistory,
1427
1487
  MAX_VISIBLE_ITERATIONS
1428
1488
  );
1429
- const FIXED_OVERHEAD = 12 + (updateVersion ? 1 : 0);
1489
+ const FIXED_OVERHEAD = 12 + (updateVersion ? 1 : 0) + (isInterjecting ? 1 : 0);
1430
1490
  const availableForTaskSection = Math.max(
1431
1491
  1,
1432
1492
  terminalRows - FIXED_OVERHEAD - LOG_PANE_MIN - iterationRowCount
@@ -1446,11 +1506,11 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1446
1506
  ).length;
1447
1507
  const totalCount = state.tasks.length;
1448
1508
  const filterInfo = options2?.stepFilter ? ` [step: ${options2.stepFilter}]` : options2?.fromStep ? ` [from step: ${options2.fromStep}]` : "";
1449
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", padding: 1, children: [
1450
- /* @__PURE__ */ jsx5(Box5, { marginBottom: 1, children: /* @__PURE__ */ jsx5(BrandMark, { tick, isActive: !state.endTime }) }),
1451
- /* @__PURE__ */ jsxs4(Box5, { marginBottom: 1, children: [
1452
- /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.primary, children: workflow2.goal }),
1453
- /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1509
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", padding: 1, children: [
1510
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(BrandMark, { tick, isActive: !state.endTime }) }),
1511
+ /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, children: [
1512
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.primary, children: workflow2.goal }),
1513
+ /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1454
1514
  " ",
1455
1515
  completedCount,
1456
1516
  "/",
@@ -1460,8 +1520,8 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1460
1520
  filterInfo
1461
1521
  ] })
1462
1522
  ] }),
1463
- /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
1464
- hiddenTaskCount > 0 && /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
1523
+ /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginBottom: 1, children: [
1524
+ hiddenTaskCount > 0 && /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
1465
1525
  " ",
1466
1526
  "\xB7\xB7\xB7 ",
1467
1527
  hiddenTaskCount,
@@ -1469,8 +1529,8 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1469
1529
  ] }),
1470
1530
  taskSlice.map((taskState, i) => {
1471
1531
  const globalIndex = hiddenTaskCount + i;
1472
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
1473
- /* @__PURE__ */ jsx5(
1532
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", children: [
1533
+ /* @__PURE__ */ jsx6(
1474
1534
  TaskRow,
1475
1535
  {
1476
1536
  index: globalIndex,
@@ -1479,7 +1539,7 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1479
1539
  isActive: globalIndex === state.currentIndex
1480
1540
  }
1481
1541
  ),
1482
- taskState.status === "running" && taskState.iterationHistory?.length ? /* @__PURE__ */ jsx5(
1542
+ taskState.status === "running" && taskState.iterationHistory?.length ? /* @__PURE__ */ jsx6(
1483
1543
  IterationList,
1484
1544
  {
1485
1545
  iterationHistory: taskState.iterationHistory,
@@ -1490,7 +1550,7 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1490
1550
  ] }, globalIndex);
1491
1551
  })
1492
1552
  ] }),
1493
- activeTask && /* @__PURE__ */ jsx5(
1553
+ activeTask && /* @__PURE__ */ jsx6(
1494
1554
  LogPane,
1495
1555
  {
1496
1556
  lines: activeTask.lines,
@@ -1498,22 +1558,44 @@ function App({ workflow: workflow2, events: events2, options: options2, updateCh
1498
1558
  maxLines: logPaneMaxLines
1499
1559
  }
1500
1560
  ),
1501
- state.endTime !== void 0 && state.writtenFiles.length > 0 && /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginTop: 1, children: [
1502
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "files written:" }),
1503
- state.writtenFiles.map((f) => /* @__PURE__ */ jsxs4(Text5, { color: theme.primary, children: [
1561
+ state.endTime !== void 0 && state.writtenFiles.length > 0 && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 1, children: [
1562
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "files written:" }),
1563
+ state.writtenFiles.map((f) => /* @__PURE__ */ jsxs5(Text6, { color: theme.primary, children: [
1504
1564
  " ",
1505
1565
  f
1506
1566
  ] }, f))
1507
1567
  ] }),
1508
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
1509
- updateVersion && /* @__PURE__ */ jsxs4(Text5, { color: theme.warning, children: [
1568
+ isInterjecting && interjectChannel && /* @__PURE__ */ jsx6(
1569
+ InterjectInput,
1570
+ {
1571
+ onSubmit: (msg) => {
1572
+ interjectChannel.interject(msg);
1573
+ dispatch({
1574
+ type: "step:interjection",
1575
+ index: state.currentIndex,
1576
+ message: msg
1577
+ });
1578
+ setIsInterjecting(false);
1579
+ },
1580
+ onCancel: () => setIsInterjecting(false)
1581
+ }
1582
+ ),
1583
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
1584
+ updateVersion && /* @__PURE__ */ jsxs5(Text6, { color: theme.warning, children: [
1510
1585
  "v",
1511
1586
  updateVersion,
1512
1587
  " available \u2014 run: executant update"
1513
1588
  ] }),
1514
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "press q to quit" })
1589
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: isInterjecting ? "typing interjection\u2026" : "press q to quit \xB7 i to interject" })
1515
1590
  ] }),
1516
- isRawModeSupported && /* @__PURE__ */ jsx5(KeyboardHandler, { onExit: exit })
1591
+ isRawModeSupported && /* @__PURE__ */ jsx6(
1592
+ KeyboardHandler,
1593
+ {
1594
+ onExit: exit,
1595
+ onInterject: interjectChannel ? () => setIsInterjecting(true) : void 0,
1596
+ isInterjecting
1597
+ }
1598
+ )
1517
1599
  ] });
1518
1600
  }
1519
1601
 
@@ -1921,10 +2003,216 @@ ${issues}`
1921
2003
  };
1922
2004
  }
1923
2005
 
2006
+ // src/refine.ts
2007
+ import { existsSync as existsSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2008
+ import { load as loadYaml, dump as dumpYaml2 } from "js-yaml";
2009
+ var PLAN_REFINE_PROMPT = loadPrompt("plan-refine");
2010
+ var PLAN_SYSTEM_RULES2 = loadPrompt("plan-system-rules");
2011
+ var PLAN_RETRY_PARSE_ERROR2 = loadPrompt("plan-retry-parse-error");
2012
+ var PLAN_RETRY_SCHEMA_ERROR2 = loadPrompt("plan-retry-schema-error");
2013
+ var PLAN_RETRY_JUDGE2 = loadPrompt("plan-retry-judge");
2014
+ var MAX_REFINE_RETRIES = 3;
2015
+ function parseRefineArgs(rawArgs2) {
2016
+ if (rawArgs2[0] === "-h" || rawArgs2[0] === "--help") {
2017
+ console.log(`Usage: executant refine <task-file> [OPTIONS] [INSTRUCTIONS]
2018
+
2019
+ Refine an existing task YAML with natural language instructions.
2020
+
2021
+ Options:
2022
+ -f, --file <path> Read instructions from file
2023
+ -h, --help Show this help message
2024
+
2025
+ Examples:
2026
+ executant refine tasks/todo/my-task.yaml "make it simpler"
2027
+ executant refine tasks/todo/my-task.yaml -f instructions.txt
2028
+ cat instructions.txt | executant refine tasks/todo/my-task.yaml`);
2029
+ process.exit(0);
2030
+ }
2031
+ const taskFile = rawArgs2[0];
2032
+ if (!taskFile) {
2033
+ console.error("Error: No task file specified");
2034
+ console.error("Usage: executant refine <task-file> [INSTRUCTIONS]");
2035
+ process.exit(1);
2036
+ }
2037
+ if (!existsSync2(taskFile)) {
2038
+ console.error(`Error: File not found: ${taskFile}`);
2039
+ process.exit(1);
2040
+ }
2041
+ let existingYaml;
2042
+ try {
2043
+ existingYaml = readFileSync5(taskFile, "utf8").trim();
2044
+ } catch {
2045
+ console.error(`Error: Cannot read file: ${taskFile}`);
2046
+ process.exit(1);
2047
+ }
2048
+ let description = "Refine workflow";
2049
+ try {
2050
+ const parsed = loadYaml(existingYaml);
2051
+ if (typeof parsed?.goal === "string") description = parsed.goal;
2052
+ } catch {
2053
+ }
2054
+ const remaining = rawArgs2.slice(1);
2055
+ let instructions = "";
2056
+ if (remaining[0] === "-f" || remaining[0] === "--file") {
2057
+ const filePath2 = remaining[1];
2058
+ if (!filePath2) {
2059
+ console.error("Error: -f/--file requires a file path argument");
2060
+ process.exit(1);
2061
+ }
2062
+ if (!existsSync2(filePath2)) {
2063
+ console.error(`Error: File not found: ${filePath2}`);
2064
+ process.exit(1);
2065
+ }
2066
+ try {
2067
+ instructions = readFileSync5(filePath2, "utf8").trim();
2068
+ } catch {
2069
+ console.error(`Error: Cannot read file: ${filePath2}`);
2070
+ process.exit(1);
2071
+ }
2072
+ } else if (remaining.length > 0) {
2073
+ instructions = remaining.join(" ").trim();
2074
+ } else if (!process.stdin.isTTY) {
2075
+ try {
2076
+ instructions = readFileSync5("/dev/stdin", "utf8").trim();
2077
+ } catch {
2078
+ }
2079
+ }
2080
+ if (!instructions) {
2081
+ console.error("Error: No refinement instructions provided");
2082
+ console.error("Usage: executant refine <task-file> [INSTRUCTIONS]");
2083
+ console.error(" executant refine <task-file> -f <filepath>");
2084
+ console.error(" cat instructions.txt | executant refine <task-file>");
2085
+ process.exit(1);
2086
+ }
2087
+ return { taskFile, existingYaml, instructions, description };
2088
+ }
2089
+ async function* streamRefine(args) {
2090
+ const { taskFile, existingYaml, instructions, description } = args;
2091
+ yield { type: "plan:start", description };
2092
+ yield { type: "plan:stages", names: ["Refine", "Validate"] };
2093
+ yield { type: "plan:stage", stage: 1, total: 2, name: "Refine" };
2094
+ let retryPrefix = "";
2095
+ for (let attempt = 0; attempt < MAX_REFINE_RETRIES; attempt++) {
2096
+ if (attempt > 0) {
2097
+ yield {
2098
+ type: "plan:retry",
2099
+ attempt: attempt + 1,
2100
+ maxAttempts: MAX_REFINE_RETRIES,
2101
+ reason: retryPrefix.replace(/\n/g, " ")
2102
+ };
2103
+ yield { type: "plan:stage", stage: 1, total: 2, name: "Refine" };
2104
+ }
2105
+ const basePrompt = fillTemplate(PLAN_REFINE_PROMPT, {
2106
+ DESCRIPTION: description,
2107
+ EXISTING_YAML: existingYaml,
2108
+ INSTRUCTIONS: instructions
2109
+ });
2110
+ const refineTask = {
2111
+ type: "claude",
2112
+ name: "plan:refine",
2113
+ prompt: retryPrefix ? `${retryPrefix}
2114
+
2115
+ ${basePrompt}` : basePrompt,
2116
+ allowedTools: [],
2117
+ permissionMode: "bypassPermissions",
2118
+ model: "sonnet",
2119
+ appendSystemPrompt: `${METHODOLOGY}
2120
+
2121
+ ${PLAN_SYSTEM_RULES2}`,
2122
+ jsonSchema: WORKFLOW_JSON_SCHEMA
2123
+ };
2124
+ let structuredOutput;
2125
+ const textLines = [];
2126
+ try {
2127
+ for await (const event of runClaude(refineTask)) {
2128
+ if (event.type === "output:tool") {
2129
+ yield { type: "plan:tool", tool: event.tool, input: event.input };
2130
+ } else if (event.type === "output:text") {
2131
+ textLines.push(event.text);
2132
+ yield { type: "plan:text", text: event.text };
2133
+ } else if (event.type === "output:structured") {
2134
+ structuredOutput = event.data;
2135
+ }
2136
+ }
2137
+ } catch (err) {
2138
+ const msg = getErrorMessage(err);
2139
+ if (attempt === MAX_REFINE_RETRIES - 1) {
2140
+ yield { type: "plan:error", message: msg };
2141
+ return;
2142
+ }
2143
+ retryPrefix = fillTemplate(PLAN_RETRY_PARSE_ERROR2, {
2144
+ ERROR: msg,
2145
+ EXCERPT: textLines.join("\n")
2146
+ });
2147
+ continue;
2148
+ }
2149
+ if (structuredOutput === void 0) {
2150
+ const issues = "No structured output returned \u2014 ensure the response is a JSON object";
2151
+ if (attempt === MAX_REFINE_RETRIES - 1) {
2152
+ yield { type: "plan:error", message: issues };
2153
+ return;
2154
+ }
2155
+ retryPrefix = fillTemplate(PLAN_RETRY_SCHEMA_ERROR2, { ISSUES: issues });
2156
+ continue;
2157
+ }
2158
+ const zodResult = WorkflowSchema.safeParse(structuredOutput);
2159
+ if (!zodResult.success) {
2160
+ const issues = formatZodIssues(zodResult.error.issues);
2161
+ if (attempt === MAX_REFINE_RETRIES - 1) {
2162
+ yield {
2163
+ type: "plan:error",
2164
+ message: `Refined plan did not match expected schema:
2165
+ ${issues}`
2166
+ };
2167
+ return;
2168
+ }
2169
+ retryPrefix = fillTemplate(PLAN_RETRY_SCHEMA_ERROR2, { ISSUES: issues });
2170
+ continue;
2171
+ }
2172
+ yield { type: "plan:stage", stage: 2, total: 2, name: "Validate" };
2173
+ const judgeResult = await runPass3Judge(description, zodResult.data);
2174
+ if (judgeResult.skipped) {
2175
+ yield {
2176
+ type: "plan:warn",
2177
+ message: "Judge skipped due to error \u2014 proceeding without validation"
2178
+ };
2179
+ }
2180
+ if (!judgeResult.pass && attempt < MAX_REFINE_RETRIES - 1) {
2181
+ retryPrefix = fillTemplate(PLAN_RETRY_JUDGE2, {
2182
+ FEEDBACK: judgeResult.feedback
2183
+ });
2184
+ continue;
2185
+ }
2186
+ if (!judgeResult.pass) {
2187
+ yield {
2188
+ type: "plan:warn",
2189
+ message: `Judge rejected refinement but retries exhausted: ${judgeResult.feedback}`
2190
+ };
2191
+ }
2192
+ const { goal, vars, steps, ...rest } = normalizeWorkflow(zodResult.data);
2193
+ const ordered = { goal, ...vars && { vars }, steps, ...rest };
2194
+ const yamlContent = dumpYaml2(ordered, {
2195
+ lineWidth: -1,
2196
+ noRefs: true,
2197
+ quotingType: '"',
2198
+ forceQuotes: false
2199
+ }).trimEnd();
2200
+ writeFileSync3(taskFile, yamlContent + "\n", "utf8");
2201
+ const yamlLines = yamlContent.split("\n");
2202
+ const preview = yamlLines.slice(0, 30).join("\n") + (yamlLines.length > 30 ? "\n..." : "");
2203
+ yield { type: "plan:complete", taskFile, preview };
2204
+ return;
2205
+ }
2206
+ yield {
2207
+ type: "plan:error",
2208
+ message: "Refine failed after maximum retries"
2209
+ };
2210
+ }
2211
+
1924
2212
  // src/ui/PlanApp.tsx
1925
- import { useEffect as useEffect3, useReducer as useReducer2, useState as useState2 } from "react";
1926
- import { Box as Box6, Text as Text6, useApp as useApp2, useStdin as useStdin2 } from "ink";
1927
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2213
+ import { useEffect as useEffect3, useReducer as useReducer2, useState as useState3 } from "react";
2214
+ import { Box as Box7, Text as Text7, useApp as useApp2, useStdin as useStdin2 } from "ink";
2215
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1928
2216
  var truncate = (str, max) => str.length > max ? str.slice(0, max - 3) + "..." : str;
1929
2217
  function buildInitial(description) {
1930
2218
  return {
@@ -2000,7 +2288,7 @@ function StageProgress({ stage, totalStages, stageNames, tick, isActive, status
2000
2288
  }
2001
2289
  return { icon, color, name, bold, dim };
2002
2290
  });
2003
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", marginBottom: 1, children: rows.map(({ icon, color, name, bold, dim }, i) => /* @__PURE__ */ jsx6(Box6, { children: /* @__PURE__ */ jsxs5(Text6, { color, dimColor: dim, bold, children: [
2291
+ return /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", marginBottom: 1, children: rows.map(({ icon, color, name, bold, dim }, i) => /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs6(Text7, { color, dimColor: dim, bold, children: [
2004
2292
  " ",
2005
2293
  icon,
2006
2294
  name ? ` ${name}` : ""
@@ -2025,7 +2313,7 @@ function PlanApp({ description, events: events2 }) {
2025
2313
  };
2026
2314
  }, [events2, exit]);
2027
2315
  const { isRawModeSupported } = useStdin2();
2028
- const [tick, setTick] = useState2(0);
2316
+ const [tick, setTick] = useState3(0);
2029
2317
  const isActive = state.status === "running" || state.status === "retrying";
2030
2318
  useInterval(() => {
2031
2319
  if (isActive) setTick((t) => t + 1);
@@ -2033,19 +2321,19 @@ function PlanApp({ description, events: events2 }) {
2033
2321
  const elapsed = formatHeaderElapsed(state.startTime);
2034
2322
  const icon = isActive ? SPINNER[tick % SPINNER.length] : state.status === "complete" ? "\u2713" : "\u2717";
2035
2323
  const iconColor = state.status === "complete" ? theme.success : state.status === "error" ? theme.error : theme.primary;
2036
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", padding: 1, children: [
2037
- /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(BrandMark, { tick, isActive }) }),
2038
- /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, children: [
2039
- /* @__PURE__ */ jsxs5(Text6, { color: iconColor, children: [
2324
+ return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", padding: 1, children: [
2325
+ /* @__PURE__ */ jsx7(Box7, { marginBottom: 1, children: /* @__PURE__ */ jsx7(BrandMark, { tick, isActive }) }),
2326
+ /* @__PURE__ */ jsxs6(Box7, { marginBottom: 1, children: [
2327
+ /* @__PURE__ */ jsxs6(Text7, { color: iconColor, children: [
2040
2328
  icon,
2041
2329
  " "
2042
2330
  ] }),
2043
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.primary, children: "Generating plan" }),
2044
- /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
2331
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.primary, children: "Generating plan" }),
2332
+ /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
2045
2333
  " ",
2046
2334
  elapsed
2047
2335
  ] }),
2048
- state.status === "retrying" && /* @__PURE__ */ jsxs5(Text6, { color: theme.warning, children: [
2336
+ state.status === "retrying" && /* @__PURE__ */ jsxs6(Text7, { color: theme.warning, children: [
2049
2337
  " ",
2050
2338
  "(attempt ",
2051
2339
  state.attempt,
@@ -2054,11 +2342,11 @@ function PlanApp({ description, events: events2 }) {
2054
2342
  ")"
2055
2343
  ] })
2056
2344
  ] }),
2057
- /* @__PURE__ */ jsxs5(Box6, { marginBottom: 1, children: [
2058
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
2059
- /* @__PURE__ */ jsx6(Text6, { children: truncate(state.description, 80) })
2345
+ /* @__PURE__ */ jsxs6(Box7, { marginBottom: 1, children: [
2346
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " " }),
2347
+ /* @__PURE__ */ jsx7(Text7, { children: truncate(state.description, 80) })
2060
2348
  ] }),
2061
- /* @__PURE__ */ jsx6(
2349
+ /* @__PURE__ */ jsx7(
2062
2350
  StageProgress,
2063
2351
  {
2064
2352
  stage: state.stage,
@@ -2069,40 +2357,40 @@ function PlanApp({ description, events: events2 }) {
2069
2357
  status: state.status
2070
2358
  }
2071
2359
  ),
2072
- state.lines.length > 0 && /* @__PURE__ */ jsx6(LogPane, { lines: state.lines, isActive, maxLines: 10 }),
2073
- state.status === "complete" && state.taskFile && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 1, children: [
2074
- /* @__PURE__ */ jsxs5(Text6, { color: theme.success, children: [
2360
+ state.lines.length > 0 && /* @__PURE__ */ jsx7(LogPane, { lines: state.lines, isActive, maxLines: 10 }),
2361
+ state.status === "complete" && state.taskFile && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
2362
+ /* @__PURE__ */ jsxs6(Text7, { color: theme.success, children: [
2075
2363
  "\u2705 Task plan saved: ",
2076
2364
  state.taskFile
2077
2365
  ] }),
2078
- state.preview && /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", marginTop: 1, children: [
2079
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Preview:" }),
2080
- /* @__PURE__ */ jsx6(Text6, { children: state.preview })
2366
+ state.preview && /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", marginTop: 1, children: [
2367
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Preview:" }),
2368
+ /* @__PURE__ */ jsx7(Text7, { children: state.preview })
2081
2369
  ] })
2082
2370
  ] }),
2083
- state.status === "error" && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text6, { color: theme.error, children: [
2371
+ state.status === "error" && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: theme.error, children: [
2084
2372
  "Error: ",
2085
2373
  state.errorMessage
2086
2374
  ] }) }),
2087
- isActive && /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "press q to quit" }) }),
2088
- isRawModeSupported && /* @__PURE__ */ jsx6(KeyboardHandler, { onExit: exit })
2375
+ isActive && /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "press q to quit" }) }),
2376
+ isRawModeSupported && /* @__PURE__ */ jsx7(KeyboardHandler, { onExit: exit })
2089
2377
  ] });
2090
2378
  }
2091
2379
 
2092
2380
  // src/logger.ts
2093
2381
  import {
2094
2382
  appendFileSync,
2095
- existsSync as existsSync2,
2383
+ existsSync as existsSync3,
2096
2384
  mkdirSync as mkdirSync3,
2097
2385
  readdirSync,
2098
- writeFileSync as writeFileSync3
2386
+ writeFileSync as writeFileSync4
2099
2387
  } from "node:fs";
2100
2388
  import { dirname as dirname3, join as join3, resolve as resolve2 } from "node:path";
2101
2389
  function findExecutantLocalDir(startDir) {
2102
2390
  let dir = resolve2(startDir);
2103
2391
  while (true) {
2104
2392
  const candidate = join3(dir, ".claude", "executant.local");
2105
- if (existsSync2(candidate)) return candidate;
2393
+ if (existsSync3(candidate)) return candidate;
2106
2394
  const parent = dirname3(dir);
2107
2395
  if (parent === dir) return null;
2108
2396
  dir = parent;
@@ -2134,7 +2422,7 @@ function onWorkflowStart(ctx, s) {
2134
2422
  mkdirSync3(ctx.logDir, { recursive: true });
2135
2423
  mkdirSync3(ctx.highlightsDir, { recursive: true });
2136
2424
  const logFile = join3(ctx.logDir, `${ctx.ts}_${ctx.slug}.log`);
2137
- writeFileSync3(
2425
+ writeFileSync4(
2138
2426
  logFile,
2139
2427
  `# Execution Log
2140
2428
  Task: ${ctx.slug}
@@ -2213,7 +2501,7 @@ function complexSequenceHeader(ctx, s) {
2213
2501
  }
2214
2502
  function createComplexSequenceFile(ctx, s) {
2215
2503
  const path = highlightPath(ctx, s.stepIndex, "complex_sequence");
2216
- writeFileSync3(path, complexSequenceHeader(ctx, s));
2504
+ writeFileSync4(path, complexSequenceHeader(ctx, s));
2217
2505
  return path;
2218
2506
  }
2219
2507
  function onTool(ctx, s, tool, input) {
@@ -2231,7 +2519,7 @@ function onTool(ctx, s, tool, input) {
2231
2519
  return { ...s, toolCount, complexSequenceFile };
2232
2520
  }
2233
2521
  function saveJudgeHighlight(ctx, s, verdict, text) {
2234
- writeFileSync3(
2522
+ writeFileSync4(
2235
2523
  highlightPath(ctx, s.stepIndex, `judge_${verdict}`),
2236
2524
  buildHighlightHeader(ctx, s, `Judge Verdict: ${verdict}`, [
2237
2525
  `**Attempt:** ${s.judgeAttempt}`
@@ -2252,7 +2540,7 @@ var LOG_MATCHERS = [
2252
2540
  pattern: /\[self-healing\].*failed.*exit\s+(\d+)/i,
2253
2541
  apply: (ctx, s, _text, match) => {
2254
2542
  const selfHealingFile = highlightPath(ctx, s.stepIndex, "self_healing");
2255
- writeFileSync3(
2543
+ writeFileSync4(
2256
2544
  selfHealingFile,
2257
2545
  buildHighlightHeader(ctx, s, "Self-Healing Activation") + [
2258
2546
  "## \u274C Failure Detected",
@@ -2322,8 +2610,8 @@ ${"\u2501".repeat(51)}
2322
2610
  `
2323
2611
  );
2324
2612
  const indexFile = join3(ctx.highlightsDir, "README.md");
2325
- if (!existsSync2(indexFile)) {
2326
- writeFileSync3(
2613
+ if (!existsSync3(indexFile)) {
2614
+ writeFileSync4(
2327
2615
  indexFile,
2328
2616
  [
2329
2617
  "# Execution Highlights",
@@ -2418,11 +2706,11 @@ async function* withLogger(gen, logger2) {
2418
2706
 
2419
2707
  // src/retrospective.ts
2420
2708
  import {
2421
- existsSync as existsSync3,
2709
+ existsSync as existsSync4,
2422
2710
  mkdirSync as mkdirSync4,
2423
2711
  readdirSync as readdirSync2,
2424
- readFileSync as readFileSync5,
2425
- writeFileSync as writeFileSync4
2712
+ readFileSync as readFileSync6,
2713
+ writeFileSync as writeFileSync5
2426
2714
  } from "node:fs";
2427
2715
  import { basename as basename2, dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
2428
2716
  import { spawnSync } from "node:child_process";
@@ -2449,7 +2737,7 @@ Self-improvement: retrospective failed: ${getErrorMessage(err)}`
2449
2737
  }
2450
2738
  }
2451
2739
  async function doRetrospective(workflowFilePath, workflow2, highlightsDir, runTimestamp) {
2452
- if (!existsSync3(highlightsDir)) {
2740
+ if (!existsSync4(highlightsDir)) {
2453
2741
  console.log("\nSelf-improvement: no highlights directory found, skipping.");
2454
2742
  return;
2455
2743
  }
@@ -2486,12 +2774,12 @@ ${metrics}
2486
2774
  `);
2487
2775
  console.log("Analyzing execution and generating improvements...\n");
2488
2776
  const highlightContents = runHighlights.map((f) => {
2489
- const content = readFileSync5(join4(highlightsDir, f), "utf8");
2777
+ const content = readFileSync6(join4(highlightsDir, f), "utf8");
2490
2778
  return `### ${f}
2491
2779
 
2492
2780
  ${content}`;
2493
2781
  }).join("\n\n---\n\n");
2494
- const originalYaml = readFileSync5(workflowFilePath, "utf8");
2782
+ const originalYaml = readFileSync6(workflowFilePath, "utf8");
2495
2783
  const taskName = basename2(workflowFilePath, ".yaml");
2496
2784
  const prompt = fillTemplate(RETROSPECTIVE_PROMPT, {
2497
2785
  TASK_NAME: taskName,
@@ -2567,8 +2855,8 @@ Response: ${response.trim()}`
2567
2855
  const slug = slugify(taskName, 40);
2568
2856
  const improvedFile = join4(backlogDir, `${ts}-${slug}-improved.yaml`);
2569
2857
  const changelogFile = join4(backlogDir, `${ts}-${slug}-changelog.md`);
2570
- writeFileSync4(improvedFile, improvedYaml + "\n", "utf8");
2571
- writeFileSync4(changelogFile, changelog + "\n", "utf8");
2858
+ writeFileSync5(improvedFile, improvedYaml + "\n", "utf8");
2859
+ writeFileSync5(changelogFile, changelog + "\n", "utf8");
2572
2860
  console.log(`\u2705 Improved task saved: ${improvedFile}`);
2573
2861
  console.log(`\u2705 Changelog saved: ${changelogFile}`);
2574
2862
  console.log(`
@@ -2586,9 +2874,36 @@ function extractJson(text) {
2586
2874
  return text.slice(start, end + 1);
2587
2875
  }
2588
2876
 
2877
+ // src/types.ts
2878
+ var InterjectChannel = class {
2879
+ sender = null;
2880
+ _queue = [];
2881
+ /** Called by runClaude when a Claude step starts to activate direct delivery. */
2882
+ register(sender) {
2883
+ this.sender = sender;
2884
+ for (const msg of this._queue) sender(msg);
2885
+ this._queue = [];
2886
+ }
2887
+ /** Called by runClaude when a Claude step ends. */
2888
+ unregister() {
2889
+ this.sender = null;
2890
+ }
2891
+ /** Called by the TUI. Delivers immediately if a Claude step is running, else queues. */
2892
+ interject(message) {
2893
+ if (this.sender) this.sender(message);
2894
+ else this._queue.push(message);
2895
+ }
2896
+ /** Drains and returns any queued messages (for non-Claude steps to consume). */
2897
+ consumeQueue() {
2898
+ const q = this._queue.slice();
2899
+ this._queue = [];
2900
+ return q;
2901
+ }
2902
+ };
2903
+
2589
2904
  // src/index.ts
2590
2905
  var CURRENT_VERSION = JSON.parse(
2591
- readFileSync6(
2906
+ readFileSync7(
2592
2907
  join5(dirname5(fileURLToPath2(import.meta.url)), "../package.json"),
2593
2908
  "utf-8"
2594
2909
  )
@@ -2609,6 +2924,21 @@ if (rawArgs[0] === "plan") {
2609
2924
  }
2610
2925
  process.exit(0);
2611
2926
  }
2927
+ if (rawArgs[0] === "refine") {
2928
+ const refineArgs = parseRefineArgs(rawArgs.slice(1));
2929
+ const refineEvents = streamRefine(refineArgs);
2930
+ const inkApp = render(
2931
+ React3.createElement(PlanApp, {
2932
+ description: refineArgs.description,
2933
+ events: refineEvents
2934
+ })
2935
+ );
2936
+ try {
2937
+ await inkApp.waitUntilExit();
2938
+ } catch {
2939
+ }
2940
+ process.exit(0);
2941
+ }
2612
2942
  if (rawArgs[0] === "update") {
2613
2943
  const { checkForUpdate: checkForUpdate2, doUpdate: doUpdate2 } = await Promise.resolve().then(() => (init_update(), update_exports));
2614
2944
  const newer = await checkForUpdate2(CURRENT_VERSION);
@@ -2637,6 +2967,7 @@ Options:
2637
2967
 
2638
2968
  Commands:
2639
2969
  plan <description> Generate a task YAML from a natural language description
2970
+ refine <file> <inst> Refine an existing task YAML with natural language instructions
2640
2971
  update Upgrade executant to the latest version
2641
2972
 
2642
2973
  YAML \u2014 top-level fields:
@@ -2740,7 +3071,8 @@ try {
2740
3071
  process.exit(1);
2741
3072
  }
2742
3073
  var options = { stepFilter, fromStep };
2743
- var rawEvents = runWorkflow(workflow, options);
3074
+ var channel = new InterjectChannel();
3075
+ var rawEvents = runWorkflow(workflow, options, channel);
2744
3076
  var logger = createLogger(resolveLogDir(filePath), workflow.goal);
2745
3077
  var events = withLogger(rawEvents, logger);
2746
3078
  var updateCheck = checkForUpdate(CURRENT_VERSION);
@@ -2780,7 +3112,13 @@ if (ciMode) {
2780
3112
  });
2781
3113
  } else {
2782
3114
  const inkApp = render(
2783
- React3.createElement(App, { workflow, events, options, updateCheck })
3115
+ React3.createElement(App, {
3116
+ workflow,
3117
+ events,
3118
+ options,
3119
+ updateCheck,
3120
+ interjectChannel: channel
3121
+ })
2784
3122
  );
2785
3123
  if (workflow.selfImprove) {
2786
3124
  inkApp.waitUntilExit().then(() => maybeRunRetrospective(filePath, workflow, logger)).catch(() => {