@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.d.cts CHANGED
@@ -3099,6 +3099,7 @@ interface RunTaskStateSlice {
3099
3099
  originalMessage?: string;
3100
3100
  workflowPhase?: string;
3101
3101
  workflowVariant?: string;
3102
+ /** Suggested markdown artifact path for the task */
3102
3103
  planPath?: string;
3103
3104
  planWritten?: boolean;
3104
3105
  bestCandidatePath?: string;
@@ -4267,7 +4268,7 @@ interface RunTaskState {
4267
4268
  bootstrapContext?: string;
4268
4269
  /** Current structured marathon workflow phase (widened to string for custom workflows) */
4269
4270
  workflowPhase?: string;
4270
- /** Suggested markdown plan path for the task */
4271
+ /** Suggested markdown artifact path for the task */
4271
4272
  planPath?: string;
4272
4273
  /** Whether the planning artifact has been written */
4273
4274
  planWritten?: boolean;
@@ -4611,6 +4612,7 @@ declare class AgentsEndpoint {
4611
4612
  private readonly TOOL_OUTPUT_INLINE_THRESHOLD;
4612
4613
  private offloadToolResult;
4613
4614
  private getDefaultPlanPath;
4615
+ private getDefaultExternalReportPath;
4614
4616
  private dirnameOfCandidatePath;
4615
4617
  private joinCandidatePath;
4616
4618
  private scoreCandidatePath;
package/dist/index.d.ts CHANGED
@@ -3099,6 +3099,7 @@ interface RunTaskStateSlice {
3099
3099
  originalMessage?: string;
3100
3100
  workflowPhase?: string;
3101
3101
  workflowVariant?: string;
3102
+ /** Suggested markdown artifact path for the task */
3102
3103
  planPath?: string;
3103
3104
  planWritten?: boolean;
3104
3105
  bestCandidatePath?: string;
@@ -4267,7 +4268,7 @@ interface RunTaskState {
4267
4268
  bootstrapContext?: string;
4268
4269
  /** Current structured marathon workflow phase (widened to string for custom workflows) */
4269
4270
  workflowPhase?: string;
4270
- /** Suggested markdown plan path for the task */
4271
+ /** Suggested markdown artifact path for the task */
4271
4272
  planPath?: string;
4272
4273
  /** Whether the planning artifact has been written */
4273
4274
  planWritten?: boolean;
@@ -4611,6 +4612,7 @@ declare class AgentsEndpoint {
4611
4612
  private readonly TOOL_OUTPUT_INLINE_THRESHOLD;
4612
4613
  private offloadToolResult;
4613
4614
  private getDefaultPlanPath;
4615
+ private getDefaultExternalReportPath;
4614
4616
  private dirnameOfCandidatePath;
4615
4617
  private joinCandidatePath;
4616
4618
  private scoreCandidatePath;
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
  "",
@@ -6112,25 +6239,34 @@ Do NOT redo any of the above work.`
6112
6239
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
6113
6240
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
6114
6241
  const isDeployWorkflow = wf.name === "deploy";
6242
+ const isExternalTask2 = state.workflowVariant === "external";
6243
+ const requiredArtifactPath = state.planPath;
6115
6244
  const localToolsBlock = localToolNames?.length ? [
6116
6245
  "",
6117
6246
  "--- Local Tools ---",
6118
6247
  `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."] : [
6248
+ ...isDeployWorkflow ? ["Use deploy_sandbox to deploy code and get a live preview URL."] : isExternalTask2 ? [
6249
+ "This is a web/external research task. Do not inspect or edit the local repository unless the user explicitly asked for workspace changes.",
6250
+ ...requiredArtifactPath ? [`Write the final markdown findings to exactly: ${requiredArtifactPath}`] : []
6251
+ ] : [
6120
6252
  "Use these tools to inspect the existing repository and make real file edits \u2014 not just code in your response."
6121
6253
  ],
6122
6254
  ...toolGuidanceLines,
6123
- ...isDeployWorkflow ? [] : ["Always use write_file to save your output so the user can run it immediately."]
6255
+ ...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
6256
  ].join("\n") : "";
6125
6257
  const builtinToolNames = builtinToolIds?.map((id) => id.replace(/^builtin:/, ""));
6126
6258
  const builtinToolsBlock = builtinToolNames?.length ? [
6127
6259
  "",
6128
6260
  "--- Built-in Tools ---",
6129
6261
  `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."
6262
+ 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
6263
  ].join("\n") : "";
6132
6264
  const toolsBlock = localToolsBlock + builtinToolsBlock;
6133
- const bootstrapBlock = state.bootstrapContext ? ["", "--- Initial Repository Discovery ---", state.bootstrapContext].join("\n") : "";
6265
+ const bootstrapBlock = state.bootstrapContext ? [
6266
+ "",
6267
+ isExternalTask2 ? "--- Initial Research Context ---" : "--- Initial Repository Discovery ---",
6268
+ state.bootstrapContext
6269
+ ].join("\n") : "";
6134
6270
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
6135
6271
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
6136
6272
  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.`;