codeharness 0.29.3 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -2895,7 +2895,7 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
|
|
|
2895
2895
|
}
|
|
2896
2896
|
|
|
2897
2897
|
// src/modules/infra/init-project.ts
|
|
2898
|
-
var HARNESS_VERSION = true ? "0.
|
|
2898
|
+
var HARNESS_VERSION = true ? "0.30.0" : "0.0.0-dev";
|
|
2899
2899
|
function failResult(opts, error) {
|
|
2900
2900
|
return {
|
|
2901
2901
|
status: "fail",
|
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-S3RKDJMC.js";
|
|
44
44
|
|
|
45
45
|
// src/index.ts
|
|
46
46
|
import { Command } from "commander";
|
|
@@ -3882,75 +3882,165 @@ async function executeWorkflow(config) {
|
|
|
3882
3882
|
let halted = false;
|
|
3883
3883
|
let lastOutputContract = null;
|
|
3884
3884
|
let accumulatedCostUsd = 0;
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
}
|
|
3894
|
-
if (isLoopBlock(step)) {
|
|
3895
|
-
const loopResult = await executeLoopBlock(step, state, config, workItems, lastOutputContract);
|
|
3896
|
-
state = loopResult.state;
|
|
3897
|
-
errors.push(...loopResult.errors);
|
|
3898
|
-
tasksCompleted += loopResult.tasksCompleted;
|
|
3899
|
-
lastOutputContract = loopResult.lastContract;
|
|
3900
|
-
for (const item of workItems) {
|
|
3901
|
-
processedStories.add(item.key);
|
|
3885
|
+
if (config.storyPipeline) {
|
|
3886
|
+
const preTasks = [];
|
|
3887
|
+
let loopIdx = -1;
|
|
3888
|
+
for (let i = 0; i < config.workflow.storyFlow.length; i++) {
|
|
3889
|
+
const s = config.workflow.storyFlow[i];
|
|
3890
|
+
if (isLoopBlock(s)) {
|
|
3891
|
+
loopIdx = i;
|
|
3892
|
+
break;
|
|
3902
3893
|
}
|
|
3903
|
-
if (
|
|
3894
|
+
if (typeof s === "string") preTasks.push(s);
|
|
3895
|
+
}
|
|
3896
|
+
for (const item of workItems) {
|
|
3897
|
+
if (halted || config.abortSignal?.aborted) {
|
|
3898
|
+
if (config.abortSignal?.aborted) {
|
|
3899
|
+
info("Execution interrupted \u2014 saving state");
|
|
3900
|
+
state = { ...state, phase: "interrupted" };
|
|
3901
|
+
writeWorkflowState(state, projectDir);
|
|
3902
|
+
}
|
|
3904
3903
|
halted = true;
|
|
3904
|
+
break;
|
|
3905
3905
|
}
|
|
3906
|
-
|
|
3907
|
-
|
|
3906
|
+
processedStories.add(item.key);
|
|
3907
|
+
for (const taskName of preTasks) {
|
|
3908
|
+
if (halted || config.abortSignal?.aborted) {
|
|
3909
|
+
halted = true;
|
|
3910
|
+
break;
|
|
3911
|
+
}
|
|
3912
|
+
const task = config.workflow.tasks[taskName];
|
|
3913
|
+
if (!task || task.agent === null) continue;
|
|
3914
|
+
const definition = config.agents[task.agent];
|
|
3915
|
+
if (!definition) {
|
|
3916
|
+
warn(`workflow-engine: agent "${task.agent}" not found for "${taskName}"`);
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3919
|
+
if (isTaskCompleted(state, taskName, item.key)) continue;
|
|
3920
|
+
try {
|
|
3921
|
+
const dr = await dispatchTaskWithResult(task, taskName, item.key, definition, state, config, void 0, lastOutputContract ?? void 0);
|
|
3922
|
+
state = dr.updatedState;
|
|
3923
|
+
lastOutputContract = dr.contract;
|
|
3924
|
+
propagateVerifyFlags(taskName, dr.contract, projectDir);
|
|
3925
|
+
accumulatedCostUsd += dr.contract?.cost_usd ?? 0;
|
|
3926
|
+
tasksCompleted++;
|
|
3927
|
+
} catch (err) {
|
|
3928
|
+
const engineError = handleDispatchError(err, taskName, item.key);
|
|
3929
|
+
errors.push(engineError);
|
|
3930
|
+
if (config.onEvent) {
|
|
3931
|
+
config.onEvent({ type: "dispatch-error", taskName, storyKey: item.key, error: { code: engineError.code, message: engineError.message } });
|
|
3932
|
+
} else {
|
|
3933
|
+
warn(`[${taskName}] ${item.key} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
|
|
3934
|
+
}
|
|
3935
|
+
state = recordErrorInState(state, taskName, item.key, engineError);
|
|
3936
|
+
writeWorkflowState(state, projectDir);
|
|
3937
|
+
if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
|
|
3938
|
+
halted = true;
|
|
3939
|
+
}
|
|
3940
|
+
break;
|
|
3941
|
+
}
|
|
3908
3942
|
}
|
|
3909
|
-
continue;
|
|
3910
3943
|
}
|
|
3911
|
-
const
|
|
3912
|
-
const
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3944
|
+
const remaining = loopIdx >= 0 ? config.workflow.storyFlow.slice(loopIdx) : [];
|
|
3945
|
+
for (const step of remaining) {
|
|
3946
|
+
if (halted || config.abortSignal?.aborted) {
|
|
3947
|
+
halted = true;
|
|
3948
|
+
break;
|
|
3949
|
+
}
|
|
3950
|
+
if (isLoopBlock(step)) {
|
|
3951
|
+
const loopResult = await executeLoopBlock(step, state, config, workItems, lastOutputContract);
|
|
3952
|
+
state = loopResult.state;
|
|
3953
|
+
errors.push(...loopResult.errors);
|
|
3954
|
+
tasksCompleted += loopResult.tasksCompleted;
|
|
3955
|
+
lastOutputContract = loopResult.lastContract;
|
|
3956
|
+
for (const item of workItems) processedStories.add(item.key);
|
|
3957
|
+
if (loopResult.halted || state.phase === "max-iterations" || state.phase === "circuit-breaker") {
|
|
3958
|
+
halted = true;
|
|
3922
3959
|
}
|
|
3960
|
+
continue;
|
|
3961
|
+
}
|
|
3962
|
+
const taskName = step;
|
|
3963
|
+
const task = config.workflow.tasks[taskName];
|
|
3964
|
+
if (!task) continue;
|
|
3965
|
+
if (task.agent === null) continue;
|
|
3966
|
+
const definition = config.agents[task.agent];
|
|
3967
|
+
if (!definition) continue;
|
|
3968
|
+
if (task.scope === "per-run" || task.scope === "per-epic") {
|
|
3969
|
+
if (isTaskCompleted(state, taskName, PER_RUN_SENTINEL)) continue;
|
|
3923
3970
|
try {
|
|
3924
|
-
const
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
PER_RUN_SENTINEL,
|
|
3928
|
-
state,
|
|
3929
|
-
config,
|
|
3930
|
-
lastOutputContract ?? void 0,
|
|
3931
|
-
accumulatedCostUsd
|
|
3932
|
-
);
|
|
3933
|
-
state = nullResult.updatedState;
|
|
3934
|
-
lastOutputContract = nullResult.contract;
|
|
3971
|
+
const dr = await dispatchTaskWithResult(task, taskName, PER_RUN_SENTINEL, definition, state, config, void 0, lastOutputContract ?? void 0);
|
|
3972
|
+
state = dr.updatedState;
|
|
3973
|
+
lastOutputContract = dr.contract;
|
|
3935
3974
|
tasksCompleted++;
|
|
3936
3975
|
} catch (err) {
|
|
3937
|
-
const engineError =
|
|
3976
|
+
const engineError = handleDispatchError(err, taskName, PER_RUN_SENTINEL);
|
|
3938
3977
|
errors.push(engineError);
|
|
3939
3978
|
state = recordErrorInState(state, taskName, PER_RUN_SENTINEL, engineError);
|
|
3940
3979
|
writeWorkflowState(state, projectDir);
|
|
3941
3980
|
}
|
|
3942
3981
|
} else {
|
|
3982
|
+
for (const item of workItems) {
|
|
3983
|
+
if (halted || config.abortSignal?.aborted) break;
|
|
3984
|
+
if (isTaskCompleted(state, taskName, item.key)) continue;
|
|
3985
|
+
try {
|
|
3986
|
+
const dr = await dispatchTaskWithResult(task, taskName, item.key, definition, state, config, void 0, lastOutputContract ?? void 0);
|
|
3987
|
+
state = dr.updatedState;
|
|
3988
|
+
lastOutputContract = dr.contract;
|
|
3989
|
+
tasksCompleted++;
|
|
3990
|
+
} catch (err) {
|
|
3991
|
+
const engineError = handleDispatchError(err, taskName, item.key);
|
|
3992
|
+
errors.push(engineError);
|
|
3993
|
+
state = recordErrorInState(state, taskName, item.key, engineError);
|
|
3994
|
+
writeWorkflowState(state, projectDir);
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
if (!config.storyPipeline)
|
|
4001
|
+
for (const step of config.workflow.storyFlow) {
|
|
4002
|
+
if (halted) break;
|
|
4003
|
+
if (config.abortSignal?.aborted) {
|
|
4004
|
+
info("Execution interrupted \u2014 saving state");
|
|
4005
|
+
state = { ...state, phase: "interrupted" };
|
|
4006
|
+
writeWorkflowState(state, projectDir);
|
|
4007
|
+
halted = true;
|
|
4008
|
+
break;
|
|
4009
|
+
}
|
|
4010
|
+
if (isLoopBlock(step)) {
|
|
4011
|
+
const loopResult = await executeLoopBlock(step, state, config, workItems, lastOutputContract);
|
|
4012
|
+
state = loopResult.state;
|
|
4013
|
+
errors.push(...loopResult.errors);
|
|
4014
|
+
tasksCompleted += loopResult.tasksCompleted;
|
|
4015
|
+
lastOutputContract = loopResult.lastContract;
|
|
3943
4016
|
for (const item of workItems) {
|
|
3944
4017
|
processedStories.add(item.key);
|
|
3945
|
-
|
|
3946
|
-
|
|
4018
|
+
}
|
|
4019
|
+
if (loopResult.halted) {
|
|
4020
|
+
halted = true;
|
|
4021
|
+
}
|
|
4022
|
+
if (state.phase === "max-iterations" || state.phase === "circuit-breaker") {
|
|
4023
|
+
halted = true;
|
|
4024
|
+
}
|
|
4025
|
+
continue;
|
|
4026
|
+
}
|
|
4027
|
+
const taskName = step;
|
|
4028
|
+
const task = config.workflow.tasks[taskName];
|
|
4029
|
+
if (!task) {
|
|
4030
|
+
warn(`workflow-engine: task "${taskName}" not found in workflow tasks, skipping`);
|
|
4031
|
+
continue;
|
|
4032
|
+
}
|
|
4033
|
+
if (task.agent === null) {
|
|
4034
|
+
if (task.scope === "per-run") {
|
|
4035
|
+
if (isTaskCompleted(state, taskName, PER_RUN_SENTINEL)) {
|
|
4036
|
+
warn(`workflow-engine: skipping completed task ${taskName} for ${PER_RUN_SENTINEL}`);
|
|
3947
4037
|
continue;
|
|
3948
4038
|
}
|
|
3949
4039
|
try {
|
|
3950
4040
|
const nullResult = await executeNullTask(
|
|
3951
4041
|
task,
|
|
3952
4042
|
taskName,
|
|
3953
|
-
|
|
4043
|
+
PER_RUN_SENTINEL,
|
|
3954
4044
|
state,
|
|
3955
4045
|
config,
|
|
3956
4046
|
lastOutputContract ?? void 0,
|
|
@@ -3960,71 +4050,56 @@ async function executeWorkflow(config) {
|
|
|
3960
4050
|
lastOutputContract = nullResult.contract;
|
|
3961
4051
|
tasksCompleted++;
|
|
3962
4052
|
} catch (err) {
|
|
3963
|
-
const engineError = isEngineError(err) ? err : handleDispatchError(err, taskName,
|
|
4053
|
+
const engineError = isEngineError(err) ? err : handleDispatchError(err, taskName, PER_RUN_SENTINEL);
|
|
3964
4054
|
errors.push(engineError);
|
|
3965
|
-
state = recordErrorInState(state, taskName,
|
|
4055
|
+
state = recordErrorInState(state, taskName, PER_RUN_SENTINEL, engineError);
|
|
3966
4056
|
writeWorkflowState(state, projectDir);
|
|
3967
4057
|
}
|
|
4058
|
+
} else {
|
|
4059
|
+
for (const item of workItems) {
|
|
4060
|
+
processedStories.add(item.key);
|
|
4061
|
+
if (isTaskCompleted(state, taskName, item.key)) {
|
|
4062
|
+
warn(`workflow-engine: skipping completed task ${taskName} for ${item.key}`);
|
|
4063
|
+
continue;
|
|
4064
|
+
}
|
|
4065
|
+
try {
|
|
4066
|
+
const nullResult = await executeNullTask(
|
|
4067
|
+
task,
|
|
4068
|
+
taskName,
|
|
4069
|
+
item.key,
|
|
4070
|
+
state,
|
|
4071
|
+
config,
|
|
4072
|
+
lastOutputContract ?? void 0,
|
|
4073
|
+
accumulatedCostUsd
|
|
4074
|
+
);
|
|
4075
|
+
state = nullResult.updatedState;
|
|
4076
|
+
lastOutputContract = nullResult.contract;
|
|
4077
|
+
tasksCompleted++;
|
|
4078
|
+
} catch (err) {
|
|
4079
|
+
const engineError = isEngineError(err) ? err : handleDispatchError(err, taskName, item.key);
|
|
4080
|
+
errors.push(engineError);
|
|
4081
|
+
state = recordErrorInState(state, taskName, item.key, engineError);
|
|
4082
|
+
writeWorkflowState(state, projectDir);
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
3968
4085
|
}
|
|
3969
|
-
}
|
|
3970
|
-
continue;
|
|
3971
|
-
}
|
|
3972
|
-
const definition = config.agents[task.agent];
|
|
3973
|
-
if (!definition) {
|
|
3974
|
-
warn(`workflow-engine: agent "${task.agent}" not found for task "${taskName}", skipping`);
|
|
3975
|
-
continue;
|
|
3976
|
-
}
|
|
3977
|
-
if (task.scope === "per-run") {
|
|
3978
|
-
if (isTaskCompleted(state, taskName, PER_RUN_SENTINEL)) {
|
|
3979
|
-
warn(`workflow-engine: skipping completed task ${taskName} for ${PER_RUN_SENTINEL}`);
|
|
3980
4086
|
continue;
|
|
3981
4087
|
}
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
PER_RUN_SENTINEL,
|
|
3987
|
-
definition,
|
|
3988
|
-
state,
|
|
3989
|
-
config,
|
|
3990
|
-
void 0,
|
|
3991
|
-
lastOutputContract ?? void 0
|
|
3992
|
-
);
|
|
3993
|
-
state = dispatchResult.updatedState;
|
|
3994
|
-
lastOutputContract = dispatchResult.contract;
|
|
3995
|
-
propagateVerifyFlags(taskName, dispatchResult.contract, projectDir);
|
|
3996
|
-
accumulatedCostUsd += dispatchResult.contract?.cost_usd ?? 0;
|
|
3997
|
-
tasksCompleted++;
|
|
3998
|
-
} catch (err) {
|
|
3999
|
-
const engineError = handleDispatchError(err, taskName, PER_RUN_SENTINEL);
|
|
4000
|
-
errors.push(engineError);
|
|
4001
|
-
if (config.onEvent) {
|
|
4002
|
-
config.onEvent({ type: "dispatch-error", taskName, storyKey: PER_RUN_SENTINEL, error: { code: engineError.code, message: engineError.message } });
|
|
4003
|
-
} else {
|
|
4004
|
-
warn(`[${taskName}] ${PER_RUN_SENTINEL} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
|
|
4005
|
-
}
|
|
4006
|
-
state = recordErrorInState(state, taskName, PER_RUN_SENTINEL, engineError);
|
|
4007
|
-
writeWorkflowState(state, projectDir);
|
|
4008
|
-
if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
|
|
4009
|
-
halted = true;
|
|
4010
|
-
}
|
|
4088
|
+
const definition = config.agents[task.agent];
|
|
4089
|
+
if (!definition) {
|
|
4090
|
+
warn(`workflow-engine: agent "${task.agent}" not found for task "${taskName}", skipping`);
|
|
4091
|
+
continue;
|
|
4011
4092
|
}
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
halted = true;
|
|
4016
|
-
break;
|
|
4017
|
-
}
|
|
4018
|
-
processedStories.add(item.key);
|
|
4019
|
-
if (isTaskCompleted(state, taskName, item.key)) {
|
|
4020
|
-
warn(`workflow-engine: skipping completed task ${taskName} for ${item.key}`);
|
|
4093
|
+
if (task.scope === "per-run") {
|
|
4094
|
+
if (isTaskCompleted(state, taskName, PER_RUN_SENTINEL)) {
|
|
4095
|
+
warn(`workflow-engine: skipping completed task ${taskName} for ${PER_RUN_SENTINEL}`);
|
|
4021
4096
|
continue;
|
|
4022
4097
|
}
|
|
4023
4098
|
try {
|
|
4024
4099
|
const dispatchResult = await dispatchTaskWithResult(
|
|
4025
4100
|
task,
|
|
4026
4101
|
taskName,
|
|
4027
|
-
|
|
4102
|
+
PER_RUN_SENTINEL,
|
|
4028
4103
|
definition,
|
|
4029
4104
|
state,
|
|
4030
4105
|
config,
|
|
@@ -4037,24 +4112,65 @@ async function executeWorkflow(config) {
|
|
|
4037
4112
|
accumulatedCostUsd += dispatchResult.contract?.cost_usd ?? 0;
|
|
4038
4113
|
tasksCompleted++;
|
|
4039
4114
|
} catch (err) {
|
|
4040
|
-
const engineError = handleDispatchError(err, taskName,
|
|
4115
|
+
const engineError = handleDispatchError(err, taskName, PER_RUN_SENTINEL);
|
|
4041
4116
|
errors.push(engineError);
|
|
4042
4117
|
if (config.onEvent) {
|
|
4043
|
-
config.onEvent({ type: "dispatch-error", taskName, storyKey:
|
|
4118
|
+
config.onEvent({ type: "dispatch-error", taskName, storyKey: PER_RUN_SENTINEL, error: { code: engineError.code, message: engineError.message } });
|
|
4044
4119
|
} else {
|
|
4045
|
-
warn(`[${taskName}] ${
|
|
4120
|
+
warn(`[${taskName}] ${PER_RUN_SENTINEL} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
|
|
4046
4121
|
}
|
|
4047
|
-
state = recordErrorInState(state, taskName,
|
|
4122
|
+
state = recordErrorInState(state, taskName, PER_RUN_SENTINEL, engineError);
|
|
4048
4123
|
writeWorkflowState(state, projectDir);
|
|
4049
4124
|
if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
|
|
4050
4125
|
halted = true;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
} else {
|
|
4129
|
+
for (const item of workItems) {
|
|
4130
|
+
if (config.abortSignal?.aborted) {
|
|
4131
|
+
halted = true;
|
|
4051
4132
|
break;
|
|
4052
4133
|
}
|
|
4053
|
-
|
|
4134
|
+
processedStories.add(item.key);
|
|
4135
|
+
if (isTaskCompleted(state, taskName, item.key)) {
|
|
4136
|
+
warn(`workflow-engine: skipping completed task ${taskName} for ${item.key}`);
|
|
4137
|
+
continue;
|
|
4138
|
+
}
|
|
4139
|
+
try {
|
|
4140
|
+
const dispatchResult = await dispatchTaskWithResult(
|
|
4141
|
+
task,
|
|
4142
|
+
taskName,
|
|
4143
|
+
item.key,
|
|
4144
|
+
definition,
|
|
4145
|
+
state,
|
|
4146
|
+
config,
|
|
4147
|
+
void 0,
|
|
4148
|
+
lastOutputContract ?? void 0
|
|
4149
|
+
);
|
|
4150
|
+
state = dispatchResult.updatedState;
|
|
4151
|
+
lastOutputContract = dispatchResult.contract;
|
|
4152
|
+
propagateVerifyFlags(taskName, dispatchResult.contract, projectDir);
|
|
4153
|
+
accumulatedCostUsd += dispatchResult.contract?.cost_usd ?? 0;
|
|
4154
|
+
tasksCompleted++;
|
|
4155
|
+
} catch (err) {
|
|
4156
|
+
const engineError = handleDispatchError(err, taskName, item.key);
|
|
4157
|
+
errors.push(engineError);
|
|
4158
|
+
if (config.onEvent) {
|
|
4159
|
+
config.onEvent({ type: "dispatch-error", taskName, storyKey: item.key, error: { code: engineError.code, message: engineError.message } });
|
|
4160
|
+
} else {
|
|
4161
|
+
warn(`[${taskName}] ${item.key} \u2014 ERROR: [${engineError.code}] ${engineError.message}`);
|
|
4162
|
+
}
|
|
4163
|
+
state = recordErrorInState(state, taskName, item.key, engineError);
|
|
4164
|
+
writeWorkflowState(state, projectDir);
|
|
4165
|
+
if (err instanceof DispatchError && HALT_ERROR_CODES.has(err.code)) {
|
|
4166
|
+
halted = true;
|
|
4167
|
+
break;
|
|
4168
|
+
}
|
|
4169
|
+
continue;
|
|
4170
|
+
}
|
|
4054
4171
|
}
|
|
4055
4172
|
}
|
|
4056
4173
|
}
|
|
4057
|
-
}
|
|
4058
4174
|
if (state.phase === "interrupted") {
|
|
4059
4175
|
} else if (errors.length === 0 && state.phase !== "max-iterations" && state.phase !== "circuit-breaker") {
|
|
4060
4176
|
state = { ...state, phase: "completed" };
|
|
@@ -5430,14 +5546,17 @@ function LaneActivityHeader({ activeLaneId, laneCount }) {
|
|
|
5430
5546
|
if (laneCount <= 1 || !activeLaneId) return null;
|
|
5431
5547
|
return /* @__PURE__ */ jsx7(Text7, { children: /* @__PURE__ */ jsx7(Text7, { color: "cyan", children: `[Lane ${activeLaneId} \u25B8]` }) });
|
|
5432
5548
|
}
|
|
5433
|
-
function App({ state, onCycleLane }) {
|
|
5549
|
+
function App({ state, onCycleLane, onQuit }) {
|
|
5434
5550
|
const lanes = state.lanes;
|
|
5435
5551
|
const laneCount = lanes?.length ?? 0;
|
|
5436
5552
|
const terminalWidth = process.stdout.columns || 80;
|
|
5437
|
-
useInput((
|
|
5438
|
-
if (key.ctrl &&
|
|
5553
|
+
useInput((input, key) => {
|
|
5554
|
+
if (key.ctrl && input === "l" && onCycleLane && laneCount > 1) {
|
|
5439
5555
|
onCycleLane();
|
|
5440
5556
|
}
|
|
5557
|
+
if (input === "q" && onQuit) {
|
|
5558
|
+
onQuit();
|
|
5559
|
+
}
|
|
5441
5560
|
}, { isActive: typeof process.stdin.setRawMode === "function" });
|
|
5442
5561
|
const activeLaneCount = state.laneCount ?? 0;
|
|
5443
5562
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
@@ -5661,7 +5780,8 @@ function startRenderer(options) {
|
|
|
5661
5780
|
let lastStoryKey = state.sprintInfo?.storyKey ?? null;
|
|
5662
5781
|
const pendingStoryCosts = /* @__PURE__ */ new Map();
|
|
5663
5782
|
let cleaned = false;
|
|
5664
|
-
const
|
|
5783
|
+
const onQuit = options?.onQuit;
|
|
5784
|
+
const inkInstance = inkRender(/* @__PURE__ */ jsx9(App, { state, onCycleLane: () => cycleLane(), onQuit: onQuit ? () => onQuit() : void 0 }), {
|
|
5665
5785
|
exitOnCtrlC: false,
|
|
5666
5786
|
patchConsole: !options?._forceTTY,
|
|
5667
5787
|
maxFps: 15
|
|
@@ -5669,16 +5789,17 @@ function startRenderer(options) {
|
|
|
5669
5789
|
function rerender() {
|
|
5670
5790
|
if (!cleaned) {
|
|
5671
5791
|
state = { ...state };
|
|
5672
|
-
inkInstance.rerender(/* @__PURE__ */ jsx9(App, { state, onCycleLane: () => cycleLane() }));
|
|
5792
|
+
inkInstance.rerender(/* @__PURE__ */ jsx9(App, { state, onCycleLane: () => cycleLane(), onQuit: onQuit ? () => onQuit() : void 0 }));
|
|
5673
5793
|
}
|
|
5674
5794
|
}
|
|
5795
|
+
const heartbeat = setInterval(() => {
|
|
5796
|
+
if (!cleaned) rerender();
|
|
5797
|
+
}, 200);
|
|
5675
5798
|
function onSigint() {
|
|
5676
5799
|
cleanupFull();
|
|
5677
|
-
process.kill(process.pid, "SIGINT");
|
|
5678
5800
|
}
|
|
5679
5801
|
function onSigterm() {
|
|
5680
5802
|
cleanupFull();
|
|
5681
|
-
process.kill(process.pid, "SIGTERM");
|
|
5682
5803
|
}
|
|
5683
5804
|
process.on("SIGINT", onSigint);
|
|
5684
5805
|
process.on("SIGTERM", onSigterm);
|
|
@@ -5940,6 +6061,7 @@ function startRenderer(options) {
|
|
|
5940
6061
|
function cleanupFull() {
|
|
5941
6062
|
if (cleaned) return;
|
|
5942
6063
|
cleaned = true;
|
|
6064
|
+
clearInterval(heartbeat);
|
|
5943
6065
|
try {
|
|
5944
6066
|
inkInstance.unmount();
|
|
5945
6067
|
} catch {
|
|
@@ -6173,29 +6295,46 @@ function registerRunCommand(program) {
|
|
|
6173
6295
|
}
|
|
6174
6296
|
}
|
|
6175
6297
|
const abortController = new AbortController();
|
|
6176
|
-
const renderer = startRenderer({
|
|
6177
|
-
quiet: !!options.quiet || isJson,
|
|
6178
|
-
sprintState: {
|
|
6179
|
-
storyKey: "",
|
|
6180
|
-
phase: "executing",
|
|
6181
|
-
done: counts.done,
|
|
6182
|
-
total: counts.total,
|
|
6183
|
-
totalCost: 0
|
|
6184
|
-
}
|
|
6185
|
-
});
|
|
6186
6298
|
let interrupted = false;
|
|
6299
|
+
let renderer;
|
|
6187
6300
|
const onInterrupt = () => {
|
|
6188
6301
|
if (interrupted) {
|
|
6189
|
-
renderer.cleanup();
|
|
6190
6302
|
process.exit(1);
|
|
6191
6303
|
}
|
|
6192
6304
|
interrupted = true;
|
|
6305
|
+
renderer.cleanup();
|
|
6193
6306
|
abortController.abort();
|
|
6307
|
+
info("Interrupted \u2014 waiting for current task to finish...", outputOpts);
|
|
6194
6308
|
};
|
|
6309
|
+
renderer = startRenderer({
|
|
6310
|
+
quiet: !!options.quiet || isJson,
|
|
6311
|
+
sprintState: {
|
|
6312
|
+
storyKey: "",
|
|
6313
|
+
phase: "executing",
|
|
6314
|
+
done: counts.done,
|
|
6315
|
+
total: counts.total,
|
|
6316
|
+
totalCost: 0
|
|
6317
|
+
},
|
|
6318
|
+
onQuit: () => onInterrupt()
|
|
6319
|
+
});
|
|
6195
6320
|
process.on("SIGINT", onInterrupt);
|
|
6196
6321
|
process.on("SIGTERM", onInterrupt);
|
|
6322
|
+
const sessionStartMs = Date.now();
|
|
6197
6323
|
let totalCostUsd = 0;
|
|
6198
6324
|
let storiesDone = counts.done;
|
|
6325
|
+
let currentStoryKey = "";
|
|
6326
|
+
let currentTaskName = "";
|
|
6327
|
+
const headerRefresh = setInterval(() => {
|
|
6328
|
+
if (interrupted) return;
|
|
6329
|
+
renderer.updateSprintState({
|
|
6330
|
+
storyKey: currentStoryKey,
|
|
6331
|
+
phase: currentTaskName,
|
|
6332
|
+
done: storiesDone,
|
|
6333
|
+
total: counts.total,
|
|
6334
|
+
totalCost: totalCostUsd,
|
|
6335
|
+
elapsed: formatElapsed(Date.now() - sessionStartMs)
|
|
6336
|
+
});
|
|
6337
|
+
}, 2e3);
|
|
6199
6338
|
const taskStates = {};
|
|
6200
6339
|
const taskMeta = {};
|
|
6201
6340
|
for (const [tn, task] of Object.entries(parsedWorkflow.tasks)) {
|
|
@@ -6216,12 +6355,15 @@ function registerRunCommand(program) {
|
|
|
6216
6355
|
renderer.update(event.streamEvent, event.driverName);
|
|
6217
6356
|
}
|
|
6218
6357
|
if (event.type === "dispatch-start") {
|
|
6358
|
+
currentStoryKey = event.storyKey;
|
|
6359
|
+
currentTaskName = event.taskName;
|
|
6219
6360
|
renderer.updateSprintState({
|
|
6220
6361
|
storyKey: event.storyKey,
|
|
6221
6362
|
phase: event.taskName,
|
|
6222
6363
|
done: storiesDone,
|
|
6223
6364
|
total: counts.total,
|
|
6224
|
-
totalCost: totalCostUsd
|
|
6365
|
+
totalCost: totalCostUsd,
|
|
6366
|
+
elapsed: formatElapsed(Date.now() - sessionStartMs)
|
|
6225
6367
|
});
|
|
6226
6368
|
taskStates[event.taskName] = "active";
|
|
6227
6369
|
renderer.updateWorkflowState(parsedWorkflow.flow, event.taskName, { ...taskStates }, { ...taskMeta });
|
|
@@ -6266,6 +6408,7 @@ function registerRunCommand(program) {
|
|
|
6266
6408
|
runId: `run-${Date.now()}`,
|
|
6267
6409
|
projectDir,
|
|
6268
6410
|
abortSignal: abortController.signal,
|
|
6411
|
+
storyPipeline: true,
|
|
6269
6412
|
maxIterations,
|
|
6270
6413
|
onEvent
|
|
6271
6414
|
};
|
|
@@ -6345,6 +6488,7 @@ function registerRunCommand(program) {
|
|
|
6345
6488
|
} else {
|
|
6346
6489
|
try {
|
|
6347
6490
|
const result = await executeWorkflow(config);
|
|
6491
|
+
clearInterval(headerRefresh);
|
|
6348
6492
|
process.removeListener("SIGINT", onInterrupt);
|
|
6349
6493
|
process.removeListener("SIGTERM", onInterrupt);
|
|
6350
6494
|
renderer.cleanup();
|
|
@@ -6361,6 +6505,7 @@ function registerRunCommand(program) {
|
|
|
6361
6505
|
process.exitCode = 1;
|
|
6362
6506
|
}
|
|
6363
6507
|
} catch (err) {
|
|
6508
|
+
clearInterval(headerRefresh);
|
|
6364
6509
|
renderer.cleanup();
|
|
6365
6510
|
const msg = err instanceof Error ? err.message : String(err);
|
|
6366
6511
|
fail(`Workflow engine error: ${msg}`, outputOpts);
|
|
@@ -11145,7 +11290,7 @@ function registerTeardownCommand(program) {
|
|
|
11145
11290
|
} else if (otlpMode === "remote-routed") {
|
|
11146
11291
|
if (!options.keepDocker) {
|
|
11147
11292
|
try {
|
|
11148
|
-
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-
|
|
11293
|
+
const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-7QONH2B5.js");
|
|
11149
11294
|
stopCollectorOnly2();
|
|
11150
11295
|
result.docker.stopped = true;
|
|
11151
11296
|
if (!isJson) {
|
|
@@ -11177,7 +11322,7 @@ function registerTeardownCommand(program) {
|
|
|
11177
11322
|
info("Shared stack: kept running (other projects may use it)");
|
|
11178
11323
|
}
|
|
11179
11324
|
} else if (isLegacyStack) {
|
|
11180
|
-
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-
|
|
11325
|
+
const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-7QONH2B5.js");
|
|
11181
11326
|
let stackRunning = false;
|
|
11182
11327
|
try {
|
|
11183
11328
|
stackRunning = isStackRunning2(composeFile);
|
|
@@ -13408,11 +13553,38 @@ function classifyError(err) {
|
|
|
13408
13553
|
}
|
|
13409
13554
|
return "UNKNOWN";
|
|
13410
13555
|
}
|
|
13411
|
-
function
|
|
13556
|
+
function mapSdkMessages(message) {
|
|
13412
13557
|
const type = message.type;
|
|
13558
|
+
const events = [];
|
|
13559
|
+
if (type === "assistant") {
|
|
13560
|
+
const msg = message.message;
|
|
13561
|
+
if (!msg) return events;
|
|
13562
|
+
const content = msg.content;
|
|
13563
|
+
if (!Array.isArray(content)) return events;
|
|
13564
|
+
for (const block of content) {
|
|
13565
|
+
if (block.type === "tool_use") {
|
|
13566
|
+
const name = block.name;
|
|
13567
|
+
const id = block.id;
|
|
13568
|
+
const input = block.input;
|
|
13569
|
+
if (typeof name === "string") {
|
|
13570
|
+
events.push({ type: "tool-start", name, id: typeof id === "string" ? id : "" });
|
|
13571
|
+
if (input != null) {
|
|
13572
|
+
events.push({ type: "tool-input", partial: typeof input === "string" ? input : JSON.stringify(input) });
|
|
13573
|
+
}
|
|
13574
|
+
events.push({ type: "tool-complete" });
|
|
13575
|
+
}
|
|
13576
|
+
} else if (block.type === "text") {
|
|
13577
|
+
const text = block.text;
|
|
13578
|
+
if (typeof text === "string" && text.length > 0) {
|
|
13579
|
+
events.push({ type: "text", text });
|
|
13580
|
+
}
|
|
13581
|
+
}
|
|
13582
|
+
}
|
|
13583
|
+
return events;
|
|
13584
|
+
}
|
|
13413
13585
|
if (type === "stream_event") {
|
|
13414
13586
|
const event = message.event;
|
|
13415
|
-
if (!event || typeof event !== "object") return
|
|
13587
|
+
if (!event || typeof event !== "object") return events;
|
|
13416
13588
|
const eventType = event.type;
|
|
13417
13589
|
if (eventType === "content_block_start") {
|
|
13418
13590
|
const contentBlock = event.content_block;
|
|
@@ -13420,34 +13592,26 @@ function mapSdkMessage(message) {
|
|
|
13420
13592
|
const name = contentBlock.name;
|
|
13421
13593
|
const id = contentBlock.id;
|
|
13422
13594
|
if (typeof name === "string" && typeof id === "string") {
|
|
13423
|
-
|
|
13595
|
+
events.push({ type: "tool-start", name, id });
|
|
13424
13596
|
}
|
|
13425
13597
|
}
|
|
13426
|
-
return
|
|
13598
|
+
return events;
|
|
13427
13599
|
}
|
|
13428
13600
|
if (eventType === "content_block_delta") {
|
|
13429
13601
|
const delta = event.delta;
|
|
13430
|
-
if (!delta) return
|
|
13431
|
-
if (delta.type === "input_json_delta") {
|
|
13432
|
-
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
}
|
|
13436
|
-
return null;
|
|
13602
|
+
if (!delta) return events;
|
|
13603
|
+
if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
|
|
13604
|
+
events.push({ type: "tool-input", partial: delta.partial_json });
|
|
13605
|
+
} else if (delta.type === "text_delta" && typeof delta.text === "string") {
|
|
13606
|
+
events.push({ type: "text", text: delta.text });
|
|
13437
13607
|
}
|
|
13438
|
-
|
|
13439
|
-
const text = delta.text;
|
|
13440
|
-
if (typeof text === "string") {
|
|
13441
|
-
return { type: "text", text };
|
|
13442
|
-
}
|
|
13443
|
-
return null;
|
|
13444
|
-
}
|
|
13445
|
-
return null;
|
|
13608
|
+
return events;
|
|
13446
13609
|
}
|
|
13447
13610
|
if (eventType === "content_block_stop") {
|
|
13448
|
-
|
|
13611
|
+
events.push({ type: "tool-complete" });
|
|
13612
|
+
return events;
|
|
13449
13613
|
}
|
|
13450
|
-
return
|
|
13614
|
+
return events;
|
|
13451
13615
|
}
|
|
13452
13616
|
if (type === "system") {
|
|
13453
13617
|
const subtype = message.subtype;
|
|
@@ -13455,12 +13619,12 @@ function mapSdkMessage(message) {
|
|
|
13455
13619
|
const attempt = message.attempt;
|
|
13456
13620
|
const delay = message.retry_delay_ms;
|
|
13457
13621
|
if (typeof attempt === "number" && typeof delay === "number") {
|
|
13458
|
-
|
|
13622
|
+
events.push({ type: "retry", attempt, delay });
|
|
13459
13623
|
}
|
|
13460
13624
|
}
|
|
13461
|
-
return
|
|
13625
|
+
return events;
|
|
13462
13626
|
}
|
|
13463
|
-
return
|
|
13627
|
+
return events;
|
|
13464
13628
|
}
|
|
13465
13629
|
var ClaudeCodeDriver = class {
|
|
13466
13630
|
name = "claude-code";
|
|
@@ -13521,8 +13685,7 @@ var ClaudeCodeDriver = class {
|
|
|
13521
13685
|
yieldedResult = true;
|
|
13522
13686
|
continue;
|
|
13523
13687
|
}
|
|
13524
|
-
const streamEvent
|
|
13525
|
-
if (streamEvent) {
|
|
13688
|
+
for (const streamEvent of mapSdkMessages(msg)) {
|
|
13526
13689
|
yield streamEvent;
|
|
13527
13690
|
}
|
|
13528
13691
|
}
|
|
@@ -14055,7 +14218,7 @@ function registerDriversCommand(program) {
|
|
|
14055
14218
|
}
|
|
14056
14219
|
|
|
14057
14220
|
// src/index.ts
|
|
14058
|
-
var VERSION = true ? "0.
|
|
14221
|
+
var VERSION = true ? "0.30.0" : "0.0.0-dev";
|
|
14059
14222
|
function createProgram() {
|
|
14060
14223
|
const program = new Command();
|
|
14061
14224
|
program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");
|