@runtypelabs/cli 2.1.0 → 2.2.1

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
@@ -5,10 +5,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
8
14
  var __esm = (fn, res) => function __init() {
9
15
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
16
  };
11
- var __commonJS = (cb, mod) => function __require() {
17
+ var __commonJS = (cb, mod) => function __require2() {
12
18
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
19
  };
14
20
  var __export = (target, all) => {
@@ -147,6 +153,33 @@ var init_credential_store = __esm({
147
153
  }
148
154
  });
149
155
 
156
+ // ../shared/dist/integration-providers.js
157
+ var require_integration_providers = __commonJS({
158
+ "../shared/dist/integration-providers.js"(exports) {
159
+ "use strict";
160
+ Object.defineProperty(exports, "__esModule", { value: true });
161
+ exports.NON_AI_PROVIDER_IDS = void 0;
162
+ exports.isNonAIProvider = isNonAIProvider2;
163
+ exports.NON_AI_PROVIDER_IDS = /* @__PURE__ */ new Set([
164
+ // Analytics & Tracking
165
+ "posthog",
166
+ // Development Tools
167
+ "daytona",
168
+ // Web Scraping & Data Extraction
169
+ "firecrawl",
170
+ // Search Providers
171
+ "exa",
172
+ // Email Services
173
+ "resend",
174
+ // Audio / Speech
175
+ "elevenlabs"
176
+ ]);
177
+ function isNonAIProvider2(id) {
178
+ return exports.NON_AI_PROVIDER_IDS.has(id.trim().toLowerCase());
179
+ }
180
+ }
181
+ });
182
+
150
183
  // ../shared/dist/builtin-tools-registry.js
151
184
  var require_builtin_tools_registry = __commonJS({
152
185
  "../shared/dist/builtin-tools-registry.js"(exports) {
@@ -4565,8 +4598,8 @@ var CallbackServer = class {
4565
4598
  expectedState;
4566
4599
  constructor() {
4567
4600
  this.app = express();
4568
- this.codePromise = new Promise((resolve7, reject) => {
4569
- this.codeResolve = resolve7;
4601
+ this.codePromise = new Promise((resolve8, reject) => {
4602
+ this.codeResolve = resolve8;
4570
4603
  this.codeReject = reject;
4571
4604
  });
4572
4605
  this.app.get("/callback", (req, res) => {
@@ -5761,14 +5794,14 @@ async function promptConfirm(message, options) {
5761
5794
  output: process.stdout,
5762
5795
  terminal: true
5763
5796
  });
5764
- return new Promise((resolve7) => {
5797
+ return new Promise((resolve8) => {
5765
5798
  rl.question(chalk2.cyan(`${message} (${hint}): `), (answer) => {
5766
5799
  rl.close();
5767
5800
  const trimmed = answer.trim().toLowerCase();
5768
5801
  if (trimmed === "") {
5769
- resolve7(defaultYes);
5802
+ resolve8(defaultYes);
5770
5803
  } else {
5771
- resolve7(trimmed === "y" || trimmed === "yes");
5804
+ resolve8(trimmed === "y" || trimmed === "yes");
5772
5805
  }
5773
5806
  });
5774
5807
  });
@@ -5843,13 +5876,24 @@ function getCliVersion() {
5843
5876
  if (cachedCliVersion) {
5844
5877
  return cachedCliVersion;
5845
5878
  }
5846
- try {
5847
- const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
5848
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
5849
- cachedCliVersion = pkg.version || "0.0.0";
5850
- } catch {
5851
- cachedCliVersion = "0.0.0";
5879
+ const base = dirname(fileURLToPath(import.meta.url));
5880
+ const candidates = [
5881
+ join(base, "..", "..", "package.json"),
5882
+ // src/lib/ package root
5883
+ join(base, "..", "package.json")
5884
+ // dist/ → package root
5885
+ ];
5886
+ for (const pkgPath of candidates) {
5887
+ try {
5888
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
5889
+ if (pkg.name === CLI_PACKAGE_NAME && pkg.version) {
5890
+ cachedCliVersion = pkg.version;
5891
+ return cachedCliVersion;
5892
+ }
5893
+ } catch {
5894
+ }
5852
5895
  }
5896
+ cachedCliVersion = "0.0.0";
5853
5897
  return cachedCliVersion;
5854
5898
  }
5855
5899
 
@@ -5869,9 +5913,9 @@ var ApiClient = class {
5869
5913
  this.baseUrl = baseUrl || getApiUrl();
5870
5914
  }
5871
5915
  /** Prefix path with API version (e.g. /v1) so all requests hit versioned routes. */
5872
- path(path13) {
5916
+ path(path14) {
5873
5917
  const version = getApiVersion();
5874
- const p = path13.startsWith("/") ? path13 : `/${path13}`;
5918
+ const p = path14.startsWith("/") ? path14 : `/${path14}`;
5875
5919
  return p.startsWith(`/${version}/`) ? p : `/${version}${p}`;
5876
5920
  }
5877
5921
  headers(extra) {
@@ -5904,8 +5948,8 @@ var ApiClient = class {
5904
5948
  }
5905
5949
  return response.json();
5906
5950
  }
5907
- async get(path13, params) {
5908
- const url = new URL(this.path(path13), this.baseUrl);
5951
+ async get(path14, params) {
5952
+ const url = new URL(this.path(path14), this.baseUrl);
5909
5953
  if (params) {
5910
5954
  for (const [key, value] of Object.entries(params)) {
5911
5955
  if (value !== void 0 && value !== "") {
@@ -5918,32 +5962,32 @@ var ApiClient = class {
5918
5962
  });
5919
5963
  return this.handleResponse(response);
5920
5964
  }
5921
- async post(path13, body) {
5922
- const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5965
+ async post(path14, body) {
5966
+ const response = await fetch(new URL(this.path(path14), this.baseUrl).toString(), {
5923
5967
  method: "POST",
5924
5968
  headers: this.headers(),
5925
5969
  body: body !== void 0 ? JSON.stringify(body) : void 0
5926
5970
  });
5927
5971
  return this.handleResponse(response);
5928
5972
  }
5929
- async put(path13, body) {
5930
- const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5973
+ async put(path14, body) {
5974
+ const response = await fetch(new URL(this.path(path14), this.baseUrl).toString(), {
5931
5975
  method: "PUT",
5932
5976
  headers: this.headers(),
5933
5977
  body: body !== void 0 ? JSON.stringify(body) : void 0
5934
5978
  });
5935
5979
  return this.handleResponse(response);
5936
5980
  }
5937
- async patch(path13, body) {
5938
- const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5981
+ async patch(path14, body) {
5982
+ const response = await fetch(new URL(this.path(path14), this.baseUrl).toString(), {
5939
5983
  method: "PATCH",
5940
5984
  headers: this.headers(),
5941
5985
  body: body !== void 0 ? JSON.stringify(body) : void 0
5942
5986
  });
5943
5987
  return this.handleResponse(response);
5944
5988
  }
5945
- async delete(path13) {
5946
- const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
5989
+ async delete(path14) {
5990
+ const response = await fetch(new URL(this.path(path14), this.baseUrl).toString(), {
5947
5991
  method: "DELETE",
5948
5992
  headers: this.headers()
5949
5993
  });
@@ -5957,8 +6001,8 @@ var ApiClient = class {
5957
6001
  throw new ApiError(response.status, message);
5958
6002
  }
5959
6003
  }
5960
- async stream(path13, body) {
5961
- const response = await fetch(new URL(this.path(path13), this.baseUrl).toString(), {
6004
+ async stream(path14, body) {
6005
+ const response = await fetch(new URL(this.path(path14), this.baseUrl).toString(), {
5962
6006
  method: "POST",
5963
6007
  headers: this.headers({ Accept: "text/event-stream" }),
5964
6008
  body: body !== void 0 ? JSON.stringify(body) : void 0
@@ -8346,6 +8390,7 @@ import React9 from "react";
8346
8390
  import { useState as useState22, useEffect as useEffect19, useRef as useRef9, useCallback as useCallback8, useMemo as useMemo11 } from "react";
8347
8391
  import { execSync } from "child_process";
8348
8392
  import * as fs4 from "fs";
8393
+ import * as path4 from "path";
8349
8394
  import open4 from "open";
8350
8395
  import { Box as Box28, Text as Text28, useApp as useApp4, useInput as useInput11, useStdout as useStdout5 } from "ink";
8351
8396
  import { StatusBar, theme as theme31 } from "@runtypelabs/ink-components";
@@ -8469,8 +8514,12 @@ function setStoredToolPayloadField(payloads, toolId, field, value) {
8469
8514
  const next = { ...payloads.get(toolId) ?? {} };
8470
8515
  if (value === void 0) {
8471
8516
  delete next[field];
8517
+ } else if (field === "parameters") {
8518
+ next.parameters = value;
8519
+ } else if (field === "result") {
8520
+ next.result = value;
8472
8521
  } else {
8473
- next[field] = value;
8522
+ next.streamedInput = value;
8474
8523
  }
8475
8524
  if (next.parameters === void 0 && next.result === void 0 && next.streamedInput === void 0) {
8476
8525
  payloads.delete(toolId);
@@ -8566,7 +8615,8 @@ function formatContextCompactionCompleteLine(event) {
8566
8615
  return thresholdLabel ? `[context] History compacted ${modeLabel}${modelLabel}${strategyLabel} at ${thresholdLabel}${breakdownLabel}.` : `[context] History compacted ${modeLabel}${modelLabel}${strategyLabel}${breakdownLabel}.`;
8567
8616
  }
8568
8617
  function formatContextNoticeLine(event) {
8569
- return `[context] ${event.message}`;
8618
+ const prefix = event.kind === "server_network_retry" ? "[retry]" : "[context]";
8619
+ return `${prefix} ${event.message}`;
8570
8620
  }
8571
8621
  var toolCounter = 0;
8572
8622
  function useMarathonStream() {
@@ -8574,6 +8624,7 @@ function useMarathonStream() {
8574
8624
  const rawEventsRef = useRef3([]);
8575
8625
  const stateRef = useRef3(state);
8576
8626
  stateRef.current = state;
8627
+ const costOffsetRef = useRef3(0);
8577
8628
  const eventWriterRef = useRef3(null);
8578
8629
  const toolPayloadsRef = useRef3(/* @__PURE__ */ new Map());
8579
8630
  const hydrateToolEntry = useCallback2((tool) => {
@@ -8954,7 +9005,7 @@ function useMarathonStream() {
8954
9005
  executionTime: Date.now() - (t.localExecutionStartedAt ?? t.startedAt)
8955
9006
  } : t
8956
9007
  ),
8957
- totalCost: event.totalCost != null ? event.totalCost : prev.totalCost,
9008
+ totalCost: event.totalCost != null ? costOffsetRef.current + event.totalCost : prev.totalCost,
8958
9009
  totalInputTokens: event.totalTokens?.input ?? prev.totalInputTokens,
8959
9010
  totalOutputTokens: event.totalTokens?.output ?? prev.totalOutputTokens
8960
9011
  }));
@@ -9036,18 +9087,21 @@ function useMarathonStream() {
9036
9087
  }, []);
9037
9088
  const resetForNewSession = useCallback2(() => {
9038
9089
  toolPayloadsRef.current.clear();
9039
- setState((prev) => ({
9040
- ...prev,
9041
- phase: "idle",
9042
- content: "",
9043
- reasoning: "",
9044
- lastTranscriptKind: null,
9045
- thinkingStartedAt: null,
9046
- tools: [],
9047
- currentIteration: 0,
9048
- error: null,
9049
- contextCompaction: null
9050
- }));
9090
+ setState((prev) => {
9091
+ costOffsetRef.current = prev.totalCost;
9092
+ return {
9093
+ ...prev,
9094
+ phase: "idle",
9095
+ content: "",
9096
+ reasoning: "",
9097
+ lastTranscriptKind: null,
9098
+ thinkingStartedAt: null,
9099
+ tools: [],
9100
+ currentIteration: 0,
9101
+ error: null,
9102
+ contextCompaction: null
9103
+ };
9104
+ });
9051
9105
  }, []);
9052
9106
  const showError = useCallback2((error) => {
9053
9107
  pushRawEvent(rawEventsRef, "agent_error", { message: error.message }, eventWriterRef);
@@ -10353,6 +10407,10 @@ function FileContentView({
10353
10407
  separator,
10354
10408
  "from tool call"
10355
10409
  ] }),
10410
+ /* @__PURE__ */ jsxs14(Text16, { color: theme17.textSubtle, children: [
10411
+ separator,
10412
+ "o: open"
10413
+ ] }),
10356
10414
  /* @__PURE__ */ jsxs14(Text16, { color: theme17.textSubtle, children: [
10357
10415
  separator,
10358
10416
  "c: copy"
@@ -10455,6 +10513,14 @@ function FilesPanel({
10455
10513
  separator,
10456
10514
  files.length,
10457
10515
  " files"
10516
+ ] }),
10517
+ /* @__PURE__ */ jsxs14(Text16, { color: theme17.border, children: [
10518
+ separator,
10519
+ "Enter: view",
10520
+ separator,
10521
+ "o: open",
10522
+ separator,
10523
+ "c: copy path"
10458
10524
  ] })
10459
10525
  ] }),
10460
10526
  /* @__PURE__ */ jsxs14(Box15, { flexDirection: "row", children: [
@@ -11955,7 +12021,7 @@ function CheckpointRecap({
11955
12021
  `Tools: ${toolCallsMade}`,
11956
12022
  `Tokens: ${formatTokenCount2(tokensInput)} in${separator}${formatTokenCount2(tokensOutput)} out`,
11957
12023
  ...reasoningTokens && reasoningTokens > 0 ? [`Reasoning: ${formatTokenCount2(reasoningTokens)}`] : [],
11958
- `Cost: $${cost.toFixed(4)}`
12024
+ `Total cost: $${cost.toFixed(4)}`
11959
12025
  ].join(separator);
11960
12026
  return /* @__PURE__ */ jsxs19(
11961
12027
  Box20,
@@ -13086,8 +13152,8 @@ function MarathonApp({
13086
13152
  setIsTerminalCheckpoint(true);
13087
13153
  isTerminalCheckpointRef.current = true;
13088
13154
  }
13089
- return new Promise((resolve7) => {
13090
- checkpointResolveRef.current = resolve7;
13155
+ return new Promise((resolve8) => {
13156
+ checkpointResolveRef.current = resolve8;
13091
13157
  });
13092
13158
  },
13093
13159
  updateMilestone: (milestone) => {
@@ -13413,7 +13479,7 @@ function MarathonApp({
13413
13479
  setUpgradeModalDismissed(true);
13414
13480
  return;
13415
13481
  }
13416
- if (_input === "o" && agentPageUrl && !(state.phase === "checkpoint" && checkpointRecap)) {
13482
+ if (_input === "o" && agentPageUrl && !showFilesPanel && !(state.phase === "checkpoint" && checkpointRecap)) {
13417
13483
  void open4(agentPageUrl);
13418
13484
  return;
13419
13485
  }
@@ -13569,6 +13635,19 @@ function MarathonApp({
13569
13635
  setReasoningCollapsed((prev) => !prev);
13570
13636
  return;
13571
13637
  }
13638
+ if (_input === "o" && showFilesPanel) {
13639
+ const file = detailFile ?? trackedFiles[fileCursor];
13640
+ if (file) {
13641
+ const filePath = path4.resolve(file.path);
13642
+ if (fs4.existsSync(filePath)) {
13643
+ void open4(filePath);
13644
+ showFlash(`Opening ${file.path}`);
13645
+ } else {
13646
+ showFlash("File not found on disk");
13647
+ }
13648
+ }
13649
+ return;
13650
+ }
13572
13651
  if (_input === "c" && showFilesPanel) {
13573
13652
  if (detailFile && detailFileContent) {
13574
13653
  const ok = copyToClipboard(detailFileContent);
@@ -14321,7 +14400,8 @@ function buildPromptShellModel(prompt, columnWidth) {
14321
14400
  }
14322
14401
  function MarathonStartupShell({
14323
14402
  startupRef,
14324
- marathonAppProps
14403
+ marathonAppProps,
14404
+ version
14325
14405
  }) {
14326
14406
  const { exit } = useApp5();
14327
14407
  const { stdout } = useStdout6();
@@ -14347,16 +14427,16 @@ function MarathonStartupShell({
14347
14427
  latestAppPropsRef.current = marathonAppProps;
14348
14428
  useEffect20(() => {
14349
14429
  if (scene !== "app" || !appReadyResolverRef.current) return;
14350
- const resolve7 = appReadyResolverRef.current;
14430
+ const resolve8 = appReadyResolverRef.current;
14351
14431
  appReadyResolverRef.current = null;
14352
- resolve7();
14432
+ resolve8();
14353
14433
  }, [scene]);
14354
14434
  const beginTransition = (target) => {
14355
14435
  if (transitionPromiseRef.current) return transitionPromiseRef.current;
14356
14436
  if (target === "app" && !latestAppPropsRef.current) {
14357
14437
  throw new Error("Cannot complete startup before marathon app props are ready.");
14358
14438
  }
14359
- const promise = new Promise((resolve7) => {
14439
+ const promise = new Promise((resolve8) => {
14360
14440
  globalThis.setTimeout(() => {
14361
14441
  setPrompt(null);
14362
14442
  setModelChoices(null);
@@ -14377,12 +14457,12 @@ function MarathonStartupShell({
14377
14457
  if (target === "app") {
14378
14458
  appReadyResolverRef.current = () => {
14379
14459
  transitionPromiseRef.current = null;
14380
- resolve7();
14460
+ resolve8();
14381
14461
  };
14382
14462
  } else {
14383
14463
  dismissResolverRef.current = () => {
14384
14464
  transitionPromiseRef.current = null;
14385
- resolve7();
14465
+ resolve8();
14386
14466
  };
14387
14467
  }
14388
14468
  }, Math.max(0, MIN_HOLD_MS - (Date.now() - mountedAtRef.current)));
@@ -14399,8 +14479,8 @@ function MarathonStartupShell({
14399
14479
  setModelChoices(null);
14400
14480
  setPrompt(nextPrompt);
14401
14481
  setSelectedPromptIndex(0);
14402
- return new Promise((resolve7) => {
14403
- promptResolverRef.current = resolve7;
14482
+ return new Promise((resolve8) => {
14483
+ promptResolverRef.current = resolve8;
14404
14484
  });
14405
14485
  },
14406
14486
  requestModelChoice: async (nextCurrentModel, models) => {
@@ -14408,8 +14488,8 @@ function MarathonStartupShell({
14408
14488
  setPlaybookConfirm(null);
14409
14489
  setCurrentModel(nextCurrentModel);
14410
14490
  setModelChoices(models);
14411
- return new Promise((resolve7) => {
14412
- modelResolverRef.current = resolve7;
14491
+ return new Promise((resolve8) => {
14492
+ modelResolverRef.current = resolve8;
14413
14493
  });
14414
14494
  },
14415
14495
  requestPlaybookModelConfirm: async (playbookName, milestoneModels) => {
@@ -14424,8 +14504,8 @@ function MarathonStartupShell({
14424
14504
  // Default selection is the "Confirm" action (first item after milestones)
14425
14505
  selectedIndex: names.length
14426
14506
  });
14427
- return new Promise((resolve7) => {
14428
- playbookConfirmResolverRef.current = resolve7;
14507
+ return new Promise((resolve8) => {
14508
+ playbookConfirmResolverRef.current = resolve8;
14429
14509
  });
14430
14510
  },
14431
14511
  completeStartup: () => beginTransition("app"),
@@ -14575,6 +14655,22 @@ function MarathonStartupShell({
14575
14655
  children: /* @__PURE__ */ jsx34(StartupGridIconCompactLightInverted, { autoplay: false })
14576
14656
  }
14577
14657
  ),
14658
+ version && /* @__PURE__ */ jsx34(
14659
+ Box29,
14660
+ {
14661
+ position: "absolute",
14662
+ width: terminalWidth,
14663
+ height: terminalRows,
14664
+ alignItems: "flex-end",
14665
+ justifyContent: "flex-end",
14666
+ paddingX: 2,
14667
+ paddingY: 1,
14668
+ children: /* @__PURE__ */ jsxs27(Text29, { color: theme32.border, children: [
14669
+ "v",
14670
+ version
14671
+ ] })
14672
+ }
14673
+ ),
14578
14674
  /* @__PURE__ */ jsx34(
14579
14675
  Box29,
14580
14676
  {
@@ -14676,23 +14772,25 @@ function MarathonStartupShell({
14676
14772
  }
14677
14773
 
14678
14774
  // src/commands/agents-task.ts
14775
+ var import_integration_providers = __toESM(require_integration_providers(), 1);
14679
14776
  import * as fs13 from "fs";
14680
14777
  import {
14681
14778
  RuntypeClient as RuntypeClient2,
14682
14779
  defaultWorkflow,
14683
14780
  deployWorkflow,
14684
- gameWorkflow
14781
+ gameWorkflow,
14782
+ getDefaultPlanPath
14685
14783
  } from "@runtypelabs/sdk";
14686
14784
 
14687
14785
  // src/lib/terminal-title.ts
14688
- import path4 from "path";
14786
+ import path5 from "path";
14689
14787
  function setTerminalTitle(title) {
14690
14788
  if (process.stdout.isTTY) {
14691
14789
  process.stdout.write(`\x1B]0;${title}\x07`);
14692
14790
  }
14693
14791
  }
14694
14792
  function getFolderName() {
14695
- return path4.basename(process.cwd());
14793
+ return path5.basename(process.cwd());
14696
14794
  }
14697
14795
  function setCliTitle() {
14698
14796
  setTerminalTitle(`Runtype (${getFolderName()})`);
@@ -14703,21 +14801,21 @@ function setMarathonTitle() {
14703
14801
 
14704
14802
  // src/marathon/checkpoint.ts
14705
14803
  import * as fs6 from "fs";
14706
- import * as path6 from "path";
14804
+ import * as path7 from "path";
14707
14805
 
14708
14806
  // src/config/paths.ts
14709
14807
  import * as os3 from "os";
14710
- import * as path5 from "path";
14808
+ import * as path6 from "path";
14711
14809
  import * as crypto3 from "crypto";
14712
14810
  import * as fs5 from "fs";
14713
14811
  function getRuntypeHomeDir() {
14714
- return process.env.RUNTYPE_STATE_DIR || path5.join(os3.homedir(), ".runtype");
14812
+ return process.env.RUNTYPE_STATE_DIR || path6.join(os3.homedir(), ".runtype");
14715
14813
  }
14716
14814
  function getProjectStateDir(projectDir) {
14717
14815
  const dir = projectDir || process.cwd();
14718
14816
  const hash = crypto3.createHash("sha256").update(dir).digest("hex").slice(0, 12);
14719
- const stateDir = path5.join(getRuntypeHomeDir(), "projects", hash);
14720
- const breadcrumb = path5.join(stateDir, "project-path.txt");
14817
+ const stateDir = path6.join(getRuntypeHomeDir(), "projects", hash);
14818
+ const breadcrumb = path6.join(stateDir, "project-path.txt");
14721
14819
  if (!fs5.existsSync(breadcrumb)) {
14722
14820
  fs5.mkdirSync(stateDir, { recursive: true });
14723
14821
  fs5.writeFileSync(breadcrumb, dir);
@@ -14725,7 +14823,7 @@ function getProjectStateDir(projectDir) {
14725
14823
  return stateDir;
14726
14824
  }
14727
14825
  function getMarathonStateDir(projectDir) {
14728
- return path5.join(getProjectStateDir(projectDir), "marathons");
14826
+ return path6.join(getProjectStateDir(projectDir), "marathons");
14729
14827
  }
14730
14828
 
14731
14829
  // src/marathon/checkpoint.ts
@@ -14736,12 +14834,12 @@ function stateSafeName(name) {
14736
14834
  return name.replace(/[^a-zA-Z0-9_-]/g, "_");
14737
14835
  }
14738
14836
  function marathonArtifactsDir(taskName, stateDir) {
14739
- return path6.join(stateDir || defaultStateDir(), stateSafeName(taskName));
14837
+ return path7.join(stateDir || defaultStateDir(), stateSafeName(taskName));
14740
14838
  }
14741
14839
  function resolveMarathonCheckpointPath(taskName, targetPath, stateDir) {
14742
- const normalizedTargetPath = path6.normalize(targetPath);
14743
- const relativeTargetPath = path6.isAbsolute(normalizedTargetPath) ? path6.relative(process.cwd(), normalizedTargetPath) : normalizedTargetPath;
14744
- return path6.join(
14840
+ const normalizedTargetPath = path7.normalize(targetPath);
14841
+ const relativeTargetPath = path7.isAbsolute(normalizedTargetPath) ? path7.relative(process.cwd(), normalizedTargetPath) : normalizedTargetPath;
14842
+ return path7.join(
14745
14843
  marathonArtifactsDir(taskName, stateDir),
14746
14844
  "checkpoints",
14747
14845
  "original",
@@ -14749,23 +14847,23 @@ function resolveMarathonCheckpointPath(taskName, targetPath, stateDir) {
14749
14847
  );
14750
14848
  }
14751
14849
  function ensureMarathonFileCheckpoint(taskName, targetPath, stateDir) {
14752
- const normalizedTargetPath = path6.resolve(targetPath);
14753
- const relativeTargetPath = path6.relative(process.cwd(), normalizedTargetPath);
14850
+ const normalizedTargetPath = path7.resolve(targetPath);
14851
+ const relativeTargetPath = path7.relative(process.cwd(), normalizedTargetPath);
14754
14852
  if (!relativeTargetPath || relativeTargetPath.startsWith("..")) return void 0;
14755
14853
  if (!fs6.existsSync(normalizedTargetPath)) return void 0;
14756
14854
  if (!fs6.statSync(normalizedTargetPath).isFile()) return void 0;
14757
14855
  const normalizedRelativeTargetPath = relativeTargetPath.replace(/\\/g, "/");
14758
14856
  if (normalizedRelativeTargetPath.startsWith(".runtype/")) return void 0;
14759
- if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path6.sep)) return void 0;
14857
+ if (normalizedTargetPath.startsWith(getRuntypeHomeDir() + path7.sep)) return void 0;
14760
14858
  const checkpointPath = resolveMarathonCheckpointPath(taskName, normalizedTargetPath, stateDir);
14761
14859
  if (fs6.existsSync(checkpointPath)) return checkpointPath;
14762
- fs6.mkdirSync(path6.dirname(checkpointPath), { recursive: true });
14860
+ fs6.mkdirSync(path7.dirname(checkpointPath), { recursive: true });
14763
14861
  fs6.copyFileSync(normalizedTargetPath, checkpointPath);
14764
14862
  return checkpointPath;
14765
14863
  }
14766
14864
  function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir) {
14767
- const normalizedTargetPath = path6.resolve(targetPath);
14768
- const relativeTargetPath = path6.relative(process.cwd(), normalizedTargetPath);
14865
+ const normalizedTargetPath = path7.resolve(targetPath);
14866
+ const relativeTargetPath = path7.relative(process.cwd(), normalizedTargetPath);
14769
14867
  if (!relativeTargetPath || relativeTargetPath.startsWith("..")) {
14770
14868
  return {
14771
14869
  restored: false,
@@ -14780,7 +14878,7 @@ function restoreMarathonFileCheckpoint(taskName, targetPath, stateDir) {
14780
14878
  error: `No checkpoint found for ${normalizedTargetPath}`
14781
14879
  };
14782
14880
  }
14783
- fs6.mkdirSync(path6.dirname(normalizedTargetPath), { recursive: true });
14881
+ fs6.mkdirSync(path7.dirname(normalizedTargetPath), { recursive: true });
14784
14882
  fs6.copyFileSync(checkpointPath, normalizedTargetPath);
14785
14883
  return { restored: true, checkpointPath };
14786
14884
  }
@@ -14797,6 +14895,8 @@ var NETWORK_ERROR_PATTERNS = [
14797
14895
  "econnrefused",
14798
14896
  "econnaborted",
14799
14897
  "etimedout",
14898
+ "timeout",
14899
+ "request timeout",
14800
14900
  "enetunreach",
14801
14901
  "enetdown",
14802
14902
  "ehostunreach",
@@ -14816,12 +14916,78 @@ var NETWORK_ERROR_PATTERNS = [
14816
14916
  "unable to connect",
14817
14917
  "err_network"
14818
14918
  ];
14919
+ var LOCAL_NETWORK_PATTERNS = [
14920
+ "enetunreach",
14921
+ "enetdown",
14922
+ "enotfound",
14923
+ "network error",
14924
+ "network request failed",
14925
+ "networkerror",
14926
+ "err_network"
14927
+ ];
14928
+ var SERVER_UNREACHABLE_PATTERNS = [
14929
+ "econnrefused",
14930
+ "econnreset",
14931
+ "connection refused",
14932
+ "connection reset",
14933
+ "ehostunreach"
14934
+ ];
14935
+ function collectErrorSignals(error, seen = /* @__PURE__ */ new Set()) {
14936
+ if (error == null || seen.has(error)) return [];
14937
+ if (typeof error === "string") return [error];
14938
+ if (typeof error !== "object") return [String(error)];
14939
+ seen.add(error);
14940
+ const parts = [];
14941
+ if ("message" in error && typeof error.message === "string") {
14942
+ parts.push(error.message);
14943
+ }
14944
+ if ("code" in error && typeof error.code === "string") {
14945
+ parts.push(error.code);
14946
+ }
14947
+ if ("cause" in error) {
14948
+ parts.push(...collectErrorSignals(error.cause, seen));
14949
+ }
14950
+ return parts;
14951
+ }
14952
+ function getNetworkErrorContext(error) {
14953
+ const signals = collectErrorSignals(error);
14954
+ const fallbackMessage = error instanceof Error ? error.message : String(error);
14955
+ const uniqueSignals = [...new Set(signals.map((signal) => signal.trim()).filter(Boolean))];
14956
+ const searchText = uniqueSignals.join(" ").toLowerCase();
14957
+ const detailMessage = uniqueSignals.find((signal) => signal.toLowerCase() !== "fetch failed") ?? fallbackMessage;
14958
+ return {
14959
+ searchText,
14960
+ detailMessage
14961
+ };
14962
+ }
14963
+ function describeNetworkError(error) {
14964
+ const { searchText, detailMessage } = getNetworkErrorContext(error);
14965
+ const isLocalNetwork = LOCAL_NETWORK_PATTERNS.some((p) => searchText.includes(p));
14966
+ const isServerUnreachable = SERVER_UNREACHABLE_PATTERNS.some((p) => searchText.includes(p));
14967
+ const isTimeout = searchText.includes("etimedout") || searchText.includes("timeout");
14968
+ const lines = [];
14969
+ if (isLocalNetwork) {
14970
+ lines.push("Could not reach the Runtype API \u2014 your network appears to be offline.");
14971
+ lines.push("Check your internet connection and try again.");
14972
+ } else if (isServerUnreachable) {
14973
+ lines.push("Could not reach the Runtype API \u2014 the server is not responding.");
14974
+ lines.push("The service may be temporarily unavailable. Try again in a few minutes.");
14975
+ } else if (isTimeout) {
14976
+ lines.push("Could not reach the Runtype API \u2014 the request timed out.");
14977
+ lines.push("This could be a network issue or the server may be under heavy load.");
14978
+ } else {
14979
+ lines.push("Could not reach the Runtype API \u2014 a network error occurred.");
14980
+ lines.push("Check your internet connection or try again in a few minutes.");
14981
+ }
14982
+ lines.push(`Details: ${detailMessage}`);
14983
+ return lines;
14984
+ }
14819
14985
  function isTransientNetworkError(error) {
14820
14986
  if (error instanceof RuntypeApiError) return false;
14821
- const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
14822
- if (error instanceof TypeError && message.includes("fetch")) return true;
14987
+ const { searchText } = getNetworkErrorContext(error);
14988
+ if (error instanceof TypeError && searchText.includes("fetch")) return true;
14823
14989
  if (error instanceof DOMException && error.name === "AbortError") return true;
14824
- return NETWORK_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
14990
+ return NETWORK_ERROR_PATTERNS.some((pattern) => searchText.includes(pattern));
14825
14991
  }
14826
14992
  async function retryOnNetworkError(fn, opts = {}) {
14827
14993
  const maxRetries = opts.maxRetries ?? 3;
@@ -14838,7 +15004,7 @@ async function retryOnNetworkError(fn, opts = {}) {
14838
15004
  }
14839
15005
  const delay = Math.min(baseDelay * 2 ** attempt, maxDelay);
14840
15006
  opts.onRetry?.(attempt + 1, delay, error);
14841
- await new Promise((resolve7) => setTimeout(resolve7, delay));
15007
+ await new Promise((resolve8) => setTimeout(resolve8, delay));
14842
15008
  }
14843
15009
  }
14844
15010
  throw lastError;
@@ -14898,9 +15064,8 @@ function describeMarathonApiError(error) {
14898
15064
  if (!(error instanceof Error)) {
14899
15065
  return ["Task failed: Unknown error"];
14900
15066
  }
14901
- if (!(error instanceof RuntypeApiError) || error.statusCode !== 429) {
14902
- const message = error instanceof Error ? error.message : "Unknown error";
14903
- return [`Task failed: ${message}`];
15067
+ if (isTransientNetworkError(error)) {
15068
+ return describeNetworkError(error);
14904
15069
  }
14905
15070
  return [`Task failed: ${error.message}`];
14906
15071
  }
@@ -14913,6 +15078,72 @@ function formatMarathonApiError(error) {
14913
15078
  }
14914
15079
 
14915
15080
  // src/marathon/verification.ts
15081
+ var SAFE_ENV_KEYS = [
15082
+ "PATH",
15083
+ "HOME",
15084
+ "USER",
15085
+ "SHELL",
15086
+ "TERM",
15087
+ "COLORTERM",
15088
+ "FORCE_COLOR",
15089
+ "CI",
15090
+ "NODE_PATH",
15091
+ "NODE_ENV",
15092
+ "NPM_CONFIG_CACHE",
15093
+ "LANG",
15094
+ "LC_ALL",
15095
+ "LC_CTYPE",
15096
+ "TMPDIR",
15097
+ "TMP",
15098
+ "TEMP",
15099
+ "HOSTNAME",
15100
+ "PNPM_HOME",
15101
+ "XDG_CONFIG_HOME",
15102
+ "XDG_DATA_HOME",
15103
+ "XDG_CACHE_HOME"
15104
+ ];
15105
+ function getSanitizedEnv() {
15106
+ const clean = {};
15107
+ for (const key of SAFE_ENV_KEYS) {
15108
+ if (process.env[key]) clean[key] = process.env[key];
15109
+ }
15110
+ return clean;
15111
+ }
15112
+ function redactSecrets(output, secrets) {
15113
+ const sorted = secrets.filter((s) => s.length >= 8).sort((a, b) => b.length - a.length);
15114
+ let redacted = output;
15115
+ for (const secret of sorted) {
15116
+ redacted = redacted.replaceAll(secret, "[REDACTED]");
15117
+ }
15118
+ return redacted;
15119
+ }
15120
+ function collectSecretValues() {
15121
+ const secretPatterns = [
15122
+ /key/i,
15123
+ /secret/i,
15124
+ /token/i,
15125
+ /password/i,
15126
+ /credential/i,
15127
+ /auth/i,
15128
+ /api_key/i,
15129
+ /apikey/i,
15130
+ /private/i,
15131
+ /signing/i,
15132
+ /encryption/i,
15133
+ /database_url/i,
15134
+ /connection_string/i,
15135
+ /dsn/i
15136
+ ];
15137
+ const secrets = [];
15138
+ for (const [key, value] of Object.entries(process.env)) {
15139
+ if (!value || value.length < 8) continue;
15140
+ if (SAFE_ENV_KEYS.includes(key)) continue;
15141
+ if (secretPatterns.some((pattern) => pattern.test(key))) {
15142
+ secrets.push(value);
15143
+ }
15144
+ }
15145
+ return secrets;
15146
+ }
14916
15147
  var BLOCKED_VERIFICATION_PATTERNS = [
14917
15148
  /&&/,
14918
15149
  /\|\|/,
@@ -14951,10 +15182,54 @@ function isSafeVerificationCommand(command) {
14951
15182
  if (BLOCKED_VERIFICATION_PATTERNS.some((pattern) => pattern.test(trimmed))) return false;
14952
15183
  return ALLOWED_VERIFICATION_COMMANDS.some((pattern) => pattern.test(trimmed));
14953
15184
  }
15185
+ var SCRIPT_COMMAND_PATTERNS = [
15186
+ {
15187
+ pattern: /^(?:pnpm|npm|yarn|bun)\s+(?:test|lint|build|check|typecheck|run\s+\w+)\b/i,
15188
+ files: ["package.json"]
15189
+ },
15190
+ {
15191
+ pattern: /^make\s+/i,
15192
+ files: ["Makefile", "makefile", "GNUmakefile"]
15193
+ },
15194
+ {
15195
+ pattern: /^just\s+/i,
15196
+ files: ["justfile", "Justfile"]
15197
+ }
15198
+ ];
15199
+ function getScriptConfigFiles(command) {
15200
+ const trimmed = command.trim();
15201
+ for (const { pattern, files } of SCRIPT_COMMAND_PATTERNS) {
15202
+ if (pattern.test(trimmed)) return files;
15203
+ }
15204
+ return [];
15205
+ }
15206
+ function isScriptConfigModified(cwd, files) {
15207
+ try {
15208
+ const { spawnSync: spawnSync2 } = __require("child_process");
15209
+ const spawnOpts = { cwd, encoding: "utf-8", timeout: 5e3 };
15210
+ const failed = (r) => r.error != null || r.status === null || r.status !== 0;
15211
+ const diff = spawnSync2("git", ["diff", "--name-only", "HEAD", "--", ...files], spawnOpts);
15212
+ if (failed(diff)) return true;
15213
+ if (diff.stdout?.trim()) return true;
15214
+ const staged = spawnSync2("git", ["diff", "--name-only", "--cached", "--", ...files], spawnOpts);
15215
+ if (failed(staged)) return true;
15216
+ if (staged.stdout?.trim()) return true;
15217
+ const lsFiles = spawnSync2(
15218
+ "git",
15219
+ ["ls-files", "--others", "--exclude-standard", "--", ...files],
15220
+ spawnOpts
15221
+ );
15222
+ if (lsFiles.error != null || lsFiles.status === null) return true;
15223
+ if (lsFiles.stdout?.trim()) return true;
15224
+ return false;
15225
+ } catch {
15226
+ return true;
15227
+ }
15228
+ }
14954
15229
 
14955
15230
  // src/marathon/state.ts
14956
15231
  import * as fs7 from "fs";
14957
- import * as path7 from "path";
15232
+ import * as path8 from "path";
14958
15233
  import chalk15 from "chalk";
14959
15234
 
14960
15235
  // src/lib/select-prompt.ts
@@ -14989,14 +15264,14 @@ async function promptNumericSelect(choices, promptLabel) {
14989
15264
  output: process.stdout,
14990
15265
  terminal: true
14991
15266
  });
14992
- return new Promise((resolve7) => {
15267
+ return new Promise((resolve8) => {
14993
15268
  const ask = () => {
14994
15269
  rl.question(chalk14.cyan(`
14995
15270
  ${promptLabel} (1-${choices.length}): `), (answer) => {
14996
15271
  const value = parseInt(answer.trim(), 10);
14997
15272
  if (value >= 1 && value <= choices.length) {
14998
15273
  rl.close();
14999
- resolve7(choices[value - 1].value);
15274
+ resolve8(choices[value - 1].value);
15000
15275
  return;
15001
15276
  }
15002
15277
  console.log(chalk14.red(`Please enter a number between 1 and ${choices.length}.`));
@@ -15024,7 +15299,7 @@ ${message}`));
15024
15299
  const previousRawMode = input.isRaw === true;
15025
15300
  let selectedIndex = 0;
15026
15301
  let renderedLineCount = 0;
15027
- return new Promise((resolve7) => {
15302
+ return new Promise((resolve8) => {
15028
15303
  const renderMenu = () => {
15029
15304
  if (renderedLineCount > 0) {
15030
15305
  clearRenderedLines(output, renderedLineCount);
@@ -15046,7 +15321,7 @@ ${message}`));
15046
15321
  };
15047
15322
  const finish = (value) => {
15048
15323
  cleanup();
15049
- resolve7(value);
15324
+ resolve8(value);
15050
15325
  };
15051
15326
  const onKeypress = (_, key) => {
15052
15327
  if (key.ctrl && key.name === "c") {
@@ -15092,7 +15367,7 @@ function stateSafeName2(name) {
15092
15367
  }
15093
15368
  function stateFilePath(name, stateDir) {
15094
15369
  const dir = stateDir || defaultStateDir2();
15095
- return path7.join(dir, `${stateSafeName2(name)}.json`);
15370
+ return path8.join(dir, `${stateSafeName2(name)}.json`);
15096
15371
  }
15097
15372
  function normalizeMarathonStatePath(candidatePath) {
15098
15373
  if (!candidatePath) return void 0;
@@ -15101,15 +15376,15 @@ function normalizeMarathonStatePath(candidatePath) {
15101
15376
  }
15102
15377
  function marathonStatePathExists(candidatePath) {
15103
15378
  if (!candidatePath) return false;
15104
- return fs7.existsSync(path7.resolve(candidatePath));
15379
+ return fs7.existsSync(path8.resolve(candidatePath));
15105
15380
  }
15106
15381
  function isMarathonArtifactStatePath(candidatePath) {
15107
15382
  if (!candidatePath) return false;
15108
15383
  const normalized = normalizeMarathonStatePath(candidatePath)?.toLowerCase();
15109
15384
  if (normalized === ".runtype" || normalized?.startsWith(".runtype/") === true) return true;
15110
- const resolved = path7.resolve(candidatePath);
15385
+ const resolved = path8.resolve(candidatePath);
15111
15386
  const homeStateDir = getRuntypeHomeDir();
15112
- return resolved.startsWith(homeStateDir + path7.sep) || resolved === homeStateDir;
15387
+ return resolved.startsWith(homeStateDir + path8.sep) || resolved === homeStateDir;
15113
15388
  }
15114
15389
  function scoreMarathonCandidatePath(candidatePath) {
15115
15390
  const normalized = candidatePath.toLowerCase();
@@ -15282,7 +15557,7 @@ function loadState(filePath) {
15282
15557
  }
15283
15558
  }
15284
15559
  function saveState(filePath, state, options) {
15285
- const dir = path7.dirname(filePath);
15560
+ const dir = path8.dirname(filePath);
15286
15561
  fs7.mkdirSync(dir, { recursive: true });
15287
15562
  const stateToWrite = options?.stripSnapshotEvents && state.sessionSnapshots?.length ? {
15288
15563
  ...state,
@@ -15322,9 +15597,9 @@ function findStateFile(name, stateDir) {
15322
15597
  }
15323
15598
  const homePath = stateFilePath(name, getMarathonStateDir());
15324
15599
  if (fs7.existsSync(homePath)) return homePath;
15325
- const oldMarathonPath = stateFilePath(name, path7.join(process.cwd(), ".runtype", "marathons"));
15600
+ const oldMarathonPath = stateFilePath(name, path8.join(process.cwd(), ".runtype", "marathons"));
15326
15601
  if (fs7.existsSync(oldMarathonPath)) return oldMarathonPath;
15327
- const oldTaskPath = stateFilePath(name, path7.join(process.cwd(), ".runtype", "tasks"));
15602
+ const oldTaskPath = stateFilePath(name, path8.join(process.cwd(), ".runtype", "tasks"));
15328
15603
  if (fs7.existsSync(oldTaskPath)) return oldTaskPath;
15329
15604
  return homePath;
15330
15605
  }
@@ -15333,15 +15608,15 @@ function findLatestStateFile(stateDir) {
15333
15608
  // New home-dir location
15334
15609
  getMarathonStateDir(),
15335
15610
  // Old project-dir locations (backward compat)
15336
- path7.join(process.cwd(), ".runtype", "marathons"),
15337
- path7.join(process.cwd(), ".runtype", "tasks")
15611
+ path8.join(process.cwd(), ".runtype", "marathons"),
15612
+ path8.join(process.cwd(), ".runtype", "tasks")
15338
15613
  ];
15339
15614
  let latest = null;
15340
15615
  for (const dir of dirs) {
15341
15616
  if (!fs7.existsSync(dir)) continue;
15342
15617
  const files = fs7.readdirSync(dir).filter((f) => f.endsWith(".json"));
15343
15618
  for (const file of files) {
15344
- const fullPath = path7.join(dir, file);
15619
+ const fullPath = path8.join(dir, file);
15345
15620
  const stat = fs7.statSync(fullPath);
15346
15621
  if (!latest || stat.mtimeMs > latest.mtime) {
15347
15622
  latest = { name: file.replace(".json", ""), filePath: fullPath, mtime: stat.mtimeMs };
@@ -15463,12 +15738,12 @@ function buildResumeCommand(agent, options, parsedSandbox) {
15463
15738
 
15464
15739
  // src/marathon/local-tools.ts
15465
15740
  import * as fs9 from "fs";
15466
- import * as path9 from "path";
15741
+ import * as path10 from "path";
15467
15742
  import { spawnSync } from "child_process";
15468
15743
 
15469
15744
  // src/marathon/repo-discovery.ts
15470
15745
  import * as fs8 from "fs";
15471
- import * as path8 from "path";
15746
+ import * as path9 from "path";
15472
15747
  var IGNORED_REPO_DIRS = /* @__PURE__ */ new Set([
15473
15748
  ".git",
15474
15749
  ".next",
@@ -15566,8 +15841,8 @@ var SEARCH_STOP_WORDS = /* @__PURE__ */ new Set([
15566
15841
  "do"
15567
15842
  ]);
15568
15843
  function normalizeToolPath(toolPath) {
15569
- const resolved = path8.resolve(toolPath || ".");
15570
- return path8.relative(process.cwd(), resolved) || ".";
15844
+ const resolved = path9.resolve(toolPath || ".");
15845
+ return path9.relative(process.cwd(), resolved) || ".";
15571
15846
  }
15572
15847
  function tokenizeSearchQuery(query) {
15573
15848
  return query.toLowerCase().split(/[^a-z0-9./_-]+/g).map((token) => token.trim()).filter((token) => token.length >= 2 && !SEARCH_STOP_WORDS.has(token));
@@ -15576,7 +15851,7 @@ function scoreSearchPath(relativePath) {
15576
15851
  const normalized = relativePath.replace(/\\/g, "/");
15577
15852
  const segments = normalized.split("/");
15578
15853
  const fileName = segments[segments.length - 1] || normalized;
15579
- const extension = path8.extname(fileName).toLowerCase();
15854
+ const extension = path9.extname(fileName).toLowerCase();
15580
15855
  let score = 0;
15581
15856
  if (LOW_SIGNAL_FILE_NAMES.has(fileName)) score -= 50;
15582
15857
  if (segments.some((segment) => LOW_SIGNAL_PATH_SEGMENTS.has(segment))) score -= 15;
@@ -15592,7 +15867,7 @@ function shouldIgnoreRepoEntry(entryPath) {
15592
15867
  const normalized = normalizeToolPath(entryPath).replace(/\\/g, "/");
15593
15868
  if (normalized === ".") return false;
15594
15869
  if (isSensitivePath(normalized)) return true;
15595
- return normalized.split(path8.sep).some((segment) => IGNORED_REPO_DIRS.has(segment));
15870
+ return normalized.split(path9.sep).some((segment) => IGNORED_REPO_DIRS.has(segment));
15596
15871
  }
15597
15872
  function safeReadTextFile(filePath) {
15598
15873
  try {
@@ -15620,7 +15895,7 @@ function walkRepo(startPath, visitor) {
15620
15895
  continue;
15621
15896
  }
15622
15897
  for (const entry of entries) {
15623
- const entryPath = path8.join(currentDir, entry.name);
15898
+ const entryPath = path9.join(currentDir, entry.name);
15624
15899
  if (shouldIgnoreRepoEntry(entryPath)) continue;
15625
15900
  const shouldStop = visitor(entryPath, entry);
15626
15901
  if (shouldStop === true) return;
@@ -15665,7 +15940,7 @@ function buildTree(dirPath, maxDepth, depth = 0) {
15665
15940
  }
15666
15941
  const lines = [];
15667
15942
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
15668
- const entryPath = path8.join(dirPath, entry.name);
15943
+ const entryPath = path9.join(dirPath, entry.name);
15669
15944
  if (shouldIgnoreRepoEntry(entryPath)) continue;
15670
15945
  lines.push(`${" ".repeat(depth)}- ${entry.name}${entry.isDirectory() ? "/" : ""}`);
15671
15946
  if (entry.isDirectory() && depth < maxDepth) {
@@ -15681,14 +15956,14 @@ function stateSafeName3(name) {
15681
15956
  return name.replace(/[^a-zA-Z0-9_-]/g, "_");
15682
15957
  }
15683
15958
  function getOffloadedOutputDir(taskName, stateDir) {
15684
- return path9.join(stateDir || getMarathonStateDir(), stateSafeName3(taskName), "outputs");
15959
+ return path10.join(stateDir || getMarathonStateDir(), stateSafeName3(taskName), "outputs");
15685
15960
  }
15686
15961
  function isPathWithinRoot(targetPath, rootPath) {
15687
- const relativePath = path9.relative(rootPath, targetPath);
15688
- return relativePath === "" || !relativePath.startsWith("..") && !path9.isAbsolute(relativePath);
15962
+ const relativePath = path10.relative(rootPath, targetPath);
15963
+ return relativePath === "" || !relativePath.startsWith("..") && !path10.isAbsolute(relativePath);
15689
15964
  }
15690
15965
  function resolveCanonicalToolPath(toolPath, allowMissing) {
15691
- const absolutePath = path9.resolve(toolPath);
15966
+ const absolutePath = path10.resolve(toolPath);
15692
15967
  if (fs9.existsSync(absolutePath)) {
15693
15968
  return {
15694
15969
  canonicalPath: fs9.realpathSync.native(absolutePath),
@@ -15699,22 +15974,22 @@ function resolveCanonicalToolPath(toolPath, allowMissing) {
15699
15974
  const missingSegments = [];
15700
15975
  let currentPath = absolutePath;
15701
15976
  while (!fs9.existsSync(currentPath)) {
15702
- const parentPath = path9.dirname(currentPath);
15977
+ const parentPath = path10.dirname(currentPath);
15703
15978
  if (parentPath === currentPath) return null;
15704
- missingSegments.unshift(path9.basename(currentPath));
15979
+ missingSegments.unshift(path10.basename(currentPath));
15705
15980
  currentPath = parentPath;
15706
15981
  }
15707
15982
  return {
15708
- canonicalPath: path9.join(fs9.realpathSync.native(currentPath), ...missingSegments),
15983
+ canonicalPath: path10.join(fs9.realpathSync.native(currentPath), ...missingSegments),
15709
15984
  exists: false
15710
15985
  };
15711
15986
  }
15712
15987
  function canonicalizeAllowedRoot(rootPath) {
15713
- return resolveCanonicalToolPath(rootPath, true)?.canonicalPath || path9.resolve(rootPath);
15988
+ return resolveCanonicalToolPath(rootPath, true)?.canonicalPath || path10.resolve(rootPath);
15714
15989
  }
15715
15990
  function findBlockedWorkspaceSegment(targetPath, workspaceRoot) {
15716
15991
  if (!isPathWithinRoot(targetPath, workspaceRoot)) return void 0;
15717
- const relativePath = path9.relative(workspaceRoot, targetPath).replace(/\\/g, "/");
15992
+ const relativePath = path10.relative(workspaceRoot, targetPath).replace(/\\/g, "/");
15718
15993
  if (!relativePath) return void 0;
15719
15994
  return relativePath.split("/").find((segment) => DIRECT_TOOL_BLOCKED_SEGMENTS.has(segment));
15720
15995
  }
@@ -15743,19 +16018,24 @@ function resolveToolPath(toolPath, options = {}) {
15743
16018
  };
15744
16019
  }
15745
16020
  if (matchedRoot === workspaceRoot) {
15746
- const blockedSegment = findBlockedWorkspaceSegment(resolved.canonicalPath, workspaceRoot);
15747
- if (blockedSegment) {
15748
- return {
15749
- ok: false,
15750
- error: `Access denied: ${requestedPath} is inside restricted workspace state (${blockedSegment})`
15751
- };
15752
- }
15753
- const relativeFromWorkspace = path9.relative(workspaceRoot, resolved.canonicalPath).replace(/\\/g, "/");
15754
- if (isSensitivePath(relativeFromWorkspace)) {
15755
- return {
15756
- ok: false,
15757
- error: `Access denied: ${requestedPath} is a sensitive path and cannot be read or written`
15758
- };
16021
+ const isUnderExtraRoot = extraRoots.some(
16022
+ (extraRoot) => isPathWithinRoot(resolved.canonicalPath, extraRoot)
16023
+ );
16024
+ if (!isUnderExtraRoot) {
16025
+ const blockedSegment = findBlockedWorkspaceSegment(resolved.canonicalPath, workspaceRoot);
16026
+ if (blockedSegment) {
16027
+ return {
16028
+ ok: false,
16029
+ error: `Access denied: ${requestedPath} is inside restricted workspace state (${blockedSegment})`
16030
+ };
16031
+ }
16032
+ const relativeFromWorkspace = path10.relative(workspaceRoot, resolved.canonicalPath).replace(/\\/g, "/");
16033
+ if (isSensitivePath(relativeFromWorkspace)) {
16034
+ return {
16035
+ ok: false,
16036
+ error: `Access denied: ${requestedPath} is a sensitive path and cannot be read or written`
16037
+ };
16038
+ }
15759
16039
  }
15760
16040
  }
15761
16041
  if (resolved.exists) {
@@ -15778,15 +16058,17 @@ function resolveToolPath(toolPath, options = {}) {
15778
16058
  return { ok: true, resolvedPath: resolved.canonicalPath };
15779
16059
  }
15780
16060
  function getTaskStateRoot(taskName, stateDir) {
15781
- return path9.join(stateDir || getMarathonStateDir(), stateSafeName3(taskName));
16061
+ return path10.join(stateDir || getMarathonStateDir(), stateSafeName3(taskName));
15782
16062
  }
15783
16063
  function createDefaultLocalTools(context) {
15784
16064
  const taskStateRoot = context?.taskName ? getTaskStateRoot(context.taskName, context.stateDir) : void 0;
15785
- const planDir = context?.taskName ? path9.resolve(`.runtype/marathons/${stateSafeName3(context.taskName)}`) : void 0;
16065
+ const planDir = context?.taskName ? path10.resolve(`.runtype/marathons/${stateSafeName3(context.taskName)}`) : void 0;
16066
+ const planPathDir = context?.planPath ? path10.resolve(path10.dirname(context.planPath)) : void 0;
15786
16067
  const allowedReadRoots = context?.taskName ? [
15787
16068
  getOffloadedOutputDir(context.taskName, context.stateDir),
15788
16069
  ...taskStateRoot ? [taskStateRoot] : [],
15789
- ...planDir ? [planDir] : []
16070
+ ...planDir ? [planDir] : [],
16071
+ ...planPathDir ? [planPathDir] : []
15790
16072
  ] : [];
15791
16073
  return {
15792
16074
  read_file: {
@@ -15824,7 +16106,7 @@ function createDefaultLocalTools(context) {
15824
16106
  if (fs9.existsSync(resolvedPath.resolvedPath) && fs9.statSync(resolvedPath.resolvedPath).isDirectory()) {
15825
16107
  return `Error: Path is a directory: ${String(args.path || "")}`;
15826
16108
  }
15827
- const dir = path9.dirname(resolvedPath.resolvedPath);
16109
+ const dir = path10.dirname(resolvedPath.resolvedPath);
15828
16110
  fs9.mkdirSync(dir, { recursive: true });
15829
16111
  fs9.writeFileSync(resolvedPath.resolvedPath, content);
15830
16112
  return "ok";
@@ -15841,7 +16123,7 @@ function createDefaultLocalTools(context) {
15841
16123
  requireDirectory: true
15842
16124
  });
15843
16125
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
15844
- return fs9.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path9.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
16126
+ return fs9.readdirSync(resolvedPath.resolvedPath, { withFileTypes: true }).filter((entry) => !shouldIgnoreRepoEntry(path10.join(resolvedPath.resolvedPath, entry.name))).map((entry) => entry.name).join("\n");
15845
16127
  }
15846
16128
  },
15847
16129
  search_repo: {
@@ -15988,9 +16270,12 @@ function createDefaultLocalTools(context) {
15988
16270
  }
15989
16271
  };
15990
16272
  }
15991
- function createCheckpointedWriteFileTool(taskName, stateDir) {
16273
+ function createCheckpointedWriteFileTool(taskName, stateDir, planPath) {
15992
16274
  const taskStateRoot = getTaskStateRoot(taskName, stateDir);
15993
- const planDir = path9.resolve(`.runtype/marathons/${stateSafeName3(taskName)}`);
16275
+ const planDir = path10.resolve(`.runtype/marathons/${stateSafeName3(taskName)}`);
16276
+ const marathonPlanRoot = path10.resolve(".runtype/marathons");
16277
+ const planPathDir = planPath ? path10.resolve(path10.dirname(planPath)) : void 0;
16278
+ const writeAllowedRoots = [taskStateRoot, planDir, marathonPlanRoot, ...planPathDir ? [planPathDir] : []];
15994
16279
  return {
15995
16280
  description: "Write content to a file, creating directories as needed and checkpointing original repo files",
15996
16281
  parametersSchema: {
@@ -16004,14 +16289,102 @@ function createCheckpointedWriteFileTool(taskName, stateDir) {
16004
16289
  execute: async (args) => {
16005
16290
  const resolvedPath = resolveToolPath(String(args.path || ""), {
16006
16291
  allowMissing: true,
16007
- allowedRoots: [taskStateRoot, planDir]
16292
+ allowedRoots: writeAllowedRoots
16008
16293
  });
16009
16294
  if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
16010
16295
  const content = String(args.content || "");
16011
16296
  ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir);
16012
- const dir = path9.dirname(resolvedPath.resolvedPath);
16297
+ const dir = path10.dirname(resolvedPath.resolvedPath);
16013
16298
  fs9.mkdirSync(dir, { recursive: true });
16014
16299
  fs9.writeFileSync(resolvedPath.resolvedPath, content);
16300
+ const ext = path10.extname(resolvedPath.resolvedPath).toLowerCase();
16301
+ if (ext === ".js" || ext === ".jsx") {
16302
+ const syntaxError = quickJsSyntaxCheck(content);
16303
+ if (syntaxError) {
16304
+ return `ok
16305
+
16306
+ \u26A0\uFE0F Syntax error detected in written file: ${syntaxError}. The file was written but contains errors. Read it back and fix the issues.`;
16307
+ }
16308
+ }
16309
+ return "ok";
16310
+ }
16311
+ };
16312
+ }
16313
+ function quickJsSyntaxCheck(content) {
16314
+ try {
16315
+ new Function(content);
16316
+ return void 0;
16317
+ } catch (err) {
16318
+ if (err instanceof SyntaxError) {
16319
+ return err.message;
16320
+ }
16321
+ return void 0;
16322
+ }
16323
+ }
16324
+ function createEditFileTool(taskName, stateDir, planPath) {
16325
+ const taskStateRoot = getTaskStateRoot(taskName, stateDir);
16326
+ const planDir = path10.resolve(`.runtype/marathons/${stateSafeName3(taskName)}`);
16327
+ const marathonPlanRoot = path10.resolve(".runtype/marathons");
16328
+ const planPathDir = planPath ? path10.resolve(path10.dirname(planPath)) : void 0;
16329
+ const writeAllowedRoots = [taskStateRoot, planDir, marathonPlanRoot, ...planPathDir ? [planPathDir] : []];
16330
+ return {
16331
+ description: "Make a targeted edit to a file by replacing a specific string. Much more efficient than rewriting the entire file with write_file. The old_string must appear exactly once in the file (or use replace_all for global replacement). Always read the file first to get the exact text to replace.",
16332
+ parametersSchema: {
16333
+ type: "object",
16334
+ properties: {
16335
+ path: { type: "string", description: "File path to edit" },
16336
+ // @snake-case-ok: Parameter names use snake_case to match agent tool conventions
16337
+ old_string: { type: "string", description: "Exact string to find and replace (must be unique in the file)" },
16338
+ // @snake-case-ok: Parameter names use snake_case to match agent tool conventions
16339
+ new_string: { type: "string", description: "Replacement string" },
16340
+ // @snake-case-ok: Parameter names use snake_case to match agent tool conventions
16341
+ replace_all: {
16342
+ type: "boolean",
16343
+ description: "If true, replace all occurrences instead of requiring uniqueness. Default: false"
16344
+ }
16345
+ },
16346
+ required: ["path", "old_string", "new_string"]
16347
+ },
16348
+ execute: async (args) => {
16349
+ const resolvedPath = resolveToolPath(String(args.path || ""), {
16350
+ requireFile: true,
16351
+ allowedRoots: writeAllowedRoots
16352
+ });
16353
+ if (!resolvedPath.ok) return `Error: ${resolvedPath.error}`;
16354
+ const oldString = String(args.old_string ?? "");
16355
+ const newString = String(args.new_string ?? "");
16356
+ const replaceAll = Boolean(args.replace_all);
16357
+ if (!oldString) return "Error: old_string cannot be empty";
16358
+ if (oldString === newString) return "Error: old_string and new_string are identical";
16359
+ let content;
16360
+ try {
16361
+ content = fs9.readFileSync(resolvedPath.resolvedPath, "utf-8");
16362
+ } catch {
16363
+ return `Error: Could not read file: ${String(args.path || "")}`;
16364
+ }
16365
+ if (!content.includes(oldString)) {
16366
+ return `Error: old_string not found in ${String(args.path || "")}. Read the file first to get the exact text.`;
16367
+ }
16368
+ if (!replaceAll) {
16369
+ const firstIndex = content.indexOf(oldString);
16370
+ const secondIndex = content.indexOf(oldString, firstIndex + 1);
16371
+ if (secondIndex !== -1) {
16372
+ const count = content.split(oldString).length - 1;
16373
+ return `Error: old_string appears ${count} times in the file. Provide more surrounding context to make it unique, or set replace_all to true.`;
16374
+ }
16375
+ }
16376
+ ensureMarathonFileCheckpoint(taskName, resolvedPath.resolvedPath, stateDir);
16377
+ const updated = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
16378
+ fs9.writeFileSync(resolvedPath.resolvedPath, updated);
16379
+ const ext = path10.extname(resolvedPath.resolvedPath).toLowerCase();
16380
+ if (ext === ".js" || ext === ".jsx") {
16381
+ const syntaxError = quickJsSyntaxCheck(updated);
16382
+ if (syntaxError) {
16383
+ return `ok
16384
+
16385
+ \u26A0\uFE0F Syntax error detected after edit: ${syntaxError}. The edit was applied but the file now contains errors.`;
16386
+ }
16387
+ }
16015
16388
  return "ok";
16016
16389
  }
16017
16390
  };
@@ -16058,7 +16431,7 @@ function createReadOffloadedOutputTool(taskName, stateDir) {
16058
16431
  if (!/^[a-zA-Z0-9_-]+$/.test(outputId)) {
16059
16432
  return `Error: invalid offloaded output id: ${outputId}`;
16060
16433
  }
16061
- const outputPath = path9.join(getOffloadedOutputDir(taskName, stateDir), `${outputId}.txt`);
16434
+ const outputPath = path10.join(getOffloadedOutputDir(taskName, stateDir), `${outputId}.txt`);
16062
16435
  if (!fs9.existsSync(outputPath) || !fs9.statSync(outputPath).isFile()) {
16063
16436
  return `Error: offloaded output not found: ${outputId}`;
16064
16437
  }
@@ -16100,6 +16473,15 @@ function createRunCheckTool() {
16100
16473
  error: "Blocked unsafe verification command. Use a single non-destructive lint/test/typecheck/build command."
16101
16474
  });
16102
16475
  }
16476
+ const configFiles = getScriptConfigFiles(command);
16477
+ if (configFiles.length > 0 && isScriptConfigModified(process.cwd(), configFiles)) {
16478
+ return JSON.stringify({
16479
+ success: false,
16480
+ blocked: true,
16481
+ command,
16482
+ error: `Blocked: ${configFiles.join("/")} has been modified since last commit. Cannot run script commands when config files have uncommitted changes.`
16483
+ });
16484
+ }
16103
16485
  const timeoutMs = Math.max(
16104
16486
  1e3,
16105
16487
  Math.min(
@@ -16113,9 +16495,11 @@ function createRunCheckTool() {
16113
16495
  encoding: "utf-8",
16114
16496
  shell: true,
16115
16497
  timeout: timeoutMs,
16116
- maxBuffer: 1024 * 1024 * 4
16498
+ maxBuffer: 1024 * 1024 * 4,
16499
+ env: getSanitizedEnv()
16117
16500
  });
16118
- const output = `${result.stdout || ""}${result.stderr || ""}`.trim().slice(0, 12e3);
16501
+ const rawOutput = `${result.stdout || ""}${result.stderr || ""}`.trim().slice(0, 12e3);
16502
+ const output = redactSecrets(rawOutput, collectSecretValues());
16119
16503
  return JSON.stringify({
16120
16504
  success: result.status === 0 && !result.error,
16121
16505
  command,
@@ -16134,12 +16518,64 @@ function createRunCheckTool() {
16134
16518
  }
16135
16519
  };
16136
16520
  }
16521
+ function createSearchSessionHistoryTool(client, taskName) {
16522
+ return {
16523
+ description: "Search across all prior marathon sessions for specific information, decisions, findings, or tool outputs. Use this when you need to recall something from earlier sessions that may have been compacted away. Returns ranked results with content snippets from matching sessions.",
16524
+ parametersSchema: {
16525
+ type: "object",
16526
+ properties: {
16527
+ query: {
16528
+ type: "string",
16529
+ description: 'What to search for (e.g. "authentication flow decisions", "test failures in auth module")'
16530
+ },
16531
+ limit: {
16532
+ type: "number",
16533
+ description: "Maximum number of results to return (default 5, max 20)"
16534
+ },
16535
+ types: {
16536
+ type: "array",
16537
+ items: { type: "string", enum: ["response", "reasoning", "tool_output"] },
16538
+ description: "Filter by content type (default: all types)"
16539
+ }
16540
+ },
16541
+ required: ["query"]
16542
+ },
16543
+ execute: async (args) => {
16544
+ const query = String(args.query || "").trim();
16545
+ if (!query) return "Error: query is required";
16546
+ const limit = Math.max(1, Math.min(20, Number(args.limit) || 5));
16547
+ const types = Array.isArray(args.types) ? args.types : void 0;
16548
+ try {
16549
+ const response = await client.post("/session-context/search", {
16550
+ query,
16551
+ taskName,
16552
+ limit,
16553
+ ...types ? { types } : {}
16554
+ });
16555
+ if (!response.success || !response.results || response.results.length === 0) {
16556
+ return "No matching session context found for your query.";
16557
+ }
16558
+ const formatted = response.results.map((r, i) => {
16559
+ const header = `[Result ${i + 1}] Session ${r.sessionIndex} | ${r.type}${r.toolName ? ` (${r.toolName})` : ""} | Score: ${r.score.toFixed(3)}`;
16560
+ return `${header}
16561
+ ${r.content}`;
16562
+ });
16563
+ return `Found ${response.count} matching results:
16564
+
16565
+ ${formatted.join("\n\n---\n\n")}`;
16566
+ } catch (error) {
16567
+ return `Session search unavailable: ${error instanceof Error ? error.message : String(error)}`;
16568
+ }
16569
+ }
16570
+ };
16571
+ }
16137
16572
  function buildLocalTools(client, sandboxProvider, options, context) {
16138
16573
  const enabledTools = {};
16139
16574
  if (!options.noLocalTools) {
16140
16575
  Object.assign(enabledTools, createDefaultLocalTools(context));
16141
16576
  if (context) {
16142
- enabledTools.write_file = createCheckpointedWriteFileTool(context.taskName, context.stateDir);
16577
+ enabledTools.write_file = createCheckpointedWriteFileTool(context.taskName, context.stateDir, context.planPath);
16578
+ enabledTools.edit_file = createEditFileTool(context.taskName, context.stateDir, context.planPath);
16143
16579
  enabledTools.restore_file_checkpoint = createRestoreFileCheckpointTool(
16144
16580
  context.taskName,
16145
16581
  context.stateDir
@@ -16149,6 +16585,9 @@ function buildLocalTools(client, sandboxProvider, options, context) {
16149
16585
  context.stateDir
16150
16586
  );
16151
16587
  enabledTools.run_check = createRunCheckTool();
16588
+ if (options.sessionSearch === true) {
16589
+ enabledTools.search_session_history = createSearchSessionHistoryTool(client, context.taskName);
16590
+ }
16152
16591
  }
16153
16592
  }
16154
16593
  if (sandboxProvider) {
@@ -16160,6 +16599,62 @@ function buildLocalTools(client, sandboxProvider, options, context) {
16160
16599
  return Object.keys(enabledTools).length > 0 ? enabledTools : void 0;
16161
16600
  }
16162
16601
 
16602
+ // src/marathon/session-chunker.ts
16603
+ var DEFAULT_MAX_CHUNK_CHARS = 2e3;
16604
+ var MIN_CONTENT_LENGTH = 50;
16605
+ function extractSessionChunks(snapshot, maxChunkChars = DEFAULT_MAX_CHUNK_CHARS) {
16606
+ const chunks = [];
16607
+ if (snapshot.content && snapshot.content.length >= MIN_CONTENT_LENGTH) {
16608
+ chunks.push(...chunkText(snapshot.content, "response", maxChunkChars));
16609
+ }
16610
+ if (snapshot.reasoning && snapshot.reasoning.length >= MIN_CONTENT_LENGTH) {
16611
+ chunks.push(...chunkText(snapshot.reasoning, "reasoning", maxChunkChars));
16612
+ }
16613
+ for (const tool of snapshot.tools) {
16614
+ const result = typeof tool.result === "string" ? tool.result : JSON.stringify(tool.result ?? "");
16615
+ if (result.length >= MIN_CONTENT_LENGTH) {
16616
+ chunks.push(
16617
+ ...chunkText(result, "tool_output", maxChunkChars, tool.name)
16618
+ );
16619
+ }
16620
+ }
16621
+ return chunks;
16622
+ }
16623
+ function chunkText(text, type, maxChars, toolName) {
16624
+ if (text.length <= maxChars) {
16625
+ return [{ content: text, type, ...toolName ? { toolName } : {} }];
16626
+ }
16627
+ const chunks = [];
16628
+ const paragraphs = text.split(/\n\n+/);
16629
+ let current = "";
16630
+ for (const paragraph of paragraphs) {
16631
+ if (paragraph.length > maxChars) {
16632
+ if (current.length >= MIN_CONTENT_LENGTH) {
16633
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16634
+ current = "";
16635
+ }
16636
+ const sentences = paragraph.match(/[^.!?]+[.!?]+\s*|[^.!?]+$/g) || [paragraph];
16637
+ for (const sentence of sentences) {
16638
+ if (current.length + sentence.length > maxChars && current.length > 0) {
16639
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16640
+ current = "";
16641
+ }
16642
+ current += sentence;
16643
+ }
16644
+ continue;
16645
+ }
16646
+ if (current.length + paragraph.length + 2 > maxChars && current.length >= MIN_CONTENT_LENGTH) {
16647
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16648
+ current = "";
16649
+ }
16650
+ current += (current ? "\n\n" : "") + paragraph;
16651
+ }
16652
+ if (current.length >= MIN_CONTENT_LENGTH) {
16653
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16654
+ }
16655
+ return chunks;
16656
+ }
16657
+
16163
16658
  // src/marathon/loop-detector.ts
16164
16659
  var DEFAULT_MAX_HISTORY = 30;
16165
16660
  var DEFAULT_MIN_PATTERN_LENGTH = 2;
@@ -16241,7 +16736,7 @@ function createLoopDetector(options = {}) {
16241
16736
 
16242
16737
  // src/marathon/context-offload.ts
16243
16738
  import * as fs10 from "fs";
16244
- import * as path10 from "path";
16739
+ import * as path11 from "path";
16245
16740
  var DEFAULT_OUTPUT_THRESHOLD = 2e3;
16246
16741
  var DEFAULT_PREVIEW_LENGTH = 200;
16247
16742
  function buildOffloadedOutputId(toolName) {
@@ -16273,13 +16768,13 @@ function offloadToolOutput(taskName, toolName, output, options = {}, stateDir) {
16273
16768
  return { offloaded: false, content: output };
16274
16769
  }
16275
16770
  const outputId = buildOffloadedOutputId(toolName);
16276
- const dir = path10.join(
16771
+ const dir = path11.join(
16277
16772
  stateDir || defaultStateDir3(),
16278
16773
  stateSafeName4(taskName),
16279
16774
  "outputs"
16280
16775
  );
16281
16776
  const fileName = `${outputId}.txt`;
16282
- const filePath = path10.join(dir, fileName);
16777
+ const filePath = path11.join(dir, fileName);
16283
16778
  fs10.mkdirSync(dir, { recursive: true });
16284
16779
  fs10.writeFileSync(filePath, output, "utf-8");
16285
16780
  const preview = output.slice(0, previewLength).replace(/\n/g, " ");
@@ -16454,17 +16949,17 @@ function resolveAutoCompactTokenThreshold(modelContextLength, rawThreshold, stra
16454
16949
 
16455
16950
  // src/marathon/recipes.ts
16456
16951
  import * as fs11 from "fs";
16457
- import * as path11 from "path";
16952
+ import * as path12 from "path";
16458
16953
  var RULES_DIR = ".marathon/rules";
16459
16954
  function loadRules(cwd) {
16460
16955
  const baseCwd = cwd || process.cwd();
16461
- const rulesDir = path11.resolve(baseCwd, RULES_DIR);
16956
+ const rulesDir = path12.resolve(baseCwd, RULES_DIR);
16462
16957
  if (!fs11.existsSync(rulesDir)) return [];
16463
16958
  const rules = [];
16464
16959
  try {
16465
16960
  const entries = fs11.readdirSync(rulesDir).filter((f) => f.endsWith(".md"));
16466
16961
  for (const entry of entries) {
16467
- const filePath = path11.join(rulesDir, entry);
16962
+ const filePath = path12.join(rulesDir, entry);
16468
16963
  try {
16469
16964
  const raw = fs11.readFileSync(filePath, "utf-8");
16470
16965
  const parsed = parseRuleFile(raw);
@@ -16541,7 +17036,7 @@ function resolveErrorHandlingForPhase(phase, cliFallbackModel, milestoneFallback
16541
17036
 
16542
17037
  // src/marathon/playbook-loader.ts
16543
17038
  import * as fs12 from "fs";
16544
- import * as path12 from "path";
17039
+ import * as path13 from "path";
16545
17040
  import * as os4 from "os";
16546
17041
  import micromatch from "micromatch";
16547
17042
  import { parse as parseYaml } from "yaml";
@@ -16556,20 +17051,20 @@ function getCandidatePaths(nameOrPath, cwd) {
16556
17051
  const home = os4.homedir();
16557
17052
  return [
16558
17053
  // Exact path
16559
- path12.resolve(cwd, nameOrPath),
17054
+ path13.resolve(cwd, nameOrPath),
16560
17055
  // Repo-level
16561
- path12.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
16562
- path12.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
16563
- path12.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.json`),
17056
+ path13.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
17057
+ path13.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
17058
+ path13.resolve(cwd, PLAYBOOKS_DIR, `${nameOrPath}.json`),
16564
17059
  // User-level
16565
- path12.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
16566
- path12.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
16567
- path12.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`)
17060
+ path13.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yaml`),
17061
+ path13.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.yml`),
17062
+ path13.resolve(home, PLAYBOOKS_DIR, `${nameOrPath}.json`)
16568
17063
  ];
16569
17064
  }
16570
17065
  function parsePlaybookFile(filePath) {
16571
17066
  const raw = fs12.readFileSync(filePath, "utf-8");
16572
- const ext = path12.extname(filePath).toLowerCase();
17067
+ const ext = path13.extname(filePath).toLowerCase();
16573
17068
  if (ext === ".json") {
16574
17069
  return JSON.parse(raw);
16575
17070
  }
@@ -16835,6 +17330,8 @@ function buildMarathonStartupModelOptions(configuredModels, availableModels) {
16835
17330
  for (const model of configuredModels) {
16836
17331
  const modelId = model.modelId?.trim();
16837
17332
  if (!modelId) continue;
17333
+ const provider = model.provider?.trim().toLowerCase() ?? "";
17334
+ if ((0, import_integration_providers.isNonAIProvider)(provider) || (0, import_integration_providers.isNonAIProvider)(modelId)) continue;
16838
17335
  pushOption({
16839
17336
  label: model.displayName?.trim() || modelId,
16840
17337
  value: modelId,
@@ -16846,6 +17343,8 @@ function buildMarathonStartupModelOptions(configuredModels, availableModels) {
16846
17343
  for (const provider of model.providers) {
16847
17344
  const modelId2 = provider.modelId?.trim() || model.baseModel?.trim();
16848
17345
  if (!modelId2) continue;
17346
+ const providerName = provider.provider?.trim().toLowerCase() ?? "";
17347
+ if ((0, import_integration_providers.isNonAIProvider)(providerName) || (0, import_integration_providers.isNonAIProvider)(modelId2)) continue;
16849
17348
  pushOption({
16850
17349
  label: provider.displayName?.trim() || model.displayName?.trim() || model.baseModel?.trim() || modelId2,
16851
17350
  value: modelId2,
@@ -16856,6 +17355,7 @@ function buildMarathonStartupModelOptions(configuredModels, availableModels) {
16856
17355
  }
16857
17356
  const modelId = model.baseModel?.trim();
16858
17357
  if (!modelId) continue;
17358
+ if ((0, import_integration_providers.isNonAIProvider)(modelId)) continue;
16859
17359
  pushOption({
16860
17360
  label: model.displayName?.trim() || modelId,
16861
17361
  value: modelId,
@@ -17021,14 +17521,15 @@ async function taskAction(agent, options) {
17021
17521
  const renderedShell = render9(
17022
17522
  React9.createElement(MarathonStartupShell, {
17023
17523
  startupRef: startupShellRef,
17024
- marathonAppProps: null
17524
+ marathonAppProps: null,
17525
+ version: getCliVersion()
17025
17526
  }),
17026
17527
  { exitOnCtrlC: false }
17027
17528
  );
17028
17529
  waitForUiExit = renderedShell.waitUntilExit;
17029
17530
  rerenderUi = renderedShell.rerender;
17030
17531
  unmountUi = renderedShell.unmount;
17031
- await new Promise((resolve7) => setTimeout(resolve7, 0));
17532
+ await new Promise((resolve8) => setTimeout(resolve8, 0));
17032
17533
  if (!startupShellRef.current) {
17033
17534
  exitAltScreen();
17034
17535
  unmountUi?.();
@@ -17090,6 +17591,9 @@ async function taskAction(agent, options) {
17090
17591
  console.log(chalk16.green(`Created agent: ${agentId}`));
17091
17592
  }
17092
17593
  } catch (createErr) {
17594
+ if (isTransientNetworkError(createErr)) {
17595
+ await failBeforeMain(formatMarathonApiError(createErr));
17596
+ }
17093
17597
  const errMsg = createErr instanceof Error ? createErr.message : String(createErr);
17094
17598
  await failBeforeMain([
17095
17599
  chalk16.red(`Failed to create agent "${normalizedAgent}"`),
@@ -17098,6 +17602,9 @@ async function taskAction(agent, options) {
17098
17602
  }
17099
17603
  }
17100
17604
  } catch (error) {
17605
+ if (isTransientNetworkError(error)) {
17606
+ await failBeforeMain(formatMarathonApiError(error));
17607
+ }
17101
17608
  const errMsg = error instanceof Error ? error.message : String(error);
17102
17609
  await failBeforeMain([
17103
17610
  chalk16.red("Failed to list agents"),
@@ -17402,9 +17909,11 @@ ${rulesContext}`;
17402
17909
  });
17403
17910
  }
17404
17911
  }
17912
+ const resolvedPlanPath = resumeLoadedState?.planPath ?? resumeState?.planPath ?? getDefaultPlanPath(taskName);
17405
17913
  let localTools = buildLocalTools(client, parsedSandbox, options, {
17406
17914
  taskName,
17407
- stateDir: options.stateDir
17915
+ stateDir: options.stateDir,
17916
+ planPath: resolvedPlanPath
17408
17917
  });
17409
17918
  const resolveContextLimitForModel = (modelId) => resolveModelContextLength(modelId, configuredModels, availableModels);
17410
17919
  const resolveCompactStrategyForModel = (modelId) => {
@@ -17464,7 +17973,8 @@ ${rulesContext}`;
17464
17973
  const checkpointHasConfigChanges = (result) => Boolean(result.model) || result.tools === true || result.sandbox !== void 0;
17465
17974
  const rebuildCheckpointTools = () => buildLocalTools(client, parsedSandbox, options, {
17466
17975
  taskName,
17467
- stateDir: options.stateDir
17976
+ stateDir: options.stateDir,
17977
+ planPath: resolvedPlanPath
17468
17978
  });
17469
17979
  const applyCheckpointConfig = (result) => {
17470
17980
  if (result.model) {
@@ -17551,7 +18061,8 @@ Saving state... done. Session saved to ${filePath}`);
17551
18061
  rerenderUi?.(
17552
18062
  React9.createElement(MarathonStartupShell, {
17553
18063
  startupRef: startupShellRef,
17554
- marathonAppProps
18064
+ marathonAppProps,
18065
+ version: getCliVersion()
17555
18066
  })
17556
18067
  );
17557
18068
  await startupShellRef.current?.completeStartup();
@@ -17564,7 +18075,7 @@ Saving state... done. Session saved to ${filePath}`);
17564
18075
  waitForUiExit = renderedApp.waitUntilExit;
17565
18076
  unmountUi = renderedApp.unmount;
17566
18077
  }
17567
- await new Promise((resolve7) => setTimeout(resolve7, 0));
18078
+ await new Promise((resolve8) => setTimeout(resolve8, 0));
17568
18079
  const streamActions = streamRef.current;
17569
18080
  if (!streamActions) {
17570
18081
  exitAltScreen();
@@ -17689,7 +18200,7 @@ Saving state... done. Session saved to ${filePath}`);
17689
18200
  };
17690
18201
  if (event.phase === "start") {
17691
18202
  currentActions.startContextCompaction(absoluteEvent);
17692
- await new Promise((resolve7) => setTimeout(resolve7, 0));
18203
+ await new Promise((resolve8) => setTimeout(resolve8, 0));
17693
18204
  return;
17694
18205
  }
17695
18206
  currentActions.finishContextCompaction(absoluteEvent);
@@ -17752,6 +18263,21 @@ Saving state... done. Session saved to ${filePath}`);
17752
18263
  resumeState = extractRunTaskResumeState(adjustedState);
17753
18264
  lastSessionMessages = state.messages ?? [];
17754
18265
  saveState(filePath, adjustedState, { stripSnapshotEvents: !!eventLogWriter });
18266
+ if (options.sessionSearch === true) {
18267
+ const latestSnapshot = persistedSessionSnapshots[persistedSessionSnapshots.length - 1];
18268
+ if (latestSnapshot) {
18269
+ const chunks = extractSessionChunks(latestSnapshot);
18270
+ if (chunks.length > 0) {
18271
+ const sessionIdx = currentSessionOffset + state.sessionCount - 1;
18272
+ client.post("/session-context/index", {
18273
+ taskName,
18274
+ sessionIndex: sessionIdx,
18275
+ chunks
18276
+ }).catch(() => {
18277
+ });
18278
+ }
18279
+ }
18280
+ }
17755
18281
  if (resumeState?.workflowPhase) {
17756
18282
  const displayMilestone = detectedVariant === "external" && resumeState.workflowPhase === "research" && adjustedState.planWritten ? "report" : resumeState.workflowPhase;
17757
18283
  streamRef.current?.updateMilestone(displayMilestone);
@@ -17916,7 +18442,7 @@ Saving state... done. Session saved to ${filePath}`);
17916
18442
  toolCallsMade: accumulatedToolCalls,
17917
18443
  tokensInput: accumulatedTokens.input,
17918
18444
  tokensOutput: accumulatedTokens.output,
17919
- cost: accumulatedCost,
18445
+ cost: priorCost + accumulatedCost,
17920
18446
  outputPreview: terminalPreview.slice(0, 100)
17921
18447
  };
17922
18448
  const checkpointResult = await currentActions.requestCheckpoint(recap, true);
@@ -18127,7 +18653,7 @@ ${details}`);
18127
18653
  }
18128
18654
  return resolved;
18129
18655
  }
18130
- function detectDeployWorkflow(_message, sandboxProvider, resumeState) {
18656
+ function detectDeployWorkflow(_message, _sandboxProvider, resumeState) {
18131
18657
  if (resumeState?.workflowVariant === "game") return gameWorkflow;
18132
18658
  if (resumeState?.workflowPhase === "design" || resumeState?.workflowPhase === "build" || resumeState?.workflowPhase === "verify") {
18133
18659
  return gameWorkflow;
@@ -18158,7 +18684,7 @@ function resolveSandboxWorkflowSelection(message, sandboxProvider, resumeState)
18158
18684
  };
18159
18685
  }
18160
18686
  function applyTaskOptions(cmd) {
18161
- 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-reasoning", "Disable model reasoning/thinking (enabled by default for supported models)").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("--fallback-model <modelId>", "Model to fall back to when primary model fails").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);
18687
+ 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("--session-search", "Enable session context indexing and search_session_history tool").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-reasoning", "Disable model reasoning/thinking (enabled by default for supported models)").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("--fallback-model <modelId>", "Model to fall back to when primary model fails").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);
18162
18688
  }
18163
18689
  var taskCommand = applyTaskOptions(
18164
18690
  new Command10("task").description("Run a multi-session agent task")
@@ -19646,13 +20172,13 @@ apiKeysCommand.command("delete <id>").description("Delete an API key").option("-
19646
20172
  await waitUntilExit2();
19647
20173
  return;
19648
20174
  }
19649
- const confirmed = await new Promise((resolve7) => {
20175
+ const confirmed = await new Promise((resolve8) => {
19650
20176
  const { unmount } = render14(
19651
20177
  React14.createElement(ConfirmPrompt, {
19652
20178
  message: `Delete API key ${id}?`,
19653
20179
  defaultValue: false,
19654
20180
  onConfirm: (result) => {
19655
- resolve7(result);
20181
+ resolve8(result);
19656
20182
  unmount();
19657
20183
  }
19658
20184
  })
@@ -19757,8 +20283,8 @@ apiKeysCommand.command("analytics").description("Show API key usage analytics").
19757
20283
  const client = new ApiClient(apiKey);
19758
20284
  if (!isTTY(options) || options.json) {
19759
20285
  try {
19760
- const path13 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
19761
- const data = await client.get(path13);
20286
+ const path14 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
20287
+ const data = await client.get(path14);
19762
20288
  printJson(data);
19763
20289
  } catch (error) {
19764
20290
  const message = error instanceof Error ? error.message : "Unknown error";
@@ -19775,8 +20301,8 @@ apiKeysCommand.command("analytics").description("Show API key usage analytics").
19775
20301
  useEffect25(() => {
19776
20302
  const run = async () => {
19777
20303
  try {
19778
- const path13 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
19779
- const data = await client.get(path13);
20304
+ const path14 = options.key ? `/api-keys/${options.key}/analytics` : "/api-keys/analytics";
20305
+ const data = await client.get(path14);
19780
20306
  printJson(data);
19781
20307
  setSuccess(true);
19782
20308
  setLoading(false);