@runtypelabs/cli 1.9.4 → 2.0.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
@@ -428,7 +428,7 @@ var require_builtin_tools_registry = __commonJS({
428
428
  {
429
429
  id: "firecrawl",
430
430
  name: "Firecrawl",
431
- description: "Scrape and extract content from web pages. Supports markdown, HTML, screenshots, JSON extraction, and interactive actions.",
431
+ description: "Scrape and extract content from a URL as markdown. Provide a url and optional formats array.",
432
432
  category: exports.BuiltInToolCategory.WEB_SCRAPING,
433
433
  providers: [exports.BuiltInToolProvider.MULTI],
434
434
  parametersSchema: {
@@ -436,12 +436,12 @@ var require_builtin_tools_registry = __commonJS({
436
436
  properties: {
437
437
  url: {
438
438
  type: "string",
439
- description: "URL of the webpage to scrape",
439
+ description: 'The full URL to scrape (e.g. "https://example.com/page")',
440
440
  minLength: 1
441
441
  },
442
442
  formats: {
443
443
  type: "array",
444
- description: "Content formats to extract from the page",
444
+ description: 'Output formats (e.g. ["markdown"]). Options: markdown, html, rawHtml, screenshot, links',
445
445
  items: {
446
446
  type: "string",
447
447
  enum: ["markdown", "html", "rawHtml", "screenshot", "links"]
@@ -450,12 +450,12 @@ var require_builtin_tools_registry = __commonJS({
450
450
  },
451
451
  onlyMainContent: {
452
452
  type: "boolean",
453
- description: "Extract only main content (removes navigation, footers, ads)",
453
+ description: "If true, strips navigation/footers/ads and returns only the main content",
454
454
  default: true
455
455
  },
456
456
  waitFor: {
457
457
  type: "number",
458
- description: "Milliseconds to wait before extracting content (for dynamic pages)",
458
+ description: "Milliseconds to wait for dynamic content before scraping (0-30000)",
459
459
  minimum: 0,
460
460
  maximum: 3e4
461
461
  }
@@ -4545,8 +4545,8 @@ var CallbackServer = class {
4545
4545
  expectedState;
4546
4546
  constructor() {
4547
4547
  this.app = express();
4548
- this.codePromise = new Promise((resolve6, reject) => {
4549
- this.codeResolve = resolve6;
4548
+ this.codePromise = new Promise((resolve7, reject) => {
4549
+ this.codeResolve = resolve7;
4550
4550
  this.codeReject = reject;
4551
4551
  });
4552
4552
  this.app.get("/callback", (req, res) => {
@@ -5741,14 +5741,14 @@ async function promptConfirm(message, options) {
5741
5741
  output: process.stdout,
5742
5742
  terminal: true
5743
5743
  });
5744
- return new Promise((resolve6) => {
5744
+ return new Promise((resolve7) => {
5745
5745
  rl.question(chalk2.cyan(`${message} (${hint}): `), (answer) => {
5746
5746
  rl.close();
5747
5747
  const trimmed = answer.trim().toLowerCase();
5748
5748
  if (trimmed === "") {
5749
- resolve6(defaultYes);
5749
+ resolve7(defaultYes);
5750
5750
  } else {
5751
- resolve6(trimmed === "y" || trimmed === "yes");
5751
+ resolve7(trimmed === "y" || trimmed === "yes");
5752
5752
  }
5753
5753
  });
5754
5754
  });
@@ -5849,9 +5849,9 @@ var ApiClient = class {
5849
5849
  this.baseUrl = baseUrl || getApiUrl();
5850
5850
  }
5851
5851
  /** Prefix path with API version (e.g. /v1) so all requests hit versioned routes. */
5852
- path(path11) {
5852
+ path(path13) {
5853
5853
  const version = getApiVersion();
5854
- const p = path11.startsWith("/") ? path11 : `/${path11}`;
5854
+ const p = path13.startsWith("/") ? path13 : `/${path13}`;
5855
5855
  return p.startsWith(`/${version}/`) ? p : `/${version}${p}`;
5856
5856
  }
5857
5857
  headers(extra) {
@@ -5884,8 +5884,8 @@ var ApiClient = class {
5884
5884
  }
5885
5885
  return response.json();
5886
5886
  }
5887
- async get(path11, params) {
5888
- const url = new URL(this.path(path11), this.baseUrl);
5887
+ async get(path13, params) {
5888
+ const url = new URL(this.path(path13), this.baseUrl);
5889
5889
  if (params) {
5890
5890
  for (const [key, value] of Object.entries(params)) {
5891
5891
  if (value !== void 0 && value !== "") {
@@ -5898,32 +5898,32 @@ var ApiClient = class {
5898
5898
  });
5899
5899
  return this.handleResponse(response);
5900
5900
  }
5901
- async post(path11, body) {
5902
- const response = await fetch(new URL(this.path(path11), this.baseUrl).toString(), {
5901
+ async post(path13, body) {
5902
+ const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5903
5903
  method: "POST",
5904
5904
  headers: this.headers(),
5905
5905
  body: body !== void 0 ? JSON.stringify(body) : void 0
5906
5906
  });
5907
5907
  return this.handleResponse(response);
5908
5908
  }
5909
- async put(path11, body) {
5910
- const response = await fetch(new URL(this.path(path11), this.baseUrl).toString(), {
5909
+ async put(path13, body) {
5910
+ const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5911
5911
  method: "PUT",
5912
5912
  headers: this.headers(),
5913
5913
  body: body !== void 0 ? JSON.stringify(body) : void 0
5914
5914
  });
5915
5915
  return this.handleResponse(response);
5916
5916
  }
5917
- async patch(path11, body) {
5918
- const response = await fetch(new URL(this.path(path11), this.baseUrl).toString(), {
5917
+ async patch(path13, body) {
5918
+ const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5919
5919
  method: "PATCH",
5920
5920
  headers: this.headers(),
5921
5921
  body: body !== void 0 ? JSON.stringify(body) : void 0
5922
5922
  });
5923
5923
  return this.handleResponse(response);
5924
5924
  }
5925
- async delete(path11) {
5926
- const response = await fetch(new URL(this.path(path11), this.baseUrl).toString(), {
5925
+ async delete(path13) {
5926
+ const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5927
5927
  method: "DELETE",
5928
5928
  headers: this.headers()
5929
5929
  });
@@ -5937,8 +5937,8 @@ var ApiClient = class {
5937
5937
  throw new ApiError(response.status, message);
5938
5938
  }
5939
5939
  }
5940
- async stream(path11, body) {
5941
- const response = await fetch(new URL(this.path(path11), this.baseUrl).toString(), {
5940
+ async stream(path13, body) {
5941
+ const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5942
5942
  method: "POST",
5943
5943
  headers: this.headers({ Accept: "text/event-stream" }),
5944
5944
  body: body !== void 0 ? JSON.stringify(body) : void 0
@@ -8588,8 +8588,8 @@ function useMarathonStream() {
8588
8588
  const reset = useCallback2(() => {
8589
8589
  setState(INITIAL_STATE);
8590
8590
  }, []);
8591
- const setSteering = useCallback2(() => {
8592
- setState((prev) => ({ ...prev, phase: "steering" }));
8591
+ const setCheckpoint = useCallback2(() => {
8592
+ setState((prev) => ({ ...prev, phase: "checkpoint" }));
8593
8593
  }, []);
8594
8594
  const markPendingStart = useCallback2(() => {
8595
8595
  setState((prev) => ({
@@ -8667,7 +8667,7 @@ function useMarathonStream() {
8667
8667
  state,
8668
8668
  callbacks,
8669
8669
  reset,
8670
- setSteering,
8670
+ setCheckpoint,
8671
8671
  markPendingStart,
8672
8672
  startContextCompaction,
8673
8673
  finishContextCompaction,
@@ -8895,6 +8895,37 @@ function renderSegments(segments) {
8895
8895
  (seg, i) => /* @__PURE__ */ jsx8(Text8, { color: seg.color, bold: seg.bold, children: seg.text }, i)
8896
8896
  );
8897
8897
  }
8898
+ function buildMilestoneText(milestones, currentMilestone) {
8899
+ if (milestones.length === 0) return "";
8900
+ const currentIndex = currentMilestone ? currentMilestone === "complete" ? milestones.length : milestones.indexOf(currentMilestone) : 0;
8901
+ return " " + milestones.map((name, i) => {
8902
+ const isCompleted = currentIndex > i;
8903
+ const isCurrent = currentIndex === i;
8904
+ if (isCompleted) return `\u2713 ${name}`;
8905
+ if (isCurrent) return `\u25CF ${name}`;
8906
+ return `\u25CB ${name}`;
8907
+ }).join(" \u2500\u2500 ");
8908
+ }
8909
+ function buildMilestoneSegments(milestones, currentMilestone) {
8910
+ if (milestones.length === 0) return [];
8911
+ const currentIndex = currentMilestone ? currentMilestone === "complete" ? milestones.length : milestones.indexOf(currentMilestone) : 0;
8912
+ const segments = [{ text: " " }];
8913
+ milestones.forEach((name, i) => {
8914
+ const isCompleted = currentIndex > i;
8915
+ const isCurrent = currentIndex === i;
8916
+ if (isCompleted) {
8917
+ segments.push({ text: `\u2713 ${name}`, color: theme9.textMuted });
8918
+ } else if (isCurrent) {
8919
+ segments.push({ text: `\u25CF ${name}`, color: theme9.accent, bold: true });
8920
+ } else {
8921
+ segments.push({ text: `\u25CB ${name}`, color: theme9.textSubtle });
8922
+ }
8923
+ if (i < milestones.length - 1) {
8924
+ segments.push({ text: " \u2500\u2500 ", color: theme9.border });
8925
+ }
8926
+ });
8927
+ return segments;
8928
+ }
8898
8929
  function SessionHeader({
8899
8930
  sessionName,
8900
8931
  sessionCount,
@@ -8911,13 +8942,17 @@ function SessionHeader({
8911
8942
  runnerLaps,
8912
8943
  onRunnerPositionChange,
8913
8944
  trackWidth,
8914
- previewUrl
8945
+ previewUrl,
8946
+ workflowMilestones,
8947
+ currentMilestone
8915
8948
  }) {
8916
8949
  const showName = sessionName && sessionName !== `Marathon #${sessionCount}`;
8917
8950
  const separator = theme9.separator ?? " \xB7 ";
8918
8951
  const resolvedWidth = trackWidth ?? 40;
8952
+ const hasMilestones = workflowMilestones && workflowMilestones.length > 0;
8919
8953
  let contentRowCount = 1;
8920
8954
  if (previewUrl) contentRowCount++;
8955
+ if (hasMilestones) contentRowCount++;
8921
8956
  if (goal) contentRowCount++;
8922
8957
  const { topSegments, bottomSegments, innerWidth, borderSegments } = useRunnerTrack({
8923
8958
  width: resolvedWidth,
@@ -8956,6 +8991,11 @@ function SessionHeader({
8956
8991
  rowIdx++
8957
8992
  ));
8958
8993
  }
8994
+ if (hasMilestones) {
8995
+ const milestoneSegs = buildMilestoneSegments(workflowMilestones, currentMilestone);
8996
+ const milestoneText = buildMilestoneText(workflowMilestones, currentMilestone);
8997
+ rows.push(borderSegments(milestoneSegs, milestoneText.length, rowIdx++));
8998
+ }
8959
8999
  if (goal) {
8960
9000
  const maxGoalLen = innerWidth - 1;
8961
9001
  const displayGoal = goalExpanded ? goal : goal.slice(0, maxGoalLen);
@@ -10170,7 +10210,7 @@ var FILES_SHORTCUTS = [
10170
10210
  { key: "\u2190/\u2192", desc: "Previous / next file" },
10171
10211
  { key: "c", desc: "Copy path / content" }
10172
10212
  ];
10173
- var STEERING_COMMANDS = [
10213
+ var CHECKPOINT_COMMANDS = [
10174
10214
  { key: "/model", desc: "Change AI model" },
10175
10215
  { key: "/tools", desc: "Toggle local tools on/off" },
10176
10216
  { key: "/sandbox", desc: "Switch sandbox provider" },
@@ -10183,7 +10223,7 @@ var STEERING_COMMANDS = [
10183
10223
  { key: "/stop", desc: "Stop the marathon" },
10184
10224
  { key: "/help", desc: "Toggle this help dialog" }
10185
10225
  ];
10186
- function buildLines(isSteering) {
10226
+ function buildLines(isCheckpoint) {
10187
10227
  const lines = [];
10188
10228
  const sections = [
10189
10229
  { title: "Navigation", items: NAVIGATION_SHORTCUTS },
@@ -10191,7 +10231,7 @@ function buildLines(isSteering) {
10191
10231
  { title: "Reasoning", items: REASONING_SHORTCUTS },
10192
10232
  { title: "Events", items: EVENTS_SHORTCUTS },
10193
10233
  { title: "Files", items: FILES_SHORTCUTS },
10194
- ...isSteering ? [{ title: "Steering Commands", items: STEERING_COMMANDS }] : []
10234
+ ...isCheckpoint ? [{ title: "Checkpoint Commands", items: CHECKPOINT_COMMANDS }] : []
10195
10235
  ];
10196
10236
  for (let s = 0; s < sections.length; s++) {
10197
10237
  const section = sections[s];
@@ -10205,14 +10245,14 @@ function buildLines(isSteering) {
10205
10245
  }
10206
10246
  return lines;
10207
10247
  }
10208
- function getHelpLineCount(isSteering) {
10209
- return buildLines(isSteering).length;
10248
+ function getHelpLineCount(isCheckpoint) {
10249
+ return buildLines(isCheckpoint).length;
10210
10250
  }
10211
- function HelpPanel({ width, height, isSteering, scrollOffset }) {
10251
+ function HelpPanel({ width, height, isCheckpoint, scrollOffset }) {
10212
10252
  const dialogWidth = Math.min(width - 4, 60);
10213
- const contentHeight = Math.min(height - 2, isSteering ? 38 : 28) - 8;
10253
+ const contentHeight = Math.min(height - 2, isCheckpoint ? 38 : 28) - 8;
10214
10254
  const visibleRows = Math.max(1, contentHeight);
10215
- const allLines = useMemo7(() => buildLines(isSteering), [isSteering]);
10255
+ const allLines = useMemo7(() => buildLines(isCheckpoint), [isCheckpoint]);
10216
10256
  const maxScroll = Math.max(0, allLines.length - visibleRows);
10217
10257
  const clampedOffset = Math.min(scrollOffset, maxScroll);
10218
10258
  const visibleLines = allLines.slice(clampedOffset, clampedOffset + visibleRows);
@@ -10220,7 +10260,7 @@ function HelpPanel({ width, height, isSteering, scrollOffset }) {
10220
10260
  Box17,
10221
10261
  {
10222
10262
  width: dialogWidth,
10223
- height: Math.min(height - 2, isSteering ? 38 : 28),
10263
+ height: Math.min(height - 2, isCheckpoint ? 38 : 28),
10224
10264
  flexDirection: "column",
10225
10265
  borderStyle: "round",
10226
10266
  borderColor: theme21.accent,
@@ -10257,10 +10297,10 @@ function HelpPanel({ width, height, isSteering, scrollOffset }) {
10257
10297
  /* @__PURE__ */ jsx20(Text18, { color: theme21.muted, children: " or " }),
10258
10298
  /* @__PURE__ */ jsx20(Text18, { color: theme21.info, children: "?" }),
10259
10299
  /* @__PURE__ */ jsx20(Text18, { color: theme21.muted, children: " to close" }),
10260
- isSteering && /* @__PURE__ */ jsxs16(Fragment2, { children: [
10300
+ isCheckpoint && /* @__PURE__ */ jsxs16(Fragment2, { children: [
10261
10301
  /* @__PURE__ */ jsx20(Text18, { color: theme21.muted, children: " (use " }),
10262
10302
  /* @__PURE__ */ jsx20(Text18, { color: theme21.info, children: "/help" }),
10263
- /* @__PURE__ */ jsx20(Text18, { color: theme21.muted, children: " during steering)" })
10303
+ /* @__PURE__ */ jsx20(Text18, { color: theme21.muted, children: " during checkpoint)" })
10264
10304
  ] })
10265
10305
  ] })
10266
10306
  ]
@@ -10733,7 +10773,7 @@ function readSessionEventLog(stateFilePath2, sessionIndex) {
10733
10773
  return events;
10734
10774
  }
10735
10775
 
10736
- // src/ink/marathon/SteeringPrompt.tsx
10776
+ // src/ink/marathon/CheckpointPrompt.tsx
10737
10777
  import { useState as useState21, useEffect as useEffect19, useRef as useRef7 } from "react";
10738
10778
  import { Box as Box22, Text as Text24 } from "ink";
10739
10779
 
@@ -10868,7 +10908,7 @@ function BlinkingTextInput({
10868
10908
  return /* @__PURE__ */ jsx21(Text19, { children: placeholder ? value.length > 0 ? renderedValue : renderedPlaceholder : renderedValue });
10869
10909
  }
10870
10910
 
10871
- // src/ink/marathon/SteeringPrompt.tsx
10911
+ // src/ink/marathon/CheckpointPrompt.tsx
10872
10912
  import { theme as theme26 } from "@runtypelabs/ink-components";
10873
10913
 
10874
10914
  // src/ink/talk/ModelPicker.tsx
@@ -11052,7 +11092,7 @@ import TextInput3 from "ink-text-input";
11052
11092
  import { theme as theme23 } from "@runtypelabs/ink-components";
11053
11093
 
11054
11094
  // src/ink/marathon/types.ts
11055
- var MARATHON_STEER_COMMANDS = [
11095
+ var MARATHON_CHECKPOINT_COMMANDS = [
11056
11096
  { name: "/model", description: "Change AI model", hasArgs: false },
11057
11097
  { name: "/tools", description: "Toggle local tools on/off", hasArgs: false },
11058
11098
  {
@@ -11086,9 +11126,9 @@ function CommandPicker({ onSelect, onCancel, initialFilter = "" }) {
11086
11126
  [contentWidth]
11087
11127
  );
11088
11128
  const filtered = useMemo9(() => {
11089
- if (!search.trim()) return MARATHON_STEER_COMMANDS;
11129
+ if (!search.trim()) return MARATHON_CHECKPOINT_COMMANDS;
11090
11130
  const q = search.toLowerCase();
11091
- return MARATHON_STEER_COMMANDS.filter(
11131
+ return MARATHON_CHECKPOINT_COMMANDS.filter(
11092
11132
  (cmd) => cmd.name.toLowerCase().includes(q) || cmd.description.toLowerCase().includes(q)
11093
11133
  );
11094
11134
  }, [search]);
@@ -11510,14 +11550,14 @@ function ReflectEditor({ onSubmit, onCancel, maxHeight = 10 }) {
11510
11550
  );
11511
11551
  }
11512
11552
 
11513
- // src/ink/marathon/SteeringRecap.tsx
11553
+ // src/ink/marathon/CheckpointRecap.tsx
11514
11554
  import { Box as Box21, Text as Text23 } from "ink";
11515
11555
  import { theme as theme25 } from "@runtypelabs/ink-components";
11516
11556
  import { jsx as jsx26, jsxs as jsxs20 } from "react/jsx-runtime";
11517
11557
  function formatTokenCount2(value) {
11518
11558
  return value.toLocaleString("en-US");
11519
11559
  }
11520
- function SteeringRecap({
11560
+ function CheckpointRecap({
11521
11561
  sessionNumber,
11522
11562
  toolCallsMade,
11523
11563
  tokensInput,
@@ -11553,13 +11593,13 @@ function SteeringRecap({
11553
11593
  );
11554
11594
  }
11555
11595
 
11556
- // src/ink/marathon/steering-prompt-state.ts
11557
- function shouldPauseSteeringTimer(state) {
11596
+ // src/ink/marathon/checkpoint-prompt-state.ts
11597
+ function shouldPauseCheckpointTimer(state) {
11558
11598
  return Boolean(
11559
11599
  state.isTerminal || state.timeout === 0 || state.isTyping || state.hasPendingChanges || state.showModelPicker || state.showReflectEditor || state.showCommandPicker || state.isReviewing
11560
11600
  );
11561
11601
  }
11562
- function getSteeringCountdownText(state) {
11602
+ function getCheckpointCountdownText(state) {
11563
11603
  if (state.isTerminal) return "(complete)";
11564
11604
  if (state.timeout === 0) return "";
11565
11605
  if (state.isTyping) return "(typing)";
@@ -11568,9 +11608,9 @@ function getSteeringCountdownText(state) {
11568
11608
  return `(${state.remaining}s)`;
11569
11609
  }
11570
11610
 
11571
- // src/ink/marathon/SteeringPrompt.tsx
11611
+ // src/ink/marathon/CheckpointPrompt.tsx
11572
11612
  import { jsx as jsx27, jsxs as jsxs21 } from "react/jsx-runtime";
11573
- function SteeringPrompt({
11613
+ function CheckpointPrompt({
11574
11614
  onSubmit,
11575
11615
  onToggleHelp,
11576
11616
  onCopySession,
@@ -11637,7 +11677,7 @@ function SteeringPrompt({
11637
11677
  }
11638
11678
  };
11639
11679
  useEffect19(() => {
11640
- if (shouldPauseSteeringTimer({
11680
+ if (shouldPauseCheckpointTimer({
11641
11681
  isTerminal,
11642
11682
  timeout,
11643
11683
  isTyping,
@@ -11760,7 +11800,7 @@ ${trimmed}` : trimmed);
11760
11800
  const handleCommandCancel = () => {
11761
11801
  setShowCommandPicker(false);
11762
11802
  };
11763
- const countdownText = getSteeringCountdownText({
11803
+ const countdownText = getCheckpointCountdownText({
11764
11804
  isTerminal,
11765
11805
  timeout,
11766
11806
  isTyping,
@@ -11823,7 +11863,7 @@ ${trimmed}` : trimmed);
11823
11863
  value,
11824
11864
  onChange: handleChange,
11825
11865
  onSubmit: handleSubmit,
11826
- placeholder: isTerminal ? "Send new instructions to continue marathon, or press enter to exit..." : "Steer next iteration..."
11866
+ placeholder: isTerminal ? "Send new instructions to continue marathon, or press enter to exit..." : "Guide next iteration..."
11827
11867
  }
11828
11868
  ) }),
11829
11869
  countdownText && /* @__PURE__ */ jsxs21(Text24, { color: theme26.textSubtle, children: [
@@ -11840,7 +11880,7 @@ ${trimmed}` : trimmed);
11840
11880
  ]
11841
11881
  }
11842
11882
  ),
11843
- /* @__PURE__ */ jsx27(SteeringRecap, { ...recap, isTerminal })
11883
+ /* @__PURE__ */ jsx27(CheckpointRecap, { ...recap, isTerminal })
11844
11884
  ] });
11845
11885
  }
11846
11886
 
@@ -12144,8 +12184,8 @@ function getSessionTabKeyForShortcut(visibleTabs, input) {
12144
12184
  return visibleTabs.find((tab) => tab.shortcut === slot)?.key;
12145
12185
  }
12146
12186
 
12147
- // src/ink/marathon/steering-navigation.ts
12148
- function isAllowedSteeringBrowseKey(key, isBrowsingScreen) {
12187
+ // src/ink/marathon/checkpoint-navigation.ts
12188
+ function isAllowedCheckpointBrowseKey(key, isBrowsingScreen) {
12149
12189
  return Boolean(
12150
12190
  key.upArrow || key.downArrow || key.return && isBrowsingScreen || key.input === "c" && isBrowsingScreen || key.leftArrow && isBrowsingScreen || key.rightArrow && isBrowsingScreen || key.tab || key.escape || key.shift && key.leftArrow || key.shift && key.rightArrow
12151
12191
  );
@@ -12160,7 +12200,7 @@ var STACKED_THRESHOLD = 80;
12160
12200
  var CHROME_ROWS_BASE = 9;
12161
12201
  var GOAL_ROW = 1;
12162
12202
  var SCROLL_STEP = 3;
12163
- var STEERING_PROMPT_ROWS = 7;
12203
+ var CHECKPOINT_PROMPT_ROWS = 7;
12164
12204
  var SESSION_TABS_ROWS = 2;
12165
12205
  var SCREEN_TABS_ROWS = 2;
12166
12206
  function cloneSessionSnapshot(snapshot) {
@@ -12246,8 +12286,10 @@ function MarathonApp({
12246
12286
  stateFilePath: stateFilePath2,
12247
12287
  debug: _debug,
12248
12288
  plainText,
12249
- noSteer: _noSteer,
12250
- steeringTimeout,
12289
+ noCheckpoint: _noCheckpoint,
12290
+ checkpointTimeout,
12291
+ workflowMilestones,
12292
+ currentMilestone: initialCurrentMilestone,
12251
12293
  dashboardUrl,
12252
12294
  billingUrl,
12253
12295
  onSaveState,
@@ -12258,7 +12300,7 @@ function MarathonApp({
12258
12300
  state,
12259
12301
  callbacks,
12260
12302
  reset: _reset,
12261
- setSteering,
12303
+ setCheckpoint,
12262
12304
  markPendingStart,
12263
12305
  startContextCompaction,
12264
12306
  finishContextCompaction,
@@ -12270,11 +12312,12 @@ function MarathonApp({
12270
12312
  const { exit } = useApp4();
12271
12313
  const { stdout } = useStdout5();
12272
12314
  const separator = theme30.separator ?? " \xB7 ";
12273
- const steeringResolveRef = useRef8(null);
12274
- const [steeringRecap, setSteeringRecap] = useState22(null);
12315
+ const checkpointResolveRef = useRef8(null);
12316
+ const [checkpointRecap, setCheckpointRecap] = useState22(null);
12275
12317
  const [currentModel, setCurrentModel] = useState22(model || "default");
12276
12318
  const [currentSandbox, setCurrentSandbox] = useState22(sandbox);
12277
- const [isTerminalSteering, setIsTerminalSteering] = useState22(false);
12319
+ const [isTerminalCheckpoint, setIsTerminalCheckpoint] = useState22(false);
12320
+ const [currentMilestone, setCurrentMilestone] = useState22(initialCurrentMilestone);
12278
12321
  const [allowInitialInlineLoader, setAllowInitialInlineLoader] = useState22(!suppressInitialInlineLoader);
12279
12322
  const [sessionSnapshots, setSessionSnapshots] = useState22(
12280
12323
  () => (initialSessionSnapshots || []).map((snapshot) => cloneSessionSnapshot(snapshot))
@@ -12284,35 +12327,35 @@ function MarathonApp({
12284
12327
  const [followLatest, setFollowLatest] = useState22(true);
12285
12328
  const [latestUnreadKey, setLatestUnreadKey] = useState22(void 0);
12286
12329
  const [previewUrl, setPreviewUrl] = useState22(initialPreviewUrl);
12287
- const [isSteeringExploring, setIsSteeringExploring] = useState22(false);
12330
+ const [isCheckpointExploring, setIsCheckpointExploring] = useState22(false);
12288
12331
  const isRunnerAnimating = state.phase === "thinking" || state.phase === "streaming" || state.phase === "tool" || Boolean(state.contextCompaction?.active);
12289
- const isTerminalSteeringRef = useRef8(false);
12290
- const markSteeringExploring = useCallback8(() => {
12291
- if (state.phase === "steering" && steeringRecap) {
12292
- setIsSteeringExploring(true);
12332
+ const isTerminalCheckpointRef = useRef8(false);
12333
+ const markCheckpointExploring = useCallback8(() => {
12334
+ if (state.phase === "checkpoint" && checkpointRecap) {
12335
+ setIsCheckpointExploring(true);
12293
12336
  }
12294
- }, [state.phase, steeringRecap]);
12295
- const handleSteeringSubmit = useCallback8(
12337
+ }, [state.phase, checkpointRecap]);
12338
+ const handleCheckpointSubmit = useCallback8(
12296
12339
  (result) => {
12297
- setIsSteeringExploring(false);
12340
+ setIsCheckpointExploring(false);
12298
12341
  if (result.model) {
12299
12342
  setCurrentModel(result.model);
12300
12343
  }
12301
12344
  if (result.sandbox !== void 0) {
12302
12345
  setCurrentSandbox(result.sandbox || void 0);
12303
12346
  }
12304
- if (isTerminalSteeringRef.current) {
12305
- if (steeringResolveRef.current) {
12306
- steeringResolveRef.current(result);
12307
- steeringResolveRef.current = null;
12347
+ if (isTerminalCheckpointRef.current) {
12348
+ if (checkpointResolveRef.current) {
12349
+ checkpointResolveRef.current(result);
12350
+ checkpointResolveRef.current = null;
12308
12351
  }
12309
12352
  } else {
12310
12353
  resetForNewSession();
12311
- setIsTerminalSteering(false);
12312
- isTerminalSteeringRef.current = false;
12313
- if (steeringResolveRef.current) {
12314
- steeringResolveRef.current(result);
12315
- steeringResolveRef.current = null;
12354
+ setIsTerminalCheckpoint(false);
12355
+ isTerminalCheckpointRef.current = false;
12356
+ if (checkpointResolveRef.current) {
12357
+ checkpointResolveRef.current(result);
12358
+ checkpointResolveRef.current = null;
12316
12359
  }
12317
12360
  }
12318
12361
  },
@@ -12342,18 +12385,21 @@ function MarathonApp({
12342
12385
  setFollowLatest(true);
12343
12386
  setLatestUnreadKey(void 0);
12344
12387
  },
12345
- requestSteering: async (recap, isTerminal) => {
12346
- setSteering();
12347
- setSteeringRecap(recap);
12348
- setIsSteeringExploring(false);
12388
+ requestCheckpoint: async (recap, isTerminal) => {
12389
+ setCheckpoint();
12390
+ setCheckpointRecap(recap);
12391
+ setIsCheckpointExploring(false);
12349
12392
  if (isTerminal) {
12350
- setIsTerminalSteering(true);
12351
- isTerminalSteeringRef.current = true;
12393
+ setIsTerminalCheckpoint(true);
12394
+ isTerminalCheckpointRef.current = true;
12352
12395
  }
12353
- return new Promise((resolve6) => {
12354
- steeringResolveRef.current = resolve6;
12396
+ return new Promise((resolve7) => {
12397
+ checkpointResolveRef.current = resolve7;
12355
12398
  });
12356
12399
  },
12400
+ updateMilestone: (milestone) => {
12401
+ setCurrentMilestone(milestone);
12402
+ },
12357
12403
  startContextCompaction,
12358
12404
  finishContextCompaction,
12359
12405
  reportContextNotice,
@@ -12370,9 +12416,9 @@ function MarathonApp({
12370
12416
  latestCompletedSessionIndex,
12371
12417
  markPendingStart,
12372
12418
  reportContextNotice,
12373
- setIsSteeringExploring,
12419
+ setIsCheckpointExploring,
12374
12420
  setEventWriter,
12375
- setSteering,
12421
+ setCheckpoint,
12376
12422
  showError,
12377
12423
  startContextCompaction,
12378
12424
  state,
@@ -12381,7 +12427,8 @@ function MarathonApp({
12381
12427
  ]);
12382
12428
  const terminalWidth = stdout?.columns ?? 120;
12383
12429
  const terminalRows = stdout?.rows ?? 40;
12384
- const chromeRows = CHROME_ROWS_BASE + (goal ? GOAL_ROW : 0);
12430
+ const milestoneRow = workflowMilestones && workflowMilestones.length > 0 ? 1 : 0;
12431
+ const chromeRows = CHROME_ROWS_BASE + (goal ? GOAL_ROW : 0) + milestoneRow;
12385
12432
  const contentHeight = Math.max(5, terminalRows - chromeRows);
12386
12433
  const isStacked = terminalWidth < STACKED_THRESHOLD;
12387
12434
  const toolPanelWidth = terminalWidth < NARROW_THRESHOLD ? TOOL_PANEL_NARROW : TOOL_PANEL_WIDE;
@@ -12470,7 +12517,7 @@ function MarathonApp({
12470
12517
  void open4(agentPageUrl);
12471
12518
  }
12472
12519
  }, [agentPageUrl]);
12473
- const shouldShowLiveTab = Boolean(state.contextCompaction?.active) || state.phase !== "idle" && state.phase !== "steering" && state.phase !== "complete" && (Boolean(state.content) || Boolean(state.reasoning) || state.tools.length > 0 || state.rawEvents.length > 0 || state.phase === "thinking" || state.phase === "error");
12520
+ const shouldShowLiveTab = Boolean(state.contextCompaction?.active) || state.phase !== "idle" && state.phase !== "checkpoint" && state.phase !== "complete" && (Boolean(state.content) || Boolean(state.reasoning) || state.tools.length > 0 || state.rawEvents.length > 0 || state.phase === "thinking" || state.phase === "error");
12474
12521
  const liveSessionSnapshot = useMemo11(
12475
12522
  () => shouldShowLiveTab ? buildLiveSessionSnapshot(state, latestCompletedSessionIndex + 1, currentModel) : void 0,
12476
12523
  [currentModel, latestCompletedSessionIndex, shouldShowLiveTab, state]
@@ -12641,7 +12688,7 @@ function MarathonApp({
12641
12688
  setUpgradeModalDismissed(true);
12642
12689
  return;
12643
12690
  }
12644
- if (_input === "o" && agentPageUrl && !(state.phase === "steering" && steeringRecap)) {
12691
+ if (_input === "o" && agentPageUrl && !(state.phase === "checkpoint" && checkpointRecap)) {
12645
12692
  void open4(agentPageUrl);
12646
12693
  return;
12647
12694
  }
@@ -12689,19 +12736,19 @@ function MarathonApp({
12689
12736
  setShowHelpOverlay(false);
12690
12737
  setHelpScrollOffset(0);
12691
12738
  } else if (key.downArrow) {
12692
- const isSteering = state.phase === "steering" && !!steeringRecap;
12693
- const maxScroll = Math.max(0, getHelpLineCount(isSteering) - 1);
12739
+ const isAtCheckpoint = state.phase === "checkpoint" && !!checkpointRecap;
12740
+ const maxScroll = Math.max(0, getHelpLineCount(isAtCheckpoint) - 1);
12694
12741
  setHelpScrollOffset((prev) => Math.min(prev + SCROLL_STEP, maxScroll));
12695
12742
  } else if (key.upArrow) {
12696
12743
  setHelpScrollOffset((prev) => Math.max(0, prev - SCROLL_STEP));
12697
12744
  }
12698
12745
  return;
12699
12746
  }
12700
- if (_input === "?" && !(state.phase === "steering" && steeringRecap)) {
12747
+ if (_input === "?" && !(state.phase === "checkpoint" && checkpointRecap)) {
12701
12748
  setShowHelpOverlay(true);
12702
12749
  return;
12703
12750
  }
12704
- if (_input === "s" && !(state.phase === "steering" && steeringRecap)) {
12751
+ if (_input === "s" && !(state.phase === "checkpoint" && checkpointRecap)) {
12705
12752
  setShowSessionMenu(true);
12706
12753
  return;
12707
12754
  }
@@ -12718,16 +12765,16 @@ function MarathonApp({
12718
12765
  return;
12719
12766
  }
12720
12767
  }
12721
- if (state.phase === "steering" && steeringRecap) {
12722
- if (!isAllowedSteeringBrowseKey({ ...key, input: _input }, showFilesPanel || showEventStream || showToolsPanel)) return;
12768
+ if (state.phase === "checkpoint" && checkpointRecap) {
12769
+ if (!isAllowedCheckpointBrowseKey({ ...key, input: _input }, showFilesPanel || showEventStream || showToolsPanel)) return;
12723
12770
  }
12724
12771
  if (key.shift && key.leftArrow) {
12725
- markSteeringExploring();
12772
+ markCheckpointExploring();
12726
12773
  selectSessionTab(getAdjacentSessionTabKey(allTabs, selectedSessionKey, -1));
12727
12774
  return;
12728
12775
  }
12729
12776
  if (key.shift && key.rightArrow) {
12730
- markSteeringExploring();
12777
+ markCheckpointExploring();
12731
12778
  selectSessionTab(getAdjacentSessionTabKey(allTabs, selectedSessionKey, 1));
12732
12779
  return;
12733
12780
  }
@@ -12741,7 +12788,7 @@ function MarathonApp({
12741
12788
  return;
12742
12789
  }
12743
12790
  if (key.tab) {
12744
- markSteeringExploring();
12791
+ markCheckpointExploring();
12745
12792
  if (showFilesPanel) {
12746
12793
  setDetailFile(null);
12747
12794
  setDetailFileContent(null);
@@ -12769,8 +12816,8 @@ function MarathonApp({
12769
12816
  return;
12770
12817
  }
12771
12818
  if (key.escape) {
12772
- if (state.phase === "steering" && steeringRecap) {
12773
- markSteeringExploring();
12819
+ if (state.phase === "checkpoint" && checkpointRecap) {
12820
+ markCheckpointExploring();
12774
12821
  }
12775
12822
  if (detailFile) {
12776
12823
  setDetailFile(null);
@@ -12881,8 +12928,8 @@ function MarathonApp({
12881
12928
  return;
12882
12929
  }
12883
12930
  if (key.upArrow) {
12884
- if (state.phase === "steering" && steeringRecap) {
12885
- markSteeringExploring();
12931
+ if (state.phase === "checkpoint" && checkpointRecap) {
12932
+ markCheckpointExploring();
12886
12933
  }
12887
12934
  if (showReasoningPanel) {
12888
12935
  setReasoningScrollOffset((prev) => Math.max(0, prev - SCROLL_STEP));
@@ -12900,15 +12947,15 @@ function MarathonApp({
12900
12947
  setEventCursor((prev) => Math.max(0, prev - 1));
12901
12948
  } else {
12902
12949
  const contentLines = displayedContent.split("\n").length * 2;
12903
- const visibleRows = state.phase === "steering" && steeringRecap ? adjustedContentHeight - steeringPromptRows : transcriptRows;
12950
+ const visibleRows = state.phase === "checkpoint" && checkpointRecap ? adjustedContentHeight - checkpointPromptRows : transcriptRows;
12904
12951
  const maxOverviewScroll = Math.max(0, contentLines - visibleRows);
12905
12952
  setScrollOffset((prev) => Math.min(prev + SCROLL_STEP, maxOverviewScroll));
12906
12953
  }
12907
12954
  return;
12908
12955
  }
12909
12956
  if (key.downArrow) {
12910
- if (state.phase === "steering" && steeringRecap) {
12911
- markSteeringExploring();
12957
+ if (state.phase === "checkpoint" && checkpointRecap) {
12958
+ markCheckpointExploring();
12912
12959
  }
12913
12960
  if (showReasoningPanel) {
12914
12961
  const totalReasoningLines = getReasoningLines(displayedReasoning, terminalWidth).length;
@@ -13049,7 +13096,7 @@ function MarathonApp({
13049
13096
  if (clipboardFlash) return clipboardFlash;
13050
13097
  if (showUpgradeModal) return joinHints("u: upgrade", "Enter: upgrade", "Esc: close", "Ctrl+C");
13051
13098
  if (state.phase === "error" && !canBrowseAfterUpgradeError) return joinHints("Enter: exit", "Ctrl+C");
13052
- if (state.phase === "steering" && steeringRecap) {
13099
+ if (state.phase === "checkpoint" && checkpointRecap) {
13053
13100
  return joinHints(
13054
13101
  "\u2191\u2193: scroll",
13055
13102
  activeScreen !== "overview" ? "Esc: overview" : void 0,
@@ -13121,8 +13168,8 @@ function MarathonApp({
13121
13168
  const goalText = goal || "";
13122
13169
  const goalExpandedRows = goalExpanded && goalText.length > terminalWidth - 4 ? Math.ceil(goalText.length / (terminalWidth - 4)) - 1 : 0;
13123
13170
  const adjustedContentHeight = contentHeight - goalExpandedRows - SCREEN_TABS_ROWS - (hasTabs ? SESSION_TABS_ROWS : 0);
13124
- const steeringWarningRows = steeringRecap?.historyWarning ? Math.max(1, Math.ceil(steeringRecap.historyWarning.length / Math.max(10, terminalWidth - 6))) : 0;
13125
- const steeringPromptRows = STEERING_PROMPT_ROWS + steeringWarningRows;
13171
+ const checkpointWarningRows = checkpointRecap?.historyWarning ? Math.max(1, Math.ceil(checkpointRecap.historyWarning.length / Math.max(10, terminalWidth - 6))) : 0;
13172
+ const checkpointPromptRows = CHECKPOINT_PROMPT_ROWS + checkpointWarningRows;
13126
13173
  const reasoningHintRows = hasReasoning ? 1 : 0;
13127
13174
  const thinkingRows = showThinkingIndicator ? 2 : 0;
13128
13175
  const compactionIndicatorRows = showContextCompactionIndicator ? 1 : 0;
@@ -13165,7 +13212,9 @@ function MarathonApp({
13165
13212
  showRunner,
13166
13213
  showFinish,
13167
13214
  trackWidth: terminalWidth,
13168
- previewUrl
13215
+ previewUrl,
13216
+ workflowMilestones,
13217
+ currentMilestone
13169
13218
  }
13170
13219
  ),
13171
13220
  /* @__PURE__ */ jsx31(Box26, { marginLeft: 1, children: /* @__PURE__ */ jsx31(ScreenTabs, { activeTab: activeScreen, width: terminalWidth - 1, shortcutHint: "Tab: next screen" }) }),
@@ -13255,7 +13304,7 @@ function MarathonApp({
13255
13304
  }
13256
13305
  )
13257
13306
  }
13258
- ) : state.phase === "steering" && steeringRecap ? /* @__PURE__ */ jsxs25(
13307
+ ) : state.phase === "checkpoint" && checkpointRecap ? /* @__PURE__ */ jsxs25(
13259
13308
  Box26,
13260
13309
  {
13261
13310
  flexDirection: isStacked ? "column" : "row",
@@ -13277,38 +13326,38 @@ function MarathonApp({
13277
13326
  content: displayedContent,
13278
13327
  isStreaming: false,
13279
13328
  enableMarkdown: !plainText,
13280
- maxVisibleLines: adjustedContentHeight - steeringPromptRows - 1,
13329
+ maxVisibleLines: adjustedContentHeight - checkpointPromptRows - 1,
13281
13330
  scrollOffset
13282
13331
  }
13283
13332
  ) }),
13284
13333
  (() => {
13285
- const steeringVisibleRows = adjustedContentHeight - steeringPromptRows - 1;
13286
- const steeringTotalLines = displayedContent.split("\n").length;
13287
- const steeringMaxScroll = Math.max(0, steeringTotalLines - steeringVisibleRows);
13334
+ const checkpointVisibleRows = adjustedContentHeight - checkpointPromptRows - 1;
13335
+ const checkpointTotalLines = displayedContent.split("\n").length;
13336
+ const checkpointMaxScroll = Math.max(0, checkpointTotalLines - checkpointVisibleRows);
13288
13337
  return /* @__PURE__ */ jsx31(
13289
13338
  Scrollbar,
13290
13339
  {
13291
- totalLines: steeringTotalLines,
13292
- visibleLines: steeringVisibleRows,
13293
- scrollOffset: steeringMaxScroll - Math.min(scrollOffset, steeringMaxScroll),
13294
- height: steeringVisibleRows
13340
+ totalLines: checkpointTotalLines,
13341
+ visibleLines: checkpointVisibleRows,
13342
+ scrollOffset: checkpointMaxScroll - Math.min(scrollOffset, checkpointMaxScroll),
13343
+ height: checkpointVisibleRows
13295
13344
  }
13296
13345
  );
13297
13346
  })()
13298
13347
  ] }),
13299
13348
  /* @__PURE__ */ jsx31(
13300
- SteeringPrompt,
13349
+ CheckpointPrompt,
13301
13350
  {
13302
- onSubmit: handleSteeringSubmit,
13351
+ onSubmit: handleCheckpointSubmit,
13303
13352
  onToggleHelp: () => setShowHelpOverlay((prev) => !prev),
13304
13353
  onCopySession: handleCopySession,
13305
13354
  onOpenStateFile: handleOpenStateFile,
13306
- timeout: steeringTimeout ?? 10,
13307
- isTerminal: isTerminalSteering,
13355
+ timeout: checkpointTimeout ?? 10,
13356
+ isTerminal: isTerminalCheckpoint,
13308
13357
  currentModel,
13309
13358
  currentSandbox,
13310
- recap: steeringRecap,
13311
- isReviewing: isSteeringExploring
13359
+ recap: checkpointRecap,
13360
+ isReviewing: isCheckpointExploring
13312
13361
  }
13313
13362
  )
13314
13363
  ]
@@ -13458,7 +13507,7 @@ function MarathonApp({
13458
13507
  {
13459
13508
  width: terminalWidth,
13460
13509
  height: adjustedContentHeight,
13461
- isSteering: state.phase === "steering" && !!steeringRecap,
13510
+ isCheckpoint: state.phase === "checkpoint" && !!checkpointRecap,
13462
13511
  scrollOffset: helpScrollOffset
13463
13512
  }
13464
13513
  )
@@ -13545,11 +13594,13 @@ function MarathonApp({
13545
13594
  import { useEffect as useEffect21, useRef as useRef9, useState as useState23 } from "react";
13546
13595
  import { Box as Box27, Text as Text29, useApp as useApp5, useInput as useInput12, useStdout as useStdout6 } from "ink";
13547
13596
  import { theme as theme31 } from "@runtypelabs/ink-components";
13597
+ import { AnimatedVariant, StartupGridIconCompactLightInverted } from "@runtypelabs/terminal-animations";
13548
13598
  import { jsx as jsx32, jsxs as jsxs26 } from "react/jsx-runtime";
13549
13599
  var SCROLL_HINT = "\u2191\u2193 Enter 1-3";
13550
13600
  var PROMPT_COLUMN_MAX = 68;
13551
13601
  var MIN_HOLD_MS = 1500;
13552
13602
  var FADE_DURATION_MS = 260;
13603
+ var STARTUP_BACKGROUND_VARIANTS = ["vector3d-ghost-wave", "vector3d-ghost-box-drift"];
13553
13604
  function shortenPath2(filePath, maxLength) {
13554
13605
  if (filePath.length <= maxLength) return filePath;
13555
13606
  const normalizedSegments = filePath.split("/").filter(Boolean);
@@ -13599,6 +13650,7 @@ function MarathonStartupShell({
13599
13650
  const mountedAtRef = useRef9(Date.now());
13600
13651
  const promptResolverRef = useRef9(null);
13601
13652
  const modelResolverRef = useRef9(null);
13653
+ const playbookConfirmResolverRef = useRef9(null);
13602
13654
  const appReadyResolverRef = useRef9(null);
13603
13655
  const dismissResolverRef = useRef9(null);
13604
13656
  const transitionPromiseRef = useRef9(null);
@@ -13609,20 +13661,24 @@ function MarathonStartupShell({
13609
13661
  const [prompt, setPrompt] = useState23(null);
13610
13662
  const [modelChoices, setModelChoices] = useState23(null);
13611
13663
  const [currentModel, setCurrentModel] = useState23("default");
13664
+ const [playbookConfirm, setPlaybookConfirm] = useState23(null);
13612
13665
  const [selectedPromptIndex, setSelectedPromptIndex] = useState23(0);
13666
+ const [backgroundVariantId] = useState23(
13667
+ () => STARTUP_BACKGROUND_VARIANTS[Math.floor(Math.random() * STARTUP_BACKGROUND_VARIANTS.length)]
13668
+ );
13613
13669
  latestAppPropsRef.current = marathonAppProps;
13614
13670
  useEffect21(() => {
13615
13671
  if (scene !== "app" || !appReadyResolverRef.current) return;
13616
- const resolve6 = appReadyResolverRef.current;
13672
+ const resolve7 = appReadyResolverRef.current;
13617
13673
  appReadyResolverRef.current = null;
13618
- resolve6();
13674
+ resolve7();
13619
13675
  }, [scene]);
13620
13676
  const beginTransition = (target) => {
13621
13677
  if (transitionPromiseRef.current) return transitionPromiseRef.current;
13622
13678
  if (target === "app" && !latestAppPropsRef.current) {
13623
13679
  throw new Error("Cannot complete startup before marathon app props are ready.");
13624
13680
  }
13625
- const promise = new Promise((resolve6) => {
13681
+ const promise = new Promise((resolve7) => {
13626
13682
  globalThis.setTimeout(() => {
13627
13683
  setPrompt(null);
13628
13684
  setModelChoices(null);
@@ -13643,12 +13699,12 @@ function MarathonStartupShell({
13643
13699
  if (target === "app") {
13644
13700
  appReadyResolverRef.current = () => {
13645
13701
  transitionPromiseRef.current = null;
13646
- resolve6();
13702
+ resolve7();
13647
13703
  };
13648
13704
  } else {
13649
13705
  dismissResolverRef.current = () => {
13650
13706
  transitionPromiseRef.current = null;
13651
- resolve6();
13707
+ resolve7();
13652
13708
  };
13653
13709
  }
13654
13710
  }, Math.max(0, MIN_HOLD_MS - (Date.now() - mountedAtRef.current)));
@@ -13665,16 +13721,33 @@ function MarathonStartupShell({
13665
13721
  setModelChoices(null);
13666
13722
  setPrompt(nextPrompt);
13667
13723
  setSelectedPromptIndex(0);
13668
- return new Promise((resolve6) => {
13669
- promptResolverRef.current = resolve6;
13724
+ return new Promise((resolve7) => {
13725
+ promptResolverRef.current = resolve7;
13670
13726
  });
13671
13727
  },
13672
13728
  requestModelChoice: async (nextCurrentModel, models) => {
13673
13729
  setPrompt(null);
13730
+ setPlaybookConfirm(null);
13674
13731
  setCurrentModel(nextCurrentModel);
13675
13732
  setModelChoices(models);
13676
- return new Promise((resolve6) => {
13677
- modelResolverRef.current = resolve6;
13733
+ return new Promise((resolve7) => {
13734
+ modelResolverRef.current = resolve7;
13735
+ });
13736
+ },
13737
+ requestPlaybookModelConfirm: async (playbookName, milestoneModels) => {
13738
+ setPrompt(null);
13739
+ setModelChoices(null);
13740
+ const names = Object.keys(milestoneModels);
13741
+ setPlaybookConfirm({
13742
+ name: playbookName,
13743
+ milestoneModels: { ...milestoneModels },
13744
+ originalModels: { ...milestoneModels },
13745
+ milestoneNames: names,
13746
+ // Default selection is the "Confirm" action (first item after milestones)
13747
+ selectedIndex: names.length
13748
+ });
13749
+ return new Promise((resolve7) => {
13750
+ playbookConfirmResolverRef.current = resolve7;
13678
13751
  });
13679
13752
  },
13680
13753
  completeStartup: () => beginTransition("app"),
@@ -13690,7 +13763,7 @@ function MarathonStartupShell({
13690
13763
  process.kill(process.pid, "SIGINT");
13691
13764
  return;
13692
13765
  }
13693
- if (!prompt || transition || modelChoices) return;
13766
+ if (!prompt || transition || modelChoices || playbookConfirm) return;
13694
13767
  if (key.upArrow || input === "k") {
13695
13768
  setSelectedPromptIndex((current) => (current - 1 + prompt.choices.length) % prompt.choices.length);
13696
13769
  return;
@@ -13719,97 +13792,253 @@ function MarathonStartupShell({
13719
13792
  }
13720
13793
  }
13721
13794
  },
13722
- { isActive: scene !== "app" }
13795
+ { isActive: scene !== "app" && !playbookConfirm && !modelChoices }
13796
+ );
13797
+ useInput12(
13798
+ (input, key) => {
13799
+ if (key.ctrl && input === "c") {
13800
+ process.kill(process.pid, "SIGINT");
13801
+ return;
13802
+ }
13803
+ if (!playbookConfirm) return;
13804
+ const hasEdits = JSON.stringify(playbookConfirm.milestoneModels) !== JSON.stringify(playbookConfirm.originalModels);
13805
+ const actionCount = hasEdits ? 3 : 2;
13806
+ const totalItems = playbookConfirm.milestoneNames.length + actionCount;
13807
+ if (key.upArrow || input === "k") {
13808
+ setPlaybookConfirm((prev) => prev ? {
13809
+ ...prev,
13810
+ selectedIndex: (prev.selectedIndex - 1 + totalItems) % totalItems
13811
+ } : null);
13812
+ return;
13813
+ }
13814
+ if (key.downArrow || input === "j") {
13815
+ setPlaybookConfirm((prev) => prev ? {
13816
+ ...prev,
13817
+ selectedIndex: (prev.selectedIndex + 1) % totalItems
13818
+ } : null);
13819
+ return;
13820
+ }
13821
+ if (key.return) {
13822
+ const idx = playbookConfirm.selectedIndex;
13823
+ const milestoneCount = playbookConfirm.milestoneNames.length;
13824
+ if (idx < milestoneCount) {
13825
+ const milestone = playbookConfirm.milestoneNames[idx];
13826
+ playbookConfirmResolverRef.current?.({
13827
+ action: "edit",
13828
+ milestoneModels: playbookConfirm.milestoneModels,
13829
+ editMilestone: milestone
13830
+ });
13831
+ playbookConfirmResolverRef.current = null;
13832
+ setPlaybookConfirm(null);
13833
+ } else {
13834
+ const actionIndex = idx - milestoneCount;
13835
+ if (actionIndex === 0) {
13836
+ playbookConfirmResolverRef.current?.({
13837
+ action: "confirm",
13838
+ milestoneModels: playbookConfirm.milestoneModels
13839
+ });
13840
+ playbookConfirmResolverRef.current = null;
13841
+ setPlaybookConfirm(null);
13842
+ } else if (actionIndex === 1) {
13843
+ playbookConfirmResolverRef.current?.({ action: "override" });
13844
+ playbookConfirmResolverRef.current = null;
13845
+ setPlaybookConfirm(null);
13846
+ } else if (actionIndex === 2 && hasEdits) {
13847
+ setPlaybookConfirm((prev) => prev ? {
13848
+ ...prev,
13849
+ milestoneModels: { ...prev.originalModels },
13850
+ selectedIndex: prev.milestoneNames.length
13851
+ // back to Confirm
13852
+ } : null);
13853
+ }
13854
+ }
13855
+ }
13856
+ },
13857
+ { isActive: scene !== "app" && !!playbookConfirm }
13723
13858
  );
13724
13859
  const terminalWidth = stdout?.columns ?? 120;
13725
13860
  const terminalRows = stdout?.rows ?? 40;
13726
13861
  const promptColumnWidth = Math.max(44, Math.min(PROMPT_COLUMN_MAX, terminalWidth - 14));
13727
13862
  const promptShellModel = prompt ? buildPromptShellModel(prompt, promptColumnWidth) : null;
13728
13863
  const selectedChoice = prompt?.choices[selectedPromptIndex];
13864
+ const contentWidth = prompt || modelChoices || playbookConfirm ? promptColumnWidth : void 0;
13729
13865
  if (scene === "app" && marathonAppProps) {
13730
13866
  return /* @__PURE__ */ jsx32(MarathonApp, { ...marathonAppProps });
13731
13867
  }
13732
- return /* @__PURE__ */ jsx32(
13733
- Box27,
13734
- {
13735
- width: terminalWidth,
13736
- height: terminalRows,
13737
- backgroundColor: theme31.background,
13738
- flexDirection: "column",
13739
- justifyContent: "center",
13740
- alignItems: "center",
13741
- children: /* @__PURE__ */ jsxs26(Box27, { flexDirection: "column", alignItems: "center", children: [
13742
- !prompt && !modelChoices && /* @__PURE__ */ jsx32(Box27, { width: terminalWidth, justifyContent: "center", children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textMuted, children: statusMessage }) }),
13743
- modelChoices && /* @__PURE__ */ jsx32(Box27, { width: promptColumnWidth, flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx32(
13744
- ModelPicker,
13868
+ return /* @__PURE__ */ jsxs26(Box27, { width: terminalWidth, height: terminalRows, backgroundColor: theme31.background, children: [
13869
+ /* @__PURE__ */ jsx32(
13870
+ Box27,
13871
+ {
13872
+ position: "absolute",
13873
+ width: terminalWidth,
13874
+ height: terminalRows,
13875
+ overflow: "hidden",
13876
+ children: /* @__PURE__ */ jsx32(
13877
+ AnimatedVariant,
13745
13878
  {
13746
- currentModel,
13747
- models: modelChoices,
13748
- onSelect: (model) => {
13749
- modelResolverRef.current?.(model);
13750
- modelResolverRef.current = null;
13751
- setCurrentModel(model);
13752
- setModelChoices(null);
13753
- },
13754
- onCancel: () => {
13755
- modelResolverRef.current?.(currentModel);
13756
- modelResolverRef.current = null;
13757
- setModelChoices(null);
13758
- }
13879
+ variantId: backgroundVariantId,
13880
+ width: terminalWidth,
13881
+ height: terminalRows
13759
13882
  }
13760
- ) }),
13761
- prompt && /* @__PURE__ */ jsxs26(Box27, { width: promptColumnWidth, flexDirection: "column", marginTop: 1, children: [
13762
- /* @__PURE__ */ jsx32(Text29, { bold: true, color: theme31.text, children: promptShellModel?.heading }),
13763
- promptShellModel?.task && /* @__PURE__ */ jsxs26(Box27, { marginTop: 1, children: [
13764
- /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: "task " }),
13765
- /* @__PURE__ */ jsx32(Text29, { color: theme31.text, children: promptShellModel.task })
13766
- ] }),
13767
- promptShellModel?.filePath && /* @__PURE__ */ jsxs26(Box27, { children: [
13768
- /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: "state " }),
13769
- /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: promptShellModel.filePath })
13770
- ] }),
13771
- promptShellModel?.metaLine && /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: promptShellModel.metaLine }) }),
13772
- /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: prompt.hint }) }),
13773
- /* @__PURE__ */ jsx32(Box27, { flexDirection: "column", marginTop: 1, children: prompt.choices.map((choice, index) => {
13774
- const isSelected = index === selectedPromptIndex;
13775
- return /* @__PURE__ */ jsxs26(Box27, { marginTop: index === 0 ? 0 : 1, children: [
13776
- /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.accent : theme31.textSubtle, bold: isSelected, children: isSelected ? "\u203A " : " " }),
13777
- /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.text : theme31.textSubtle, bold: isSelected, children: `${index + 1} ${choice.label}` })
13778
- ] }, choice.value);
13779
- }) }),
13780
- selectedChoice && /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: selectedChoice.description }) }),
13781
- /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.border, children: SCROLL_HINT }) })
13782
- ] })
13783
- ] })
13784
- }
13785
- );
13883
+ )
13884
+ }
13885
+ ),
13886
+ /* @__PURE__ */ jsx32(
13887
+ Box27,
13888
+ {
13889
+ position: "absolute",
13890
+ width: terminalWidth,
13891
+ height: terminalRows,
13892
+ alignItems: "flex-start",
13893
+ justifyContent: "flex-start",
13894
+ paddingX: 1,
13895
+ paddingY: 1,
13896
+ children: /* @__PURE__ */ jsx32(StartupGridIconCompactLightInverted, { autoplay: false })
13897
+ }
13898
+ ),
13899
+ /* @__PURE__ */ jsx32(
13900
+ Box27,
13901
+ {
13902
+ width: terminalWidth,
13903
+ height: terminalRows,
13904
+ flexDirection: "column",
13905
+ justifyContent: "center",
13906
+ alignItems: "center",
13907
+ children: /* @__PURE__ */ jsxs26(
13908
+ Box27,
13909
+ {
13910
+ flexDirection: "column",
13911
+ alignItems: prompt || modelChoices || playbookConfirm ? "stretch" : "center",
13912
+ backgroundColor: theme31.background,
13913
+ paddingX: 2,
13914
+ paddingY: 1,
13915
+ children: [
13916
+ !prompt && !modelChoices && !playbookConfirm && /* @__PURE__ */ jsx32(Text29, { color: theme31.textMuted, children: statusMessage }),
13917
+ playbookConfirm && !modelChoices && (() => {
13918
+ const milestoneCount = playbookConfirm.milestoneNames.length;
13919
+ const hasEdits = JSON.stringify(playbookConfirm.milestoneModels) !== JSON.stringify(playbookConfirm.originalModels);
13920
+ const actions = [
13921
+ "Confirm",
13922
+ "Use single model instead",
13923
+ ...hasEdits ? ["Discard changes"] : []
13924
+ ];
13925
+ return /* @__PURE__ */ jsxs26(Box27, { width: contentWidth, flexDirection: "column", marginTop: 1, children: [
13926
+ /* @__PURE__ */ jsxs26(Text29, { bold: true, color: theme31.text, children: [
13927
+ "Playbook: ",
13928
+ playbookConfirm.name
13929
+ ] }),
13930
+ /* @__PURE__ */ jsx32(Box27, { marginTop: 1, flexDirection: "column", children: playbookConfirm.milestoneNames.map((milestone, i) => {
13931
+ const isSelected = i === playbookConfirm.selectedIndex;
13932
+ const model = playbookConfirm.milestoneModels[milestone];
13933
+ const wasEdited = model !== playbookConfirm.originalModels[milestone];
13934
+ return /* @__PURE__ */ jsxs26(Box27, { children: [
13935
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.accent : theme31.textSubtle, children: isSelected ? "\u203A " : " " }),
13936
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.text : theme31.textSubtle, bold: isSelected, children: milestone.padEnd(16) }),
13937
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.accent : theme31.textMuted, children: model }),
13938
+ wasEdited && /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: " (edited)" })
13939
+ ] }, milestone);
13940
+ }) }),
13941
+ /* @__PURE__ */ jsx32(Box27, { marginTop: 1, flexDirection: "column", children: actions.map((label, ai) => {
13942
+ const isSelected = playbookConfirm.selectedIndex === milestoneCount + ai;
13943
+ return /* @__PURE__ */ jsxs26(Box27, { children: [
13944
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.accent : theme31.textSubtle, children: isSelected ? "\u203A " : " " }),
13945
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.text : theme31.textSubtle, bold: isSelected, children: label })
13946
+ ] }, label);
13947
+ }) }),
13948
+ /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.border, children: "\u2191\u2193 navigate \xB7 Enter select" }) })
13949
+ ] });
13950
+ })(),
13951
+ modelChoices && /* @__PURE__ */ jsx32(Box27, { width: contentWidth, flexDirection: "column", marginTop: 1, children: /* @__PURE__ */ jsx32(
13952
+ ModelPicker,
13953
+ {
13954
+ currentModel,
13955
+ models: modelChoices,
13956
+ onSelect: (model) => {
13957
+ modelResolverRef.current?.(model);
13958
+ modelResolverRef.current = null;
13959
+ setCurrentModel(model);
13960
+ setModelChoices(null);
13961
+ },
13962
+ onCancel: () => {
13963
+ modelResolverRef.current?.(currentModel);
13964
+ modelResolverRef.current = null;
13965
+ setModelChoices(null);
13966
+ }
13967
+ }
13968
+ ) }),
13969
+ prompt && /* @__PURE__ */ jsxs26(Box27, { width: contentWidth, flexDirection: "column", marginTop: 1, children: [
13970
+ /* @__PURE__ */ jsx32(Text29, { bold: true, color: theme31.text, children: promptShellModel?.heading }),
13971
+ promptShellModel?.task && /* @__PURE__ */ jsxs26(Box27, { marginTop: 1, children: [
13972
+ /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: "task " }),
13973
+ /* @__PURE__ */ jsx32(Text29, { color: theme31.text, children: promptShellModel.task })
13974
+ ] }),
13975
+ promptShellModel?.filePath && /* @__PURE__ */ jsxs26(Box27, { children: [
13976
+ /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: "state " }),
13977
+ /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: promptShellModel.filePath })
13978
+ ] }),
13979
+ promptShellModel?.metaLine && /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: promptShellModel.metaLine }) }),
13980
+ /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: prompt.hint }) }),
13981
+ /* @__PURE__ */ jsx32(Box27, { flexDirection: "column", marginTop: 1, children: prompt.choices.map((choice, index) => {
13982
+ const isSelected = index === selectedPromptIndex;
13983
+ return /* @__PURE__ */ jsxs26(Box27, { marginTop: index === 0 ? 0 : 1, children: [
13984
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.accent : theme31.textSubtle, bold: isSelected, children: isSelected ? "\u203A " : " " }),
13985
+ /* @__PURE__ */ jsx32(Text29, { color: isSelected ? theme31.text : theme31.textSubtle, bold: isSelected, children: `${index + 1} ${choice.label}` })
13986
+ ] }, choice.value);
13987
+ }) }),
13988
+ selectedChoice && /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.textSubtle, children: selectedChoice.description }) }),
13989
+ /* @__PURE__ */ jsx32(Box27, { marginTop: 1, children: /* @__PURE__ */ jsx32(Text29, { color: theme31.border, children: SCROLL_HINT }) })
13990
+ ] })
13991
+ ]
13992
+ }
13993
+ )
13994
+ }
13995
+ )
13996
+ ] });
13786
13997
  }
13787
13998
 
13788
13999
  // src/commands/agents-task.ts
13789
- import * as fs12 from "fs";
14000
+ import * as fs13 from "fs";
13790
14001
  import {
13791
14002
  RuntypeClient as RuntypeClient2,
14003
+ defaultWorkflow,
13792
14004
  deployWorkflow,
13793
14005
  gameWorkflow
13794
14006
  } from "@runtypelabs/sdk";
13795
14007
 
14008
+ // src/lib/terminal-title.ts
14009
+ import path4 from "path";
14010
+ function setTerminalTitle(title) {
14011
+ if (process.stdout.isTTY) {
14012
+ process.stdout.write(`\x1B]0;${title}\x07`);
14013
+ }
14014
+ }
14015
+ function getFolderName() {
14016
+ return path4.basename(process.cwd());
14017
+ }
14018
+ function setCliTitle() {
14019
+ setTerminalTitle(`Runtype (${getFolderName()})`);
14020
+ }
14021
+ function setMarathonTitle() {
14022
+ setTerminalTitle(`Runtype: Marathon (${getFolderName()})`);
14023
+ }
14024
+
13796
14025
  // src/marathon/checkpoint.ts
13797
14026
  import * as fs6 from "fs";
13798
- import * as path5 from "path";
14027
+ import * as path6 from "path";
13799
14028
 
13800
14029
  // src/config/paths.ts
13801
14030
  import * as os3 from "os";
13802
- import * as path4 from "path";
14031
+ import * as path5 from "path";
13803
14032
  import * as crypto3 from "crypto";
13804
14033
  import * as fs5 from "fs";
13805
14034
  function getRuntypeHomeDir() {
13806
- return process.env.RUNTYPE_STATE_DIR || path4.join(os3.homedir(), ".runtype");
14035
+ return process.env.RUNTYPE_STATE_DIR || path5.join(os3.homedir(), ".runtype");
13807
14036
  }
13808
14037
  function getProjectStateDir(projectDir) {
13809
14038
  const dir = projectDir || process.cwd();
13810
14039
  const hash = crypto3.createHash("sha256").update(dir).digest("hex").slice(0, 12);
13811
- const stateDir = path4.join(getRuntypeHomeDir(), "projects", hash);
13812
- const breadcrumb = path4.join(stateDir, "project-path.txt");
14040
+ const stateDir = path5.join(getRuntypeHomeDir(), "projects", hash);
14041
+ const breadcrumb = path5.join(stateDir, "project-path.txt");
13813
14042
  if (!fs5.existsSync(breadcrumb)) {
13814
14043
  fs5.mkdirSync(stateDir, { recursive: true });
13815
14044
  fs5.writeFileSync(breadcrumb, dir);
@@ -13817,7 +14046,7 @@ function getProjectStateDir(projectDir) {
13817
14046
  return stateDir;
13818
14047
  }
13819
14048
  function getMarathonStateDir(projectDir) {
13820
- return path4.join(getProjectStateDir(projectDir), "marathons");
14049
+ return path5.join(getProjectStateDir(projectDir), "marathons");
13821
14050
  }
13822
14051
 
13823
14052
  // src/marathon/checkpoint.ts
@@ -13828,12 +14057,12 @@ function stateSafeName(name) {
13828
14057
  return name.replace(/[^a-zA-Z0-9_-]/g, "_");
13829
14058
  }
13830
14059
  function marathonArtifactsDir(taskName, stateDir) {
13831
- return path5.join(stateDir || defaultStateDir(), stateSafeName(taskName));
14060
+ return path6.join(stateDir || defaultStateDir(), stateSafeName(taskName));
13832
14061
  }
13833
14062
  function resolveMarathonCheckpointPath(taskName, targetPath, stateDir) {
13834
- const normalizedTargetPath = path5.normalize(targetPath);
13835
- const relativeTargetPath = path5.isAbsolute(normalizedTargetPath) ? path5.relative(process.cwd(), normalizedTargetPath) : normalizedTargetPath;
13836
- return path5.join(
14063
+ const normalizedTargetPath = path6.normalize(targetPath);
14064
+ const relativeTargetPath = path6.isAbsolute(normalizedTargetPath) ? path6.relative(process.cwd(), normalizedTargetPath) : normalizedTargetPath;
14065
+ return path6.join(
13837
14066
  marathonArtifactsDir(taskName, stateDir),
13838
14067
  "checkpoints",
13839
14068
  "original",
@@ -13841,23 +14070,23 @@ function resolveMarathonCheckpointPath(taskName, targetPath, stateDir) {
13841
14070
  );
13842
14071
  }
13843
14072
  function ensureMarathonFileCheckpoint(taskName, targetPath, stateDir) {
13844
- const normalizedTargetPath = path5.resolve(targetPath);
13845
- const relativeTargetPath = path5.relative(process.cwd(), normalizedTargetPath);
14073
+ const normalizedTargetPath = path6.resolve(targetPath);
14074
+ const relativeTargetPath = path6.relative(process.cwd(), normalizedTargetPath);
13846
14075
  if (!relativeTargetPath || relativeTargetPath.startsWith("..")) return void 0;
13847
14076
  if (!fs6.existsSync(normalizedTargetPath)) return void 0;
13848
14077
  if (!fs6.statSync(normalizedTargetPath).isFile()) return void 0;
13849
14078
  const normalizedRelativeTargetPath = relativeTargetPath.replace(/\\/g, "/");
13850
14079
  if (normalizedRelativeTargetPath.startsWith(".runtype/")) return void 0;
13851
- if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path5.sep)) return void 0;
14080
+ if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path6.sep)) return void 0;
13852
14081
  const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir);
13853
14082
  if (fs6.existsSync(checkpointPath)) return checkpointPath;
13854
- fs6.mkdirSync(path5.dirname(checkpointPath), { recursive: true });
14083
+ fs6.mkdirSync(path6.dirname(checkpointPath), { recursive: true });
13855
14084
  fs6.copyFileSync(normalizedTargetPath, checkpointPath);
13856
14085
  return checkpointPath;
13857
14086
  }
13858
14087
  function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir) {
13859
- const normalizedTargetPath = path5.resolve(targetPath);
13860
- const relativeTargetPath = path5.relative(process.cwd(), normalizedTargetPath);
14088
+ const normalizedTargetPath = path6.resolve(targetPath);
14089
+ const relativeTargetPath = path6.relative(process.cwd(), normalizedTargetPath);
13861
14090
  if (!relativeTargetPath || relativeTargetPath.startsWith("..")) {
13862
14091
  return {
13863
14092
  restored: false,
@@ -13872,7 +14101,7 @@ function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir) {
13872
14101
  error: `No checkpoint found for ${normalizedTargetPath}`
13873
14102
  };
13874
14103
  }
13875
- fs6.mkdirSync(path5.dirname(normalizedTargetPath), { recursive: true });
14104
+ fs6.mkdirSync(path6.dirname(normalizedTargetPath), { recursive: true });
13876
14105
  fs6.copyFileSync(checkpointPath, normalizedTargetPath);
13877
14106
  return { restored: true, checkpointPath };
13878
14107
  }
@@ -13930,7 +14159,7 @@ async function retryOnNetworkError(fn, opts = {}) {
13930
14159
  }
13931
14160
  const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
13932
14161
  opts.onRetry?.(attempt + 1, delay, error);
13933
- await new Promise((resolve6) => setTimeout(resolve6, delay));
14162
+ await new Promise((resolve7) => setTimeout(resolve7, delay));
13934
14163
  }
13935
14164
  }
13936
14165
  throw lastError;
@@ -14046,7 +14275,7 @@ function isSafeVerificationCommand(command) {
14046
14275
 
14047
14276
  // src/marathon/state.ts
14048
14277
  import * as fs7 from "fs";
14049
- import * as path6 from "path";
14278
+ import * as path7 from "path";
14050
14279
  import chalk15 from "chalk";
14051
14280
 
14052
14281
  // src/lib/select-prompt.ts
@@ -14081,14 +14310,14 @@ async function promptNumericSelect(choices, promptLabel) {
14081
14310
  output: process.stdout,
14082
14311
  terminal: true
14083
14312
  });
14084
- return new Promise((resolve6) => {
14313
+ return new Promise((resolve7) => {
14085
14314
  const ask = () => {
14086
14315
  rl.question(chalk14.cyan(`
14087
14316
  ${promptLabel} (1-${choices.length}): `), (answer) => {
14088
14317
  const value = parseInt(answer.trim(), 10);
14089
14318
  if (value >= 1 && value <= choices.length) {
14090
14319
  rl.close();
14091
- resolve6(choices[value - 1].value);
14320
+ resolve7(choices[value - 1].value);
14092
14321
  return;
14093
14322
  }
14094
14323
  console.log(chalk14.red(`Please enter a number between 1 and ${choices.length}.`));
@@ -14116,7 +14345,7 @@ ${message}`));
14116
14345
  const previousRawMode = input.isRaw === true;
14117
14346
  let selectedIndex = 0;
14118
14347
  let renderedLineCount = 0;
14119
- return new Promise((resolve6) => {
14348
+ return new Promise((resolve7) => {
14120
14349
  const renderMenu = () => {
14121
14350
  if (renderedLineCount > 0) {
14122
14351
  clearRenderedLines(output, renderedLineCount);
@@ -14138,7 +14367,7 @@ ${message}`));
14138
14367
  };
14139
14368
  const finish = (value) => {
14140
14369
  cleanup();
14141
- resolve6(value);
14370
+ resolve7(value);
14142
14371
  };
14143
14372
  const onKeypress = (_, key) => {
14144
14373
  if (key.ctrl && key.name === "c") {
@@ -14184,7 +14413,7 @@ function stateSafeName2(name) {
14184
14413
  }
14185
14414
  function stateFilePath(name, stateDir) {
14186
14415
  const dir = stateDir || defaultStateDir2();
14187
- return path6.join(dir, `${stateSafeName2(name)}.json`);
14416
+ return path7.join(dir, `${stateSafeName2(name)}.json`);
14188
14417
  }
14189
14418
  function normalizeMarathonStatePath(candidatePath) {
14190
14419
  if (!candidatePath) return void 0;
@@ -14193,15 +14422,15 @@ function normalizeMarathonStatePath(candidatePath) {
14193
14422
  }
14194
14423
  function marathonStatePathExists(candidatePath) {
14195
14424
  if (!candidatePath) return false;
14196
- return fs7.existsSync(path6.resolve(candidatePath));
14425
+ return fs7.existsSync(path7.resolve(candidatePath));
14197
14426
  }
14198
14427
  function isMarathonArtifactStatePath(candidatePath) {
14199
14428
  if (!candidatePath) return false;
14200
14429
  const normalized = normalizeMarathonStatePath(candidatePath)?.toLowerCase();
14201
14430
  if (normalized === ".runtype" || normalized?.startsWith(".runtype/") === true) return true;
14202
- const resolved = path6.resolve(candidatePath);
14431
+ const resolved = path7.resolve(candidatePath);
14203
14432
  const homeStateDir = getRuntypeHomeDir();
14204
- return resolved.startsWith(homeStateDir + path6.sep) || resolved === homeStateDir;
14433
+ return resolved.startsWith(homeStateDir + path7.sep) || resolved === homeStateDir;
14205
14434
  }
14206
14435
  function scoreMarathonCandidatePath(candidatePath) {
14207
14436
  const normalized = candidatePath.toLowerCase();
@@ -14374,7 +14603,7 @@ function loadState(filePath) {
14374
14603
  }
14375
14604
  }
14376
14605
  function saveState(filePath, state, options) {
14377
- const dir = path6.dirname(filePath);
14606
+ const dir = path7.dirname(filePath);
14378
14607
  fs7.mkdirSync(dir, { recursive: true });
14379
14608
  const stateToWrite = options?.stripSnapshotEvents && state.sessionSnapshots?.length ? {
14380
14609
  ...state,
@@ -14412,9 +14641,9 @@ function findStateFile(name, stateDir) {
14412
14641
  }
14413
14642
  const homePath = stateFilePath(name, getMarathonStateDir());
14414
14643
  if (fs7.existsSync(homePath)) return homePath;
14415
- const oldMarathonPath = stateFilePath(name, path6.join(process.cwd(), ".runtype", "marathons"));
14644
+ const oldMarathonPath = stateFilePath(name, path7.join(process.cwd(), ".runtype", "marathons"));
14416
14645
  if (fs7.existsSync(oldMarathonPath)) return oldMarathonPath;
14417
- const oldTaskPath = stateFilePath(name, path6.join(process.cwd(), ".runtype", "tasks"));
14646
+ const oldTaskPath = stateFilePath(name, path7.join(process.cwd(), ".runtype", "tasks"));
14418
14647
  if (fs7.existsSync(oldTaskPath)) return oldTaskPath;
14419
14648
  return homePath;
14420
14649
  }
@@ -14423,15 +14652,15 @@ function findLatestStateFile(stateDir) {
14423
14652
  // New home-dir location
14424
14653
  getMarathonStateDir(),
14425
14654
  // Old project-dir locations (backward compat)
14426
- path6.join(process.cwd(), ".runtype", "marathons"),
14427
- path6.join(process.cwd(), ".runtype", "tasks")
14655
+ path7.join(process.cwd(), ".runtype", "marathons"),
14656
+ path7.join(process.cwd(), ".runtype", "tasks")
14428
14657
  ];
14429
14658
  let latest = null;
14430
14659
  for (const dir of dirs) {
14431
14660
  if (!fs7.existsSync(dir)) continue;
14432
14661
  const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".json"));
14433
14662
  for (const file of files) {
14434
- const fullPath = path6.join(dir, file);
14663
+ const fullPath = path7.join(dir, file);
14435
14664
  const stat = fs7.statSync(fullPath);
14436
14665
  if (!latest || stat.mtimeMs > latest.mtime) {
14437
14666
  latest = { name: file.replace(".json", ""), filePath: fullPath, mtime: stat.mtimeMs };
@@ -14553,12 +14782,12 @@ function buildResumeCommand(agent, options, parsedSandbox) {
14553
14782
 
14554
14783
  // src/marathon/local-tools.ts
14555
14784
  import * as fs9 from "fs";
14556
- import * as path8 from "path";
14785
+ import * as path9 from "path";
14557
14786
  import { spawnSync } from "child_process";
14558
14787
 
14559
14788
  // src/marathon/repo-discovery.ts
14560
14789
  import * as fs8 from "fs";
14561
- import * as path7 from "path";
14790
+ import * as path8 from "path";
14562
14791
  var IGNORED_REPO_DIRS = /* @__PURE__ */ new Set([
14563
14792
  ".git",
14564
14793
  ".next",
@@ -14633,8 +14862,8 @@ var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
14633
14862
  "do"
14634
14863
  ]);
14635
14864
  function normalizeToolPath(toolPath) {
14636
- const resolved = path7.resolve(toolPath || ".");
14637
- return path7.relative(process.cwd(), resolved) || ".";
14865
+ const resolved = path8.resolve(toolPath || ".");
14866
+ return path8.relative(process.cwd(), resolved) || ".";
14638
14867
  }
14639
14868
  function tokenizeSearchQuery(query) {
14640
14869
  return query.toLowerCase().split(/[^a-z0-9./_-]+/g).map((token) => token.trim()).filter((token) => token.length >= 2 && !SEARCH_STOP_WORDS.has(token));
@@ -14643,7 +14872,7 @@ function scoreSearchPath(relativePath) {
14643
14872
  const normalized = relativePath.replace(/\\/g, "/");
14644
14873
  const segments = normalized.split("/");
14645
14874
  const fileName = segments[segments.length - 1] || normalized;
14646
- const extension = path7.extname(fileName).toLowerCase();
14875
+ const extension = path8.extname(fileName).toLowerCase();
14647
14876
  let score = 0;
14648
14877
  if (LOW_SIGNAL_FILE_NAMES.has(fileName)) score -= 50;
14649
14878
  if (segments.some((segment) => LOW_SIGNAL_PATH_SEGMENTS.has(segment))) score -= 15;
@@ -14658,7 +14887,7 @@ function scoreSearchPath(relativePath) {
14658
14887
  function shouldIgnoreRepoEntry(entryPath) {
14659
14888
  const normalized = normalizeToolPath(entryPath);
14660
14889
  if (normalized === ".") return false;
14661
- return normalized.split(path7.sep).some((segment) => IGNORED_REPO_DIRS.has(segment));
14890
+ return normalized.split(path8.sep).some((segment) => IGNORED_REPO_DIRS.has(segment));
14662
14891
  }
14663
14892
  function safeReadTextFile(filePath) {
14664
14893
  try {
@@ -14684,7 +14913,7 @@ function walkRepo(startPath, visitor) {
14684
14913
  continue;
14685
14914
  }
14686
14915
  for (const entry of entries) {
14687
- const entryPath = path7.join(currentDir, entry.name);
14916
+ const entryPath = path8.join(currentDir, entry.name);
14688
14917
  if (shouldIgnoreRepoEntry(entryPath)) continue;
14689
14918
  const shouldStop = visitor(entryPath, entry);
14690
14919
  if (shouldStop === true) return;
@@ -14729,7 +14958,7 @@ function buildTree(dirPath, maxDepth, depth = 0) {
14729
14958
  }
14730
14959
  const lines = [];
14731
14960
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
14732
- const entryPath = path7.join(dirPath, entry.name);
14961
+ const entryPath = path8.join(dirPath, entry.name);
14733
14962
  if (shouldIgnoreRepoEntry(entryPath)) continue;
14734
14963
  lines.push(`${" ".repeat(depth)}- ${entry.name}${entry.isDirectory() ? "/" : ""}`);
14735
14964
  if (entry.isDirectory() && depth < maxDepth) {
@@ -14745,14 +14974,14 @@ function stateSafeName3(name) {
14745
14974
  return name.replace(/[^a-zA-Z0-9_-]/g, "_");
14746
14975
  }
14747
14976
  function getOffloadedOutputDir(taskName, stateDir) {
14748
- return path8.join(stateDir || getMarathonStateDir(), stateSafeName3(taskName), "outputs");
14977
+ return path9.join(stateDir || getMarathonStateDir(), stateSafeName3(taskName), "outputs");
14749
14978
  }
14750
14979
  function isPathWithinRoot(targetPath, rootPath) {
14751
- const relativePath = path8.relative(rootPath, targetPath);
14752
- return relativePath === "" || !relativePath.startsWith("..") && !path8.isAbsolute(relativePath);
14980
+ const relativePath = path9.relative(rootPath, targetPath);
14981
+ return relativePath === "" || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath);
14753
14982
  }
14754
14983
  function resolveCanonicalToolPath(toolPath, allowMissing) {
14755
- const absolutePath = path8.resolve(toolPath);
14984
+ const absolutePath = path9.resolve(toolPath);
14756
14985
  if (fs9.existsSync(absolutePath)) {
14757
14986
  return {
14758
14987
  canonicalPath: fs9.realpathSync.native(absolutePath),
@@ -14763,22 +14992,22 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
14763
14992
  const missingSegments = [];
14764
14993
  let currentPath = absolutePath;
14765
14994
  while (!fs9.existsSync(currentPath)) {
14766
- const parentPath = path8.dirname(currentPath);
14995
+ const parentPath = path9.dirname(currentPath);
14767
14996
  if (parentPath === currentPath) return null;
14768
- missingSegments.unshift(path8.basename(currentPath));
14997
+ missingSegments.unshift(path9.basename(currentPath));
14769
14998
  currentPath = parentPath;
14770
14999
  }
14771
15000
  return {
14772
- canonicalPath: path8.join(fs9.realpathSync.native(currentPath), ...missingSegments),
15001
+ canonicalPath: path9.join(fs9.realpathSync.native(currentPath), ...missingSegments),
14773
15002
  exists: false
14774
15003
  };
14775
15004
  }
14776
15005
  function canonicalizeAllowedRoot(rootPath) {
14777
- return resolveCanonicalToolPath(rootPath, true)?.canonicalPath || path8.resolve(rootPath);
15006
+ return resolveCanonicalToolPath(rootPath, true)?.canonicalPath || path9.resolve(rootPath);
14778
15007
  }
14779
15008
  function findBlockedWorkspaceSegment(targetPath, workspaceRoot) {
14780
15009
  if (!isPathWithinRoot(targetPath, workspaceRoot)) return void 0;
14781
- const relativePath = path8.relative(workspaceRoot, targetPath).replace(/\\/g, "/");
15010
+ const relativePath = path9.relative(workspaceRoot, targetPath).replace(/\\/g, "/");
14782
15011
  if (!relativePath) return void 0;
14783
15012
  return relativePath.split("/").find((segment) => DIRECT_TOOL_BLOCKED_SEGMENTS.has(segment));
14784
15013
  }
@@ -14871,7 +15100,7 @@ function createDefaultLocalTools(context) {
14871
15100
  if (fs9.existsSync(resolvedPath.resolvedPath) && fs9.statSync(resolvedPath.resolvedPath).isDirectory()) {
14872
15101
  return `Error: Path is a directory: ${String(args.path || "")}`;
14873
15102
  }
14874
- const dir = path8.dirname(resolvedPath.resolvedPath);
15103
+ const dir = path9.dirname(resolvedPath.resolvedPath);
14875
15104
  fs9.mkdirSync(dir, { recursive: true });
14876
15105
  fs9.writeFileSync(resolvedPath.resolvedPath, content);
14877
15106
  return "ok";
@@ -14888,7 +15117,7 @@ function createDefaultLocalTools(context) {
14888
15117
  requireDirectory: true
14889
15118
  });
14890
15119
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
14891
- return fs9.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path8.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
15120
+ return fs9.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path9.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
14892
15121
  }
14893
15122
  },
14894
15123
  search_repo: {
@@ -15053,7 +15282,7 @@ function createCheckpointedWriteFileTool(taskName, stateDir) {
15053
15282
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
15054
15283
  const content = String(args.content || "");
15055
15284
  ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir);
15056
- const dir = path8.dirname(resolvedPath.resolvedPath);
15285
+ const dir = path9.dirname(resolvedPath.resolvedPath);
15057
15286
  fs9.mkdirSync(dir, { recursive: true });
15058
15287
  fs9.writeFileSync(resolvedPath.resolvedPath, content);
15059
15288
  return "ok";
@@ -15102,7 +15331,7 @@ function createReadOffloadedOutputTool(taskName, stateDir) {
15102
15331
  if (!/^[a-zA-Z0-9_-]+$/.test(outputId)) {
15103
15332
  return `Error: invalid offloaded output id: ${outputId}`;
15104
15333
  }
15105
- const outputPath = path8.join(getOffloadedOutputDir(taskName, stateDir), `${outputId}.txt`);
15334
+ const outputPath = path9.join(getOffloadedOutputDir(taskName, stateDir), `${outputId}.txt`);
15106
15335
  if (!fs9.existsSync(outputPath) || !fs9.statSync(outputPath).isFile()) {
15107
15336
  return `Error: offloaded output not found: ${outputId}`;
15108
15337
  }
@@ -15265,7 +15494,7 @@ function createLoopDetector(options = {}) {
15265
15494
  cachedLoopInfo = null;
15266
15495
  dirty = false;
15267
15496
  },
15268
- getSteeringMessage() {
15497
+ getCheckpointMessage() {
15269
15498
  const info = this.getLoopInfo();
15270
15499
  if (!info) return "";
15271
15500
  return [
@@ -15284,7 +15513,7 @@ function createLoopDetector(options = {}) {
15284
15513
 
15285
15514
  // src/marathon/context-offload.ts
15286
15515
  import * as fs10 from "fs";
15287
- import * as path9 from "path";
15516
+ import * as path10 from "path";
15288
15517
  var DEFAULT_OUTPUT_THRESHOLD = 2e3;
15289
15518
  var DEFAULT_PREVIEW_LENGTH = 200;
15290
15519
  function buildOffloadedOutputId(toolName) {
@@ -15316,13 +15545,13 @@ function offloadToolOutput(taskName, toolName, output, options = {}, stateDir) {
15316
15545
  return { offloaded: false, content: output };
15317
15546
  }
15318
15547
  const outputId = buildOffloadedOutputId(toolName);
15319
- const dir = path9.join(
15548
+ const dir = path10.join(
15320
15549
  stateDir || defaultStateDir3(),
15321
15550
  stateSafeName4(taskName),
15322
15551
  "outputs"
15323
15552
  );
15324
15553
  const fileName = `${outputId}.txt`;
15325
- const filePath = path9.join(dir, fileName);
15554
+ const filePath = path10.join(dir, fileName);
15326
15555
  fs10.mkdirSync(dir, { recursive: true });
15327
15556
  fs10.writeFileSync(filePath, output, "utf-8");
15328
15557
  const preview = output.slice(0, previewLength).replace(/\n/g, " ");
@@ -15497,42 +15726,17 @@ function resolveAutoCompactTokenThreshold(modelContextLength, rawThreshold, stra
15497
15726
 
15498
15727
  // src/marathon/recipes.ts
15499
15728
  import * as fs11 from "fs";
15500
- import * as path10 from "path";
15501
- var RECIPES_DIR = ".marathon/recipes";
15502
- function loadRecipe(nameOrPath, cwd) {
15503
- const baseCwd = cwd || process.cwd();
15504
- const candidates = [
15505
- // Exact path
15506
- path10.resolve(baseCwd, nameOrPath),
15507
- // .marathon/recipes/<name>.json
15508
- path10.resolve(baseCwd, RECIPES_DIR, `${nameOrPath}.json`),
15509
- // .marathon/recipes/<name>/recipe.json
15510
- path10.resolve(baseCwd, RECIPES_DIR, nameOrPath, "recipe.json")
15511
- ];
15512
- for (const candidate of candidates) {
15513
- if (!fs11.existsSync(candidate)) continue;
15514
- try {
15515
- const raw = fs11.readFileSync(candidate, "utf-8");
15516
- const parsed = JSON.parse(raw);
15517
- if (parsed.meta?.name && parsed.meta?.version) {
15518
- return parsed;
15519
- }
15520
- } catch {
15521
- continue;
15522
- }
15523
- }
15524
- return null;
15525
- }
15729
+ import * as path11 from "path";
15526
15730
  var RULES_DIR = ".marathon/rules";
15527
15731
  function loadRules(cwd) {
15528
15732
  const baseCwd = cwd || process.cwd();
15529
- const rulesDir = path10.resolve(baseCwd, RULES_DIR);
15733
+ const rulesDir = path11.resolve(baseCwd, RULES_DIR);
15530
15734
  if (!fs11.existsSync(rulesDir)) return [];
15531
15735
  const rules = [];
15532
15736
  try {
15533
15737
  const entries = fs11.readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
15534
15738
  for (const entry of entries) {
15535
- const filePath = path10.join(rulesDir, entry);
15739
+ const filePath = path11.join(rulesDir, entry);
15536
15740
  try {
15537
15741
  const raw = fs11.readFileSync(filePath, "utf-8");
15538
15742
  const parsed = parseRuleFile(raw);
@@ -15567,26 +15771,154 @@ function extractYamlArray(yaml, key) {
15567
15771
  if (!match) return [];
15568
15772
  return match[1].split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
15569
15773
  }
15570
- function resolveModelForPhase(phase, recipe, cliOverrides) {
15774
+ function resolveModelForPhase(phase, cliOverrides, milestoneModels) {
15775
+ if (phase && milestoneModels?.[phase]) {
15776
+ return milestoneModels[phase];
15777
+ }
15571
15778
  if (phase === "research" || phase === "planning") {
15572
15779
  if (cliOverrides.planningModel) return cliOverrides.planningModel;
15573
15780
  }
15574
15781
  if (phase === "execution") {
15575
15782
  if (cliOverrides.executionModel) return cliOverrides.executionModel;
15576
15783
  }
15577
- if (recipe?.model) {
15578
- if (phase === "research" && recipe.model.research) return recipe.model.research;
15579
- if (phase === "planning" && recipe.model.planning) return recipe.model.planning;
15580
- if (phase === "execution" && recipe.model.execution) return recipe.model.execution;
15581
- }
15582
15784
  return cliOverrides.defaultModel;
15583
15785
  }
15584
15786
 
15787
+ // src/marathon/playbook-loader.ts
15788
+ import * as fs12 from "fs";
15789
+ import * as path12 from "path";
15790
+ import * as os4 from "os";
15791
+ import { parse as parseYaml } from "yaml";
15792
+ var PLAYBOOKS_DIR = ".runtype/marathons/playbooks";
15793
+ function getCandidatePaths(nameOrPath, cwd) {
15794
+ const home = os4.homedir();
15795
+ return [
15796
+ // Exact path
15797
+ path12.resolve(cwd, nameOrPath),
15798
+ // Repo-level
15799
+ path12.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
15800
+ path12.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
15801
+ path12.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.json`),
15802
+ // User-level
15803
+ path12.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
15804
+ path12.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
15805
+ path12.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`)
15806
+ ];
15807
+ }
15808
+ function parsePlaybookFile(filePath) {
15809
+ const raw = fs12.readFileSync(filePath, "utf-8");
15810
+ const ext = path12.extname(filePath).toLowerCase();
15811
+ if (ext === ".json") {
15812
+ return JSON.parse(raw);
15813
+ }
15814
+ return parseYaml(raw);
15815
+ }
15816
+ function validatePlaybook(config2, filePath) {
15817
+ if (!config2.name) {
15818
+ throw new Error(`Playbook at ${filePath} is missing required 'name' field`);
15819
+ }
15820
+ if (!config2.milestones || config2.milestones.length === 0) {
15821
+ throw new Error(`Playbook '${config2.name}' must have at least one milestone`);
15822
+ }
15823
+ for (const milestone of config2.milestones) {
15824
+ if (!milestone.name) {
15825
+ throw new Error(`Playbook '${config2.name}': each milestone must have a 'name'`);
15826
+ }
15827
+ if (!milestone.instructions) {
15828
+ throw new Error(`Playbook '${config2.name}': milestone '${milestone.name}' is missing 'instructions'`);
15829
+ }
15830
+ }
15831
+ }
15832
+ function interpolate(template, state) {
15833
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
15834
+ const value = state[key];
15835
+ if (value === void 0 || value === null) return `{{${key}}}`;
15836
+ return String(value);
15837
+ });
15838
+ }
15839
+ function buildIsComplete(criteria) {
15840
+ if (!criteria) return () => false;
15841
+ switch (criteria.type) {
15842
+ case "evidence":
15843
+ return (ctx) => {
15844
+ const minFiles = criteria.minReadFiles ?? 1;
15845
+ return (ctx.state.recentReadPaths?.length ?? 0) >= minFiles;
15846
+ };
15847
+ case "sessions": {
15848
+ let baselineSessionCount;
15849
+ return (ctx) => {
15850
+ const minSessions = criteria.minSessions ?? 1;
15851
+ if (baselineSessionCount === void 0) {
15852
+ baselineSessionCount = ctx.state.sessions.length;
15853
+ }
15854
+ return ctx.state.sessions.length - baselineSessionCount >= minSessions;
15855
+ };
15856
+ }
15857
+ case "planWritten":
15858
+ return (ctx) => {
15859
+ return ctx.trace.planWritten;
15860
+ };
15861
+ case "never":
15862
+ default:
15863
+ return () => false;
15864
+ }
15865
+ }
15866
+ function convertToWorkflow(config2) {
15867
+ const phases = config2.milestones.map((milestone) => ({
15868
+ name: milestone.name,
15869
+ description: milestone.description,
15870
+ buildInstructions(state) {
15871
+ const header = `--- Workflow Phase: ${milestone.name} ---`;
15872
+ const desc = milestone.description ? `
15873
+ ${milestone.description}` : "";
15874
+ const instructions = interpolate(milestone.instructions, state);
15875
+ return `${header}${desc}
15876
+ ${instructions}`;
15877
+ },
15878
+ buildToolGuidance(_state) {
15879
+ return milestone.toolGuidance ?? [];
15880
+ },
15881
+ isComplete: buildIsComplete(milestone.completionCriteria),
15882
+ // Default to rejecting TASK_COMPLETE unless the playbook explicitly allows it.
15883
+ // The SDK accepts completion by default when canAcceptCompletion is undefined,
15884
+ // which would let the model end the marathon prematurely in early phases.
15885
+ canAcceptCompletion: milestone.canAcceptCompletion !== void 0 ? () => milestone.canAcceptCompletion : () => false
15886
+ }));
15887
+ return {
15888
+ name: config2.name,
15889
+ phases
15890
+ };
15891
+ }
15892
+ function loadPlaybook(nameOrPath, cwd) {
15893
+ const baseCwd = cwd || process.cwd();
15894
+ const candidates = getCandidatePaths(nameOrPath, baseCwd);
15895
+ for (const candidate of candidates) {
15896
+ if (!fs12.existsSync(candidate)) continue;
15897
+ const config2 = parsePlaybookFile(candidate);
15898
+ validatePlaybook(config2, candidate);
15899
+ const milestoneModels = {};
15900
+ for (const m of config2.milestones) {
15901
+ if (m.model) milestoneModels[m.name] = m.model;
15902
+ }
15903
+ return {
15904
+ workflow: convertToWorkflow(config2),
15905
+ milestones: config2.milestones.map((m) => m.name),
15906
+ milestoneModels: Object.keys(milestoneModels).length > 0 ? milestoneModels : void 0,
15907
+ verification: config2.verification,
15908
+ rules: config2.rules
15909
+ };
15910
+ }
15911
+ throw new Error(
15912
+ `Playbook '${nameOrPath}' not found. Searched:
15913
+ ${candidates.map((c) => ` ${c}`).join("\n")}`
15914
+ );
15915
+ }
15916
+
15585
15917
  // src/commands/agents-task.ts
15586
15918
  var import_builtin_tools_registry = __toESM(require_builtin_tools_registry(), 1);
15587
15919
  var import_provider_routing = __toESM(require_provider_routing(), 1);
15588
- function shouldRequestResumeSteering(status, resumeMessage, noSteer, originalMessage, continuations) {
15589
- if (status !== "paused" || noSteer) return false;
15920
+ function shouldRequestResumeCheckpoint(status, resumeMessage, noCheckpoint, originalMessage, continuations) {
15921
+ if (status !== "paused" || noCheckpoint) return false;
15590
15922
  const normalizedResumeMessage = resumeMessage?.trim();
15591
15923
  if (!normalizedResumeMessage) return true;
15592
15924
  const normalizedOriginalMessage = originalMessage?.trim();
@@ -15600,7 +15932,7 @@ function shouldRequestResumeSteering(status, resumeMessage, noSteer, originalMes
15600
15932
  }
15601
15933
  return false;
15602
15934
  }
15603
- function buildResumeSteeringRecap(state, snapshots) {
15935
+ function buildResumeCheckpointRecap(state, snapshots) {
15604
15936
  const latestSnapshot = snapshots[snapshots.length - 1];
15605
15937
  const derivedTokens = state.sessions?.reduce(
15606
15938
  (totals, session) => ({
@@ -15780,15 +16112,6 @@ async function taskAction(agent, options) {
15780
16112
  console.error(chalk16.red(error instanceof Error ? error.message : String(error)));
15781
16113
  process.exit(1);
15782
16114
  }
15783
- let recipe = null;
15784
- if (options.recipe) {
15785
- recipe = loadRecipe(options.recipe);
15786
- if (!recipe) {
15787
- console.error(chalk16.red(`Recipe not found: "${options.recipe}"`));
15788
- console.log(chalk16.gray(" Place recipes in .marathon/recipes/<name>.json"));
15789
- process.exit(1);
15790
- }
15791
- }
15792
16115
  const rules = loadRules();
15793
16116
  const rulesContext = rules.length > 0 ? rules.map((r) => r.content).join("\n\n---\n\n") : "";
15794
16117
  const allModels = [
@@ -15796,10 +16119,7 @@ async function taskAction(agent, options) {
15796
16119
  [
15797
16120
  options.model,
15798
16121
  options.planningModel,
15799
- options.executionModel,
15800
- recipe?.model?.planning,
15801
- recipe?.model?.execution,
15802
- recipe?.model?.research
16122
+ options.executionModel
15803
16123
  ].filter((m) => Boolean(m))
15804
16124
  )
15805
16125
  ];
@@ -15841,7 +16161,7 @@ async function taskAction(agent, options) {
15841
16161
  waitForUiExit = renderedShell.waitUntilExit;
15842
16162
  rerenderUi = renderedShell.rerender;
15843
16163
  unmountUi = renderedShell.unmount;
15844
- await new Promise((resolve6) => setTimeout(resolve6, 0));
16164
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
15845
16165
  if (!startupShellRef.current) {
15846
16166
  exitAltScreen();
15847
16167
  unmountUi?.();
@@ -15962,7 +16282,7 @@ async function taskAction(agent, options) {
15962
16282
  forcedResumeFilePath = stateResolution.filePath;
15963
16283
  } else {
15964
16284
  resumeRequested = false;
15965
- if (options.fresh && fs12.existsSync(filePath)) {
16285
+ if (options.fresh && fs13.existsSync(filePath)) {
15966
16286
  if (useStartupShell) {
15967
16287
  setStartupStatus("starting fresh");
15968
16288
  } else {
@@ -15989,7 +16309,7 @@ async function taskAction(agent, options) {
15989
16309
  let persistedSessionSnapshots = [];
15990
16310
  let resumeState;
15991
16311
  let resumeLoadedState = null;
15992
- let shouldPromptResumeSteering = false;
16312
+ let shouldPromptResumeCheckpoint = false;
15993
16313
  let resumeHistoryWarning;
15994
16314
  let eventLogWriter;
15995
16315
  if (resumeRequested) {
@@ -16038,15 +16358,15 @@ async function taskAction(agent, options) {
16038
16358
  resumeLoadedState = existing;
16039
16359
  resumeHistoryWarning = buildResumeHistoryWarning(existing);
16040
16360
  const resumeMessage = typeof options.resume === "string" ? options.resume : options.goal;
16041
- shouldPromptResumeSteering = shouldRequestResumeSteering(
16361
+ shouldPromptResumeCheckpoint = shouldRequestResumeCheckpoint(
16042
16362
  existing.status,
16043
16363
  resumeMessage,
16044
- options.noSteer ?? false,
16364
+ options.noCheckpoint ?? false,
16045
16365
  existing.originalMessage,
16046
16366
  existing.continuations
16047
16367
  );
16048
16368
  previousMessages = existing.messages ?? [];
16049
- continuationMessage = shouldPromptResumeSteering ? void 0 : resumeMessage;
16369
+ continuationMessage = shouldPromptResumeCheckpoint ? void 0 : resumeMessage;
16050
16370
  useCompact = options.compact ?? false;
16051
16371
  priorContinuations = [...existing.continuations ?? []];
16052
16372
  resumeState = extractRunTaskResumeState(existing);
@@ -16071,19 +16391,64 @@ async function taskAction(agent, options) {
16071
16391
  }
16072
16392
  }
16073
16393
  }
16394
+ let playbookWorkflow;
16395
+ let playbookMilestones;
16396
+ let playbookMilestoneModels;
16397
+ if (options.playbook) {
16398
+ const result = loadPlaybook(options.playbook);
16399
+ playbookWorkflow = result.workflow;
16400
+ playbookMilestones = result.milestones;
16401
+ playbookMilestoneModels = result.milestoneModels;
16402
+ }
16074
16403
  if (useStartupShell && !options.model?.trim()) {
16075
- let startupModelOptions = buildMarathonStartupModelOptions(configuredModels, availableModels);
16076
- if (startupModelOptions.length === 0) {
16077
- startupModelOptions = DEFAULT_MODELS;
16078
- }
16079
- const initialStartupModel = agentConfigModel || defaultConfiguredModel || startupModelOptions[0]?.value;
16080
- if (startupModelOptions.length > 0 && initialStartupModel && startupShellRef.current) {
16081
- setStartupStatus("selecting model");
16082
- options.model = await startupShellRef.current.requestModelChoice(
16083
- initialStartupModel,
16084
- startupModelOptions
16085
- );
16086
- setStartupStatus(`model ready: ${options.model}`);
16404
+ if (playbookMilestoneModels && Object.keys(playbookMilestoneModels).length > 0 && startupShellRef.current) {
16405
+ let editableModels = { ...playbookMilestoneModels };
16406
+ let confirmed = false;
16407
+ while (!confirmed) {
16408
+ setStartupStatus("reviewing playbook models");
16409
+ const confirmResult = await startupShellRef.current.requestPlaybookModelConfirm(
16410
+ options.playbook,
16411
+ editableModels
16412
+ );
16413
+ if (confirmResult.action === "confirm") {
16414
+ playbookMilestoneModels = confirmResult.milestoneModels ?? editableModels;
16415
+ const firstMilestoneModel = Object.values(playbookMilestoneModels)[0];
16416
+ if (firstMilestoneModel) options.model = firstMilestoneModel;
16417
+ confirmed = true;
16418
+ } else if (confirmResult.action === "edit" && confirmResult.editMilestone) {
16419
+ let startupModelOptions = buildMarathonStartupModelOptions(configuredModels, availableModels);
16420
+ if (startupModelOptions.length === 0) {
16421
+ startupModelOptions = DEFAULT_MODELS;
16422
+ }
16423
+ const currentMilestoneModel = editableModels[confirmResult.editMilestone];
16424
+ const pickedModel = await startupShellRef.current.requestModelChoice(
16425
+ currentMilestoneModel,
16426
+ startupModelOptions
16427
+ );
16428
+ editableModels = {
16429
+ ...confirmResult.milestoneModels ?? editableModels,
16430
+ [confirmResult.editMilestone]: pickedModel
16431
+ };
16432
+ } else {
16433
+ playbookMilestoneModels = void 0;
16434
+ confirmed = true;
16435
+ }
16436
+ }
16437
+ }
16438
+ if (!playbookMilestoneModels) {
16439
+ let startupModelOptions = buildMarathonStartupModelOptions(configuredModels, availableModels);
16440
+ if (startupModelOptions.length === 0) {
16441
+ startupModelOptions = DEFAULT_MODELS;
16442
+ }
16443
+ const initialStartupModel = agentConfigModel || defaultConfiguredModel || startupModelOptions[0]?.value;
16444
+ if (startupModelOptions.length > 0 && initialStartupModel && startupShellRef.current) {
16445
+ setStartupStatus("selecting model");
16446
+ options.model = await startupShellRef.current.requestModelChoice(
16447
+ initialStartupModel,
16448
+ startupModelOptions
16449
+ );
16450
+ setStartupStatus(`model ready: ${options.model}`);
16451
+ }
16087
16452
  }
16088
16453
  }
16089
16454
  try {
@@ -16109,23 +16474,25 @@ async function taskAction(agent, options) {
16109
16474
  const remainingCost = maxCost ? maxCost - priorCost : void 0;
16110
16475
  const baseMessage = options.goal || (resumeRequested ? "Continue the task." : "");
16111
16476
  const sandboxPrompt = parsedSandbox ? createSandboxInstructions(parsedSandbox) : "";
16112
- const resolvedWorkflow = detectDeployWorkflow(baseMessage, parsedSandbox, resumeState);
16477
+ const resolvedWorkflow = playbookWorkflow ?? detectDeployWorkflow(baseMessage, parsedSandbox, resumeState);
16478
+ const DEFAULT_MILESTONE_NAMES = ["research", "planning", "execution"];
16479
+ const EXTERNAL_MILESTONE_NAMES = ["research", "report"];
16480
+ const detectedVariant = resumeState?.workflowVariant ?? defaultWorkflow.classifyVariant?.(baseMessage);
16481
+ const defaultMilestones = detectedVariant === "external" ? EXTERNAL_MILESTONE_NAMES : DEFAULT_MILESTONE_NAMES;
16482
+ const workflowMilestones = playbookMilestones ?? (resolvedWorkflow ? resolvedWorkflow.phases.map((p) => p.name) : defaultMilestones);
16113
16483
  let taskMessage = baseMessage;
16114
16484
  if (sandboxPrompt && !resolvedWorkflow) taskMessage = `${taskMessage}
16115
16485
 
16116
16486
  ${sandboxPrompt}`;
16117
- if (recipe?.rules) taskMessage = `${taskMessage}
16118
-
16119
- ${recipe.rules}`;
16120
16487
  if (rulesContext) taskMessage = `${taskMessage}
16121
16488
 
16122
16489
  ${rulesContext}`;
16123
16490
  const currentPhase = resumeState?.workflowPhase;
16124
- const resolvedModel = resolveModelForPhase(currentPhase, recipe, {
16491
+ const resolvedModel = resolveModelForPhase(currentPhase, {
16125
16492
  planningModel: options.planningModel,
16126
16493
  executionModel: options.executionModel,
16127
16494
  defaultModel: options.model
16128
- });
16495
+ }, playbookMilestoneModels);
16129
16496
  if (resolvedModel) options.model = resolvedModel;
16130
16497
  let localTools = buildLocalTools(client, parsedSandbox, options, {
16131
16498
  taskName,
@@ -16186,12 +16553,12 @@ ${rulesContext}`;
16186
16553
  }
16187
16554
  }
16188
16555
  let toolsEnabled = true;
16189
- const steeringHasConfigChanges = (result) => Boolean(result.model) || result.tools === true || result.sandbox !== void 0;
16190
- const rebuildSteeringTools = () => buildLocalTools(client, parsedSandbox, options, {
16556
+ const checkpointHasConfigChanges = (result) => Boolean(result.model) || result.tools === true || result.sandbox !== void 0;
16557
+ const rebuildCheckpointTools = () => buildLocalTools(client, parsedSandbox, options, {
16191
16558
  taskName,
16192
16559
  stateDir: options.stateDir
16193
16560
  });
16194
- const applySteeringConfig = (result) => {
16561
+ const applyCheckpointConfig = (result) => {
16195
16562
  if (result.model) {
16196
16563
  options.model = result.model;
16197
16564
  }
@@ -16205,12 +16572,12 @@ ${rulesContext}`;
16205
16572
  }
16206
16573
  }
16207
16574
  if (toolsEnabled) {
16208
- localTools = rebuildSteeringTools();
16575
+ localTools = rebuildCheckpointTools();
16209
16576
  }
16210
16577
  }
16211
16578
  if (result.tools) {
16212
16579
  toolsEnabled = !toolsEnabled;
16213
- localTools = toolsEnabled ? rebuildSteeringTools() : void 0;
16580
+ localTools = toolsEnabled ? rebuildCheckpointTools() : void 0;
16214
16581
  }
16215
16582
  };
16216
16583
  const loopDetector = createLoopDetector();
@@ -16235,8 +16602,10 @@ ${rulesContext}`;
16235
16602
  stateFilePath: filePath,
16236
16603
  debug: options.debug,
16237
16604
  plainText: options.plainText ?? false,
16238
- noSteer: options.noSteer ?? false,
16239
- steeringTimeout: parseInt(options.steerTimeout || "10", 10),
16605
+ noCheckpoint: options.noCheckpoint ?? false,
16606
+ checkpointTimeout: parseInt(options.checkpointTimeout || "10", 10),
16607
+ workflowMilestones: workflowMilestones.length > 0 ? workflowMilestones : void 0,
16608
+ currentMilestone: detectedVariant === "external" && resumeState?.workflowPhase === "research" && resumeLoadedState?.planWritten ? "report" : resumeState?.workflowPhase ?? workflowMilestones[0],
16240
16609
  dashboardUrl: marathonDashboardBaseUrl,
16241
16610
  billingUrl: `${marathonDashboardBaseUrl}/settings/billing`,
16242
16611
  onSaveState: (payload) => {
@@ -16268,6 +16637,7 @@ Saving state... done. Session saved to ${filePath}`);
16268
16637
  },
16269
16638
  streamRef
16270
16639
  };
16640
+ setMarathonTitle();
16271
16641
  if (useStartupShell) {
16272
16642
  setStartupStatus("preparing marathon");
16273
16643
  rerenderUi?.(
@@ -16286,7 +16656,7 @@ Saving state... done. Session saved to ${filePath}`);
16286
16656
  waitForUiExit = renderedApp.waitUntilExit;
16287
16657
  unmountUi = renderedApp.unmount;
16288
16658
  }
16289
- await new Promise((resolve6) => setTimeout(resolve6, 0));
16659
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
16290
16660
  const streamActions = streamRef.current;
16291
16661
  if (!streamActions) {
16292
16662
  exitAltScreen();
@@ -16300,14 +16670,14 @@ Saving state... done. Session saved to ${filePath}`);
16300
16670
  if (persistedSessionSnapshots.length > 0) {
16301
16671
  streamActions.hydrateSessionSnapshots(persistedSessionSnapshots);
16302
16672
  }
16303
- if (resumeLoadedState && shouldPromptResumeSteering) {
16304
- const recap = buildResumeSteeringRecap(resumeLoadedState, persistedSessionSnapshots);
16673
+ if (resumeLoadedState && shouldPromptResumeCheckpoint) {
16674
+ const recap = buildResumeCheckpointRecap(resumeLoadedState, persistedSessionSnapshots);
16305
16675
  if (resumeHistoryWarning) {
16306
16676
  recap.historyWarning = resumeHistoryWarning;
16307
16677
  }
16308
- const steeringResult = await streamActions.requestSteering(recap);
16309
- applySteeringConfig(steeringResult);
16310
- switch (steeringResult.action) {
16678
+ const checkpointResult = await streamActions.requestCheckpoint(recap);
16679
+ applyCheckpointConfig(checkpointResult);
16680
+ switch (checkpointResult.action) {
16311
16681
  case "stop":
16312
16682
  streamActions.exit();
16313
16683
  await waitForUiExit?.();
@@ -16316,9 +16686,9 @@ Saving state... done. Session saved to ${filePath}`);
16316
16686
  console.log(chalk16.gray(`Resume cancelled. Local state unchanged at ${filePath}`));
16317
16687
  return;
16318
16688
  case "steer":
16319
- if (steeringResult.message) {
16320
- taskMessage = steeringResult.message;
16321
- continuationMessage = steeringResult.message;
16689
+ if (checkpointResult.message) {
16690
+ taskMessage = checkpointResult.message;
16691
+ continuationMessage = checkpointResult.message;
16322
16692
  useCompact = false;
16323
16693
  }
16324
16694
  break;
@@ -16364,12 +16734,12 @@ Saving state... done. Session saved to ${filePath}`);
16364
16734
  const currentRemainingCost = remainingCost ? remainingCost - accumulatedCost : void 0;
16365
16735
  const phaseModel = resolveModelForPhase(
16366
16736
  resumeState?.workflowPhase,
16367
- recipe,
16368
16737
  {
16369
16738
  planningModel: options.planningModel,
16370
16739
  executionModel: options.executionModel,
16371
16740
  defaultModel: options.model
16372
- }
16741
+ },
16742
+ playbookMilestoneModels
16373
16743
  );
16374
16744
  const effectiveModelForContext = phaseModel || options.model || agentConfigModel || defaultConfiguredModel;
16375
16745
  const compactStrategy = resolveCompactStrategyForModel(effectiveModelForContext);
@@ -16389,7 +16759,7 @@ Saving state... done. Session saved to ${filePath}`);
16389
16759
  trackProgress: options.track ? taskName : void 0,
16390
16760
  ...resolvedToolIds.length > 0 ? { toolIds: resolvedToolIds } : {},
16391
16761
  ...resolvedWorkflow ? { workflow: resolvedWorkflow } : {},
16392
- // Continuation context (only set on resume or after steering restart)
16762
+ // Continuation context (only set on resume or after checkpoint restart)
16393
16763
  ...previousMessages.length > 0 ? {
16394
16764
  previousMessages,
16395
16765
  continuationMessage,
@@ -16409,7 +16779,7 @@ Saving state... done. Session saved to ${filePath}`);
16409
16779
  };
16410
16780
  if (event.phase === "start") {
16411
16781
  currentActions.startContextCompaction(absoluteEvent);
16412
- await new Promise((resolve6) => setTimeout(resolve6, 0));
16782
+ await new Promise((resolve7) => setTimeout(resolve7, 0));
16413
16783
  return;
16414
16784
  }
16415
16785
  currentActions.finishContextCompaction(absoluteEvent);
@@ -16466,18 +16836,24 @@ Saving state... done. Session saved to ${filePath}`);
16466
16836
  resumeState = extractRunTaskResumeState(adjustedState);
16467
16837
  lastSessionMessages = state.messages ?? [];
16468
16838
  saveState(filePath, adjustedState, { stripSnapshotEvents: !!eventLogWriter });
16839
+ if (resumeState?.workflowPhase) {
16840
+ const displayMilestone = detectedVariant === "external" && resumeState.workflowPhase === "research" && adjustedState.planWritten ? "report" : resumeState.workflowPhase;
16841
+ streamRef.current?.updateMilestone(displayMilestone);
16842
+ }
16843
+ let modelChangedOnPhaseTransition = false;
16469
16844
  if (resumeState?.workflowPhase) {
16470
16845
  const newPhaseModel = resolveModelForPhase(
16471
16846
  resumeState.workflowPhase,
16472
- recipe,
16473
16847
  {
16474
16848
  planningModel: options.planningModel,
16475
16849
  executionModel: options.executionModel,
16476
16850
  defaultModel: options.model
16477
- }
16851
+ },
16852
+ playbookMilestoneModels
16478
16853
  );
16479
16854
  if (newPhaseModel && newPhaseModel !== options.model) {
16480
16855
  options.model = newPhaseModel;
16856
+ modelChangedOnPhaseTransition = true;
16481
16857
  }
16482
16858
  }
16483
16859
  if (state.recentActionKeys && state.recentActionKeys.length > 0) {
@@ -16485,7 +16861,11 @@ Saving state... done. Session saved to ${filePath}`);
16485
16861
  loopDetector.recordAction(key);
16486
16862
  }
16487
16863
  }
16488
- if (options.noSteer) return;
16864
+ if (modelChangedOnPhaseTransition) {
16865
+ shouldContinue = true;
16866
+ return false;
16867
+ }
16868
+ if (options.noCheckpoint) return;
16489
16869
  if (state.status !== "running") return;
16490
16870
  if (!currentActions) return;
16491
16871
  const recap = {
@@ -16496,15 +16876,15 @@ Saving state... done. Session saved to ${filePath}`);
16496
16876
  cost: state.totalCost,
16497
16877
  outputPreview: adjustedState.lastOutput.slice(0, 100)
16498
16878
  };
16499
- const steeringResult = await currentActions.requestSteering(recap);
16500
- applySteeringConfig(steeringResult);
16501
- switch (steeringResult.action) {
16879
+ const checkpointResult = await currentActions.requestCheckpoint(recap);
16880
+ applyCheckpointConfig(checkpointResult);
16881
+ switch (checkpointResult.action) {
16502
16882
  case "stop":
16503
16883
  return false;
16504
16884
  // Tells SDK to stop the loop
16505
16885
  case "steer":
16506
- if (steeringResult.message) {
16507
- taskMessage = steeringResult.message;
16886
+ if (checkpointResult.message) {
16887
+ taskMessage = checkpointResult.message;
16508
16888
  shouldContinue = true;
16509
16889
  return false;
16510
16890
  }
@@ -16577,6 +16957,9 @@ Saving state... done. Session saved to ${filePath}`);
16577
16957
  };
16578
16958
  }
16579
16959
  lastResult = result2;
16960
+ if (result2.status === "complete") {
16961
+ streamRef.current?.updateMilestone("complete");
16962
+ }
16580
16963
  if (shouldContinue) {
16581
16964
  previousMessages = lastSessionMessages;
16582
16965
  continuationMessage = taskMessage;
@@ -16584,7 +16967,7 @@ Saving state... done. Session saved to ${filePath}`);
16584
16967
  resumeState = extractRunTaskResumeState(lastKnownState);
16585
16968
  }
16586
16969
  if (result2.status === "complete" || result2.status === "budget_exceeded" || result2.status === "max_sessions") {
16587
- if (options.noSteer) break;
16970
+ if (options.noCheckpoint) break;
16588
16971
  const currentActions = streamRef.current;
16589
16972
  if (!currentActions) break;
16590
16973
  let exitTerminal = false;
@@ -16599,13 +16982,13 @@ Saving state... done. Session saved to ${filePath}`);
16599
16982
  cost: accumulatedCost,
16600
16983
  outputPreview: terminalPreview.slice(0, 100)
16601
16984
  };
16602
- const steeringResult = await currentActions.requestSteering(recap, true);
16603
- applySteeringConfig(steeringResult);
16604
- switch (steeringResult.action) {
16985
+ const checkpointResult = await currentActions.requestCheckpoint(recap, true);
16986
+ applyCheckpointConfig(checkpointResult);
16987
+ switch (checkpointResult.action) {
16605
16988
  case "steer":
16606
- if (steeringResult.message) {
16989
+ if (checkpointResult.message) {
16607
16990
  currentActions.resetForNewSession();
16608
- taskMessage = steeringResult.message;
16991
+ taskMessage = checkpointResult.message;
16609
16992
  previousMessages = lastSessionMessages;
16610
16993
  continuationMessage = taskMessage;
16611
16994
  useCompact = false;
@@ -16615,7 +16998,7 @@ Saving state... done. Session saved to ${filePath}`);
16615
16998
  exitTerminal = true;
16616
16999
  break;
16617
17000
  case "continue":
16618
- if (steeringHasConfigChanges(steeringResult)) {
17001
+ if (checkpointHasConfigChanges(checkpointResult)) {
16619
17002
  break;
16620
17003
  }
16621
17004
  currentActions.exit();
@@ -16860,7 +17243,7 @@ function detectDeployWorkflow(message, sandboxProvider, resumeState) {
16860
17243
  return void 0;
16861
17244
  }
16862
17245
  function applyTaskOptions(cmd) {
16863
- return cmd.argument("<agent>", "Agent ID or name").option("-g, --goal <text>", "Goal message for the agent").option("--max-sessions <n>", "Maximum sessions", "50").option("--max-cost <n>", "Budget in USD").option("--model <modelId>", "Model ID to use (overrides agent config)").option("--name <name>", "Task name (used for state file, defaults to agent name)").option("--session <name>", "Resume a specific session by name").option("--state-dir <path>", "Directory for state files (default: ~/.runtype/projects/<hash>/marathons/)").option("--resume [message]", "Resume from existing local state, optionally with a new message").option("--fresh", "Start a new run and ignore any existing local state for this task").option("--compact", "Force compact-summary resume mode instead of replaying full history").option("--compact-strategy <strategy>", "Compaction strategy: auto (default), provider_native, or summary_fallback").option("--compact-threshold <value>", "Auto-compact when estimated context crosses this threshold (default: 80% fallback, 90% native; accepts percent like 90% or absolute token count like 120000)").option("--compact-instructions <text>", "Extra instructions for what a compact summary must preserve").option("--no-auto-compact", "Disable automatic context-aware history compaction").option("--track", "Sync progress to a Runtype record (visible in dashboard)").option("--debug", "Show debug output from each session").option("--json", "Output final result as JSON").option("--sandbox <provider>", "Enable sandbox code execution tool (cloudflare-worker, quickjs, or daytona)").option("--no-local-tools", "Disable built-in local tool execution (read_file, write_file, list_directory)").option("-t, --tools <tools...>", "Enable built-in tools (e.g., exa, firecrawl, dalle, openai_web_search, anthropic_web_search)").option("--plain-text", "Disable markdown rendering in output").option("--no-steer", "Run all iterations without steering pauses (fully autonomous)").option("--steer-timeout <seconds>", "Auto-continue timeout in seconds (default: 10)", "10").option("--planning-model <modelId>", "Model to use during research/planning phases").option("--execution-model <modelId>", "Model to use during execution phase").option("--recipe <name>", "Load a workflow recipe from .marathon/recipes/").option("--offload-threshold <chars>", 'Offload tool outputs larger than this to files (default: 100000; use "off" or "0" to disable guardrails)').option("--tool-context <mode>", "Tool result storage: hot-tail (default), observation-mask, or full-inline").option("--tool-window <window>", 'Compaction window: "session" (default) or a number for last-N tool results (e.g. 10)').option("--runner-char <char>", "Custom runner emoji (default: \u{1F3C3})").option("--finish-char <char>", "Custom finish line emoji (default: \u{1F3C1})").option("--no-runner", "Hide the runner emoji from the header border").option("--no-finish", "Hide the finish line emoji from the header border").action(taskAction);
17246
+ return cmd.argument("<agent>", "Agent ID or name").option("-g, --goal <text>", "Goal message for the agent").option("--max-sessions <n>", "Maximum sessions", "50").option("--max-cost <n>", "Budget in USD").option("--model <modelId>", "Model ID to use (overrides agent config)").option("--name <name>", "Task name (used for state file, defaults to agent name)").option("--session <name>", "Resume a specific session by name").option("--state-dir <path>", "Directory for state files (default: ~/.runtype/projects/<hash>/marathons/)").option("--resume [message]", "Resume from existing local state, optionally with a new message").option("--fresh", "Start a new run and ignore any existing local state for this task").option("--compact", "Force compact-summary resume mode instead of replaying full history").option("--compact-strategy <strategy>", "Compaction strategy: auto (default), provider_native, or summary_fallback").option("--compact-threshold <value>", "Auto-compact when estimated context crosses this threshold (default: 80% fallback, 90% native; accepts percent like 90% or absolute token count like 120000)").option("--compact-instructions <text>", "Extra instructions for what a compact summary must preserve").option("--no-auto-compact", "Disable automatic context-aware history compaction").option("--track", "Sync progress to a Runtype record (visible in dashboard)").option("--debug", "Show debug output from each session").option("--json", "Output final result as JSON").option("--sandbox <provider>", "Enable sandbox code execution tool (cloudflare-worker, quickjs, or daytona)").option("--no-local-tools", "Disable built-in local tool execution (read_file, write_file, list_directory)").option("-t, --tools <tools...>", "Enable built-in tools (e.g., exa, firecrawl, dalle, openai_web_search, anthropic_web_search)").option("--plain-text", "Disable markdown rendering in output").option("--no-checkpoint", "Run all iterations without checkpoint pauses (fully autonomous)").option("--checkpoint-timeout <seconds>", "Auto-continue timeout in seconds (default: 10)", "10").option("--planning-model <modelId>", "Model to use during research/planning phases").option("--execution-model <modelId>", "Model to use during execution phase").option("--playbook <name>", "Load a playbook from .runtype/marathons/playbooks/").option("--offload-threshold <chars>", 'Offload tool outputs larger than this to files (default: 100000; use "off" or "0" to disable guardrails)').option("--tool-context <mode>", "Tool result storage: hot-tail (default), observation-mask, or full-inline").option("--tool-window <window>", 'Compaction window: "session" (default) or a number for last-N tool results (e.g. 10)').option("--runner-char <char>", "Custom runner emoji (default: \u{1F3C3})").option("--finish-char <char>", "Custom finish line emoji (default: \u{1F3C1})").option("--no-runner", "Hide the runner emoji from the header border").option("--no-finish", "Hide the finish line emoji from the header border").action(taskAction);
16864
17247
  }
16865
17248
  var taskCommand = applyTaskOptions(
16866
17249
  new Command10("task").description("Run a multi-session agent task")
@@ -17783,14 +18166,14 @@ import React13 from "react";
17783
18166
  import { render as render13 } from "ink";
17784
18167
  import { useState as useState27, useEffect as useEffect25 } from "react";
17785
18168
  import { Text as Text30 } from "ink";
17786
- import { readFileSync as readFileSync12 } from "fs";
18169
+ import { readFileSync as readFileSync13 } from "fs";
17787
18170
  var evalCommand = new Command14("eval").description("Manage evaluations");
17788
18171
  evalCommand.command("submit").description("Submit an eval batch").requiredOption("-f, --flow <id>", "Flow ID to evaluate").requiredOption("-r, --records <file>", "JSON file with record IDs").option("-n, --name <name>", "Eval batch name").option("--json", "Output as JSON").option("--tty", "Force TTY mode").option("--no-tty", "Force non-TTY mode").action(async (options) => {
17789
18172
  const apiKey = await ensureAuth();
17790
18173
  if (!apiKey) return;
17791
18174
  let recordIds;
17792
18175
  try {
17793
- const content = readFileSync12(options.records, "utf-8");
18176
+ const content = readFileSync13(options.records, "utf-8");
17794
18177
  const parsed = JSON.parse(content);
17795
18178
  recordIds = Array.isArray(parsed) ? parsed : parsed.recordIds || parsed.records || [];
17796
18179
  } catch (error) {
@@ -18348,13 +18731,13 @@ apiKeysCommand.command("delete <id>").description("Delete an API key").option("-
18348
18731
  await waitUntilExit2();
18349
18732
  return;
18350
18733
  }
18351
- const confirmed = await new Promise((resolve6) => {
18734
+ const confirmed = await new Promise((resolve7) => {
18352
18735
  const { unmount } = render14(
18353
18736
  React14.createElement(ConfirmPrompt, {
18354
18737
  message: `Delete API key ${id}?`,
18355
18738
  defaultValue: false,
18356
18739
  onConfirm: (result) => {
18357
- resolve6(result);
18740
+ resolve7(result);
18358
18741
  unmount();
18359
18742
  }
18360
18743
  })
@@ -18459,8 +18842,8 @@ apiKeysCommand.command("analytics").description("Show API key usage analytics").
18459
18842
  const client = new ApiClient(apiKey);
18460
18843
  if (!isTTY(options) || options.json) {
18461
18844
  try {
18462
- const path11 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
18463
- const data = await client.get(path11);
18845
+ const path13 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
18846
+ const data = await client.get(path13);
18464
18847
  printJson(data);
18465
18848
  } catch (error) {
18466
18849
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -18477,8 +18860,8 @@ apiKeysCommand.command("analytics").description("Show API key usage analytics").
18477
18860
  useEffect26(() => {
18478
18861
  const run = async () => {
18479
18862
  try {
18480
- const path11 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
18481
- const data = await client.get(path11);
18863
+ const path13 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
18864
+ const data = await client.get(path13);
18482
18865
  printJson(data);
18483
18866
  setSuccess(true);
18484
18867
  setLoading(false);
@@ -19264,6 +19647,7 @@ function maybeNotifyAboutCliUpdate(args, options = {}) {
19264
19647
 
19265
19648
  // src/index.ts
19266
19649
  loadEnv();
19650
+ setCliTitle();
19267
19651
  var program = new Command19();
19268
19652
  program.name("runtype").description("CLI for Runtype AI Platform").version(getCliVersion()).option("-v, --verbose", "Enable verbose output").option("--api-url <url>", "Override API URL").option("--json", "Output in JSON format");
19269
19653
  program.addCommand(initCommand);