substrate-ai 0.2.21 → 0.2.24

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.
@@ -1,7 +1,5 @@
1
1
  import { exec } from "child_process";
2
2
  import { promisify } from "util";
3
- import * as readline from "node:readline";
4
- import { EventEmitter } from "node:events";
5
3
 
6
4
  //#region src/adapters/claude-adapter.ts
7
5
  const execAsync$2 = promisify(exec);
@@ -816,734 +814,114 @@ var AdapterRegistry = class {
816
814
  };
817
815
 
818
816
  //#endregion
819
- //#region src/tui/ansi.ts
817
+ //#region src/core/errors.ts
820
818
  /**
821
- * ANSI escape code helpers for TUI rendering.
822
- *
823
- * Provides cursor control, color, and screen manipulation utilities.
824
- */
825
- const ANSI = {
826
- RESET: "\x1B[0m",
827
- BOLD: "\x1B[1m",
828
- DIM: "\x1B[2m",
829
- BLACK: "\x1B[30m",
830
- RED: "\x1B[31m",
831
- GREEN: "\x1B[32m",
832
- YELLOW: "\x1B[33m",
833
- BLUE: "\x1B[34m",
834
- MAGENTA: "\x1B[35m",
835
- CYAN: "\x1B[36m",
836
- WHITE: "\x1B[37m",
837
- BRIGHT_BLACK: "\x1B[90m",
838
- BRIGHT_RED: "\x1B[91m",
839
- BRIGHT_GREEN: "\x1B[92m",
840
- BRIGHT_YELLOW: "\x1B[93m",
841
- BRIGHT_BLUE: "\x1B[94m",
842
- BRIGHT_MAGENTA: "\x1B[95m",
843
- BRIGHT_CYAN: "\x1B[96m",
844
- BRIGHT_WHITE: "\x1B[97m",
845
- BG_BLACK: "\x1B[40m",
846
- BG_WHITE: "\x1B[47m",
847
- BG_BLUE: "\x1B[44m",
848
- BG_BRIGHT_BLACK: "\x1B[100m",
849
- HIDE_CURSOR: "\x1B[?25l",
850
- SHOW_CURSOR: "\x1B[?25h",
851
- CLEAR_SCREEN: "\x1B[2J",
852
- HOME: "\x1B[H",
853
- CLEAR_LINE: "\x1B[2K",
854
- ERASE_DOWN: "\x1B[J",
855
- ALT_SCREEN_ENTER: "\x1B[?1049h",
856
- ALT_SCREEN_EXIT: "\x1B[?1049l"
857
- };
858
- /** Check if color output is supported. */
859
- function supportsColor(isTTY) {
860
- if (process.env.NO_COLOR !== void 0) return false;
861
- return isTTY;
862
- }
863
- /** Wrap text with an ANSI color code (only if color is enabled). */
864
- function colorize(text, code, useColor) {
865
- if (!useColor) return text;
866
- return `${code}${text}${ANSI.RESET}`;
867
- }
868
- /** Bold text. */
869
- function bold(text, useColor) {
870
- if (!useColor) return text;
871
- return `${ANSI.BOLD}${text}${ANSI.RESET}`;
872
- }
873
- /**
874
- * Get current terminal dimensions.
875
- * Returns { cols, rows } with fallback defaults if unavailable.
876
- */
877
- function getTerminalSize() {
878
- const cols = process.stdout.columns ?? 80;
879
- const rows = process.stdout.rows ?? 24;
880
- return {
881
- cols,
882
- rows
883
- };
884
- }
885
- /**
886
- * Truncate a string to fit within maxWidth characters.
887
- * Adds ellipsis if truncated.
888
- */
889
- function truncate(text, maxWidth) {
890
- if (maxWidth <= 0) return "";
891
- if (text.length <= maxWidth) return text;
892
- if (maxWidth <= 3) return text.slice(0, maxWidth);
893
- return text.slice(0, maxWidth - 3) + "...";
894
- }
895
- /**
896
- * Pad or truncate a string to exactly `width` characters.
897
- */
898
- function padOrTruncate(text, width, padChar = " ") {
899
- if (text.length > width) return truncate(text, width);
900
- return text.padEnd(width, padChar);
901
- }
902
-
903
- //#endregion
904
- //#region src/tui/story-panel.ts
905
- const COL_KEY_WIDTH = 12;
906
- const COL_PHASE_WIDTH = 8;
907
- const COL_STATUS_WIDTH = 30;
908
- /**
909
- * Get ANSI color code for a story status.
910
- */
911
- function statusColor(status) {
912
- switch (status) {
913
- case "pending": return ANSI.BRIGHT_BLACK;
914
- case "in_progress": return ANSI.YELLOW;
915
- case "succeeded": return ANSI.GREEN;
916
- case "failed":
917
- case "escalated": return ANSI.RED;
918
- default: return ANSI.RESET;
919
- }
920
- }
921
- /**
922
- * Get status indicator symbol for a story status.
923
- */
924
- function statusSymbol(status) {
925
- switch (status) {
926
- case "pending": return "○";
927
- case "in_progress": return "◉";
928
- case "succeeded": return "✓";
929
- case "failed":
930
- case "escalated": return "✗";
931
- default: return "·";
932
- }
933
- }
934
- /**
935
- * Render the story panel header row.
936
- */
937
- function renderStoryPanelHeader(useColor) {
938
- const key = padOrTruncate("STORY", COL_KEY_WIDTH);
939
- const phase = padOrTruncate("PHASE", COL_PHASE_WIDTH);
940
- const status = "STATUS";
941
- const header = ` ${key} ${phase} ${status}`;
942
- return bold(colorize(header, ANSI.BRIGHT_WHITE, useColor), useColor);
943
- }
944
- /**
945
- * Render a single story row.
946
- */
947
- function renderStoryRow(story, isSelected, useColor, width) {
948
- const symbol = statusSymbol(story.status);
949
- const color = statusColor(story.status);
950
- const keyCol = padOrTruncate(story.key, COL_KEY_WIDTH);
951
- const phaseCol = padOrTruncate(story.phase, COL_PHASE_WIDTH);
952
- const statusCol = padOrTruncate(story.statusLabel, COL_STATUS_WIDTH);
953
- const maxWidth = Math.max(width - 2, 10);
954
- let row = `${symbol} ${keyCol} ${phaseCol} ${statusCol}`;
955
- row = row.slice(0, maxWidth);
956
- if (useColor) row = `${color}${row}${ANSI.RESET}`;
957
- if (isSelected && useColor) row = `${ANSI.BG_BRIGHT_BLACK}${row}${ANSI.RESET}`;
958
- else if (isSelected) row = `> ${row.slice(2)}`;
959
- return row;
960
- }
961
- /**
962
- * Render the complete story panel.
963
- *
964
- * Returns an array of lines to be written to the terminal.
965
- */
966
- function renderStoryPanel(options) {
967
- const { stories, selectedIndex, useColor, width } = options;
968
- const lines = [];
969
- lines.push(bold(colorize(" Story Status", ANSI.CYAN, useColor), useColor));
970
- lines.push(renderStoryPanelHeader(useColor));
971
- lines.push(" " + "─".repeat(Math.max(width - 4, 20)));
972
- if (stories.length === 0) lines.push(colorize(" (no stories)", ANSI.BRIGHT_BLACK, useColor));
973
- else for (let i = 0; i < stories.length; i++) {
974
- const story = stories[i];
975
- if (story !== void 0) lines.push(renderStoryRow(story, i === selectedIndex, useColor, width));
976
- }
977
- return lines;
978
- }
979
-
980
- //#endregion
981
- //#region src/tui/log-panel.ts
982
- /**
983
- * Format a log entry timestamp to a short HH:MM:SS format.
984
- */
985
- function formatTimestamp(ts) {
986
- try {
987
- const date = new Date(ts);
988
- const hh = date.getHours().toString().padStart(2, "0");
989
- const mm = date.getMinutes().toString().padStart(2, "0");
990
- const ss = date.getSeconds().toString().padStart(2, "0");
991
- return `${hh}:${mm}:${ss}`;
992
- } catch {
993
- return ts.slice(11, 19);
994
- }
995
- }
996
- /**
997
- * Render a single log entry line.
819
+ * Error definitions for Substrate
820
+ * Provides structured error hierarchy for all toolkit operations
998
821
  */
