opencode-acp 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAyBjD,QAAA,MAAM,MAAM,EAAE,MAkHK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AA0BjD,QAAA,MAAM,MAAM,EAAE,MAmHK,CAAA;AAEnB,eAAe,MAAM,CAAA"}
package/dist/index.js CHANGED
@@ -1526,7 +1526,7 @@ var defaultConfig = {
1526
1526
  protectTags: false,
1527
1527
  protectUserMessages: false,
1528
1528
  maxSummaryLength: 200,
1529
- maxSummaryLengthHard: 800,
1529
+ maxSummaryLengthHard: 3e3,
1530
1530
  minCompressRange: 2e3
1531
1531
  },
1532
1532
  strategies: {
@@ -1841,7 +1841,7 @@ Using previous/default values`
1841
1841
  }
1842
1842
 
1843
1843
  // lib/compress/message.ts
1844
- import { tool } from "@opencode-ai/plugin";
1844
+ import { tool as tool2 } from "@opencode-ai/plugin";
1845
1845
 
1846
1846
  // lib/token-utils.ts
1847
1847
  import * as _anthropicTokenizer from "@anthropic-ai/tokenizer";
@@ -2219,6 +2219,7 @@ function allocateNextMessageRef(state) {
2219
2219
  }
2220
2220
 
2221
2221
  // lib/compress/search.ts
2222
+ import { tool } from "@opencode-ai/plugin";
2222
2223
  async function fetchSessionMessages(client, sessionId) {
2223
2224
  const response = await client.session.messages({
2224
2225
  path: { id: sessionId }
@@ -2430,6 +2431,120 @@ function buildBoundaryLookup(context, state) {
2430
2431
  }
2431
2432
  return lookup;
2432
2433
  }
2434
+ var SEARCH_CONTEXT_TOOL_DESCRIPTION = `Search through all compressed block summaries AND visible messages to find relevant content. Use this BEFORE decompressing to find the right block. Returns a hit list with block/message IDs, relevance scores, and previews.
2435
+
2436
+ Examples:
2437
+ - search_context({ query: "decoder accuracy" }) \u2014 find blocks/messages about decoder accuracy
2438
+ - search_context({ query: "training loss PPL" }) \u2014 find training results
2439
+ - search_context({ query: "architecture design", limit: 5 }) \u2014 top 5 results`;
2440
+ function countOccurrences(text, term) {
2441
+ if (!text || !term) return 0;
2442
+ let count = 0;
2443
+ let idx = 0;
2444
+ while ((idx = text.indexOf(term, idx)) !== -1) {
2445
+ count++;
2446
+ idx += term.length;
2447
+ }
2448
+ return count;
2449
+ }
2450
+ function buildSearchPreview(text, firstTerm) {
2451
+ if (!text) return "";
2452
+ const matchIdx = text.toLowerCase().indexOf(firstTerm);
2453
+ if (matchIdx >= 0) {
2454
+ const start = Math.max(0, matchIdx - 50);
2455
+ const end = Math.min(text.length, matchIdx + 150);
2456
+ return (start > 0 ? "..." : "") + text.substring(start, end) + (end < text.length ? "..." : "");
2457
+ }
2458
+ return text.substring(0, 200) + (text.length > 200 ? "..." : "");
2459
+ }
2460
+ function createSearchContextTool(ctx) {
2461
+ ctx.prompts.reload();
2462
+ return tool({
2463
+ description: SEARCH_CONTEXT_TOOL_DESCRIPTION,
2464
+ args: {
2465
+ query: tool.schema.string().describe("Search query \u2014 keywords or phrase to find"),
2466
+ limit: tool.schema.number().optional().describe("Maximum results to return (default: 10)"),
2467
+ deep: tool.schema.boolean().optional().describe("If true, also search visible (uncompressed) messages. Slower but more thorough (default: false)")
2468
+ },
2469
+ async execute(args) {
2470
+ const query = (args.query || "").toLowerCase().trim();
2471
+ const limit = args.limit ?? 10;
2472
+ if (!query) {
2473
+ return "Error: query is required.";
2474
+ }
2475
+ const queryTerms = query.split(/\s+/).filter((t) => t.length > 0);
2476
+ const results = [];
2477
+ const MIN_RELEVANCE = 0.1;
2478
+ const blocksById = ctx.state.prune.messages.blocksById;
2479
+ for (const [blockId, block] of blocksById) {
2480
+ if (!block.active) continue;
2481
+ const topic = (block.topic || "").toLowerCase();
2482
+ const summary = (block.summary || "").toLowerCase();
2483
+ let relevance = 0;
2484
+ let termsHit = 0;
2485
+ for (const term of queryTerms) {
2486
+ let termHit = false;
2487
+ const topicCount = countOccurrences(topic, term);
2488
+ if (topicCount > 0) {
2489
+ relevance += Math.min(topicCount * 0.15, 0.45);
2490
+ termHit = true;
2491
+ }
2492
+ const summaryCount = countOccurrences(summary, term);
2493
+ if (summaryCount > 0) {
2494
+ relevance += Math.min(summaryCount * 0.04, 0.2);
2495
+ termHit = true;
2496
+ }
2497
+ if (termHit) termsHit++;
2498
+ }
2499
+ if (termsHit === queryTerms.length && queryTerms.length > 1) {
2500
+ relevance *= 1.2;
2501
+ }
2502
+ if (queryTerms.length > 1 && query.includes(" ")) {
2503
+ if (topic.includes(query) || summary.includes(query)) {
2504
+ relevance += 0.25;
2505
+ }
2506
+ }
2507
+ relevance = Math.min(relevance, 1);
2508
+ if (relevance < MIN_RELEVANCE) continue;
2509
+ const origSummary = block.summary || "";
2510
+ const preview = buildSearchPreview(origSummary, queryTerms[0]);
2511
+ results.push({
2512
+ type: "block",
2513
+ id: `b${blockId}`,
2514
+ relevance,
2515
+ label: block.topic || "(no topic)",
2516
+ preview,
2517
+ action: `\u2192 decompress(b${blockId}) for full content`
2518
+ });
2519
+ }
2520
+ results.sort((a, b) => b.relevance - a.relevance);
2521
+ const limited = results.slice(0, limit);
2522
+ if (limited.length === 0) {
2523
+ return `No matches found for "${args.query}". Try different keywords.`;
2524
+ }
2525
+ const lines = [];
2526
+ lines.push(
2527
+ `\u{1F50D} Found ${results.length} matches for "${args.query}" (showing top ${limited.length}):`
2528
+ );
2529
+ lines.push("");
2530
+ for (const result of limited) {
2531
+ const icon = result.type === "block" ? "\u{1F4E6}" : "\u{1F4C4}";
2532
+ const stars = "\u2B50".repeat(Math.ceil(result.relevance * 5));
2533
+ lines.push(
2534
+ `${icon} [${result.id}] ${stars} (${result.relevance.toFixed(2)}) "${result.label}"`
2535
+ );
2536
+ lines.push(` ${result.preview}`);
2537
+ lines.push(` ${result.action}`);
2538
+ lines.push("");
2539
+ }
2540
+ let output = lines.join("\n");
2541
+ if (output.length > 3e3) {
2542
+ output = output.substring(0, 3e3) + "\n... (truncated, refine query for more specific results)";
2543
+ }
2544
+ return output;
2545
+ }
2546
+ });
2547
+ }
2433
2548
 
2434
2549
  // lib/compress/state.ts
2435
2550
  var DEFAULT_PROMOTION_THRESHOLD = 5;
@@ -3620,20 +3735,20 @@ function matchesGlob(inputPath, pattern) {
3620
3735
  regex += "$";
3621
3736
  return new RegExp(regex).test(input);
3622
3737
  }
3623
- function getFilePathsFromParameters(tool4, parameters) {
3738
+ function getFilePathsFromParameters(tool5, parameters) {
3624
3739
  if (typeof parameters !== "object" || parameters === null) {
3625
3740
  return [];
3626
3741
  }
3627
3742
  const paths = [];
3628
3743
  const params = parameters;
3629
- if (tool4 === "apply_patch" && typeof params.patchText === "string") {
3744
+ if (tool5 === "apply_patch" && typeof params.patchText === "string") {
3630
3745
  const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
3631
3746
  let match;
3632
3747
  while ((match = pathRegex.exec(params.patchText)) !== null) {
3633
3748
  paths.push(match[1].trim());
3634
3749
  }
3635
3750
  }
3636
- if (tool4 === "multiedit") {
3751
+ if (tool5 === "multiedit") {
3637
3752
  if (typeof params.filePath === "string") {
3638
3753
  paths.push(params.filePath);
3639
3754
  }
@@ -3728,13 +3843,13 @@ var deduplicate = (state, logger, config, messages) => {
3728
3843
  logger.debug(`Marked ${newPruneIds.length} duplicate tool calls for pruning`);
3729
3844
  }
3730
3845
  };
3731
- function createToolSignature(tool4, parameters) {
3846
+ function createToolSignature(tool5, parameters) {
3732
3847
  if (!parameters) {
3733
- return tool4;
3848
+ return tool5;
3734
3849
  }
3735
3850
  const normalized = normalizeParameters(parameters);
3736
3851
  const sorted = sortObjectKeys(normalized);
3737
- return `${tool4}::${JSON.stringify(sorted)}`;
3852
+ return `${tool5}::${JSON.stringify(sorted)}`;
3738
3853
  }
3739
3854
  function normalizeParameters(params) {
3740
3855
  if (typeof params !== "object" || params === null) return params;
@@ -3809,9 +3924,9 @@ var purgeErrors = (state, logger, config, messages) => {
3809
3924
  };
3810
3925
 
3811
3926
  // lib/ui/utils.ts
3812
- function extractParameterKey(tool4, parameters) {
3927
+ function extractParameterKey(tool5, parameters) {
3813
3928
  if (!parameters) return "";
3814
- if (tool4 === "read" && parameters.filePath) {
3929
+ if (tool5 === "read" && parameters.filePath) {
3815
3930
  const offset = parameters.offset;
3816
3931
  const limit = parameters.limit;
3817
3932
  if (offset !== void 0 && limit !== void 0) {
@@ -3825,10 +3940,10 @@ function extractParameterKey(tool4, parameters) {
3825
3940
  }
3826
3941
  return parameters.filePath;
3827
3942
  }
3828
- if ((tool4 === "write" || tool4 === "edit" || tool4 === "multiedit") && parameters.filePath) {
3943
+ if ((tool5 === "write" || tool5 === "edit" || tool5 === "multiedit") && parameters.filePath) {
3829
3944
  return parameters.filePath;
3830
3945
  }
3831
- if (tool4 === "apply_patch" && typeof parameters.patchText === "string") {
3946
+ if (tool5 === "apply_patch" && typeof parameters.patchText === "string") {
3832
3947
  const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
3833
3948
  const paths = [];
3834
3949
  let match;
@@ -3845,51 +3960,51 @@ function extractParameterKey(tool4, parameters) {
3845
3960
  }
3846
3961
  return "patch";
3847
3962
  }
3848
- if (tool4 === "list") {
3963
+ if (tool5 === "list") {
3849
3964
  return parameters.path || "(current directory)";
3850
3965
  }
3851
- if (tool4 === "glob") {
3966
+ if (tool5 === "glob") {
3852
3967
  if (parameters.pattern) {
3853
3968
  const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
3854
3969
  return `"${parameters.pattern}"${pathInfo}`;
3855
3970
  }
3856
3971
  return "(unknown pattern)";
3857
3972
  }
3858
- if (tool4 === "grep") {
3973
+ if (tool5 === "grep") {
3859
3974
  if (parameters.pattern) {
3860
3975
  const pathInfo = parameters.path ? ` in ${parameters.path}` : "";
3861
3976
  return `"${parameters.pattern}"${pathInfo}`;
3862
3977
  }
3863
3978
  return "(unknown pattern)";
3864
3979
  }
3865
- if (tool4 === "bash") {
3980
+ if (tool5 === "bash") {
3866
3981
  if (parameters.description) return parameters.description;
3867
3982
  if (parameters.command) {
3868
3983
  return parameters.command.length > 50 ? parameters.command.substring(0, 50) + "..." : parameters.command;
3869
3984
  }
3870
3985
  }
3871
- if (tool4 === "webfetch" && parameters.url) {
3986
+ if (tool5 === "webfetch" && parameters.url) {
3872
3987
  return parameters.url;
3873
3988
  }
3874
- if (tool4 === "websearch" && parameters.query) {
3989
+ if (tool5 === "websearch" && parameters.query) {
3875
3990
  return `"${parameters.query}"`;
3876
3991
  }
3877
- if (tool4 === "codesearch" && parameters.query) {
3992
+ if (tool5 === "codesearch" && parameters.query) {
3878
3993
  return `"${parameters.query}"`;
3879
3994
  }
3880
- if (tool4 === "todowrite") {
3995
+ if (tool5 === "todowrite") {
3881
3996
  return `${parameters.todos?.length || 0} todos`;
3882
3997
  }
3883
- if (tool4 === "todoread") {
3998
+ if (tool5 === "todoread") {
3884
3999
  return "read todo list";
3885
4000
  }
3886
- if (tool4 === "task" && parameters.description) {
4001
+ if (tool5 === "task" && parameters.description) {
3887
4002
  return parameters.description;
3888
4003
  }
3889
- if (tool4 === "skill" && parameters.name) {
4004
+ if (tool5 === "skill" && parameters.name) {
3890
4005
  return parameters.name;
3891
4006
  }
3892
- if (tool4 === "lsp") {
4007
+ if (tool5 === "lsp") {
3893
4008
  const op = parameters.operation || "lsp";
3894
4009
  const path = parameters.filePath || "";
3895
4010
  const line = parameters.line;
@@ -3902,7 +4017,7 @@ function extractParameterKey(tool4, parameters) {
3902
4017
  }
3903
4018
  return op;
3904
4019
  }
3905
- if (tool4 === "question") {
4020
+ if (tool5 === "question") {
3906
4021
  const questions = parameters.questions;
3907
4022
  if (Array.isArray(questions) && questions.length > 0) {
3908
4023
  const headers = questions.map((q) => q.header || "").filter(Boolean).slice(0, 3);
@@ -4483,14 +4598,14 @@ ${output}`);
4483
4598
  // lib/compress/message.ts
