opencode-acp 1.14.27 → 1.15.0-pr.376.118

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
@@ -869,6 +869,7 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
869
869
  "autoUpdate",
870
870
  "debug",
871
871
  "logLevel",
872
+ "storagePath",
872
873
  "showUpdateToasts",
873
874
  "allowSubAgents",
874
875
  "pruneNotification",
@@ -909,6 +910,9 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
909
910
  "compress.preserveRecentMessages",
910
911
  "compress.preserveRecentTokens",
911
912
  "compress.preserveLastUserMessage",
913
+ "compress.reasoning",
914
+ "compress.reasoning.drop",
915
+ "compress.reasoning.threshold",
912
916
  "gc",
913
917
  "gc.algorithm",
914
918
  "gc.promotionThreshold",
@@ -956,6 +960,9 @@ function validateConfigTypes(config) {
956
960
  if (config.debug !== void 0 && typeof config.debug !== "boolean") {
957
961
  errors.push({ key: "debug", expected: "boolean", actual: typeof config.debug });
958
962
  }
963
+ if (config.storagePath !== void 0 && typeof config.storagePath !== "string") {
964
+ errors.push({ key: "storagePath", expected: "string", actual: typeof config.storagePath });
965
+ }
959
966
  if (config.allowSubAgents !== void 0 && typeof config.allowSubAgents !== "boolean") {
960
967
  errors.push({ key: "allowSubAgents", expected: "boolean", actual: typeof config.allowSubAgents });
961
968
  }
@@ -1262,6 +1269,30 @@ function validateConfigTypes(config) {
1262
1269
  actual: typeof compress.preserveLastUserMessage
1263
1270
  });
1264
1271
  }
