@runtypelabs/sdk 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
  "",
@@ -6112,6 +6239,75 @@ Do NOT redo any of the above work.`
6112
6239
  (state.lastOutput || "").slice(0, 1800) || "- No final output recorded yet."
6113
6240
  ].join("\n");
6114
6241
  }
6242
+ isAssistantToolCallMessage(message) {
6243
+ return Boolean(message?.role === "assistant" && message.toolCalls && message.toolCalls.length > 0);
6244
+ }
6245
+ isToolResultMessage(message) {
6246
+ return Boolean(message?.role === "tool" && message.toolResults && message.toolResults.length > 0);
6247
+ }
6248
+ /**
6249
+ * Replay only complete adjacent tool-call/result pairs so provider validation
6250
+ * never sees an orphaned tool result after history trimming or resume.
6251
+ */
6252
+ sanitizeReplayHistoryMessages(messages) {
6253
+ const sanitized = [];
6254
+ for (let index = 0; index < messages.length; index++) {
6255
+ const message = messages[index];
6256
+ if (this.isAssistantToolCallMessage(message)) {
6257
+ const nextMessage = messages[index + 1];
6258
+ if (!this.isToolResultMessage(nextMessage)) {
6259
+ continue;
6260
+ }
6261
+ const matchedResultIds = new Set(
6262
+ nextMessage.toolResults.filter(
6263
+ (toolResult) => message.toolCalls.some((toolCall) => toolCall.toolCallId === toolResult.toolCallId)
6264
+ ).map((toolResult) => toolResult.toolCallId)
6265
+ );
6266
+ if (matchedResultIds.size === 0) {
6267
+ continue;
6268
+ }
6269
+ const matchedToolCalls = message.toolCalls.filter(
6270
+ (toolCall) => matchedResultIds.has(toolCall.toolCallId)
6271
+ );
6272
+ const matchedToolResults = nextMessage.toolResults.filter(
6273
+ (toolResult) => matchedResultIds.has(toolResult.toolCallId)
6274
+ );
6275
+ sanitized.push(
6276
+ matchedToolCalls.length === message.toolCalls.length ? message : { ...message, toolCalls: matchedToolCalls }
6277
+ );
6278
+ sanitized.push(
6279
+ matchedToolResults.length === nextMessage.toolResults.length ? nextMessage : { ...nextMessage, toolResults: matchedToolResults }
6280
+ );
6281
+ index += 1;
6282
+ continue;
6283
+ }
6284
+ if (this.isToolResultMessage(message)) {
6285
+ continue;
6286
+ }
6287
+ sanitized.push(message);
6288
+ }
6289
+ return sanitized;
6290
+ }
6291
+ /**
6292
+ * Keep replay trimming on a pair boundary. If the trim cut would start on a
6293
+ * tool-result message, slide back to include the matching assistant tool call.
6294
+ */
6295
+ trimReplayHistoryMessages(messages, maxMessages) {
6296
+ if (messages.length <= maxMessages) {
6297
+ return {
6298
+ historyMessages: messages,
6299
+ trimmedCount: 0
6300
+ };
6301
+ }
6302
+ let startIndex = messages.length - maxMessages;
6303
+ while (startIndex > 0 && messages[startIndex]?.role === "tool") {
6304
+ startIndex -= 1;
6305
+ }
6306
+ return {
6307
+ historyMessages: messages.slice(startIndex),
6308
+ trimmedCount: startIndex
6309
+ };
6310
+ }
6115
6311
  /**
6116
6312
  * Build messages for a session, injecting progress context for continuation sessions.
6117
6313
  * Optionally accepts continuation context for marathon resume scenarios.
@@ -6182,29 +6378,41 @@ Do NOT redo any of the above work.`
6182
6378
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
6183
6379
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
6184
6380
  const isDeployWorkflow = wf.name === "deploy";
6381
+ const isExternalTask2 = state.workflowVariant === "external";
6382
+ const requiredArtifactPath = state.planPath;
6185
6383
  const localToolsBlock = localToolNames?.length ? [
6186
6384
  "",
6187
6385
  "--- Local Tools ---",
6188
6386
  `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."] : [
6387
+ ...isDeployWorkflow ? ["Use deploy_sandbox to deploy code and get a live preview URL."] : isExternalTask2 ? [
6388
+ "This is a web/external research task. Do not inspect or edit the local repository unless the user explicitly asked for workspace changes.",
6389
+ ...requiredArtifactPath ? [`Write the final markdown findings to exactly: ${requiredArtifactPath}`] : []
6390
+ ] : [
6190
6391
  "Use these tools to inspect the existing repository and make real file edits \u2014 not just code in your response."
6191
6392
  ],
6192
6393
  ...toolGuidanceLines,
6193
- ...isDeployWorkflow ? [] : ["Always use write_file to save your output so the user can run it immediately."]
6394
+ ...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
6395
  ].join("\n") : "";
6195
6396
  const builtinToolNames = builtinToolIds?.map((id) => id.replace(/^builtin:/, ""));
6196
6397
  const builtinToolsBlock = builtinToolNames?.length ? [
6197
6398
  "",
6198
6399
  "--- Built-in Tools ---",
6199
6400
  `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."
6401
+ 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
6402
  ].join("\n") : "";
6202
6403
  const toolsBlock = localToolsBlock + builtinToolsBlock;