4484
4599
  function buildSchema(maxSummaryLength) {
4485
4600
  return {
4486
- topic: tool.schema.string().describe(
4601
+ topic: tool2.schema.string().describe(
4487
4602
  "Short label (3-5 words) for the overall batch - e.g., 'Closed Research Notes'"
4488
4603
  ),
4489
- content: tool.schema.array(
4490
- tool.schema.object({
4491
- messageId: tool.schema.string().describe("Raw message ID to compress (e.g. m00001)"),
4492
- topic: tool.schema.string().describe("Short label (3-5 words) for this one message summary"),
4493
- summary: tool.schema.string().describe(
4604
+ content: tool2.schema.array(
4605
+ tool2.schema.object({
4606
+ messageId: tool2.schema.string().describe("Raw message ID to compress (e.g. m00001)"),
4607
+ topic: tool2.schema.string().describe("Short label (3-5 words) for this one message summary"),
4608
+ summary: tool2.schema.string().describe(
4494
4609
  `Complete technical summary replacing that one message. Aim for <=${maxSummaryLength} chars; exceed only when strictly necessary to preserve critical detail (file paths, decisions, signatures, exact values). Never pad.`
4495
4610
  )
4496
4611
  })
@@ -4500,7 +4615,7 @@ function buildSchema(maxSummaryLength) {
4500
4615
  function createCompressMessageTool(ctx) {
4501
4616
  ctx.prompts.reload();
4502
4617
  const runtimePrompts = ctx.prompts.getRuntimePrompts();
4503
- return tool({
4618
+ return tool2({
4504
4619
  description: runtimePrompts.compressMessage + MESSAGE_FORMAT_EXTENSION,
4505
4620
  args: buildSchema(ctx.config.compress.maxSummaryLength),
4506
4621
  async execute(args, toolCtx) {
@@ -4510,7 +4625,7 @@ function createCompressMessageTool(ctx) {
4510
4625
  for (const entry of input.content) {
4511
4626
  if (entry.summary.length > maxSummaryLengthHard) {
4512
4627
  throw new Error(
4513
- `Summary too long (${entry.summary.length} chars, hard ceiling ${maxSummaryLengthHard}). Aim for <=${ctx.config.compress.maxSummaryLength}; exceed only when strictly necessary. Rewrite more concisely.`
4628
+ `Summary too long (${entry.summary.length} chars; limit ${maxSummaryLengthHard}). Rewrite to under ${maxSummaryLengthHard} chars \u2014 keep only the most essential details (conclusions, file paths, decisions, exact values) and drop verbose narration or raw dumps.`
4514
4629
  );
4515
4630
  }
4516
4631
  }
@@ -4613,7 +4728,7 @@ function createCompressMessageTool(ctx) {
4613
4728
  }
4614
4729
 
4615
4730
  // lib/compress/range.ts
4616
- import { tool as tool2 } from "@opencode-ai/plugin";
4731
+ import { tool as tool3 } from "@opencode-ai/plugin";
4617
4732
 
4618
4733
  // lib/compress/range-utils.ts
4619
4734
  var BLOCK_PLACEHOLDER_REGEX = /\(b(\d+)\)|\{block_(\d+)\}/gi;
@@ -4757,14 +4872,14 @@ function appendMissingBlockSummaries(summary, _missingBlockIds, _summaryByBlockI
4757
4872
  // lib/compress/range.ts
4758
4873
  function buildSchema2(maxSummaryLength) {
4759
4874
  return {
4760
- topic: tool2.schema.string().describe("Short label (3-5 words) for display - e.g., 'Auth System Exploration'"),
4761
- content: tool2.schema.array(
4762
- tool2.schema.object({
4763
- startId: tool2.schema.string().describe(
4875
+ topic: tool3.schema.string().describe("Short label (3-5 words) for display - e.g., 'Auth System Exploration'"),
4876
+ content: tool3.schema.array(
4877
+ tool3.schema.object({
4878
+ startId: tool3.schema.string().describe(
4764
4879
  "Message or block ID marking the beginning of range (e.g. m00001, b2)"
4765
4880
  ),
4766
- endId: tool2.schema.string().describe("Message or block ID marking the end of range (e.g. m00012, b5)"),
4767
- summary: tool2.schema.string().describe(
4881
+ endId: tool3.schema.string().describe("Message or block ID marking the end of range (e.g. m00012, b5)"),
4882
+ summary: tool3.schema.string().describe(
4768
4883
  `Complete technical summary replacing all content in range. Aim for <=${maxSummaryLength} chars; exceed only when strictly necessary to preserve critical detail (file paths, decisions, signatures, exact values). Never pad.`
4769
4884
  )
4770
4885
  })
@@ -4776,7 +4891,7 @@ function buildSchema2(maxSummaryLength) {
4776
4891
  function createCompressRangeTool(ctx) {
4777
4892
  ctx.prompts.reload();
4778
4893
  const runtimePrompts = ctx.prompts.getRuntimePrompts();
4779
- return tool2({
4894
+ return tool3({
4780
4895
  description: runtimePrompts.compressRange + RANGE_FORMAT_EXTENSION,
4781
4896
  args: buildSchema2(ctx.config.compress.maxSummaryLength),
4782
4897
  async execute(args, toolCtx) {
@@ -4786,7 +4901,7 @@ function createCompressRangeTool(ctx) {
4786
4901
  for (const entry of input.content) {
4787
4902
  if (entry.summary.length > maxSummaryLengthHard) {
4788
4903
  throw new Error(
4789
- `Summary too long (${entry.summary.length} chars, hard ceiling ${maxSummaryLengthHard}). Aim for <=${ctx.config.compress.maxSummaryLength}; exceed only when strictly necessary. Rewrite more concisely.`
4904
+ `Summary too long (${entry.summary.length} chars; limit ${maxSummaryLengthHard}). Rewrite to under ${maxSummaryLengthHard} chars \u2014 keep only the most essential details (conclusions, file paths, decisions, exact values) and drop verbose narration or raw dumps.`
4790
4905
  );
4791
4906
  }
4792
4907
  }
@@ -4923,7 +5038,8 @@ function createCompressRangeTool(ctx) {
4923
5038
  }
4924
5039
  await finalizeSession(ctx, toolCtx, rawMessages, notifications, input.topic);
4925
5040
  return `Compressed ${totalCompressedMessages} messages into ${COMPRESSED_BLOCK_HEADER}.
4926
- IMPORTANT: This was an automatic context compression. You MUST continue your previous task exactly where you left off. Do NOT ask the user what to do next.`;
5041
+ IMPORTANT: This was an automatic context compression. You MUST continue your previous task exactly where you left off. Do NOT ask the user what to do next.
5042
+ \u{1F4A1} Tip: Use search_context('keyword') to find compressed content when you need it later.`;
4927
5043
  }
4928
5044
  });
4929
5045
  }
@@ -4940,7 +5056,7 @@ function extractBoundaryConsumedBlocks(startReference, endReference) {
4940
5056
  }
4941
5057
 
4942
5058
  // lib/compress/decompress.ts
4943
- import { tool as tool3 } from "@opencode-ai/plugin";
5059
+ import { tool as tool4 } from "@opencode-ai/plugin";
4944
5060
 
4945
5061
  // lib/messages/utils.ts
4946
5062
  import { createHash } from "crypto";
@@ -5392,8 +5508,8 @@ var resolveEffectiveCompressPermission = (basePermission, hostPermissions, agent
5392
5508
  agentName ? hostPermissions.agents[agentName] : void 0
5393
5509
  ) ? "deny" : basePermission;
5394
5510
  };
5395
- var hasExplicitToolPermission = (permissionConfig, tool4) => {
5396
- return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool4) : false;
5511
+ var hasExplicitToolPermission = (permissionConfig, tool5) => {
5512
+ return permissionConfig ? Object.prototype.hasOwnProperty.call(permissionConfig, tool5) : false;
5397
5513
  };
5398
5514
 
5399
5515
  // lib/compress-permission.ts
@@ -6495,11 +6611,11 @@ IMPORTANT:
6495
6611
  - Do NOT call this tool in parallel with compress \u2014 their state mutations may conflict.`;
6496
6612
  function buildSchema3() {
6497
6613
  return {
6498
- blockId: tool3.schema.string().describe('Block reference to decompress (e.g., "b0", "b2")')
6614
+ blockId: tool4.schema.string().describe('Block reference to decompress (e.g., "b0", "b2")')
6499
6615
  };
6500
6616
  }
6501
6617
  function createDecompressTool(ctx) {
6502
- return tool3({
6618
+ return tool4({
6503
6619
  description: TOOL_DESCRIPTION,
6504
6620
  args: buildSchema3(),
6505
6621
  async execute(args, toolCtx) {
@@ -6785,7 +6901,7 @@ var SYSTEM = `
6785
6901
 
6786
6902
  You operate in a context-constrained environment. Context management helps preserve retrieval quality, but your primary goal is completing the task at hand. Do not let context management distract from the actual work.
6787
6903
 
6788
- The tools you have for context management are \`compress\` and \`decompress\`. \`compress\` replaces older conversation content with technical summaries you produce. \`decompress\` restores previously compressed content when you need exact details.
6904
+ The tools you have for context management are \`compress\`, \`decompress\`, and \`search_context\`. \`compress\` replaces older conversation content with technical summaries you produce. \`decompress\` restores previously compressed content when you need exact details. \`search_context\` searches compressed block summaries (and visible messages) to locate relevant content before you decompress.
6789
6905
 
6790
6906
  \`<dcp-message-id>\` and \`<dcp-system-reminder>\` tags are environment-injected metadata. Do not output them.
6791
6907
 
@@ -6793,13 +6909,13 @@ COMPRESSION PHILOSOPHY
6793
6909
 
6794
6910
  Compression replaces raw conversation content with dense summaries. When used correctly, it keeps your context sharp and focused. When used carelessly, it destroys information you need.
6795
6911
 
6796
- The key principle: compress proactively to keep context lean, but selectively. Large tool outputs (shell, diffs, logs) can be compressed into summaries at any time \u2014 you can decompress later if needed. Extract and keep what matters: user intent, key decisions, file paths, and important findings \u2014 even if buried in large messages. Compress everything else, including verbose parts of user messages, large code dumps, and long discussions.
6912
+ The key principle: compress selectively to keep context lean \u2014 but never compress content you're actively using for an ongoing task. Compression is for COMPLETED work, not work in progress. Before compressing, ask: "Will I need this in the next few turns?" If yes or unsure, keep it. Large tool outputs (shell, diffs, logs) can be compressed into summaries after the task using them is done \u2014 you can decompress later if needed. Extract and keep what matters: user intent, key decisions, file paths, and important findings \u2014 even if buried in large messages. Compress everything else, including verbose parts of user messages, large code dumps, and long discussions.
6797
6913
 
6798
6914
  Target the largest UNCOMPRESSED content first. Savings scale with original size \u2014 compressing a 5000-token tool output frees far more than re-shrinking an already-summarized 300-token block.
6799
6915
 
6800
6916
  CONTEXT PRESSURE LEVELS
6801
6917
 
6802
- - Normal: Be frugal \u2014 compress large completed outputs into summaries. You can decompress later if needed.
6918
+ - Normal: After completing a task or sub-task, compress its tool outputs (agent results, verbose commands, large tool outputs) into summaries. Do NOT compress content you're actively using for an ongoing task \u2014 wait until the task is complete. You can decompress later if needed.
6803
6919
  - Elevated: Context is growing \u2014 compress completed sections and high-token waste now.
6804
6920
  - Critical: Compress aggressively now \u2014 preserve only what is essential for the current task.
6805
6921
 
@@ -6849,6 +6965,8 @@ Generate recovery breadcrumbs in your summary so future-you can reconstruct the
6849
6965
 
6850
6966
  If you later realize you need the original details from a compressed block, use \`decompress\` to restore them. You can decompress, read the content, then re-compress if needed.
6851
6967
 
6968
+ Use \`search_context\` to find relevant compressed content before decompressing \u2014 it returns ranked matches across all active block summaries so you can pick the right block ID without inflating context by trial-and-error decompression.
6969
+
6852
6970
  Use \`compress\` and \`decompress\` deliberately with quality-first summaries. Prioritize stale content intelligently to maintain a high-signal context window.
6853
6971
  `;
6854
6972
 
@@ -7801,7 +7919,7 @@ var COMPRESS_TRIGGER_PROMPT = [
7801
7919
  "Follow the active compress mode, preserve all critical implementation details, and choose safe targets.",
7802
7920
  "Return after compress with a brief explanation of what content was compressed."
7803
7921
  ].join("\n\n");
7804
- function getTriggerPrompt(tool4, state, config, userFocus) {
7922
+ function getTriggerPrompt(tool5, state, config, userFocus) {
7805
7923
  const base = COMPRESS_TRIGGER_PROMPT;
7806
7924
  const compressedBlockGuidance = config.compress.mode === "message" ? "" : buildCompressedBlockGuidance(state, config.gc);
7807
7925
  const sections = [base, compressedBlockGuidance];
@@ -7830,8 +7948,8 @@ async function handleManualToggleCommand(ctx, modeArg) {
7830
7948
  );
7831
7949
  logger.info("Manual mode toggled", { manualMode: state.manualMode });
7832
7950
  }
7833
- async function handleManualTriggerCommand(ctx, tool4, userFocus) {
7834
- return getTriggerPrompt(tool4, ctx.state, ctx.config, userFocus);
7951
+ async function handleManualTriggerCommand(ctx, tool5, userFocus) {
7952
+ return getTriggerPrompt(tool5, ctx.state, ctx.config, userFocus);
7835
7953
  }
7836
7954
  function applyPendingManualTrigger(state, messages, logger) {
7837
7955
  const pending = state.pendingManualTrigger;
@@ -9091,7 +9209,8 @@ var server = (async (ctx) => {
9091
9209
  tool: {
9092
9210
  ...config.compress.permission !== "deny" && {
9093
9211
  compress: config.compress.mode === "message" ? createCompressMessageTool(compressToolContext) : createCompressRangeTool(compressToolContext),
9094
- decompress: createDecompressTool(compressToolContext)
9212
+ decompress: createDecompressTool(compressToolContext),
9213
+ search_context: createSearchContextTool(compressToolContext)
9095
9214
  }
9096
9215
  },
9097
9216
  config: async (opencodeConfig) => {
@@ -9107,7 +9226,7 @@ var server = (async (ctx) => {
9107
9226
  }
9108
9227
  const toolsToAdd = [];
9109
9228
  if (config.compress.permission !== "deny" && !config.experimental.allowSubAgents) {
9110
- toolsToAdd.push("compress", "decompress");
9229
+ toolsToAdd.push("compress", "decompress", "search_context");
9111
9230
  }
9112
9231
  if (toolsToAdd.length > 0) {
9113
9232
  const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [];