@runtypelabs/sdk 1.8.1 → 1.8.2

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.cjs CHANGED
@@ -2293,12 +2293,22 @@ function getDefaultPlanPath(taskName) {
2293
2293
  const slug = sanitizeTaskSlug(taskName || "task");
2294
2294
  return `.runtype/marathons/${slug}/plan.md`;
2295
2295
  }
2296
+ function getDefaultExternalReportPath(taskName) {
2297
+ const slug = sanitizeTaskSlug(taskName || "task");
2298
+ return `${slug}.md`;
2299
+ }
2296
2300
  function sanitizeTaskSlug(taskName) {
2297
2301
  return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
2298
2302
  }
2299
2303
 
2300
2304
  // src/workflows/default-workflow.ts
2305
+ function isExternalTask(state) {
2306
+ return state.workflowVariant === "external";
2307
+ }
2301
2308
  function hasSufficientResearchEvidence(state) {
2309
+ if (isExternalTask(state)) {
2310
+ return false;
2311
+ }
2302
2312
  if (state.isCreationTask) {
2303
2313
  return (state.recentReadPaths?.length || 0) >= 1;
2304
2314
  }
@@ -2442,6 +2452,17 @@ var researchPhase = {
2442
2452
  description: "Inspect the repo and identify the correct target file",
2443
2453
  buildInstructions(state) {
2444
2454
  const planPath = state.planPath || getDefaultPlanPath(state.taskName);
2455
+ const reportPath = state.planPath || getDefaultExternalReportPath(state.taskName);
2456
+ if (isExternalTask(state)) {
2457
+ return [
2458
+ "--- Workflow Phase: Research ---",
2459
+ "This is an external web research task, not a repository editing task.",
2460
+ "Research the requested website or external source directly using built-in web tools first.",
2461
+ "Do NOT inspect the local repo or search for a target file unless the user explicitly asks for local workspace changes.",
2462
+ `Write the final markdown report to exactly: ${reportPath}`,
2463
+ "Do NOT end with TASK_COMPLETE until that markdown file has been written."
2464
+ ].join("\n");
2465
+ }
2445
2466
  if (state.isCreationTask) {
2446
2467
  return [
2447
2468
  "--- Workflow Phase: Research ---",
@@ -2461,6 +2482,15 @@ var researchPhase = {
2461
2482
  ].join("\n");
2462
2483
  },
2463
2484
  buildToolGuidance(state) {
2485
+ if (isExternalTask(state)) {
2486
+ const reportPath = state.planPath || getDefaultExternalReportPath(state.taskName);
2487
+ return [
2488
+ "This is a web/external research task.",
2489
+ "Start with built-in web tools such as firecrawl or web search, not local repo discovery tools.",
2490
+ "Do not call search_repo, glob_files, tree_directory, list_directory, or read_file unless the user explicitly asked you to inspect local files.",
2491
+ `Use write_file to save the final markdown report to exactly: ${reportPath}.`
2492
+ ];
2493
+ }
2464
2494
  if (state.isCreationTask) {
2465
2495
  return [
2466
2496
  "This is a creation task \u2014 you are building new files, not editing existing ones.",
@@ -2494,10 +2524,45 @@ var researchPhase = {
2494
2524
  `Next step: write the plan markdown to ${state.planPath} before editing the product file.`
2495
2525
  ].join("\n");
2496
2526
  },
2497
- interceptToolCall() {
2527
+ interceptToolCall(toolName, _args, ctx) {
2528
+ if (!isExternalTask(ctx.state)) {
2529
+ return void 0;
2530
+ }
2531
+ const normalizedPathArg = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
2532
+ const normalizedReportPath = ctx.normalizePath(
2533
+ ctx.state.planPath || getDefaultExternalReportPath(ctx.state.taskName)
2534
+ );
2535
+ if (ctx.isDiscoveryTool(toolName) || toolName === "read_file" || toolName === "restore_file_checkpoint") {
2536
+ return [
2537
+ `Blocked by marathon external research guard: ${toolName} is disabled for web research tasks.`,
2538
+ "Research the requested external source with built-in web tools instead.",
2539
+ "Use local file tools only if the user explicitly asked for workspace inspection or if you are saving the final deliverable."
2540
+ ].join(" ");
2541
+ }
2542
+ if (toolName === "write_file" && normalizedPathArg && normalizedPathArg !== normalizedReportPath) {
2543
+ return [
2544
+ `Blocked by marathon external research guard: write_file must target "${normalizedReportPath}".`,
2545
+ "Save the final markdown report to the required workspace path before TASK_COMPLETE."
2546
+ ].join(" ");
2547
+ }
2498
2548
  return void 0;
2499
2549
  },
2500
2550
  buildRecoveryMessage(state) {
2551
+ if (isExternalTask(state)) {
2552
+ const recent2 = state.sessions.slice(-2);
2553
+ const reportPath = state.planPath || getDefaultExternalReportPath(state.taskName);
2554
+ if (recent2.length < 2 || !recent2.every((session) => session.wroteFiles === false)) {
2555
+ return void 0;
2556
+ }
2557
+ const repeatedSameActions2 = hasRepeatedSameActions(state.sessions);
2558
+ return [
2559
+ "Recovery instruction:",
2560
+ "This web research task is missing its required markdown artifact.",
2561
+ `Your next action must be write_file to "${reportPath}" with the final findings in markdown.`,
2562
+ "Do not end with TASK_COMPLETE until that file has been written.",
2563
+ ...repeatedSameActions2 ? ["You are repeating the same actions; break the loop by writing the final report now."] : []
2564
+ ].join("\n");
2565
+ }
2501
2566
  const recent = state.sessions.slice(-2);
2502
2567
  const normalizedPlanPath = typeof state.planPath === "string" && state.planPath.trim() ? normalizeCandidatePath(state.planPath) : void 0;
2503
2568
  const recentPlanOnlyLoop = Boolean(normalizedPlanPath) && recent.length === 2 && recent.every((session) => {
@@ -2541,6 +2606,12 @@ var researchPhase = {
2541
2606
  ].join("\n");
2542
2607
  },
2543
2608
  shouldForceEndTurn(snapshot, ctx) {
2609
+ if (isExternalTask(ctx.state)) {
2610
+ if (ctx.isDiscoveryTool(snapshot.toolName) && snapshot.actionKeyCount >= 2) {
2611
+ return "this web research task is looping on local repo discovery instead of using external tools";
2612
+ }
2613
+ return void 0;
2614
+ }
2544
2615
  const state = ctx.state;
2545
2616
  const sufficientResearch = hasSufficientResearchEvidence(state);
2546
2617
  if (sufficientResearch && snapshot.discoveryPauseCount >= 12) {
@@ -2553,6 +2624,12 @@ var researchPhase = {
2553
2624
  return "this session exceeded the discovery-tool budget without ending the turn";
2554
2625
  }
2555
2626
  return void 0;
2627
+ },
2628
+ canAcceptCompletion(state, trace) {
2629
+ if (!isExternalTask(state)) {
2630
+ return true;
2631
+ }
2632
+ return Boolean(state.planWritten || trace.planWritten);
2556
2633
  }
2557
2634
  };
2558
2635
  var planningPhase = {
@@ -2779,6 +2856,65 @@ var executionPhase = {
2779
2856
  };
2780
2857
  function classifyVariant(message) {
2781
2858
  const lower = message.toLowerCase();
2859
+ const externalVerbs = [
2860
+ "fetch",
2861
+ "browse",
2862
+ "scrape",
2863
+ "crawl",
2864
+ "download",
2865
+ "visit",
2866
+ "navigate to",
2867
+ "go to",
2868
+ "open the",
2869
+ "look up",
2870
+ "search for",
2871
+ "find out",
2872
+ "check out",
2873
+ "pull up"
2874
+ ];
2875
+ const externalTargets = [
2876
+ "http://",
2877
+ "https://",
2878
+ "www.",
2879
+ ".com",
2880
+ ".org",
2881
+ ".io",
2882
+ ".net",
2883
+ ".dev",
2884
+ "website",
2885
+ "webpage",
2886
+ "web page",
2887
+ "home page",
2888
+ "homepage",
2889
+ "hacker news",
2890
+ "reddit",
2891
+ "twitter",
2892
+ "github.com",
2893
+ "firecrawl",
2894
+ "exa_search"
2895
+ ];
2896
+ const hasExternalVerb = externalVerbs.some((v) => lower.includes(v));
2897
+ const hasExternalTarget = externalTargets.some((t) => lower.includes(t));
2898
+ const modificationVerbs = [
2899
+ "fix",
2900
+ "update",
2901
+ "change",
2902
+ "modify",
2903
+ "edit",
2904
+ "refactor",
2905
+ "improve",
2906
+ "add to",
2907
+ "remove",
2908
+ "delete",
2909
+ "rename",
2910
+ "migrate"
2911
+ ];
2912
+ const hasModificationVerb = modificationVerbs.some(
2913
+ (v) => lower.startsWith(v) || new RegExp(`\\b${v}\\b`).test(lower)
2914
+ );
2915
+ if (hasExternalVerb && hasExternalTarget && !hasModificationVerb) {
2916
+ return "external";
2917
+ }
2782
2918
  const creationPatterns = [
2783
2919
  /^create\b/,
2784
2920
  /^build\b/,
@@ -2799,30 +2935,13 @@ function classifyVariant(message) {
2799
2935
  /^new\b/
2800
2936
  ];
2801
2937
  const hasCreationStart = creationPatterns.some((p) => p.test(lower));
2802
- const modificationVerbs = [
2803
- "fix",
2804
- "update",
2805
- "change",
2806
- "modify",
2807
- "edit",
2808
- "refactor",
2809
- "improve",
2810
- "add to",
2811
- "remove",
2812
- "delete",
2813
- "rename",
2814
- "migrate"
2815
- ];
2816
- const hasModificationVerb = modificationVerbs.some(
2817
- (v) => lower.startsWith(v) || new RegExp(`\\b${v}\\b`).test(lower)
2818
- );
2819
2938
  if (hasCreationStart && !hasModificationVerb) {
2820
2939
  return "create";
2821
2940
  }
2822
2941
  return "modify";
2823
2942
  }
2824
2943
  async function generateBootstrapContext(message, localTools, variant) {
2825
- if (variant === "create") return void 0;
2944
+ if (variant === "create" || variant === "external") return void 0;
2826
2945
  if (!localTools) return void 0;
2827
2946
  const searchTool = localTools.search_repo;
2828
2947
  const globTool = localTools.glob_files;
@@ -2858,7 +2977,7 @@ async function generateBootstrapContext(message, localTools, variant) {
2858
2977
  return ["Bootstrap repo hints:", ...lines].join("\n").slice(0, 1500);
2859
2978
  }
2860
2979
  function buildCandidateBlock(state) {
2861
- if (!state.bestCandidatePath || state.isCreationTask) return "";
2980
+ if (!state.bestCandidatePath || state.isCreationTask || isExternalTask(state)) return "";
2862
2981
  return [
2863
2982
  "",
2864
2983
  "--- Best Candidate ---",
@@ -4945,8 +5064,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4945
5064
  return `[${toolName} output (${resultStr.length} chars) saved to ${filePath} \u2014 use read_file to retrieve if needed]`;
4946
5065
  }
4947
5066
  getDefaultPlanPath(taskName) {
4948
- const slug = this.sanitizeTaskSlug(taskName || "task");
4949
- return `.runtype/marathons/${slug}/plan.md`;
5067
+ return getDefaultPlanPath(taskName);
5068
+ }
5069
+ getDefaultExternalReportPath(taskName) {
5070
+ return getDefaultExternalReportPath(taskName);
4950
5071
  }
4951
5072
  dirnameOfCandidatePath(candidatePath) {
4952
5073
  const normalized = this.normalizeCandidatePath(candidatePath);
@@ -5052,7 +5173,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5052
5173
  }
5053
5174
  sanitizeResumeState(resumeState, taskName) {
5054
5175
  if (!resumeState) return void 0;
5055
- const planPath = typeof resumeState.planPath === "string" && resumeState.planPath.trim() ? this.normalizeCandidatePath(resumeState.planPath) : this.getDefaultPlanPath(taskName);
5176
+ const workflowVariant = resumeState.workflowVariant;
5177
+ const defaultPlanPath = workflowVariant === "external" ? this.getDefaultExternalReportPath(taskName) : this.getDefaultPlanPath(taskName);
5178
+ const planPath = typeof resumeState.planPath === "string" && resumeState.planPath.trim() ? this.normalizeCandidatePath(resumeState.planPath) : defaultPlanPath;
5179
+ const migratedPlanPath = workflowVariant === "external" && planPath === this.getDefaultPlanPath(taskName) ? this.getDefaultExternalReportPath(taskName) : planPath;
5056
5180
  const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
5057
5181
  const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
5058
5182
  const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === "string" && resumeState.bestCandidatePath.trim() ? this.normalizeCandidatePath(resumeState.bestCandidatePath) : void 0;
@@ -5063,7 +5187,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5063
5187
  return {
5064
5188
  ...resumeState,
5065
5189
  workflowPhase,
5066
- planPath,
5190
+ planPath: migratedPlanPath,
5067
5191
  planWritten: Boolean(resumeState.planWritten),
5068
5192
  bestCandidatePath,
5069
5193
  bestCandidateReason: bestCandidatePath ? resumeState.bestCandidateReason : void 0,
@@ -5489,13 +5613,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5489
5613
  const taskName = typeof options.trackProgress === "string" ? options.trackProgress : options.trackProgress ? `${agent.name} task` : "";
5490
5614
  const resolvedTaskName = taskName || `${agent.name} task`;
5491
5615
  const seededResumeState = this.sanitizeResumeState(options.resumeState, resolvedTaskName);
5616
+ const classifiedVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5492
5617
  const state = {
5493
5618
  agentId: id,
5494
5619
  agentName: agent.name,
5495
5620
  taskName: resolvedTaskName,
5496
5621
  status: "running",
5497
5622
  workflowPhase: seededResumeState?.workflowPhase || workflow.phases[0]?.name || "research",
5498
- planPath: seededResumeState?.planPath || this.getDefaultPlanPath(resolvedTaskName),
5623
+ planPath: seededResumeState?.planPath || (classifiedVariant === "external" ? this.getDefaultExternalReportPath(resolvedTaskName) : this.getDefaultPlanPath(resolvedTaskName)),
5499
5624
  planWritten: seededResumeState?.planWritten || false,
5500
5625
  bestCandidateNeedsVerification: seededResumeState?.bestCandidateNeedsVerification || false,
5501
5626
  bestCandidateVerified: seededResumeState?.bestCandidateVerified || false,
@@ -5518,7 +5643,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5518
5643
  ...seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {},
5519
5644
  ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {}
5520
5645
  };
5521
- state.workflowVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5646
+ state.workflowVariant = classifiedVariant;
5522
5647
  state.isCreationTask = seededResumeState?.isCreationTask ?? state.workflowVariant === "create";
5523
5648
  this.updateWorkflowPhase(state, this.createEmptyToolTrace(), workflow);
5524
5649
  let recordId;
@@ -5536,7 +5661,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5536
5661
  state.bootstrapContext = await this.generateBootstrapDiscoveryContext(
5537
5662
  options.message,
5538
5663
  options.localTools,
5539
- state.isCreationTask
5664
+ state.isCreationTask || state.workflowVariant === "external"
5540
5665
  );
5541
5666
  }
5542
5667
  const bootstrapCandidate = this.extractBestCandidateFromBootstrapContext(state.bootstrapContext);
@@ -5762,13 +5887,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5762
5887
  if (state.sessions.length > 50) {
5763
5888
  state.sessions = state.sessions.slice(-50);
5764
5889
  }
5765
- if (sessionResult.stopReason === "complete") {
5890
+ const detectedTaskCompletion = this.detectTaskCompletion(sessionResult.result);
5891
+ const acceptedTaskCompletion = detectedTaskCompletion && this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace, workflow);
5892
+ if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
5766
5893
  state.status = "complete";
5767
5894
  } else if (sessionResult.stopReason === "error") {
5768
5895
  state.status = "error";
5769
5896
  } else if (sessionResult.stopReason === "max_cost") {
5770
5897
  state.status = "budget_exceeded";
5771
- } else if (this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace, workflow)) {
5898
+ } else if (acceptedTaskCompletion) {
5772
5899
  state.status = "complete";
5773
5900
  } else if (maxCost && state.totalCost >= maxCost) {
5774
5901
  state.status = "budget_exceeded";
@@ -6091,7 +6218,7 @@ Do NOT redo any of the above work.`
6091
6218
  "",
6092
6219
  "Changed Files / Candidate Paths",
6093
6220
  ...candidatePaths.length > 0 ? candidatePaths.map((candidatePath) => `- ${candidatePath}`) : ["- No candidate paths recorded yet."],
6094
- ...state.planPath ? [`- Plan path: ${state.planPath}`] : [],
6221
+ ...state.planPath ? [`- ${state.workflowVariant === "external" ? "Report path" : "Plan path"}: ${state.planPath}`] : [],
6095
6222
  ...state.planWritten ? ["- Planning artifact has been written."] : [],
6096
6223
  ...state.bestCandidateReason ? [`- Best candidate rationale: ${state.bestCandidateReason}`] : [],
6097
6224
  "",
@@ -6182,25 +6309,34 @@ Do NOT redo any of the above work.`
6182
6309
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
6183
6310
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
6184
6311
  const isDeployWorkflow = wf.name === "deploy";
6312
+ const isExternalTask2 = state.workflowVariant === "external";
6313
+ const requiredArtifactPath = state.planPath;
6185
6314
  const localToolsBlock = localToolNames?.length ? [
6186
6315
  "",
6187
6316
  "--- Local Tools ---",
6188
6317
  `You have access to tools (${localToolNames.join(", ")}) that execute directly on the user's machine.`,
6189
- ...isDeployWorkflow ? ["Use deploy_sandbox to deploy code and get a live preview URL."] : [
6318
+ ...isDeployWorkflow ? ["Use deploy_sandbox to deploy code and get a live preview URL."] : isExternalTask2 ? [
6319
+ "This is a web/external research task. Do not inspect or edit the local repository unless the user explicitly asked for workspace changes.",
6320
+ ...requiredArtifactPath ? [`Write the final markdown findings to exactly: ${requiredArtifactPath}`] : []
6321
+ ] : [
6190
6322
  "Use these tools to inspect the existing repository and make real file edits \u2014 not just code in your response."
6191
6323
  ],
6192
6324
  ...toolGuidanceLines,
6193
- ...isDeployWorkflow ? [] : ["Always use write_file to save your output so the user can run it immediately."]
6325
+ ...isDeployWorkflow ? [] : isExternalTask2 ? ["Use write_file only if you want to save the final deliverable into the workspace."] : ["Always use write_file to save your output so the user can run it immediately."]
6194
6326
  ].join("\n") : "";
6195
6327
  const builtinToolNames = builtinToolIds?.map((id) => id.replace(/^builtin:/, ""));
6196
6328
  const builtinToolsBlock = builtinToolNames?.length ? [
6197
6329
  "",
6198
6330
  "--- Built-in Tools ---",
6199
6331
  `You have access to built-in tools (${builtinToolNames.join(", ")}) for web search, web scraping, image generation, and other capabilities.`,
6200
- "Use these tools when the task requires information from the web, generating images, or other capabilities beyond local file operations."
6332
+ isExternalTask2 ? "This task targets an external site or source. Start with these built-in tools before considering any local file tools." : "Use these tools when the task requires information from the web, generating images, or other capabilities beyond local file operations."
6201
6333
  ].join("\n") : "";
6202
6334
  const toolsBlock = localToolsBlock + builtinToolsBlock;
6203
- const bootstrapBlock = state.bootstrapContext ? ["", "--- Initial Repository Discovery ---", state.bootstrapContext].join("\n") : "";
6335
+ const bootstrapBlock = state.bootstrapContext ? [
6336
+ "",
6337
+ isExternalTask2 ? "--- Initial Research Context ---" : "--- Initial Repository Discovery ---",
6338
+ state.bootstrapContext
6339
+ ].join("\n") : "";
6204
6340
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
6205
6341
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
6206
6342
  const multiSessionInstruction = `This is a multi-session task (session ${sessionIndex + 1}/${maxSessions}). When you have fully completed the task, end your response with TASK_COMPLETE on its own line.`;