6203
- const bootstrapBlock = state.bootstrapContext ? ["", "--- Initial Repository Discovery ---", state.bootstrapContext].join("\n") : "";
6404
+ const bootstrapBlock = state.bootstrapContext ? [
6405
+ "",
6406
+ isExternalTask2 ? "--- Initial Research Context ---" : "--- Initial Repository Discovery ---",
6407
+ state.bootstrapContext
6408
+ ].join("\n") : "";
6204
6409
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
6205
6410
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
6206
6411
  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.`;
6207
6412
  if (continuationContext && sessionIndex === 0) {
6413
+ const replayHistoryMessages = this.sanitizeReplayHistoryMessages(
6414
+ continuationContext.previousMessages
6415
+ );
6208
6416
  const defaultContinueMessage = "Continue the task. Review your prior work above and proceed with any remaining work. If everything is already complete, respond with TASK_COMPLETE.";
6209
6417
  const userMessage = continuationContext.newUserMessage || defaultContinueMessage;
6210
6418
  const userContent = [
@@ -6217,7 +6425,7 @@ Do NOT redo any of the above work.`
6217
6425
  multiSessionInstruction
6218
6426
  ].join("\n");
6219
6427
  const fullHistoryMessages = [
6220
- ...continuationContext.previousMessages,
6428
+ ...replayHistoryMessages,
6221
6429
  {
6222
6430
  role: "system",
6223
6431
  content: "IMPORTANT: You are continuing a previously completed task. The conversation above shows your prior work. Do NOT redo any of it. Build on what was already accomplished. If there is nothing new to do, respond with TASK_COMPLETE."
@@ -6229,7 +6437,7 @@ Do NOT redo any of the above work.`
6229
6437
  ];
6230
6438
  const summaryText = this.generateCompactSummary(state, compactInstructions);
6231
6439
  const breakdown = this.buildContextBudgetBreakdown({
6232
- historyMessages: continuationContext.previousMessages,
6440
+ historyMessages: replayHistoryMessages,
6233
6441
  currentTurnContent: userContent,
6234
6442
  localTools: compactionOptions?.localTools,
6235
6443
  builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
@@ -6319,16 +6527,19 @@ Do NOT redo any of the above work.`
6319
6527
  "Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
6320
6528
  ].join("\n");
6321
6529
  const MAX_HISTORY_MESSAGES = 60;
6322
- let historyMessages = state.messages;
6530
+ let historyMessages = this.sanitizeReplayHistoryMessages(state.messages);
6323
6531
  if (historyMessages.length > MAX_HISTORY_MESSAGES) {
6324
- const trimmedCount = historyMessages.length - MAX_HISTORY_MESSAGES;
6325
- historyMessages = [
6326
- {
6327
- role: "system",
6328
- content: `[${trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`
6329
- },
6330
- ...historyMessages.slice(-MAX_HISTORY_MESSAGES)
6331
- ];
6532
+ const trimmedHistory = this.trimReplayHistoryMessages(historyMessages, MAX_HISTORY_MESSAGES);
6533
+ historyMessages = trimmedHistory.historyMessages;
6534
+ if (trimmedHistory.trimmedCount > 0) {
6535
+ historyMessages = [
6536
+ {
6537
+ role: "system",
6538
+ content: `[${trimmedHistory.trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`
6539
+ },
6540
+ ...historyMessages
6541
+ ];
6542
+ }
6332
6543
  }
6333
6544
  const summaryText = this.generateCompactSummary(state, compactInstructions);
6334
6545
  const breakdown = this.buildContextBudgetBreakdown({
@@ -6590,6 +6801,42 @@ var FlowBuilder = class {
6590
6801
  );
6591
6802
  return this;
6592
6803
  }
6804
+ /**
6805
+ * Add a crawl step
6806
+ */
6807
+ crawl(config) {
6808
+ this.addStep(
6809
+ "crawl",
6810
+ config.name,
6811
+ {
6812
+ url: config.url,
6813
+ limit: config.limit,
6814
+ depth: config.depth,
6815
+ source: config.source,
6816
+ formats: config.formats,
6817
+ render: config.render,
6818
+ maxAge: config.maxAge,
6819
+ modifiedSince: config.modifiedSince,
6820
+ options: config.options,
6821
+ authenticate: config.authenticate,
6822
+ cookies: config.cookies,
6823
+ setExtraHTTPHeaders: config.setExtraHTTPHeaders,
6824
+ gotoOptions: config.gotoOptions,
6825
+ waitForSelector: config.waitForSelector,
6826
+ rejectResourceTypes: config.rejectResourceTypes,
6827
+ rejectRequestPattern: config.rejectRequestPattern,
6828
+ userAgent: config.userAgent,
6829
+ jsonOptions: config.jsonOptions,
6830
+ outputVariable: config.outputVariable,
6831
+ errorHandling: config.errorHandling,
6832
+ streamOutput: config.streamOutput,
6833
+ pollIntervalMs: config.pollIntervalMs,
6834
+ completionTimeoutMs: config.completionTimeoutMs
6835
+ },
6836
+ config.enabled
6837
+ );
6838
+ return this;
6839
+ }
6593
6840
  /**
6594
6841
  * Add a fetch URL step
6595
6842
  */
@@ -6932,7 +7179,7 @@ var ClientFlowBuilder = class extends FlowBuilder {
6932
7179
  this.boundClient = client;
6933
7180
  this.createFlow({ name });
6934
7181
  }
6935
- async run(arg1, arg2, arg3, arg4) {
7182
+ async run(arg1, arg2, arg3, _arg4) {
6936
7183
  const config = this.build();
6937
7184
  let runOptions;
6938
7185
  let runCallbacks;