codeharness 0.29.0 → 0.29.2
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
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
validateDockerfile,
|
|
41
41
|
warn,
|
|
42
42
|
writeState
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-3ZSXMCZV.js";
|
|
44
44
|
|
|
45
45
|
// src/index.ts
|
|
46
46
|
import { Command } from "commander";
|
|
@@ -1896,7 +1896,7 @@ var AgentResolveError = class extends Error {
|
|
|
1896
1896
|
}
|
|
1897
1897
|
};
|
|
1898
1898
|
var TEMPLATES_DIR = resolve2(getPackageRoot(), "templates/agents");
|
|
1899
|
-
var DEFAULT_MODEL = "claude-sonnet-4-6
|
|
1899
|
+
var DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
1900
1900
|
var SAFE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
1901
1901
|
function validateName(name) {
|
|
1902
1902
|
if (!name || !SAFE_NAME_RE.test(name)) {
|
|
@@ -3357,7 +3357,12 @@ ${coverageDedup}`;
|
|
|
3357
3357
|
...task.plugins ?? definition.plugins ? { plugins: task.plugins ?? definition.plugins } : {},
|
|
3358
3358
|
...task.max_budget_usd != null ? { timeout: task.max_budget_usd } : {}
|
|
3359
3359
|
};
|
|
3360
|
-
|
|
3360
|
+
const emit = config.onEvent;
|
|
3361
|
+
if (emit) {
|
|
3362
|
+
emit({ type: "dispatch-start", taskName, storyKey, driverName, model });
|
|
3363
|
+
} else {
|
|
3364
|
+
info(`[${taskName}] ${storyKey} \u2014 dispatching via ${driverName} (model: ${model})...`);
|
|
3365
|
+
}
|
|
3361
3366
|
let output = "";
|
|
3362
3367
|
let resultSessionId = "";
|
|
3363
3368
|
let cost = 0;
|
|
@@ -3368,6 +3373,9 @@ ${coverageDedup}`;
|
|
|
3368
3373
|
const startMs = Date.now();
|
|
3369
3374
|
try {
|
|
3370
3375
|
for await (const event of driver.dispatch(dispatchOpts)) {
|
|
3376
|
+
if (emit) {
|
|
3377
|
+
emit({ type: "stream-event", taskName, storyKey, driverName, streamEvent: event });
|
|
3378
|
+
}
|
|
3371
3379
|
if (event.type === "text") {
|
|
3372
3380
|
output += event.text;
|
|
3373
3381
|
}
|
|
@@ -3407,6 +3415,13 @@ ${coverageDedup}`;
|
|
|
3407
3415
|
await workspace.cleanup();
|
|
3408
3416
|
}
|
|
3409
3417
|
}
|
|
3418
|
+
const elapsedMs = Date.now() - startMs;
|
|
3419
|
+
if (emit) {
|
|
3420
|
+
emit({ type: "dispatch-end", taskName, storyKey, driverName, elapsedMs, costUsd: cost });
|
|
3421
|
+
} else {
|
|
3422
|
+
const elapsed = (elapsedMs / 1e3).toFixed(1);
|
|
3423
|
+
info(`[${taskName}] ${storyKey} \u2014 done (${elapsed}s, cost: $${cost.toFixed(4)})`);
|
|
3424
|
+
}
|
|
3410
3425
|
if (errorEvent) {
|
|
3411
3426
|
const categoryToCode = {
|
|
3412
3427
|
RATE_LIMIT: "RATE_LIMIT",
|
|
@@ -3976,6 +3991,11 @@ async function executeWorkflow(config) {
|
|
|
3976
3991
|
} catch (err) {
|
|
3977
3992
|
const engineError = handleDispatchError(err, taskName, PER_RUN_SENTINEL);
|
|
3978
3993
|
errors.push(engineError);
|
|
3994
|
+
if (config.onEvent) {
|
|
3995
|
+
config.onEvent({ type: "dispatch-error", taskName, storyKey: PER_RUN_SENTINEL, error: { code: engineError.code, message: engineError.message } });
|
|
3996
|
+
} else {
|
|
3997
|
+
warn(`[${taskName}] ${PER_RUN_SENTINEL} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
|
|
3998
|
+
}
|
|
3979
3999
|
state = recordErrorInState(state, taskName, PER_RUN_SENTINEL, engineError);
|
|
3980
4000
|
writeWorkflowState(state, projectDir);
|
|
3981
4001
|
if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
|
|
@@ -4008,6 +4028,11 @@ async function executeWorkflow(config) {
|
|
|
4008
4028
|
} catch (err) {
|
|
4009
4029
|
const engineError = handleDispatchError(err, taskName, item.key);
|
|
4010
4030
|
errors.push(engineError);
|
|
4031
|
+
if (config.onEvent) {
|
|
4032
|
+
config.onEvent({ type: "dispatch-error", taskName, storyKey: item.key, error: { code: engineError.code, message: engineError.message } });
|
|
4033
|
+
} else {
|
|
4034
|
+
warn(`[${taskName}] ${item.key} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
|
|
4035
|
+
}
|
|
4011
4036
|
state = recordErrorInState(state, taskName, item.key, engineError);
|
|
4012
4037
|
writeWorkflowState(state, projectDir);
|
|
4013
4038
|
if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
|
|
@@ -4857,6 +4882,1145 @@ var LanePool = class {
|
|
|
4857
4882
|
}
|
|
4858
4883
|
};
|
|
4859
4884
|
|
|
4885
|
+
// src/lib/ink-renderer.tsx
|
|
4886
|
+
import { render as inkRender } from "ink";
|
|
4887
|
+
|
|
4888
|
+
// src/lib/ink-components.tsx
|
|
4889
|
+
import { Text as Text8, Box as Box8 } from "ink";
|
|
4890
|
+
|
|
4891
|
+
// src/lib/ink-activity-components.tsx
|
|
4892
|
+
import { Text, Box } from "ink";
|
|
4893
|
+
import { Spinner } from "@inkjs/ui";
|
|
4894
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4895
|
+
var MESSAGE_STYLE = {
|
|
4896
|
+
ok: { prefix: "[OK]", color: "green" },
|
|
4897
|
+
warn: { prefix: "[WARN]", color: "yellow" },
|
|
4898
|
+
fail: { prefix: "[FAIL]", color: "red" }
|
|
4899
|
+
};
|
|
4900
|
+
function StoryMessageLine({ msg }) {
|
|
4901
|
+
const style = MESSAGE_STYLE[msg.type];
|
|
4902
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
4903
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
4904
|
+
/* @__PURE__ */ jsx(Text, { color: style.color, bold: true, children: style.prefix }),
|
|
4905
|
+
/* @__PURE__ */ jsx(Text, { children: ` Story ${msg.key}: ${msg.message}` })
|
|
4906
|
+
] }),
|
|
4907
|
+
msg.details?.map((d, j) => /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2514 ${d}` }, j))
|
|
4908
|
+
] });
|
|
4909
|
+
}
|
|
4910
|
+
function CompletedTool({ entry }) {
|
|
4911
|
+
const argsSummary = entry.args.length > 60 ? entry.args.slice(0, 60) + "\u2026" : entry.args;
|
|
4912
|
+
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
|
|
4913
|
+
/* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713 " }),
|
|
4914
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[" }),
|
|
4915
|
+
/* @__PURE__ */ jsx(Text, { children: entry.name }),
|
|
4916
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "] " }),
|
|
4917
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: argsSummary }),
|
|
4918
|
+
entry.driver && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` (${entry.driver})` })
|
|
4919
|
+
] });
|
|
4920
|
+
}
|
|
4921
|
+
var VISIBLE_COMPLETED_TOOLS = 5;
|
|
4922
|
+
function CompletedTools({ tools }) {
|
|
4923
|
+
const visible = tools.slice(-VISIBLE_COMPLETED_TOOLS);
|
|
4924
|
+
const hidden = tools.length - visible.length;
|
|
4925
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
4926
|
+
hidden > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: ` \u2026 ${hidden} earlier tools` }),
|
|
4927
|
+
visible.map((entry, i) => /* @__PURE__ */ jsx(CompletedTool, { entry }, i))
|
|
4928
|
+
] });
|
|
4929
|
+
}
|
|
4930
|
+
function ActiveTool({ name, driverName }) {
|
|
4931
|
+
return /* @__PURE__ */ jsxs(Box, { children: [
|
|
4932
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u26A1 " }),
|
|
4933
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "[" }),
|
|
4934
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: name }),
|
|
4935
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "] " }),
|
|
4936
|
+
driverName && /* @__PURE__ */ jsx(Text, { dimColor: true, children: `(${driverName}) ` }),
|
|
4937
|
+
/* @__PURE__ */ jsx(Spinner, { label: "" })
|
|
4938
|
+
] });
|
|
4939
|
+
}
|
|
4940
|
+
function LastThought({ text }) {
|
|
4941
|
+
return /* @__PURE__ */ jsxs(Text, { wrap: "truncate-end", children: [
|
|
4942
|
+
/* @__PURE__ */ jsx(Text, { children: "\u{1F4AD} " }),
|
|
4943
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: text })
|
|
4944
|
+
] });
|
|
4945
|
+
}
|
|
4946
|
+
function RetryNotice({ info: info3 }) {
|
|
4947
|
+
return /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
|
|
4948
|
+
"\u23F3 API retry ",
|
|
4949
|
+
info3.attempt,
|
|
4950
|
+
" (waiting ",
|
|
4951
|
+
info3.delay,
|
|
4952
|
+
"ms)"
|
|
4953
|
+
] });
|
|
4954
|
+
}
|
|
4955
|
+
function DriverCostSummary({ driverCosts }) {
|
|
4956
|
+
if (!driverCosts) return null;
|
|
4957
|
+
const entries = Object.entries(driverCosts).sort(([a], [b]) => a.localeCompare(b));
|
|
4958
|
+
if (entries.length === 0) return null;
|
|
4959
|
+
const parts = entries.map(([driver, cost]) => `${driver} $${cost.toFixed(2)}`).join(", ");
|
|
4960
|
+
return /* @__PURE__ */ jsx(Text, { dimColor: true, children: `Cost: ${parts}` });
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
// src/lib/ink-app.tsx
|
|
4964
|
+
import { Box as Box7, Static, Text as Text7, useInput } from "ink";
|
|
4965
|
+
|
|
4966
|
+
// src/lib/ink-workflow.tsx
|
|
4967
|
+
import { Text as Text2, Box as Box2 } from "ink";
|
|
4968
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
4969
|
+
var termWidth = () => Math.min(process.stdout.columns || 60, 80);
|
|
4970
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4971
|
+
function isLoopBlock2(step) {
|
|
4972
|
+
return typeof step === "object" && step !== null && "loop" in step;
|
|
4973
|
+
}
|
|
4974
|
+
function formatCost(costUsd) {
|
|
4975
|
+
if (costUsd == null) return "...";
|
|
4976
|
+
return `$${costUsd.toFixed(2)}`;
|
|
4977
|
+
}
|
|
4978
|
+
function formatElapsed2(ms) {
|
|
4979
|
+
if (ms == null) return "...";
|
|
4980
|
+
const seconds = Math.round(ms / 1e3);
|
|
4981
|
+
if (seconds >= 60) {
|
|
4982
|
+
return `${Math.floor(seconds / 60)}m`;
|
|
4983
|
+
}
|
|
4984
|
+
return `${seconds}s`;
|
|
4985
|
+
}
|
|
4986
|
+
function TaskNode({ name, status, spinnerFrame }) {
|
|
4987
|
+
const s = status ?? "pending";
|
|
4988
|
+
switch (s) {
|
|
4989
|
+
case "done":
|
|
4990
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: "green", children: [
|
|
4991
|
+
name,
|
|
4992
|
+
" \u2713"
|
|
4993
|
+
] });
|
|
4994
|
+
case "active": {
|
|
4995
|
+
const frame = SPINNER_FRAMES[(spinnerFrame ?? 0) % SPINNER_FRAMES.length];
|
|
4996
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
|
|
4997
|
+
frame,
|
|
4998
|
+
" ",
|
|
4999
|
+
name
|
|
5000
|
+
] });
|
|
5001
|
+
}
|
|
5002
|
+
case "failed":
|
|
5003
|
+
return /* @__PURE__ */ jsxs2(Text2, { color: "red", children: [
|
|
5004
|
+
name,
|
|
5005
|
+
" \u2717"
|
|
5006
|
+
] });
|
|
5007
|
+
case "pending":
|
|
5008
|
+
default:
|
|
5009
|
+
return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: name });
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
function loopIteration(tasks, taskStates) {
|
|
5013
|
+
const anyStarted = tasks.some((t) => {
|
|
5014
|
+
const s = taskStates[t];
|
|
5015
|
+
return s !== void 0 && s !== "pending";
|
|
5016
|
+
});
|
|
5017
|
+
return anyStarted ? 1 : 0;
|
|
5018
|
+
}
|
|
5019
|
+
function collectTaskNames(flow) {
|
|
5020
|
+
const names = [];
|
|
5021
|
+
for (const step of flow) {
|
|
5022
|
+
if (isLoopBlock2(step)) {
|
|
5023
|
+
names.push(...step.loop);
|
|
5024
|
+
} else {
|
|
5025
|
+
names.push(step);
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
return names;
|
|
5029
|
+
}
|
|
5030
|
+
function hasMetaData(taskMeta) {
|
|
5031
|
+
if (!taskMeta) return false;
|
|
5032
|
+
return Object.keys(taskMeta).length > 0;
|
|
5033
|
+
}
|
|
5034
|
+
function WorkflowGraph({ flow, currentTask, taskStates, taskMeta }) {
|
|
5035
|
+
if (flow.length === 0 || Object.keys(taskStates).length === 0) {
|
|
5036
|
+
return null;
|
|
5037
|
+
}
|
|
5038
|
+
const meta = taskMeta ?? {};
|
|
5039
|
+
const showMeta = hasMetaData(taskMeta);
|
|
5040
|
+
const spinnerFrame = Math.floor(Date.now() / 80);
|
|
5041
|
+
const elements = [];
|
|
5042
|
+
for (let i = 0; i < flow.length; i++) {
|
|
5043
|
+
const step = flow[i];
|
|
5044
|
+
if (i > 0) {
|
|
5045
|
+
elements.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `arrow-${i}`));
|
|
5046
|
+
}
|
|
5047
|
+
if (isLoopBlock2(step)) {
|
|
5048
|
+
const iteration = loopIteration(step.loop, taskStates);
|
|
5049
|
+
const loopNodes = [];
|
|
5050
|
+
for (let j = 0; j < step.loop.length; j++) {
|
|
5051
|
+
if (j > 0) {
|
|
5052
|
+
loopNodes.push(/* @__PURE__ */ jsx2(Text2, { children: " \u2192 " }, `loop-arrow-${i}-${j}`));
|
|
5053
|
+
}
|
|
5054
|
+
loopNodes.push(
|
|
5055
|
+
/* @__PURE__ */ jsx2(TaskNode, { name: step.loop[j], status: taskStates[step.loop[j]], spinnerFrame }, `loop-task-${i}-${j}`)
|
|
5056
|
+
);
|
|
5057
|
+
}
|
|
5058
|
+
elements.push(
|
|
5059
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
5060
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
5061
|
+
"loop(",
|
|
5062
|
+
iteration,
|
|
5063
|
+
")[ "
|
|
5064
|
+
] }),
|
|
5065
|
+
loopNodes,
|
|
5066
|
+
/* @__PURE__ */ jsx2(Text2, { children: " ]" })
|
|
5067
|
+
] }, `loop-${i}`)
|
|
5068
|
+
);
|
|
5069
|
+
} else {
|
|
5070
|
+
elements.push(
|
|
5071
|
+
/* @__PURE__ */ jsx2(TaskNode, { name: step, status: taskStates[step], spinnerFrame }, `task-${i}`)
|
|
5072
|
+
);
|
|
5073
|
+
}
|
|
5074
|
+
}
|
|
5075
|
+
let driverRow = null;
|
|
5076
|
+
let costRow = null;
|
|
5077
|
+
if (showMeta) {
|
|
5078
|
+
const taskNames = collectTaskNames(flow);
|
|
5079
|
+
const driverParts = [];
|
|
5080
|
+
const costParts = [];
|
|
5081
|
+
let hasAnyCost = false;
|
|
5082
|
+
for (const name of taskNames) {
|
|
5083
|
+
const m = meta[name];
|
|
5084
|
+
const driver = m?.driver ?? "";
|
|
5085
|
+
driverParts.push(driver);
|
|
5086
|
+
const state = taskStates[name];
|
|
5087
|
+
if (state === "done") {
|
|
5088
|
+
const costStr = formatCost(m?.costUsd);
|
|
5089
|
+
const timeStr = formatElapsed2(m?.elapsedMs);
|
|
5090
|
+
costParts.push(`${costStr} / ${timeStr}`);
|
|
5091
|
+
hasAnyCost = true;
|
|
5092
|
+
} else {
|
|
5093
|
+
costParts.push("");
|
|
5094
|
+
}
|
|
5095
|
+
}
|
|
5096
|
+
const hasSomeDriver = driverParts.some((d) => d.length > 0);
|
|
5097
|
+
if (hasSomeDriver) {
|
|
5098
|
+
const driverLabels = [];
|
|
5099
|
+
for (let idx = 0; idx < taskNames.length; idx++) {
|
|
5100
|
+
if (idx > 0) {
|
|
5101
|
+
driverLabels.push(/* @__PURE__ */ jsx2(Text2, { children: " " }, `drv-sep-${idx}`));
|
|
5102
|
+
}
|
|
5103
|
+
driverLabels.push(
|
|
5104
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: driverParts[idx] || " " }, `drv-${idx}`)
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
driverRow = /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
5108
|
+
" ",
|
|
5109
|
+
driverLabels
|
|
5110
|
+
] });
|
|
5111
|
+
}
|
|
5112
|
+
if (hasAnyCost) {
|
|
5113
|
+
const costLabels = [];
|
|
5114
|
+
for (let idx = 0; idx < taskNames.length; idx++) {
|
|
5115
|
+
if (idx > 0) {
|
|
5116
|
+
costLabels.push(/* @__PURE__ */ jsx2(Text2, { children: " " }, `cost-sep-${idx}`));
|
|
5117
|
+
}
|
|
5118
|
+
costLabels.push(
|
|
5119
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: costParts[idx] || " " }, `cost-${idx}`)
|
|
5120
|
+
);
|
|
5121
|
+
}
|
|
5122
|
+
costRow = /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
5123
|
+
" ",
|
|
5124
|
+
costLabels
|
|
5125
|
+
] });
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
5129
|
+
/* @__PURE__ */ jsx2(Text2, { children: "\u2501".repeat(termWidth()) }),
|
|
5130
|
+
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
5131
|
+
" ",
|
|
5132
|
+
elements
|
|
5133
|
+
] }),
|
|
5134
|
+
driverRow,
|
|
5135
|
+
costRow,
|
|
5136
|
+
/* @__PURE__ */ jsx2(Text2, { children: "\u2501".repeat(termWidth()) })
|
|
5137
|
+
] });
|
|
5138
|
+
}
|
|
5139
|
+
|
|
5140
|
+
// src/lib/ink-lane-container.tsx
|
|
5141
|
+
import { Text as Text4, Box as Box4 } from "ink";
|
|
5142
|
+
|
|
5143
|
+
// src/lib/ink-lane.tsx
|
|
5144
|
+
import { Text as Text3, Box as Box3 } from "ink";
|
|
5145
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
5146
|
+
function formatLaneCost(cost) {
|
|
5147
|
+
if (cost == null) return "--";
|
|
5148
|
+
return `$${cost.toFixed(2)}`;
|
|
5149
|
+
}
|
|
5150
|
+
function formatLaneElapsed(ms) {
|
|
5151
|
+
if (ms == null) return "--";
|
|
5152
|
+
const totalSeconds = Math.round(ms / 1e3);
|
|
5153
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
5154
|
+
if (minutes >= 60) {
|
|
5155
|
+
const hours = Math.floor(minutes / 60);
|
|
5156
|
+
const remainingMinutes = minutes % 60;
|
|
5157
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
5158
|
+
}
|
|
5159
|
+
if (minutes >= 1) {
|
|
5160
|
+
return `${minutes}m`;
|
|
5161
|
+
}
|
|
5162
|
+
return `${totalSeconds}s`;
|
|
5163
|
+
}
|
|
5164
|
+
function StoryProgressBar({ entries }) {
|
|
5165
|
+
if (entries.length === 0) return null;
|
|
5166
|
+
const items = [];
|
|
5167
|
+
for (let i = 0; i < entries.length; i++) {
|
|
5168
|
+
const entry = entries[i];
|
|
5169
|
+
if (i > 0) items.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, `sp-${i}`));
|
|
5170
|
+
switch (entry.status) {
|
|
5171
|
+
case "done":
|
|
5172
|
+
items.push(/* @__PURE__ */ jsx3(Text3, { color: "green", children: `\u2713 ${entry.key}` }, `s-${i}`));
|
|
5173
|
+
break;
|
|
5174
|
+
case "in-progress":
|
|
5175
|
+
items.push(/* @__PURE__ */ jsx3(Text3, { color: "yellow", children: `\u25C6 ${entry.key}` }, `s-${i}`));
|
|
5176
|
+
break;
|
|
5177
|
+
case "pending":
|
|
5178
|
+
items.push(/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `\u25CB ${entry.key}` }, `s-${i}`));
|
|
5179
|
+
break;
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
return /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
5183
|
+
" ",
|
|
5184
|
+
items
|
|
5185
|
+
] });
|
|
5186
|
+
}
|
|
5187
|
+
function Lane(props) {
|
|
5188
|
+
const {
|
|
5189
|
+
epicId,
|
|
5190
|
+
epicTitle,
|
|
5191
|
+
currentStory,
|
|
5192
|
+
phase,
|
|
5193
|
+
acProgress,
|
|
5194
|
+
storyProgressEntries,
|
|
5195
|
+
driver,
|
|
5196
|
+
cost,
|
|
5197
|
+
elapsedTime,
|
|
5198
|
+
laneIndex
|
|
5199
|
+
} = props;
|
|
5200
|
+
const laneLabel = laneIndex != null ? `Lane ${laneIndex}: ` : "";
|
|
5201
|
+
const titleLine = `${laneLabel}Epic ${epicId} \u2014 ${epicTitle}`;
|
|
5202
|
+
const storyParts = [];
|
|
5203
|
+
if (currentStory) storyParts.push(currentStory);
|
|
5204
|
+
if (phase) storyParts.push(`\u25C6 ${phase}`);
|
|
5205
|
+
if (acProgress) storyParts.push(`(AC ${acProgress})`);
|
|
5206
|
+
const storyLine = storyParts.length > 0 ? ` ${storyParts.join(" ")}` : null;
|
|
5207
|
+
const driverLine = ` ${driver ?? "unknown"} | ${formatLaneCost(cost)} / ${formatLaneElapsed(elapsedTime)}`;
|
|
5208
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
5209
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: titleLine }),
|
|
5210
|
+
storyLine && /* @__PURE__ */ jsx3(Text3, { children: storyLine }),
|
|
5211
|
+
/* @__PURE__ */ jsx3(StoryProgressBar, { entries: storyProgressEntries }),
|
|
5212
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: driverLine })
|
|
5213
|
+
] });
|
|
5214
|
+
}
|
|
5215
|
+
|
|
5216
|
+
// src/lib/ink-lane-container.tsx
|
|
5217
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
5218
|
+
function getLayoutMode(terminalWidth) {
|
|
5219
|
+
if (terminalWidth >= 120) return "side-by-side";
|
|
5220
|
+
if (terminalWidth >= 80) return "stacked";
|
|
5221
|
+
return "single";
|
|
5222
|
+
}
|
|
5223
|
+
function truncate(str, maxLen) {
|
|
5224
|
+
if (maxLen < 4) return str.slice(0, maxLen);
|
|
5225
|
+
if (str.length <= maxLen) return str;
|
|
5226
|
+
return str.slice(0, maxLen - 1) + "\u2026";
|
|
5227
|
+
}
|
|
5228
|
+
function CollapsedLanes({ lanes }) {
|
|
5229
|
+
if (lanes.length === 0) return null;
|
|
5230
|
+
return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: lanes.map((lane) => {
|
|
5231
|
+
const storyPart = lane.currentStory ?? "--";
|
|
5232
|
+
const phasePart = lane.phase ?? "--";
|
|
5233
|
+
const costPart = formatLaneCost(lane.cost);
|
|
5234
|
+
const timePart = formatLaneElapsed(lane.elapsedTime);
|
|
5235
|
+
const line = `Lane ${lane.laneIndex}: ${lane.epicTitle} \u2502 ${storyPart} \u25C6 ${phasePart} \u2502 ${costPart} / ${timePart}`;
|
|
5236
|
+
return /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: line }, `collapsed-${lane.laneIndex}`);
|
|
5237
|
+
}) });
|
|
5238
|
+
}
|
|
5239
|
+
function LaneContainer({ lanes, terminalWidth }) {
|
|
5240
|
+
if (lanes.length === 0) return null;
|
|
5241
|
+
const mode = getLayoutMode(terminalWidth);
|
|
5242
|
+
let fullLanes;
|
|
5243
|
+
let collapsedLaneData;
|
|
5244
|
+
if (mode === "single") {
|
|
5245
|
+
let mostRecentIndex = 0;
|
|
5246
|
+
let mostRecentTime = -Infinity;
|
|
5247
|
+
for (let i = 0; i < lanes.length; i++) {
|
|
5248
|
+
const t = lanes[i].lastActivityTime ?? 0;
|
|
5249
|
+
if (t >= mostRecentTime) {
|
|
5250
|
+
mostRecentTime = t;
|
|
5251
|
+
mostRecentIndex = i;
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
fullLanes = [lanes[mostRecentIndex]];
|
|
5255
|
+
collapsedLaneData = lanes.filter((_, i) => i !== mostRecentIndex).map((lane, i) => {
|
|
5256
|
+
const originalIndex = i >= mostRecentIndex ? i + 2 : i + 1;
|
|
5257
|
+
return {
|
|
5258
|
+
laneIndex: originalIndex,
|
|
5259
|
+
epicTitle: truncate(lane.epicTitle, 30),
|
|
5260
|
+
currentStory: lane.currentStory,
|
|
5261
|
+
phase: lane.phase,
|
|
5262
|
+
cost: lane.cost,
|
|
5263
|
+
elapsedTime: lane.elapsedTime
|
|
5264
|
+
};
|
|
5265
|
+
});
|
|
5266
|
+
} else {
|
|
5267
|
+
fullLanes = lanes.slice(0, 2);
|
|
5268
|
+
collapsedLaneData = lanes.slice(2).map((lane, i) => ({
|
|
5269
|
+
laneIndex: i + 3,
|
|
5270
|
+
epicTitle: truncate(lane.epicTitle, 30),
|
|
5271
|
+
currentStory: lane.currentStory,
|
|
5272
|
+
phase: lane.phase,
|
|
5273
|
+
cost: lane.cost,
|
|
5274
|
+
elapsedTime: lane.elapsedTime
|
|
5275
|
+
}));
|
|
5276
|
+
}
|
|
5277
|
+
const laneWidth = mode === "side-by-side" ? Math.floor(terminalWidth / 2) - 1 : terminalWidth;
|
|
5278
|
+
const fullLaneElements = [];
|
|
5279
|
+
for (let i = 0; i < fullLanes.length; i++) {
|
|
5280
|
+
const lane = fullLanes[i];
|
|
5281
|
+
const laneIndex = mode === "single" ? void 0 : i + 1;
|
|
5282
|
+
if (i > 0 && mode !== "side-by-side") {
|
|
5283
|
+
fullLaneElements.push(/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(Math.min(terminalWidth, 60)) }, `sep-${i}`));
|
|
5284
|
+
}
|
|
5285
|
+
fullLaneElements.push(
|
|
5286
|
+
/* @__PURE__ */ jsx4(Box4, { width: mode === "side-by-side" ? laneWidth : void 0, flexDirection: "column", children: /* @__PURE__ */ jsx4(
|
|
5287
|
+
Lane,
|
|
5288
|
+
{
|
|
5289
|
+
epicId: lane.epicId,
|
|
5290
|
+
epicTitle: lane.epicTitle,
|
|
5291
|
+
currentStory: lane.currentStory,
|
|
5292
|
+
phase: lane.phase,
|
|
5293
|
+
acProgress: lane.acProgress,
|
|
5294
|
+
storyProgressEntries: lane.storyProgressEntries,
|
|
5295
|
+
driver: lane.driver,
|
|
5296
|
+
cost: lane.cost,
|
|
5297
|
+
elapsedTime: lane.elapsedTime,
|
|
5298
|
+
laneIndex
|
|
5299
|
+
}
|
|
5300
|
+
) }, `lane-${lane.epicId}`)
|
|
5301
|
+
);
|
|
5302
|
+
}
|
|
5303
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
5304
|
+
mode === "side-by-side" ? /* @__PURE__ */ jsx4(Box4, { flexDirection: "row", children: fullLaneElements }) : /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: fullLaneElements }),
|
|
5305
|
+
/* @__PURE__ */ jsx4(CollapsedLanes, { lanes: collapsedLaneData })
|
|
5306
|
+
] });
|
|
5307
|
+
}
|
|
5308
|
+
|
|
5309
|
+
// src/lib/ink-summary-bar.tsx
|
|
5310
|
+
import { Text as Text5, Box as Box5 } from "ink";
|
|
5311
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
5312
|
+
function formatConflictText(count) {
|
|
5313
|
+
return count === 1 ? "1 conflict" : `${count} conflicts`;
|
|
5314
|
+
}
|
|
5315
|
+
function formatCost2(cost) {
|
|
5316
|
+
return `$${cost.toFixed(2)}`;
|
|
5317
|
+
}
|
|
5318
|
+
function SummaryBar({ doneStories, mergingEpic, pendingEpics, completedLanes }) {
|
|
5319
|
+
const doneSection = doneStories.length > 0 ? doneStories.map((s) => `${s} \u2713`).join(" ") : "\u2014";
|
|
5320
|
+
let mergingNode;
|
|
5321
|
+
if (!mergingEpic) {
|
|
5322
|
+
mergingNode = /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2014" });
|
|
5323
|
+
} else if (mergingEpic.status === "resolving") {
|
|
5324
|
+
const conflictText = mergingEpic.conflictCount != null ? ` (resolving ${formatConflictText(mergingEpic.conflictCount)})` : "";
|
|
5325
|
+
mergingNode = /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: `${mergingEpic.epicId} \u2192 main${conflictText} \u25CC` });
|
|
5326
|
+
} else if (mergingEpic.status === "in-progress") {
|
|
5327
|
+
mergingNode = /* @__PURE__ */ jsx5(Text5, { children: `${mergingEpic.epicId} \u2192 main \u25CC` });
|
|
5328
|
+
} else {
|
|
5329
|
+
mergingNode = /* @__PURE__ */ jsx5(Text5, { color: "green", children: `${mergingEpic.epicId} \u2192 main \u2713` });
|
|
5330
|
+
}
|
|
5331
|
+
const pendingSection = pendingEpics.length > 0 ? pendingEpics.join(", ") : "\u2014";
|
|
5332
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
5333
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
5334
|
+
/* @__PURE__ */ jsx5(Text5, { color: "green", children: `Done: ${doneSection}` }),
|
|
5335
|
+
/* @__PURE__ */ jsx5(Text5, { children: " \u2502 " }),
|
|
5336
|
+
/* @__PURE__ */ jsx5(Text5, { children: "Merging: " }),
|
|
5337
|
+
mergingNode,
|
|
5338
|
+
/* @__PURE__ */ jsx5(Text5, { children: " \u2502 " }),
|
|
5339
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `Pending: ${pendingSection}` })
|
|
5340
|
+
] }),
|
|
5341
|
+
completedLanes && completedLanes.length > 0 && completedLanes.map((lane) => /* @__PURE__ */ jsx5(Text5, { color: "green", children: `[OK] Lane ${lane.laneIndex}: Epic ${lane.epicId} complete (${lane.storyCount} stories, ${formatCost2(lane.cost)}, ${lane.elapsed})` }, `lane-complete-${lane.laneIndex}`))
|
|
5342
|
+
] });
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
// src/lib/ink-merge-status.tsx
|
|
5346
|
+
import { Text as Text6, Box as Box6 } from "ink";
|
|
5347
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
5348
|
+
function MergeStatus({ mergeState }) {
|
|
5349
|
+
if (!mergeState) return null;
|
|
5350
|
+
const lines = [];
|
|
5351
|
+
if (mergeState.outcome === "clean") {
|
|
5352
|
+
const count = mergeState.conflictCount ?? 0;
|
|
5353
|
+
lines.push(
|
|
5354
|
+
/* @__PURE__ */ jsx6(Text6, { color: "green", children: `[OK] Merge ${mergeState.epicId} \u2192 main: clean (${count} conflicts)` }, "merge-clean")
|
|
5355
|
+
);
|
|
5356
|
+
} else if (mergeState.outcome === "resolved") {
|
|
5357
|
+
const count = mergeState.conflictCount ?? mergeState.conflicts?.length ?? 0;
|
|
5358
|
+
const suffix = count === 1 ? "" : "s";
|
|
5359
|
+
lines.push(
|
|
5360
|
+
/* @__PURE__ */ jsx6(Text6, { color: "green", children: `[OK] Merge ${mergeState.epicId} \u2192 main: ${count} conflict${suffix} auto-resolved` }, "merge-resolved")
|
|
5361
|
+
);
|
|
5362
|
+
if (mergeState.conflicts && mergeState.conflicts.length > 0) {
|
|
5363
|
+
for (let i = 0; i < mergeState.conflicts.length; i++) {
|
|
5364
|
+
lines.push(
|
|
5365
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
5366
|
+
" \u2514 ",
|
|
5367
|
+
mergeState.conflicts[i]
|
|
5368
|
+
] }, `conflict-${i}`)
|
|
5369
|
+
);
|
|
5370
|
+
}
|
|
5371
|
+
}
|
|
5372
|
+
} else if (mergeState.outcome === "escalated") {
|
|
5373
|
+
lines.push(
|
|
5374
|
+
/* @__PURE__ */ jsx6(Text6, { color: "red", children: `[FAIL] Merge ${mergeState.epicId} \u2192 main: ${mergeState.reason ?? "unknown error"}` }, "merge-escalated")
|
|
5375
|
+
);
|
|
5376
|
+
if (mergeState.conflicts && mergeState.conflicts.length > 0) {
|
|
5377
|
+
for (let i = 0; i < mergeState.conflicts.length; i++) {
|
|
5378
|
+
lines.push(
|
|
5379
|
+
/* @__PURE__ */ jsxs6(Text6, { children: [
|
|
5380
|
+
" \u2514 ",
|
|
5381
|
+
mergeState.conflicts[i]
|
|
5382
|
+
] }, `esc-conflict-${i}`)
|
|
5383
|
+
);
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
lines.push(
|
|
5387
|
+
/* @__PURE__ */ jsx6(Text6, { color: "red", children: " \u2192 Manual resolution required" }, "manual")
|
|
5388
|
+
);
|
|
5389
|
+
if (mergeState.worktreePath) {
|
|
5390
|
+
lines.push(
|
|
5391
|
+
/* @__PURE__ */ jsx6(Text6, { color: "red", children: ` \u2192 Worktree preserved: ${mergeState.worktreePath}` }, "worktree")
|
|
5392
|
+
);
|
|
5393
|
+
}
|
|
5394
|
+
} else if (mergeState.outcome === "in-progress") {
|
|
5395
|
+
lines.push(
|
|
5396
|
+
/* @__PURE__ */ jsx6(Text6, { children: `Merging ${mergeState.epicId} \u2192 main \u25CC` }, "merge-inprog")
|
|
5397
|
+
);
|
|
5398
|
+
}
|
|
5399
|
+
if (mergeState.testResults) {
|
|
5400
|
+
const t = mergeState.testResults;
|
|
5401
|
+
const hasFailed = t.failed > 0;
|
|
5402
|
+
const prefix = hasFailed ? "[FAIL]" : "[OK]";
|
|
5403
|
+
const color = hasFailed ? "red" : "green";
|
|
5404
|
+
let testLine = `${prefix} Tests: ${t.passed}/${t.total} passed (${t.durationSecs}s)`;
|
|
5405
|
+
if (t.coverage != null) {
|
|
5406
|
+
testLine += ` ${t.coverage}% coverage`;
|
|
5407
|
+
}
|
|
5408
|
+
lines.push(
|
|
5409
|
+
/* @__PURE__ */ jsx6(Text6, { color, children: testLine }, "tests")
|
|
5410
|
+
);
|
|
5411
|
+
}
|
|
5412
|
+
return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: lines });
|
|
5413
|
+
}
|
|
5414
|
+
|
|
5415
|
+
// src/lib/ink-app.tsx
|
|
5416
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
5417
|
+
function LaneActivityHeader({ activeLaneId, laneCount }) {
|
|
5418
|
+
if (laneCount <= 1 || !activeLaneId) return null;
|
|
5419
|
+
return /* @__PURE__ */ jsx7(Text7, { children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: `[Lane ${activeLaneId} \u25B8]` }) });
|
|
5420
|
+
}
|
|
5421
|
+
function App({ state, onCycleLane }) {
|
|
5422
|
+
const lanes = state.lanes;
|
|
5423
|
+
const laneCount = lanes?.length ?? 0;
|
|
5424
|
+
const terminalWidth = process.stdout.columns || 80;
|
|
5425
|
+
useInput((_input, key) => {
|
|
5426
|
+
if (key.ctrl && _input === "l" && onCycleLane && laneCount > 1) {
|
|
5427
|
+
onCycleLane();
|
|
5428
|
+
}
|
|
5429
|
+
}, { isActive: typeof process.stdin.setRawMode === "function" });
|
|
5430
|
+
const activeLaneCount = state.laneCount ?? 0;
|
|
5431
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
5432
|
+
/* @__PURE__ */ jsx7(Static, { items: state.messages, children: (msg, i) => /* @__PURE__ */ jsx7(StoryMessageLine, { msg }, i) }),
|
|
5433
|
+
/* @__PURE__ */ jsx7(Header, { info: state.sprintInfo, laneCount: laneCount > 1 ? laneCount : void 0 }),
|
|
5434
|
+
laneCount > 1 ? /* @__PURE__ */ jsx7(LaneContainer, { lanes, terminalWidth }) : /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
5435
|
+
/* @__PURE__ */ jsx7(WorkflowGraph, { flow: state.workflowFlow, currentTask: state.currentTaskName, taskStates: state.taskStates, taskMeta: state.taskMeta }),
|
|
5436
|
+
/* @__PURE__ */ jsx7(StoryBreakdown, { stories: state.stories, sprintInfo: state.sprintInfo }),
|
|
5437
|
+
/* @__PURE__ */ jsx7(DriverCostSummary, { driverCosts: state.driverCosts })
|
|
5438
|
+
] }),
|
|
5439
|
+
laneCount > 1 && state.summaryBar && /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
5440
|
+
/* @__PURE__ */ jsx7(Separator, {}),
|
|
5441
|
+
/* @__PURE__ */ jsx7(SummaryBar, { ...state.summaryBar })
|
|
5442
|
+
] }),
|
|
5443
|
+
laneCount > 1 && state.mergeState && /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
5444
|
+
/* @__PURE__ */ jsx7(Separator, {}),
|
|
5445
|
+
/* @__PURE__ */ jsx7(MergeStatus, { mergeState: state.mergeState })
|
|
5446
|
+
] }),
|
|
5447
|
+
(state.stories.length > 0 || laneCount > 1 && (state.summaryBar || state.mergeState)) && /* @__PURE__ */ jsx7(Separator, {}),
|
|
5448
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingLeft: 1, children: [
|
|
5449
|
+
/* @__PURE__ */ jsx7(LaneActivityHeader, { activeLaneId: state.activeLaneId ?? null, laneCount: activeLaneCount }),
|
|
5450
|
+
/* @__PURE__ */ jsx7(CompletedTools, { tools: state.completedTools }),
|
|
5451
|
+
state.activeTool && /* @__PURE__ */ jsx7(ActiveTool, { name: state.activeTool.name, driverName: state.activeDriverName }),
|
|
5452
|
+
state.lastThought && /* @__PURE__ */ jsx7(LastThought, { text: state.lastThought }),
|
|
5453
|
+
state.retryInfo && /* @__PURE__ */ jsx7(RetryNotice, { info: state.retryInfo })
|
|
5454
|
+
] })
|
|
5455
|
+
] });
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
// src/lib/ink-components.tsx
|
|
5459
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
5460
|
+
function Separator() {
|
|
5461
|
+
const width = process.stdout.columns || 60;
|
|
5462
|
+
return /* @__PURE__ */ jsx8(Text8, { children: "\u2501".repeat(width) });
|
|
5463
|
+
}
|
|
5464
|
+
function shortKey(key) {
|
|
5465
|
+
const m = key.match(/^(\d+-\d+)/);
|
|
5466
|
+
return m ? m[1] : key;
|
|
5467
|
+
}
|
|
5468
|
+
function formatCost3(cost) {
|
|
5469
|
+
return `$${cost.toFixed(2)}`;
|
|
5470
|
+
}
|
|
5471
|
+
function Header({ info: info3, laneCount }) {
|
|
5472
|
+
if (!info3) return null;
|
|
5473
|
+
const parts = ["codeharness run"];
|
|
5474
|
+
if (laneCount != null && laneCount > 1) {
|
|
5475
|
+
parts.push(`${laneCount} lanes`);
|
|
5476
|
+
}
|
|
5477
|
+
if (info3.iterationCount != null) {
|
|
5478
|
+
parts.push(`iteration ${info3.iterationCount}`);
|
|
5479
|
+
}
|
|
5480
|
+
if (info3.elapsed) {
|
|
5481
|
+
parts.push(`${info3.elapsed} elapsed`);
|
|
5482
|
+
}
|
|
5483
|
+
const displayCost = laneCount != null && laneCount > 1 && info3.laneTotalCost != null ? info3.laneTotalCost : info3.totalCost;
|
|
5484
|
+
if (displayCost != null) {
|
|
5485
|
+
parts.push(`${formatCost3(displayCost)} spent`);
|
|
5486
|
+
}
|
|
5487
|
+
const headerLine = parts.join(" | ");
|
|
5488
|
+
let phaseLine = "";
|
|
5489
|
+
if (info3.phase) {
|
|
5490
|
+
phaseLine = `Phase: ${info3.phase}`;
|
|
5491
|
+
if (info3.acProgress) {
|
|
5492
|
+
phaseLine += ` \u2192 AC ${info3.acProgress}`;
|
|
5493
|
+
}
|
|
5494
|
+
if (info3.currentCommand) {
|
|
5495
|
+
phaseLine += ` (${info3.currentCommand})`;
|
|
5496
|
+
}
|
|
5497
|
+
}
|
|
5498
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
5499
|
+
/* @__PURE__ */ jsx8(Text8, { children: headerLine }),
|
|
5500
|
+
/* @__PURE__ */ jsx8(Separator, {}),
|
|
5501
|
+
/* @__PURE__ */ jsx8(Text8, { children: `Story: ${info3.storyKey || "(waiting)"}` }),
|
|
5502
|
+
phaseLine && /* @__PURE__ */ jsx8(Text8, { children: phaseLine })
|
|
5503
|
+
] });
|
|
5504
|
+
}
|
|
5505
|
+
function StoryBreakdown({ stories, sprintInfo }) {
|
|
5506
|
+
if (stories.length === 0) return null;
|
|
5507
|
+
const done = [];
|
|
5508
|
+
const inProgress = [];
|
|
5509
|
+
const pending = [];
|
|
5510
|
+
const failed = [];
|
|
5511
|
+
const blocked = [];
|
|
5512
|
+
for (const s of stories) {
|
|
5513
|
+
switch (s.status) {
|
|
5514
|
+
case "done":
|
|
5515
|
+
done.push(s);
|
|
5516
|
+
break;
|
|
5517
|
+
case "in-progress":
|
|
5518
|
+
inProgress.push(s);
|
|
5519
|
+
break;
|
|
5520
|
+
case "pending":
|
|
5521
|
+
pending.push(s);
|
|
5522
|
+
break;
|
|
5523
|
+
case "failed":
|
|
5524
|
+
failed.push(s);
|
|
5525
|
+
break;
|
|
5526
|
+
case "blocked":
|
|
5527
|
+
blocked.push(s);
|
|
5528
|
+
break;
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
const lines = [];
|
|
5532
|
+
if (done.length > 0) {
|
|
5533
|
+
const doneItems = done.map((s) => {
|
|
5534
|
+
let item = `${shortKey(s.key)} \u2713`;
|
|
5535
|
+
if (s.costByDriver && Object.keys(s.costByDriver).length > 0) {
|
|
5536
|
+
const costParts = Object.keys(s.costByDriver).sort().map(
|
|
5537
|
+
(driver) => `${driver} ${formatCost3(s.costByDriver[driver])}`
|
|
5538
|
+
);
|
|
5539
|
+
item += ` ${costParts.join(", ")}`;
|
|
5540
|
+
}
|
|
5541
|
+
return item;
|
|
5542
|
+
}).join(" ");
|
|
5543
|
+
lines.push(
|
|
5544
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5545
|
+
/* @__PURE__ */ jsx8(Text8, { color: "green", children: "Done: " }),
|
|
5546
|
+
/* @__PURE__ */ jsx8(Text8, { color: "green", children: doneItems })
|
|
5547
|
+
] }, "done")
|
|
5548
|
+
);
|
|
5549
|
+
}
|
|
5550
|
+
if (inProgress.length > 0) {
|
|
5551
|
+
for (const s of inProgress) {
|
|
5552
|
+
let thisText = `${shortKey(s.key)} \u25C6`;
|
|
5553
|
+
if (sprintInfo && sprintInfo.storyKey && shortKey(s.key) === shortKey(sprintInfo.storyKey)) {
|
|
5554
|
+
if (sprintInfo.phase) thisText += ` ${sprintInfo.phase}`;
|
|
5555
|
+
if (sprintInfo.acProgress) thisText += ` (${sprintInfo.acProgress} ACs)`;
|
|
5556
|
+
}
|
|
5557
|
+
lines.push(
|
|
5558
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5559
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", children: "This: " }),
|
|
5560
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", children: thisText })
|
|
5561
|
+
] }, `this-${s.key}`)
|
|
5562
|
+
);
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
if (pending.length > 0) {
|
|
5566
|
+
lines.push(
|
|
5567
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5568
|
+
/* @__PURE__ */ jsx8(Text8, { children: "Next: " }),
|
|
5569
|
+
/* @__PURE__ */ jsx8(Text8, { children: shortKey(pending[0].key) }),
|
|
5570
|
+
pending.length > 1 && /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` (+${pending.length - 1} more)` })
|
|
5571
|
+
] }, "next")
|
|
5572
|
+
);
|
|
5573
|
+
}
|
|
5574
|
+
if (blocked.length > 0) {
|
|
5575
|
+
const blockedItems = blocked.map((s) => {
|
|
5576
|
+
let item = `${shortKey(s.key)} \u2715`;
|
|
5577
|
+
if (s.retryCount != null && s.maxRetries != null) item += ` (${s.retryCount}/${s.maxRetries})`;
|
|
5578
|
+
return item;
|
|
5579
|
+
}).join(" ");
|
|
5580
|
+
lines.push(
|
|
5581
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5582
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "Blocked: " }),
|
|
5583
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", children: blockedItems })
|
|
5584
|
+
] }, "blocked")
|
|
5585
|
+
);
|
|
5586
|
+
}
|
|
5587
|
+
if (failed.length > 0) {
|
|
5588
|
+
const failedItems = failed.map((s) => {
|
|
5589
|
+
let item = `${shortKey(s.key)} \u2717`;
|
|
5590
|
+
if (s.retryCount != null && s.maxRetries != null) item += ` (${s.retryCount}/${s.maxRetries})`;
|
|
5591
|
+
return item;
|
|
5592
|
+
}).join(" ");
|
|
5593
|
+
lines.push(
|
|
5594
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
5595
|
+
/* @__PURE__ */ jsx8(Text8, { color: "red", children: "Failed: " }),
|
|
5596
|
+
/* @__PURE__ */ jsx8(Text8, { color: "red", children: failedItems })
|
|
5597
|
+
] }, "failed")
|
|
5598
|
+
);
|
|
5599
|
+
}
|
|
5600
|
+
return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: lines });
|
|
5601
|
+
}
|
|
5602
|
+
|
|
5603
|
+
// src/lib/ink-renderer.tsx
|
|
5604
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
5605
|
+
var noopHandle = {
|
|
5606
|
+
update(_event, _driverName, _laneId) {
|
|
5607
|
+
},
|
|
5608
|
+
updateSprintState() {
|
|
5609
|
+
},
|
|
5610
|
+
updateStories() {
|
|
5611
|
+
},
|
|
5612
|
+
addMessage() {
|
|
5613
|
+
},
|
|
5614
|
+
updateWorkflowState() {
|
|
5615
|
+
},
|
|
5616
|
+
processLaneEvent() {
|
|
5617
|
+
},
|
|
5618
|
+
updateMergeState() {
|
|
5619
|
+
},
|
|
5620
|
+
cleanup() {
|
|
5621
|
+
}
|
|
5622
|
+
};
|
|
5623
|
+
var MAX_COMPLETED_TOOLS = 50;
|
|
5624
|
+
function startRenderer(options) {
|
|
5625
|
+
if (options?.quiet || !process.stdout.isTTY && !options?._forceTTY) {
|
|
5626
|
+
return noopHandle;
|
|
5627
|
+
}
|
|
5628
|
+
let state = {
|
|
5629
|
+
sprintInfo: options?.sprintState ?? null,
|
|
5630
|
+
stories: [],
|
|
5631
|
+
messages: [],
|
|
5632
|
+
completedTools: [],
|
|
5633
|
+
activeTool: null,
|
|
5634
|
+
activeToolArgs: "",
|
|
5635
|
+
lastThought: null,
|
|
5636
|
+
retryInfo: null,
|
|
5637
|
+
workflowFlow: [],
|
|
5638
|
+
currentTaskName: null,
|
|
5639
|
+
taskStates: {},
|
|
5640
|
+
taskMeta: {},
|
|
5641
|
+
activeDriverName: null,
|
|
5642
|
+
driverCosts: {},
|
|
5643
|
+
activeLaneId: null,
|
|
5644
|
+
laneCount: 0
|
|
5645
|
+
};
|
|
5646
|
+
const laneStates = /* @__PURE__ */ new Map();
|
|
5647
|
+
let pinnedLane = false;
|
|
5648
|
+
let currentStoryCosts = {};
|
|
5649
|
+
let lastStoryKey = state.sprintInfo?.storyKey ?? null;
|
|
5650
|
+
const pendingStoryCosts = /* @__PURE__ */ new Map();
|
|
5651
|
+
let cleaned = false;
|
|
5652
|
+
const inkInstance = inkRender(/* @__PURE__ */ jsx9(App, { state, onCycleLane: () => cycleLane() }), {
|
|
5653
|
+
exitOnCtrlC: false,
|
|
5654
|
+
patchConsole: !options?._forceTTY,
|
|
5655
|
+
maxFps: 15
|
|
5656
|
+
});
|
|
5657
|
+
function rerender() {
|
|
5658
|
+
if (!cleaned) {
|
|
5659
|
+
state = { ...state };
|
|
5660
|
+
inkInstance.rerender(/* @__PURE__ */ jsx9(App, { state, onCycleLane: () => cycleLane() }));
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
function onSigint() {
|
|
5664
|
+
cleanupFull();
|
|
5665
|
+
process.kill(process.pid, "SIGINT");
|
|
5666
|
+
}
|
|
5667
|
+
function onSigterm() {
|
|
5668
|
+
cleanupFull();
|
|
5669
|
+
process.kill(process.pid, "SIGTERM");
|
|
5670
|
+
}
|
|
5671
|
+
process.on("SIGINT", onSigint);
|
|
5672
|
+
process.on("SIGTERM", onSigterm);
|
|
5673
|
+
function promoteActiveTool(clearActive, targetState) {
|
|
5674
|
+
const s = targetState ?? state;
|
|
5675
|
+
if (!s.activeTool) return;
|
|
5676
|
+
const entry = {
|
|
5677
|
+
name: s.activeTool.name,
|
|
5678
|
+
args: s.activeToolArgs,
|
|
5679
|
+
driver: s.activeDriverName ?? void 0
|
|
5680
|
+
};
|
|
5681
|
+
const updated = [...s.completedTools, entry];
|
|
5682
|
+
s.completedTools = updated.length > MAX_COMPLETED_TOOLS ? updated.slice(updated.length - MAX_COMPLETED_TOOLS) : updated;
|
|
5683
|
+
if (clearActive) {
|
|
5684
|
+
s.activeTool = null;
|
|
5685
|
+
s.activeToolArgs = "";
|
|
5686
|
+
s.activeDriverName = null;
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
function copyLaneToDisplay(laneId) {
|
|
5690
|
+
const ls = laneStates.get(laneId);
|
|
5691
|
+
if (!ls) return;
|
|
5692
|
+
state.completedTools = [...ls.completedTools];
|
|
5693
|
+
state.activeTool = ls.activeTool ? { ...ls.activeTool } : null;
|
|
5694
|
+
state.activeToolArgs = ls.activeToolArgs;
|
|
5695
|
+
state.lastThought = ls.lastThought;
|
|
5696
|
+
state.retryInfo = ls.retryInfo ? { ...ls.retryInfo } : null;
|
|
5697
|
+
state.activeDriverName = ls.activeDriverName;
|
|
5698
|
+
state.activeLaneId = laneId;
|
|
5699
|
+
}
|
|
5700
|
+
function getOrCreateLaneState(laneId) {
|
|
5701
|
+
let ls = laneStates.get(laneId);
|
|
5702
|
+
if (!ls) {
|
|
5703
|
+
ls = {
|
|
5704
|
+
completedTools: [],
|
|
5705
|
+
activeTool: null,
|
|
5706
|
+
activeToolArgs: "",
|
|
5707
|
+
lastThought: null,
|
|
5708
|
+
retryInfo: null,
|
|
5709
|
+
activeDriverName: null,
|
|
5710
|
+
status: "active",
|
|
5711
|
+
lastActivityTime: Date.now()
|
|
5712
|
+
};
|
|
5713
|
+
laneStates.set(laneId, ls);
|
|
5714
|
+
}
|
|
5715
|
+
return ls;
|
|
5716
|
+
}
|
|
5717
|
+
function getActiveLaneIds() {
|
|
5718
|
+
const ids = [];
|
|
5719
|
+
for (const [id, ls] of laneStates) {
|
|
5720
|
+
if (ls.status === "active") ids.push(id);
|
|
5721
|
+
}
|
|
5722
|
+
return ids;
|
|
5723
|
+
}
|
|
5724
|
+
function update(event, driverName, laneId) {
|
|
5725
|
+
if (cleaned) return;
|
|
5726
|
+
if (laneId) {
|
|
5727
|
+
const ls = getOrCreateLaneState(laneId);
|
|
5728
|
+
ls.lastActivityTime = Date.now();
|
|
5729
|
+
switch (event.type) {
|
|
5730
|
+
case "tool-start":
|
|
5731
|
+
promoteActiveTool(false, ls);
|
|
5732
|
+
ls.activeTool = { name: event.name };
|
|
5733
|
+
ls.activeToolArgs = "";
|
|
5734
|
+
ls.activeDriverName = driverName ?? null;
|
|
5735
|
+
ls.lastThought = null;
|
|
5736
|
+
ls.retryInfo = null;
|
|
5737
|
+
break;
|
|
5738
|
+
case "tool-input":
|
|
5739
|
+
ls.activeToolArgs += event.partial;
|
|
5740
|
+
return;
|
|
5741
|
+
case "tool-complete":
|
|
5742
|
+
if (ls.activeTool) {
|
|
5743
|
+
if (["Agent", "Skill"].includes(ls.activeTool.name)) break;
|
|
5744
|
+
promoteActiveTool(true, ls);
|
|
5745
|
+
}
|
|
5746
|
+
break;
|
|
5747
|
+
case "text":
|
|
5748
|
+
ls.lastThought = event.text;
|
|
5749
|
+
ls.retryInfo = null;
|
|
5750
|
+
break;
|
|
5751
|
+
case "retry":
|
|
5752
|
+
ls.retryInfo = { attempt: event.attempt, delay: event.delay };
|
|
5753
|
+
break;
|
|
5754
|
+
case "result":
|
|
5755
|
+
if (event.cost > 0 && state.sprintInfo) {
|
|
5756
|
+
state.sprintInfo = {
|
|
5757
|
+
...state.sprintInfo,
|
|
5758
|
+
totalCost: (state.sprintInfo.totalCost ?? 0) + event.cost
|
|
5759
|
+
};
|
|
5760
|
+
}
|
|
5761
|
+
if (event.cost > 0 && driverName) {
|
|
5762
|
+
state.driverCosts = {
|
|
5763
|
+
...state.driverCosts,
|
|
5764
|
+
[driverName]: (state.driverCosts[driverName] ?? 0) + event.cost
|
|
5765
|
+
};
|
|
5766
|
+
currentStoryCosts = {
|
|
5767
|
+
...currentStoryCosts,
|
|
5768
|
+
[driverName]: (currentStoryCosts[driverName] ?? 0) + event.cost
|
|
5769
|
+
};
|
|
5770
|
+
}
|
|
5771
|
+
break;
|
|
5772
|
+
}
|
|
5773
|
+
if (!pinnedLane && state.activeLaneId !== laneId) {
|
|
5774
|
+
state.activeLaneId = laneId;
|
|
5775
|
+
}
|
|
5776
|
+
if (pinnedLane && state.activeLaneId !== laneId) {
|
|
5777
|
+
pinnedLane = false;
|
|
5778
|
+
}
|
|
5779
|
+
if (state.activeLaneId === laneId) {
|
|
5780
|
+
copyLaneToDisplay(laneId);
|
|
5781
|
+
}
|
|
5782
|
+
state.laneCount = laneStates.size;
|
|
5783
|
+
rerender();
|
|
5784
|
+
return;
|
|
5785
|
+
}
|
|
5786
|
+
switch (event.type) {
|
|
5787
|
+
case "tool-start":
|
|
5788
|
+
promoteActiveTool(false);
|
|
5789
|
+
state.activeTool = { name: event.name };
|
|
5790
|
+
state.activeToolArgs = "";
|
|
5791
|
+
state.activeDriverName = driverName ?? null;
|
|
5792
|
+
state.lastThought = null;
|
|
5793
|
+
state.retryInfo = null;
|
|
5794
|
+
break;
|
|
5795
|
+
case "tool-input":
|
|
5796
|
+
state.activeToolArgs += event.partial;
|
|
5797
|
+
return;
|
|
5798
|
+
// Skip rerender
|
|
5799
|
+
case "tool-complete":
|
|
5800
|
+
if (state.activeTool) {
|
|
5801
|
+
if (["Agent", "Skill"].includes(state.activeTool.name)) break;
|
|
5802
|
+
promoteActiveTool(true);
|
|
5803
|
+
}
|
|
5804
|
+
break;
|
|
5805
|
+
case "text":
|
|
5806
|
+
state.lastThought = event.text;
|
|
5807
|
+
state.retryInfo = null;
|
|
5808
|
+
break;
|
|
5809
|
+
case "retry":
|
|
5810
|
+
state.retryInfo = { attempt: event.attempt, delay: event.delay };
|
|
5811
|
+
break;
|
|
5812
|
+
case "result":
|
|
5813
|
+
if (event.cost > 0 && state.sprintInfo) {
|
|
5814
|
+
state.sprintInfo = {
|
|
5815
|
+
...state.sprintInfo,
|
|
5816
|
+
totalCost: (state.sprintInfo.totalCost ?? 0) + event.cost
|
|
5817
|
+
};
|
|
5818
|
+
}
|
|
5819
|
+
if (event.cost > 0 && driverName) {
|
|
5820
|
+
state.driverCosts = {
|
|
5821
|
+
...state.driverCosts,
|
|
5822
|
+
[driverName]: (state.driverCosts[driverName] ?? 0) + event.cost
|
|
5823
|
+
};
|
|
5824
|
+
currentStoryCosts = {
|
|
5825
|
+
...currentStoryCosts,
|
|
5826
|
+
[driverName]: (currentStoryCosts[driverName] ?? 0) + event.cost
|
|
5827
|
+
};
|
|
5828
|
+
}
|
|
5829
|
+
break;
|
|
5830
|
+
}
|
|
5831
|
+
rerender();
|
|
5832
|
+
}
|
|
5833
|
+
function processLaneEvent(event) {
|
|
5834
|
+
if (cleaned) return;
|
|
5835
|
+
switch (event.type) {
|
|
5836
|
+
case "lane-started": {
|
|
5837
|
+
const ls = getOrCreateLaneState(event.epicId);
|
|
5838
|
+
ls.status = "active";
|
|
5839
|
+
ls.lastActivityTime = Date.now();
|
|
5840
|
+
if (state.activeLaneId == null) {
|
|
5841
|
+
state.activeLaneId = event.epicId;
|
|
5842
|
+
copyLaneToDisplay(event.epicId);
|
|
5843
|
+
}
|
|
5844
|
+
state.laneCount = laneStates.size;
|
|
5845
|
+
break;
|
|
5846
|
+
}
|
|
5847
|
+
case "lane-completed": {
|
|
5848
|
+
const ls = laneStates.get(event.epicId);
|
|
5849
|
+
if (ls) {
|
|
5850
|
+
ls.status = "completed";
|
|
5851
|
+
}
|
|
5852
|
+
if (state.summaryBar) {
|
|
5853
|
+
const epicId = event.epicId;
|
|
5854
|
+
const newDone = [...state.summaryBar.doneStories];
|
|
5855
|
+
if (!newDone.includes(epicId)) newDone.push(epicId);
|
|
5856
|
+
const newPending = state.summaryBar.pendingEpics.filter((e) => e !== epicId);
|
|
5857
|
+
state.summaryBar = {
|
|
5858
|
+
...state.summaryBar,
|
|
5859
|
+
doneStories: newDone,
|
|
5860
|
+
pendingEpics: newPending
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
state.laneCount = laneStates.size;
|
|
5864
|
+
break;
|
|
5865
|
+
}
|
|
5866
|
+
case "lane-failed": {
|
|
5867
|
+
const ls = laneStates.get(event.epicId);
|
|
5868
|
+
if (ls) {
|
|
5869
|
+
ls.status = "failed";
|
|
5870
|
+
} else {
|
|
5871
|
+
const newLs = getOrCreateLaneState(event.epicId);
|
|
5872
|
+
newLs.status = "failed";
|
|
5873
|
+
}
|
|
5874
|
+
if (state.activeLaneId === event.epicId) {
|
|
5875
|
+
const activeIds = getActiveLaneIds();
|
|
5876
|
+
if (activeIds.length > 0) {
|
|
5877
|
+
state.activeLaneId = activeIds[0];
|
|
5878
|
+
copyLaneToDisplay(activeIds[0]);
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
state.laneCount = laneStates.size;
|
|
5882
|
+
break;
|
|
5883
|
+
}
|
|
5884
|
+
case "epic-queued": {
|
|
5885
|
+
if (state.summaryBar) {
|
|
5886
|
+
if (!state.summaryBar.pendingEpics.includes(event.epicId)) {
|
|
5887
|
+
state.summaryBar = {
|
|
5888
|
+
...state.summaryBar,
|
|
5889
|
+
pendingEpics: [...state.summaryBar.pendingEpics, event.epicId]
|
|
5890
|
+
};
|
|
5891
|
+
}
|
|
5892
|
+
}
|
|
5893
|
+
break;
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
rerender();
|
|
5897
|
+
}
|
|
5898
|
+
function updateMergeState(mergeState) {
|
|
5899
|
+
if (cleaned) return;
|
|
5900
|
+
state.mergeState = mergeState;
|
|
5901
|
+
if (state.summaryBar && !mergeState) {
|
|
5902
|
+
state.summaryBar = { ...state.summaryBar, mergingEpic: null };
|
|
5903
|
+
}
|
|
5904
|
+
if (state.summaryBar && mergeState) {
|
|
5905
|
+
const mergingStatus = mergeState.outcome === "clean" || mergeState.outcome === "resolved" ? "complete" : mergeState.outcome === "escalated" ? "complete" : "in-progress";
|
|
5906
|
+
state.summaryBar = {
|
|
5907
|
+
...state.summaryBar,
|
|
5908
|
+
mergingEpic: {
|
|
5909
|
+
epicId: mergeState.epicId,
|
|
5910
|
+
status: mergingStatus,
|
|
5911
|
+
conflictCount: mergeState.conflictCount
|
|
5912
|
+
}
|
|
5913
|
+
};
|
|
5914
|
+
}
|
|
5915
|
+
rerender();
|
|
5916
|
+
}
|
|
5917
|
+
function cycleLane() {
|
|
5918
|
+
if (cleaned) return;
|
|
5919
|
+
const activeIds = getActiveLaneIds();
|
|
5920
|
+
if (activeIds.length <= 1) return;
|
|
5921
|
+
const currentIndex = state.activeLaneId ? activeIds.indexOf(state.activeLaneId) : -1;
|
|
5922
|
+
const nextIndex = (currentIndex + 1) % activeIds.length;
|
|
5923
|
+
state.activeLaneId = activeIds[nextIndex];
|
|
5924
|
+
copyLaneToDisplay(activeIds[nextIndex]);
|
|
5925
|
+
pinnedLane = true;
|
|
5926
|
+
rerender();
|
|
5927
|
+
}
|
|
5928
|
+
function cleanupFull() {
|
|
5929
|
+
if (cleaned) return;
|
|
5930
|
+
cleaned = true;
|
|
5931
|
+
try {
|
|
5932
|
+
inkInstance.unmount();
|
|
5933
|
+
} catch {
|
|
5934
|
+
}
|
|
5935
|
+
try {
|
|
5936
|
+
inkInstance.cleanup();
|
|
5937
|
+
} catch {
|
|
5938
|
+
}
|
|
5939
|
+
process.removeListener("SIGINT", onSigint);
|
|
5940
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
5941
|
+
}
|
|
5942
|
+
function updateSprintState(sprintState) {
|
|
5943
|
+
if (cleaned) return;
|
|
5944
|
+
if (sprintState && state.sprintInfo) {
|
|
5945
|
+
state.sprintInfo = {
|
|
5946
|
+
...sprintState,
|
|
5947
|
+
totalCost: sprintState.totalCost ?? state.sprintInfo.totalCost,
|
|
5948
|
+
acProgress: sprintState.acProgress ?? state.sprintInfo.acProgress,
|
|
5949
|
+
currentCommand: sprintState.currentCommand ?? state.sprintInfo.currentCommand
|
|
5950
|
+
};
|
|
5951
|
+
} else {
|
|
5952
|
+
state.sprintInfo = sprintState ?? null;
|
|
5953
|
+
}
|
|
5954
|
+
const newKey = state.sprintInfo?.storyKey ?? null;
|
|
5955
|
+
if (newKey && lastStoryKey && newKey !== lastStoryKey) {
|
|
5956
|
+
if (Object.keys(currentStoryCosts).length > 0) {
|
|
5957
|
+
pendingStoryCosts.set(lastStoryKey, { ...currentStoryCosts });
|
|
5958
|
+
}
|
|
5959
|
+
currentStoryCosts = {};
|
|
5960
|
+
lastStoryKey = newKey;
|
|
5961
|
+
} else if (newKey && !lastStoryKey) {
|
|
5962
|
+
lastStoryKey = newKey;
|
|
5963
|
+
}
|
|
5964
|
+
rerender();
|
|
5965
|
+
}
|
|
5966
|
+
function updateStories(stories) {
|
|
5967
|
+
if (cleaned) return;
|
|
5968
|
+
const currentKey = state.sprintInfo?.storyKey ?? null;
|
|
5969
|
+
const hasCurrentCosts = Object.keys(currentStoryCosts).length > 0;
|
|
5970
|
+
const updatedStories = stories.map((s) => {
|
|
5971
|
+
if (s.status !== "done" || s.costByDriver) return s;
|
|
5972
|
+
const pending = pendingStoryCosts.get(s.key);
|
|
5973
|
+
if (pending) {
|
|
5974
|
+
pendingStoryCosts.delete(s.key);
|
|
5975
|
+
return { ...s, costByDriver: pending };
|
|
5976
|
+
}
|
|
5977
|
+
if (hasCurrentCosts && s.key === (lastStoryKey ?? currentKey)) {
|
|
5978
|
+
const snap = { ...currentStoryCosts };
|
|
5979
|
+
currentStoryCosts = {};
|
|
5980
|
+
return { ...s, costByDriver: snap };
|
|
5981
|
+
}
|
|
5982
|
+
return s;
|
|
5983
|
+
});
|
|
5984
|
+
if (currentKey && currentKey !== lastStoryKey) {
|
|
5985
|
+
if (lastStoryKey && Object.keys(currentStoryCosts).length > 0) {
|
|
5986
|
+
pendingStoryCosts.set(lastStoryKey, { ...currentStoryCosts });
|
|
5987
|
+
}
|
|
5988
|
+
currentStoryCosts = {};
|
|
5989
|
+
lastStoryKey = currentKey;
|
|
5990
|
+
} else if (currentKey && !lastStoryKey) {
|
|
5991
|
+
lastStoryKey = currentKey;
|
|
5992
|
+
}
|
|
5993
|
+
state.stories = updatedStories;
|
|
5994
|
+
rerender();
|
|
5995
|
+
}
|
|
5996
|
+
function addMessage(msg) {
|
|
5997
|
+
if (cleaned) return;
|
|
5998
|
+
state.messages = [...state.messages, msg];
|
|
5999
|
+
rerender();
|
|
6000
|
+
}
|
|
6001
|
+
function updateWorkflowState(flow, currentTask, taskStates, taskMeta) {
|
|
6002
|
+
if (cleaned) return;
|
|
6003
|
+
state.workflowFlow = flow;
|
|
6004
|
+
state.currentTaskName = currentTask;
|
|
6005
|
+
state.taskStates = { ...taskStates };
|
|
6006
|
+
state.taskMeta = taskMeta ? { ...taskMeta } : state.taskMeta;
|
|
6007
|
+
rerender();
|
|
6008
|
+
}
|
|
6009
|
+
return {
|
|
6010
|
+
update,
|
|
6011
|
+
updateSprintState,
|
|
6012
|
+
updateStories,
|
|
6013
|
+
addMessage,
|
|
6014
|
+
updateWorkflowState,
|
|
6015
|
+
processLaneEvent,
|
|
6016
|
+
updateMergeState,
|
|
6017
|
+
cleanup: cleanupFull,
|
|
6018
|
+
_getState: () => state,
|
|
6019
|
+
_getLaneStates: () => laneStates,
|
|
6020
|
+
_cycleLane: () => cycleLane()
|
|
6021
|
+
};
|
|
6022
|
+
}
|
|
6023
|
+
|
|
4860
6024
|
// src/commands/run.ts
|
|
4861
6025
|
function resolvePluginDir() {
|
|
4862
6026
|
return join15(process.cwd(), ".claude");
|
|
@@ -4996,6 +6160,80 @@ function registerRunCommand(program) {
|
|
|
4996
6160
|
info("Resuming after circuit breaker \u2014 previous findings preserved", outputOpts);
|
|
4997
6161
|
}
|
|
4998
6162
|
}
|
|
6163
|
+
const renderer = startRenderer({
|
|
6164
|
+
quiet: !!options.quiet || isJson,
|
|
6165
|
+
sprintState: {
|
|
6166
|
+
storyKey: "",
|
|
6167
|
+
phase: "executing",
|
|
6168
|
+
done: counts.done,
|
|
6169
|
+
total: counts.total,
|
|
6170
|
+
totalCost: 0
|
|
6171
|
+
}
|
|
6172
|
+
});
|
|
6173
|
+
let totalCostUsd = 0;
|
|
6174
|
+
let storiesDone = counts.done;
|
|
6175
|
+
const taskStates = {};
|
|
6176
|
+
const taskMeta = {};
|
|
6177
|
+
for (const [tn, task] of Object.entries(parsedWorkflow.tasks)) {
|
|
6178
|
+
taskStates[tn] = "pending";
|
|
6179
|
+
taskMeta[tn] = { driver: task.driver ?? "claude-code" };
|
|
6180
|
+
}
|
|
6181
|
+
const storyEntries = [];
|
|
6182
|
+
for (const [key, status] of Object.entries(statuses)) {
|
|
6183
|
+
if (key.startsWith("epic-")) continue;
|
|
6184
|
+
if (status === "done") storyEntries.push({ key, status: "done" });
|
|
6185
|
+
else if (status === "in-progress") storyEntries.push({ key, status: "in-progress" });
|
|
6186
|
+
else if (status === "backlog" || status === "ready-for-dev") storyEntries.push({ key, status: "pending" });
|
|
6187
|
+
else if (status === "failed") storyEntries.push({ key, status: "failed" });
|
|
6188
|
+
}
|
|
6189
|
+
renderer.updateStories(storyEntries);
|
|
6190
|
+
const onEvent = (event) => {
|
|
6191
|
+
if (event.type === "stream-event" && event.streamEvent) {
|
|
6192
|
+
renderer.update(event.streamEvent, event.driverName);
|
|
6193
|
+
}
|
|
6194
|
+
if (event.type === "dispatch-start") {
|
|
6195
|
+
renderer.updateSprintState({
|
|
6196
|
+
storyKey: event.storyKey,
|
|
6197
|
+
phase: event.taskName,
|
|
6198
|
+
done: storiesDone,
|
|
6199
|
+
total: counts.total,
|
|
6200
|
+
totalCost: totalCostUsd
|
|
6201
|
+
});
|
|
6202
|
+
taskStates[event.taskName] = "active";
|
|
6203
|
+
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
6204
|
+
const idx = storyEntries.findIndex((s) => s.key === event.storyKey);
|
|
6205
|
+
if (idx >= 0 && storyEntries[idx].status === "pending") {
|
|
6206
|
+
storyEntries[idx] = { ...storyEntries[idx], status: "in-progress" };
|
|
6207
|
+
renderer.updateStories([...storyEntries]);
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
if (event.type === "dispatch-end") {
|
|
6211
|
+
totalCostUsd += event.costUsd ?? 0;
|
|
6212
|
+
taskStates[event.taskName] = "done";
|
|
6213
|
+
taskMeta[event.taskName] = {
|
|
6214
|
+
...taskMeta[event.taskName],
|
|
6215
|
+
costUsd: (taskMeta[event.taskName]?.costUsd ?? 0) + (event.costUsd ?? 0),
|
|
6216
|
+
elapsedMs: (taskMeta[event.taskName]?.elapsedMs ?? 0) + (event.elapsedMs ?? 0)
|
|
6217
|
+
};
|
|
6218
|
+
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
6219
|
+
renderer.updateSprintState({
|
|
6220
|
+
storyKey: event.storyKey,
|
|
6221
|
+
phase: event.taskName,
|
|
6222
|
+
done: storiesDone,
|
|
6223
|
+
total: counts.total,
|
|
6224
|
+
totalCost: totalCostUsd
|
|
6225
|
+
});
|
|
6226
|
+
}
|
|
6227
|
+
if (event.type === "dispatch-error") {
|
|
6228
|
+
taskStates[event.taskName] = "failed";
|
|
6229
|
+
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
6230
|
+
renderer.addMessage({
|
|
6231
|
+
type: "fail",
|
|
6232
|
+
key: event.storyKey,
|
|
6233
|
+
message: `[${event.taskName}] ${event.error?.message ?? "unknown error"}`
|
|
6234
|
+
});
|
|
6235
|
+
}
|
|
6236
|
+
};
|
|
4999
6237
|
const config = {
|
|
5000
6238
|
workflow: parsedWorkflow,
|
|
5001
6239
|
agents,
|
|
@@ -5003,7 +6241,8 @@ function registerRunCommand(program) {
|
|
|
5003
6241
|
issuesPath: join15(projectDir, ".codeharness", "issues.yaml"),
|
|
5004
6242
|
runId: `run-${Date.now()}`,
|
|
5005
6243
|
projectDir,
|
|
5006
|
-
maxIterations
|
|
6244
|
+
maxIterations,
|
|
6245
|
+
onEvent
|
|
5007
6246
|
};
|
|
5008
6247
|
const execution = parsedWorkflow.execution;
|
|
5009
6248
|
const isParallel = execution?.epic_strategy === "parallel";
|
|
@@ -5081,6 +6320,7 @@ function registerRunCommand(program) {
|
|
|
5081
6320
|
} else {
|
|
5082
6321
|
try {
|
|
5083
6322
|
const result = await executeWorkflow(config);
|
|
6323
|
+
renderer.cleanup();
|
|
5084
6324
|
if (result.success) {
|
|
5085
6325
|
ok(`Workflow completed \u2014 ${result.storiesProcessed} stories processed, ${result.tasksCompleted} tasks completed in ${formatElapsed(result.durationMs)}`, outputOpts);
|
|
5086
6326
|
} else {
|
|
@@ -5091,6 +6331,7 @@ function registerRunCommand(program) {
|
|
|
5091
6331
|
process.exitCode = 1;
|
|
5092
6332
|
}
|
|
5093
6333
|
} catch (err) {
|
|
6334
|
+
renderer.cleanup();
|
|
5094
6335
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5095
6336
|
fail(`Workflow engine error: ${msg}`, outputOpts);
|
|
5096
6337
|
process.exitCode = 1;
|
|
@@ -9177,7 +10418,7 @@ async function handleDockerCheck(isJson) {
|
|
|
9177
10418
|
}
|
|
9178
10419
|
}
|
|
9179
10420
|
}
|
|
9180
|
-
function
|
|
10421
|
+
function formatElapsed3(ms) {
|
|
9181
10422
|
const s = Math.floor(ms / 1e3);
|
|
9182
10423
|
const h = Math.floor(s / 3600);
|
|
9183
10424
|
const m = Math.floor(s % 3600 / 60);
|
|
@@ -9197,7 +10438,7 @@ function printWorkflowState() {
|
|
|
9197
10438
|
console.log(` Tasks completed: ${state.tasks_completed.length}`);
|
|
9198
10439
|
if (state.phase === "executing" && state.started) {
|
|
9199
10440
|
const elapsed = Date.now() - Date.parse(state.started);
|
|
9200
|
-
console.log(` Elapsed: ${
|
|
10441
|
+
console.log(` Elapsed: ${formatElapsed3(elapsed)}`);
|
|
9201
10442
|
}
|
|
9202
10443
|
if (state.evaluator_scores.length > 0) {
|
|
9203
10444
|
const latest = state.evaluator_scores[state.evaluator_scores.length - 1];
|
|
@@ -9222,7 +10463,7 @@ function getWorkflowStateData() {
|
|
|
9222
10463
|
};
|
|
9223
10464
|
if (state.phase === "executing" && state.started) {
|
|
9224
10465
|
data.elapsed_ms = Date.now() - Date.parse(state.started);
|
|
9225
|
-
data.elapsed =
|
|
10466
|
+
data.elapsed = formatElapsed3(data.elapsed_ms);
|
|
9226
10467
|
}
|
|
9227
10468
|
return data;
|
|
9228
10469
|
}
|
|
@@ -9874,7 +11115,7 @@ function registerTeardownCommand(program) {
|
|
|
9874
11115
|
} else if (otlpMode === "remote-routed") {
|
|
9875
11116
|
if (!options.keepDocker) {
|
|
9876
11117
|
try {
|
|
9877
|
-
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-
|
|
11118
|
+
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-QJGQIPTO.js");
|
|
9878
11119
|
stopCollectorOnly2();
|
|
9879
11120
|
result.docker.stopped = true;
|
|
9880
11121
|
if (!isJson) {
|
|
@@ -9906,7 +11147,7 @@ function registerTeardownCommand(program) {
|
|
|
9906
11147
|
info("Shared stack: kept running (other projects may use it)");
|
|
9907
11148
|
}
|
|
9908
11149
|
} else if (isLegacyStack) {
|
|
9909
|
-
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-
|
|
11150
|
+
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-QJGQIPTO.js");
|
|
9910
11151
|
let stackRunning = false;
|
|
9911
11152
|
try {
|
|
9912
11153
|
stackRunning = isStackRunning2(composeFile);
|
|
@@ -12784,7 +14025,7 @@ function registerDriversCommand(program) {
|
|
|
12784
14025
|
}
|
|
12785
14026
|
|
|
12786
14027
|
// src/index.ts
|
|
12787
|
-
var VERSION = true ? "0.29.
|
|
14028
|
+
var VERSION = true ? "0.29.2" : "0.0.0-dev";
|
|
12788
14029
|
function createProgram() {
|
|
12789
14030
|
const program = new Command();
|
|
12790
14031
|
program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");
|