opencode-acp 1.14.27-pr.374.107 → 1.14.27-pr.376.109

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
@@ -2323,12 +2323,12 @@ function serializePruneMessagesState(messagesState) {
2323
2323
  markedForCleanup: Array.from(messagesState.markedForCleanup)
2324
2324
  };
2325
2325
  }
2326
- async function isSubAgentSession(client, sessionID) {
2326
+ async function getForkParentId(client, sessionID) {
2327
2327
  try {
2328
2328
  const result = await client.session.get({ path: { id: sessionID } });
2329
- return !!result.data?.parentID;
2329
+ return result.data?.parentID ?? null;
2330
2330
  } catch (error) {
2331
- return false;
2331
+ return null;
2332
2332
  }
2333
2333
  }
2334
2334
  function findLastCompactionTimestamp(messages) {
@@ -4365,6 +4365,205 @@ function rebuildCompressionState(state, messages, config, logger) {
4365
4365
  return rebuilt;
4366
4366
  }
4367
4367
 
4368
+ // lib/state/fork-transfer.ts
4369
+ function extractIdentity(message) {
4370
+ const id = message?.info?.id;
4371
+ if (typeof id !== "string" || id.length === 0) {
4372
+ return null;
4373
+ }
4374
+ if (isSyntheticMessage(message)) {
4375
+ return null;
4376
+ }
4377
+ const role = message.info.role;
4378
+ const created = message.info.time?.created;
4379
+ if (typeof created !== "number") {
4380
+ return null;
4381
+ }
4382
+ return { id, role, created };
4383
+ }
4384
+ function buildSharedPrefixMapping(parentMessages, forkMessages) {
4385
+ const parentIds = parentMessages.map(extractIdentity).filter((x) => x !== null);
4386
+ const forkIds = forkMessages.map(extractIdentity).filter((x) => x !== null);
4387
+ const mapping = /* @__PURE__ */ new Map();
4388
+ const limit = Math.min(parentIds.length, forkIds.length);
4389
+ for (let i = 0; i < limit; i++) {
4390
+ const p = parentIds[i];
4391
+ const f = forkIds[i];
4392
+ if (p.created === f.created && p.role === f.role) {
4393
+ mapping.set(p.id, f.id);
4394
+ } else {
4395
+ break;
4396
+ }
4397
+ }
4398
+ return mapping;
4399
+ }
4400
+ function remapBoundaryRef(ref, mapping, forkByRawId, parentByRef) {
4401
+ const parsed = parseBoundaryId(ref);
4402
+ if (!parsed) {
4403
+ return ref;
4404
+ }
4405
+ if (parsed.kind === "compressed-block") {
4406
+ return parsed.ref;
4407
+ }
4408
+ const parentRawId = parentByRef.get(parsed.ref);
4409
+ if (!parentRawId) {
4410
+ return ref;
4411
+ }
4412
+ const forkRawId = mapping.get(parentRawId);
4413
+ if (!forkRawId) {
4414
+ return ref;
4415
+ }
4416
+ const forkRef = forkByRawId.get(forkRawId);
4417
+ return forkRef ?? ref;
4418
+ }
4419
+ function translateBlock(block, mapping, forkByRawId, parentByRef) {
4420
+ const anchorForkId = mapping.get(block.anchorMessageId);
4421
+ if (!anchorForkId) {
4422
+ return null;
4423
+ }
4424
+ const compressForkId = mapping.get(block.compressMessageId);
4425
+ if (!compressForkId) {
4426
+ return null;
4427
+ }
4428
+ const translateList = (ids) => ids.map((id) => mapping.get(id)).filter((id) => id !== void 0);
4429
+ const preserveList = (ids) => [...new Set(ids)];
4430
+ return {
4431
+ ...block,
4432
+ anchorMessageId: anchorForkId,
4433
+ compressMessageId: compressForkId,
4434
+ directMessageIds: translateList(block.directMessageIds),
4435
+ effectiveMessageIds: translateList(block.effectiveMessageIds),
4436
+ directToolIds: preserveList(block.directToolIds),
4437
+ effectiveToolIds: preserveList(block.effectiveToolIds),
4438
+ startId: remapBoundaryRef(block.startId, mapping, forkByRawId, parentByRef),
4439
+ endId: remapBoundaryRef(block.endId, mapping, forkByRawId, parentByRef)
4440
+ };
4441
+ }
4442
+ function translateByMessageId(parentByMessageId, mapping, translatedBlockIds) {
4443
+ const result = /* @__PURE__ */ new Map();
4444
+ for (const [parentMsgId, entry] of Object.entries(parentByMessageId)) {
4445
+ if (!entry || typeof entry !== "object") {
4446
+ continue;
4447
+ }
4448
+ const forkMsgId = mapping.get(parentMsgId);
4449
+ if (!forkMsgId) {
4450
+ continue;
4451
+ }
4452
+ const allBlockIds = (entry.allBlockIds ?? []).filter((id) => translatedBlockIds.has(id));
4453
+ const activeBlockIds = (entry.activeBlockIds ?? []).filter(
4454
+ (id) => translatedBlockIds.has(id)
4455
+ );
4456
+ result.set(forkMsgId, {
4457
+ tokenCount: typeof entry.tokenCount === "number" ? entry.tokenCount : 0,
4458
+ allBlockIds,
4459
+ activeBlockIds
4460
+ });
4461
+ }
4462
+ return result;
4463
+ }
4464
+ async function recoverFromParentState(client, state, forkMessages, parentId, logger) {
4465
+ const parentState = await loadSessionState(parentId, logger);
4466
+ if (!parentState) {
4467
+ logger.info("fork-transfer: no parent state found, falling back to replay", {
4468
+ parentId
4469
+ });
4470
+ return 0;
4471
+ }
4472
+ const parentMessagesState = parentState.prune?.messages;
4473
+ const parentBlocks = parentMessagesState?.blocksById;
4474
+ if (!parentBlocks) {
4475
+ logger.info("fork-transfer: parent state has no blocks, falling back to replay", {
4476
+ parentId
4477
+ });
4478
+ return 0;
4479
+ }
4480
+ const parentBlockEntries = Object.entries(parentBlocks).filter(
4481
+ ([id, block]) => Number.isInteger(Number.parseInt(id, 10)) && Number.parseInt(id, 10) >= 1 && !!block
4482
+ );
4483
+ if (parentBlockEntries.length === 0) {
4484
+ logger.info("fork-transfer: parent state has no blocks, falling back to replay", {
4485
+ parentId
4486
+ });
4487
+ return 0;
4488
+ }
4489
+ let parentMessages;
4490
+ try {
4491
+ const response = await client.session.messages({ path: { id: parentId } });
4492
+ parentMessages = filterMessages(response?.data || response);
4493
+ } catch (error) {
4494
+ logger.warn("fork-transfer: failed to fetch parent messages, falling back to replay", {
4495
+ parentId,
4496
+ error: error?.message || String(error)
4497
+ });
4498
+ return 0;
4499
+ }
4500
+ const mapping = buildSharedPrefixMapping(parentMessages, forkMessages);
4501
+ if (mapping.size === 0) {
4502
+ logger.info("fork-transfer: no shared prefix detected, falling back to replay", {
4503
+ parentId
4504
+ });
4505
+ return 0;
4506
+ }
4507
+ assignMessageRefs(state, forkMessages);
4508
+ const forkByRawId = state.messageIds.byRawId;
4509
+ const parentByRef = new Map(Object.entries(parentState.messageIds?.byRef ?? {}));
4510
+ const translatedBlocks = /* @__PURE__ */ new Map();
4511
+ let skipped = 0;
4512
+ for (const [blockIdStr, block] of parentBlockEntries) {
4513
+ const blockId = Number.parseInt(blockIdStr, 10);
4514
+ const translated = translateBlock(block, mapping, forkByRawId, parentByRef);
4515
+ if (translated) {
4516
+ translatedBlocks.set(blockId, translated);
4517
+ } else {
4518
+ skipped++;
4519
+ }
4520
+ }
4521
+ if (translatedBlocks.size === 0) {
4522
+ logger.info("fork-transfer: no blocks translatable, falling back to replay", {
4523
+ parentId,
4524
+ parentBlockCount: parentBlockEntries.length,
4525
+ mappingSize: mapping.size
4526
+ });
4527
+ return 0;
4528
+ }
4529
+ const activeCount = Array.from(translatedBlocks.values()).filter((b) => b.active).length;
4530
+ if (activeCount === 0) {
4531
+ logger.info("fork-transfer: no active blocks translatable, falling back to replay", {
4532
+ parentId,
4533
+ recoveredBlocks: translatedBlocks.size,
4534
+ skippedBlocks: skipped,
4535
+ mappingSize: mapping.size
4536
+ });
4537
+ return 0;
4538
+ }
4539
+ const translatedByMessageId = translateByMessageId(
4540
+ parentMessagesState.byMessageId ?? {},
4541
+ mapping,
4542
+ new Set(translatedBlocks.keys())
4543
+ );
4544
+ state.prune.messages = loadPruneMessagesState({
4545
+ byMessageId: Object.fromEntries(translatedByMessageId),
4546
+ blocksById: Object.fromEntries(
4547
+ Array.from(translatedBlocks.entries()).map(
4548
+ ([id, block]) => [String(id), block]
4549
+ )
4550
+ ),
4551
+ activeBlockIds: [],
4552
+ activeByAnchorMessageId: {},
4553
+ nextBlockId: 1,
4554
+ nextRunId: 1,
4555
+ markedForCleanup: []
4556
+ });
4557
+ logger.info("fork-transfer: recovered compression state from parent", {
4558
+ parentId,
4559
+ recoveredBlocks: translatedBlocks.size,
4560
+ activeBlocks: activeCount,
4561
+ skippedBlocks: skipped,
4562
+ mappingSize: mapping.size
4563
+ });
4564
+ return activeCount;
4565
+ }
4566
+
4368
4567
  // lib/state/state.ts
4369
4568
  async function updatePerTurnState(state, logger, messages) {
4370
4569
  const lastCompactionTimestamp = findLastCompactionTimestamp(messages);
@@ -4553,16 +4752,22 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
4553
4752
  }
4554
4753
  resetSessionState(state);
4555
4754
  state.sessionId = sessionId;
4556
- const isSubAgent = await isSubAgentSession(client, sessionId);
4557
- state.isSubAgent = isSubAgent;
4755
+ const parentId = await getForkParentId(client, sessionId);
4756
+ state.isSubAgent = parentId !== null;
4558
4757
  state.lastCompaction = findLastCompactionTimestamp(messages);
4559
4758
  state.currentTurn = countTurns(state, messages);
4560
4759
  state.nudges.turnNudgeAnchors = collectTurnNudgeAnchors(messages);
4561
4760
  const persisted = await loadSessionState(sessionId, logger);
4562
4761
  if (persisted === null) {
4563
4762
  if (config) {
4564
- const rebuilt = rebuildCompressionState(state, messages, config, logger);
4565
- if (rebuilt > 0) {
4763
+ let recovered = 0;
4764
+ if (parentId) {
4765
+ recovered = await recoverFromParentState(client, state, messages, parentId, logger);
4766
+ }
4767
+ if (recovered === 0) {
4768
+ recovered = rebuildCompressionState(state, messages, config, logger);
4769
+ }
4770
+ if (recovered > 0) {
4566
4771
  await saveSessionState(state, logger);
4567
4772
  }
4568
4773
  }
@@ -6819,7 +7024,6 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
6819
7024
  let summaryTokens = 0;
6820
7025
  let messageTokens = 0;
6821
7026
  let protectedTokens = 0;
6822
- let reasoningTokens = 0;
6823
7027
  const perMessage = [];
6824
7028
  const perTool = [];
6825
7029
  const perCode = [];
@@ -6873,10 +7077,6 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
6873
7077
  summaryTokens += summaryPartTokens;
6874
7078
  toolTypeMap.set(toolName, (toolTypeMap.get(toolName) || 0) + toolPartTokens);
6875
7079
  if (!msgToolName) msgToolName = toolName;
6876
- } else if (part.type === "reasoning" && typeof part.text === "string") {
6877
- const tokens = Math.round(part.text.length / 4);
6878
- msgTotal += tokens;
6879
- reasoningTokens += tokens;
6880
7080
  }
6881
7081
  }
6882
7082
  if (isProtected && !isSummary) {
@@ -6904,8 +7104,7 @@ function estimateContextComposition(messages, state, protectedTools = [], protec
6904
7104
  textTokens: Math.max(0, messageTokens - codeTokens),
6905
7105
  systemTokens,
6906
7106
  protectedTokens,
6907
- reasoningTokens,
6908
- total: systemTokens + toolTokens + summaryTokens + messageTokens + reasoningTokens,
7107
+ total: systemTokens + toolTokens + summaryTokens + messageTokens,
6909
7108
  largestRanges: perMessage.slice(0, 15),
6910
7109
  largestToolRanges: perTool.slice(0, 15),
6911
7110
  largestCodeRanges: perCode.slice(0, 5),
@@ -8100,7 +8299,7 @@ This is an efficiency nudge to compress early and keep context lean \u2014 not a
8100
8299
  ${COMPRESS_PHILOSOPHY}` : "";
8101
8300
  const sysPart = composition.systemTokens > 0 ? `${fmt(composition.systemTokens)} system (${pct2(composition.systemTokens)}%) | ` : "";
8102
8301
  let breakdown = `${efficiencyNote}
8103
- Breakdown: ${sysPart}${fmt(composition.toolTokens)} tool (${pct2(composition.toolTokens)}%) | ${fmt(composition.summaryTokens)} summaries (${pct2(composition.summaryTokens)}%) | ${fmt(composition.codeTokens)} code (${pct2(composition.codeTokens)}%) | ${fmt(plainTextTokens)} text (${pct2(plainTextTokens)}%) | ${fmt(composition.reasoningTokens)} reasoning (${pct2(composition.reasoningTokens)}%)${growthStr}`;
8302
+ Breakdown: ${sysPart}${fmt(composition.toolTokens)} tool (${pct2(composition.toolTokens)}%) | ${fmt(composition.summaryTokens)} summaries (${pct2(composition.summaryTokens)}%) | ${fmt(composition.codeTokens)} code (${pct2(composition.codeTokens)}%) | ${fmt(plainTextTokens)} text (${pct2(plainTextTokens)}%)${growthStr}`;
8104
8303
  const compressibleTokens = composition.total - composition.systemTokens - composition.protectedTokens - composition.summaryTokens;
8105
8304
  if (composition.protectedTokens > 0) {
8106
8305
  breakdown += `
@@ -8948,7 +9147,6 @@ function collectVisibleMessages(rawMessages, ctx) {
8948
9147
  const ref = byRawId.get(msgId);
8949
9148
  if (!ref) return;
8950
9149
  let tokens = 0;
8951
- let reasoning = 0;
8952
9150
  let toolName = "";
8953
9151
  for (const part of msg.parts || []) {
8954
9152
  if (part.type === "text" && typeof part.text === "string") {
@@ -8959,12 +9157,10 @@ function collectVisibleMessages(rawMessages, ctx) {
8959
9157
  if (!toolName) {
8960
9158
  toolName = part?.tool || "unknown";
8961
9159
  }
8962
- } else if (part.type === "reasoning" && typeof part.text === "string") {
8963
- reasoning += Math.round(part.text.length / 4);
8964
9160
  }
8965
9161
  }
8966
- if (tokens > 0 || reasoning > 0) {
8967
- result.push({ ref, tokens, tool: toolName || "text", index: idx, reasoning });
9162
+ if (tokens > 0) {
9163
+ result.push({ ref, tokens, tool: toolName || "text", index: idx });
8968
9164
  }
8969
9165
  });
8970
9166
  return {
@@ -8986,16 +9182,14 @@ function renderOverview(visibleMessages, summaryTokens, systemTokens, blocks, fe
8986
9182
  } else {
8987
9183
  const totalTool = visibleMessages.filter((m) => m.tool !== "text" && m.tool !== "step-finish").reduce((s, m) => s + m.tokens, 0);
8988
9184
  const totalText = visibleMessages.filter((m) => m.tool === "text").reduce((s, m) => s + m.tokens, 0);
8989
- const totalReasoning = visibleMessages.reduce((s, m) => s + m.reasoning, 0);
8990
- const total = systemTokens + totalTool + totalText + summaryTokens + totalReasoning;
9185
+ const total = systemTokens + totalTool + totalText + summaryTokens;
8991
9186
  const sysPct = pct(systemTokens, total);
8992
9187
  const toolPct = pct(totalTool, total);
8993
9188
  const textPct = pct(totalText, total);
8994
9189
  const summaryPct = pct(summaryTokens, total);
8995
- const reasoningPct = pct(totalReasoning, total);
8996
9190
  lines.push("CONTEXT BREAKDOWN");
8997
9191
  lines.push(
8998
- ` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%) | ${formatTokens(totalReasoning)} reasoning (${reasoningPct}%)`
9192
+ ` ${formatTokens(systemTokens)} system (${sysPct}%) | ${formatTokens(totalTool)} tool (${toolPct}%) | ${formatTokens(totalText)} text (${textPct}%) | ${formatTokens(summaryTokens)} summaries (${summaryPct}%)`
8999
9193
  );
9000
9194
  const topTypes = Array.from(toolTypeMap.entries()).map(([tool6, tokens]) => ({ tool: tool6, tokens })).sort((a, b) => b.tokens - a.tokens).slice(0, 3);
9001
9195
  if (topTypes.length > 0) {
@@ -9106,23 +9300,22 @@ function renderUncompressedDrilldown(visibleMessages, toolFilter, sort, limit) {
9106
9300
  if (toolFilter) {
9107
9301
  filtered = filtered.filter((m) => m.tool === toolFilter);
9108
9302
  }
9109
- const sizeOf = (m) => m.tokens + m.reasoning;
9110
9303
  if (sort === "time") {
9111
9304
  filtered.sort((a, b) => a.index - b.index);
9112
9305
  } else if (sort === "tool") {
9113
- filtered.sort((a, b) => a.tool.localeCompare(b.tool) || sizeOf(b) - sizeOf(a));
9306
+ filtered.sort((a, b) => a.tool.localeCompare(b.tool) || b.tokens - a.tokens);
9114
9307
  } else {
9115
- filtered.sort((a, b) => sizeOf(b) - sizeOf(a));
9308
+ filtered.sort((a, b) => b.tokens - a.tokens);
9116
9309
  }
9117
- const totalTokens = filtered.reduce((s, m) => s + sizeOf(m), 0);
9118
- const allTokens = visibleMessages.reduce((s, m) => s + sizeOf(m), 0);
9310
+ const totalTokens = filtered.reduce((s, m) => s + m.tokens, 0);
9311
+ const allTokens = visibleMessages.reduce((s, m) => s + m.tokens, 0);
9119
9312
  const header = toolFilter ? `UNCOMPRESSED \u2014 ${toolFilter}: ${formatTokens(totalTokens)} | ${filtered.length} msgs | ${pct(totalTokens, allTokens)}% of visible` : `UNCOMPRESSED \u2014 ${formatTokens(totalTokens)} | ${filtered.length} msgs`;
9120
9313
  lines.push(header);
9121
9314
  lines.push(`Sorted by ${sort}`);
9122
9315
  lines.push("");
9123
9316
  const shown = filtered.slice(0, limit);
9124
9317
  for (const m of shown) {
9125
- lines.push(` ${m.ref} (${formatTokens(sizeOf(m))}) ${m.tool}`);
9318
+ lines.push(` ${m.ref} (${formatTokens(m.tokens)}) ${m.tool}`);
9126
9319
  }
9127
9320
  if (filtered.length > shown.length) {
9128
9321
  lines.push("");
@@ -9390,7 +9583,7 @@ import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9390
9583
  import { join as join3 } from "path";
9391
9584
  import { existsSync as existsSync3 } from "fs";
9392
9585
  import { homedir as homedir3 } from "os";
9393
- var LOG_VERSION = true ? "1.14.27-pr.374.107" : "dev";
9586
+ var LOG_VERSION = true ? "1.14.27-pr.376.109" : "dev";
9394
9587
  var LEVEL_RANK = {
9395
9588
  debug: 10,
9396
9589
  info: 20,
@@ -9686,14 +9879,13 @@ CONTEXT BREAKDOWN
9686
9879
 
9687
9880
  When context usage passes a threshold, the system appends a breakdown showing where your context tokens are spent:
9688
9881
 
9689
- Breakdown: 4.2K system (21%) | 8.0K tool (40%) | 2.0K summaries (10%) | 2.6K code (13%) | 2.2K text (11%) | 1.0K reasoning (5%)
9882
+ Breakdown: 5.2K system (21%) | 12.3K tool (40%) | 3.1K summaries (10%) | 8.5K code (28%) | 6.5K text (22%)
9690
9883
 
9691
9884
  - "system" = system prompt tokens (AGENTS.md, tool definitions \u2014 not compressible)
9692
9885
  - "tool" = tool call outputs (largest category \u2014 compress first when consumed)
9693
9886
  - "summaries" = existing compression block summaries (already compressed; do not re-compress standalone)
9694
9887
  - "code" = messages containing code blocks
9695
9888
  - "text" = plain text messages
9696
- - "reasoning" = model thinking blocks (counted to match real API usage; freed when their message is compressed)
9697
9889
 
9698
9890
  Below the breakdown, the system lists compressible ranges grouped by conversation turn. All listed ranges should be compressed to summary format \u2014 the only exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct. Compress the largest ranges first when the current step no longer needs them.
9699
9891
 
@@ -11864,7 +12056,7 @@ var server = (async (ctx) => {
11864
12056
  }
11865
12057
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
11866
12058
  logger.info("ACP plugin initialized", {
11867
- version: true ? "1.14.27-pr.374.107" : "dev",
12059
+ version: true ? "1.14.27-pr.376.109" : "dev",
11868
12060
  workspace: ctx.directory,
11869
12061
  logLevel: logger.level,
11870
12062
  debug: config.debug,