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/README.md +14 -0
- package/dist/index.js +492 -154
- package/dist/prompts/plan-refine.txt +268 -0
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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
|
|
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({
|
|
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__ */
|
|
1194
|
-
/* @__PURE__ */
|
|
1246
|
+
return /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
1247
|
+
/* @__PURE__ */ jsxs2(Text2, { color, children: [
|
|
1195
1248
|
icon,
|
|
1196
1249
|
" "
|
|
1197
1250
|
] }),
|
|
1198
|
-
/* @__PURE__ */
|
|
1199
|
-
/* @__PURE__ */
|
|
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
|
|
1219
|
-
import { Fragment, jsx as
|
|
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__ */
|
|
1227
|
-
/* @__PURE__ */
|
|
1228
|
-
/* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */
|
|
1230
|
-
/* @__PURE__ */
|
|
1231
|
-
/* @__PURE__ */
|
|
1232
|
-
/* @__PURE__ */
|
|
1233
|
-
|
|
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__ */
|
|
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__ */
|
|
1265
|
-
hidden > 0 && /* @__PURE__ */
|
|
1266
|
-
visible.map((record) => /* @__PURE__ */
|
|
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
|
|
1272
|
-
import { jsx as
|
|
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__ */
|
|
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__ */
|
|
1279
|
-
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1303
|
-
/* @__PURE__ */
|
|
1304
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1392
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: theme.muted, children: [
|
|
1340
1393
|
text,
|
|
1341
1394
|
suffix
|
|
1342
1395
|
] });
|
|
1343
|
-
return /* @__PURE__ */
|
|
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
|
|
1364
|
-
import { jsx as
|
|
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__ */
|
|
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
|
|
1434
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1382
1435
|
var MAX_VISIBLE_ITERATIONS = 8;
|
|
1383
|
-
function App({
|
|
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] =
|
|
1474
|
+
const [updateVersion, setUpdateVersion] = useState2(null);
|
|
1415
1475
|
useEffect2(() => {
|
|
1416
1476
|
updateCheck2.then(setUpdateVersion);
|
|
1417
1477
|
}, [updateCheck2]);
|
|
1418
|
-
const [tick, setTick] =
|
|
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__ */
|
|
1450
|
-
/* @__PURE__ */
|
|
1451
|
-
/* @__PURE__ */
|
|
1452
|
-
/* @__PURE__ */
|
|
1453
|
-
/* @__PURE__ */
|
|
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__ */
|
|
1464
|
-
hiddenTaskCount > 0 && /* @__PURE__ */
|
|
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__ */
|
|
1473
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
1502
|
-
/* @__PURE__ */
|
|
1503
|
-
state.writtenFiles.map((f) => /* @__PURE__ */
|
|
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__ */
|
|
1509
|
-
|
|
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__ */
|
|
1589
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: isInterjecting ? "typing interjection\u2026" : "press q to quit \xB7 i to interject" })
|
|
1515
1590
|
] }),
|
|
1516
|
-
isRawModeSupported && /* @__PURE__ */
|
|
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
|
|
1926
|
-
import { Box as
|
|
1927
|
-
import { jsx as
|
|
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__ */
|
|
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] =
|
|
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__ */
|
|
2037
|
-
/* @__PURE__ */
|
|
2038
|
-
/* @__PURE__ */
|
|
2039
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2044
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2058
|
-
/* @__PURE__ */
|
|
2059
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2073
|
-
state.status === "complete" && state.taskFile && /* @__PURE__ */
|
|
2074
|
-
/* @__PURE__ */
|
|
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__ */
|
|
2079
|
-
/* @__PURE__ */
|
|
2080
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
2088
|
-
isRawModeSupported && /* @__PURE__ */
|
|
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
|
|
2383
|
+
existsSync as existsSync3,
|
|
2096
2384
|
mkdirSync as mkdirSync3,
|
|
2097
2385
|
readdirSync,
|
|
2098
|
-
writeFileSync as
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
2326
|
-
|
|
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
|
|
2709
|
+
existsSync as existsSync4,
|
|
2422
2710
|
mkdirSync as mkdirSync4,
|
|
2423
2711
|
readdirSync as readdirSync2,
|
|
2424
|
-
readFileSync as
|
|
2425
|
-
writeFileSync as
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2571
|
-
|
|
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
|
-
|
|
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
|
|
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, {
|
|
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(() => {
|