@runtypelabs/cli 2.1.0 → 2.2.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
@@ -8469,8 +8469,12 @@ function setStoredToolPayloadField(payloads, toolId, field, value) {
8469
8469
  const next = { ...payloads.get(toolId) ?? {} };
8470
8470
  if (value === void 0) {
8471
8471
  delete next[field];
8472
+ } else if (field === "parameters") {
8473
+ next.parameters = value;
8474
+ } else if (field === "result") {
8475
+ next.result = value;
8472
8476
  } else {
8473
- next[field] = value;
8477
+ next.streamedInput = value;
8474
8478
  }
8475
8479
  if (next.parameters === void 0 && next.result === void 0 && next.streamedInput === void 0) {
8476
8480
  payloads.delete(toolId);
@@ -14797,6 +14801,8 @@ var NETWORK_ERROR_PATTERNS = [
14797
14801
  "econnrefused",
14798
14802
  "econnaborted",
14799
14803
  "etimedout",
14804
+ "timeout",
14805
+ "request timeout",
14800
14806
  "enetunreach",
14801
14807
  "enetdown",
14802
14808
  "ehostunreach",
@@ -14816,12 +14822,78 @@ var NETWORK_ERROR_PATTERNS = [
14816
14822
  "unable to connect",
14817
14823
  "err_network"
14818
14824
  ];
14825
+ var LOCAL_NETWORK_PATTERNS = [
14826
+ "enetunreach",
14827
+ "enetdown",
14828
+ "enotfound",
14829
+ "network error",
14830
+ "network request failed",
14831
+ "networkerror",
14832
+ "err_network"
14833
+ ];
14834
+ var SERVER_UNREACHABLE_PATTERNS = [
14835
+ "econnrefused",
14836
+ "econnreset",
14837
+ "connection refused",
14838
+ "connection reset",
14839
+ "ehostunreach"
14840
+ ];
14841
+ function collectErrorSignals(error, seen = /* @__PURE__ */ new Set()) {
14842
+ if (error == null || seen.has(error)) return [];
14843
+ if (typeof error === "string") return [error];
14844
+ if (typeof error !== "object") return [String(error)];
14845
+ seen.add(error);
14846
+ const parts = [];
14847
+ if ("message" in error && typeof error.message === "string") {
14848
+ parts.push(error.message);
14849
+ }
14850
+ if ("code" in error && typeof error.code === "string") {
14851
+ parts.push(error.code);
14852
+ }
14853
+ if ("cause" in error) {
14854
+ parts.push(...collectErrorSignals(error.cause, seen));
14855
+ }
14856
+ return parts;
14857
+ }
14858
+ function getNetworkErrorContext(error) {
14859
+ const signals = collectErrorSignals(error);
14860
+ const fallbackMessage = error instanceof Error ? error.message : String(error);
14861
+ const uniqueSignals = [...new Set(signals.map((signal) => signal.trim()).filter(Boolean))];
14862
+ const searchText = uniqueSignals.join(" ").toLowerCase();
14863
+ const detailMessage = uniqueSignals.find((signal) => signal.toLowerCase() !== "fetch failed") ?? fallbackMessage;
14864
+ return {
14865
+ searchText,
14866
+ detailMessage
14867
+ };
14868
+ }
14869
+ function describeNetworkError(error) {
14870
+ const { searchText, detailMessage } = getNetworkErrorContext(error);
14871
+ const isLocalNetwork = LOCAL_NETWORK_PATTERNS.some((p) => searchText.includes(p));
14872
+ const isServerUnreachable = SERVER_UNREACHABLE_PATTERNS.some((p) => searchText.includes(p));
14873
+ const isTimeout = searchText.includes("etimedout") || searchText.includes("timeout");
14874
+ const lines = [];
14875
+ if (isLocalNetwork) {
14876
+ lines.push("Could not reach the Runtype API \u2014 your network appears to be offline.");
14877
+ lines.push("Check your internet connection and try again.");
14878
+ } else if (isServerUnreachable) {
14879
+ lines.push("Could not reach the Runtype API \u2014 the server is not responding.");
14880
+ lines.push("The service may be temporarily unavailable. Try again in a few minutes.");
14881
+ } else if (isTimeout) {
14882
+ lines.push("Could not reach the Runtype API \u2014 the request timed out.");
14883
+ lines.push("This could be a network issue or the server may be under heavy load.");
14884
+ } else {
14885
+ lines.push("Could not reach the Runtype API \u2014 a network error occurred.");
14886
+ lines.push("Check your internet connection or try again in a few minutes.");
14887
+ }
14888
+ lines.push(`Details: ${detailMessage}`);
14889
+ return lines;
14890
+ }
14819
14891
  function isTransientNetworkError(error) {
14820
14892
  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;
14893
+ const { searchText } = getNetworkErrorContext(error);
14894
+ if (error instanceof TypeError && searchText.includes("fetch")) return true;
14823
14895
  if (error instanceof DOMException && error.name === "AbortError") return true;
14824
- return NETWORK_ERROR_PATTERNS.some((pattern) => message.includes(pattern));
14896
+ return NETWORK_ERROR_PATTERNS.some((pattern) => searchText.includes(pattern));
14825
14897
  }
14826
14898
  async function retryOnNetworkError(fn, opts = {}) {
14827
14899
  const maxRetries = opts.maxRetries ?? 3;
@@ -14898,9 +14970,8 @@ function describeMarathonApiError(error) {
14898
14970
  if (!(error instanceof Error)) {
14899
14971
  return ["Task failed: Unknown error"];
14900
14972
  }
14901
- if (!(error instanceof RuntypeApiError) || error.statusCode !== 429) {
14902
- const message = error instanceof Error ? error.message : "Unknown error";
14903
- return [`Task failed: ${message}`];
14973
+ if (isTransientNetworkError(error)) {
14974
+ return describeNetworkError(error);
14904
14975
  }
14905
14976
  return [`Task failed: ${error.message}`];
14906
14977
  }
@@ -16134,6 +16205,57 @@ function createRunCheckTool() {
16134
16205
  }
16135
16206
  };
16136
16207
  }
16208
+ function createSearchSessionHistoryTool(client, taskName) {
16209
+ return {
16210
+ 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.",
16211
+ parametersSchema: {
16212
+ type: "object",
16213
+ properties: {
16214
+ query: {
16215
+ type: "string",
16216
+ description: 'What to search for (e.g. "authentication flow decisions", "test failures in auth module")'
16217
+ },
16218
+ limit: {
16219
+ type: "number",
16220
+ description: "Maximum number of results to return (default 5, max 20)"
16221
+ },
16222
+ types: {
16223
+ type: "array",
16224
+ items: { type: "string", enum: ["response", "reasoning", "tool_output"] },
16225
+ description: "Filter by content type (default: all types)"
16226
+ }
16227
+ },
16228
+ required: ["query"]
16229
+ },
16230
+ execute: async (args) => {
16231
+ const query = String(args.query || "").trim();
16232
+ if (!query) return "Error: query is required";
16233
+ const limit = Math.max(1, Math.min(20, Number(args.limit) || 5));
16234
+ const types = Array.isArray(args.types) ? args.types : void 0;
16235
+ try {
16236
+ const response = await client.post("/session-context/search", {
16237
+ query,
16238
+ taskName,
16239
+ limit,
16240
+ ...types ? { types } : {}
16241
+ });
16242
+ if (!response.success || !response.results || response.results.length === 0) {
16243
+ return "No matching session context found for your query.";
16244
+ }
16245
+ const formatted = response.results.map((r, i) => {
16246
+ const header = `[Result ${i + 1}] Session ${r.sessionIndex} | ${r.type}${r.toolName ? ` (${r.toolName})` : ""} | Score: ${r.score.toFixed(3)}`;
16247
+ return `${header}
16248
+ ${r.content}`;
16249
+ });
16250
+ return `Found ${response.count} matching results:
16251
+
16252
+ ${formatted.join("\n\n---\n\n")}`;
16253
+ } catch (error) {
16254
+ return `Session search unavailable: ${error instanceof Error ? error.message : String(error)}`;
16255
+ }
16256
+ }
16257
+ };
16258
+ }
16137
16259
  function buildLocalTools(client, sandboxProvider, options, context) {
16138
16260
  const enabledTools = {};
16139
16261
  if (!options.noLocalTools) {
@@ -16149,6 +16271,9 @@ function buildLocalTools(client, sandboxProvider, options, context) {
16149
16271
  context.stateDir
16150
16272
  );
16151
16273
  enabledTools.run_check = createRunCheckTool();
16274
+ if (options.sessionSearch === true) {
16275
+ enabledTools.search_session_history = createSearchSessionHistoryTool(client, context.taskName);
16276
+ }
16152
16277
  }
16153
16278
  }
16154
16279
  if (sandboxProvider) {
@@ -16160,6 +16285,62 @@ function buildLocalTools(client, sandboxProvider, options, context) {
16160
16285
  return Object.keys(enabledTools).length > 0 ? enabledTools : void 0;
16161
16286
  }
16162
16287
 
16288
+ // src/marathon/session-chunker.ts
16289
+ var DEFAULT_MAX_CHUNK_CHARS = 2e3;
16290
+ var MIN_CONTENT_LENGTH = 50;
16291
+ function extractSessionChunks(snapshot, maxChunkChars = DEFAULT_MAX_CHUNK_CHARS) {
16292
+ const chunks = [];
16293
+ if (snapshot.content && snapshot.content.length >= MIN_CONTENT_LENGTH) {
16294
+ chunks.push(...chunkText(snapshot.content, "response", maxChunkChars));
16295
+ }
16296
+ if (snapshot.reasoning && snapshot.reasoning.length >= MIN_CONTENT_LENGTH) {
16297
+ chunks.push(...chunkText(snapshot.reasoning, "reasoning", maxChunkChars));
16298
+ }
16299
+ for (const tool of snapshot.tools) {
16300
+ const result = typeof tool.result === "string" ? tool.result : JSON.stringify(tool.result ?? "");
16301
+ if (result.length >= MIN_CONTENT_LENGTH) {
16302
+ chunks.push(
16303
+ ...chunkText(result, "tool_output", maxChunkChars, tool.name)
16304
+ );
16305
+ }
16306
+ }
16307
+ return chunks;
16308
+ }
16309
+ function chunkText(text, type, maxChars, toolName) {
16310
+ if (text.length <= maxChars) {
16311
+ return [{ content: text, type, ...toolName ? { toolName } : {} }];
16312
+ }
16313
+ const chunks = [];
16314
+ const paragraphs = text.split(/\n\n+/);
16315
+ let current = "";
16316
+ for (const paragraph of paragraphs) {
16317
+ if (paragraph.length > maxChars) {
16318
+ if (current.length >= MIN_CONTENT_LENGTH) {
16319
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16320
+ current = "";
16321
+ }
16322
+ const sentences = paragraph.match(/[^.!?]+[.!?]+\s*|[^.!?]+$/g) || [paragraph];
16323
+ for (const sentence of sentences) {
16324
+ if (current.length + sentence.length > maxChars && current.length > 0) {
16325
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16326
+ current = "";
16327
+ }
16328
+ current += sentence;
16329
+ }
16330
+ continue;
16331
+ }
16332
+ if (current.length + paragraph.length + 2 > maxChars && current.length >= MIN_CONTENT_LENGTH) {
16333
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16334
+ current = "";
16335
+ }
16336
+ current += (current ? "\n\n" : "") + paragraph;
16337
+ }
16338
+ if (current.length >= MIN_CONTENT_LENGTH) {
16339
+ chunks.push({ content: current.trim(), type, ...toolName ? { toolName } : {} });
16340
+ }
16341
+ return chunks;
16342
+ }
16343
+
16163
16344
  // src/marathon/loop-detector.ts
16164
16345
  var DEFAULT_MAX_HISTORY = 30;
16165
16346
  var DEFAULT_MIN_PATTERN_LENGTH = 2;
@@ -17090,6 +17271,9 @@ async function taskAction(agent, options) {
17090
17271
  console.log(chalk16.green(`Created agent: ${agentId}`));
17091
17272
  }
17092
17273
  } catch (createErr) {
17274
+ if (isTransientNetworkError(createErr)) {
17275
+ await failBeforeMain(formatMarathonApiError(createErr));
17276
+ }
17093
17277
  const errMsg = createErr instanceof Error ? createErr.message : String(createErr);
17094
17278
  await failBeforeMain([
17095
17279
  chalk16.red(`Failed to create agent "${normalizedAgent}"`),
@@ -17098,6 +17282,9 @@ async function taskAction(agent, options) {
17098
17282
  }
17099
17283
  }
17100
17284
  } catch (error) {
17285
+ if (isTransientNetworkError(error)) {
17286
+ await failBeforeMain(formatMarathonApiError(error));
17287
+ }
17101
17288
  const errMsg = error instanceof Error ? error.message : String(error);
17102
17289
  await failBeforeMain([
17103
17290
  chalk16.red("Failed to list agents"),
@@ -17752,6 +17939,21 @@ Saving state... done. Session saved to ${filePath}`);
17752
17939
  resumeState = extractRunTaskResumeState(adjustedState);
17753
17940
  lastSessionMessages = state.messages ?? [];
17754
17941
  saveState(filePath, adjustedState, { stripSnapshotEvents: !!eventLogWriter });
17942
+ if (options.sessionSearch === true) {
17943
+ const latestSnapshot = persistedSessionSnapshots[persistedSessionSnapshots.length - 1];
17944
+ if (latestSnapshot) {
17945
+ const chunks = extractSessionChunks(latestSnapshot);
17946
+ if (chunks.length > 0) {
17947
+ const sessionIdx = currentSessionOffset + state.sessionCount - 1;
17948
+ client.post("/session-context/index", {
17949
+ taskName,
17950
+ sessionIndex: sessionIdx,
17951
+ chunks
17952
+ }).catch(() => {
17953
+ });
17954
+ }
17955
+ }
17956
+ }
17755
17957
  if (resumeState?.workflowPhase) {
17756
17958
  const displayMilestone = detectedVariant === "external" && resumeState.workflowPhase === "research" && adjustedState.planWritten ? "report" : resumeState.workflowPhase;
17757
17959
  streamRef.current?.updateMilestone(displayMilestone);
@@ -18127,7 +18329,7 @@ ${details}`);
18127
18329
  }
18128
18330
  return resolved;
18129
18331
  }
18130
- function detectDeployWorkflow(_message, sandboxProvider, resumeState) {
18332
+ function detectDeployWorkflow(_message, _sandboxProvider, resumeState) {
18131
18333
  if (resumeState?.workflowVariant === "game") return gameWorkflow;
18132
18334
  if (resumeState?.workflowPhase === "design" || resumeState?.workflowPhase === "build" || resumeState?.workflowPhase === "verify") {
18133
18335
  return gameWorkflow;
@@ -18158,7 +18360,7 @@ function resolveSandboxWorkflowSelection(message, sandboxProvider, resumeState)
18158
18360
  };
18159
18361
  }
18160
18362
  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);
18363
+ 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
18364
  }
18163
18365
  var taskCommand = applyTaskOptions(
18164
18366
  new Command10("task").description("Run a multi-session agent task")