@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.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 ---",
@@ -3038,7 +3157,11 @@ var designPhase = {
3038
3157
  buildTransitionSummary(_state, _nextPhaseName) {
3039
3158
  return [
3040
3159
  "Automatic phase transition: design \u2192 build.",
3041
- "Requirements understood. Build the game and deploy it using deploy_sandbox with the files parameter."
3160
+ "Requirements understood. Build the game and deploy it using deploy_sandbox.",
3161
+ "",
3162
+ "MANDATORY: Use the `files` parameter for ALL game HTML/JS/CSS.",
3163
+ "code = minimal Express static server (~4 lines), files = game assets.",
3164
+ "NEVER embed HTML inside res.send() template literals."
3042
3165
  ].join("\n");
3043
3166
  },
3044
3167
  interceptToolCall(toolName, _args, _ctx) {
@@ -3098,7 +3221,32 @@ var buildPhase = {
3098
3221
  " 4. If the deploy fails, read the error output, fix the code, and call deploy_sandbox again.",
3099
3222
  " 5. The sandbox is persistent \u2014 subsequent calls reuse the same sandbox.",
3100
3223
  " 6. Load game libraries from CDN in your HTML (e.g., Three.js from unpkg/cdnjs) OR include them in packageJson.",
3101
- " 7. Make the game fullscreen by default (width: 100vw, height: 100vh, no margin/padding on body)."
3224
+ " 7. Make the game fullscreen by default (width: 100vw, height: 100vh, no margin/padding on body).",
3225
+ "",
3226
+ "STARTER TEMPLATES \u2014 Copy and customize based on the game type:",
3227
+ "",
3228
+ "ALL templates use this server code:",
3229
+ ' code: `const express = require("express"); const app = express(); app.use(express.static("public")); app.listen(3000, () => console.log("Game server running on port 3000"));`',
3230
+ ' packageJson: { "dependencies": { "express": "^4.18.2" } }',
3231
+ ' language: "javascript"',
3232
+ "",
3233
+ "THREE.JS (3D games \u2014 racing, flying, FPS, etc.):",
3234
+ " files: {",
3235
+ ' "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>',
3236
+ ' "public/game.js": Import three, create scene/camera/renderer, game loop with requestAnimationFrame',
3237
+ " }",
3238
+ "",
3239
+ "PHASER (2D games \u2014 platformers, shooters, puzzles):",
3240
+ " files: {",
3241
+ ' "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>',
3242
+ ' "public/game.js": Phaser.Game config with scenes, preload/create/update lifecycle',
3243
+ " }",
3244
+ "",
3245
+ "CANVAS 2D (simple games \u2014 pong, snake, breakout):",
3246
+ " files: {",
3247
+ ' "public/index.html": HTML with <canvas id="game" width="800" height="600"></canvas> and <script src="game.js"></script>',
3248
+ ' "public/game.js": Get canvas context, game loop with requestAnimationFrame, draw calls',
3249
+ " }"
3102
3250
  ].join("\n");
3103
3251
  },
3104
3252
  buildToolGuidance(_state) {
@@ -3112,8 +3260,23 @@ var buildPhase = {
3112
3260
  isComplete() {
3113
3261
  return false;
3114
3262
  },
3115
- interceptToolCall(toolName, _args, _ctx) {
3116
- return blockLocalTools(toolName);
3263
+ interceptToolCall(toolName, args, _ctx) {
3264
+ const localBlock = blockLocalTools(toolName);
3265
+ if (localBlock) return localBlock;
3266
+ if (toolName === "deploy_sandbox" && args) {
3267
+ const hasFiles = args.files && typeof args.files === "object" && Object.keys(args.files).length > 0;
3268
+ const codeStr = typeof args.code === "string" ? args.code : "";
3269
+ const embedsHtml = /res\.send\s*\(/.test(codeStr) && /<html|<body|<script/i.test(codeStr);
3270
+ if (!hasFiles && embedsHtml) {
3271
+ return [
3272
+ "Blocked: You are embedding HTML inside res.send() \u2014 this will break due to nested template literals.",
3273
+ "You MUST use the `files` parameter. Put your game HTML/JS/CSS in files and use a minimal Express static server for code.",
3274
+ 'Example: code: `const express = require("express"); const app = express(); app.use(express.static("public")); app.listen(3000);`',
3275
+ 'files: { "public/index.html": "<!DOCTYPE html>...", "public/game.js": "// game code" }'
3276
+ ].join("\n");
3277
+ }
3278
+ }
3279
+ return void 0;
3117
3280
  },
3118
3281
  canAcceptCompletion(_state, trace) {
3119
3282
  return trace.entries.some((entry) => entry.startsWith("deploy_sandbox"));
@@ -4831,8 +4994,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4831
4994
  return `[${toolName} output (${resultStr.length} chars) saved to ${filePath} \u2014 use read_file to retrieve if needed]`;
4832
4995
  }
4833
4996
  getDefaultPlanPath(taskName) {
4834
- const slug = this.sanitizeTaskSlug(taskName || "task");
4835
- return `.runtype/marathons/${slug}/plan.md`;
4997
+ return getDefaultPlanPath(taskName);
4998
+ }
4999
+ getDefaultExternalReportPath(taskName) {
5000
+ return getDefaultExternalReportPath(taskName);
4836
5001
  }
4837
5002
  dirnameOfCandidatePath(candidatePath) {
4838
5003
  const normalized = this.normalizeCandidatePath(candidatePath);
@@ -4938,7 +5103,10 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4938
5103
  }
4939
5104
  sanitizeResumeState(resumeState, taskName) {
4940
5105
  if (!resumeState) return void 0;
4941
- 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;
4942
5110
  const candidatePaths = this.dedupeNormalizedCandidatePaths(resumeState.candidatePaths);
4943
5111
  const recentReadPaths = this.dedupeNormalizedCandidatePaths(resumeState.recentReadPaths);
4944
5112
  const normalizedBestCandidatePath = typeof resumeState.bestCandidatePath === "string" && resumeState.bestCandidatePath.trim() ? this.normalizeCandidatePath(resumeState.bestCandidatePath) : void 0;
@@ -4949,7 +5117,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
4949
5117
  return {
4950
5118
  ...resumeState,
4951
5119
  workflowPhase,
4952
- planPath,
5120
+ planPath: migratedPlanPath,
4953
5121
  planWritten: Boolean(resumeState.planWritten),
4954
5122
  bestCandidatePath,
4955
5123
  bestCandidateReason: bestCandidatePath ? resumeState.bestCandidateReason : void 0,
@@ -5375,13 +5543,14 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5375
5543
  const taskName = typeof options.trackProgress === "string" ? options.trackProgress : options.trackProgress ? `${agent.name} task` : "";
5376
5544
  const resolvedTaskName = taskName || `${agent.name} task`;
5377
5545
  const seededResumeState = this.sanitizeResumeState(options.resumeState, resolvedTaskName);
5546
+ const classifiedVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5378
5547
  const state = {
5379
5548
  agentId: id,
5380
5549
  agentName: agent.name,
5381
5550
  taskName: resolvedTaskName,
5382
5551
  status: "running",
5383
5552
  workflowPhase: seededResumeState?.workflowPhase || workflow.phases[0]?.name || "research",
5384
- planPath: seededResumeState?.planPath || this.getDefaultPlanPath(resolvedTaskName),
5553
+ planPath: seededResumeState?.planPath || (classifiedVariant === "external" ? this.getDefaultExternalReportPath(resolvedTaskName) : this.getDefaultPlanPath(resolvedTaskName)),
5385
5554
  planWritten: seededResumeState?.planWritten || false,
5386
5555
  bestCandidateNeedsVerification: seededResumeState?.bestCandidateNeedsVerification || false,
5387
5556
  bestCandidateVerified: seededResumeState?.bestCandidateVerified || false,
@@ -5404,11 +5573,13 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5404
5573
  ...seededResumeState?.recentReadPaths ? { recentReadPaths: seededResumeState.recentReadPaths } : {},
5405
5574
  ...seededResumeState?.recentActionKeys ? { recentActionKeys: seededResumeState.recentActionKeys } : {}
5406
5575
  };
5407
- state.workflowVariant = seededResumeState?.workflowVariant ?? workflow.classifyVariant?.(options.message);
5576
+ state.workflowVariant = classifiedVariant;
5408
5577
  state.isCreationTask = seededResumeState?.isCreationTask ?? state.workflowVariant === "create";
5409
5578
  this.updateWorkflowPhase(state, this.createEmptyToolTrace(), workflow);
5410
5579
  let recordId;
5411
5580
  const localToolNames = options.localTools ? Object.keys(options.localTools) : void 0;
5581
+ const builtinToolSchemas = await this.loadBuiltinToolSchemas(options.toolIds);
5582
+ const compactInstructions = this.resolveCompactInstructions(options.compactInstructions, agent);
5412
5583
  if (!options.previousMessages) {
5413
5584
  if (workflow.generateBootstrapContext) {
5414
5585
  state.bootstrapContext = await workflow.generateBootstrapContext(
@@ -5420,7 +5591,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5420
5591
  state.bootstrapContext = await this.generateBootstrapDiscoveryContext(
5421
5592
  options.message,
5422
5593
  options.localTools,
5423
- state.isCreationTask
5594
+ state.isCreationTask || state.workflowVariant === "external"
5424
5595
  );
5425
5596
  }
5426
5597
  const bootstrapCandidate = this.extractBestCandidateFromBootstrapContext(state.bootstrapContext);
@@ -5442,7 +5613,7 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5442
5613
  if (session === 0 && !options.previousMessages) {
5443
5614
  state.originalMessage = options.message;
5444
5615
  }
5445
- const messages = this.buildSessionMessages(
5616
+ const preparedSession = await this.prepareSessionContext(
5446
5617
  options.message,
5447
5618
  state,
5448
5619
  session,
@@ -5450,61 +5621,93 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5450
5621
  localToolNames,
5451
5622
  continuationContext,
5452
5623
  workflow,
5453
- options.toolIds
5624
+ options.toolIds,
5625
+ {
5626
+ autoCompactTokenThreshold: options.autoCompactTokenThreshold,
5627
+ contextLimitTokens: options.contextLimitTokens,
5628
+ model: options.model,
5629
+ compactStrategy: options.compactStrategy,
5630
+ compactInstructions,
5631
+ localTools: options.localTools,
5632
+ builtinToolSchemas,
5633
+ onContextCompaction: options.onContextCompaction,
5634
+ onContextNotice: options.onContextNotice
5635
+ }
5454
5636
  );
5637
+ const {
5638
+ messages,
5639
+ requestContextManagement,
5640
+ pendingNativeCompactionEvent
5641
+ } = preparedSession;
5455
5642
  let sessionResult;
5456
5643
  const sessionData = {
5457
5644
  messages,
5458
5645
  debugMode: options.debugMode,
5459
5646
  model: options.model,
5460
- ...options.toolIds?.length ? { tools: { toolIds: options.toolIds } } : {}
5647
+ ...options.toolIds?.length ? { tools: { toolIds: options.toolIds } } : {},
5648
+ ...requestContextManagement ? { contextManagement: requestContextManagement } : {}
5461
5649
  };
5462
5650
  let sessionToolMessages = [];
5463
- if (useStream && options.localTools) {
5464
- const localToolResult = await this.executeWithLocalTools(
5465
- id,
5466
- sessionData,
5467
- sessionLocalTools || options.localTools,
5468
- sessionCallbacks,
5469
- {
5470
- onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow)
5471
- },
5472
- state.taskName
5473
- );
5474
- if (!localToolResult) {
5475
- throw new Error("Agent stream ended without a complete event");
5651
+ if (pendingNativeCompactionEvent) {
5652
+ await this.emitContextCompactionEvent(options.onContextCompaction, {
5653
+ phase: "start",
5654
+ ...pendingNativeCompactionEvent
5655
+ });
5656
+ }
5657
+ try {
5658
+ if (useStream && options.localTools) {
5659
+ const localToolResult = await this.executeWithLocalTools(
5660
+ id,
5661
+ sessionData,
5662
+ sessionLocalTools || options.localTools,
5663
+ sessionCallbacks,
5664
+ {
5665
+ onLocalToolResult: this.createLocalToolLoopGuard(state, sessionTrace, workflow)
5666
+ },
5667
+ state.taskName
5668
+ );
5669
+ if (!localToolResult) {
5670
+ throw new Error("Agent stream ended without a complete event");
5671
+ }
5672
+ const { completeEvent, toolMessages: captured } = localToolResult;
5673
+ sessionToolMessages = captured;
5674
+ sessionResult = {
5675
+ success: completeEvent.success,
5676
+ result: completeEvent.finalOutput || "",
5677
+ iterations: completeEvent.iterations,
5678
+ totalCost: completeEvent.totalCost || 0,
5679
+ totalTokens: completeEvent.totalTokens,
5680
+ stopReason: completeEvent.stopReason,
5681
+ error: completeEvent.error
5682
+ };
5683
+ } else if (useStream && options.streamCallbacks) {
5684
+ const completeEvent = await this.executeWithCallbacks(
5685
+ id,
5686
+ sessionData,
5687
+ sessionCallbacks || options.streamCallbacks
5688
+ );
5689
+ if (!completeEvent) {
5690
+ throw new Error("Agent stream ended without a complete event");
5691
+ }
5692
+ sessionResult = {
5693
+ success: completeEvent.success,
5694
+ result: completeEvent.finalOutput || "",
5695
+ iterations: completeEvent.iterations,
5696
+ totalCost: completeEvent.totalCost || 0,
5697
+ totalTokens: completeEvent.totalTokens,
5698
+ stopReason: completeEvent.stopReason,
5699
+ error: completeEvent.error
5700
+ };
5701
+ } else {
5702
+ sessionResult = await this.execute(id, sessionData);
5476
5703
  }
5477
- const { completeEvent, toolMessages: captured } = localToolResult;
5478
- sessionToolMessages = captured;
5479
- sessionResult = {
5480
- success: completeEvent.success,
5481
- result: completeEvent.finalOutput || "",
5482
- iterations: completeEvent.iterations,
5483
- totalCost: completeEvent.totalCost || 0,
5484
- totalTokens: completeEvent.totalTokens,
5485
- stopReason: completeEvent.stopReason,
5486
- error: completeEvent.error
5487
- };
5488
- } else if (useStream && options.streamCallbacks) {
5489
- const completeEvent = await this.executeWithCallbacks(
5490
- id,
5491
- sessionData,
5492
- sessionCallbacks || options.streamCallbacks
5493
- );
5494
- if (!completeEvent) {
5495
- throw new Error("Agent stream ended without a complete event");
5704
+ } finally {
5705
+ if (pendingNativeCompactionEvent) {
5706
+ await this.emitContextCompactionEvent(options.onContextCompaction, {
5707
+ phase: "complete",
5708
+ ...pendingNativeCompactionEvent
5709
+ });
5496
5710
  }
5497
- sessionResult = {
5498
- success: completeEvent.success,
5499
- result: completeEvent.finalOutput || "",
5500
- iterations: completeEvent.iterations,
5501
- totalCost: completeEvent.totalCost || 0,
5502
- totalTokens: completeEvent.totalTokens,
5503
- stopReason: completeEvent.stopReason,
5504
- error: completeEvent.error
5505
- };
5506
- } else {
5507
- sessionResult = await this.execute(id, sessionData);
5508
5711
  }
5509
5712
  const toolTraceSummary = this.buildToolTraceSummary(sessionTrace);
5510
5713
  const effectiveSessionOutput = this.buildEffectiveSessionOutput(
@@ -5591,7 +5794,9 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5591
5794
  }
5592
5795
  }
5593
5796
  if (!state.messages) state.messages = [];
5594
- if (state.messages.length > 0 && messages.length > state.messages.length) {
5797
+ if (this.isCompactHistoryMessageSet(messages)) {
5798
+ state.messages = [...messages];
5799
+ } else if (state.messages.length > 0 && messages.length > state.messages.length) {
5595
5800
  const newMessages = messages.slice(state.messages.length);
5596
5801
  state.messages.push(...newMessages);
5597
5802
  } else {
@@ -5612,13 +5817,15 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5612
5817
  if (state.sessions.length > 50) {
5613
5818
  state.sessions = state.sessions.slice(-50);
5614
5819
  }
5615
- 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) {
5616
5823
  state.status = "complete";
5617
5824
  } else if (sessionResult.stopReason === "error") {
5618
5825
  state.status = "error";
5619
5826
  } else if (sessionResult.stopReason === "max_cost") {
5620
5827
  state.status = "budget_exceeded";
5621
- } else if (this.canAcceptTaskCompletion(sessionResult.result, state, sessionTrace, workflow)) {
5828
+ } else if (acceptedTaskCompletion) {
5622
5829
  state.status = "complete";
5623
5830
  } else if (maxCost && state.totalCost >= maxCost) {
5624
5831
  state.status = "budget_exceeded";
@@ -5656,88 +5863,426 @@ var _AgentsEndpoint = class _AgentsEndpoint {
5656
5863
  const upper = output.toUpperCase();
5657
5864
  return _AgentsEndpoint.STOP_PHRASES.some((phrase) => upper.includes(phrase.toUpperCase()));
5658
5865
  }
5866
+ resolveReservedOutputTokens(contextLimitTokens) {
5867
+ if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0) {
5868
+ return void 0;
5869
+ }
5870
+ return Math.max(8e3, Math.min(64e3, Math.floor(contextLimitTokens * 0.15)));
5871
+ }
5872
+ resolveEffectiveInputBudgetTokens(contextLimitTokens) {
5873
+ if (typeof contextLimitTokens !== "number" || !Number.isFinite(contextLimitTokens) || contextLimitTokens <= 0) {
5874
+ return void 0;
5875
+ }
5876
+ const reservedOutputTokens = this.resolveReservedOutputTokens(contextLimitTokens) ?? 0;
5877
+ return Math.max(1, contextLimitTokens - reservedOutputTokens);
5878
+ }
5879
+ resolveCompactStrategy(strategy, modelId) {
5880
+ if (strategy === "summary_fallback") {
5881
+ return "summary_fallback";
5882
+ }
5883
+ const normalizedModelId = modelId?.trim().toLowerCase() ?? "";
5884
+ const supportsAnthropicNativeCompaction = normalizedModelId.includes("claude") || normalizedModelId.includes("anthropic");
5885
+ if (strategy === "provider_native") {
5886
+ return supportsAnthropicNativeCompaction ? "provider_native" : "summary_fallback";
5887
+ }
5888
+ return supportsAnthropicNativeCompaction ? "provider_native" : "summary_fallback";
5889
+ }
5890
+ estimateTextTokens(text) {
5891
+ if (!text) return 0;
5892
+ return Math.ceil(text.length / 4);
5893
+ }
5894
+ estimateUnknownTokens(value) {
5895
+ if (typeof value === "string") return this.estimateTextTokens(value);
5896
+ try {
5897
+ return this.estimateTextTokens(JSON.stringify(value));
5898
+ } catch {
5899
+ return 0;
5900
+ }
5901
+ }
5902
+ estimateMessageContentTokens(content) {
5903
+ if (typeof content === "string") return this.estimateTextTokens(content);
5904
+ return content.reduce((total, part) => {
5905
+ if (typeof part.text === "string") total += this.estimateTextTokens(part.text);
5906
+ if (typeof part.image === "string") total += 85;
5907
+ if (typeof part.data === "string") {
5908
+ total += Math.min(200, this.estimateTextTokens(part.data));
5909
+ }
5910
+ return total + 4;
5911
+ }, 0);
5912
+ }
5913
+ estimateToolCallTokens(toolCalls) {
5914
+ if (!toolCalls || toolCalls.length === 0) return 0;
5915
+ return toolCalls.reduce(
5916
+ (sum, toolCall) => sum + 12 + this.estimateTextTokens(toolCall.toolName) + this.estimateUnknownTokens(toolCall.args),
5917
+ 0
5918
+ );
5919
+ }
5920
+ estimateToolResultTokens(toolResults) {
5921
+ if (!toolResults || toolResults.length === 0) return 0;
5922
+ return toolResults.reduce(
5923
+ (sum, toolResult) => sum + 12 + this.estimateTextTokens(toolResult.toolName) + this.estimateUnknownTokens(toolResult.result),
5924
+ 0
5925
+ );
5926
+ }
5927
+ estimateMessageTokens(message) {
5928
+ return 6 + this.estimateMessageContentTokens(message.content) + this.estimateToolCallTokens(message.toolCalls) + this.estimateToolResultTokens(message.toolResults);
5929
+ }
5930
+ estimateConversationTokens(messages) {
5931
+ return messages.reduce((sum, message) => sum + this.estimateMessageTokens(message), 0);
5932
+ }
5933
+ estimateConversationBreakdown(messages) {
5934
+ let historyTokens = 0;
5935
+ let toolOutputTokens = 0;
5936
+ for (const message of messages) {
5937
+ const contentTokens = this.estimateMessageContentTokens(message.content);
5938
+ const toolCallTokens = this.estimateToolCallTokens(message.toolCalls);
5939
+ const toolResultTokens = this.estimateToolResultTokens(message.toolResults);
5940
+ const messageTotal = 6 + contentTokens + toolCallTokens + toolResultTokens;
5941
+ if (message.role === "tool") {
5942
+ toolOutputTokens += messageTotal;
5943
+ } else {
5944
+ historyTokens += 6 + contentTokens + toolCallTokens;
5945
+ toolOutputTokens += toolResultTokens;
5946
+ }
5947
+ }
5948
+ return {
5949
+ historyTokens,
5950
+ toolOutputTokens,
5951
+ estimatedInputTokens: historyTokens + toolOutputTokens
5952
+ };
5953
+ }
5954
+ estimateToolDefinitionTokens(localTools, builtinToolSchemas) {
5955
+ const localToolDefinitions = Object.entries(localTools || {}).map(([name, definition]) => ({
5956
+ name,
5957
+ description: definition.description,
5958
+ parametersSchema: definition.parametersSchema
5959
+ }));
5960
+ const payload = [...localToolDefinitions, ...builtinToolSchemas];
5961
+ if (payload.length === 0) return 0;
5962
+ return this.estimateUnknownTokens(payload);
5963
+ }
5964
+ async loadBuiltinToolSchemas(toolIds) {
5965
+ if (!toolIds || toolIds.length === 0) return [];
5966
+ try {
5967
+ const schemas = await this.client.get("/tools/schema", { toolIds });
5968
+ return Array.isArray(schemas) ? schemas : [];
5969
+ } catch {
5970
+ return [];
5971
+ }
5972
+ }
5973
+ buildDefaultCompactInstructions() {
5974
+ return [
5975
+ "Preserve changed files or best candidate paths.",
5976
+ "Preserve verification or test results.",
5977
+ "Preserve the current workflow phase and remaining work.",
5978
+ "Preserve unresolved blockers or follow-up risks.",
5979
+ "Preserve artifact references and offloaded tool outputs."
5980
+ ].join(" ");
5981
+ }
5982
+ resolveCompactInstructions(optionInstructions, agent) {
5983
+ if (typeof optionInstructions === "string" && optionInstructions.trim()) {
5984
+ return optionInstructions.trim();
5985
+ }
5986
+ const config = agent.config;
5987
+ if (!config || typeof config !== "object") {
5988
+ return void 0;
5989
+ }
5990
+ const compactInstructions = config.compactInstructions;
5991
+ return typeof compactInstructions === "string" && compactInstructions.trim() ? compactInstructions.trim() : void 0;
5992
+ }
5993
+ extractArtifactReferences(state) {
5994
+ const references = /* @__PURE__ */ new Set();
5995
+ const offloadPrefix = "[Output saved to ";
5996
+ for (const message of state.messages ?? []) {
5997
+ if (!message.toolResults) continue;
5998
+ for (const toolResult of message.toolResults) {
5999
+ if (typeof toolResult.result !== "string") continue;
6000
+ let startIndex = 0;
6001
+ while (startIndex < toolResult.result.length) {
6002
+ const prefixIndex = toolResult.result.indexOf(offloadPrefix, startIndex);
6003
+ if (prefixIndex === -1) break;
6004
+ const pathStart = prefixIndex + offloadPrefix.length;
6005
+ const closingBracket = toolResult.result.indexOf("]", pathStart);
6006
+ if (closingBracket === -1) break;
6007
+ const bracketContent = toolResult.result.slice(pathStart, closingBracket);
6008
+ const separatorIndex = bracketContent.indexOf(" \u2014 ");
6009
+ const pathText = separatorIndex === -1 ? bracketContent.trim() : bracketContent.slice(0, separatorIndex).trim();
6010
+ if (pathText) {
6011
+ references.add(pathText);
6012
+ }
6013
+ startIndex = closingBracket + 1;
6014
+ }
6015
+ }
6016
+ }
6017
+ if (state.planPath) {
6018
+ references.add(state.planPath);
6019
+ }
6020
+ return [...references].slice(0, 8);
6021
+ }
6022
+ buildContextBudgetBreakdown(details) {
6023
+ const conversationBreakdown = this.estimateConversationBreakdown(details.historyMessages);
6024
+ const currentTurnTokens = this.estimateTextTokens(details.currentTurnContent);
6025
+ const toolDefinitionTokens = this.estimateToolDefinitionTokens(
6026
+ details.localTools,
6027
+ details.builtinToolSchemas
6028
+ );
6029
+ const reservedOutputTokens = this.resolveReservedOutputTokens(details.contextLimitTokens);
6030
+ const effectiveInputBudgetTokens = this.resolveEffectiveInputBudgetTokens(details.contextLimitTokens);
6031
+ const summaryTokens = details.summaryText ? this.estimateTextTokens(
6032
+ `${_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX}
6033
+
6034
+ ${details.summaryText}
6035
+
6036
+ Do NOT redo any of the above work.`
6037
+ ) : void 0;
6038
+ return {
6039
+ historyTokens: conversationBreakdown.historyTokens,
6040
+ toolOutputTokens: conversationBreakdown.toolOutputTokens,
6041
+ currentTurnTokens,
6042
+ toolDefinitionTokens,
6043
+ ...summaryTokens ? { summaryTokens } : {},
6044
+ ...reservedOutputTokens ? { reservedOutputTokens } : {},
6045
+ ...effectiveInputBudgetTokens ? { effectiveInputBudgetTokens } : {},
6046
+ estimatedInputTokens: conversationBreakdown.estimatedInputTokens + currentTurnTokens + toolDefinitionTokens
6047
+ };
6048
+ }
6049
+ async emitContextCompactionEvent(onContextCompaction, event) {
6050
+ if (!onContextCompaction) return;
6051
+ try {
6052
+ await onContextCompaction(event);
6053
+ } catch {
6054
+ }
6055
+ }
6056
+ async emitContextNotice(onContextNotice, event) {
6057
+ if (!onContextNotice) return;
6058
+ try {
6059
+ await onContextNotice(event);
6060
+ } catch {
6061
+ }
6062
+ }
6063
+ async buildCompactHistoryMessagesWithLifecycle(state, userContent, sessionIndex, details) {
6064
+ const baseEvent = {
6065
+ mode: details.mode,
6066
+ strategy: details.strategy,
6067
+ sessionIndex: sessionIndex + 1,
6068
+ model: details.model,
6069
+ estimatedTokens: details.beforeTokens,
6070
+ thresholdTokens: details.thresholdTokens,
6071
+ contextLimitTokens: details.contextLimitTokens,
6072
+ effectiveInputBudgetTokens: details.effectiveInputBudgetTokens,
6073
+ reservedOutputTokens: details.reservedOutputTokens,
6074
+ beforeTokens: details.beforeTokens,
6075
+ afterTokens: details.afterTokens,
6076
+ breakdown: details.breakdown
6077
+ };
6078
+ await this.emitContextCompactionEvent(details.onContextCompaction, {
6079
+ phase: "start",
6080
+ ...baseEvent
6081
+ });
6082
+ const compactMessages = this.buildCompactHistoryMessages(
6083
+ state,
6084
+ userContent,
6085
+ details.compactInstructions,
6086
+ details.mode
6087
+ );
6088
+ await this.emitContextCompactionEvent(details.onContextCompaction, {
6089
+ phase: "complete",
6090
+ ...baseEvent
6091
+ });
6092
+ return compactMessages;
6093
+ }
6094
+ buildCompactHistoryMessages(state, userContent, compactInstructions, mode = "auto") {
6095
+ const summary = this.generateCompactSummary(state, compactInstructions);
6096
+ const prefix = mode === "forced" ? _AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX : _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX;
6097
+ return [
6098
+ {
6099
+ role: "system",
6100
+ content: `${prefix}
6101
+
6102
+ ${summary}
6103
+
6104
+ Do NOT redo any of the above work.`
6105
+ },
6106
+ {
6107
+ role: "user",
6108
+ content: userContent
6109
+ }
6110
+ ];
6111
+ }
6112
+ isCompactHistoryMessageSet(messages) {
6113
+ if (messages.length === 0) return false;
6114
+ const firstMessage = messages[0];
6115
+ return firstMessage?.role === "system" && typeof firstMessage.content === "string" && (firstMessage.content.startsWith(_AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX) || firstMessage.content.startsWith(_AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX));
6116
+ }
5659
6117
  /**
5660
6118
  * Generate a compact summary of prior work for continuation context.
5661
6119
  * Used when compact mode is enabled to keep token usage low.
5662
6120
  */
5663
- generateCompactSummary(state) {
5664
- const sessionSummaries = (state.sessions ?? []).map(
5665
- (s) => `- Session ${s.index}: ${s.stopReason} ($${s.cost.toFixed(4)}) -- ${s.outputPreview.slice(0, 100)}`
6121
+ generateCompactSummary(state, compactInstructions) {
6122
+ const recentSessions = (state.sessions ?? []).slice(-5);
6123
+ const sessionSummaries = recentSessions.map(
6124
+ (session) => `- Session ${session.index}: ${session.stopReason} ($${session.cost.toFixed(4)}) ${session.outputPreview.slice(0, 160)}`
5666
6125
  ).join("\n");
6126
+ const candidatePaths = Array.from(
6127
+ new Set(
6128
+ [
6129
+ ...state.bestCandidatePath ? [state.bestCandidatePath] : [],
6130
+ ...state.candidatePaths ?? []
6131
+ ].filter(Boolean)
6132
+ )
6133
+ ).slice(0, 8);
6134
+ 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.";
6135
+ const artifactReferences = this.extractArtifactReferences(state);
6136
+ const pendingNextStep = state.lastStopReason === "complete" ? "Confirm nothing else remains before declaring the task complete." : `Continue the ${state.workflowPhase || "research"} phase without redoing prior work.`;
6137
+ const instructions = compactInstructions || this.buildDefaultCompactInstructions();
5667
6138
  return [
5668
- `Task: ${state.taskName}`,
5669
- `Status: ${state.status}`,
5670
- `Workflow phase: ${state.workflowPhase || "research"}`,
5671
- `Sessions completed: ${state.sessionCount}`,
5672
- `Total cost: $${state.totalCost.toFixed(4)}`,
5673
- ...state.planPath ? [`Plan path: ${state.planPath}`] : [],
5674
- ...state.planWritten ? ["Plan written: yes"] : [],
5675
- ...state.bestCandidatePath ? [
5676
- `Best candidate: ${state.bestCandidatePath}`,
5677
- ...state.bestCandidateReason ? [`Candidate reason: ${state.bestCandidateReason}`] : []
5678
- ] : [],
5679
- ...state.bootstrapContext ? ["", state.bootstrapContext] : [],
6139
+ "Task + Phase",
6140
+ `- Task: ${state.taskName}`,
6141
+ `- Status: ${state.status}`,
6142
+ `- Workflow phase: ${state.workflowPhase || "research"}`,
6143
+ `- Sessions completed: ${state.sessionCount}`,
6144
+ `- Total cost: $${state.totalCost.toFixed(4)}`,
6145
+ "",
6146
+ "Completed Work",
6147
+ ...sessionSummaries ? [sessionSummaries] : ["- No completed session summaries recorded yet."],
6148
+ "",
6149
+ "Changed Files / Candidate Paths",
6150
+ ...candidatePaths.length > 0 ? candidatePaths.map((candidatePath) => `- ${candidatePath}`) : ["- No candidate paths recorded yet."],
6151
+ ...state.planPath ? [`- ${state.workflowVariant === "external" ? "Report path" : "Plan path"}: ${state.planPath}`] : [],
6152
+ ...state.planWritten ? ["- Planning artifact has been written."] : [],
6153
+ ...state.bestCandidateReason ? [`- Best candidate rationale: ${state.bestCandidateReason}`] : [],
6154
+ "",
6155
+ "Verification Status",
6156
+ `- ${verificationSummary}`,
6157
+ ...state.lastError ? [`- Last error: ${state.lastError}`] : [],
6158
+ "",
6159
+ "Pending Next Step",
6160
+ `- ${pendingNextStep}`,
5680
6161
  "",
5681
- "Session history:",
5682
- sessionSummaries,
6162
+ "Artifact / Tool Output References",
6163
+ ...artifactReferences.length > 0 ? artifactReferences.map((reference) => `- ${reference}`) : ["- No offloaded artifacts recorded."],
5683
6164
  "",
5684
- "Last output (truncated):",
5685
- (state.lastOutput || "").slice(0, 1500)
6165
+ "Compaction Instructions",
6166
+ `- ${instructions}`,
6167
+ "",
6168
+ "Last Output (truncated)",
6169
+ (state.lastOutput || "").slice(0, 1800) || "- No final output recorded yet."
5686
6170
  ].join("\n");
5687
6171
  }
5688
6172
  /**
5689
6173
  * Build messages for a session, injecting progress context for continuation sessions.
5690
6174
  * Optionally accepts continuation context for marathon resume scenarios.
5691
6175
  */
5692
- buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds) {
6176
+ async buildSessionMessages(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds, compactionOptions) {
6177
+ const prepared = await this.prepareSessionContext(
6178
+ originalMessage,
6179
+ state,
6180
+ sessionIndex,
6181
+ maxSessions,
6182
+ localToolNames,
6183
+ continuationContext,
6184
+ workflow,
6185
+ builtinToolIds,
6186
+ compactionOptions
6187
+ );
6188
+ return prepared.messages;
6189
+ }
6190
+ async prepareSessionContext(originalMessage, state, sessionIndex, maxSessions, localToolNames, continuationContext, workflow, builtinToolIds, compactionOptions) {
5693
6191
  const wf = workflow ?? defaultWorkflow;
6192
+ const compactInstructions = compactionOptions?.compactInstructions;
6193
+ const resolvedStrategy = continuationContext?.compact ? "summary_fallback" : this.resolveCompactStrategy(compactionOptions?.compactStrategy, compactionOptions?.model);
6194
+ const requestContextManagement = resolvedStrategy === "provider_native" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 ? {
6195
+ compactStrategy: resolvedStrategy,
6196
+ ...compactInstructions ? { compactInstructions } : {},
6197
+ nativeCompaction: {
6198
+ provider: "anthropic",
6199
+ thresholdTokens: compactionOptions.autoCompactTokenThreshold
6200
+ }
6201
+ } : void 0;
6202
+ const maybeEmitToolDefinitionWarning = async (breakdown) => {
6203
+ if (sessionIndex !== 0) return;
6204
+ if (typeof compactionOptions?.contextLimitTokens !== "number" || compactionOptions.contextLimitTokens <= 0) {
6205
+ return;
6206
+ }
6207
+ if (breakdown.toolDefinitionTokens <= 0) return;
6208
+ const ratio = breakdown.toolDefinitionTokens / compactionOptions.contextLimitTokens;
6209
+ if (ratio < 0.1) return;
6210
+ await this.emitContextNotice(compactionOptions?.onContextNotice, {
6211
+ kind: "tool_definitions_warning",
6212
+ sessionIndex: sessionIndex + 1,
6213
+ model: compactionOptions?.model,
6214
+ 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.`,
6215
+ estimatedTokens: breakdown.toolDefinitionTokens,
6216
+ contextLimitTokens: compactionOptions.contextLimitTokens,
6217
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6218
+ reservedOutputTokens: breakdown.reservedOutputTokens
6219
+ });
6220
+ };
6221
+ const buildNativeCompactionEvent = (mode, breakdown) => {
6222
+ if (resolvedStrategy !== "provider_native" || typeof compactionOptions?.autoCompactTokenThreshold !== "number" || compactionOptions.autoCompactTokenThreshold <= 0 || breakdown.estimatedInputTokens < compactionOptions.autoCompactTokenThreshold) {
6223
+ return void 0;
6224
+ }
6225
+ return {
6226
+ mode,
6227
+ strategy: resolvedStrategy,
6228
+ sessionIndex: sessionIndex + 1,
6229
+ model: compactionOptions?.model,
6230
+ estimatedTokens: breakdown.estimatedInputTokens,
6231
+ thresholdTokens: compactionOptions.autoCompactTokenThreshold,
6232
+ contextLimitTokens: compactionOptions.contextLimitTokens,
6233
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6234
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6235
+ beforeTokens: breakdown.estimatedInputTokens,
6236
+ breakdown
6237
+ };
6238
+ };
5694
6239
  const currentPhase = wf.phases.find((p) => p.name === state.workflowPhase);
5695
6240
  const toolGuidanceLines = currentPhase?.buildToolGuidance(state) ?? [];
5696
6241
  const isDeployWorkflow = wf.name === "deploy";
6242
+ const isExternalTask2 = state.workflowVariant === "external";
6243
+ const requiredArtifactPath = state.planPath;
5697
6244
  const localToolsBlock = localToolNames?.length ? [
5698
6245
  "",
5699
6246
  "--- Local Tools ---",
5700
6247
  `You have access to tools (${localToolNames.join(", ")}) that execute directly on the user's machine.`,
5701
- ...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
+ ] : [
5702
6252
  "Use these tools to inspect the existing repository and make real file edits \u2014 not just code in your response."
5703
6253
  ],
5704
6254
  ...toolGuidanceLines,
5705
- ...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."]
5706
6256
  ].join("\n") : "";
5707
6257
  const builtinToolNames = builtinToolIds?.map((id) => id.replace(/^builtin:/, ""));
5708
6258
  const builtinToolsBlock = builtinToolNames?.length ? [
5709
6259
  "",
5710
6260
  "--- Built-in Tools ---",
5711
6261
  `You have access to built-in tools (${builtinToolNames.join(", ")}) for web search, web scraping, image generation, and other capabilities.`,
5712
- "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."
5713
6263
  ].join("\n") : "";
5714
6264
  const toolsBlock = localToolsBlock + builtinToolsBlock;
5715
- 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") : "";
5716
6270
  const phaseBlock = ["", this.buildPhaseInstructions(state, wf)].join("\n");
5717
6271
  const candidateBlock = wf.buildCandidateBlock?.(state) ?? "";
5718
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.`;
5719
6273
  if (continuationContext && sessionIndex === 0) {
5720
6274
  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.";
5721
6275
  const userMessage = continuationContext.newUserMessage || defaultContinueMessage;
5722
- if (continuationContext.compact) {
5723
- const summary = this.generateCompactSummary(state);
5724
- const messages2 = [
5725
- {
5726
- role: "system",
5727
- content: `You are continuing a previously completed task. Here is a summary of prior work:
5728
-
5729
- ${summary}
5730
-
5731
- Do NOT redo any of the above work.`
5732
- },
5733
- {
5734
- role: "user",
5735
- content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n")
5736
- }
5737
- ];
5738
- return messages2;
5739
- }
5740
- const messages = [
6276
+ const userContent = [
6277
+ userMessage,
6278
+ phaseBlock,
6279
+ toolsBlock,
6280
+ bootstrapBlock,
6281
+ candidateBlock,
6282
+ "",
6283
+ multiSessionInstruction
6284
+ ].join("\n");
6285
+ const fullHistoryMessages = [
5741
6286
  ...continuationContext.previousMessages,
5742
6287
  {
5743
6288
  role: "system",
@@ -5745,14 +6290,79 @@ Do NOT redo any of the above work.`
5745
6290
  },
5746
6291
  {
5747
6292
  role: "user",
5748
- content: [userMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n")
6293
+ content: userContent
5749
6294
  }
5750
6295
  ];
5751
- return messages;
6296
+ const summaryText = this.generateCompactSummary(state, compactInstructions);
6297
+ const breakdown = this.buildContextBudgetBreakdown({
6298
+ historyMessages: continuationContext.previousMessages,
6299
+ currentTurnContent: userContent,
6300
+ localTools: compactionOptions?.localTools,
6301
+ builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
6302
+ summaryText,
6303
+ contextLimitTokens: compactionOptions?.contextLimitTokens
6304
+ });
6305
+ await maybeEmitToolDefinitionWarning(breakdown);
6306
+ if (continuationContext.compact) {
6307
+ return {
6308
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6309
+ state,
6310
+ userContent,
6311
+ sessionIndex,
6312
+ {
6313
+ mode: "forced",
6314
+ strategy: "summary_fallback",
6315
+ model: compactionOptions?.model,
6316
+ beforeTokens: breakdown.estimatedInputTokens,
6317
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6318
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6319
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6320
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6321
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6322
+ breakdown,
6323
+ onContextCompaction: compactionOptions?.onContextCompaction,
6324
+ compactInstructions
6325
+ }
6326
+ ),
6327
+ requestContextManagement
6328
+ };
6329
+ }
6330
+ if (resolvedStrategy === "summary_fallback" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 && breakdown.estimatedInputTokens >= compactionOptions.autoCompactTokenThreshold) {
6331
+ return {
6332
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6333
+ state,
6334
+ userContent,
6335
+ sessionIndex,
6336
+ {
6337
+ mode: "auto",
6338
+ strategy: "summary_fallback",
6339
+ model: compactionOptions?.model,
6340
+ beforeTokens: breakdown.estimatedInputTokens,
6341
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6342
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6343
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6344
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6345
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6346
+ breakdown,
6347
+ onContextCompaction: compactionOptions?.onContextCompaction,
6348
+ compactInstructions
6349
+ }
6350
+ ),
6351
+ requestContextManagement
6352
+ };
6353
+ }
6354
+ return {
6355
+ messages: fullHistoryMessages,
6356
+ requestContextManagement,
6357
+ pendingNativeCompactionEvent: buildNativeCompactionEvent("auto", breakdown)
6358
+ };
5752
6359
  }
5753
6360
  if (sessionIndex === 0) {
5754
6361
  const content2 = [originalMessage, phaseBlock, toolsBlock, bootstrapBlock, candidateBlock, "", multiSessionInstruction].join("\n");
5755
- return [{ role: "user", content: content2 }];
6362
+ return {
6363
+ messages: [{ role: "user", content: content2 }],
6364
+ requestContextManagement
6365
+ };
5756
6366
  }
5757
6367
  const recentSessions = state.sessions.slice(-5);
5758
6368
  const progressSummary = recentSessions.map(
@@ -5786,10 +6396,49 @@ Do NOT redo any of the above work.`
5786
6396
  ...historyMessages.slice(-MAX_HISTORY_MESSAGES)
5787
6397
  ];
5788
6398
  }
5789
- return [
6399
+ const summaryText = this.generateCompactSummary(state, compactInstructions);
6400
+ const breakdown = this.buildContextBudgetBreakdown({
6401
+ historyMessages,
6402
+ currentTurnContent: continuationContent,
6403
+ localTools: compactionOptions?.localTools,
6404
+ builtinToolSchemas: compactionOptions?.builtinToolSchemas || [],
6405
+ summaryText,
6406
+ contextLimitTokens: compactionOptions?.contextLimitTokens
6407
+ });
6408
+ await maybeEmitToolDefinitionWarning(breakdown);
6409
+ const messages = [
5790
6410
  ...historyMessages,
5791
6411
  { role: "user", content: continuationContent }
5792
6412
  ];
6413
+ if (resolvedStrategy === "summary_fallback" && typeof compactionOptions?.autoCompactTokenThreshold === "number" && compactionOptions.autoCompactTokenThreshold > 0 && breakdown.estimatedInputTokens >= compactionOptions.autoCompactTokenThreshold) {
6414
+ return {
6415
+ messages: await this.buildCompactHistoryMessagesWithLifecycle(
6416
+ state,
6417
+ continuationContent,
6418
+ sessionIndex,
6419
+ {
6420
+ mode: "auto",
6421
+ strategy: "summary_fallback",
6422
+ model: compactionOptions?.model,
6423
+ beforeTokens: breakdown.estimatedInputTokens,
6424
+ afterTokens: (breakdown.summaryTokens || 0) + breakdown.currentTurnTokens + breakdown.toolDefinitionTokens,
6425
+ thresholdTokens: compactionOptions?.autoCompactTokenThreshold,
6426
+ contextLimitTokens: compactionOptions?.contextLimitTokens,
6427
+ effectiveInputBudgetTokens: breakdown.effectiveInputBudgetTokens,
6428
+ reservedOutputTokens: breakdown.reservedOutputTokens,
6429
+ breakdown,
6430
+ onContextCompaction: compactionOptions?.onContextCompaction,
6431
+ compactInstructions
6432
+ }
6433
+ ),
6434
+ requestContextManagement
6435
+ };
6436
+ }
6437
+ return {
6438
+ messages,
6439
+ requestContextManagement,
6440
+ pendingNativeCompactionEvent: buildNativeCompactionEvent("auto", breakdown)
6441
+ };
5793
6442
  }
5794
6443
  const recoveryMessage = this.buildStuckTurnRecoveryMessage(state, wf);
5795
6444
  const content = [
@@ -5809,7 +6458,10 @@ Do NOT redo any of the above work.`
5809
6458
  "",
5810
6459
  "Continue where you left off. Do not redo previous work. If the task is already complete, respond with TASK_COMPLETE."
5811
6460
  ].join("\n");
5812
- return [{ role: "user", content }];
6461
+ return {
6462
+ messages: [{ role: "user", content }],
6463
+ requestContextManagement
6464
+ };
5813
6465
  }
5814
6466
  /**
5815
6467
  * Upsert a record to sync long-task progress to the dashboard.
@@ -5859,6 +6511,8 @@ Do NOT redo any of the above work.`
5859
6511
  }
5860
6512
  }
5861
6513
  };
6514
+ _AgentsEndpoint.AUTO_COMPACT_SUMMARY_PREFIX = "You are continuing a long-running task. Here is a compact summary of prior work:";
6515
+ _AgentsEndpoint.FORCED_COMPACT_SUMMARY_PREFIX = "You are continuing a previously completed task. Here is a summary of prior work:";
5862
6516
  /** Stop phrases that indicate the agent considers its task complete. */
5863
6517
  _AgentsEndpoint.STOP_PHRASES = [
5864
6518
  "DONE:",