1272
+ if (compress.reasoning !== void 0) {
1273
+ if (typeof compress.reasoning !== "object" || compress.reasoning === null || Array.isArray(compress.reasoning)) {
1274
+ errors.push({
1275
+ key: "compress.reasoning",
1276
+ expected: "object { drop?: boolean; threshold?: integer (>= 0) }",
1277
+ actual: JSON.stringify(compress.reasoning)
1278
+ });
1279
+ } else {
1280
+ if (compress.reasoning.drop !== void 0 && typeof compress.reasoning.drop !== "boolean") {
1281
+ errors.push({
1282
+ key: "compress.reasoning.drop",
1283
+ expected: "boolean",
1284
+ actual: typeof compress.reasoning.drop
1285
+ });
1286
+ }
1287
+ if (compress.reasoning.threshold !== void 0 && (typeof compress.reasoning.threshold !== "number" || !Number.isInteger(compress.reasoning.threshold) || compress.reasoning.threshold < 0)) {
1288
+ errors.push({
1289
+ key: "compress.reasoning.threshold",
1290
+ expected: "integer (>= 0)",
1291
+ actual: JSON.stringify(compress.reasoning.threshold)
1292
+ });
1293
+ }
1294
+ }
1295
+ }
1265
1296
  if (typeof compress.iterationNudgeThreshold === "number" && compress.iterationNudgeThreshold < 1) {
1266
1297
  errors.push({
1267
1298
  key: "compress.iterationNudgeThreshold",
@@ -1347,7 +1378,8 @@ function validateConfigTypes(config) {
1347
1378
  maxVisibleSegments: "positiveNumber",
1348
1379
  keepEmbedMaxChars: "nonNegativeNumber",
1349
1380
  preserveRecentMessages: "nonNegativeNumber",
1350
- preserveRecentTokens: "nonNegativeNumber"
1381
+ preserveRecentTokens: "nonNegativeNumber",
1382
+ reasoning: "reasoningConfig"
1351
1383
  };
1352
1384
  const validateOverrideField = (key, type, value) => {
1353
1385
  if (value === void 0) {
@@ -1387,6 +1419,31 @@ function validateConfigTypes(config) {
1387
1419
  errors.push({ key, expected: "string[]", actual: JSON.stringify(value) });
1388
1420
  }
1389
1421
  break;
1422
+ case "reasoningConfig":
1423
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1424
+ errors.push({
1425
+ key,
1426
+ expected: "object { drop?: boolean; threshold?: integer (>= 0) }",
1427
+ actual: JSON.stringify(value)
1428
+ });
1429
+ break;
1430
+ }
1431
+ const reasoning = value;
1432
+ if (reasoning.drop !== void 0 && typeof reasoning.drop !== "boolean") {
1433
+ errors.push({
1434
+ key: `${key}.drop`,
1435
+ expected: "boolean",
1436
+ actual: typeof reasoning.drop
1437
+ });
1438
+ }
1439
+ if (reasoning.threshold !== void 0 && (typeof reasoning.threshold !== "number" || !Number.isInteger(reasoning.threshold) || reasoning.threshold < 0)) {
1440
+ errors.push({
1441
+ key: `${key}.threshold`,
1442
+ expected: "integer (>= 0)",
1443
+ actual: JSON.stringify(reasoning.threshold)
1444
+ });
1445
+ }
1446
+ break;
1390
1447
  }
1391
1448
  };
1392
1449
  const validateOverrideObject = (prefix, overrides) => {
@@ -1565,6 +1622,10 @@ function validateConfigTypes(config) {
1565
1622
  }
1566
1623
 
1567
1624
  // lib/config.ts
1625
+ var DEFAULT_COMPRESS_REASONING = {
1626
+ drop: true,
1627
+ threshold: 2048
1628
+ };
1568
1629
  var DEFAULT_PROTECTED_TOOLS = [
1569
1630
  "task",
1570
1631
  "skill",
@@ -1659,7 +1720,8 @@ var defaultConfig = {
1659
1720
  lastSegmentSoftBlock: true,
1660
1721
  preserveRecentMessages: 5,
1661
1722
  preserveRecentTokens: 5e3,
1662
- preserveLastUserMessage: true
1723
+ preserveLastUserMessage: true,
1724
+ reasoning: { ...DEFAULT_COMPRESS_REASONING }
1663
1725
  },
1664
1726
  gc: {
1665
1727
  algorithm: "truncate",
@@ -1829,7 +1891,11 @@ function mergeCompress(base, override) {
1829
1891
  lastSegmentSoftBlock: override.lastSegmentSoftBlock ?? base.lastSegmentSoftBlock,
1830
1892
  preserveRecentMessages: override.preserveRecentMessages ?? base.preserveRecentMessages,
1831
1893
  preserveRecentTokens: override.preserveRecentTokens ?? base.preserveRecentTokens,
1832
- preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage
1894
+ preserveLastUserMessage: override.preserveLastUserMessage ?? base.preserveLastUserMessage,
1895
+ reasoning: {
1896
+ drop: override.reasoning?.drop ?? base.reasoning?.drop ?? DEFAULT_COMPRESS_REASONING.drop,
1897
+ threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
1898
+ }
1833
1899
  };
1834
1900
  }
1835
1901
  function mergeCommands(base, override) {
@@ -1933,6 +1999,7 @@ function mergeLayer(config, data) {
1933
1999
  autoUpdate: data.autoUpdate ?? config.autoUpdate,
1934
2000
  debug: data.debug ?? config.debug,
1935
2001
  logLevel: data.logLevel ?? config.logLevel,
2002
+ storagePath: data.storagePath ?? config.storagePath,
1936
2003
  allowSubAgents: data.allowSubAgents ?? data.experimental?.allowSubAgents ?? config.allowSubAgents,
1937
2004
  pruneNotification: data.pruneNotification ?? config.pruneNotification,
1938
2005
  pruneNotificationType: data.pruneNotificationType ?? config.pruneNotificationType,
@@ -2287,7 +2354,7 @@ Each entry needs a topic \u2014 either its own or the top-level fallback.`;
2287
2354
  import * as fs from "fs/promises";
2288
2355
  import { existsSync as existsSync2 } from "fs";
2289
2356
  import { homedir as homedir2 } from "os";
2290
- import { join as join2 } from "path";
2357
+ import { isAbsolute, join as join2 } from "path";
2291
2358
 
2292
2359
  // lib/state/utils.ts
2293
2360
  var isMessageCompacted = (state, msg) => {
@@ -2323,12 +2390,12 @@ function serializePruneMessagesState(messagesState) {
2323
2390
  markedForCleanup: Array.from(messagesState.markedForCleanup)
2324
2391
  };
2325
2392
  }
2326
- async function isSubAgentSession(client, sessionID) {
2393
+ async function getForkParentId(client, sessionID) {
2327
2394
  try {
2328
2395
  const result = await client.session.get({ path: { id: sessionID } });
2329
- return !!result.data?.parentID;
2396
+ return result.data?.parentID ?? null;
2330
2397
  } catch (error) {
2331
- return false;
2398
+ return null;
2332
2399
  }
2333
2400
  }
2334
2401
  function findLastCompactionTimestamp(messages) {
@@ -2570,7 +2637,7 @@ function resetOnCompaction(state) {
2570
2637
  }
2571
2638
 
2572
2639
  // lib/state/persistence.ts
2573
- function getStorageDir() {
2640
+ function getDefaultStorageDir() {
2574
2641
  return join2(
2575
2642
  process.env.XDG_DATA_HOME || join2(homedir2(), ".local", "share"),
2576
2643
  "opencode",
@@ -2579,14 +2646,33 @@ function getStorageDir() {
2579
2646
  "acp"
2580
2647
  );
2581
2648
  }
2582
- function getSessionFilePath(sessionId) {
2583
- return join2(getStorageDir(), `${sessionId}.json`);
2649
+ function resolveStorageDir(configured, projectDir) {
2650
+ const trimmed = configured?.trim();
2651
+ if (!trimmed) {
2652
+ return getDefaultStorageDir();
2653
+ }
2654
+ if (trimmed === "~") {
2655
+ return homedir2();
2656
+ }
2657
+ if (trimmed.startsWith("~/")) {
2658
+ return join2(homedir2(), trimmed.slice(2));
2659
+ }
2660
+ if (isAbsolute(trimmed)) {
2661
+ return trimmed;
2662
+ }
2663
+ return join2(projectDir, trimmed);
2664
+ }
2665
+ function getStorageDir(override) {
2666
+ return override || getDefaultStorageDir();
2584
2667
  }
2585
- async function writePersistedSessionState(sessionId, state, logger) {
2586
- const filePath = getSessionFilePath(sessionId);
2587
- const storageDir = getStorageDir();
2588
- if (!existsSync2(storageDir)) {
2589
- await fs.mkdir(storageDir, { recursive: true });
2668
+ function getSessionFilePath(sessionId, storageDir) {
2669
+ return join2(getStorageDir(storageDir), `${sessionId}.json`);
2670
+ }
2671
+ async function writePersistedSessionState(sessionId, state, logger, storageDir) {
2672
+ const filePath = getSessionFilePath(sessionId, storageDir);
2673
+ const dir = getStorageDir(storageDir);
2674
+ if (!existsSync2(dir)) {
2675
+ await fs.mkdir(dir, { recursive: true });
2590
2676
  }
2591
2677
  const content = JSON.stringify(state, null, 2);
2592
2678
  await fs.writeFile(filePath, content, "utf-8");
@@ -2628,11 +2714,11 @@ async function saveSessionState(sessionState, logger, sessionName) {
2628
2714
  modelProviderID: sessionState.modelProviderID,
2629
2715
  modelID: sessionState.modelID
2630
2716
  };
2631
- await writePersistedSessionState(sessionState.sessionId, state, logger);
2717
+ await writePersistedSessionState(sessionState.sessionId, state, logger, sessionState.storageDir);
2632
2718
  }
2633
- async function loadSessionState(sessionId, logger) {
2719
+ async function loadSessionState(sessionId, logger, storageDir) {
2634
2720
  try {
2635
- const filePath = getSessionFilePath(sessionId);
2721
+ const filePath = getSessionFilePath(sessionId, storageDir);
2636
2722
  if (!existsSync2(filePath)) {
2637
2723
  return null;
2638
2724
  }
@@ -2708,6 +2794,11 @@ async function loadSessionState(sessionId, logger) {
2708
2794
  }
2709
2795
  }
2710
2796
 
2797
+ // lib/state/state.ts
2798
+ import { existsSync as existsSync3 } from "fs";
2799
+ import { join as join3 } from "path";
2800
+ import { cwd } from "process";
2801
+
2711
2802
  // lib/message-ids.ts
2712
2803
  var MESSAGE_REF_REGEX = /^m(\d{4,5})$/;
2713
2804
  var BLOCK_REF_REGEX = /^b([1-9]\d*)$/;
@@ -4365,6 +4456,205 @@ function rebuildCompressionState(state, messages, config, logger) {
4365
4456
  return rebuilt;
4366
4457
  }
4367
4458
 
4459
+ // lib/state/fork-transfer.ts
4460
+ function extractIdentity(message) {
4461
+ const id = message?.info?.id;
4462
+ if (typeof id !== "string" || id.length === 0) {
4463
+ return null;
4464
+ }
4465
+ if (isSyntheticMessage(message)) {
4466
+ return null;
4467
+ }
4468
+ const role = message.info.role;
4469
+ const created = message.info.time?.created;
4470
+ if (typeof created !== "number") {
4471
+ return null;
4472
+ }
4473
+ return { id, role, created };
4474
+ }
4475
+ function buildSharedPrefixMapping(parentMessages, forkMessages) {
4476
+ const parentIds = parentMessages.map(extractIdentity).filter((x) => x !== null);
4477
+ const forkIds = forkMessages.map(extractIdentity).filter((x) => x !== null);
4478
+ const mapping = /* @__PURE__ */ new Map();
4479
+ const limit = Math.min(parentIds.length, forkIds.length);
4480
+ for (let i = 0; i < limit; i++) {
4481
+ const p = parentIds[i];
4482
+ const f = forkIds[i];
4483
+ if (p.created === f.created && p.role === f.role) {
4484
+ mapping.set(p.id, f.id);
4485
+ } else {
4486
+ break;
4487
+ }
4488
+ }
4489
+ return mapping;
4490
+ }
4491
+ function remapBoundaryRef(ref, mapping, forkByRawId, parentByRef) {
4492
+ const parsed = parseBoundaryId(ref);
4493
+ if (!parsed) {
4494
+ return ref;
4495
+ }
4496
+ if (parsed.kind === "compressed-block") {
4497
+ return parsed.ref;
4498
+ }
4499
+ const parentRawId = parentByRef.get(parsed.ref);
4500
+ if (!parentRawId) {
4501
+ return ref;
4502
+ }
4503
+ const forkRawId = mapping.get(parentRawId);
4504
+ if (!forkRawId) {
4505
+ return ref;
4506
+ }
4507
+ const forkRef = forkByRawId.get(forkRawId);
4508
+ return forkRef ?? ref;
4509
+ }
4510
+ function translateBlock(block, mapping, forkByRawId, parentByRef) {
4511
+ const anchorForkId = mapping.get(block.anchorMessageId);
4512
+ if (!anchorForkId) {
4513
+ return null;
4514
+ }
4515
+ const compressForkId = mapping.get(block.compressMessageId);
4516
+ if (!compressForkId) {
4517
+ return null;
4518
+ }
4519
+ const translateList = (ids) => ids.map((id) => mapping.get(id)).filter((id) => id !== void 0);
4520
+ const preserveList = (ids) => [...new Set(ids)];
4521
+ return {
4522
+ ...block,
4523
+ anchorMessageId: anchorForkId,
4524
+ compressMessageId: compressForkId,
4525
+ directMessageIds: translateList(block.directMessageIds),
4526
+ effectiveMessageIds: translateList(block.effectiveMessageIds),
4527
+ directToolIds: preserveList(block.directToolIds),
4528
+ effectiveToolIds: preserveList(block.effectiveToolIds),
4529
+ startId: remapBoundaryRef(block.startId, mapping, forkByRawId, parentByRef),
4530
+ endId: remapBoundaryRef(block.endId, mapping, forkByRawId, parentByRef)
4531
+ };
4532
+ }
4533
+ function translateByMessageId(parentByMessageId, mapping, translatedBlockIds) {
4534
+ const result = /* @__PURE__ */ new Map();
4535
+ for (const [parentMsgId, entry] of Object.entries(parentByMessageId)) {
4536
+ if (!entry || typeof entry !== "object") {
4537
+ continue;
4538
+ }
4539
+ const forkMsgId = mapping.get(parentMsgId);
4540
+ if (!forkMsgId) {
4541
+ continue;
4542
+ }
4543
+ const allBlockIds = (entry.allBlockIds ?? []).filter((id) => translatedBlockIds.has(id));
4544
+ const activeBlockIds = (entry.activeBlockIds ?? []).filter(
4545
+ (id) => translatedBlockIds.has(id)
4546
+ );
4547
+ result.set(forkMsgId, {
4548
+ tokenCount: typeof entry.tokenCount === "number" ? entry.tokenCount : 0,
4549
+ allBlockIds,
4550
+ activeBlockIds
4551
+ });
4552
+ }
4553
+ return result;
4554
+ }
4555
+ async function recoverFromParentState(client, state, forkMessages, parentId, logger) {
4556
+ const parentState = await loadSessionState(parentId, logger);
4557
+ if (!parentState) {
4558
+ logger.info("fork-transfer: no parent state found, falling back to replay", {
4559
+ parentId
4560
+ });
4561
+ return 0;
4562
+ }
4563
+ const parentMessagesState = parentState.prune?.messages;
4564
+ const parentBlocks = parentMessagesState?.blocksById;
4565
+ if (!parentBlocks) {
4566
+ logger.info("fork-transfer: parent state has no blocks, falling back to replay", {
4567
+ parentId
4568
+ });
4569
+ return 0;
4570
+ }
4571
+ const parentBlockEntries = Object.entries(parentBlocks).filter(
4572
+ ([id, block]) => Number.isInteger(Number.parseInt(id, 10)) && Number.parseInt(id, 10) >= 1 && !!block
4573
+ );
4574
+ if (parentBlockEntries.length === 0) {
4575
+ logger.info("fork-transfer: parent state has no blocks, falling back to replay", {
4576
+ parentId
4577
+ });
4578
+ return 0;
4579
+ }
4580
+ let parentMessages;
4581
+ try {
4582
+ const response = await client.session.messages({ path: { id: parentId } });
4583
+ parentMessages = filterMessages(response?.data || response);
4584
+ } catch (error) {
4585
+ logger.warn("fork-transfer: failed to fetch parent messages, falling back to replay", {
4586
+ parentId,
4587
+ error: error?.message || String(error)
4588
+ });
4589
+ return 0;
4590
+ }
4591
+ const mapping = buildSharedPrefixMapping(parentMessages, forkMessages);
4592
+ if (mapping.size === 0) {
4593
+ logger.info("fork-transfer: no shared prefix detected, falling back to replay", {
4594
+ parentId
4595
+ });
4596
+ return 0;
4597
+ }
4598
+ assignMessageRefs(state, forkMessages);
4599
+ const forkByRawId = state.messageIds.byRawId;
4600
+ const parentByRef = new Map(Object.entries(parentState.messageIds?.byRef ?? {}));
4601
+ const translatedBlocks = /* @__PURE__ */ new Map();
4602
+ let skipped = 0;
4603
+ for (const [blockIdStr, block] of parentBlockEntries) {
4604
+ const blockId = Number.parseInt(blockIdStr, 10);
4605
+ const translated = translateBlock(block, mapping, forkByRawId, parentByRef);
4606
+ if (translated) {
4607
+ translatedBlocks.set(blockId, translated);
4608
+ } else {
4609
+ skipped++;
4610
+ }
4611
+ }
4612
+ if (translatedBlocks.size === 0) {
4613
+ logger.info("fork-transfer: no blocks translatable, falling back to replay", {
4614
+ parentId,
4615
+ parentBlockCount: parentBlockEntries.length,
4616
+ mappingSize: mapping.size
4617
+ });
4618
+ return 0;
4619
+ }
4620
+ const activeCount = Array.from(translatedBlocks.values()).filter((b) => b.active).length;
4621
+ if (activeCount === 0) {
4622
+ logger.info("fork-transfer: no active blocks translatable, falling back to replay", {
4623
+ parentId,
4624
+ recoveredBlocks: translatedBlocks.size,
4625
+ skippedBlocks: skipped,
4626
+ mappingSize: mapping.size
4627
+ });
4628
+ return 0;
4629
+ }
4630
+ const translatedByMessageId = translateByMessageId(
4631
+ parentMessagesState.byMessageId ?? {},
4632
+ mapping,
4633
+ new Set(translatedBlocks.keys())
4634
+ );
4635
+ state.prune.messages = loadPruneMessagesState({
4636
+ byMessageId: Object.fromEntries(translatedByMessageId),
4637
+ blocksById: Object.fromEntries(
4638
+ Array.from(translatedBlocks.entries()).map(
4639
+ ([id, block]) => [String(id), block]
4640
+ )
4641
+ ),
4642
+ activeBlockIds: [],
4643
+ activeByAnchorMessageId: {},
4644
+ nextBlockId: 1,
4645
+ nextRunId: 1,
4646
+ markedForCleanup: []
4647
+ });
4648
+ logger.info("fork-transfer: recovered compression state from parent", {
4649
+ parentId,
4650
+ recoveredBlocks: translatedBlocks.size,
4651
+ activeBlocks: activeCount,
4652
+ skippedBlocks: skipped,
4653
+ mappingSize: mapping.size
4654
+ });
4655
+ return activeCount;
4656
+ }
4657
+
4368
4658
  // lib/state/state.ts
4369
4659
  async function updatePerTurnState(state, logger, messages) {
4370
4660
  const lastCompactionTimestamp = findLastCompactionTimestamp(messages);
@@ -4384,10 +4674,12 @@ async function updatePerTurnState(state, logger, messages) {
4384
4674
  }
4385
4675
  var REGISTRY_SOFT_CAP = 32;
4386
4676
  var SessionStateRegistry = class {
4387
- constructor(logger) {
4677
+ constructor(logger, projectDir) {
4388
4678
  this.logger = logger;
4679
+ this.projectDir = projectDir;
4389
4680
  }
4390
4681
  logger;
4682
+ projectDir;
4391
4683
  states = /* @__PURE__ */ new Map();
4392
4684
  compressionTiming = {
4393
4685
  startsByCallId: /* @__PURE__ */ new Map(),
@@ -4440,7 +4732,8 @@ var SessionStateRegistry = class {
4440
4732
  sessionId,
4441
4733
  this.logger,
4442
4734
  messages,
4443
- config
4735
+ config,
4736
+ this.projectDir
4444
4737
  );
4445
4738
  } catch (err) {
4446
4739
  this.logger.error("Failed to initialize session state", {
@@ -4504,6 +4797,7 @@ function createSessionState() {
4504
4797
  modelProviderID: void 0,
4505
4798
  modelID: void 0,
4506
4799
  systemPromptTokens: void 0,
4800
+ storageDir: void 0,
4507
4801
  qualityGateRetryPending: false
4508
4802
  };
4509
4803
  }
@@ -4545,24 +4839,39 @@ function resetSessionState(state) {
4545
4839
  state.modelProviderID = void 0;
4546
4840
  state.modelID = void 0;
4547
4841
  state.systemPromptTokens = void 0;
4842
+ state.storageDir = void 0;
4548
4843
  state.qualityGateRetryPending = false;
4549
4844
  }
4550
- async function ensureSessionInitialized(client, state, sessionId, logger, messages, config) {
4845
+ async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
4551
4846
  if (state.sessionId === sessionId) {
4552
4847
  return;
4553
4848
  }
4554
4849
  resetSessionState(state);
4555
4850
  state.sessionId = sessionId;
4556
- const isSubAgent = await isSubAgentSession(client, sessionId);
4557
- state.isSubAgent = isSubAgent;
4851
+ state.storageDir = config?.storagePath ? resolveStorageDir(config.storagePath, projectDir ?? cwd()) : void 0;
4852
+ const parentId = await getForkParentId(client, sessionId);
4853
+ state.isSubAgent = parentId !== null;
4558
4854
  state.lastCompaction = findLastCompactionTimestamp(messages);
4559
4855
  state.currentTurn = countTurns(state, messages);
4560
4856
  state.nudges.turnNudgeAnchors = collectTurnNudgeAnchors(messages);
4561
- const persisted = await loadSessionState(sessionId, logger);
4857
+ const persisted = await loadSessionState(sessionId, logger, state.storageDir);
4562
4858
  if (persisted === null) {
4859
+ const defaultPath = join3(getDefaultStorageDir(), `${sessionId}.json`);
4860
+ if (state.storageDir && existsSync3(defaultPath)) {
4861
+ logger.warn(
4862
+ "storagePath is set but no valid state was found there; a state file exists at the default location \u2014 move it manually to keep history",
4863
+ { sessionId, storageDir: state.storageDir, defaultPath }
4864
+ );
4865
+ }
4563
4866
  if (config) {
4564
- const rebuilt = rebuildCompressionState(state, messages, config, logger);
4565
- if (rebuilt > 0) {
4867
+ let recovered = 0;
4868
+ if (parentId) {
4869
+ recovered = await recoverFromParentState(client, state, messages, parentId, logger);
4870
+ }
4871
+ if (recovered === 0) {
4872
+ recovered = rebuildCompressionState(state, messages, config, logger);
4873
+ }
4874
+ if (recovered > 0) {
4566
4875
  await saveSessionState(state, logger);
4567
4876
  }
4568
4877
  }
@@ -6647,10 +6956,11 @@ function resolveCompressOverrides(config, providerId, modelId) {
6647
6956
  }
6648
6957
  const { models: _models, ...providerFields } = providerEntry;
6649
6958
  const modelEntry = modelId !== void 0 ? providerEntry.models?.[modelId] : void 0;
6650
- if (modelEntry) {
6651
- return { ...providerFields, ...modelEntry };
6959
+ const merged = modelEntry ? { ...providerFields, ...modelEntry } : providerFields;
6960
+ if (providerFields.reasoning !== void 0 || modelEntry?.reasoning !== void 0) {
6961
+ merged.reasoning = { ...providerFields.reasoning, ...modelEntry?.reasoning };
6652
6962
  }
6653
- return providerFields;
6963
+ return merged;
6654
6964
  }
6655
6965
  function resolveMinNudgeContextPercent(config, providerId, modelId) {
6656
6966
  const overrides = resolveCompressOverrides(config, providerId, modelId);
@@ -6667,19 +6977,26 @@ function resolveMinNudgeFloorTokens(config, modelContextLimit, providerId, model
6667
6977
  const clamped = Math.min(100, Math.max(0, percent));
6668
6978
  return Math.round(clamped / 100 * modelContextLimit);
6669
6979
  }
6670
- var OVERRIDE_BLANKET_APPLY_EXCLUDE = /* @__PURE__ */ new Set(["maxContextLimit"]);
6980
+ var OVERRIDE_BLANKET_APPLY_EXCLUDE = /* @__PURE__ */ new Set([
6981
+ "maxContextLimit",
6982
+ "reasoning"
6983
+ ]);
6671
6984
  function applyCompressOverrides(config, providerId, modelId) {
6672
6985
  const overrides = resolveCompressOverrides(config, providerId, modelId);
6673
6986
  const applied = Object.keys(overrides).filter(
6674
6987
  (key) => !OVERRIDE_BLANKET_APPLY_EXCLUDE.has(key)
6675
6988
  );
6676
- if (applied.length === 0 || !config.compress) {
6989
+ const reasoningOverride = overrides.reasoning;
6990
+ if (applied.length === 0 && reasoningOverride === void 0 || !config.compress) {
6677
6991
  return config;
6678
6992
  }
6679
6993
  const effectiveCompress = { ...config.compress };
6680
6994
  for (const key of applied) {
6681
6995
  effectiveCompress[key] = overrides[key];
6682
6996
  }
6997
+ if (reasoningOverride !== void 0) {
6998
+ effectiveCompress.reasoning = { ...config.compress.reasoning, ...reasoningOverride };
6999
+ }
6683
7000
  return { ...config, compress: effectiveCompress };
6684
7001
  }
6685
7002
  function addAnchor(anchorMessageIds, anchorMessageId, anchorMessageIndex, messages, interval) {
@@ -8262,6 +8579,46 @@ function stripStaleMetadata(messages) {
8262
8579
  });
8263
8580
  });
8264
8581
  }
8582
+ function dropCompressReasoning(messages, threshold) {
8583
+ const lastUserMessage = getLastUserMessage(messages);
8584
+ if (!lastUserMessage || lastUserMessage.info.role !== "user") {
8585
+ return 0;
8586
+ }
8587
+ const lastUserIndex = messages.lastIndexOf(lastUserMessage);
8588
+ if (lastUserIndex <= 0) {
8589
+ return 0;
8590
+ }
8591
+ let removed = 0;
8592
+ for (let i = 0; i < lastUserIndex; i++) {
8593
+ const message = messages[i];
8594
+ if (!message || message.info.role !== "assistant") {
8595
+ continue;
8596
+ }
8597
+ const parts = Array.isArray(message.parts) ? message.parts : [];
8598
+ let hasCompressTool = false;
8599
+ let reasoningLength = 0;
8600
+ for (const part of parts) {
8601
+ if (part.type === "tool" && part.tool === "compress") {
8602
+ hasCompressTool = true;
8603
+ } else if (part.type === "reasoning") {
8604
+ reasoningLength += part.text.length;
8605
+ }
8606
+ }
8607
+ if (!hasCompressTool) {
8608
+ continue;
8609
+ }
8610
+ if (reasoningLength <= threshold) {
8611
+ continue;
8612
+ }
8613
+ const filtered = parts.filter((part) => part.type !== "reasoning");
8614
+ if (filtered.length === parts.length) {
8615
+ continue;
8616
+ }
8617
+ message.parts = filtered;
8618
+ removed += parts.length - filtered.length;
8619
+ }
8620
+ return removed;
8621
+ }
8265
8622
 
8266
8623
  // lib/messages/priority.ts
8267
8624
  function buildPriorityMap(_config, _state, _messages) {
@@ -9375,10 +9732,10 @@ function hideFailedCompressCalls(messages) {
9375
9732
 
9376
9733
  // lib/logger.ts
9377
9734
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
9378
- import { join as join3 } from "path";
9379
- import { existsSync as existsSync3 } from "fs";
9735
+ import { join as join4 } from "path";
9736
+ import { existsSync as existsSync4 } from "fs";
9380
9737
  import { homedir as homedir3 } from "os";
9381
- var LOG_VERSION = true ? "1.14.27" : "dev";
9738
+ var LOG_VERSION = true ? "1.15.0-pr.376.118" : "dev";
9382
9739
  var LEVEL_RANK = {
9383
9740
  debug: 10,
9384
9741
  info: 20,
@@ -9398,8 +9755,8 @@ var Logger = class {
9398
9755
  constructor(enabled, level) {
9399
9756
  const resolved = level !== void 0 && LEVEL_RANK[level] !== void 0 ? level : void 0;
9400
9757
  this.level = resolved ?? (enabled ? "debug" : "warn");
9401
- const configHome = process.env.XDG_CONFIG_HOME || join3(homedir3(), ".config");
9402
- this.logDir = join3(configHome, "opencode", "logs", "acp");
9758
+ const configHome = process.env.XDG_CONFIG_HOME || join4(homedir3(), ".config");
9759
+ this.logDir = join4(configHome, "opencode", "logs", "acp");
9403
9760
  }
9404
9761
  /** Back-compat view of the old flag: true only at full debug verbosity. */
9405
9762
  get enabled() {
@@ -9409,7 +9766,7 @@ var Logger = class {
9409
9766
  return LEVEL_RANK[level] >= LEVEL_RANK[this.level];
9410
9767
  }
9411
9768
  async ensureLogDir() {
9412
- if (!existsSync3(this.logDir)) {
9769
+ if (!existsSync4(this.logDir)) {
9413
9770
  await mkdir2(this.logDir, { recursive: true });
9414
9771
  }
9415
9772
  }
@@ -9461,11 +9818,11 @@ var Logger = class {
9461
9818
  const dataStr = this.formatData(data);
9462
9819
  const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""} | v=${LOG_VERSION}
9463
9820
  `;
9464
- const dailyLogDir = join3(this.logDir, "daily");
9465
- if (!existsSync3(dailyLogDir)) {
9821
+ const dailyLogDir = join4(this.logDir, "daily");
9822
+ if (!existsSync4(dailyLogDir)) {
9466
9823
  await mkdir2(dailyLogDir, { recursive: true });
9467
9824
  }
9468
- const logFile = join3(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
9825
+ const logFile = join4(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
9469
9826
  await writeFile2(logFile, logLine, { flag: "a" });
9470
9827
  } catch (error) {
9471
9828
  }
@@ -9576,18 +9933,18 @@ var Logger = class {
9576
9933
  async saveContext(sessionId, messages) {
9577
9934
  if (this.level !== "debug") return;
9578
9935
  try {
9579
- const contextDir = join3(this.logDir, "context", sessionId);
9580
- if (!existsSync3(contextDir)) {
9936
+ const contextDir = join4(this.logDir, "context", sessionId);
9937
+ if (!existsSync4(contextDir)) {
9581
9938
  await mkdir2(contextDir, { recursive: true });
9582
9939
  }
9583
9940
  const minimized = this.minimizeForDebug(messages).filter(
9584
9941
  (msg) => msg.parts && msg.parts.length > 0
9585
9942
  );
9586
9943
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9587
- const contextFile = join3(contextDir, `${timestamp}.json`);
9944
+ const contextFile = join4(contextDir, `${timestamp}.json`);
9588
9945
  await writeFile2(contextFile, JSON.stringify(minimized, null, 2));
9589
- const versionFile = join3(contextDir, "_version");
9590
- if (!existsSync3(versionFile)) {
9946
+ const versionFile = join4(contextDir, "_version");
9947
+ if (!existsSync4(versionFile)) {
9591
9948
  await writeFile2(versionFile, LOG_VERSION);
9592
9949
  }
9593
9950
  } catch (error) {
@@ -9596,8 +9953,8 @@ var Logger = class {
9596
9953
  };
9597
9954
 
9598
9955
  // lib/prompts/store.ts
9599
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, statSync as statSync2 } from "fs";
9600
- import { join as join4, dirname as dirname2 } from "path";
9956
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, statSync as statSync2 } from "fs";
9957
+ import { join as join5, dirname as dirname2 } from "path";
9601
9958
  import { homedir as homedir4 } from "os";
9602
9959
 
9603
9960
  // lib/prompts/system.ts
@@ -9927,8 +10284,8 @@ function createBundledRuntimePrompts() {
9927
10284
  function findOpencodeDir2(startDir) {
9928
10285
  let current = startDir;
9929
10286
  while (current !== "/") {
9930
- const candidate = join4(current, ".opencode");
9931
- if (existsSync4(candidate)) {
10287
+ const candidate = join5(current, ".opencode");
10288
+ if (existsSync5(candidate)) {
9932
10289
  try {
9933
10290
  if (statSync2(candidate).isDirectory()) {
9934
10291
  return candidate;
@@ -9945,13 +10302,13 @@ function findOpencodeDir2(startDir) {
9945
10302
  return null;
9946
10303
  }
9947
10304
  function resolvePromptPaths(workingDirectory) {
9948
- const configHome = process.env.XDG_CONFIG_HOME || join4(homedir4(), ".config");
9949
- const globalRoot = join4(configHome, "opencode", "acp-prompts");
9950
- const defaultsDir = join4(globalRoot, "defaults");
9951
- const globalOverridesDir = join4(globalRoot, "overrides");
9952
- const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join4(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
10305
+ const configHome = process.env.XDG_CONFIG_HOME || join5(homedir4(), ".config");
10306
+ const globalRoot = join5(configHome, "opencode", "acp-prompts");
10307
+ const defaultsDir = join5(globalRoot, "defaults");
10308
+ const globalOverridesDir = join5(globalRoot, "overrides");
10309
+ const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join5(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
9953
10310
  const opencodeDir = findOpencodeDir2(workingDirectory);
9954
- const projectOverridesDir = opencodeDir ? join4(opencodeDir, "acp-prompts", "overrides") : null;
10311
+ const projectOverridesDir = opencodeDir ? join5(opencodeDir, "acp-prompts", "overrides") : null;
9955
10312
  return {
9956
10313
  defaultsDir,
9957
10314
  globalOverridesDir,
@@ -10051,7 +10408,7 @@ function buildDefaultsReadmeContent() {
10051
10408
  `;
10052
10409
  }
10053
10410
  function readFileIfExists(filePath) {
10054
- if (!existsSync4(filePath)) {
10411
+ if (!existsSync5(filePath)) {
10055
10412
  return null;
10056
10413
  }
10057
10414
  try {
@@ -10122,16 +10479,16 @@ var PromptStore = class {
10122
10479
  const candidates = [];
10123
10480
  if (this.paths.projectOverridesDir) {
10124
10481
  candidates.push({
10125
- path: join4(this.paths.projectOverridesDir, fileName)
10482
+ path: join5(this.paths.projectOverridesDir, fileName)
10126
10483
  });
10127
10484
  }
10128
10485
  if (this.paths.configDirOverridesDir) {
10129
10486
  candidates.push({
10130
- path: join4(this.paths.configDirOverridesDir, fileName)
10487
+ path: join5(this.paths.configDirOverridesDir, fileName)
10131
10488
  });
10132
10489
  }
10133
10490
  candidates.push({
10134
- path: join4(this.paths.globalOverridesDir, fileName)
10491
+ path: join5(this.paths.globalOverridesDir, fileName)
10135
10492
  });
10136
10493
  return candidates;
10137
10494
  }
@@ -10154,7 +10511,7 @@ var PromptStore = class {
10154
10511
  const managedContent = buildDefaultPromptFileContent(
10155
10512
  bundledEditable || BUNDLED_EDITABLE_PROMPTS[definition.runtimeField]
10156
10513
  );
10157
- const filePath = join4(this.paths.defaultsDir, definition.fileName);
10514
+ const filePath = join5(this.paths.defaultsDir, definition.fileName);
10158
10515
  try {
10159
10516
  const existing = readFileIfExists(filePath);
10160
10517
  if (existing === managedContent) {
@@ -10168,7 +10525,7 @@ var PromptStore = class {
10168
10525
  });
10169
10526
  }
10170
10527
  }
10171
- const readmePath = join4(this.paths.defaultsDir, DEFAULTS_README_FILE);
10528
+ const readmePath = join5(this.paths.defaultsDir, DEFAULTS_README_FILE);
10172
10529
  const readmeContent = buildDefaultsReadmeContent();
10173
10530
  try {
10174
10531
  const existing = readFileIfExists(readmePath);
@@ -10465,8 +10822,8 @@ ${report}`;
10465
10822
 
10466
10823
  // lib/commands/export.ts
10467
10824
  import * as fs2 from "fs/promises";
10468
- import { existsSync as existsSync5 } from "fs";
10469
- import { dirname as dirname3, isAbsolute, join as join5, resolve } from "path";
10825
+ import { existsSync as existsSync6 } from "fs";
10826
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, resolve } from "path";
10470
10827
  var ALL_TIERS = [1, 2, 3];
10471
10828
  var TIER_NAMES = {
10472
10829
  1: "Tier 1 \u2014 Capture",
@@ -10577,9 +10934,9 @@ function parseTiers(raw) {
10577
10934
  }
10578
10935
  return result;
10579
10936
  }
10580
- function resolveDefaultOutputPath(sessionId, cwd) {
10937
+ function resolveDefaultOutputPath(sessionId, cwd2) {
10581
10938
  const short = (sessionId || "session").slice(0, 8);
10582
- return join5(cwd, ".opencode", `acp-export-${short}.md`);
10939
+ return join6(cwd2, ".opencode", `acp-export-${short}.md`);
10583
10940
  }
10584
10941
  function formatTokens2(n) {
10585
10942
  if (!Number.isFinite(n) || n <= 0) return "0";
@@ -10706,15 +11063,15 @@ async function handleExportCommand(ctx, args) {
10706
11063
  await sendExportNotice(client, sessionId, ctx, markdown);
10707
11064
  return;
10708
11065
  }
10709
- const cwd = ctx.workingDirectory || process.cwd();
10710
- const targetPath = options.outputPath !== "" ? isAbsolute(options.outputPath) ? options.outputPath : resolve(cwd, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd);
11066
+ const cwd2 = ctx.workingDirectory || process.cwd();
11067
+ const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
10711
11068
  try {
10712
11069
  const dir = dirname3(targetPath);
10713
- if (!existsSync5(dir)) {
11070
+ if (!existsSync6(dir)) {
10714
11071
  await fs2.mkdir(dir, { recursive: true });
10715
11072
  }
10716
11073
  const flag = options.append ? "a" : "w";
10717
- if (options.append && existsSync5(targetPath)) {
11074
+ if (options.append && existsSync6(targetPath)) {
10718
11075
  await fs2.appendFile(targetPath, `
10719
11076
 
10720
11077
  ---
@@ -11370,6 +11727,24 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
11370
11727
  return;
11371
11728
  }
11372
11729
  stripHallucinations(output.messages);
11730
+ const dropReasoningModel = lastUserMessage?.info?.model;
11731
+ const reasoningConfig = applyCompressOverrides(
11732
+ config,
11733
+ dropReasoningModel?.providerID ?? state.modelProviderID,
11734
+ dropReasoningModel?.modelID ?? state.modelID
11735
+ ).compress.reasoning;
11736
+ if (reasoningConfig?.drop !== false) {
11737
+ const droppedReasoning = dropCompressReasoning(
11738
+ output.messages,
11739
+ reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
11740
+ );
11741
+ if (droppedReasoning > 0) {
11742
+ logger.debug("compress.reasoning: dropped oversized reasoning parts", {
11743
+ dropped: droppedReasoning,
11744
+ threshold: reasoningConfig?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
11745
+ });
11746
+ }
11747
+ }
11373
11748
  ensureBuiltinFiltersRegistered();
11374
11749
  applyMessageFilters(output.messages, config.messageFilters, logger, {
11375
11750
  sessionId: state.sessionId ?? "",
@@ -11639,7 +12014,7 @@ function findBiliProxyProviders(provider) {
11639
12014
 
11640
12015
  // lib/update.ts
11641
12016
  import { readFile as readFile2, rm } from "fs/promises";
11642
- import { basename, dirname as dirname4, join as join6 } from "path";
12017
+ import { basename, dirname as dirname4, join as join7 } from "path";
11643
12018
  import { fileURLToPath } from "url";
11644
12019
  var PACKAGE_NAME = "opencode-acp";
11645
12020
  function startAutoUpdate(ctx, enabled, logger) {
@@ -11686,7 +12061,7 @@ async function checkAutoUpdate(signal, logger) {
11686
12061
  logger?.info("Auto-update skipped: package dir not found");
11687
12062
  return { updated: false };
11688
12063
  }
11689
- const pkg = await readPackageJson(join6(packageDir, "package.json"));
12064
+ const pkg = await readPackageJson(join7(packageDir, "package.json"));
11690
12065
  if (!pkg?.name || !pkg.version) {
11691
12066
  logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
11692
12067
  return { updated: false };
@@ -11727,7 +12102,7 @@ async function checkAutoUpdate(signal, logger) {
11727
12102
  async function findPackageDir(name) {
11728
12103
  let dir = dirname4(fileURLToPath(import.meta.url));
11729
12104
  for (; ; ) {
11730
- const pkg = await readPackageJson(join6(dir, "package.json"));
12105
+ const pkg = await readPackageJson(join7(dir, "package.json"));
11731
12106
  if (pkg?.name === name) return dir;
11732
12107
  const parent = dirname4(dir);
11733
12108
  if (parent === dir) return void 0;
@@ -11739,7 +12114,7 @@ async function updateTarget(packageDir, name) {
11739
12114
  const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
11740
12115
  if (basename(nodeModulesDir) !== "node_modules") return void 0;
11741
12116
  const wrapperDir = dirname4(nodeModulesDir);
11742
- const wrapperPkg = await readPackageJson(join6(wrapperDir, "package.json"));
12117
+ const wrapperPkg = await readPackageJson(join7(wrapperDir, "package.json"));
11743
12118
  const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
11744
12119
  if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
11745
12120
  return { removeDir: wrapperDir, spec };
@@ -11851,14 +12226,14 @@ var server = (async (ctx) => {
11851
12226
  }
11852
12227
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
11853
12228
  logger.info("ACP plugin initialized", {
11854
- version: true ? "1.14.27" : "dev",
12229
+ version: true ? "1.15.0-pr.376.118" : "dev",
11855
12230
  workspace: ctx.directory,
11856
12231
  logLevel: logger.level,
11857
12232
  debug: config.debug,
11858
12233
  autoUpdate: config.autoUpdate,
11859
12234
  secureMode: isSecureMode()
11860
12235
  });
11861
- const registry4 = new SessionStateRegistry(logger);
12236
+ const registry4 = new SessionStateRegistry(logger, ctx.directory);
11862
12237
  const prompts = new PromptStore(logger, ctx.directory, config.experimental.customPrompts);
11863
12238
  const hostPermissions = {
11864
12239
  global: void 0,