@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.js CHANGED
@@ -2223,12 +2223,22 @@ function getDefaultPlanPath(taskName) {
2223
2223
  const slug = sanitizeTaskSlug(taskName || "task");
2224
2224
  return `.runtype/marathons/${slug}/plan.md`;
2225
2225
  }
2226
+ function getDefaultExternalReportPath(taskName) {
2227
+ const slug = sanitizeTaskSlug(taskName || "task");
2228
+ return `${slug}.md`;
2229
+ }
2226
2230
  function sanitizeTaskSlug(taskName) {
2227
2231
  return taskName.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
2228
2232
  }
2229
2233
 
2230
2234
  // src/workflows/default-workflow.ts
2235
+ function isExternalTask(state) {
2236
+ return state.workflowVariant === "external";
2237
+ }
2231
2238
  function hasSufficientResearchEvidence(state) {
2239
+ if (isExternalTask(state)) {
2240
+ return false;
2241
+ }
2232
2242
  if (state.isCreationTask) {
2233
2243
  return (state.recentReadPaths?.length || 0) >= 1;
2234
2244
  }
@@ -2372,6 +2382,17 @@ var researchPhase = {
2372
2382
  description: "Inspect the repo and identify the correct target file",
2373
2383
  buildInstructions(state) {
2374
2384
  const planPath = state.planPath || getDefaultPlanPath(state.taskName);
2385
+ const reportPath = state.planPath || getDefaultExternalReportPath(state.taskName);
2386
+ if (isExternalTask(state)) {
2387
+ return [
2388
+ "--- Workflow Phase: Research ---",
2389
+ "This is an external web research task, not a repository editing task.",
2390
+ "Research the requested website or external source directly using built-in web tools first.",
2391
+ "Do NOT inspect the local repo or search for a target file unless the user explicitly asks for local workspace changes.",
2392
+ `Write the final markdown report to exactly: ${reportPath}`,
2393
+ "Do NOT end with TASK_COMPLETE until that markdown file has been written."
2394
+ ].join("\n");
2395
+ }
2375
2396
  if (state.isCreationTask) {
2376
2397
  return [
2377
2398
  "--- Workflow Phase: Research ---",
@@ -2391,6 +2412,15 @@ var researchPhase = {
2391
2412
  ].join("\n");
2392
2413
  },
2393
2414
  buildToolGuidance(state) {
2415
+ if (isExternalTask(state)) {
2416
+ const reportPath = state.planPath || getDefaultExternalReportPath(state.taskName);
2417
+ return [
2418
+ "This is a web/external research task.",
2419
+ "Start with built-in web tools such as firecrawl or web search, not local repo discovery tools.",
2420
+ "Do not call search_repo, glob_files, tree_directory, list_directory, or read_file unless the user explicitly asked you to inspect local files.",
2421
+ `Use write_file to save the final markdown report to exactly: ${reportPath}.`
2422
+ ];
2423
+ }
2394
2424
  if (state.isCreationTask) {
2395
2425
  return [
2396
2426
  "This is a creation task \u2014 you are building new files, not editing existing ones.",
@@ -2424,10 +2454,45 @@ var researchPhase = {
2424
2454
  `Next step: write the plan markdown to ${state.planPath} before editing the product file.`
2425
2455
  ].join("\n");
2426
2456
  },
2427
- interceptToolCall() {
2457
+ interceptToolCall(toolName, _args, ctx) {
2458
+ if (!isExternalTask(ctx.state)) {
2459
+ return void 0;
2460
+ }
2461
+ const normalizedPathArg = typeof _args.path === "string" && _args.path.trim() ? ctx.normalizePath(String(_args.path)) : void 0;
2462
+ const normalizedReportPath = ctx.normalizePath(
2463
+ ctx.state.planPath || getDefaultExternalReportPath(ctx.state.taskName)
2464
+ );
2465
+ if (ctx.isDiscoveryTool(toolName) || toolName === "read_file" || toolName === "restore_file_checkpoint") {
2466
+ return [
2467
+ `Blocked by marathon external research guard: ${toolName} is disabled for web research tasks.`,
2468
+ "Research the requested external source with built-in web tools instead.",
2469
+ "Use local file tools only if the user explicitly asked for workspace inspection or if you are saving the final deliverable."
2470
+ ].join(" ");
2471
+ }
2472
+ if (toolName === "write_file" && normalizedPathArg && normalizedPathArg !== normalizedReportPath) {
2473
+ return [
2474
+ `Blocked by marathon external research guard: write_file must target "${normalizedReportPath}".`,
2475
+ "Save the final markdown report to the required workspace path before TASK_COMPLETE."
2476
+ ].join(" ");
2477
+ }
2428
2478
  return void 0;
2429
2479
  },
2430
2480
  buildRecoveryMessage(state) {
2481
+ if (isExternalTask(state)) {
2482
+ const recent2 = state.sessions.slice(-2);
2483
+ const reportPath = state.planPath || getDefaultExternalReportPath(state.taskName);
2484
+ if (recent2.length < 2 || !recent2.every((session) => session.wroteFiles === false)) {
2485
+ return void 0;
2486
+ }
2487
+ const repeatedSameActions2 = hasRepeatedSameActions(state.sessions);
2488
+ return [
2489
+ "Recovery instruction:",
2490
+ "This web research task is missing its required markdown artifact.",
2491
+ `Your next action must be write_file to "${reportPath}" with the final findings in markdown.`,
2492
+ "Do not end with TASK_COMPLETE until that file has been written.",
2493
+ ...repeatedSameActions2 ? ["You are repeating the same actions; break the loop by writing the final report now."] : []
2494
+ ].join("\n");
2495
+ }
2431
2496
  const recent = state.sessions.slice(-2);
2432
2497
  const normalizedPlanPath = typeof state.planPath === "string" && state.planPath.trim() ? normalizeCandidatePath(state.planPath) : void 0;
2433
2498
  const recentPlanOnlyLoop = Boolean(normalizedPlanPath) && recent.length === 2 && recent.every((session) => {
@@ -2471,6 +2536,12 @@ var researchPhase = {
2471
2536
  ].join("\n");
2472
2537
  },
2473
2538
  shouldForceEndTurn(snapshot, ctx) {
2539
+ if (isExternalTask(ctx.state)) {
2540
+ if (ctx.isDiscoveryTool(snapshot.toolName) && snapshot.actionKeyCount >= 2) {
2541
+ return "this web research task is looping on local repo discovery instead of using external tools";
2542
+ }
2543
+ return void 0;
2544
+ }
2474
2545
  const state = ctx.state;
2475
2546
  const sufficientResearch = hasSufficientResearchEvidence(state);
2476
2547
  if (sufficientResearch && snapshot.discoveryPauseCount >= 12) {
@@ -2483,6 +2554,12 @@ var researchPhase = {
2483
2554
  return "this session exceeded the discovery-tool budget without ending the turn";
2484
2555
  }
2485
2556
  return void 0;
2557
+ },
2558
+ canAcceptCompletion(state, trace) {
2559
+ if (!isExternalTask(state)) {
2560
+ return true;
2561
+ }
2562
+ return Boolean(state.planWritten || trace.planWritten);
2486
2563
  }
2487
2564
  };
2488
2565
  var planningPhase = {
@@ -2709,6 +2786,65 @@ var executionPhase = {
2709
2786
  };
2710
2787
  function classifyVariant(message) {
2711
2788
  const lower = message.toLowerCase();
2789
+ const externalVerbs = [
2790
+ "fetch",
2791
+ "browse",
2792
+ "scrape",
2793
+ "crawl",
2794
+ "download",
2795
+ "visit",
2796
+ "navigate to",
2797
+ "go to",
2798
+ "open the",
2799
+ "look up",
2800
+ "search for",
2801
+ "find out",
2802
+ "check out",
2803
+ "pull up"
2804
+ ];
2805
+ const externalTargets = [
2806
+ "http://",
2807
+ "https://",
2808
+ "www.",
2809
+ ".com",
2810
+ ".org",
2811
+ ".io",
2812
+ ".net",
2813
+ ".dev",
2814
+ "website",
2815
+ "webpage",
2816
+ "web page",
2817
+ "home page",
2818
+ "homepage",
2819
+ "hacker news",
2820
+ "reddit",
2821
+ "twitter",
2822
+ "github.com",
2823
+ "firecrawl",
2824
+ "exa_search"
2825
+ ];
2826
+ const hasExternalVerb = externalVerbs.some((v) => lower.includes(v));
2827
+ const hasExternalTarget = externalTargets.some((t) => lower.includes(t));
2828
+ const modificationVerbs = [
2829
+ "fix",
2830
+ "update",
2831
+ "change",
2832
+ "modify",
2833
+ "edit",
2834
+ "refactor",
2835
+ "improve",
2836
+ "add to",
2837
+ "remove",
2838
+ "delete",
2839
+ "rename",
2840
+ "migrate"
2841
+ ];
2842
+ const hasModificationVerb = modificationVerbs.some(
2843
+ (v) => lower.startsWith(v) || new RegExp(`\\b${v}\\b`).test(lower)
2844
+ );
2845
+ if (hasExternalVerb && hasExternalTarget && !hasModificationVerb) {
2846
+ return "external";
2847
+ }
2712
2848
  const creationPatterns = [
2713
2849
  /^create\b/,
2714
2850
  /^build\b/,
@@ -2729,30 +2865,13 @@ function classifyVariant(message) {
2729
2865
  /^new\b/
2730
2866
  ];
2731
2867
  const hasCreationStart = creationPatterns.some((p) => p.test(lower));
2732
- const modificationVerbs = [
2733
- "fix",
2734
- "update",
2735
- "change",
2736
- "modify",
2737
- "edit",
2738
- "refactor",
2739
- "improve",
2740
- "add to",
2741
- "remove",
2742
- "delete",
2743
- "rename",
2744
- "migrate"
2745
- ];
2746
- const hasModificationVerb = modificationVerbs.some(
2747
- (v) => lower.startsWith(v) || new RegExp(`\\b${v}\\b`).test(lower)
2748
- );
2749
2868
  if (hasCreationStart && !hasModificationVerb) {
2750
2869
  return "create";
2751
2870
  }
2752
2871
  return "modify";
2753
2872
  }
2754
2873
  async function generateBootstrapContext(message, localTools, variant) {
2755
- if (variant === "create") return void 0;
2874
+ if (variant === "create" || variant === "external") return void 0;
2756
2875
  if (!localTools) return void 0;
2757
2876
  const searchTool = localTools.search_repo;
2758
2877
  const globTool = localTools.glob_files;
@@ -2788,7 +2907,7 @@ async function generateBootstrapContext(message, localTools, variant) {
2788
2907
  return ["Bootstrap repo hints:", ...lines].join("\n").slice(0, 1500);
2789
2908
  }
2790
2909
  function buildCandidateBlock(state) {
2791
- if (!state.bestCandidatePath || state.isCreationTask) return "";
2910
+ if (!state.bestCandidatePath || state.isCreationTask || isExternalTask(state)) return "";
2792
2911
  return [
2793
2912
  "",
2794
2913
  "--- Best Candidate ---",
@@ -4875,8 +4994,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4875
4994
  return `[${toolName} output (${resultStr.length} chars) saved to ${filePath} \u2014 use read_file to retrieve if needed]`;
4876
4995
  }
4877
4996
  getDefaultPlanPath(taskName) {
4878
- const slug = this.sanitizeTaskSlug(taskName || "task");
4879
- return `.runtype/marathons/${slug}/plan.md`;
4997
+ return getDefaultPlanPath(taskName);
4998
+ }
4999
+ getDefaultExternalReportPath(taskName) {
5000
+ return getDefaultExternalReportPath(taskName);
4880
5001
  }
4881
5002
  dirnameOfCandidatePath(candidatePath) {
4882
5003
  const normalized = this.normalizeCandidatePath(candidatePath);
@@ -4982,7 +5103,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4982
5103
  }
4983
5104
  sanitizeResumeState(resumeState, taskName) {
4984
5105
  if (!resumeState) return void 0;
4985
- const planPath = typeof resumeState.planPath === "string" && resumeState.planPath.trim() ? this.normalizeCandidatePath(resumeState.planPath) : this.getDefaultPlanPath(taskName);
5106
+ const workflowVariant = resumeState.workflowVariant;
5107
+ const defaultPlanPath = workflowVariant === "external" ? this.getDefaultExternalReportPath(taskName) : this.getDefaultPlanPath(taskName);
5108
+ const planPath = typeof resumeState.planPath === "string" && resumeState.planPath.trim() ? this.normalizeCandidatePath(resumeState.planPath) : defaultPlanPath;
5109
+ const migratedPlanPath = workflowVariant === "external" && planPath === this.getDefaultPlanPath(taskName) ? this.getDefaultExternalReportPath(taskName) : planPath;
4986
5110
  const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
4987
5111
  const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
4988
5112
  const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === "string" && resumeState.bestCandidatePath.trim() ? this.normalizeCandidatePath(resumeState.bestCandidatePath) : void 0;
@@ -4993,7 +5117,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4993
5117
  return {
4994
5118
  ...resumeState,
4995
5119
  workflowPhase,
4996
- planPath,
5120
+ planPath: migratedPlanPath,
4997
5121
  planWritten: Boolean(resumeState.planWritten),
4998
5122
  bestCandidatePath,
4999
5123
  bestCandidateReason: bestCandidatePath ? resumeState.bestCandidateReason : void 0,
@@ -5419,13 +5543,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5419
5543
  const taskName = typeof options.trackProgress === "string" ? options.trackProgress : options.trackProgress ? `${agent.name} task` : "";
5420
5544
  const resolvedTaskName = taskName || `${agent.name} task`;
5421
5545
  const seededResumeState = this.sanitizeResumeState(options.resumeState, resolvedTaskName);
5546
+ const classifiedVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5422
5547
  const state = {
5423
5548
  agentId: id,
5424
5549
  agentName: agent.name,
5425
5550
  taskName: resolvedTaskName,
5426
5551
  status: "running",
5427
5552
  workflowPhase: seededResumeState?.workflowPhase || workflow.phases[0]?.name || "research",
5428
- planPath: seededResumeState?.planPath || this.getDefaultPlanPath(resolvedTaskName),
5553
+ planPath: seededResumeState?.planPath || (classifiedVariant === "external" ? this.getDefaultExternalReportPath(resolvedTaskName) : this.getDefaultPlanPath(resolvedTaskName)),
5429
5554
  planWritten: seededResumeState?.planWritten || false,
5430
5555
  bestCandidateNeedsVerification: seededResumeState?.bestCandidateNeedsVerification || false,
5431
5556
  bestCandidateVerified: seededResumeState?.bestCandidateVerified || false,
@@ -5448,7 +5573,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5448
5573
  ...seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {},
5449
5574
  ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {}
5450
5575
  };
5451
- state.workflowVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5576
+ state.workflowVariant = classifiedVariant;
5452
5577
  state.isCreationTask = seededResumeState?.isCreationTask ?? state.workflowVariant === "create";
5453
5578
  this.updateWorkflowPhase(state, this.createEmptyToolTrace(), workflow);
5454
5579
  let recordId;
@@ -5466,7 +5591,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5466
5591
  state.bootstrapContext = await this.generateBootstrapDiscoveryContext(
5467
5592
  options.message,
5468
5593
  options.localTools,
5469
- state.isCreationTask
5594
+ state.isCreationTask || state.workflowVariant === "external"
5470
5595
  );
5471
5596
  }
5472
5597
  const bootstrapCandidate = this.extractBestCandidateFromBootstrapContext(state.bootstrapContext);
@@ -5692,13 +5817,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5692
5817
  if (state.sessions.length > 50) {
5693
5818
  state.sessions = state.sessions.slice(-50);
5694
5819
  }
5695
- if (sessionResult.stopReason === "complete") {
5820
+ const detectedTaskCompletion = this.detectTaskCompletion(sessionResult.result);
5821
+ const acceptedTaskCompletion = detectedTaskCompletion && this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace, workflow);
5822
+ if (sessionResult.stopReason === "complete" && !detectedTaskCompletion) {
5696
5823
  state.status = "complete";
5697
5824
  } else if (sessionResult.stopReason === "error") {
5698
5825
  state.status = "error";
5699
5826
  } else if (sessionResult.stopReason === "max_cost") {
5700
5827
  state.status = "budget_exceeded";
5701
- } else if (this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace, workflow)) {
5828
+ } else if (acceptedTaskCompletion) {
5702
5829
  state.status = "complete";
5703
5830
  } else if (maxCost && state.totalCost >= maxCost) {
5704
5831
  state.status = "budget_exceeded";
@@ -6021,7 +6148,7 @@ Do NOT redo any of the above work.`
6021
6148
  "",
6022
6149
  "Changed Files / Candidate Paths",
6023
6150
  ...candidatePaths.length > 0 ? candidatePaths.map((candidatePath) => `- ${candidatePath}`) : ["- No candidate paths recorded yet."],
6024
- ...state.planPath ? [`- Plan path: ${state.planPath}`] : [],
6151
+ ...state.planPath ? [`- ${state.workflowVariant === "external" ? "Report path" : "Plan path"}: ${state.planPath}`] : [],
6025
6152
  ...state.planWritten ? ["- Planning artifact has been written."] : [],
6026
6153
  ...state.bestCandidateReason ? [`- Best candidate rationale: ${state.bestCandidateReason}`] : [],
6027
6154
  "",
@@ -6042,6 +6169,75 @@ Do NOT redo any of the above work.`
6042
6169
  (state.lastOutput || "").slice(0, 1800) || "- No final output recorded yet."
6043
6170
  ].join("\n");
6044
6171
  }
6172
+ isAssistantToolCallMessage(message) {
6173
+ return Boolean(message?.role === "assistant" && message.toolCalls && message.toolCalls.length > 0);
6174
+ }
6175
+ isToolResultMessage(message) {
6176
+ return Boolean(message?.role === "tool" && message.toolResults && message.toolResults.length > 0);
6177
+ }
6178
+ /**
6179
+ * Replay only complete adjacent tool-call/result pairs so provider validation
6180
+ * never sees an orphaned tool result after history trimming or resume.
6181
+ */
6182
+ sanitizeReplayHistoryMessages(messages) {
6183
+ const sanitized = [];
6184
+ for (let index = 0; index < messages.length; index++) {
6185
+ const message = messages[index];
6186
+ if (this.isAssistantToolCallMessage(message)) {
6187
+ const nextMessage = messages[index + 1];
6188
+ if (!this.isToolResultMessage(nextMessage)) {
6189
+ continue;
6190
+ }
6191
+ const matchedResultIds = new Set(
6192
+ nextMessage.toolResults.filter(
6193
+ (toolResult) => message.toolCalls.some((toolCall) => toolCall.toolCallId === toolResult.toolCallId)
6194
+ ).map((toolResult) => toolResult.toolCallId)
6195
+ );
6196
+ if (matchedResultIds.size === 0) {
6197
+ continue;
6198
+ }
6199
+ const matchedToolCalls = message.toolCalls.filter(
6200
+ (toolCall) => matchedResultIds.has(toolCall.toolCallId)
6201
+ );
6202
+ const matchedToolResults = nextMessage.toolResults.filter(
6203
+ (toolResult) => matchedResultIds.has(toolResult.toolCallId)
6204
+ );
6205
+ sanitized.push(
6206
+ matchedToolCalls.length === message.toolCalls.length ? message : { ...message, toolCalls: matchedToolCalls }
6207
+ );
6208
+ sanitized.push(
6209
+ matchedToolResults.length === nextMessage.toolResults.length ? nextMessage : { ...nextMessage, toolResults: matchedToolResults }
6210
+ );
6211
+ index += 1;
6212
+ continue;
6213
+ }
6214
+ if (this.isToolResultMessage(message)) {
6215
+ continue;
6216
+ }
6217
+ sanitized.push(message);
6218
+ }
6219
+ return sanitized;
6220
+ }
6221
+ /**
6222
+ * Keep replay trimming on a pair boundary. If the trim cut would start on a
6223
+ * tool-result message, slide back to include the matching assistant tool call.
6224
+ */
6225
+ trimReplayHistoryMessages(messages, maxMessages) {
6226
+ if (messages.length <= maxMessages) {
6227
+ return {
6228
+ historyMessages: messages,
6229
+ trimmedCount: 0
6230
+ };
6231
+ }
6232
+ let startIndex = messages.length - maxMessages;
6233
+ while (startIndex > 0 && messages[startIndex]?.role === "tool") {
6234
+ startIndex -= 1;
6235
+ }
6236
+ return {
6237
+ historyMessages: messages.slice(startIndex),
6238
+ trimmedCount: startIndex
6239
+ };
6240
+ }
6045
6241
  /**
6046
6242
  * Build messages for a session, injecting progress context for continuation sessions.
6047
6243
  * Optionally accepts continuation context for marathon resume scenarios.
@@ -6112,29 +6308,41 @@ Do NOT redo any of the above work.`
6112
6308
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
6113
6309
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
6114
6310
  const isDeployWorkflow = wf.name === "deploy";
6311
+ const isExternalTask2 = state.workflowVariant === "external";
6312
+ const requiredArtifactPath = state.planPath;
6115
6313
  const localToolsBlock = localToolNames?.length ? [
6116
6314
  "",
6117
6315
  "--- Local Tools ---",
6118
6316
  `You have access to tools (${localToolNames.join(", ")}) that execute directly on the user's machine.`,
6119
- ...isDeployWorkflow ? ["Use deploy_sandbox to deploy code and get a live preview URL."] : [
6317
+ ...isDeployWorkflow ? ["Use deploy_sandbox to deploy code and get a live preview URL."] : isExternalTask2 ? [
6318
+ "This is a web/external research task. Do not inspect or edit the local repository unless the user explicitly asked for workspace changes.",
6319
+ ...requiredArtifactPath ? [`Write the final markdown findings to exactly: ${requiredArtifactPath}`] : []
6320
+ ] : [
6120
6321
  "Use these tools to inspect the existing repository and make real file edits \u2014 not just code in your response."
6121
6322
  ],
6122
6323
  ...toolGuidanceLines,
6123
- ...isDeployWorkflow ? [] : ["Always use write_file to save your output so the user can run it immediately."]
6324
+ ...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."]
6124
6325
  ].join("\n") : "";
6125
6326
  const builtinToolNames = builtinToolIds?.map((id) => id.replace(/^builtin:/, ""));
6126
6327
  const builtinToolsBlock = builtinToolNames?.length ? [
6127
6328
  "",
6128
6329
  "--- Built-in Tools ---",
6129
6330
  `You have access to built-in tools (${builtinToolNames.join(", ")}) for web search, web scraping, image generation, and other capabilities.`,
6130
- "Use these tools when the task requires information from the web, generating images, or other capabilities beyond local file operations."
6331
+ 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."
6131
6332
  ].join("\n") : "";
6132
6333
  const toolsBlock = localToolsBlock + builtinToolsBlock;
6133
- const bootstrapBlock = state.bootstrapContext ? ["", "--- Initial Repository Discovery ---", state.bootstrapContext].join("\n") : "";
6334
+ const bootstrapBlock = state.bootstrapContext ? [
6335
+ "",
6336
+ isExternalTask2 ? "--- Initial Research Context ---" : "--- Initial Repository Discovery ---",
6337
+ state.bootstrapContext
6338
+ ].join("\n") : "";
6134
6339
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
6135
6340
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
6136
6341
  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.`;
6137
6342
  if (continuationContext && sessionIndex === 0) {
6343
+ const replayHistoryMessages = this.sanitizeReplayHistoryMessages(
6344
+ continuationContext.previousMessages
6345
+ );
6138
6346
  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.";
6139
6347
  const userMessage = continuationContext.newUserMessage || defaultContinueMessage;
6140
6348
  const userContent = [
@@ -6147,7 +6355,7 @@ Do NOT redo any of the above work.`
6147
6355
  multiSessionInstruction
6148
6356
  ].join("\n");
6149
6357
  const fullHistoryMessages = [
6150
- ...continuationContext.previousMessages,
6358
+ ...replayHistoryMessages,
6151
6359
  {
6152
6360
  role: "system",
6153
6361
  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."
@@ -6159,7 +6367,7 @@ Do NOT redo any of the above work.`
6159
6367
  ];
6160
6368
  const summaryText = this.generateCompactSummary(state, compactInstructions);
6161
6369
  const breakdown = this.buildContextBudgetBreakdown({
6162
- historyMessages: continuationContext.previousMessages,
6370
+ historyMessages: replayHistoryMessages,
6163
6371
  currentTurnContent: userContent,
6164
6372
  localTools: compactionOptions?.localTools,
6165
6373
  builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
@@ -6249,16 +6457,19 @@ Do NOT redo any of the above work.`
6249
6457
  "Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
6250
6458
  ].join("\n");
6251
6459
  const MAX_HISTORY_MESSAGES = 60;
6252
- let historyMessages = state.messages;
6460
+ let historyMessages = this.sanitizeReplayHistoryMessages(state.messages);
6253
6461
  if (historyMessages.length > MAX_HISTORY_MESSAGES) {
6254
- const trimmedCount = historyMessages.length - MAX_HISTORY_MESSAGES;
6255
- historyMessages = [
6256
- {
6257
- role: "system",
6258
- content: `[${trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`
6259
- },
6260
- ...historyMessages.slice(-MAX_HISTORY_MESSAGES)
6261
- ];
6462
+ const trimmedHistory = this.trimReplayHistoryMessages(historyMessages, MAX_HISTORY_MESSAGES);
6463
+ historyMessages = trimmedHistory.historyMessages;
6464
+ if (trimmedHistory.trimmedCount > 0) {
6465
+ historyMessages = [
6466
+ {
6467
+ role: "system",
6468
+ content: `[${trimmedHistory.trimmedCount} earlier messages trimmed to stay within context limits. Original task: ${(state.originalMessage || originalMessage).slice(0, 500)}]`
6469
+ },
6470
+ ...historyMessages
6471
+ ];
6472
+ }
6262
6473
  }
6263
6474
  const summaryText = this.generateCompactSummary(state, compactInstructions);
6264
6475
  const breakdown = this.buildContextBudgetBreakdown({
@@ -6520,6 +6731,42 @@ var FlowBuilder = class {
6520
6731
  );
6521
6732
  return this;
6522
6733
  }
6734
+ /**
6735
+ * Add a crawl step
6736
+ */
6737
+ crawl(config) {
6738
+ this.addStep(
6739
+ "crawl",
6740
+ config.name,
6741
+ {
6742
+ url: config.url,
6743
+ limit: config.limit,
6744
+ depth: config.depth,
6745
+ source: config.source,
6746
+ formats: config.formats,
6747
+ render: config.render,
6748
+ maxAge: config.maxAge,
6749
+ modifiedSince: config.modifiedSince,
6750
+ options: config.options,
6751
+ authenticate: config.authenticate,
6752
+ cookies: config.cookies,
6753
+ setExtraHTTPHeaders: config.setExtraHTTPHeaders,
6754
+ gotoOptions: config.gotoOptions,
6755
+ waitForSelector: config.waitForSelector,
6756
+ rejectResourceTypes: config.rejectResourceTypes,
6757
+ rejectRequestPattern: config.rejectRequestPattern,
6758
+ userAgent: config.userAgent,
6759
+ jsonOptions: config.jsonOptions,
6760
+ outputVariable: config.outputVariable,
6761
+ errorHandling: config.errorHandling,
6762
+ streamOutput: config.streamOutput,
6763
+ pollIntervalMs: config.pollIntervalMs,
6764
+ completionTimeoutMs: config.completionTimeoutMs
6765
+ },
6766
+ config.enabled
6767
+ );
6768
+ return this;
6769
+ }
6523
6770
  /**
6524
6771
  * Add a fetch URL step
6525
6772
  */
@@ -6862,7 +7109,7 @@ var ClientFlowBuilder = class extends FlowBuilder {
6862
7109
  this.boundClient = client;
6863
7110
  this.createFlow({ name });
6864
7111
  }
6865
- async run(arg1, arg2, arg3, arg4) {
7112
+ async run(arg1, arg2, arg3, _arg4) {
6866
7113
  const config = this.build();
6867
7114
  let runOptions;
6868
7115
  let runCallbacks;