@runtypelabs/cli 1.8.5 → 1.9.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.
package/dist/index.js CHANGED
@@ -8847,7 +8847,7 @@ function getTabBorderColor(tab) {
8847
8847
  if (tab.isLive) return theme10.accent;
8848
8848
  return theme10.borderActive;
8849
8849
  }
8850
- function SessionTabs({ tabs, hiddenLeft, hiddenRight }) {
8850
+ function SessionTabs({ tabs, hiddenLeft, hiddenRight, shortcutHint }) {
8851
8851
  if (tabs.length === 0) return null;
8852
8852
  return /* @__PURE__ */ jsxs8(Box8, { children: [
8853
8853
  hiddenLeft > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme10.textSubtle, children: `\u2039${hiddenLeft}` }),
@@ -8875,7 +8875,8 @@ function SessionTabs({ tabs, hiddenLeft, hiddenRight }) {
8875
8875
  ] }, tab.key);
8876
8876
  }),
8877
8877
  hiddenRight > 0 && /* @__PURE__ */ jsx9(Text9, { children: " " }),
8878
- hiddenRight > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme10.textSubtle, children: `${hiddenRight}\u203A` })
8878
+ hiddenRight > 0 && /* @__PURE__ */ jsx9(Text9, { color: theme10.textSubtle, children: `${hiddenRight}\u203A` }),
8879
+ shortcutHint && /* @__PURE__ */ jsx9(Text9, { color: theme10.textSubtle, children: ` ${shortcutHint}` })
8879
8880
  ] });
8880
8881
  }
8881
8882
 