999
- function renderLogEntry(entry, useColor, width) {
1000
- const ts = formatTimestamp(entry.ts);
1001
- const prefix = `[${ts}] [${entry.key}] `;
1002
- const maxMsgWidth = Math.max(width - prefix.length - 2, 10);
1003
- const msg = truncate(entry.msg, maxMsgWidth);
1004
- const line = `${prefix}${msg}`;
1005
- if (useColor) {
1006
- if (entry.level === "warn") return colorize(line, ANSI.YELLOW, useColor);
1007
- const coloredPrefix = colorize(`[${ts}] `, ANSI.BRIGHT_BLACK, useColor) + colorize(`[${entry.key}] `, ANSI.CYAN, useColor);
1008
- return `${coloredPrefix}${msg}`;
1009
- }
1010
- return line;
1011
- }
1012
- /**
1013
- * Render the complete log panel.
1014
- *
1015
- * Auto-scrolls to show the most recent entries (last `maxLines` entries).
1016
- * Returns an array of lines to be written to the terminal.
1017
- */
1018
- function renderLogPanel(options) {
1019
- const { entries, maxLines, useColor, width, filterKey } = options;
1020
- const lines = [];
1021
- const title = filterKey !== void 0 ? ` Logs for ${filterKey}` : " Live Logs";
1022
- lines.push(bold(colorize(title, ANSI.CYAN, useColor), useColor));
1023
- lines.push(" " + "─".repeat(Math.max(width - 4, 20)));
1024
- const filtered = filterKey !== void 0 ? entries.filter((e) => e.key === filterKey) : entries;
1025
- if (filtered.length === 0) {
1026
- lines.push(colorize(" (no log entries)", ANSI.BRIGHT_BLACK, useColor));
1027
- return lines;
1028
- }
1029
- const visibleEntries = filtered.slice(-maxLines);
1030
- for (const entry of visibleEntries) lines.push(" " + renderLogEntry(entry, useColor, width - 2));
1031
- return lines;
1032
- }
1033
-
1034
- //#endregion
1035
- //#region src/tui/detail-view.ts
1036
- /**
1037
- * Render the full detail view for a story.
1038
- *
1039
- * Returns an array of lines to be written to the terminal.
1040
- */
1041
- function renderDetailView(options) {
1042
- const { story, allLogs, maxLogLines, useColor, width, height } = options;
1043
- const lines = [];
1044
- const titleBar = ` Story Detail: ${story.key}`;
1045
- lines.push(bold(colorize(titleBar, ANSI.BRIGHT_WHITE, useColor), useColor));
1046
- lines.push(" " + "═".repeat(Math.max(width - 4, 20)));
1047
- lines.push("");
1048
- const phaseLabel = padOrTruncate("Phase:", 12);
1049
- const statusLabel = padOrTruncate("Status:", 12);
1050
- const cyclesLabel = padOrTruncate("Review Cycles:", 12);
1051
- lines.push(` ${bold(phaseLabel, useColor)} ${colorize(story.phase, ANSI.CYAN, useColor)}`);
1052
- lines.push(` ${bold(statusLabel, useColor)} ${colorize(story.statusLabel, ANSI.WHITE, useColor)}`);
1053
- lines.push(` ${bold(cyclesLabel, useColor)} ${story.reviewCycles}`);
1054
- if (story.escalationReason !== void 0) lines.push(` ${bold(padOrTruncate("Escalated:", 12), useColor)} ${colorize(story.escalationReason, ANSI.RED, useColor)}`);
1055
- lines.push("");
1056
- lines.push(" " + "─".repeat(Math.max(width - 4, 20)));
1057
- const availableLogLines = Math.max(height - lines.length - 4, 3);
1058
- const logLines = renderLogPanel({
1059
- entries: allLogs,
1060
- maxLines: Math.min(maxLogLines, availableLogLines),
1061
- useColor,
1062
- width,
1063
- filterKey: story.key
1064
- });
1065
- lines.push(...logLines);
1066
- lines.push("");
1067
- lines.push(colorize(" [Esc] Back to overview", ANSI.BRIGHT_BLACK, useColor));
1068
- return lines;
1069
- }
1070
-
1071
- //#endregion
1072
- //#region src/tui/help-overlay.ts
1073
- /** All keyboard bindings shown in the help overlay. */
1074
- const KEY_BINDINGS = [
1075
- {
1076
- key: "↑ / ↓",
1077
- description: "Navigate between stories"
1078
- },
1079
- {
1080
- key: "Enter",
1081
- description: "Drill into selected story detail view"
1082
- },
1083
- {
1084
- key: "Esc",
1085
- description: "Return to overview from detail view"
1086
- },
1087
- {
1088
- key: "q",
1089
- description: "Quit TUI (pipeline completes in background)"
1090
- },
1091
- {
1092
- key: "?",
1093
- description: "Show/hide this help overlay"
1094
- }
1095
- ];
1096
- /**
1097
- * Render the help overlay.
1098
- *
1099
- * Returns an array of lines to be written to the terminal.
1100
- */
1101
- function renderHelpOverlay(options) {
1102
- const { useColor, width } = options;
1103
- const lines = [];
1104
- const boxWidth = Math.min(56, Math.max(width - 4, 30));
1105
- const horizontalBorder = "─".repeat(boxWidth - 2);
1106
- lines.push(colorize(` ┌${horizontalBorder}┐`, ANSI.CYAN, useColor));
1107
- lines.push(colorize(` │${padOrTruncate(" Keyboard Shortcuts", boxWidth - 2)}│`, ANSI.CYAN, useColor));
1108
- lines.push(colorize(` ├${horizontalBorder}┤`, ANSI.CYAN, useColor));
1109
- for (const binding of KEY_BINDINGS) {
1110
- const keyPart = bold(padOrTruncate(binding.key, 12), useColor);
1111
- const descPart = padOrTruncate(binding.description, boxWidth - 16);
1112
- lines.push(useColor ? `${ANSI.CYAN} │${ANSI.RESET} ${keyPart} ${descPart}${ANSI.CYAN}│${ANSI.RESET}` : ` │ ${keyPart} ${descPart}│`);
1113
- }
1114
- lines.push(colorize(` └${horizontalBorder}┘`, ANSI.CYAN, useColor));
1115
- lines.push(colorize(" Press ? to close", ANSI.BRIGHT_BLACK, useColor));
1116
- return lines;
1117
- }
1118
-
1119
- //#endregion
1120
- //#region src/tui/app.ts
1121
- /** Minimum terminal width for TUI to render properly. */
1122
- const MIN_COLS = 80;
1123
- /** Minimum terminal height for TUI to render properly. */
1124
- const MIN_ROWS = 24;
1125
- /** Maximum log entries to keep in memory. */
1126
- const MAX_LOG_ENTRIES = 500;
1127
- function mapPhaseToLabel(phase) {
1128
- switch (phase) {
1129
- case "create-story": return "create";
1130
- case "dev-story": return "dev";
1131
- case "code-review": return "review";
1132
- case "fix": return "fix";
1133
- default: return "wait";
1134
- }
1135
- }
1136
- function mapPhaseToStatus(phase, eventStatus) {
1137
- if (eventStatus === "failed") return "failed";
1138
- if (eventStatus === "in_progress") return "in_progress";
1139
- if (eventStatus === "complete") {
1140
- if (phase === "done") return "succeeded";
1141
- return "in_progress";
1142
- }
1143
- return "pending";
1144
- }
1145
- function makeStatusLabel(phase, eventStatus, verdict) {
1146
- if (eventStatus === "failed") return "failed";
1147
- if (eventStatus === "in_progress") switch (phase) {
1148
- case "create": return "creating story...";
1149
- case "dev": return "implementing...";
1150
- case "review": return "reviewing...";
1151
- case "fix": return "fixing issues...";
1152
- default: return "in progress...";
1153
- }
1154
- if (eventStatus === "complete") switch (phase) {
1155
- case "create": return "story created";
1156
- case "dev": return "implemented";
1157
- case "review": return verdict !== void 0 ? `reviewed (${verdict})` : "reviewed";
1158
- case "fix": return "fixes applied";
1159
- default: return "complete";
1160
- }
1161
- return "queued";
1162
- }
1163
- /**
1164
- * Factory that creates a TUI application instance.
1165
- *
1166
- * @param output - Writable stream for rendering (typically process.stdout)
1167
- * @param input - Readable stream for keyboard input (typically process.stdin)
1168
- */
1169
- function createTuiApp(output, input) {
1170
- const isTTY = output.isTTY === true;
1171
- const useColor = supportsColor(isTTY);
1172
- const state = {
1173
- headerLine: "",
1174
- storyOrder: [],
1175
- stories: new Map(),
1176
- logs: [],
1177
- selectedIndex: 0,
1178
- view: "overview",
1179
- pipelineComplete: false
1180
- };
1181
- let exitResolve;
1182
- const exitPromise = new Promise((resolve) => {
1183
- exitResolve = resolve;
1184
- });
1185
- let rl;
1186
- function write(text) {
1187
- try {
1188
- output.write(text);
1189
- } catch {}
822
+ /** Base error class for all Substrate errors */
823
+ var AdtError = class AdtError extends Error {
824
+ code;
825
+ context;
826
+ constructor(message, code, context = {}) {
827
+ super(message);
828
+ this.name = "AdtError";
829
+ this.code = code;
830
+ this.context = context;
831
+ if (Error.captureStackTrace) Error.captureStackTrace(this, AdtError);
832
+ }
833
+ toJSON() {
834
+ return {
835
+ name: this.name,
836
+ message: this.message,
837
+ code: this.code,
838
+ context: this.context,
839
+ stack: this.stack
840
+ };
1190
841
  }
1191
- function checkTerminalSize() {
1192
- const { cols, rows } = getTerminalSize();
1193
- if (cols < MIN_COLS || rows < MIN_ROWS) {
1194
- write(ANSI.CLEAR_SCREEN + ANSI.HOME);
1195
- write(colorize(`Terminal too small: ${cols}x${rows} (minimum ${MIN_COLS}x${MIN_ROWS})\n`, ANSI.YELLOW, useColor));
1196
- write("Please resize your terminal.\n");
1197
- return false;
1198
- }
1199
- return true;
842
+ };
843
+ /** Error thrown when task configuration is invalid */
844
+ var TaskConfigError = class extends AdtError {
845
+ constructor(message, context = {}) {
846
+ super(message, "TASK_CONFIG_ERROR", context);
847
+ this.name = "TaskConfigError";
1200
848
  }
1201
- function render() {
1202
- const { cols, rows } = getTerminalSize();
1203
- if (cols < MIN_COLS || rows < MIN_ROWS) {
1204
- checkTerminalSize();
1205
- return;
1206
- }
1207
- write(ANSI.CLEAR_SCREEN + ANSI.HOME);
1208
- const lines = [];
1209
- if (state.view === "help") renderHelpView(lines, cols, rows);
1210
- else if (state.view === "detail") renderDetailViewLayout(lines, cols, rows);
1211
- else renderOverviewLayout(lines, cols, rows);
1212
- for (const line of lines) write(line + "\n");
849
+ };
850
+ /** Error thrown when a worker/agent operation fails */
851
+ var WorkerError = class extends AdtError {
852
+ constructor(message, context = {}) {
853
+ super(message, "WORKER_ERROR", context);
854
+ this.name = "WorkerError";
1213
855
  }
1214
- function renderOverviewLayout(lines, cols, rows) {
1215
- lines.push(bold(colorize(" substrate run --tui", ANSI.BRIGHT_WHITE, useColor), useColor));
1216
- if (state.headerLine) lines.push(colorize(` ${state.headerLine}`, ANSI.BRIGHT_BLACK, useColor));
1217
- lines.push("");
1218
- const storyPanelHeight = Math.max(Math.floor(rows * .4), 6);
1219
- const storyPanelLines = renderStoryPanel({
1220
- stories: Array.from(state.storyOrder).map((k) => state.stories.get(k)).filter((s) => s !== void 0),
1221
- selectedIndex: state.selectedIndex,
1222
- useColor,
1223
- width: cols
1224
- });
1225
- lines.push(...storyPanelLines.slice(0, storyPanelHeight));
1226
- lines.push("");
1227
- lines.push(" " + "─".repeat(Math.max(cols - 4, 20)));
1228
- lines.push("");
1229
- const usedLines = lines.length + 3;
1230
- const logPanelHeight = Math.max(rows - usedLines, 3);
1231
- const logLines = renderLogPanel({
1232
- entries: state.logs,
1233
- maxLines: logPanelHeight,
1234
- useColor,
1235
- width: cols
1236
- });
1237
- lines.push(...logLines);
1238
- lines.push("");
1239
- const footerParts = [
1240
- colorize("[↑↓] Navigate", ANSI.BRIGHT_BLACK, useColor),
1241
- colorize("[Enter] Details", ANSI.BRIGHT_BLACK, useColor),
1242
- colorize("[q] Quit", ANSI.BRIGHT_BLACK, useColor),
1243
- colorize("[?] Help", ANSI.BRIGHT_BLACK, useColor)
1244
- ];
1245
- if (state.pipelineComplete) lines.push(colorize(" Pipeline complete. Press q to exit.", ANSI.GREEN, useColor));
1246
- lines.push(" " + footerParts.join(" "));
856
+ };
857
+ /** Error thrown when a worker/agent cannot be found */
858
+ var WorkerNotFoundError = class extends AdtError {
859
+ constructor(agentId) {
860
+ super(`Worker agent not found: ${agentId}`, "WORKER_NOT_FOUND", { agentId });
861
+ this.name = "WorkerNotFoundError";
1247
862
  }
1248
- function renderDetailViewLayout(lines, cols, rows) {
1249
- const selectedKey = state.storyOrder[state.selectedIndex];
1250
- const story = selectedKey !== void 0 ? state.stories.get(selectedKey) : void 0;
1251
- if (story === void 0) {
1252
- lines.push(" No story selected. Press Esc to go back.");
1253
- return;
1254
- }
1255
- const detailLines = renderDetailView({
1256
- story,
1257
- allLogs: state.logs,
1258
- maxLogLines: Math.max(rows - 10, 5),
1259
- useColor,
1260
- width: cols,
1261
- height: rows
1262
- });
1263
- lines.push(...detailLines);
863
+ };
864
+ /** Error thrown when a task graph is invalid */
865
+ var TaskGraphError = class extends AdtError {
866
+ constructor(message, context = {}) {
867
+ super(message, "TASK_GRAPH_ERROR", context);
868
+ this.name = "TaskGraphError";
1264
869
  }
1265
- function renderHelpView(lines, cols, _rows) {
1266
- lines.push(bold(colorize(" substrate run --tui", ANSI.BRIGHT_WHITE, useColor), useColor));
1267
- lines.push("");
1268
- const helpLines = renderHelpOverlay({
1269
- useColor,
1270
- width: cols
1271
- });
1272
- lines.push(...helpLines);
870
+ };
871
+ /** Error thrown when a task graph has cycles (deadlock) */
872
+ var TaskGraphCycleError = class extends TaskGraphError {
873
+ constructor(cycle) {
874
+ super(`Circular dependency detected in task graph: ${cycle.join(" -> ")}`, { cycle });
875
+ this.name = "TaskGraphCycleError";
1273
876
  }
1274
- function setupKeyboard() {
1275
- if (input.isTTY === true) try {
1276
- input.setRawMode(true);
1277
- } catch {}
1278
- rl = readline.createInterface({
1279
- input,
1280
- terminal: false
1281
- });
1282
- readline.emitKeypressEvents(input);
1283
- const stdin = input;
1284
- const onKeypress = (chunk, key) => {
1285
- if (key === void 0) return;
1286
- handleKeypress(key);
1287
- };
1288
- stdin.on("keypress", onKeypress);
1289
- rl.on("close", () => {
1290
- exit();
877
+ };
878
+ /** Error thrown when a budget limit is exceeded */
879
+ var BudgetExceededError = class extends AdtError {
880
+ constructor(limit, current, context = {}) {
881
+ super(`Budget cap exceeded: current=${String(current)}, limit=${String(limit)}`, "BUDGET_EXCEEDED", {
882
+ limit,
883
+ current,
884
+ ...context
1291
885
  });
886
+ this.name = "BudgetExceededError";
1292
887
  }
1293
- function handleKeypress(key) {
1294
- if (key.ctrl === true && key.name === "c") {
1295
- exit();
1296
- return;
1297
- }
1298
- switch (key.name) {
1299
- case "q":
1300
- exit();
1301
- return;
1302
- case "up":
1303
- if (state.view === "overview") {
1304
- state.selectedIndex = Math.max(0, state.selectedIndex - 1);
1305
- render();
1306
- }
1307
- break;
1308
- case "down":
1309
- if (state.view === "overview") {
1310
- state.selectedIndex = Math.min(Math.max(0, state.storyOrder.length - 1), state.selectedIndex + 1);
1311
- render();
1312
- }
1313
- break;
1314
- case "return":
1315
- case "enter":
1316
- if (state.view === "overview" && state.storyOrder.length > 0) {
1317
- state.view = "detail";
1318
- render();
1319
- }
1320
- break;
1321
- case "escape":
1322
- if (state.view === "detail" || state.view === "help") {
1323
- state.view = "overview";
1324
- render();
1325
- }
1326
- break;
1327
- case "?":
1328
- state.view = state.view === "help" ? "overview" : "help";
1329
- render();
1330
- break;
1331
- default:
1332
- if (key.sequence === "?") {
1333
- state.view = state.view === "help" ? "overview" : "help";
1334
- render();
1335
- }
1336
- break;
1337
- }
1338
- }
1339
- function onResize() {
1340
- render();
1341
- }
1342
- function init() {
1343
- write(ANSI.ALT_SCREEN_ENTER);
1344
- write(ANSI.HIDE_CURSOR);
1345
- setupKeyboard();
1346
- const resizeEmitter = typeof output.on === "function" ? output : process.stdout;
1347
- resizeEmitter.on("resize", onResize);
1348
- render();
1349
- }
1350
- function exit() {
1351
- cleanup();
1352
- if (exitResolve !== void 0) {
1353
- exitResolve();
1354
- exitResolve = void 0;
1355
- }
1356
- }
1357
- function cleanup() {
1358
- write(ANSI.SHOW_CURSOR);
1359
- write(ANSI.ALT_SCREEN_EXIT);
1360
- const resizeEmitter = typeof output.on === "function" ? output : process.stdout;
1361
- resizeEmitter.off("resize", onResize);
1362
- if (rl !== void 0) {
1363
- try {
1364
- rl.close();
1365
- } catch {}
1366
- rl = void 0;
1367
- }
1368
- if (input.isTTY === true) try {
1369
- input.setRawMode(false);
1370
- } catch {}
1371
- }
1372
- function handleEvent(event) {
1373
- switch (event.type) {
1374
- case "pipeline:start":
1375
- state.headerLine = `${event.stories.length} stories, concurrency ${event.concurrency}, run ${event.run_id.slice(0, 8)}...`;
1376
- for (const key of event.stories) {
1377
- state.storyOrder.push(key);
1378
- state.stories.set(key, {
1379
- key,
1380
- phase: "wait",
1381
- status: "pending",
1382
- statusLabel: "queued",
1383
- reviewCycles: 0
1384
- });
1385
- }
1386
- break;
1387
- case "story:phase": {
1388
- let story = state.stories.get(event.key);
1389
- if (story === void 0) {
1390
- state.storyOrder.push(event.key);
1391
- story = {
1392
- key: event.key,
1393
- phase: "wait",
1394
- status: "pending",
1395
- statusLabel: "queued",
1396
- reviewCycles: 0
1397
- };
1398
- state.stories.set(event.key, story);
1399
- }
1400
- const phaseLabel = mapPhaseToLabel(event.phase);
1401
- story.phase = phaseLabel;
1402
- story.status = mapPhaseToStatus(phaseLabel, event.status);
1403
- story.statusLabel = makeStatusLabel(phaseLabel, event.status, event.verdict);
1404
- break;
1405
- }
1406
- case "story:done": {
1407
- let story = state.stories.get(event.key);
1408
- if (story === void 0) {
1409
- state.storyOrder.push(event.key);
1410
- story = {
1411
- key: event.key,
1412
- phase: "done",
1413
- status: event.result === "success" ? "succeeded" : "failed",
1414
- statusLabel: event.result === "success" ? "SHIP_IT" : "FAILED",
1415
- reviewCycles: event.review_cycles
1416
- };
1417
- state.stories.set(event.key, story);
1418
- } else {
1419
- story.phase = event.result === "success" ? "done" : "failed";
1420
- story.status = event.result === "success" ? "succeeded" : "failed";
1421
- const cycleWord = event.review_cycles === 1 ? "cycle" : "cycles";
1422
- story.statusLabel = event.result === "success" ? `SHIP_IT (${event.review_cycles} ${cycleWord})` : "FAILED";
1423
- story.reviewCycles = event.review_cycles;
1424
- }
1425
- break;
1426
- }
1427
- case "story:escalation": {
1428
- let story = state.stories.get(event.key);
1429
- if (story === void 0) {
1430
- state.storyOrder.push(event.key);
1431
- story = {
1432
- key: event.key,
1433
- phase: "escalated",
1434
- status: "escalated",
1435
- statusLabel: `ESCALATED — ${event.reason}`,
1436
- reviewCycles: event.cycles,
1437
- escalationReason: event.reason
1438
- };
1439
- state.stories.set(event.key, story);
1440
- } else {
1441
- story.phase = "escalated";
1442
- story.status = "escalated";
1443
- story.statusLabel = `ESCALATED — ${event.reason}`;
1444
- story.reviewCycles = event.cycles;
1445
- story.escalationReason = event.reason;
1446
- }
1447
- break;
1448
- }
1449
- case "story:warn": {
1450
- const logEntry = {
1451
- ts: event.ts,
1452
- key: event.key,
1453
- msg: `[WARN] ${event.msg}`,
1454
- level: "warn"
1455
- };
1456
- state.logs.push(logEntry);
1457
- if (state.logs.length > MAX_LOG_ENTRIES) state.logs.splice(0, state.logs.length - MAX_LOG_ENTRIES);
1458
- break;
1459
- }
1460
- case "story:log": {
1461
- const logEntry = {
1462
- ts: event.ts,
1463
- key: event.key,
1464
- msg: event.msg,
1465
- level: "log"
1466
- };
1467
- state.logs.push(logEntry);
1468
- if (state.logs.length > MAX_LOG_ENTRIES) state.logs.splice(0, state.logs.length - MAX_LOG_ENTRIES);
1469
- break;
1470
- }
1471
- case "pipeline:complete":
1472
- state.pipelineComplete = true;
1473
- state.completionStats = {
1474
- succeeded: event.succeeded,
1475
- failed: event.failed,
1476
- escalated: event.escalated
1477
- };
1478
- setTimeout(() => {
1479
- if (exitResolve !== void 0) render();
1480
- }, 500);
1481
- break;
1482
- default: break;
1483
- }
1484
- render();
888
+ };
889
+ /** Error thrown when git operations fail */
890
+ var GitError = class extends AdtError {
891
+ constructor(message, context = {}) {
892
+ super(message, "GIT_ERROR", context);
893
+ this.name = "GitError";
1485
894
  }
1486
- init();
1487
- return {
1488
- handleEvent,
1489
- cleanup,
1490
- waitForExit: () => exitPromise
1491
- };
1492
- }
1493
- /**
1494
- * Check whether the TUI can run in the current environment.
1495
- *
1496
- * Returns true if stdout is a TTY, false otherwise.
1497
- * If false, the caller should print a warning and use default output.
1498
- */
1499
- function isTuiCapable() {
1500
- return process.stdout.isTTY === true;
1501
- }
1502
- /**
1503
- * Print the non-TTY fallback warning message.
1504
- */
1505
- function printNonTtyWarning() {
1506
- process.stderr.write("TUI requires an interactive terminal. Falling back to default output.\n");
1507
- }
1508
-
1509
- //#endregion
1510
- //#region src/core/event-bus.ts
1511
- /**
1512
- * Concrete implementation of TypedEventBus backed by Node.js EventEmitter.
1513
- *
1514
- * @example
1515
- * const bus = new TypedEventBusImpl()
1516
- * bus.on('task:complete', ({ taskId, result }) => {
1517
- * console.log(`Task ${taskId} finished`)
1518
- * })
1519
- * bus.emit('task:complete', { taskId: 'abc', result: { exitCode: 0 } })
1520
- */
1521
- var TypedEventBusImpl = class {
1522
- _emitter;
1523
- constructor() {
1524
- this._emitter = new EventEmitter();
1525
- this._emitter.setMaxListeners(100);
895
+ };
896
+ /** Error thrown when configuration is invalid or missing */
897
+ var ConfigError = class extends AdtError {
898
+ constructor(message, context = {}) {
899
+ super(message, "CONFIG_ERROR", context);
900
+ this.name = "ConfigError";
1526
901
  }
1527
- emit(event, payload) {
1528
- this._emitter.emit(event, payload);
902
+ };
903
+ /** Error thrown when state recovery fails */
904
+ var RecoveryError = class extends AdtError {
905
+ constructor(message, context = {}) {
906
+ super(message, "RECOVERY_ERROR", context);
907
+ this.name = "RecoveryError";
1529
908
  }
1530
- on(event, handler) {
1531
- this._emitter.on(event, handler);
909
+ };
910
+ /** Error thrown when a config file uses an incompatible format version */
911
+ var ConfigIncompatibleFormatError = class extends AdtError {
912
+ constructor(message, context = {}) {
913
+ super(message, "CONFIG_INCOMPATIBLE_FORMAT", context);
914
+ this.name = "ConfigIncompatibleFormatError";
1532
915
  }
1533
- off(event, handler) {
1534
- this._emitter.off(event, handler);
916
+ };
917
+ /** Error thrown when a task graph file uses an incompatible format version */
918
+ var TaskGraphIncompatibleFormatError = class extends AdtError {
919
+ constructor(message, context = {}) {
920
+ super(message, "TASK_GRAPH_INCOMPATIBLE_FORMAT", context);
921
+ this.name = "TaskGraphIncompatibleFormatError";
1535
922
  }
1536
923
  };
1537
- /**
1538
- * Create a new TypedEventBus instance.
1539
- *
1540
- * @example
1541
- * const bus = createEventBus()
1542
- */
1543
- function createEventBus() {
1544
- return new TypedEventBusImpl();
1545
- }
1546
924
 
1547
925
  //#endregion
1548
- export { AdapterRegistry, ClaudeCodeAdapter, CodexCLIAdapter, GeminiCLIAdapter, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning };
1549
- //# sourceMappingURL=event-bus-BMxhfxfT.js.map
926
+ export { AdapterRegistry, AdtError, BudgetExceededError, ClaudeCodeAdapter, CodexCLIAdapter, ConfigError, ConfigIncompatibleFormatError, GeminiCLIAdapter, GitError, RecoveryError, TaskConfigError, TaskGraphCycleError, TaskGraphError, TaskGraphIncompatibleFormatError, WorkerError, WorkerNotFoundError };
927
+ //# sourceMappingURL=errors-CswS7Mzg.js.map