@runtypelabs/sdk 1.8.0 → 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 ---",
@@ -3108,7 +3227,11 @@ var designPhase = {
3108
3227
  buildTransitionSummary(_state, _nextPhaseName) {
3109
3228
  return [
3110
3229
  "Automatic phase transition: design \u2192 build.",
3111
- "Requirements understood. Build the game and deploy it using deploy_sandbox with the files parameter."
3230
+ "Requirements understood. Build the game and deploy it using deploy_sandbox.",
3231
+ "",
3232
+ "MANDATORY: Use the `files` parameter for ALL game HTML/JS/CSS.",
3233
+ "code = minimal Express static server (~4 lines), files = game assets.",
3234
+ "NEVER embed HTML inside res.send() template literals."
3112
3235
  ].join("\n");
3113
3236
  },
3114
3237
  interceptToolCall(toolName, _args, _ctx) {
@@ -3168,7 +3291,32 @@ var buildPhase = {
3168
3291
  " 4. If the deploy fails, read the error output, fix the code, and call deploy_sandbox again.",
3169
3292
  " 5. The sandbox is persistent \u2014 subsequent calls reuse the same sandbox.",
3170
3293
  " 6. Load game libraries from CDN in your HTML (e.g., Three.js from unpkg/cdnjs) OR include them in packageJson.",
3171
- " 7. Make the game fullscreen by default (width: 100vw, height: 100vh, no margin/padding on body)."
3294
+ " 7. Make the game fullscreen by default (width: 100vw, height: 100vh, no margin/padding on body).",
3295
+ "",
3296
+ "STARTER TEMPLATES \u2014 Copy and customize based on the game type:",
3297
+ "",
3298
+ "ALL templates use this server code:",
3299
+ ' code: `const express = require("express"); const app = express(); app.use(express.static("public")); app.listen(3000, () => console.log("Game server running on port 3000"));`',
3300
+ ' packageJson: { "dependencies": { "express": "^4.18.2" } }',
3301
+ ' language: "javascript"',
3302
+ "",
3303
+ "THREE.JS (3D games \u2014 racing, flying, FPS, etc.):",
3304
+ " files: {",
3305
+ ' "public/index.html": HTML with <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js"}}</script> and <script type="module" src="game.js"></script>',
3306
+ ' "public/game.js": Import three, create scene/camera/renderer, game loop with requestAnimationFrame',
3307
+ " }",
3308
+ "",
3309
+ "PHASER (2D games \u2014 platformers, shooters, puzzles):",
3310
+ " files: {",
3311
+ ' "public/index.html": HTML with <script src="https://cdn.jsdelivr.net/npm/phaser@3.80.1/dist/phaser.min.js"></script> and <script src="game.js"></script>',
3312
+ ' "public/game.js": Phaser.Game config with scenes, preload/create/update lifecycle',
3313
+ " }",
3314
+ "",
3315
+ "CANVAS 2D (simple games \u2014 pong, snake, breakout):",
3316
+ " files: {",
3317
+ ' "public/index.html": HTML with <canvas id="game" width="800" height="600"></canvas> and <script src="game.js"></script>',
3318
+ ' "public/game.js": Get canvas context, game loop with requestAnimationFrame, draw calls',
3319
+ " }"
3172
3320
  ].join("\n");
3173
3321
  },
3174
3322
  buildToolGuidance(_state) {
@@ -3182,8 +3330,23 @@ var buildPhase = {
3182
3330
  isComplete() {
3183
3331
  return false;
3184
3332
  },
3185
- interceptToolCall(toolName, _args, _ctx) {
3186
- return blockLocalTools(toolName);
3333
+ interceptToolCall(toolName, args, _ctx) {
3334
+ const localBlock = blockLocalTools(toolName);
3335
+ if (localBlock) return localBlock;
3336
+ if (toolName === "deploy_sandbox" && args) {
3337
+ const hasFiles = args.files && typeof args.files === "object" && Object.keys(args.files).length > 0;
3338
+ const codeStr = typeof args.code === "string" ? args.code : "";
3339
+ const embedsHtml = /res\.send\s*\(/.test(codeStr) && /<html|<body|<script/i.test(codeStr);
3340
+ if (!hasFiles && embedsHtml) {
3341
+ return [
3342
+ "Blocked: You are embedding HTML inside res.send() \u2014 this will break due to nested template literals.",
3343
+ "You MUST use the `files` parameter. Put your game HTML/JS/CSS in files and use a minimal Express static server for code.",
3344
+ 'Example: code: `const express = require("express"); const app = express(); app.use(express.static("public")); app.listen(3000);`',
3345
+ 'files: { "public/index.html": "<!DOCTYPE html>...", "public/game.js": "// game code" }'
3346
+ ].join("\n");
3347
+ }
3348
+ }
3349
+ return void 0;
3187
3350
  },
3188
3351
  canAcceptCompletion(_state, trace) {
3189
3352
  return trace.entries.some((entry) => entry.startsWith("deploy_sandbox"));
@@ -4901,8 +5064,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4901
5064
  return `[${toolName} output (${resultStr.length} chars) saved to ${filePath} \u2014 use read_file to retrieve if needed]`;
4902
5065
  }
4903
5066
  getDefaultPlanPath(taskName) {
4904
- const slug = this.sanitizeTaskSlug(taskName || "task");
4905
- return `.runtype/marathons/${slug}/plan.md`;
5067
+ return getDefaultPlanPath(taskName);
5068
+ }
5069
+ getDefaultExternalReportPath(taskName) {
5070
+ return getDefaultExternalReportPath(taskName);
4906
5071
  }
4907
5072
  dirnameOfCandidatePath(candidatePath) {
4908
5073
  const normalized = this.normalizeCandidatePath(candidatePath);
@@ -5008,7 +5173,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5008
5173
  }
5009
5174
  sanitizeResumeState(resumeState, taskName) {
5010
5175
  if (!resumeState) return void 0;
5011
- 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;
5012
5180
  const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
5013
5181
  const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
5014
5182
  const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === "string" && resumeState.bestCandidatePath.trim() ? this.normalizeCandidatePath(resumeState.bestCandidatePath) : void 0;
@@ -5019,7 +5187,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5019
5187
  return {
5020
5188
  ...resumeState,
5021
5189
  workflowPhase,
5022
- planPath,
5190
+ planPath: migratedPlanPath,
5023
5191
  planWritten: Boolean(resumeState.planWritten),
5024
5192
  bestCandidatePath,
5025
5193
  bestCandidateReason: bestCandidatePath ? resumeState.bestCandidateReason : void 0,
@@ -5445,13 +5613,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5445
5613
  const taskName = typeof options.trackProgress === "string" ? options.trackProgress : options.trackProgress ? `${agent.name} task` : "";
5446
5614
  const resolvedTaskName = taskName || `${agent.name} task`;
5447
5615
  const seededResumeState = this.sanitizeResumeState(options.resumeState, resolvedTaskName);
5616
+ const classifiedVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5448
5617
  const state = {
5449
5618
  agentId: id,
5450
5619
  agentName: agent.name,
5451
5620
  taskName: resolvedTaskName,
5452
5621
  status: "running",
5453
5622
  workflowPhase: seededResumeState?.workflowPhase || workflow.phases[0]?.name || "research",
5454
- planPath: seededResumeState?.planPath || this.getDefaultPlanPath(resolvedTaskName),
5623
+ planPath: seededResumeState?.planPath || (classifiedVariant === "external" ? this.getDefaultExternalReportPath(resolvedTaskName) : this.getDefaultPlanPath(resolvedTaskName)),
5455
5624
  planWritten: seededResumeState?.planWritten || false,
5456
5625
  bestCandidateNeedsVerification: seededResumeState?.bestCandidateNeedsVerification || false,
5457
5626
  bestCandidateVerified: seededResumeState?.bestCandidateVerified || false,
@@ -5474,11 +5643,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5474
5643
  ...seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {},
5475
5644
  ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {}
5476
5645
  };
5477
- state.workflowVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5646
+ state.workflowVariant = classifiedVariant;
5478
5647
  state.isCreationTask = seededResumeState?.isCreationTask ?? state.workflowVariant === "create";
5479
5648
  this.updateWorkflowPhase(state, this.createEmptyToolTrace(), workflow);
5480
5649
  let recordId;
5481
5650
  const localToolNames = options.localTools ? Object.keys(options.localTools) : void 0;
5651
+ const builtinToolSchemas = await this.loadBuiltinToolSchemas(options.toolIds);
5652
+ const compactInstructions = this.resolveCompactInstructions(options.compactInstructions, agent);
5482
5653
  if (!options.previousMessages) {
5483
5654
  if (workflow.generateBootstrapContext) {
5484
5655
  state.bootstrapContext = await workflow.generateBootstrapContext(
@@ -5490,7 +5661,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5490
5661
  state.bootstrapContext = await this.generateBootstrapDiscoveryContext(
5491
5662
  options.message,
5492
5663
  options.localTools,
5493
- state.isCreationTask
5664
+ state.isCreationTask || state.workflowVariant === "external"
5494
5665
  );
5495
5666
  }
5496
5667
  const bootstrapCandidate = this.extractBestCandidateFromBootstrapContext(state.bootstrapContext);
@@ -5512,7 +5683,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5512
5683
  if (session === 0 && !options.previousMessages) {
5513
5684
  state.originalMessage = options.message;
5514
5685
  }
5515
- const messages = this.buildSessionMessages(
5686
+ const preparedSession = await this.prepareSessionContext(
5516
5687
  options.message,
5517
5688
  state,
5518
5689
  session,
@@ -5520,61 +5691,93 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5520
5691
  localToolNames,
5521
5692
  continuationContext,
5522
5693
  workflow,
5523
- options.toolIds
5694
+ options.toolIds,
5695
+ {
5696
+ autoCompactTokenThreshold: options.autoCompactTokenThreshold,
5697
+ contextLimitTokens: options.contextLimitTokens,
5698
+ model: options.model,
5699
+ compactStrategy: options.compactStrategy,
5700
+ compactInstructions,
5701
+ localTools: options.localTools,
5702
+ builtinToolSchemas,
5703
+ onContextCompaction: options.onContextCompaction,
5704
+ onContextNotice: options.onContextNotice
5705
+ }
5524
5706
  );
5707
+ const {
5708
+ messages,
5709
+ requestContextManagement,
5710
+ pendingNativeCompactionEvent
5711
+ } = preparedSession;
5525
5712
  let sessionResult;
5526
5713
  const sessionData = {
5527
5714
  messages,
5528
5715
  debugMode: options.debugMode,
5529
5716
  model: options.model,
5530
- ...options.toolIds?.length ? { tools: { toolIds: options.toolIds } } : {}
5717
+ ...options.toolIds?.length ? { tools: { toolIds: options.toolIds } } : {},
5718
+ ...requestContextManagement ? { contextManagement: requestContextManagement } : {}
5531
5719
  };
5532
5720
  let sessionToolMessages = [];
5533
- if (useStream && options.localTools) {
5534
- const localToolResult = await this.executeWithLocalTools(
5535
- id,
5536
- sessionData,
5537
- sessionLocalTools || options.localTools,
5538
- sessionCallbacks,
5539
- {
5540
- onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow)
5541
- },
5542
- state.taskName
5543
- );
5544
- if (!localToolResult) {
5545
- throw new Error("Agent stream ended without a complete event");
5721
+ if (pendingNativeCompactionEvent) {
5722
+ await this.emitContextCompactionEvent(options.onContextCompaction, {
5723
+ phase: "start",
5724
+ ...pendingNativeCompactionEvent
5725
+ });
5726
+ }
5727
+ try {
5728
+ if (useStream && options.localTools) {
5729
+ const localToolResult = await this.executeWithLocalTools(
5730
+ id,
5731
+ sessionData,
5732
+ sessionLocalTools || options.localTools,
5733
+ sessionCallbacks,
5734
+ {
5735
+ onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow)
5736
+ },
5737
+ state.taskName
5738
+ );
5739
+ if (!localToolResult) {
5740
+ throw new Error("Agent stream ended without a complete event");
5741
+ }
5742
+ const { completeEvent, toolMessages: captured } = localToolResult;
5743
+ sessionToolMessages = captured;
5744
+ sessionResult = {
5745
+ success: completeEvent.success,
5746
+ result: completeEvent.finalOutput || "",
5747
+ iterations: completeEvent.iterations,
5748
+ totalCost: completeEvent.totalCost || 0,
5749
+ totalTokens: completeEvent.totalTokens,
5750
+ stopReason: completeEvent.stopReason,
5751
+ error: completeEvent.error
5752
+ };
5753
+ } else if (useStream && options.streamCallbacks) {
5754
+ const completeEvent = await this.executeWithCallbacks(
5755
+ id,
5756
+ sessionData,
5757
+ sessionCallbacks || options.streamCallbacks
5758
+ );
5759
+ if (!completeEvent) {
5760
+ throw new Error("Agent stream ended without a complete event");
5761
+ }
5762
+ sessionResult = {
5763
+ success: completeEvent.success,
5764
+ result: completeEvent.finalOutput || "",
5765
+ iterations: completeEvent.iterations,
5766
+ totalCost: completeEvent.totalCost || 0,
5767
+ totalTokens: completeEvent.totalTokens,
5768
+ stopReason: completeEvent.stopReason,
5769
+ error: completeEvent.error
5770
+ };
5771
+ } else {
5772
+ sessionResult = await this.execute(id, sessionData);
5546
5773
  }
5547
- const { completeEvent, toolMessages: captured } = localToolResult;
5548
- sessionToolMessages = captured;
5549
- sessionResult = {
5550
- success: completeEvent.success,
5551
- result: completeEvent.finalOutput || "",
5552
- iterations: completeEvent.iterations,
5553
- totalCost: completeEvent.totalCost || 0,
5554
- totalTokens: completeEvent.totalTokens,
5555
- stopReason: completeEvent.stopReason,
5556
- error: completeEvent.error
5557
- };
5558
- } else if (useStream && options.streamCallbacks) {
5559
- const completeEvent = await this.executeWithCallbacks(
5560
- id,
5561
- sessionData,
5562
- sessionCallbacks || options.streamCallbacks
5563
- );
5564
- if (!completeEvent) {
5565
- throw new Error("Agent stream ended without a complete event");
5774
+ } finally {
5775
+ if (pendingNativeCompactionEvent) {
5776
+ await this.emitContextCompactionEvent(options.onContextCompaction, {
5777
+ phase: "complete",
5778
+ ...pendingNativeCompactionEvent
5779
+ });
5566
5780
  }
5567
- sessionResult = {
5568
- success: completeEvent.success,
5569
- result: completeEvent.finalOutput || "",
5570
- iterations: completeEvent.iterations,
5571
- totalCost: completeEvent.totalCost || 0,
5572
- totalTokens: completeEvent.totalTokens,
5573
- stopReason: completeEvent.stopReason,
5574
- error: completeEvent.error
5575
- };
5576
- } else {
5577
- sessionResult = await this.execute(id, sessionData);
5578
5781
  }
5579
5782
  const toolTraceSummary = this.buildToolTraceSummary(sessionTrace);
5580
5783
  const effectiveSessionOutput = this.buildEffectiveSessionOutput(
@@ -5661,7 +5864,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5661
5864
  }
5662
5865
  }
5663
5866
  if (!state.messages) state.messages = [];
5664
- if (state.messages.length > 0 && messages.length > state.messages.length) {
5867
+ if (this.isCompactHistoryMessageSet(messages)) {
5868
+ state.messages = [...messages];
5869
+ } else if (state.messages.length > 0 && messages.length > state.messages.length) {
5665
5870
  const newMessages = messages.slice(state.messages.length);
5666
5871
  state.messages.push(...newMessages);
5667
5872
  } else {
@@ -5682,13 +5887,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5682
5887
  if (state.sessions.length > 50) {
5683
5888
  state.sessions = state.sessions.slice(-50);
5684
5889
  }
5685
- 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) {
5686
5893
  state.status = "complete";
5687
5894
  } else if (sessionResult.stopReason === "error") {
5688
5895
  state.status = "error";
5689
5896
  } else if (sessionResult.stopReason === "max_cost") {
5690
5897
  state.status = "budget_exceeded";
5691
- } else if (this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace, workflow)) {
5898
+ } else if (acceptedTaskCompletion) {
5692
5899
  state.status = "complete";
5693
5900
  } else if (maxCost && state.totalCost >= maxCost) {
5694
5901
  state.status = "budget_exceeded";
@@ -5726,88 +5933,426 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5726
5933
  const upper = output.toUpperCase();
5727
5934
  return _AgentsEndpoint.STOP_PHRASES.some((phrase) => upper.includes(phrase.toUpperCase()));
5728
5935
  }
5936
+ resolveReservedOutputTokens(contextLimitTokens) {
5937
+ if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0) {
5938
+ return void 0;
5939
+ }
5940
+ return Math.max(8e3, Math.min(64e3, Math.floor(contextLimitTokens * 0.15)));
5941
+ }
5942
+ resolveEffectiveInputBudgetTokens(contextLimitTokens) {
5943
+ if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0) {
5944
+ return void 0;
5945
+ }
5946
+ const reservedOutputTokens = this.resolveReservedOutputTokens(contextLimitTokens) ?? 0;
5947
+ return Math.max(1, contextLimitTokens - reservedOutputTokens);
5948
+ }
5949
+ resolveCompactStrategy(strategy, modelId) {
5950
+ if (strategy === "summary_fallback") {
5951
+ return "summary_fallback";
5952
+ }
5953
+ const normalizedModelId = modelId?.trim().toLowerCase() ?? "";
5954
+ const supportsAnthropicNativeCompaction = normalizedModelId.includes("claude") || normalizedModelId.includes("anthropic");
5955
+ if (strategy === "provider_native") {
5956
+ return supportsAnthropicNativeCompaction ? "provider_native" : "summary_fallback";
5957
+ }
5958
+ return supportsAnthropicNativeCompaction ? "provider_native" : "summary_fallback";
5959
+ }
5960
+ estimateTextTokens(text) {
5961
+ if (!text) return 0;
5962
+ return Math.ceil(text.length / 4);
5963
+ }
5964
+ estimateUnknownTokens(value) {
5965
+ if (typeof value === "string") return this.estimateTextTokens(value);
5966
+ try {
5967
+ return this.estimateTextTokens(JSON.stringify(value));
5968
+ } catch {
5969
+ return 0;
5970
+ }
5971
+ }
5972
+ estimateMessageContentTokens(content) {
5973
+ if (typeof content === "string") return this.estimateTextTokens(content);
5974
+ return content.reduce((total, part) => {
5975
+ if (typeof part.text === "string") total += this.estimateTextTokens(part.text);
5976
+ if (typeof part.image === "string") total += 85;
5977
+ if (typeof part.data === "string") {
5978
+ total += Math.min(200, this.estimateTextTokens(part.data));
5979
+ }
5980
+ return total + 4;
5981
+ }, 0);
5982
+ }
5983
+ estimateToolCallTokens(toolCalls) {
5984
+ if (!toolCalls || toolCalls.length === 0) return 0;
5985
+ return toolCalls.reduce(
5986
+ (sum, toolCall) => sum + 12 + this.estimateTextTokens(toolCall.toolName) + this.estimateUnknownTokens(toolCall.args),
5987
+ 0
5988
+ );
5989
+ }
5990
+ estimateToolResultTokens(toolResults) {
5991
+ if (!toolResults || toolResults.length === 0) return 0;
5992
+ return toolResults.reduce(
5993
+ (sum, toolResult) => sum + 12 + this.estimateTextTokens(toolResult.toolName) + this.estimateUnknownTokens(toolResult.result),
5994
+ 0
5995
+ );
5996
+ }
5997
+ estimateMessageTokens(message) {
5998
+ return 6 + this.estimateMessageContentTokens(message.content) + this.estimateToolCallTokens(message.toolCalls) + this.estimateToolResultTokens(message.toolResults);
5999
+ }
6000
+ estimateConversationTokens(messages) {
6001
+ return messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
6002
+ }
6003
+ estimateConversationBreakdown(messages) {
6004
+ let historyTokens = 0;
6005
+ let toolOutputTokens = 0;
6006
+ for (const message of messages) {
6007
+ const contentTokens = this.estimateMessageContentTokens(message.content);
6008
+ const toolCallTokens = this.estimateToolCallTokens(message.toolCalls);
6009
+ const toolResultTokens = this.estimateToolResultTokens(message.toolResults);
6010
+ const messageTotal = 6 + contentTokens + toolCallTokens + toolResultTokens;
6011
+ if (message.role === "tool") {
6012
+ toolOutputTokens += messageTotal;
6013
+ } else {
6014
+ historyTokens += 6 + contentTokens + toolCallTokens;
6015
+ toolOutputTokens += toolResultTokens;
6016
+ }
6017
+ }
6018
+ return {
6019
+ historyTokens,
6020
+ toolOutputTokens,
6021
+ estimatedInputTokens: historyTokens + toolOutputTokens
6022
+ };
6023
+ }
6024
+ estimateToolDefinitionTokens(localTools, builtinToolSchemas) {
6025
+ const localToolDefinitions = Object.entries(localTools || {}).map(([name, definition]) => ({
6026
+ name,
6027
+ description: definition.description,
6028
+ parametersSchema: definition.parametersSchema
6029
+ }));
6030
+ const payload = [...localToolDefinitions, ...builtinToolSchemas];
6031
+ if (payload.length === 0) return 0;
6032
+ return this.estimateUnknownTokens(payload);
6033
+ }
6034
+ async loadBuiltinToolSchemas(toolIds) {
6035
+ if (!toolIds || toolIds.length === 0) return [];
6036
+ try {
6037
+ const schemas = await this.client.get("/tools/schema", { toolIds });
6038
+ return Array.isArray(schemas) ? schemas : [];
6039
+ } catch {
6040
+ return [];
6041
+ }
6042
+ }
6043
+ buildDefaultCompactInstructions() {
6044
+ return [
6045
+ "Preserve changed files or best candidate paths.",
6046
+ "Preserve verification or test results.",
6047
+ "Preserve the current workflow phase and remaining work.",
6048
+ "Preserve unresolved blockers or follow-up risks.",
6049
+ "Preserve artifact references and offloaded tool outputs."
6050
+ ].join(" ");
6051
+ }
6052
+ resolveCompactInstructions(optionInstructions, agent) {
6053
+ if (typeof optionInstructions === "string" && optionInstructions.trim()) {
6054
+ return optionInstructions.trim();
6055
+ }
6056
+ const config = agent.config;
6057
+ if (!config || typeof config !== "object") {
6058
+ return void 0;
6059
+ }
6060
+ const compactInstructions = config.compactInstructions;
6061
+ return typeof compactInstructions === "string" && compactInstructions.trim() ? compactInstructions.trim() : void 0;
6062
+ }
6063
+ extractArtifactReferences(state) {
6064
+ const references = /* @__PURE__ */ new Set();
6065
+ const offloadPrefix = "[Output saved to ";
6066
+ for (const message of state.messages ?? []) {
6067
+ if (!message.toolResults) continue;
6068
+ for (const toolResult of message.toolResults) {
6069
+ if (typeof toolResult.result !== "string") continue;
6070
+ let startIndex = 0;
6071
+ while (startIndex < toolResult.result.length) {
6072
+ const prefixIndex = toolResult.result.indexOf(offloadPrefix, startIndex);
6073
+ if (prefixIndex === -1) break;
6074
+ const pathStart = prefixIndex + offloadPrefix.length;
6075
+ const closingBracket = toolResult.result.indexOf("]", pathStart);
6076
+ if (closingBracket === -1) break;
6077
+ const bracketContent = toolResult.result.slice(pathStart, closingBracket);
6078
+ const separatorIndex = bracketContent.indexOf(" \u2014 ");
6079
+ const pathText = separatorIndex === -1 ? bracketContent.trim() : bracketContent.slice(0, separatorIndex).trim();
6080
+ if (pathText) {
6081
+ references.add(pathText);
6082
+ }
6083
+ startIndex = closingBracket + 1;
6084
+ }
6085
+ }
6086
+ }
6087
+ if (state.planPath) {
6088
+ references.add(state.planPath);
6089
+ }
6090
+ return [...references].slice(0, 8);
6091
+ }
6092
+ buildContextBudgetBreakdown(details) {
6093
+ const conversationBreakdown = this.estimateConversationBreakdown(details.historyMessages);
6094
+ const currentTurnTokens = this.estimateTextTokens(details.currentTurnContent);
6095
+ const toolDefinitionTokens = this.estimateToolDefinitionTokens(
6096
+ details.localTools,
6097
+ details.builtinToolSchemas
6098
+ );
6099
+ const reservedOutputTokens = this.resolveReservedOutputTokens(details.contextLimitTokens);
6100
+ const effectiveInputBudgetTokens = this.resolveEffectiveInputBudgetTokens(details.contextLimitTokens);
6101
+ const summaryTokens = details.summaryText ? this.estimateTextTokens(
6102
+ `${_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX}
6103
+
6104
+ ${details.summaryText}
6105
+
6106
+ Do NOT redo any of the above work.`
6107
+ ) : void 0;
6108
+ return {
6109
+ historyTokens: conversationBreakdown.historyTokens,
6110
+ toolOutputTokens: conversationBreakdown.toolOutputTokens,
6111
+ currentTurnTokens,
6112
+ toolDefinitionTokens,
6113
+ ...summaryTokens ? { summaryTokens } : {},
6114
+ ...reservedOutputTokens ? { reservedOutputTokens } : {},
6115
+ ...effectiveInputBudgetTokens ? { effectiveInputBudgetTokens } : {},
6116
+ estimatedInputTokens: conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens
6117
+ };
6118
+ }
6119
+ async emitContextCompactionEvent(onContextCompaction, event) {
6120
+ if (!onContextCompaction) return;
6121
+ try {
6122
+ await onContextCompaction(event);
6123
+ } catch {
6124
+ }
6125
+ }
6126
+ async emitContextNotice(onContextNotice, event) {
6127
+ if (!onContextNotice) return;
6128
+ try {
6129
+ await onContextNotice(event);
6130
+ } catch {
6131
+ }
6132
+ }
6133
+ async buildCompactHistoryMessagesWithLifecycle(state, userContent, sessionIndex, details) {
6134
+ const baseEvent = {
6135
+ mode: details.mode,
6136
+ strategy: details.strategy,
6137
+ sessionIndex: sessionIndex + 1,
6138
+ model: details.model,
6139
+ estimatedTokens: details.beforeTokens,
6140
+ thresholdTokens: details.thresholdTokens,
6141
+ contextLimitTokens: details.contextLimitTokens,
6142
+ effectiveInputBudgetTokens: details.effectiveInputBudgetTokens,
6143
+ reservedOutputTokens: details.reservedOutputTokens,
6144
+ beforeTokens: details.beforeTokens,
6145
+ afterTokens: details.afterTokens,
6146
+ breakdown: details.breakdown
6147
+ };
6148
+ await this.emitContextCompactionEvent(details.onContextCompaction, {
6149
+ phase: "start",
6150
+ ...baseEvent
6151
+ });
6152
+ const compactMessages = this.buildCompactHistoryMessages(
6153
+ state,
6154
+ userContent,
6155
+ details.compactInstructions,
6156
+ details.mode
6157
+ );
6158
+ await this.emitContextCompactionEvent(details.onContextCompaction, {
6159
+ phase: "complete",
6160
+ ...baseEvent
6161
+ });
6162
+ return compactMessages;
6163
+ }
6164
+ buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto") {
6165
+ const summary = this.generateCompactSummary(state, compactInstructions);
6166
+ const prefix = mode === "forced" ? _AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX : _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX;
6167
+ return [
6168
+ {
6169
+ role: "system",
6170
+ content: `${prefix}
6171
+
6172
+ ${summary}
6173
+
6174
+ Do NOT redo any of the above work.`
6175
+ },
6176
+ {
6177
+ role: "user",
6178
+ content: userContent
6179
+ }
6180
+ ];
6181
+ }
6182
+ isCompactHistoryMessageSet(messages) {
6183
+ if (messages.length === 0) return false;
6184
+ const firstMessage = messages[0];
6185
+ return firstMessage?.role === "system" && typeof firstMessage.content === "string" && (firstMessage.content.startsWith(_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX) || firstMessage.content.startsWith(_AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX));
6186
+ }
5729
6187
  /**
5730
6188
  * Generate a compact summary of prior work for continuation context.
5731
6189
  * Used when compact mode is enabled to keep token usage low.
5732
6190
  */
5733
- generateCompactSummary(state) {
5734
- const sessionSummaries = (state.sessions ?? []).map(
5735
- (s) => `- Session ${s.index}: ${s.stopReason} ($${s.cost.toFixed(4)}) -- ${s.outputPreview.slice(0, 100)}`
6191
+ generateCompactSummary(state, compactInstructions) {
6192
+ const recentSessions = (state.sessions ?? []).slice(-5);
6193
+ const sessionSummaries = recentSessions.map(
6194
+ (session) => `- Session ${session.index}: ${session.stopReason} ($${session.cost.toFixed(4)}) ${session.outputPreview.slice(0, 160)}`
5736
6195
  ).join("\n");
6196
+ const candidatePaths = Array.from(
6197
+ new Set(
6198
+ [
6199
+ ...state.bestCandidatePath ? [state.bestCandidatePath] : [],
6200
+ ...state.candidatePaths ?? []
6201
+ ].filter(Boolean)
6202
+ )
6203
+ ).slice(0, 8);
6204
+ const verificationSummary = state.bestCandidateVerified ? "Latest candidate verified." : state.bestCandidateNeedsVerification ? "Latest candidate still needs verification." : state.lastVerificationPassed ? "Latest verification passed." : state.verificationRequired ? "Verification is still required." : "No verification requirement recorded.";
6205
+ const artifactReferences = this.extractArtifactReferences(state);
6206
+ const pendingNextStep = state.lastStopReason === "complete" ? "Confirm nothing else remains before declaring the task complete." : `Continue the ${state.workflowPhase || "research"} phase without redoing prior work.`;
6207
+ const instructions = compactInstructions || this.buildDefaultCompactInstructions();
5737
6208
  return [
5738
- `Task: ${state.taskName}`,
5739
- `Status: ${state.status}`,
5740
- `Workflow phase: ${state.workflowPhase || "research"}`,
5741
- `Sessions completed: ${state.sessionCount}`,
5742
- `Total cost: $${state.totalCost.toFixed(4)}`,
5743
- ...state.planPath ? [`Plan path: ${state.planPath}`] : [],
5744
- ...state.planWritten ? ["Plan written: yes"] : [],
5745
- ...state.bestCandidatePath ? [
5746
- `Best candidate: ${state.bestCandidatePath}`,
5747
- ...state.bestCandidateReason ? [`Candidate reason: ${state.bestCandidateReason}`] : []
5748
- ] : [],
5749
- ...state.bootstrapContext ? ["", state.bootstrapContext] : [],
6209
+ "Task + Phase",
6210
+ `- Task: ${state.taskName}`,
6211
+ `- Status: ${state.status}`,
6212
+ `- Workflow phase: ${state.workflowPhase || "research"}`,
6213
+ `- Sessions completed: ${state.sessionCount}`,
6214
+ `- Total cost: $${state.totalCost.toFixed(4)}`,
6215
+ "",
6216
+ "Completed Work",
6217
+ ...sessionSummaries ? [sessionSummaries] : ["- No completed session summaries recorded yet."],
6218
+ "",
6219
+ "Changed Files / Candidate Paths",
6220
+ ...candidatePaths.length > 0 ? candidatePaths.map((candidatePath) => `- ${candidatePath}`) : ["- No candidate paths recorded yet."],
6221
+ ...state.planPath ? [`- ${state.workflowVariant === "external" ? "Report path" : "Plan path"}: ${state.planPath}`] : [],
6222
+ ...state.planWritten ? ["- Planning artifact has been written."] : [],
6223
+ ...state.bestCandidateReason ? [`- Best candidate rationale: ${state.bestCandidateReason}`] : [],
6224
+ "",
6225
+ "Verification Status",
6226
+ `- ${verificationSummary}`,
6227
+ ...state.lastError ? [`- Last error: ${state.lastError}`] : [],
6228
+ "",
6229
+ "Pending Next Step",
6230
+ `- ${pendingNextStep}`,
5750
6231
  "",
5751
- "Session history:",
5752
- sessionSummaries,
6232
+ "Artifact / Tool Output References",
6233
+ ...artifactReferences.length > 0 ? artifactReferences.map((reference) => `- ${reference}`) : ["- No offloaded artifacts recorded."],
5753
6234
  "",
5754
- "Last output (truncated):",
5755
- (state.lastOutput || "").slice(0, 1500)
6235
+ "Compaction Instructions",
6236
+ `- ${instructions}`,
6237
+ "",
6238
+ "Last Output (truncated)",
6239
+ (state.lastOutput || "").slice(0, 1800) || "- No final output recorded yet."
5756
6240
  ].join("\n");
5757
6241
  }
5758
6242
  /**
5759
6243
  * Build messages for a session, injecting progress context for continuation sessions.
5760
6244
  * Optionally accepts continuation context for marathon resume scenarios.
5761
6245
  */
5762
- buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds) {
6246
+ async buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds, compactionOptions) {
6247
+ const prepared = await this.prepareSessionContext(
6248
+ originalMessage,
6249
+ state,
6250
+ sessionIndex,
6251
+ maxSessions,
6252
+ localToolNames,
6253
+ continuationContext,
6254
+ workflow,
6255
+ builtinToolIds,
6256
+ compactionOptions
6257
+ );
6258
+ return prepared.messages;
6259
+ }
6260
+ async prepareSessionContext(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds, compactionOptions) {
5763
6261
  const wf = workflow ?? defaultWorkflow;
6262
+ const compactInstructions = compactionOptions?.compactInstructions;
6263
+ const resolvedStrategy = continuationContext?.compact ? "summary_fallback" : this.resolveCompactStrategy(compactionOptions?.compactStrategy, compactionOptions?.model);
6264
+ const requestContextManagement = resolvedStrategy === "provider_native" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 ? {
6265
+ compactStrategy: resolvedStrategy,
6266
+ ...compactInstructions ? { compactInstructions } : {},
6267
+ nativeCompaction: {
6268
+ provider: "anthropic",
6269
+ thresholdTokens: compactionOptions.autoCompactTokenThreshold
6270
+ }
6271
+ } : void 0;
6272
+ const maybeEmitToolDefinitionWarning = async (breakdown) => {
6273
+ if (sessionIndex !== 0) return;
6274
+ if (typeof compactionOptions?.contextLimitTokens !== "number" || compactionOptions.contextLimitTokens <= 0) {
6275
+ return;
6276
+ }
6277
+ if (breakdown.toolDefinitionTokens <= 0) return;
6278
+ const ratio = breakdown.toolDefinitionTokens / compactionOptions.contextLimitTokens;
6279
+ if (ratio < 0.1) return;
6280
+ await this.emitContextNotice(compactionOptions?.onContextNotice, {
6281
+ kind: "tool_definitions_warning",
6282
+ sessionIndex: sessionIndex + 1,
6283
+ model: compactionOptions?.model,
6284
+ message: `Tool definitions consume about ${(ratio * 100).toFixed(1)}% of the ${compactionOptions.contextLimitTokens.toLocaleString()} token context window. Reduce the enabled tool set if context pressure remains high.`,
6285
+ estimatedTokens: breakdown.toolDefinitionTokens,
6286
+ contextLimitTokens: compactionOptions.contextLimitTokens,
6287
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6288
+ reservedOutputTokens: breakdown.reservedOutputTokens
6289
+ });
6290
+ };
6291
+ const buildNativeCompactionEvent = (mode, breakdown) => {
6292
+ if (resolvedStrategy !== "provider_native" || typeof compactionOptions?.autoCompactTokenThreshold !== "number" || compactionOptions.autoCompactTokenThreshold <= 0 || breakdown.estimatedInputTokens < compactionOptions.autoCompactTokenThreshold) {
6293
+ return void 0;
6294
+ }
6295
+ return {
6296
+ mode,
6297
+ strategy: resolvedStrategy,
6298
+ sessionIndex: sessionIndex + 1,
6299
+ model: compactionOptions?.model,
6300
+ estimatedTokens: breakdown.estimatedInputTokens,
6301
+ thresholdTokens: compactionOptions.autoCompactTokenThreshold,
6302
+ contextLimitTokens: compactionOptions.contextLimitTokens,
6303
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6304
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6305
+ beforeTokens: breakdown.estimatedInputTokens,
6306
+ breakdown
6307
+ };
6308
+ };
5764
6309
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
5765
6310
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
5766
6311
  const isDeployWorkflow = wf.name === "deploy";
6312
+ const isExternalTask2 = state.workflowVariant === "external";
6313
+ const requiredArtifactPath = state.planPath;
5767
6314
  const localToolsBlock = localToolNames?.length ? [
5768
6315
  "",
5769
6316
  "--- Local Tools ---",
5770
6317
  `You have access to tools (${localToolNames.join(", ")}) that execute directly on the user's machine.`,
5771
- ...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
+ ] : [
5772
6322
  "Use these tools to inspect the existing repository and make real file edits \u2014 not just code in your response."
5773
6323
  ],
5774
6324
  ...toolGuidanceLines,
5775
- ...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."]
5776
6326
  ].join("\n") : "";
5777
6327
  const builtinToolNames = builtinToolIds?.map((id) => id.replace(/^builtin:/, ""));
5778
6328
  const builtinToolsBlock = builtinToolNames?.length ? [
5779
6329
  "",
5780
6330
  "--- Built-in Tools ---",
5781
6331
  `You have access to built-in tools (${builtinToolNames.join(", ")}) for web search, web scraping, image generation, and other capabilities.`,
5782
- "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."
5783
6333
  ].join("\n") : "";
5784
6334
  const toolsBlock = localToolsBlock + builtinToolsBlock;
5785
- 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") : "";
5786
6340
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
5787
6341
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
5788
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.`;
5789
6343
  if (continuationContext && sessionIndex === 0) {
5790
6344
  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.";
5791
6345
  const userMessage = continuationContext.newUserMessage || defaultContinueMessage;
5792
- if (continuationContext.compact) {
5793
- const summary = this.generateCompactSummary(state);
5794
- const messages2 = [
5795
- {
5796
- role: "system",
5797
- content: `You are continuing a previously completed task. Here is a summary of prior work:
5798
-
5799
- ${summary}
5800
-
5801
- Do NOT redo any of the above work.`
5802
- },
5803
- {
5804
- role: "user",
5805
- content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n")
5806
- }
5807
- ];
5808
- return messages2;
5809
- }
5810
- const messages = [
6346
+ const userContent = [
6347
+ userMessage,
6348
+ phaseBlock,
6349
+ toolsBlock,
6350
+ bootstrapBlock,
6351
+ candidateBlock,
6352
+ "",
6353
+ multiSessionInstruction
6354
+ ].join("\n");
6355
+ const fullHistoryMessages = [
5811
6356
  ...continuationContext.previousMessages,
5812
6357
  {
5813
6358
  role: "system",
@@ -5815,14 +6360,79 @@ Do NOT redo any of the above work.`
5815
6360
  },
5816
6361
  {
5817
6362
  role: "user",
5818
- content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n")
6363
+ content: userContent
5819
6364
  }
5820
6365
  ];
5821
- return messages;
6366
+ const summaryText = this.generateCompactSummary(state, compactInstructions);
6367
+ const breakdown = this.buildContextBudgetBreakdown({
6368
+ historyMessages: continuationContext.previousMessages,
6369
+ currentTurnContent: userContent,
6370
+ localTools: compactionOptions?.localTools,
6371
+ builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
6372
+ summaryText,
6373
+ contextLimitTokens: compactionOptions?.contextLimitTokens
6374
+ });
6375
+ await maybeEmitToolDefinitionWarning(breakdown);
6376
+ if (continuationContext.compact) {
6377
+ return {
6378
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6379
+ state,
6380
+ userContent,
6381
+ sessionIndex,
6382
+ {
6383
+ mode: "forced",
6384
+ strategy: "summary_fallback",
6385
+ model: compactionOptions?.model,
6386
+ beforeTokens: breakdown.estimatedInputTokens,
6387
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6388
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6389
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6390
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6391
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6392
+ breakdown,
6393
+ onContextCompaction: compactionOptions?.onContextCompaction,
6394
+ compactInstructions
6395
+ }
6396
+ ),
6397
+ requestContextManagement
6398
+ };
6399
+ }
6400
+ if (resolvedStrategy === "summary_fallback" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 && breakdown.estimatedInputTokens >= compactionOptions.autoCompactTokenThreshold) {
6401
+ return {
6402
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6403
+ state,
6404
+ userContent,
6405
+ sessionIndex,
6406
+ {
6407
+ mode: "auto",
6408
+ strategy: "summary_fallback",
6409
+ model: compactionOptions?.model,
6410
+ beforeTokens: breakdown.estimatedInputTokens,
6411
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6412
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6413
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6414
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6415
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6416
+ breakdown,
6417
+ onContextCompaction: compactionOptions?.onContextCompaction,
6418
+ compactInstructions
6419
+ }
6420
+ ),
6421
+ requestContextManagement
6422
+ };
6423
+ }
6424
+ return {
6425
+ messages: fullHistoryMessages,
6426
+ requestContextManagement,
6427
+ pendingNativeCompactionEvent: buildNativeCompactionEvent("auto", breakdown)
6428
+ };
5822
6429
  }
5823
6430
  if (sessionIndex === 0) {
5824
6431
  const content2 = [originalMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n");
5825
- return [{ role: "user", content: content2 }];
6432
+ return {
6433
+ messages: [{ role: "user", content: content2 }],
6434
+ requestContextManagement
6435
+ };
5826
6436
  }
5827
6437
  const recentSessions = state.sessions.slice(-5);
5828
6438
  const progressSummary = recentSessions.map(
@@ -5856,10 +6466,49 @@ Do NOT redo any of the above work.`
5856
6466
  ...historyMessages.slice(-MAX_HISTORY_MESSAGES)
5857
6467
  ];
5858
6468
  }
5859
- return [
6469
+ const summaryText = this.generateCompactSummary(state, compactInstructions);
6470
+ const breakdown = this.buildContextBudgetBreakdown({
6471
+ historyMessages,
6472
+ currentTurnContent: continuationContent,
6473
+ localTools: compactionOptions?.localTools,
6474
+ builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
6475
+ summaryText,
6476
+ contextLimitTokens: compactionOptions?.contextLimitTokens
6477
+ });
6478
+ await maybeEmitToolDefinitionWarning(breakdown);
6479
+ const messages = [
5860
6480
  ...historyMessages,
5861
6481
  { role: "user", content: continuationContent }
5862
6482
  ];
6483
+ if (resolvedStrategy === "summary_fallback" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 && breakdown.estimatedInputTokens >= compactionOptions.autoCompactTokenThreshold) {
6484
+ return {
6485
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6486
+ state,
6487
+ continuationContent,
6488
+ sessionIndex,
6489
+ {
6490
+ mode: "auto",
6491
+ strategy: "summary_fallback",
6492
+ model: compactionOptions?.model,
6493
+ beforeTokens: breakdown.estimatedInputTokens,
6494
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6495
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6496
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6497
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6498
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6499
+ breakdown,
6500
+ onContextCompaction: compactionOptions?.onContextCompaction,
6501
+ compactInstructions
6502
+ }
6503
+ ),
6504
+ requestContextManagement
6505
+ };
6506
+ }
6507
+ return {
6508
+ messages,
6509
+ requestContextManagement,
6510
+ pendingNativeCompactionEvent: buildNativeCompactionEvent("auto", breakdown)
6511
+ };
5863
6512
  }
5864
6513
  const recoveryMessage = this.buildStuckTurnRecoveryMessage(state, wf);
5865
6514
  const content = [
@@ -5879,7 +6528,10 @@ Do NOT redo any of the above work.`
5879
6528
  "",
5880
6529
  "Continue where you left off. Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
5881
6530
  ].join("\n");
5882
- return [{ role: "user", content }];
6531
+ return {
6532
+ messages: [{ role: "user", content }],
6533
+ requestContextManagement
6534
+ };
5883
6535
  }
5884
6536
  /**
5885
6537
  * Upsert a record to sync long-task progress to the dashboard.
@@ -5929,6 +6581,8 @@ Do NOT redo any of the above work.`
5929
6581
  }
5930
6582
  }
5931
6583
  };
6584
+ _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX = "You are continuing a long-running task. Here is a compact summary of prior work:";
6585
+ _AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX = "You are continuing a previously completed task. Here is a summary of prior work:";
5932
6586
  /** Stop phrases that indicate the agent considers its task complete. */
5933
6587
  _AgentsEndpoint.STOP_PHRASES = [
5934
6588
  "DONE:",