@@ -9519,7 +9520,7 @@ function EventStreamPanel({
9519
9520
  // src/ink/marathon/FilesPanel.tsx
9520
9521
  import { useMemo as useMemo5 } from "react";
9521
9522
  import { Box as Box15, Text as Text16 } from "ink";
9522
- import { theme as theme17 } from "@runtypelabs/ink-components";
9523
+ import { theme as theme17, renderMarkdownToTerminal } from "@runtypelabs/ink-components";
9523
9524
  import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
9524
9525
  var TYPE_COLORS = {
9525
9526
  plan: theme17.accent,
@@ -9540,20 +9541,53 @@ function shortenPath(filePath, maxWidth) {
9540
9541
  }
9541
9542
  return ".../" + segments[segments.length - 1];
9542
9543
  }
9544
+ var ANSI_RE = /\x1B\[[0-9;]*m/g;
9545
+ function visibleLength(str) {
9546
+ return str.replace(ANSI_RE, "").length;
9547
+ }
9543
9548
  function wrapLines3(lines, width) {
9544
9549
  if (width <= 0) return lines;
9545
9550
  const result = [];
9546
9551
  for (const line of lines) {
9547
- if (line.length <= width) {
9552
+ if (visibleLength(line) <= width) {
9548
9553
  result.push(line);
9549
9554
  } else {
9550
- for (let i = 0; i < line.length; i += width) {
9551
- result.push(line.slice(i, i + width));
9555
+ if (!ANSI_RE.test(line)) {
9556
+ for (let i = 0; i < line.length; i += width) {
9557
+ result.push(line.slice(i, i + width));
9558
+ }
9559
+ } else {
9560
+ let current = "";
9561
+ let vis = 0;
9562
+ let i = 0;
9563
+ while (i < line.length) {
9564
+ if (line[i] === "\x1B" && line[i + 1] === "[") {
9565
+ const end = line.indexOf("m", i);
9566
+ if (end !== -1) {
9567
+ current += line.slice(i, end + 1);
9568
+ i = end + 1;
9569
+ continue;
9570
+ }
9571
+ }
9572
+ current += line[i];
9573
+ vis++;
9574
+ i++;
9575
+ if (vis >= width) {
9576
+ result.push(current);
9577
+ current = "";
9578
+ vis = 0;
9579
+ }
9580
+ }
9581
+ if (current) result.push(current);
9552
9582
  }
9553
9583
  }
9554
9584
  }
9555
9585
  return result;
9556
9586
  }
9587
+ function isMarkdownFile(filePath) {
9588
+ const lower = filePath.toLowerCase();
9589
+ return lower.endsWith(".md") || lower.endsWith(".mdx");
9590
+ }
9557
9591
  function FileContentView({
9558
9592
  file,
9559
9593
  content,
@@ -9565,15 +9599,17 @@ function FileContentView({
9565
9599
  }) {
9566
9600
  const separator = theme17.separator ?? " \xB7 ";
9567
9601
  const contentWidth = Math.max(20, width - 4);
9602
+ const isMd = isMarkdownFile(file.path);
9568
9603
  const { lines, displayTotalLines, clampedOffset } = useMemo5(() => {
9569
- const rawLines = content.split("\n");
9604
+ const displayContent = isMd ? renderMarkdownToTerminal(content) : content;
9605
+ const rawLines = displayContent.split("\n");
9570
9606
  const allLines = wrapLines3(rawLines, contentWidth);
9571
9607
  const total = allLines.length;
9572
9608
  const clamped = Math.min(scrollOffset, Math.max(0, total - maxVisibleLines));
9573
9609
  const start = clamped;
9574
9610
  const end = Math.min(total, start + maxVisibleLines);
9575
9611
  return { lines: allLines.slice(start, end), displayTotalLines: total, clampedOffset: clamped };
9576
- }, [content, maxVisibleLines, scrollOffset, contentWidth]);
9612
+ }, [content, isMd, maxVisibleLines, scrollOffset, contentWidth]);
9577
9613
  const filename = file.path.split("/").pop() || file.path;
9578
9614
  return /* @__PURE__ */ jsxs13(
9579
9615
  Box15,
@@ -9593,6 +9629,10 @@ function FileContentView({
9593
9629
  separator,
9594
9630
  "truncated"
9595
9631
  ] }),
9632
+ /* @__PURE__ */ jsxs13(Text16, { color: theme17.textSubtle, children: [
9633
+ separator,
9634
+ "c: copy"
9635
+ ] }),
9596
9636
  /* @__PURE__ */ jsxs13(Text16, { color: theme17.textSubtle, children: [
9597
9637
  separator,
9598
9638
  "Esc: back"
@@ -12054,7 +12094,6 @@ function MarathonApp({
12054
12094
  if (state.phase === "steering" && steeringRecap) {
12055
12095
  return joinHints(
12056
12096
  "\u2191\u2193: scroll",
12057
- "Shift+\u2190/\u2192: runs",
12058
12097
  "Tab: switch screen",
12059
12098
  activeScreen !== "overview" ? "Esc: overview" : void 0,
12060
12099
  "Ctrl+C"
@@ -12080,7 +12119,6 @@ function MarathonApp({
12080
12119
  }
12081
12120
  if (showEventStream && detailEvent) {
12082
12121
  return joinHints(
12083
- "Shift+\u2190/\u2192: runs",
12084
12122
  "c: copy",
12085
12123
  "\u2190\u2192: prev/next",
12086
12124
  "Tab: next screen",
@@ -12091,7 +12129,6 @@ function MarathonApp({
12091
12129
  }
12092
12130
  if (showEventStream) {
12093
12131
  return joinHints(
12094
- "Shift+\u2190/\u2192: runs",
12095
12132
  "1-9: jump",
12096
12133
  "0: latest",
12097
12134
  "Enter: detail",
@@ -12120,7 +12157,6 @@ function MarathonApp({
12120
12157
  }
12121
12158
  return joinHints(
12122
12159
  agentPageUrl ? "o: dashboard" : void 0,
12123
- "Shift+\u2190/\u2192: runs",
12124
12160
  "1-9: jump",
12125
12161
  "0: latest",
12126
12162
  "Tab: next screen",
@@ -12187,7 +12223,8 @@ function MarathonApp({
12187
12223
  {
12188
12224
  tabs: visibleTabs.tabs,
12189
12225
  hiddenLeft: visibleTabs.hiddenLeft,
12190
- hiddenRight: visibleTabs.hiddenRight
12226
+ hiddenRight: visibleTabs.hiddenRight,
12227
+ shortcutHint: "Shift+\\u2190/\\u2192: runs"
12191
12228
  }
12192
12229
  ) }),
12193
12230
  showFilesPanel ? /* @__PURE__ */ jsx28(
@@ -12821,6 +12858,61 @@ function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir) {
12821
12858
  // src/marathon/errors.ts
12822
12859
  import chalk12 from "chalk";
12823
12860
  import { RuntypeApiError } from "@runtypelabs/sdk";
12861
+ var NETWORK_ERROR_PATTERNS = [
12862
+ "fetch failed",
12863
+ "network error",
12864
+ "network request failed",
12865
+ "networkerror",
12866
+ "econnreset",
12867
+ "econnrefused",
12868
+ "econnaborted",
12869
+ "etimedout",
12870
+ "enetunreach",
12871
+ "enetdown",
12872
+ "ehostunreach",
12873
+ "enotfound",
12874
+ "epipe",
12875
+ "socket hang up",
12876
+ "socket disconnected",
12877
+ "connection reset",
12878
+ "connection refused",
12879
+ "connection closed",
12880
+ "connection lost",
12881
+ "network connection lost",
12882
+ "request aborted",
12883
+ "the operation was aborted",
12884
+ "other side closed",
12885
+ "client network socket disconnected",
12886
+ "unable to connect",
12887
+ "err_network"
12888
+ ];
12889
+ function isTransientNetworkError(error) {
12890
+ if (error instanceof RuntypeApiError) return false;
12891
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
12892
+ if (error instanceof TypeError && message.includes("fetch")) return true;
12893
+ if (error instanceof DOMException && error.name === "AbortError") return true;
12894
+ return NETWORK_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
12895
+ }
12896
+ async function retryOnNetworkError(fn, opts = {}) {
12897
+ const maxRetries = opts.maxRetries ?? 3;
12898
+ const baseDelay = opts.baseDelayMs ?? 5e3;
12899
+ const maxDelay = opts.maxDelayMs ?? 6e4;
12900
+ let lastError;
12901
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
12902
+ try {
12903
+ return await fn();
12904
+ } catch (error) {
12905
+ lastError = error;
12906
+ if (!isTransientNetworkError(error) || attempt === maxRetries) {
12907
+ throw error;
12908
+ }
12909
+ const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
12910
+ opts.onRetry?.(attempt + 1, delay, error);
12911
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
12912
+ }
12913
+ }
12914
+ throw lastError;
12915
+ }
12824
12916
  function readRateLimitHeader(headers, names) {
12825
12917
  for (const name of names) {
12826
12918
  const value = headers?.get(name);
@@ -14539,6 +14631,16 @@ function buildResumeHistoryWarning(state) {
14539
14631
  function canUseMarathonStartupShell(options) {
14540
14632
  return process.stdin.isTTY === true && process.stdout.isTTY === true && !options.json;
14541
14633
  }
14634
+ function buildMarathonClientHeaders(devPlanOverride) {
14635
+ const cliVersion = getCliVersion();
14636
+ return {
14637
+ "X-Runtype-Client": "cli",
14638
+ "X-Runtype-Client-Origin": "marathon",
14639
+ "X-Runtype-CLI-Version": cliVersion,
14640
+ "User-Agent": `runtype-cli/${cliVersion}`,
14641
+ ...devPlanOverride ? { "X-Dev-Plan-Override": devPlanOverride } : {}
14642
+ };
14643
+ }
14542
14644
  async function taskAction(agent, options) {
14543
14645
  if (!options.resume && !options.goal) {
14544
14646
  console.error(chalk16.red("Error: -g, --goal <text> is required for new tasks"));
@@ -14551,8 +14653,7 @@ async function taskAction(agent, options) {
14551
14653
  const client = new RuntypeClient2({
14552
14654
  apiKey,
14553
14655
  baseUrl: getApiUrl(),
14554
- // @snake-case-ok: HTTP header name follows established convention
14555
- ...devPlanOverride ? { headers: { "X-Dev-Plan-Override": devPlanOverride } } : {}
14656
+ headers: buildMarathonClientHeaders(devPlanOverride)
14556
14657
  });
14557
14658
  let parsedSandbox = parseSandboxProvider(options.sandbox);
14558
14659
  if (options.sandbox && !parsedSandbox) {
@@ -15155,7 +15256,29 @@ Saving state... done. Session saved to ${filePath}`);
15155
15256
  }
15156
15257
  }
15157
15258
  };
15158
- const result2 = await client.agents.runTask(agentId, runTaskOptions);
15259
+ const result2 = await retryOnNetworkError(
15260
+ () => client.agents.runTask(agentId, runTaskOptions),
15261
+ {
15262
+ maxRetries: 3,
15263
+ baseDelayMs: 5e3,
15264
+ maxDelayMs: 6e4,
15265
+ onRetry: (attempt, delayMs, error) => {
15266
+ const delaySec = Math.round(delayMs / 1e3);
15267
+ const msg = error instanceof Error ? error.message : String(error);
15268
+ console.error(
15269
+ chalk16.yellow(`Network error (attempt ${attempt}/3): ${msg}. Retrying in ${delaySec}s...`)
15270
+ );
15271
+ if (lastKnownState) {
15272
+ previousMessages = lastSessionMessages;
15273
+ resumeState = extractRunTaskResumeState(lastKnownState);
15274
+ runTaskOptions.previousMessages = previousMessages;
15275
+ runTaskOptions.resumeState = resumeState;
15276
+ runTaskOptions.continuationMessage = taskMessage;
15277
+ runTaskOptions.compact = false;
15278
+ }
15279
+ }
15280
+ }
15281
+ );
15159
15282
  persistedSessionSummaries = mergeMarathonSessionSummaries(
15160
15283
  persistedSessionSummaries,
15161
15284
  result2.sessions,
@@ -15326,6 +15449,9 @@ Resume: ${buildResumeCommand(agent, options, parsedSandbox)}`
15326
15449
  const stateAtError = lastKnownState;
15327
15450
  if (stateAtError) {
15328
15451
  stateAtError.status = "paused";
15452
+ if (isTransientNetworkError(error)) {
15453
+ stateAtError.lastError = "Network connection lost.";
15454
+ }
15329
15455
  saveState(filePath, stateAtError);
15330
15456
  }
15331
15457
  const streamActions = streamRef.current;
@@ -15341,8 +15467,13 @@ Resume: ${buildResumeCommand(agent, options, parsedSandbox)}`
15341
15467
  if (!restoredScreen) {
15342
15468
  exitAltScreen();
15343
15469
  }
15344
- for (const line of formatMarathonApiError(error)) {
15345
- console.error(line);
15470
+ if (isTransientNetworkError(error)) {
15471
+ console.error(chalk16.red("Network connection lost after multiple retry attempts."));
15472
+ console.error(chalk16.gray("Your progress has been saved. Resume when connectivity is restored:"));
15473
+ } else {
15474
+ for (const line of formatMarathonApiError(error)) {
15475
+ console.error(line);
15476
+ }
15346
15477
  }
15347
15478
  console.log(chalk16.gray(`State saved to ${filePath} \u2014 resume with --resume`));
15348
15479
  process.exit(1);