codeharness 0.29.0 → 0.29.1
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-EJ6GZH4Z.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,48 @@ 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
|
+
const onEvent = (event) => {
|
|
6174
|
+
if (event.type === "stream-event" && event.streamEvent) {
|
|
6175
|
+
renderer.update(event.streamEvent, event.driverName);
|
|
6176
|
+
}
|
|
6177
|
+
if (event.type === "dispatch-start") {
|
|
6178
|
+
renderer.updateSprintState({
|
|
6179
|
+
storyKey: event.storyKey,
|
|
6180
|
+
phase: `${event.taskName}`,
|
|
6181
|
+
done: counts.done,
|
|
6182
|
+
total: counts.total
|
|
6183
|
+
});
|
|
6184
|
+
renderer.updateWorkflowState(
|
|
6185
|
+
parsedWorkflow.flow,
|
|
6186
|
+
event.taskName,
|
|
6187
|
+
{ [event.taskName]: "active" }
|
|
6188
|
+
);
|
|
6189
|
+
}
|
|
6190
|
+
if (event.type === "dispatch-end") {
|
|
6191
|
+
renderer.updateWorkflowState(
|
|
6192
|
+
parsedWorkflow.flow,
|
|
6193
|
+
event.taskName,
|
|
6194
|
+
{ [event.taskName]: "done" }
|
|
6195
|
+
);
|
|
6196
|
+
}
|
|
6197
|
+
if (event.type === "dispatch-error") {
|
|
6198
|
+
renderer.addMessage({
|
|
6199
|
+
type: "fail",
|
|
6200
|
+
key: event.storyKey,
|
|
6201
|
+
message: `[${event.taskName}] ${event.error?.message ?? "unknown error"}`
|
|
6202
|
+
});
|
|
6203
|
+
}
|
|
6204
|
+
};
|
|
4999
6205
|
const config = {
|
|
5000
6206
|
workflow: parsedWorkflow,
|
|
5001
6207
|
agents,
|
|
@@ -5003,7 +6209,8 @@ function registerRunCommand(program) {
|
|
|
5003
6209
|
issuesPath: join15(projectDir, ".codeharness", "issues.yaml"),
|
|
5004
6210
|
runId: `run-${Date.now()}`,
|
|
5005
6211
|
projectDir,
|
|
5006
|
-
maxIterations
|
|
6212
|
+
maxIterations,
|
|
6213
|
+
onEvent
|
|
5007
6214
|
};
|
|
5008
6215
|
const execution = parsedWorkflow.execution;
|
|
5009
6216
|
const isParallel = execution?.epic_strategy === "parallel";
|
|
@@ -5081,6 +6288,7 @@ function registerRunCommand(program) {
|
|
|
5081
6288
|
} else {
|
|
5082
6289
|
try {
|
|
5083
6290
|
const result = await executeWorkflow(config);
|
|
6291
|
+
renderer.cleanup();
|
|
5084
6292
|
if (result.success) {
|
|
5085
6293
|
ok(`Workflow completed \u2014 ${result.storiesProcessed} stories processed, ${result.tasksCompleted} tasks completed in ${formatElapsed(result.durationMs)}`, outputOpts);
|
|
5086
6294
|
} else {
|
|
@@ -5091,6 +6299,7 @@ function registerRunCommand(program) {
|
|
|
5091
6299
|
process.exitCode = 1;
|
|
5092
6300
|
}
|
|
5093
6301
|
} catch (err) {
|
|
6302
|
+
renderer.cleanup();
|
|
5094
6303
|
const msg = err instanceof Error ? err.message : String(err);
|
|
5095
6304
|
fail(`Workflow engine error: ${msg}`, outputOpts);
|
|
5096
6305
|
process.exitCode = 1;
|
|
@@ -9177,7 +10386,7 @@ async function handleDockerCheck(isJson) {
|
|
|
9177
10386
|
}
|
|
9178
10387
|
}
|
|
9179
10388
|
}
|
|
9180
|
-
function
|
|
10389
|
+
function formatElapsed3(ms) {
|
|
9181
10390
|
const s = Math.floor(ms / 1e3);
|
|
9182
10391
|
const h = Math.floor(s / 3600);
|
|
9183
10392
|
const m = Math.floor(s % 3600 / 60);
|
|
@@ -9197,7 +10406,7 @@ function printWorkflowState() {
|
|
|
9197
10406
|
console.log(` Tasks completed: ${state.tasks_completed.length}`);
|
|
9198
10407
|
if (state.phase === "executing" && state.started) {
|
|
9199
10408
|
const elapsed = Date.now() - Date.parse(state.started);
|
|
9200
|
-
console.log(` Elapsed: ${
|
|
10409
|
+
console.log(` Elapsed: ${formatElapsed3(elapsed)}`);
|
|
9201
10410
|
}
|
|
9202
10411
|
if (state.evaluator_scores.length > 0) {
|
|
9203
10412
|
const latest = state.evaluator_scores[state.evaluator_scores.length - 1];
|
|
@@ -9222,7 +10431,7 @@ function getWorkflowStateData() {
|
|
|
9222
10431
|
};
|
|
9223
10432
|
if (state.phase === "executing" && state.started) {
|
|
9224
10433
|
data.elapsed_ms = Date.now() - Date.parse(state.started);
|
|
9225
|
-
data.elapsed =
|
|
10434
|
+
data.elapsed = formatElapsed3(data.elapsed_ms);
|
|
9226
10435
|
}
|
|
9227
10436
|
return data;
|
|
9228
10437
|
}
|
|
@@ -9874,7 +11083,7 @@ function registerTeardownCommand(program) {
|
|
|
9874
11083
|
} else if (otlpMode === "remote-routed") {
|
|
9875
11084
|
if (!options.keepDocker) {
|
|
9876
11085
|
try {
|
|
9877
|
-
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-
|
|
11086
|
+
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-JBXHIWZS.js");
|
|
9878
11087
|
stopCollectorOnly2();
|
|
9879
11088
|
result.docker.stopped = true;
|
|
9880
11089
|
if (!isJson) {
|
|
@@ -9906,7 +11115,7 @@ function registerTeardownCommand(program) {
|
|
|
9906
11115
|
info("Shared stack: kept running (other projects may use it)");
|
|
9907
11116
|
}
|
|
9908
11117
|
} else if (isLegacyStack) {
|
|
9909
|
-
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-
|
|
11118
|
+
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-JBXHIWZS.js");
|
|
9910
11119
|
let stackRunning = false;
|
|
9911
11120
|
try {
|
|
9912
11121
|
stackRunning = isStackRunning2(composeFile);
|
|
@@ -12784,7 +13993,7 @@ function registerDriversCommand(program) {
|
|
|
12784
13993
|
}
|
|
12785
13994
|
|
|
12786
13995
|
// src/index.ts
|
|
12787
|
-
var VERSION = true ? "0.29.
|
|
13996
|
+
var VERSION = true ? "0.29.1" : "0.0.0-dev";
|
|
12788
13997
|
function createProgram() {
|
|
12789
13998
|
const program = new Command();
|
|
12790
13999
|
program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");
|