@coresource/hz 0.20.0 → 0.20.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/hz.mjs +709 -151
- package/package.json +2 -2
package/dist/hz.mjs
CHANGED
|
@@ -867,10 +867,9 @@ function registerMissionListCommand(mission, context, dependencies = {}) {
|
|
|
867
867
|
}
|
|
868
868
|
|
|
869
869
|
// src/commands/mission-lifecycle.ts
|
|
870
|
-
import {
|
|
871
|
-
import path3 from "path";
|
|
870
|
+
import { confirm as defaultConfirmPrompt } from "@inquirer/prompts";
|
|
872
871
|
import { CommanderError } from "commander";
|
|
873
|
-
import
|
|
872
|
+
import pc6 from "picocolors";
|
|
874
873
|
|
|
875
874
|
// src/monitor.ts
|
|
876
875
|
import { setTimeout as delay2 } from "timers/promises";
|
|
@@ -2705,13 +2704,99 @@ async function monitorMission(options) {
|
|
|
2705
2704
|
}
|
|
2706
2705
|
}
|
|
2707
2706
|
|
|
2708
|
-
// src/commands/mission-
|
|
2707
|
+
// src/commands/mission-id.ts
|
|
2708
|
+
import { readdir as readdir2, readFile as readFile3, stat } from "fs/promises";
|
|
2709
|
+
import path3 from "path";
|
|
2710
|
+
import pc5 from "picocolors";
|
|
2709
2711
|
function asNonEmptyString5(value) {
|
|
2710
2712
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
2711
2713
|
}
|
|
2714
|
+
function resolveMissionCommandHomeDir(context) {
|
|
2715
|
+
return resolveHomeDir({
|
|
2716
|
+
env: context.env,
|
|
2717
|
+
homeDir: context.homeDir
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
async function findMostRecentMissionId(homeDir) {
|
|
2721
|
+
const launchesDir = path3.join(homeDir, ".hz", "cloud-launches");
|
|
2722
|
+
let entries;
|
|
2723
|
+
try {
|
|
2724
|
+
entries = await readdir2(launchesDir, { withFileTypes: true });
|
|
2725
|
+
} catch (error) {
|
|
2726
|
+
if (error.code === "ENOENT") {
|
|
2727
|
+
return null;
|
|
2728
|
+
}
|
|
2729
|
+
throw error;
|
|
2730
|
+
}
|
|
2731
|
+
const candidates = await Promise.all(
|
|
2732
|
+
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
2733
|
+
const missionPath = path3.join(launchesDir, entry.name, "mission.json");
|
|
2734
|
+
try {
|
|
2735
|
+
const [raw, missionStats] = await Promise.all([
|
|
2736
|
+
readFile3(missionPath, "utf8"),
|
|
2737
|
+
stat(missionPath)
|
|
2738
|
+
]);
|
|
2739
|
+
const parsed = JSON.parse(raw);
|
|
2740
|
+
const missionId = asNonEmptyString5(parsed.missionId);
|
|
2741
|
+
if (!missionId) {
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2744
|
+
return {
|
|
2745
|
+
missionId,
|
|
2746
|
+
updatedAtMs: missionStats.mtimeMs
|
|
2747
|
+
};
|
|
2748
|
+
} catch (error) {
|
|
2749
|
+
if (error instanceof SyntaxError || error.code === "ENOENT") {
|
|
2750
|
+
return null;
|
|
2751
|
+
}
|
|
2752
|
+
throw error;
|
|
2753
|
+
}
|
|
2754
|
+
})
|
|
2755
|
+
);
|
|
2756
|
+
const latest = candidates.filter((candidate) => candidate !== null).sort(
|
|
2757
|
+
(left, right) => right.updatedAtMs - left.updatedAtMs || right.missionId.localeCompare(left.missionId)
|
|
2758
|
+
)[0];
|
|
2759
|
+
return latest?.missionId ?? null;
|
|
2760
|
+
}
|
|
2761
|
+
async function resolveMissionId(missionId, context, command, homeDir) {
|
|
2762
|
+
const explicitMissionId = asNonEmptyString5(missionId);
|
|
2763
|
+
if (explicitMissionId) {
|
|
2764
|
+
return explicitMissionId;
|
|
2765
|
+
}
|
|
2766
|
+
const detectedMissionId = await findMostRecentMissionId(homeDir);
|
|
2767
|
+
if (detectedMissionId) {
|
|
2768
|
+
writeLine(
|
|
2769
|
+
context.stdout,
|
|
2770
|
+
`Using the most recent local mission: ${pc5.cyan(detectedMissionId)}`
|
|
2771
|
+
);
|
|
2772
|
+
return detectedMissionId;
|
|
2773
|
+
}
|
|
2774
|
+
writeLine(
|
|
2775
|
+
context.stderr,
|
|
2776
|
+
"Mission ID is required when no recent local mission state is available."
|
|
2777
|
+
);
|
|
2778
|
+
command.help({ error: true });
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// src/commands/mission-lifecycle.ts
|
|
2782
|
+
function isRecord5(value) {
|
|
2783
|
+
return value !== null && !Array.isArray(value) && typeof value === "object";
|
|
2784
|
+
}
|
|
2785
|
+
function asNonEmptyString6(value) {
|
|
2786
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
2787
|
+
}
|
|
2788
|
+
function asFiniteNumber4(value) {
|
|
2789
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
2790
|
+
}
|
|
2791
|
+
async function defaultPromptConfirm(options) {
|
|
2792
|
+
return defaultConfirmPrompt({
|
|
2793
|
+
default: options.default,
|
|
2794
|
+
message: options.message
|
|
2795
|
+
});
|
|
2796
|
+
}
|
|
2712
2797
|
function writeSection(stdout, title) {
|
|
2713
2798
|
writeLine(stdout);
|
|
2714
|
-
writeLine(stdout,
|
|
2799
|
+
writeLine(stdout, pc6.bold(title));
|
|
2715
2800
|
}
|
|
2716
2801
|
function summarizeAssertions(assertions) {
|
|
2717
2802
|
return assertions.reduce(
|
|
@@ -2800,72 +2885,6 @@ async function createMissionClientContext(context, command, dependencies, homeDi
|
|
|
2800
2885
|
homeDir
|
|
2801
2886
|
};
|
|
2802
2887
|
}
|
|
2803
|
-
function resolveLifecycleHomeDir(context) {
|
|
2804
|
-
return resolveHomeDir({
|
|
2805
|
-
env: context.env,
|
|
2806
|
-
homeDir: context.homeDir
|
|
2807
|
-
});
|
|
2808
|
-
}
|
|
2809
|
-
async function findMostRecentMissionId(homeDir) {
|
|
2810
|
-
const launchesDir = path3.join(homeDir, ".hz", "cloud-launches");
|
|
2811
|
-
let entries;
|
|
2812
|
-
try {
|
|
2813
|
-
entries = await readdir2(launchesDir, { withFileTypes: true });
|
|
2814
|
-
} catch (error) {
|
|
2815
|
-
if (error.code === "ENOENT") {
|
|
2816
|
-
return null;
|
|
2817
|
-
}
|
|
2818
|
-
throw error;
|
|
2819
|
-
}
|
|
2820
|
-
const candidates = await Promise.all(
|
|
2821
|
-
entries.filter((entry) => entry.isDirectory()).map(async (entry) => {
|
|
2822
|
-
const missionPath = path3.join(launchesDir, entry.name, "mission.json");
|
|
2823
|
-
try {
|
|
2824
|
-
const [raw, missionStats] = await Promise.all([
|
|
2825
|
-
readFile3(missionPath, "utf8"),
|
|
2826
|
-
stat(missionPath)
|
|
2827
|
-
]);
|
|
2828
|
-
const parsed = JSON.parse(raw);
|
|
2829
|
-
const missionId = asNonEmptyString5(parsed.missionId);
|
|
2830
|
-
if (!missionId) {
|
|
2831
|
-
return null;
|
|
2832
|
-
}
|
|
2833
|
-
return {
|
|
2834
|
-
missionId,
|
|
2835
|
-
updatedAtMs: missionStats.mtimeMs
|
|
2836
|
-
};
|
|
2837
|
-
} catch (error) {
|
|
2838
|
-
if (error instanceof SyntaxError || error.code === "ENOENT") {
|
|
2839
|
-
return null;
|
|
2840
|
-
}
|
|
2841
|
-
throw error;
|
|
2842
|
-
}
|
|
2843
|
-
})
|
|
2844
|
-
);
|
|
2845
|
-
const latest = candidates.filter((candidate) => candidate !== null).sort(
|
|
2846
|
-
(left, right) => right.updatedAtMs - left.updatedAtMs || right.missionId.localeCompare(left.missionId)
|
|
2847
|
-
)[0];
|
|
2848
|
-
return latest?.missionId ?? null;
|
|
2849
|
-
}
|
|
2850
|
-
async function resolveMissionId(missionId, context, command, homeDir) {
|
|
2851
|
-
const explicitMissionId = asNonEmptyString5(missionId);
|
|
2852
|
-
if (explicitMissionId) {
|
|
2853
|
-
return explicitMissionId;
|
|
2854
|
-
}
|
|
2855
|
-
const detectedMissionId = await findMostRecentMissionId(homeDir);
|
|
2856
|
-
if (detectedMissionId) {
|
|
2857
|
-
writeLine(
|
|
2858
|
-
context.stdout,
|
|
2859
|
-
`Using the most recent local mission: ${pc5.cyan(detectedMissionId)}`
|
|
2860
|
-
);
|
|
2861
|
-
return detectedMissionId;
|
|
2862
|
-
}
|
|
2863
|
-
writeLine(
|
|
2864
|
-
context.stderr,
|
|
2865
|
-
"Mission ID is required when no recent local mission state is available."
|
|
2866
|
-
);
|
|
2867
|
-
command.help({ error: true });
|
|
2868
|
-
}
|
|
2869
2888
|
async function fetchMissionStateAfterConflict(apiClient, missionId, error) {
|
|
2870
2889
|
try {
|
|
2871
2890
|
return await fetchMissionStateRecord(apiClient, missionId);
|
|
@@ -2880,8 +2899,147 @@ async function fetchMissionSnapshotAfterConflict(apiClient, missionId, error) {
|
|
|
2880
2899
|
throw error;
|
|
2881
2900
|
}
|
|
2882
2901
|
}
|
|
2902
|
+
function normalizeMissionDeleteStep(value) {
|
|
2903
|
+
if (!isRecord5(value)) {
|
|
2904
|
+
return {
|
|
2905
|
+
detail: null,
|
|
2906
|
+
launchId: null,
|
|
2907
|
+
objectsDeleted: null,
|
|
2908
|
+
rowsDeleted: null,
|
|
2909
|
+
status: "unknown",
|
|
2910
|
+
tables: {}
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
const rawTables = isRecord5(value.tables) ? value.tables : {};
|
|
2914
|
+
const tables = {};
|
|
2915
|
+
for (const [tableName, count] of Object.entries(rawTables)) {
|
|
2916
|
+
const normalizedCount = asFiniteNumber4(count);
|
|
2917
|
+
if (normalizedCount !== null) {
|
|
2918
|
+
tables[tableName] = normalizedCount;
|
|
2919
|
+
}
|
|
2920
|
+
}
|
|
2921
|
+
return {
|
|
2922
|
+
detail: asNonEmptyString6(value.detail),
|
|
2923
|
+
launchId: asNonEmptyString6(value.launch_id),
|
|
2924
|
+
objectsDeleted: asFiniteNumber4(value.objects_deleted),
|
|
2925
|
+
rowsDeleted: asFiniteNumber4(value.rows_deleted),
|
|
2926
|
+
status: asNonEmptyString6(value.status) ?? "unknown",
|
|
2927
|
+
tables
|
|
2928
|
+
};
|
|
2929
|
+
}
|
|
2930
|
+
function normalizeMissionDeleteResponse(value) {
|
|
2931
|
+
if (!isRecord5(value)) {
|
|
2932
|
+
throw new CliError("Mission delete response was not an object.");
|
|
2933
|
+
}
|
|
2934
|
+
const missionId = asNonEmptyString6(value.mission_id);
|
|
2935
|
+
if (!missionId) {
|
|
2936
|
+
throw new CliError("Mission delete response is missing a mission_id.");
|
|
2937
|
+
}
|
|
2938
|
+
const rawSteps = isRecord5(value.steps) ? value.steps : {};
|
|
2939
|
+
const steps = {};
|
|
2940
|
+
for (const [stepName, stepValue] of Object.entries(rawSteps)) {
|
|
2941
|
+
steps[stepName] = normalizeMissionDeleteStep(stepValue);
|
|
2942
|
+
}
|
|
2943
|
+
return {
|
|
2944
|
+
deleted: value.deleted === true,
|
|
2945
|
+
missionId,
|
|
2946
|
+
steps
|
|
2947
|
+
};
|
|
2948
|
+
}
|
|
2949
|
+
function formatMissionDeleteStatus(status) {
|
|
2950
|
+
switch (status) {
|
|
2951
|
+
case "success":
|
|
2952
|
+
return pc6.green(status);
|
|
2953
|
+
case "failed":
|
|
2954
|
+
return pc6.red(status);
|
|
2955
|
+
case "skipped":
|
|
2956
|
+
return pc6.yellow(status);
|
|
2957
|
+
default:
|
|
2958
|
+
return status;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
function formatMissionDeleteStepSummary(step) {
|
|
2962
|
+
const parts = [];
|
|
2963
|
+
if (step.detail) {
|
|
2964
|
+
parts.push(step.detail);
|
|
2965
|
+
}
|
|
2966
|
+
if (step.rowsDeleted !== null) {
|
|
2967
|
+
parts.push(`rows deleted: ${step.rowsDeleted}`);
|
|
2968
|
+
}
|
|
2969
|
+
if (step.objectsDeleted !== null) {
|
|
2970
|
+
parts.push(`objects deleted: ${step.objectsDeleted}`);
|
|
2971
|
+
}
|
|
2972
|
+
const tableEntries = Object.entries(step.tables).sort(
|
|
2973
|
+
([left], [right]) => left.localeCompare(right)
|
|
2974
|
+
);
|
|
2975
|
+
if (tableEntries.length > 0) {
|
|
2976
|
+
parts.push(
|
|
2977
|
+
`tables: ${tableEntries.map(([tableName, count]) => `${tableName}=${count}`).join(", ")}`
|
|
2978
|
+
);
|
|
2979
|
+
}
|
|
2980
|
+
if (step.launchId) {
|
|
2981
|
+
parts.push(`launch id: ${step.launchId}`);
|
|
2982
|
+
}
|
|
2983
|
+
return parts.join(" \xB7 ") || "\u2014";
|
|
2984
|
+
}
|
|
2985
|
+
function sortMissionDeleteSteps(steps) {
|
|
2986
|
+
const orderedSteps = [
|
|
2987
|
+
"cancel",
|
|
2988
|
+
"revoke_key",
|
|
2989
|
+
"fleet_db",
|
|
2990
|
+
"missions_db",
|
|
2991
|
+
"r2_objects",
|
|
2992
|
+
"kv_entries",
|
|
2993
|
+
"planner_session",
|
|
2994
|
+
"orphaned_launch",
|
|
2995
|
+
"do_storage"
|
|
2996
|
+
];
|
|
2997
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2998
|
+
const result = [];
|
|
2999
|
+
for (const stepName of orderedSteps) {
|
|
3000
|
+
const step = steps[stepName];
|
|
3001
|
+
if (!step) {
|
|
3002
|
+
continue;
|
|
3003
|
+
}
|
|
3004
|
+
seen.add(stepName);
|
|
3005
|
+
result.push([stepName, step]);
|
|
3006
|
+
}
|
|
3007
|
+
for (const [stepName, step] of Object.entries(steps).sort(
|
|
3008
|
+
([left], [right]) => left.localeCompare(right)
|
|
3009
|
+
)) {
|
|
3010
|
+
if (seen.has(stepName)) {
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
result.push([stepName, step]);
|
|
3014
|
+
}
|
|
3015
|
+
return result;
|
|
3016
|
+
}
|
|
3017
|
+
function renderMissionDeleteResponse(stdout, response) {
|
|
3018
|
+
writeLine(stdout, `Mission ${response.missionId} deleted successfully.`);
|
|
3019
|
+
const failedSteps = Object.entries(response.steps).filter(
|
|
3020
|
+
([, step]) => step.status === "failed"
|
|
3021
|
+
);
|
|
3022
|
+
if (failedSteps.length > 0) {
|
|
3023
|
+
writeLine(
|
|
3024
|
+
stdout,
|
|
3025
|
+
pc6.yellow(
|
|
3026
|
+
`Warning: Mission cleanup completed with cleanup warnings (${failedSteps.length} failed step${failedSteps.length === 1 ? "" : "s"}).`
|
|
3027
|
+
)
|
|
3028
|
+
);
|
|
3029
|
+
}
|
|
3030
|
+
writeSection(stdout, "Cleanup steps");
|
|
3031
|
+
renderTable(
|
|
3032
|
+
stdout,
|
|
3033
|
+
["Step", "Status", "Result"],
|
|
3034
|
+
sortMissionDeleteSteps(response.steps).map(([stepName, step]) => [
|
|
3035
|
+
plainCell(stepName),
|
|
3036
|
+
coloredCell(formatMissionDeleteStatus(step.status), step.status),
|
|
3037
|
+
plainCell(formatMissionDeleteStepSummary(step))
|
|
3038
|
+
])
|
|
3039
|
+
);
|
|
3040
|
+
}
|
|
2883
3041
|
async function runLifecycleMutation(action, missionIdValue, command, context, dependencies) {
|
|
2884
|
-
const homeDir =
|
|
3042
|
+
const homeDir = resolveMissionCommandHomeDir(context);
|
|
2885
3043
|
const missionId = await resolveMissionId(missionIdValue, context, command, homeDir);
|
|
2886
3044
|
const { apiClient } = await createMissionClientContext(
|
|
2887
3045
|
context,
|
|
@@ -2895,17 +3053,17 @@ async function runLifecycleMutation(action, missionIdValue, command, context, de
|
|
|
2895
3053
|
method: "POST",
|
|
2896
3054
|
path: `/missions/${missionId}/mission/${action}`
|
|
2897
3055
|
});
|
|
2898
|
-
const nextState =
|
|
3056
|
+
const nextState = asNonEmptyString6(response.state) ?? (action === "pause" ? "paused" : "cancelled");
|
|
2899
3057
|
if (action === "pause") {
|
|
2900
3058
|
writeLine(
|
|
2901
3059
|
context.stdout,
|
|
2902
|
-
`Mission ${
|
|
3060
|
+
`Mission ${pc6.cyan(missionId)} paused. Current state: ${pc6.bold(nextState)}.`
|
|
2903
3061
|
);
|
|
2904
3062
|
return;
|
|
2905
3063
|
}
|
|
2906
3064
|
writeLine(
|
|
2907
3065
|
context.stdout,
|
|
2908
|
-
`Mission ${
|
|
3066
|
+
`Mission ${pc6.cyan(missionId)} cancelled. This action is irreversible. Current state: ${pc6.bold(nextState)}.`
|
|
2909
3067
|
);
|
|
2910
3068
|
} catch (error) {
|
|
2911
3069
|
if (!(error instanceof ApiError) || error.status !== 409) {
|
|
@@ -2919,11 +3077,51 @@ async function runLifecycleMutation(action, missionIdValue, command, context, de
|
|
|
2919
3077
|
const verb = action === "pause" ? "paused" : "cancelled";
|
|
2920
3078
|
writeLine(
|
|
2921
3079
|
context.stdout,
|
|
2922
|
-
`Mission ${
|
|
3080
|
+
`Mission ${pc6.cyan(missionId)} cannot be ${verb} because it is currently ${pc6.bold(missionState.state)}.`
|
|
2923
3081
|
);
|
|
2924
3082
|
throw new CommanderError(1, `mission-${action}`, "");
|
|
2925
3083
|
}
|
|
2926
3084
|
}
|
|
3085
|
+
function buildDeleteConfirmationMessage(missionId, forceDelete) {
|
|
3086
|
+
const lines = [
|
|
3087
|
+
`Are you sure you want to delete mission ${missionId}? This cannot be undone.`
|
|
3088
|
+
];
|
|
3089
|
+
if (forceDelete) {
|
|
3090
|
+
lines.push("This will cancel the running mission and delete all resources.");
|
|
3091
|
+
}
|
|
3092
|
+
return lines.join("\n");
|
|
3093
|
+
}
|
|
3094
|
+
async function runMissionDelete(missionIdValue, options, command, context, dependencies) {
|
|
3095
|
+
const homeDir = resolveMissionCommandHomeDir(context);
|
|
3096
|
+
const missionId = await resolveMissionId(missionIdValue, context, command, homeDir);
|
|
3097
|
+
if (options.yes !== true) {
|
|
3098
|
+
const confirmed = await (dependencies.promptConfirm ?? defaultPromptConfirm)({
|
|
3099
|
+
default: false,
|
|
3100
|
+
message: buildDeleteConfirmationMessage(missionId, options.force === true)
|
|
3101
|
+
});
|
|
3102
|
+
if (!confirmed) {
|
|
3103
|
+
writeLine(context.stdout, pc6.yellow("Mission deletion cancelled."));
|
|
3104
|
+
return;
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
const { apiClient } = await createMissionClientContext(
|
|
3108
|
+
context,
|
|
3109
|
+
command,
|
|
3110
|
+
dependencies,
|
|
3111
|
+
homeDir
|
|
3112
|
+
);
|
|
3113
|
+
const query = options.force === true ? "?force=true" : "";
|
|
3114
|
+
const response = normalizeMissionDeleteResponse(
|
|
3115
|
+
await apiClient.request({
|
|
3116
|
+
method: "DELETE",
|
|
3117
|
+
path: `/missions/${missionId}${query}`
|
|
3118
|
+
})
|
|
3119
|
+
);
|
|
3120
|
+
if (!response.deleted) {
|
|
3121
|
+
throw new CliError(`Mission ${missionId} was not deleted.`, 1);
|
|
3122
|
+
}
|
|
3123
|
+
renderMissionDeleteResponse(context.stdout, response);
|
|
3124
|
+
}
|
|
2927
3125
|
function resolveResumeExitCode(state) {
|
|
2928
3126
|
switch (state) {
|
|
2929
3127
|
case "cancelled":
|
|
@@ -2948,7 +3146,7 @@ function resolveWatchExitCode(state) {
|
|
|
2948
3146
|
}
|
|
2949
3147
|
}
|
|
2950
3148
|
async function runMissionResume(missionIdValue, command, context, dependencies) {
|
|
2951
|
-
const homeDir =
|
|
3149
|
+
const homeDir = resolveMissionCommandHomeDir(context);
|
|
2952
3150
|
const missionId = await resolveMissionId(missionIdValue, context, command, homeDir);
|
|
2953
3151
|
const { apiClient, authConfig } = await createMissionClientContext(
|
|
2954
3152
|
context,
|
|
@@ -2964,10 +3162,10 @@ async function runMissionResume(missionIdValue, command, context, dependencies)
|
|
|
2964
3162
|
method: "POST",
|
|
2965
3163
|
path: `/missions/${missionId}/mission/resume`
|
|
2966
3164
|
});
|
|
2967
|
-
const nextState =
|
|
3165
|
+
const nextState = asNonEmptyString6(response.state) ?? "orchestrator_turn";
|
|
2968
3166
|
writeLine(
|
|
2969
3167
|
context.stdout,
|
|
2970
|
-
`Mission ${
|
|
3168
|
+
`Mission ${pc6.cyan(missionId)} resumed. Current state: ${pc6.bold(nextState)}.`
|
|
2971
3169
|
);
|
|
2972
3170
|
} catch (error) {
|
|
2973
3171
|
if (!(error instanceof ApiError) || error.status !== 409) {
|
|
@@ -2980,14 +3178,14 @@ async function runMissionResume(missionIdValue, command, context, dependencies)
|
|
|
2980
3178
|
);
|
|
2981
3179
|
writeLine(
|
|
2982
3180
|
context.stdout,
|
|
2983
|
-
`Mission ${
|
|
3181
|
+
`Mission ${pc6.cyan(missionId)} is currently ${pc6.bold(snapshot.state.state)}. Reconnecting to live updates if available.`
|
|
2984
3182
|
);
|
|
2985
3183
|
}
|
|
2986
3184
|
snapshot = await fetchMissionSnapshot(apiClient, missionId);
|
|
2987
3185
|
} else if (snapshot.state.state !== "cancelled" && snapshot.state.state !== "completed") {
|
|
2988
3186
|
writeLine(
|
|
2989
3187
|
context.stdout,
|
|
2990
|
-
`Reconnecting to mission ${
|
|
3188
|
+
`Reconnecting to mission ${pc6.cyan(missionId)} from state ${pc6.bold(snapshot.state.state)}.`
|
|
2991
3189
|
);
|
|
2992
3190
|
}
|
|
2993
3191
|
renderMissionSnapshot(context.stdout, snapshot);
|
|
@@ -3019,7 +3217,7 @@ async function runMissionResume(missionIdValue, command, context, dependencies)
|
|
|
3019
3217
|
}
|
|
3020
3218
|
}
|
|
3021
3219
|
async function runMissionWatch(missionIdValue, command, context, dependencies) {
|
|
3022
|
-
const homeDir =
|
|
3220
|
+
const homeDir = resolveMissionCommandHomeDir(context);
|
|
3023
3221
|
const missionId = await resolveMissionId(missionIdValue, context, command, homeDir);
|
|
3024
3222
|
const { apiClient, authConfig } = await createMissionClientContext(
|
|
3025
3223
|
context,
|
|
@@ -3030,7 +3228,7 @@ async function runMissionWatch(missionIdValue, command, context, dependencies) {
|
|
|
3030
3228
|
const snapshot = await fetchMissionSnapshot(apiClient, missionId);
|
|
3031
3229
|
writeLine(
|
|
3032
3230
|
context.stdout,
|
|
3033
|
-
`Watching mission ${
|
|
3231
|
+
`Watching mission ${pc6.cyan(missionId)} from state ${pc6.bold(snapshot.state.state)}.`
|
|
3034
3232
|
);
|
|
3035
3233
|
renderMissionSnapshot(context.stdout, snapshot);
|
|
3036
3234
|
const watchExitCode = resolveWatchExitCode(snapshot.state.state);
|
|
@@ -3085,6 +3283,365 @@ function registerMissionLifecycleCommands(mission, context, dependencies = {}) {
|
|
|
3085
3283
|
dependencies
|
|
3086
3284
|
);
|
|
3087
3285
|
});
|
|
3286
|
+
mission.command("delete").description("Delete a mission and clean up its resources").argument("[missionId]").option("--force", "Force-cancel active missions before deletion").option("--yes", "Skip the confirmation prompt").action(async (missionId, options, command) => {
|
|
3287
|
+
await runMissionDelete(
|
|
3288
|
+
missionId,
|
|
3289
|
+
options,
|
|
3290
|
+
command,
|
|
3291
|
+
context,
|
|
3292
|
+
dependencies
|
|
3293
|
+
);
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
// src/commands/mission-resources.ts
|
|
3298
|
+
import pc7 from "picocolors";
|
|
3299
|
+
function isRecord6(value) {
|
|
3300
|
+
return value !== null && !Array.isArray(value) && typeof value === "object";
|
|
3301
|
+
}
|
|
3302
|
+
function asBoolean(value) {
|
|
3303
|
+
return value === true;
|
|
3304
|
+
}
|
|
3305
|
+
function asFiniteNumber5(value) {
|
|
3306
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
3307
|
+
}
|
|
3308
|
+
function asNullableNumber3(value) {
|
|
3309
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
3310
|
+
}
|
|
3311
|
+
function asNonEmptyString7(value) {
|
|
3312
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3313
|
+
}
|
|
3314
|
+
function formatInteger(value) {
|
|
3315
|
+
return new Intl.NumberFormat("en-US", {
|
|
3316
|
+
maximumFractionDigits: 0
|
|
3317
|
+
}).format(value);
|
|
3318
|
+
}
|
|
3319
|
+
function formatDecimal(value, maximumFractionDigits = 2) {
|
|
3320
|
+
return new Intl.NumberFormat("en-US", {
|
|
3321
|
+
maximumFractionDigits,
|
|
3322
|
+
minimumFractionDigits: 0
|
|
3323
|
+
}).format(value);
|
|
3324
|
+
}
|
|
3325
|
+
function formatPercentValue(value) {
|
|
3326
|
+
return `${formatDecimal(value, 2)}%`;
|
|
3327
|
+
}
|
|
3328
|
+
function formatBoolean(value) {
|
|
3329
|
+
return value ? "yes" : "no";
|
|
3330
|
+
}
|
|
3331
|
+
function formatCents(value) {
|
|
3332
|
+
return formatCurrency(value / 100);
|
|
3333
|
+
}
|
|
3334
|
+
function formatBytes(value) {
|
|
3335
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
3336
|
+
let size = value;
|
|
3337
|
+
let unitIndex = 0;
|
|
3338
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
3339
|
+
size /= 1024;
|
|
3340
|
+
unitIndex += 1;
|
|
3341
|
+
}
|
|
3342
|
+
const formatted = unitIndex === 0 ? formatInteger(size) : formatDecimal(size, 2);
|
|
3343
|
+
return `${formatted} ${units[unitIndex]}`;
|
|
3344
|
+
}
|
|
3345
|
+
function writeSection2(stdout, title) {
|
|
3346
|
+
writeLine(stdout);
|
|
3347
|
+
writeLine(stdout, pc7.bold(title));
|
|
3348
|
+
}
|
|
3349
|
+
function renderMetricTable(stdout, rows) {
|
|
3350
|
+
renderTable(
|
|
3351
|
+
stdout,
|
|
3352
|
+
["Metric", "Value"],
|
|
3353
|
+
rows.map(([label, value]) => [plainCell(label), plainCell(value)])
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
function renderOptionalTable(stdout, title, headers, rows, emptyMessage) {
|
|
3357
|
+
writeLine(stdout);
|
|
3358
|
+
writeLine(stdout, title);
|
|
3359
|
+
if (rows.length === 0) {
|
|
3360
|
+
writeLine(stdout, emptyMessage);
|
|
3361
|
+
return;
|
|
3362
|
+
}
|
|
3363
|
+
renderTable(
|
|
3364
|
+
stdout,
|
|
3365
|
+
headers,
|
|
3366
|
+
rows.map((row) => row.map((value) => plainCell(value)))
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
function normalizeInferenceByModel(value) {
|
|
3370
|
+
if (!Array.isArray(value)) {
|
|
3371
|
+
return [];
|
|
3372
|
+
}
|
|
3373
|
+
return value.flatMap((entry) => {
|
|
3374
|
+
if (!isRecord6(entry)) {
|
|
3375
|
+
return [];
|
|
3376
|
+
}
|
|
3377
|
+
const model = asNonEmptyString7(entry.model);
|
|
3378
|
+
if (!model) {
|
|
3379
|
+
return [];
|
|
3380
|
+
}
|
|
3381
|
+
return [{
|
|
3382
|
+
costUsd: asFiniteNumber5(entry.cost_usd),
|
|
3383
|
+
model,
|
|
3384
|
+
requests: asFiniteNumber5(entry.requests),
|
|
3385
|
+
standardTokens: asFiniteNumber5(entry.standard_tokens),
|
|
3386
|
+
tokens: asFiniteNumber5(entry.tokens)
|
|
3387
|
+
}];
|
|
3388
|
+
});
|
|
3389
|
+
}
|
|
3390
|
+
function normalizeInferenceByFeature(value) {
|
|
3391
|
+
if (!Array.isArray(value)) {
|
|
3392
|
+
return [];
|
|
3393
|
+
}
|
|
3394
|
+
return value.flatMap((entry) => {
|
|
3395
|
+
if (!isRecord6(entry)) {
|
|
3396
|
+
return [];
|
|
3397
|
+
}
|
|
3398
|
+
const featureId = asNonEmptyString7(entry.feature_id);
|
|
3399
|
+
if (!featureId) {
|
|
3400
|
+
return [];
|
|
3401
|
+
}
|
|
3402
|
+
return [{
|
|
3403
|
+
costUsd: asFiniteNumber5(entry.cost_usd),
|
|
3404
|
+
featureId,
|
|
3405
|
+
requests: asFiniteNumber5(entry.requests),
|
|
3406
|
+
standardTokens: asFiniteNumber5(entry.standard_tokens),
|
|
3407
|
+
tokens: asFiniteNumber5(entry.tokens)
|
|
3408
|
+
}];
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
function normalizeSandboxByClass(value) {
|
|
3412
|
+
if (!Array.isArray(value)) {
|
|
3413
|
+
return [];
|
|
3414
|
+
}
|
|
3415
|
+
return value.flatMap((entry) => {
|
|
3416
|
+
if (!isRecord6(entry)) {
|
|
3417
|
+
return [];
|
|
3418
|
+
}
|
|
3419
|
+
const instanceClass = asNonEmptyString7(entry.instance_class);
|
|
3420
|
+
if (!instanceClass) {
|
|
3421
|
+
return [];
|
|
3422
|
+
}
|
|
3423
|
+
return [{
|
|
3424
|
+
containers: asFiniteNumber5(entry.containers),
|
|
3425
|
+
costCents: asFiniteNumber5(entry.cost_cents),
|
|
3426
|
+
instanceClass,
|
|
3427
|
+
minutes: asFiniteNumber5(entry.minutes)
|
|
3428
|
+
}];
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
function normalizeSandboxContainers(value) {
|
|
3432
|
+
if (!Array.isArray(value)) {
|
|
3433
|
+
return [];
|
|
3434
|
+
}
|
|
3435
|
+
return value.flatMap((entry) => {
|
|
3436
|
+
if (!isRecord6(entry)) {
|
|
3437
|
+
return [];
|
|
3438
|
+
}
|
|
3439
|
+
const containerName = asNonEmptyString7(entry.container_name);
|
|
3440
|
+
if (!containerName) {
|
|
3441
|
+
return [];
|
|
3442
|
+
}
|
|
3443
|
+
return [{
|
|
3444
|
+
containerName,
|
|
3445
|
+
createdAt: asNonEmptyString7(entry.created_at),
|
|
3446
|
+
endedAt: asNonEmptyString7(entry.ended_at),
|
|
3447
|
+
featureId: asNonEmptyString7(entry.feature_id),
|
|
3448
|
+
instanceClass: asNonEmptyString7(entry.instance_class) ?? "unknown",
|
|
3449
|
+
startedAt: asNonEmptyString7(entry.started_at),
|
|
3450
|
+
status: asNonEmptyString7(entry.status) ?? "unknown",
|
|
3451
|
+
workerSessionId: asNonEmptyString7(entry.worker_session_id)
|
|
3452
|
+
}];
|
|
3453
|
+
});
|
|
3454
|
+
}
|
|
3455
|
+
function normalizeStorageD1Rows(value) {
|
|
3456
|
+
if (!isRecord6(value)) {
|
|
3457
|
+
return {};
|
|
3458
|
+
}
|
|
3459
|
+
const normalized = {};
|
|
3460
|
+
for (const [key, count] of Object.entries(value)) {
|
|
3461
|
+
normalized[key] = asFiniteNumber5(count);
|
|
3462
|
+
}
|
|
3463
|
+
return normalized;
|
|
3464
|
+
}
|
|
3465
|
+
function normalizeMissionResourceSummary(value) {
|
|
3466
|
+
if (!isRecord6(value)) {
|
|
3467
|
+
throw new Error("Mission resources response was not an object.");
|
|
3468
|
+
}
|
|
3469
|
+
const missionId = asNonEmptyString7(value.mission_id);
|
|
3470
|
+
if (!missionId) {
|
|
3471
|
+
throw new Error("Mission resources response is missing a mission_id.");
|
|
3472
|
+
}
|
|
3473
|
+
const inference = isRecord6(value.inference) ? value.inference : {};
|
|
3474
|
+
const sandboxCompute = isRecord6(value.sandbox_compute) ? value.sandbox_compute : {};
|
|
3475
|
+
const storage = isRecord6(value.storage) ? value.storage : {};
|
|
3476
|
+
const storageR2 = isRecord6(storage.r2) ? storage.r2 : {};
|
|
3477
|
+
const storageDo = isRecord6(storage.do_storage) ? storage.do_storage : {};
|
|
3478
|
+
const budget = isRecord6(value.budget) ? value.budget : {};
|
|
3479
|
+
return {
|
|
3480
|
+
budget: {
|
|
3481
|
+
exceeded: asBoolean(budget.exceeded),
|
|
3482
|
+
maxInferenceCostUsd: asNullableNumber3(budget.max_inference_cost_usd),
|
|
3483
|
+
maxInferenceTokens: asNullableNumber3(budget.max_inference_tokens),
|
|
3484
|
+
percentUsed: asFiniteNumber5(budget.percent_used),
|
|
3485
|
+
warning: asBoolean(budget.warning)
|
|
3486
|
+
},
|
|
3487
|
+
inference: {
|
|
3488
|
+
byFeature: normalizeInferenceByFeature(inference.by_feature),
|
|
3489
|
+
byModel: normalizeInferenceByModel(inference.by_model),
|
|
3490
|
+
totalCostUsd: asFiniteNumber5(inference.total_cost_usd),
|
|
3491
|
+
totalRequests: asFiniteNumber5(inference.total_requests),
|
|
3492
|
+
totalStandardTokens: asFiniteNumber5(inference.total_standard_tokens),
|
|
3493
|
+
totalTokens: asFiniteNumber5(inference.total_tokens)
|
|
3494
|
+
},
|
|
3495
|
+
missionId,
|
|
3496
|
+
sandboxCompute: {
|
|
3497
|
+
byClass: normalizeSandboxByClass(sandboxCompute.by_class),
|
|
3498
|
+
containers: normalizeSandboxContainers(sandboxCompute.containers),
|
|
3499
|
+
totalContainers: asFiniteNumber5(sandboxCompute.total_containers),
|
|
3500
|
+
totalCostCents: asFiniteNumber5(sandboxCompute.total_cost_cents),
|
|
3501
|
+
totalMinutes: asFiniteNumber5(sandboxCompute.total_minutes)
|
|
3502
|
+
},
|
|
3503
|
+
snapshotAt: asNonEmptyString7(value.snapshot_at),
|
|
3504
|
+
storage: {
|
|
3505
|
+
d1Rows: normalizeStorageD1Rows(storage.d1_rows),
|
|
3506
|
+
doStorage: {
|
|
3507
|
+
progressEventCount: asFiniteNumber5(storageDo.progress_event_count)
|
|
3508
|
+
},
|
|
3509
|
+
r2: {
|
|
3510
|
+
objectCount: asFiniteNumber5(storageR2.object_count),
|
|
3511
|
+
totalBytes: asFiniteNumber5(storageR2.total_bytes)
|
|
3512
|
+
}
|
|
3513
|
+
},
|
|
3514
|
+
tenantId: asNonEmptyString7(value.tenant_id)
|
|
3515
|
+
};
|
|
3516
|
+
}
|
|
3517
|
+
function renderMissionResourceSummary(stdout, summary) {
|
|
3518
|
+
writeLine(stdout, `Mission ${summary.missionId}`);
|
|
3519
|
+
writeLine(stdout, `Tenant: ${summary.tenantId ?? "\u2014"}`);
|
|
3520
|
+
writeLine(stdout, `Snapshot: ${formatDateTime(summary.snapshotAt)}`);
|
|
3521
|
+
writeSection2(stdout, "Inference");
|
|
3522
|
+
renderMetricTable(stdout, [
|
|
3523
|
+
["Total tokens", formatInteger(summary.inference.totalTokens)],
|
|
3524
|
+
["Standard tokens", formatInteger(summary.inference.totalStandardTokens)],
|
|
3525
|
+
["Cost", formatCurrency(summary.inference.totalCostUsd)],
|
|
3526
|
+
["Requests", formatInteger(summary.inference.totalRequests)]
|
|
3527
|
+
]);
|
|
3528
|
+
renderOptionalTable(
|
|
3529
|
+
stdout,
|
|
3530
|
+
"By model",
|
|
3531
|
+
["Model", "Tokens", "Standard tokens", "Cost", "Requests"],
|
|
3532
|
+
summary.inference.byModel.map((entry) => [
|
|
3533
|
+
entry.model,
|
|
3534
|
+
formatInteger(entry.tokens),
|
|
3535
|
+
formatInteger(entry.standardTokens),
|
|
3536
|
+
formatCurrency(entry.costUsd),
|
|
3537
|
+
formatInteger(entry.requests)
|
|
3538
|
+
]),
|
|
3539
|
+
"No per-model inference usage recorded."
|
|
3540
|
+
);
|
|
3541
|
+
renderOptionalTable(
|
|
3542
|
+
stdout,
|
|
3543
|
+
"By feature",
|
|
3544
|
+
["Feature", "Tokens", "Standard tokens", "Cost", "Requests"],
|
|
3545
|
+
summary.inference.byFeature.map((entry) => [
|
|
3546
|
+
entry.featureId,
|
|
3547
|
+
formatInteger(entry.tokens),
|
|
3548
|
+
formatInteger(entry.standardTokens),
|
|
3549
|
+
formatCurrency(entry.costUsd),
|
|
3550
|
+
formatInteger(entry.requests)
|
|
3551
|
+
]),
|
|
3552
|
+
"No per-feature inference usage recorded."
|
|
3553
|
+
);
|
|
3554
|
+
writeSection2(stdout, "Sandbox compute");
|
|
3555
|
+
renderMetricTable(stdout, [
|
|
3556
|
+
["Total containers", formatInteger(summary.sandboxCompute.totalContainers)],
|
|
3557
|
+
["Total minutes", formatInteger(summary.sandboxCompute.totalMinutes)],
|
|
3558
|
+
["Cost", formatCents(summary.sandboxCompute.totalCostCents)]
|
|
3559
|
+
]);
|
|
3560
|
+
renderOptionalTable(
|
|
3561
|
+
stdout,
|
|
3562
|
+
"Per class",
|
|
3563
|
+
["Instance class", "Containers", "Minutes", "Cost"],
|
|
3564
|
+
summary.sandboxCompute.byClass.map((entry) => [
|
|
3565
|
+
entry.instanceClass,
|
|
3566
|
+
formatInteger(entry.containers),
|
|
3567
|
+
formatInteger(entry.minutes),
|
|
3568
|
+
formatCents(entry.costCents)
|
|
3569
|
+
]),
|
|
3570
|
+
"No sandbox class breakdown recorded."
|
|
3571
|
+
);
|
|
3572
|
+
renderOptionalTable(
|
|
3573
|
+
stdout,
|
|
3574
|
+
"Containers",
|
|
3575
|
+
[
|
|
3576
|
+
"Container",
|
|
3577
|
+
"Feature",
|
|
3578
|
+
"Worker",
|
|
3579
|
+
"Class",
|
|
3580
|
+
"Status",
|
|
3581
|
+
"Created",
|
|
3582
|
+
"Started",
|
|
3583
|
+
"Ended"
|
|
3584
|
+
],
|
|
3585
|
+
summary.sandboxCompute.containers.map((entry) => [
|
|
3586
|
+
entry.containerName,
|
|
3587
|
+
entry.featureId ?? "\u2014",
|
|
3588
|
+
entry.workerSessionId ?? "\u2014",
|
|
3589
|
+
entry.instanceClass,
|
|
3590
|
+
entry.status,
|
|
3591
|
+
formatDateTime(entry.createdAt),
|
|
3592
|
+
formatDateTime(entry.startedAt),
|
|
3593
|
+
formatDateTime(entry.endedAt)
|
|
3594
|
+
]),
|
|
3595
|
+
"No sandbox containers recorded."
|
|
3596
|
+
);
|
|
3597
|
+
writeSection2(stdout, "Storage");
|
|
3598
|
+
renderMetricTable(stdout, [
|
|
3599
|
+
["R2 objects", formatInteger(summary.storage.r2.objectCount)],
|
|
3600
|
+
["R2 bytes", formatBytes(summary.storage.r2.totalBytes)],
|
|
3601
|
+
[
|
|
3602
|
+
"Progress events",
|
|
3603
|
+
formatInteger(summary.storage.doStorage.progressEventCount)
|
|
3604
|
+
]
|
|
3605
|
+
]);
|
|
3606
|
+
renderOptionalTable(
|
|
3607
|
+
stdout,
|
|
3608
|
+
"D1 rows",
|
|
3609
|
+
["Table", "Rows"],
|
|
3610
|
+
Object.entries(summary.storage.d1Rows).sort(([left], [right]) => left.localeCompare(right)).map(([tableName, count]) => [tableName, formatInteger(count)]),
|
|
3611
|
+
"No mission D1 rows recorded."
|
|
3612
|
+
);
|
|
3613
|
+
writeSection2(stdout, "Budget");
|
|
3614
|
+
renderMetricTable(stdout, [
|
|
3615
|
+
[
|
|
3616
|
+
"Max inference cost",
|
|
3617
|
+
summary.budget.maxInferenceCostUsd === null ? "\u2014" : formatCurrency(summary.budget.maxInferenceCostUsd)
|
|
3618
|
+
],
|
|
3619
|
+
[
|
|
3620
|
+
"Max inference tokens",
|
|
3621
|
+
summary.budget.maxInferenceTokens === null ? "\u2014" : formatInteger(summary.budget.maxInferenceTokens)
|
|
3622
|
+
],
|
|
3623
|
+
["Percent used", formatPercentValue(summary.budget.percentUsed)],
|
|
3624
|
+
["Exceeded", formatBoolean(summary.budget.exceeded)],
|
|
3625
|
+
["Warning", formatBoolean(summary.budget.warning)]
|
|
3626
|
+
]);
|
|
3627
|
+
}
|
|
3628
|
+
function registerMissionResourcesCommand(mission, context, dependencies = {}) {
|
|
3629
|
+
mission.command("resources").description("Show resource usage for a mission").argument("[missionId]").action(async (missionIdValue, _options, command) => {
|
|
3630
|
+
const homeDir = resolveMissionCommandHomeDir(context);
|
|
3631
|
+
const missionId = await resolveMissionId(
|
|
3632
|
+
missionIdValue,
|
|
3633
|
+
context,
|
|
3634
|
+
command,
|
|
3635
|
+
homeDir
|
|
3636
|
+
);
|
|
3637
|
+
const apiClient = await createMissionApiClient(context, command, dependencies);
|
|
3638
|
+
const summary = normalizeMissionResourceSummary(
|
|
3639
|
+
await apiClient.request({
|
|
3640
|
+
path: `/missions/${missionId}/resources`
|
|
3641
|
+
})
|
|
3642
|
+
);
|
|
3643
|
+
renderMissionResourceSummary(context.stdout, summary);
|
|
3644
|
+
});
|
|
3088
3645
|
}
|
|
3089
3646
|
|
|
3090
3647
|
// src/commands/mission-run.ts
|
|
@@ -3095,54 +3652,54 @@ import { CommanderError as CommanderError2 } from "commander";
|
|
|
3095
3652
|
// src/planning.ts
|
|
3096
3653
|
import path4 from "path";
|
|
3097
3654
|
import {
|
|
3098
|
-
confirm as
|
|
3655
|
+
confirm as defaultConfirmPrompt2,
|
|
3099
3656
|
input as defaultInputPrompt2,
|
|
3100
3657
|
select as defaultSelectPrompt2
|
|
3101
3658
|
} from "@inquirer/prompts";
|
|
3102
3659
|
import ora2 from "ora";
|
|
3103
|
-
import
|
|
3660
|
+
import pc8 from "picocolors";
|
|
3104
3661
|
var DEFAULT_ANALYSIS_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
3105
3662
|
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
3106
3663
|
var FREE_TEXT_OPTION_ID = "free_text";
|
|
3107
|
-
function
|
|
3664
|
+
function isRecord7(value) {
|
|
3108
3665
|
return value !== null && !Array.isArray(value) && typeof value === "object";
|
|
3109
3666
|
}
|
|
3110
|
-
function
|
|
3667
|
+
function asNonEmptyString8(value) {
|
|
3111
3668
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
3112
3669
|
}
|
|
3113
3670
|
function asStringArray2(value) {
|
|
3114
|
-
return Array.isArray(value) ? value.map((item) =>
|
|
3671
|
+
return Array.isArray(value) ? value.map((item) => asNonEmptyString8(item)).filter((item) => item !== null) : [];
|
|
3115
3672
|
}
|
|
3116
3673
|
function extractQuestions(payload) {
|
|
3117
3674
|
if (!payload || !Array.isArray(payload.questions)) {
|
|
3118
3675
|
return [];
|
|
3119
3676
|
}
|
|
3120
|
-
return payload.questions.filter((question) =>
|
|
3121
|
-
detailPrompt:
|
|
3122
|
-
freeTextOptionId:
|
|
3123
|
-
id:
|
|
3124
|
-
inputDefault:
|
|
3125
|
-
options: Array.isArray(question.options) ? question.options.filter((option) =>
|
|
3126
|
-
description:
|
|
3127
|
-
id:
|
|
3128
|
-
label:
|
|
3677
|
+
return payload.questions.filter((question) => isRecord7(question)).map((question) => ({
|
|
3678
|
+
detailPrompt: asNonEmptyString8(question.detailPrompt) ?? void 0,
|
|
3679
|
+
freeTextOptionId: asNonEmptyString8(question.freeTextOptionId) ?? void 0,
|
|
3680
|
+
id: asNonEmptyString8(question.id) ?? "question",
|
|
3681
|
+
inputDefault: asNonEmptyString8(question.inputDefault) ?? void 0,
|
|
3682
|
+
options: Array.isArray(question.options) ? question.options.filter((option) => isRecord7(option)).map((option) => ({
|
|
3683
|
+
description: asNonEmptyString8(option.description) ?? void 0,
|
|
3684
|
+
id: asNonEmptyString8(option.id) ?? "option",
|
|
3685
|
+
label: asNonEmptyString8(option.label) ?? "Option"
|
|
3129
3686
|
})) : [],
|
|
3130
3687
|
references: asStringArray2(question.references),
|
|
3131
|
-
text:
|
|
3688
|
+
text: asNonEmptyString8(question.text) ?? "Clarification required."
|
|
3132
3689
|
}));
|
|
3133
3690
|
}
|
|
3134
3691
|
function extractMilestones(payload) {
|
|
3135
3692
|
if (!payload || !Array.isArray(payload.milestones)) {
|
|
3136
3693
|
return [];
|
|
3137
3694
|
}
|
|
3138
|
-
return payload.milestones.filter((milestone) =>
|
|
3139
|
-
description:
|
|
3140
|
-
name:
|
|
3695
|
+
return payload.milestones.filter((milestone) => isRecord7(milestone)).map((milestone) => ({
|
|
3696
|
+
description: asNonEmptyString8(milestone.description) ?? void 0,
|
|
3697
|
+
name: asNonEmptyString8(milestone.name) ?? "milestone",
|
|
3141
3698
|
testableOutcomes: asStringArray2(milestone.testableOutcomes)
|
|
3142
3699
|
}));
|
|
3143
3700
|
}
|
|
3144
3701
|
function looksLikeMissionDraft(value) {
|
|
3145
|
-
return
|
|
3702
|
+
return isRecord7(value) && typeof value.taskDescription === "string" && Array.isArray(value.milestones) && Array.isArray(value.features) && Array.isArray(value.assertions);
|
|
3146
3703
|
}
|
|
3147
3704
|
function extractDraft(payload) {
|
|
3148
3705
|
if (!payload) {
|
|
@@ -3161,7 +3718,7 @@ function countItems(payload, key) {
|
|
|
3161
3718
|
return Array.isArray(value) ? value.length : 0;
|
|
3162
3719
|
}
|
|
3163
3720
|
function formatBudgetSummary(draft) {
|
|
3164
|
-
if (!
|
|
3721
|
+
if (!isRecord7(draft.config) || !isRecord7(draft.config.budget)) {
|
|
3165
3722
|
return "unknown";
|
|
3166
3723
|
}
|
|
3167
3724
|
const budget = draft.config.budget;
|
|
@@ -3175,13 +3732,13 @@ function formatBudgetSummary(draft) {
|
|
|
3175
3732
|
}
|
|
3176
3733
|
}
|
|
3177
3734
|
function extractFailures(error) {
|
|
3178
|
-
return error instanceof ApiError &&
|
|
3735
|
+
return error instanceof ApiError && isRecord7(error.payload) ? asStringArray2(error.payload.failures) : [];
|
|
3179
3736
|
}
|
|
3180
3737
|
function extractErrorCode(error) {
|
|
3181
|
-
if (!(error instanceof ApiError) || !
|
|
3738
|
+
if (!(error instanceof ApiError) || !isRecord7(error.payload)) {
|
|
3182
3739
|
return null;
|
|
3183
3740
|
}
|
|
3184
|
-
return
|
|
3741
|
+
return asNonEmptyString8(error.payload.error);
|
|
3185
3742
|
}
|
|
3186
3743
|
function extractRound(payload) {
|
|
3187
3744
|
return typeof payload?.round === "number" && Number.isFinite(payload.round) ? payload.round : 1;
|
|
@@ -3234,8 +3791,8 @@ async function defaultPromptInput2(options) {
|
|
|
3234
3791
|
message: options.message
|
|
3235
3792
|
});
|
|
3236
3793
|
}
|
|
3237
|
-
async function
|
|
3238
|
-
return
|
|
3794
|
+
async function defaultPromptConfirm2(options) {
|
|
3795
|
+
return defaultConfirmPrompt2({
|
|
3239
3796
|
default: options.default,
|
|
3240
3797
|
message: options.message
|
|
3241
3798
|
});
|
|
@@ -3265,20 +3822,20 @@ async function postDraftApproval(client, sessionId) {
|
|
|
3265
3822
|
path: `/plan/${sessionId}/approve`
|
|
3266
3823
|
});
|
|
3267
3824
|
}
|
|
3268
|
-
function
|
|
3825
|
+
function writeSection3(stdout, title) {
|
|
3269
3826
|
writeLine(stdout);
|
|
3270
|
-
writeLine(stdout,
|
|
3827
|
+
writeLine(stdout, pc8.bold(title));
|
|
3271
3828
|
}
|
|
3272
3829
|
function renderQuestion(stdout, question, index) {
|
|
3273
3830
|
writeLine(stdout, `${index + 1}. ${question.text}`);
|
|
3274
3831
|
if (question.references && question.references.length > 0) {
|
|
3275
|
-
writeLine(stdout, ` ${
|
|
3832
|
+
writeLine(stdout, ` ${pc8.dim(`References: ${question.references.join(", ")}`)}`);
|
|
3276
3833
|
}
|
|
3277
3834
|
}
|
|
3278
3835
|
function renderMilestones(stdout, milestones, reviewRound) {
|
|
3279
|
-
|
|
3836
|
+
writeSection3(stdout, `Milestone review round ${reviewRound}`);
|
|
3280
3837
|
milestones.forEach((milestone, index) => {
|
|
3281
|
-
writeLine(stdout, `${index + 1}. ${
|
|
3838
|
+
writeLine(stdout, `${index + 1}. ${pc8.cyan(milestone.name)}`);
|
|
3282
3839
|
if (milestone.description) {
|
|
3283
3840
|
writeLine(stdout, ` ${milestone.description}`);
|
|
3284
3841
|
}
|
|
@@ -3289,14 +3846,14 @@ function renderMilestones(stdout, milestones, reviewRound) {
|
|
|
3289
3846
|
});
|
|
3290
3847
|
}
|
|
3291
3848
|
function renderDraftSummary(stdout, payload, draft) {
|
|
3292
|
-
|
|
3849
|
+
writeSection3(stdout, "Draft summary");
|
|
3293
3850
|
writeLine(stdout, `Milestones: ${countItems(payload, "milestones") || countItems(draft, "milestones")}`);
|
|
3294
3851
|
writeLine(stdout, `Features: ${countItems(payload, "features") || countItems(draft, "features")}`);
|
|
3295
3852
|
writeLine(stdout, `Assertions: ${countItems(payload, "assertions") || countItems(draft, "assertions")}`);
|
|
3296
3853
|
writeLine(stdout, `Budget: ${formatBudgetSummary(draft)}`);
|
|
3297
3854
|
}
|
|
3298
3855
|
function renderFailures(stdout, failures) {
|
|
3299
|
-
|
|
3856
|
+
writeSection3(stdout, "Unresolved clarification items");
|
|
3300
3857
|
failures.forEach((failure) => {
|
|
3301
3858
|
writeLine(stdout, `- ${failure}`);
|
|
3302
3859
|
});
|
|
@@ -3467,7 +4024,7 @@ async function resolveClarification(options, sessionId, initialPayload) {
|
|
|
3467
4024
|
if (payload.state !== "clarifying") {
|
|
3468
4025
|
break;
|
|
3469
4026
|
}
|
|
3470
|
-
|
|
4027
|
+
writeSection3(options.stdout, `Clarification round ${round}`);
|
|
3471
4028
|
questions.forEach((question, index) => renderQuestion(options.stdout, question, index));
|
|
3472
4029
|
const prompted = await promptForAnswers(options, questions, round);
|
|
3473
4030
|
localTranscript.push(...prompted.transcript);
|
|
@@ -3488,7 +4045,7 @@ async function resolveClarification(options, sessionId, initialPayload) {
|
|
|
3488
4045
|
}
|
|
3489
4046
|
writeLine(
|
|
3490
4047
|
options.stdout,
|
|
3491
|
-
|
|
4048
|
+
pc8.yellow("Proceeding because --force is enabled.")
|
|
3492
4049
|
);
|
|
3493
4050
|
payload = await postMilestoneConfirmation(options.client, sessionId, {});
|
|
3494
4051
|
return {
|
|
@@ -3529,7 +4086,7 @@ async function resolveMilestones(options, sessionId, initialPayload) {
|
|
|
3529
4086
|
while (true) {
|
|
3530
4087
|
const milestones = extractMilestones(payload);
|
|
3531
4088
|
renderMilestones(options.stdout, milestones, reviewRound);
|
|
3532
|
-
const confirmed = options.autoApprove ? true : await (options.promptConfirm ??
|
|
4089
|
+
const confirmed = options.autoApprove ? true : await (options.promptConfirm ?? defaultPromptConfirm2)({
|
|
3533
4090
|
default: true,
|
|
3534
4091
|
message: "Do these milestones look correct?"
|
|
3535
4092
|
});
|
|
@@ -3570,7 +4127,7 @@ async function runPlanningFlow(options) {
|
|
|
3570
4127
|
if (options.existingMissionDraft) {
|
|
3571
4128
|
writeLine(
|
|
3572
4129
|
options.stdout,
|
|
3573
|
-
|
|
4130
|
+
pc8.cyan("Skipping planning because mission-draft.json already exists.")
|
|
3574
4131
|
);
|
|
3575
4132
|
return {
|
|
3576
4133
|
cancelled: false,
|
|
@@ -3579,7 +4136,7 @@ async function runPlanningFlow(options) {
|
|
|
3579
4136
|
skippedPlanning: true
|
|
3580
4137
|
};
|
|
3581
4138
|
}
|
|
3582
|
-
const sessionId =
|
|
4139
|
+
const sessionId = asNonEmptyString8(options.existingSessionId) ?? asNonEmptyString8(
|
|
3583
4140
|
(await options.client.request({
|
|
3584
4141
|
body: {
|
|
3585
4142
|
repos: options.repoPaths,
|
|
@@ -3621,12 +4178,12 @@ async function runPlanningFlow(options) {
|
|
|
3621
4178
|
payload
|
|
3622
4179
|
);
|
|
3623
4180
|
renderDraftSummary(options.stdout, summaryPayload, draft);
|
|
3624
|
-
const approved = options.autoApprove ? true : await (options.promptConfirm ??
|
|
4181
|
+
const approved = options.autoApprove ? true : await (options.promptConfirm ?? defaultPromptConfirm2)({
|
|
3625
4182
|
default: true,
|
|
3626
4183
|
message: "Approve this draft and continue to upload?"
|
|
3627
4184
|
});
|
|
3628
4185
|
if (!approved) {
|
|
3629
|
-
writeLine(options.stdout,
|
|
4186
|
+
writeLine(options.stdout, pc8.yellow("Planning cancelled before upload."));
|
|
3630
4187
|
return {
|
|
3631
4188
|
cancelled: true,
|
|
3632
4189
|
draft,
|
|
@@ -3635,7 +4192,7 @@ async function runPlanningFlow(options) {
|
|
|
3635
4192
|
};
|
|
3636
4193
|
}
|
|
3637
4194
|
await options.persistMissionDraft(draft);
|
|
3638
|
-
writeLine(options.stdout,
|
|
4195
|
+
writeLine(options.stdout, pc8.green("Saved approved mission draft."));
|
|
3639
4196
|
return {
|
|
3640
4197
|
cancelled: false,
|
|
3641
4198
|
draft,
|
|
@@ -3650,7 +4207,7 @@ import { createReadStream as createReadStream2 } from "fs";
|
|
|
3650
4207
|
import path6 from "path";
|
|
3651
4208
|
import { Transform as Transform2 } from "stream";
|
|
3652
4209
|
import ora3 from "ora";
|
|
3653
|
-
import
|
|
4210
|
+
import pc9 from "picocolors";
|
|
3654
4211
|
|
|
3655
4212
|
// src/snapshot.ts
|
|
3656
4213
|
import { execFile } from "child_process";
|
|
@@ -4198,13 +4755,13 @@ function defaultCreateSpinner3(text) {
|
|
|
4198
4755
|
spinner.start();
|
|
4199
4756
|
return spinner;
|
|
4200
4757
|
}
|
|
4201
|
-
async function
|
|
4758
|
+
async function defaultPromptConfirm3(options) {
|
|
4202
4759
|
return confirm({
|
|
4203
4760
|
default: options.default,
|
|
4204
4761
|
message: options.message
|
|
4205
4762
|
});
|
|
4206
4763
|
}
|
|
4207
|
-
function
|
|
4764
|
+
function formatBytes2(bytes) {
|
|
4208
4765
|
if (!Number.isFinite(bytes) || bytes < 1024) {
|
|
4209
4766
|
return `${bytes} B`;
|
|
4210
4767
|
}
|
|
@@ -4226,7 +4783,7 @@ function formatDuration(milliseconds) {
|
|
|
4226
4783
|
function formatRate(bytesTransferred, elapsedMs) {
|
|
4227
4784
|
const safeElapsedMs = Math.max(elapsedMs, 1);
|
|
4228
4785
|
const bytesPerSecond = bytesTransferred * 1e3 / safeElapsedMs;
|
|
4229
|
-
return `${
|
|
4786
|
+
return `${formatBytes2(Math.max(1, Math.round(bytesPerSecond)))}/s`;
|
|
4230
4787
|
}
|
|
4231
4788
|
function formatFinding(finding) {
|
|
4232
4789
|
const location = finding.line === null ? finding.filePath : `${finding.filePath}:${finding.line}`;
|
|
@@ -4247,19 +4804,19 @@ function isTerminalUploadStatus2(status) {
|
|
|
4247
4804
|
return status === "uploaded" || status === "blob_exists";
|
|
4248
4805
|
}
|
|
4249
4806
|
function renderSafetyGate(stdout, repos, existingBlobs) {
|
|
4250
|
-
writeLine(stdout,
|
|
4807
|
+
writeLine(stdout, pc9.bold("Upload safety check"));
|
|
4251
4808
|
writeLine(stdout, "Repos queued for upload:");
|
|
4252
4809
|
for (const repo of repos) {
|
|
4253
|
-
const cleanliness = repo.manifest.dirty ?
|
|
4254
|
-
const secretSummary = repo.secretFindings.length > 0 ?
|
|
4255
|
-
const dedupSummary = existingBlobs.has(repo.manifest.archiveSha256) ?
|
|
4810
|
+
const cleanliness = repo.manifest.dirty ? pc9.yellow("dirty") : pc9.green("clean");
|
|
4811
|
+
const secretSummary = repo.secretFindings.length > 0 ? pc9.red(`${repo.secretFindings.length} finding(s)`) : pc9.green("no findings");
|
|
4812
|
+
const dedupSummary = existingBlobs.has(repo.manifest.archiveSha256) ? pc9.cyan("blob already exists remotely") : "new upload";
|
|
4256
4813
|
writeLine(
|
|
4257
4814
|
stdout,
|
|
4258
|
-
`- ${
|
|
4815
|
+
`- ${pc9.cyan(repo.manifest.repoId)} \u2014 ${repo.manifest.localPath}`
|
|
4259
4816
|
);
|
|
4260
4817
|
writeLine(
|
|
4261
4818
|
stdout,
|
|
4262
|
-
` Status: ${cleanliness}; archive ${
|
|
4819
|
+
` Status: ${cleanliness}; archive ${formatBytes2(repo.manifest.archiveBytes)}; ${dedupSummary}; secret scan ${secretSummary}`
|
|
4263
4820
|
);
|
|
4264
4821
|
if (repo.warnings.length > 0) {
|
|
4265
4822
|
for (const warning of repo.warnings) {
|
|
@@ -4316,7 +4873,7 @@ async function uploadArchive(options, repo, uploadUrl) {
|
|
|
4316
4873
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
4317
4874
|
const createSpinner = options.createSpinner ?? defaultCreateSpinner3;
|
|
4318
4875
|
const spinner = createSpinner(
|
|
4319
|
-
`Uploading ${repo.manifest.repoId} 0 B / ${
|
|
4876
|
+
`Uploading ${repo.manifest.repoId} 0 B / ${formatBytes2(repo.manifest.archiveBytes)} (0 B/s)`
|
|
4320
4877
|
);
|
|
4321
4878
|
spinner.start(spinner.text);
|
|
4322
4879
|
const startedAt = Date.now();
|
|
@@ -4325,7 +4882,7 @@ async function uploadArchive(options, repo, uploadUrl) {
|
|
|
4325
4882
|
transform(chunk, _encoding, callback) {
|
|
4326
4883
|
bytesTransferred += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
|
|
4327
4884
|
const elapsedMs2 = Date.now() - startedAt;
|
|
4328
|
-
spinner.text = `Uploading ${repo.manifest.repoId} ${
|
|
4885
|
+
spinner.text = `Uploading ${repo.manifest.repoId} ${formatBytes2(bytesTransferred)} / ${formatBytes2(repo.manifest.archiveBytes)} (${formatRate(bytesTransferred, elapsedMs2)})`;
|
|
4329
4886
|
callback(null, chunk);
|
|
4330
4887
|
}
|
|
4331
4888
|
});
|
|
@@ -4356,7 +4913,7 @@ async function uploadArchive(options, repo, uploadUrl) {
|
|
|
4356
4913
|
}
|
|
4357
4914
|
const elapsedMs = Date.now() - startedAt;
|
|
4358
4915
|
spinner.succeed(
|
|
4359
|
-
`Uploaded ${repo.manifest.repoId} ${
|
|
4916
|
+
`Uploaded ${repo.manifest.repoId} ${formatBytes2(bytesTransferred)} in ${formatDuration(elapsedMs)} (${formatRate(bytesTransferred, elapsedMs)})`
|
|
4360
4917
|
);
|
|
4361
4918
|
}
|
|
4362
4919
|
async function processRepoUpload(options, repo, remoteLaunchId) {
|
|
@@ -4399,7 +4956,7 @@ async function processRepoUpload(options, repo, remoteLaunchId) {
|
|
|
4399
4956
|
);
|
|
4400
4957
|
writeLine(
|
|
4401
4958
|
options.stdout,
|
|
4402
|
-
|
|
4959
|
+
pc9.cyan(`Skipping upload for ${repo.manifest.repoId}; blob already exists remotely.`)
|
|
4403
4960
|
);
|
|
4404
4961
|
return;
|
|
4405
4962
|
}
|
|
@@ -4436,7 +4993,7 @@ async function processRepoUpload(options, repo, remoteLaunchId) {
|
|
|
4436
4993
|
);
|
|
4437
4994
|
}
|
|
4438
4995
|
async function runUploadPipeline(options) {
|
|
4439
|
-
const promptConfirm = options.promptConfirm ??
|
|
4996
|
+
const promptConfirm = options.promptConfirm ?? defaultPromptConfirm3;
|
|
4440
4997
|
const launchSnapshot = await loadLaunchSnapshot(options.launchId, {
|
|
4441
4998
|
cwd: options.cwd,
|
|
4442
4999
|
homeDir: options.homeDir
|
|
@@ -4493,7 +5050,7 @@ async function runUploadPipeline(options) {
|
|
|
4493
5050
|
})) {
|
|
4494
5051
|
writeLine(
|
|
4495
5052
|
options.stdout,
|
|
4496
|
-
|
|
5053
|
+
pc9.yellow("Upload cancelled before creating a remote launch.")
|
|
4497
5054
|
);
|
|
4498
5055
|
return {
|
|
4499
5056
|
finalized: false,
|
|
@@ -4510,7 +5067,7 @@ async function runUploadPipeline(options) {
|
|
|
4510
5067
|
if (existingBlobs.size > 0) {
|
|
4511
5068
|
writeLine(
|
|
4512
5069
|
options.stdout,
|
|
4513
|
-
|
|
5070
|
+
pc9.cyan(
|
|
4514
5071
|
`Remote dedup will reuse ${existingBlobs.size} existing blob${existingBlobs.size === 1 ? "" : "s"}.`
|
|
4515
5072
|
)
|
|
4516
5073
|
);
|
|
@@ -4537,7 +5094,7 @@ async function runUploadPipeline(options) {
|
|
|
4537
5094
|
if (isTerminalUploadStatus2(uploadStates.get(repo.manifest.repoId))) {
|
|
4538
5095
|
writeLine(
|
|
4539
5096
|
options.stdout,
|
|
4540
|
-
|
|
5097
|
+
pc9.cyan(`Skipping ${repo.manifest.repoId}; already completed in upload-state.json.`)
|
|
4541
5098
|
);
|
|
4542
5099
|
continue;
|
|
4543
5100
|
}
|
|
@@ -4555,7 +5112,7 @@ async function runUploadPipeline(options) {
|
|
|
4555
5112
|
failures.push(repo.manifest.repoId);
|
|
4556
5113
|
writeLine(
|
|
4557
5114
|
options.stdout,
|
|
4558
|
-
|
|
5115
|
+
pc9.red(
|
|
4559
5116
|
error instanceof Error ? error.message : `Upload failed for ${repo.manifest.repoId}.`
|
|
4560
5117
|
)
|
|
4561
5118
|
);
|
|
@@ -4577,7 +5134,7 @@ async function runUploadPipeline(options) {
|
|
|
4577
5134
|
}
|
|
4578
5135
|
writeLine(
|
|
4579
5136
|
options.stdout,
|
|
4580
|
-
|
|
5137
|
+
pc9.green(`Upload complete. Remote launch ${remoteLaunchId} is finalized.`)
|
|
4581
5138
|
);
|
|
4582
5139
|
return {
|
|
4583
5140
|
finalized: true,
|
|
@@ -4752,10 +5309,10 @@ function registerMissionRunCommand(mission, context, dependencies = {}) {
|
|
|
4752
5309
|
}
|
|
4753
5310
|
|
|
4754
5311
|
// src/commands/mission-status.ts
|
|
4755
|
-
import
|
|
4756
|
-
function
|
|
5312
|
+
import pc10 from "picocolors";
|
|
5313
|
+
function writeSection4(stdout, title) {
|
|
4757
5314
|
writeLine(stdout);
|
|
4758
|
-
writeLine(stdout,
|
|
5315
|
+
writeLine(stdout, pc10.bold(title));
|
|
4759
5316
|
}
|
|
4760
5317
|
function normalizeMilestoneOrder(milestones, features, assertions) {
|
|
4761
5318
|
const known = new Map(milestones.map((milestone) => [milestone.name, milestone]));
|
|
@@ -4828,7 +5385,7 @@ function renderMissionOverview(stdout, missionState) {
|
|
|
4828
5385
|
);
|
|
4829
5386
|
}
|
|
4830
5387
|
function renderActiveWorker(stdout, missionState) {
|
|
4831
|
-
|
|
5388
|
+
writeSection4(stdout, "Active worker");
|
|
4832
5389
|
writeLine(stdout, `Feature: ${missionState.currentFeatureId ?? "none"}`);
|
|
4833
5390
|
writeLine(
|
|
4834
5391
|
stdout,
|
|
@@ -4839,7 +5396,7 @@ function renderBudget(stdout, missionState) {
|
|
|
4839
5396
|
if (missionState.estimatedCostUsd === null && missionState.inferenceTokensUsed === null && missionState.sandboxMinutesUsed === null) {
|
|
4840
5397
|
return;
|
|
4841
5398
|
}
|
|
4842
|
-
|
|
5399
|
+
writeSection4(stdout, "Budget / usage");
|
|
4843
5400
|
writeLine(stdout, `Estimated cost: ${formatCurrency(missionState.estimatedCostUsd)}`);
|
|
4844
5401
|
writeLine(
|
|
4845
5402
|
stdout,
|
|
@@ -4851,11 +5408,11 @@ function renderBudget(stdout, missionState) {
|
|
|
4851
5408
|
);
|
|
4852
5409
|
}
|
|
4853
5410
|
function renderMilestones2(stdout, milestones, features) {
|
|
4854
|
-
|
|
5411
|
+
writeSection4(stdout, "Milestones");
|
|
4855
5412
|
for (const milestone of milestones) {
|
|
4856
5413
|
writeLine(
|
|
4857
5414
|
stdout,
|
|
4858
|
-
`- ${
|
|
5415
|
+
`- ${pc10.cyan(milestone.name)} (${milestone.state}) \xB7 ${milestone.completedFeatureCount}/${milestone.featureCount} features \xB7 ${milestone.passedAssertionCount}/${milestone.assertionCount} assertions`
|
|
4859
5416
|
);
|
|
4860
5417
|
const milestoneFeatures = features.filter(
|
|
4861
5418
|
(feature) => feature.milestone === milestone.name
|
|
@@ -4866,7 +5423,7 @@ function renderMilestones2(stdout, milestones, features) {
|
|
|
4866
5423
|
}
|
|
4867
5424
|
}
|
|
4868
5425
|
function renderAssertionSummary(stdout, milestones, assertions) {
|
|
4869
|
-
|
|
5426
|
+
writeSection4(stdout, "Assertion pass rates");
|
|
4870
5427
|
const overall = summarizeAssertions2(assertions);
|
|
4871
5428
|
writeLine(
|
|
4872
5429
|
stdout,
|
|
@@ -4917,6 +5474,7 @@ function registerMissionCommands(program, context, dependencies = {}) {
|
|
|
4917
5474
|
registerMissionStatusCommand(mission, context, dependencies);
|
|
4918
5475
|
registerMissionInfoCommand(mission, context, dependencies);
|
|
4919
5476
|
registerMissionListCommand(mission, context, dependencies);
|
|
5477
|
+
registerMissionResourcesCommand(mission, context, dependencies);
|
|
4920
5478
|
registerMissionLifecycleCommands(mission, context, dependencies);
|
|
4921
5479
|
}
|
|
4922
5480
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coresource/hz",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hz": "dist/hz.mjs"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsup",
|
|
16
16
|
"prepublishOnly": "npm run build",
|
|
17
|
-
"test": "vitest run",
|
|
17
|
+
"test": "vitest run --sequence.concurrent false",
|
|
18
18
|
"typecheck": "tsc --noEmit"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|