opencode-acp 1.18.3 → 1.18.4-pr.483.216

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
@@ -916,6 +916,10 @@ var VALID_CONFIG_KEYS = /* @__PURE__ */ new Set([
916
916
  "compress.reasoning",
917
917
  "compress.reasoning.drop",
918
918
  "compress.reasoning.threshold",
919
+ "compress.images",
920
+ "compress.images.enabled",
921
+ "compress.images.minBytes",
922
+ "compress.images.ttlDays",
919
923
  "compress.completionReserveTokens",
920
924
  "gc",
921
925
  "gc.algorithm",
@@ -1308,6 +1312,37 @@ function validateConfigTypes(config) {
1308
1312
  }
1309
1313
  }
1310
1314
  }
1315
+ if (compress.images !== void 0) {
1316
+ if (typeof compress.images !== "object" || compress.images === null || Array.isArray(compress.images)) {
1317
+ errors.push({
1318
+ key: "compress.images",
1319
+ expected: "object { enabled?: boolean; minBytes?: integer (>= 0); ttlDays?: number (> 0) }",
1320
+ actual: JSON.stringify(compress.images)
1321
+ });
1322
+ } else {
1323
+ if (compress.images.enabled !== void 0 && typeof compress.images.enabled !== "boolean") {
1324
+ errors.push({
1325
+ key: "compress.images.enabled",
1326
+ expected: "boolean",
1327
+ actual: typeof compress.images.enabled
1328
+ });
1329
+ }
1330
+ if (compress.images.minBytes !== void 0 && (typeof compress.images.minBytes !== "number" || !Number.isInteger(compress.images.minBytes) || compress.images.minBytes < 0)) {
1331
+ errors.push({
1332
+ key: "compress.images.minBytes",
1333
+ expected: "integer (>= 0)",
1334
+ actual: JSON.stringify(compress.images.minBytes)
1335
+ });
1336
+ }
1337
+ if (compress.images.ttlDays !== void 0 && (typeof compress.images.ttlDays !== "number" || !Number.isFinite(compress.images.ttlDays) || compress.images.ttlDays <= 0)) {
1338
+ errors.push({
1339
+ key: "compress.images.ttlDays",
1340
+ expected: "number (> 0)",
1341
+ actual: JSON.stringify(compress.images.ttlDays)
1342
+ });
1343
+ }
1344
+ }
1345
+ }
1311
1346
  if (compress.completionReserveTokens !== void 0 && typeof compress.completionReserveTokens !== "number") {
1312
1347
  errors.push({
1313
1348
  key: "compress.completionReserveTokens",
@@ -1695,6 +1730,11 @@ var DEFAULT_COMPRESS_REASONING = {
1695
1730
  drop: true,
1696
1731
  threshold: 2048
1697
1732
  };
1733
+ var DEFAULT_COMPRESS_IMAGES = {
1734
+ enabled: true,
1735
+ minBytes: 16384,
1736
+ ttlDays: 7
1737
+ };
1698
1738
  var DEFAULT_PROTECTED_TOOLS = [
1699
1739
  "task",
1700
1740
  "skill",
@@ -1793,7 +1833,8 @@ var defaultConfig = {
1793
1833
  preserveRecentMessages: 5,
1794
1834
  preserveRecentTokens: 5e3,
1795
1835
  preserveLastUserMessage: true,
1796
- reasoning: { ...DEFAULT_COMPRESS_REASONING }
1836
+ reasoning: { ...DEFAULT_COMPRESS_REASONING },
1837
+ images: { ...DEFAULT_COMPRESS_IMAGES }
1797
1838
  },
1798
1839
  gc: {
1799
1840
  algorithm: "truncate",
@@ -1971,6 +2012,11 @@ function mergeCompress(base, override) {
1971
2012
  drop: override.reasoning?.drop ?? base.reasoning?.drop ?? DEFAULT_COMPRESS_REASONING.drop,
1972
2013
  threshold: override.reasoning?.threshold ?? base.reasoning?.threshold ?? DEFAULT_COMPRESS_REASONING.threshold
1973
2014
  },
2015
+ images: {
2016
+ enabled: override.images?.enabled ?? base.images?.enabled ?? DEFAULT_COMPRESS_IMAGES.enabled,
2017
+ minBytes: override.images?.minBytes ?? base.images?.minBytes ?? DEFAULT_COMPRESS_IMAGES.minBytes,
2018
+ ttlDays: override.images?.ttlDays ?? base.images?.ttlDays ?? DEFAULT_COMPRESS_IMAGES.ttlDays
2019
+ },
1974
2020
  completionReserveTokens: override.completionReserveTokens ?? base.completionReserveTokens
1975
2021
  };
1976
2022
  }
@@ -2908,7 +2954,8 @@ function saveSessionState(sessionState, logger, sessionName) {
2908
2954
  lastCompaction: sessionState.lastCompaction,
2909
2955
  modelContextLimit: sessionState.modelContextLimit,
2910
2956
  modelProviderID: sessionState.modelProviderID,
2911
- modelID: sessionState.modelID
2957
+ modelID: sessionState.modelID,
2958
+ imageArchive: sessionState.imageArchive
2912
2959
  };
2913
2960
  const key = saveQueueKey(sessionState.sessionId, sessionState.storageDir);
2914
2961
  let queue = saveQueues.get(key);
@@ -5075,7 +5122,9 @@ function createSessionState() {
5075
5122
  systemPromptTokens: void 0,
5076
5123
  storageDir: void 0,
5077
5124
  qualityGateRetryPending: false,
5078
- noContextLimitWarned: false
5125
+ noContextLimitWarned: false,
5126
+ imageArchive: {},
5127
+ imagePruneDone: false
5079
5128
  };
5080
5129
  }
5081
5130
  function resetSessionState(state) {
@@ -5119,6 +5168,8 @@ function resetSessionState(state) {
5119
5168
  state.storageDir = void 0;
5120
5169
  state.qualityGateRetryPending = false;
5121
5170
  state.noContextLimitWarned = false;
5171
+ state.imageArchive = {};
5172
+ state.imagePruneDone = false;
5122
5173
  }
5123
5174
  async function ensureSessionInitialized(client, state, sessionId, logger, messages, config, projectDir) {
5124
5175
  if (state.sessionId === sessionId) {
@@ -5197,8 +5248,21 @@ async function ensureSessionInitialized(client, state, sessionId, logger, messag
5197
5248
  state.nudges.compressBaselineSet = persisted.nudges.compressBaselineSet ?? false;
5198
5249
  state.stats = {
5199
5250
  pruneTokenCounter: persisted.stats?.pruneTokenCounter || 0,
5200
- totalPruneTokens: persisted.stats?.totalPruneTokens || 0
5251
+ totalPruneTokens: persisted.stats?.totalPruneTokens || 0,
5252
+ imagesArchived: persisted.stats?.imagesArchived,
5253
+ imageBytesArchived: persisted.stats?.imageBytesArchived
5201
5254
  };
5255
+ if (persisted.imageArchive && typeof persisted.imageArchive === "object") {
5256
+ for (const [rawId, entries] of Object.entries(persisted.imageArchive)) {
5257
+ if (!Array.isArray(entries)) continue;
5258
+ const valid = entries.filter(
5259
+ (e) => !!e && typeof e.path === "string" && typeof e.mediaType === "string" && typeof e.bytes === "number"
5260
+ );
5261
+ if (valid.length > 0) {
5262
+ state.imageArchive[rawId] = valid;
5263
+ }
5264
+ }
5265
+ }
5202
5266
  const persistedAny = persisted;
5203
5267
  if (persistedAny._persistedMessageIds) {
5204
5268
  state.messageIds = {
@@ -6611,6 +6675,156 @@ function buildPhantomSkipNotice(identification) {
6611
6675
  return `Skipped ${identification.phantomIndices.length} already-compressed ${noun} (${skippedNums}) \u2014 they are no-ops; the remaining entries were compressed.`;
6612
6676
  }
6613
6677
 
6678
+ // lib/compress/image-archive.ts
6679
+ import { createHash as createHash2 } from "crypto";
6680
+ import {
6681
+ existsSync as existsSync4,
6682
+ mkdirSync as mkdirSync2,
6683
+ readdirSync,
6684
+ rmdirSync,
6685
+ statSync as statSync2,
6686
+ unlinkSync,
6687
+ writeFileSync as writeFileSync2
6688
+ } from "fs";
6689
+ import { join as join4 } from "path";
6690
+ var DATA_URL_REGEX = /^data:(image\/[a-z0-9.+-]+);base64,(.+)$/is;
6691
+ var IMAGE_EXTENSIONS = {
6692
+ "image/png": "png",
6693
+ "image/jpeg": "jpg",
6694
+ "image/jpg": "jpg",
6695
+ "image/gif": "gif",
6696
+ "image/webp": "webp",
6697
+ "image/avif": "avif",
6698
+ "image/bmp": "bmp"
6699
+ };
6700
+ function sha8(input) {
6701
+ return createHash2("sha256").update(input).digest("hex").slice(0, 8);
6702
+ }
6703
+ function parseImageDataUrl(url) {
6704
+ if (typeof url !== "string") return void 0;
6705
+ const match = DATA_URL_REGEX.exec(url);
6706
+ if (!match) return void 0;
6707
+ return { mediaType: match[1].toLowerCase(), base64: match[2] };
6708
+ }
6709
+ function extForMediaType(mediaType) {
6710
+ return IMAGE_EXTENSIONS[mediaType.toLowerCase()];
6711
+ }
6712
+ function archiveDirName(sessionId) {
6713
+ let name = sessionId.replace(/[^a-zA-Z0-9_-]/g, "-").slice(0, 100);
6714
+ if (name !== sessionId) {
6715
+ name = `${name}-${sha8(sessionId)}`;
6716
+ }
6717
+ return name || "-";
6718
+ }
6719
+ function archiveRoot(storageDir, sessionId) {
6720
+ return join4(storageDir ?? getDefaultStorageDir(), "images", archiveDirName(sessionId));
6721
+ }
6722
+ function archiveSessionImages(state, messages, images, logger) {
6723
+ if (!images.enabled || !state.sessionId) return 0;
6724
+ const root = archiveRoot(state.storageDir, state.sessionId);
6725
+ let archived = 0;
6726
+ for (const message of messages) {
6727
+ const rawId = message.info.id;
6728
+ if (!rawId) continue;
6729
+ const parts = Array.isArray(message.parts) ? message.parts : [];
6730
+ for (let partIndex = 0; partIndex < parts.length; partIndex++) {
6731
+ const part = parts[partIndex];
6732
+ if (part.type !== "file") continue;
6733
+ const parsed = parseImageDataUrl(part.url);
6734
+ if (!parsed) continue;
6735
+ const ext = extForMediaType(parsed.mediaType);
6736
+ if (!ext) continue;
6737
+ const fileName = `${sha8(rawId)}-p${partIndex}-${sha8(parsed.base64)}.${ext}`;
6738
+ const filePath = join4(root, fileName);
6739
+ const existing = state.imageArchive[rawId]?.find((e) => e.path === filePath);
6740
+ if (existing) continue;
6741
+ let bytes;
6742
+ try {
6743
+ if (existsSync4(filePath)) {
6744
+ bytes = statSync2(filePath).size;
6745
+ } else {
6746
+ const buffer = Buffer.from(parsed.base64, "base64");
6747
+ bytes = buffer.byteLength;
6748
+ if (bytes < images.minBytes) continue;
6749
+ mkdirSync2(root, { recursive: true });
6750
+ writeFileSync2(filePath, buffer, { mode: 384 });
6751
+ }
6752
+ } catch (error) {
6753
+ logger.warn(
6754
+ `[#478] failed to archive image ${filePath}: ${error instanceof Error ? error.message : String(error)}`
6755
+ );
6756
+ continue;
6757
+ }
6758
+ const entry = {
6759
+ path: filePath,
6760
+ mediaType: parsed.mediaType,
6761
+ bytes
6762
+ };
6763
+ if (!state.imageArchive[rawId]) {
6764
+ state.imageArchive[rawId] = [];
6765
+ }
6766
+ state.imageArchive[rawId].push(entry);
6767
+ state.stats.imagesArchived = (state.stats.imagesArchived ?? 0) + 1;
6768
+ state.stats.imageBytesArchived = (state.stats.imageBytesArchived ?? 0) + bytes;
6769
+ archived++;
6770
+ }
6771
+ }
6772
+ return archived;
6773
+ }
6774
+ function pruneImageArchive(state, images, logger) {
6775
+ if (state.imagePruneDone || !state.sessionId) return;
6776
+ state.imagePruneDone = true;
6777
+ try {
6778
+ const root = archiveRoot(state.storageDir, state.sessionId);
6779
+ if (!existsSync4(root)) return;
6780
+ const cutoff = Date.now() - images.ttlDays * 864e5;
6781
+ const prunedPaths = /* @__PURE__ */ new Set();
6782
+ for (const name of readdirSync(root)) {
6783
+ const filePath = join4(root, name);
6784
+ try {
6785
+ if (statSync2(filePath).mtimeMs < cutoff) {
6786
+ unlinkSync(filePath);
6787
+ prunedPaths.add(filePath);
6788
+ }
6789
+ } catch {
6790
+ }
6791
+ }
6792
+ if (prunedPaths.size > 0) {
6793
+ for (const [rawId, entries] of Object.entries(state.imageArchive)) {
6794
+ const kept = entries.filter((e) => !prunedPaths.has(e.path));
6795
+ if (kept.length === 0) {
6796
+ delete state.imageArchive[rawId];
6797
+ } else if (kept.length !== entries.length) {
6798
+ state.imageArchive[rawId] = kept;
6799
+ }
6800
+ }
6801
+ logger.info(`[#478] TTL sweep removed ${prunedPaths.size} expired archived image(s)`);
6802
+ }
6803
+ if (readdirSync(root).length === 0) {
6804
+ rmdirSync(root);
6805
+ }
6806
+ } catch (error) {
6807
+ logger.warn(
6808
+ `[#478] image TTL sweep failed: ${error instanceof Error ? error.message : String(error)}`
6809
+ );
6810
+ }
6811
+ }
6812
+ function appendArchivedImagePointers(summary, selection, state, enabled) {
6813
+ if (!enabled) return summary;
6814
+ const lines = [];
6815
+ for (const messageId of selection.messageIds) {
6816
+ const entries = state.imageArchive[messageId];
6817
+ if (!entries || entries.length === 0) continue;
6818
+ const ref = state.messageIds.byRawId.get(messageId) ?? messageId;
6819
+ for (const entry of entries) {
6820
+ lines.push(`- ${ref}: ${entry.path} (${entry.mediaType}, ${entry.bytes} bytes)`);
6821
+ }
6822
+ }
6823
+ if (lines.length === 0) return summary;
6824
+ const heading = "\n\nImages captured in this range were archived to disk (their pixels are NOT summarized). Re-read them with your file tools if you need the pixels again:";
6825
+ return summary + heading + "\n" + lines.join("\n");
6826
+ }
6827
+
6614
6828
  // lib/compress/keep-markers.ts
6615
6829
  var KEEP_REGEX = /\[\[KEEP:(m\d+)\]\]/g;
6616
6830
  var REF_REGEX = /\[\[REF:(m\d+)\|([^\]]+)\]\]/g;
@@ -6785,12 +6999,12 @@ ${afterClose}`;
6785
6999
  }
6786
7000
 
6787
7001
  // lib/messages/utils.ts
6788
- import { createHash as createHash2 } from "crypto";
7002
+ import { createHash as createHash3 } from "crypto";
6789
7003
  var SUMMARY_ID_HASH_LENGTH = 16;
6790
7004
  var DCP_PAIRED_TAG_REGEX = /<(?:dcp|acp)[^>]*>[\s\S]*?<\/(?:dcp|acp)[^>]*>/gi;
6791
7005
  var DCP_UNPAIRED_TAG_REGEX = /<\/?(?:dcp|acp)[^>]*>/gi;
6792
7006
  var generateStableId = (prefix, seed) => {
6793
- const hash = createHash2("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH);
7007
+ const hash = createHash3("sha256").update(seed).digest("hex").slice(0, SUMMARY_ID_HASH_LENGTH);
6794
7008
  return `${prefix}_${hash}`;
6795
7009
  };
6796
7010
  var createSyntheticMessage = (baseMessage, content, stableSeed, role = "user") => {
@@ -7203,7 +7417,8 @@ function resolveMinNudgeFloorTokens(config, modelContextLimit, providerId, model
7203
7417
  }
7204
7418
  var OVERRIDE_BLANKET_APPLY_EXCLUDE = /* @__PURE__ */ new Set([
7205
7419
  "maxContextLimit",
7206
- "reasoning"
7420
+ "reasoning",
7421
+ "images"
7207
7422
  ]);
7208
7423
  function applyCompressOverrides(config, providerId, modelId) {
7209
7424
  const overrides = resolveCompressOverrides(config, providerId, modelId);
@@ -8057,7 +8272,12 @@ function createCompressRangeTool(factoryCtx) {
8057
8272
  entry: plan.entry,
8058
8273
  selection: plan.selection,
8059
8274
  anchorMessageId: plan.anchorMessageId,
8060
- finalSummary: completedSummary.expandedSummary,
8275
+ finalSummary: appendArchivedImagePointers(
8276
+ completedSummary.expandedSummary,
8277
+ plan.selection,
8278
+ ctx.state,
8279
+ ctx.config.compress.images?.enabled !== false
8280
+ ),
8061
8281
  consumedBlockIds: mergeConsumedBlockIds
8062
8282
  });
8063
8283
  }
@@ -9733,6 +9953,66 @@ function dropCompressReasoning(messages, threshold) {
9733
9953
  return removed;
9734
9954
  }
9735
9955
 
9956
+ // lib/messages/reasoning-canonicalize.ts
9957
+ var CANONICAL_OPEN = "<thinking>\n";
9958
+ var CANONICAL_CLOSE = "\n</thinking>";
9959
+ function canonicalizeReasoningShape(messages) {
9960
+ let normalized = 0;
9961
+ for (const message of messages) {
9962
+ if (!message || message.info.role !== "assistant") {
9963
+ continue;
9964
+ }
9965
+ const parts = Array.isArray(message.parts) ? message.parts : [];
9966
+ let allReasoning = "";
9967
+ for (const part of parts) {
9968
+ if (part.type === "reasoning") {
9969
+ allReasoning += part.text;
9970
+ }
9971
+ }
9972
+ if (allReasoning.length === 0) {
9973
+ continue;
9974
+ }
9975
+ let allText = "";
9976
+ for (const part of parts) {
9977
+ if (part.type === "text") {
9978
+ allText += part.text;
9979
+ }
9980
+ }
9981
+ const merged = allText.length > 0 ? `${CANONICAL_OPEN}${allReasoning}${CANONICAL_CLOSE}
9982
+ ${allText}` : `${CANONICAL_OPEN}${allReasoning}${CANONICAL_CLOSE}`;
9983
+ const rebuilt = [];
9984
+ let placed = false;
9985
+ const placeMerged = (carrier) => {
9986
+ if (placed) {
9987
+ return;
9988
+ }
9989
+ placed = true;
9990
+ const part = {
9991
+ id: carrier.id,
9992
+ sessionID: message.info.sessionID,
9993
+ messageID: message.info.id,
9994
+ type: "text",
9995
+ text: merged
9996
+ };
9997
+ rebuilt.push(part);
9998
+ };
9999
+ for (const part of parts) {
10000
+ if (part.type === "reasoning") {
10001
+ placeMerged(part);
10002
+ continue;
10003
+ }
10004
+ if (part.type === "text") {
10005
+ placeMerged(part);
10006
+ continue;
10007
+ }
10008
+ rebuilt.push(part);
10009
+ }
10010
+ message.parts = rebuilt;
10011
+ normalized++;
10012
+ }
10013
+ return normalized;
10014
+ }
10015
+
9736
10016
  // lib/messages/priority.ts
9737
10017
  function buildPriorityMap(_config, _state, _messages) {
9738
10018
  return /* @__PURE__ */ new Map();
@@ -11106,10 +11386,10 @@ function hideFailedCompressCalls(messages) {
11106
11386
 
11107
11387
  // lib/logger.ts
11108
11388
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
11109
- import { join as join4 } from "path";
11110
- import { existsSync as existsSync4 } from "fs";
11389
+ import { join as join5 } from "path";
11390
+ import { existsSync as existsSync5 } from "fs";
11111
11391
  import { homedir as homedir3 } from "os";
11112
- var LOG_VERSION = true ? "1.18.3" : "dev";
11392
+ var LOG_VERSION = true ? "1.18.4-pr.483.216" : "dev";
11113
11393
  var LEVEL_RANK = {
11114
11394
  debug: 10,
11115
11395
  info: 20,
@@ -11129,8 +11409,8 @@ var Logger = class {
11129
11409
  constructor(enabled, level) {
11130
11410
  const resolved = level !== void 0 && LEVEL_RANK[level] !== void 0 ? level : void 0;
11131
11411
  this.level = resolved ?? (enabled ? "debug" : "warn");
11132
- const configHome = process.env.XDG_CONFIG_HOME || join4(homedir3(), ".config");
11133
- this.logDir = join4(configHome, "opencode", "logs", "acp");
11412
+ const configHome = process.env.XDG_CONFIG_HOME || join5(homedir3(), ".config");
11413
+ this.logDir = join5(configHome, "opencode", "logs", "acp");
11134
11414
  }
11135
11415
  /** Back-compat view of the old flag: true only at full debug verbosity. */
11136
11416
  get enabled() {
@@ -11140,7 +11420,7 @@ var Logger = class {
11140
11420
  return LEVEL_RANK[level] >= LEVEL_RANK[this.level];
11141
11421
  }
11142
11422
  async ensureLogDir() {
11143
- if (!existsSync4(this.logDir)) {
11423
+ if (!existsSync5(this.logDir)) {
11144
11424
  await mkdir2(this.logDir, { recursive: true });
11145
11425
  }
11146
11426
  }
@@ -11192,11 +11472,11 @@ var Logger = class {
11192
11472
  const dataStr = this.formatData(data);
11193
11473
  const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""} | v=${LOG_VERSION}
11194
11474
  `;
11195
- const dailyLogDir = join4(this.logDir, "daily");
11196
- if (!existsSync4(dailyLogDir)) {
11475
+ const dailyLogDir = join5(this.logDir, "daily");
11476
+ if (!existsSync5(dailyLogDir)) {
11197
11477
  await mkdir2(dailyLogDir, { recursive: true });
11198
11478
  }
11199
- const logFile = join4(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
11479
+ const logFile = join5(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
11200
11480
  await writeFile2(logFile, logLine, { flag: "a" });
11201
11481
  } catch (error) {
11202
11482
  }
@@ -11307,18 +11587,18 @@ var Logger = class {
11307
11587
  async saveContext(sessionId, messages) {
11308
11588
  if (this.level !== "debug") return;
11309
11589
  try {
11310
- const contextDir = join4(this.logDir, "context", sessionId);
11311
- if (!existsSync4(contextDir)) {
11590
+ const contextDir = join5(this.logDir, "context", sessionId);
11591
+ if (!existsSync5(contextDir)) {
11312
11592
  await mkdir2(contextDir, { recursive: true });
11313
11593
  }
11314
11594
  const minimized = this.minimizeForDebug(messages).filter(
11315
11595
  (msg) => msg.parts && msg.parts.length > 0
11316
11596
  );
11317
11597
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11318
- const contextFile = join4(contextDir, `${timestamp}.json`);
11598
+ const contextFile = join5(contextDir, `${timestamp}.json`);
11319
11599
  await writeFile2(contextFile, JSON.stringify(minimized, null, 2));
11320
- const versionFile = join4(contextDir, "_version");
11321
- if (!existsSync4(versionFile)) {
11600
+ const versionFile = join5(contextDir, "_version");
11601
+ if (!existsSync5(versionFile)) {
11322
11602
  await writeFile2(versionFile, LOG_VERSION);
11323
11603
  }
11324
11604
  } catch (error) {
@@ -11327,8 +11607,8 @@ var Logger = class {
11327
11607
  };
11328
11608
 
11329
11609
  // lib/prompts/store.ts
11330
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, statSync as statSync2 } from "fs";
11331
- import { join as join5, dirname as dirname2 } from "path";
11610
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3, statSync as statSync3 } from "fs";
11611
+ import { join as join6, dirname as dirname2 } from "path";
11332
11612
  import { homedir as homedir4 } from "os";
11333
11613
 
11334
11614
  // lib/prompts/system.ts
@@ -11636,10 +11916,10 @@ function createBundledRuntimePrompts(candidatesEnabled) {
11636
11916
  function findOpencodeDir2(startDir) {
11637
11917
  let current = startDir;
11638
11918
  while (current !== "/") {
11639
- const candidate = join5(current, ".opencode");
11640
- if (existsSync5(candidate)) {
11919
+ const candidate = join6(current, ".opencode");
11920
+ if (existsSync6(candidate)) {
11641
11921
  try {
11642
- if (statSync2(candidate).isDirectory()) {
11922
+ if (statSync3(candidate).isDirectory()) {
11643
11923
  return candidate;
11644
11924
  }
11645
11925
  } catch {
@@ -11654,13 +11934,13 @@ function findOpencodeDir2(startDir) {
11654
11934
  return null;
11655
11935
  }
11656
11936
  function resolvePromptPaths(workingDirectory) {
11657
- const configHome = process.env.XDG_CONFIG_HOME || join5(homedir4(), ".config");
11658
- const globalRoot = join5(configHome, "opencode", "acp-prompts");
11659
- const defaultsDir = join5(globalRoot, "defaults");
11660
- const globalOverridesDir = join5(globalRoot, "overrides");
11661
- const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join5(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
11937
+ const configHome = process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config");
11938
+ const globalRoot = join6(configHome, "opencode", "acp-prompts");
11939
+ const defaultsDir = join6(globalRoot, "defaults");
11940
+ const globalOverridesDir = join6(globalRoot, "overrides");
11941
+ const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join6(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
11662
11942
  const opencodeDir = findOpencodeDir2(workingDirectory);
11663
- const projectOverridesDir = opencodeDir ? join5(opencodeDir, "acp-prompts", "overrides") : null;
11943
+ const projectOverridesDir = opencodeDir ? join6(opencodeDir, "acp-prompts", "overrides") : null;
11664
11944
  return {
11665
11945
  defaultsDir,
11666
11946
  globalOverridesDir,
@@ -11760,7 +12040,7 @@ function buildDefaultsReadmeContent() {
11760
12040
  `;
11761
12041
  }
11762
12042
  function readFileIfExists(filePath) {
11763
- if (!existsSync5(filePath)) {
12043
+ if (!existsSync6(filePath)) {
11764
12044
  return null;
11765
12045
  }
11766
12046
  try {
@@ -11833,23 +12113,23 @@ var PromptStore = class {
11833
12113
  const candidates = [];
11834
12114
  if (this.paths.projectOverridesDir) {
11835
12115
  candidates.push({
11836
- path: join5(this.paths.projectOverridesDir, fileName)
12116
+ path: join6(this.paths.projectOverridesDir, fileName)
11837
12117
  });
11838
12118
  }
11839
12119
  if (this.paths.configDirOverridesDir) {
11840
12120
  candidates.push({
11841
- path: join5(this.paths.configDirOverridesDir, fileName)
12121
+ path: join6(this.paths.configDirOverridesDir, fileName)
11842
12122
  });
11843
12123
  }
11844
12124
  candidates.push({
11845
- path: join5(this.paths.globalOverridesDir, fileName)
12125
+ path: join6(this.paths.globalOverridesDir, fileName)
11846
12126
  });
11847
12127
  return candidates;
11848
12128
  }
11849
12129
  ensureDefaultFiles() {
11850
12130
  try {
11851
- mkdirSync2(this.paths.defaultsDir, { recursive: true });
11852
- mkdirSync2(this.paths.globalOverridesDir, { recursive: true });
12131
+ mkdirSync3(this.paths.defaultsDir, { recursive: true });
12132
+ mkdirSync3(this.paths.globalOverridesDir, { recursive: true });
11853
12133
  } catch {
11854
12134
  this.logger.warn("Failed to initialize prompt directories", {
11855
12135
  defaultsDir: this.paths.defaultsDir,
@@ -11866,13 +12146,13 @@ var PromptStore = class {
11866
12146
  const managedContent = buildDefaultPromptFileContent(
11867
12147
  bundledEditable || bundledPrompts[definition.runtimeField]
11868
12148
  );
11869
- const filePath = join5(this.paths.defaultsDir, definition.fileName);
12149
+ const filePath = join6(this.paths.defaultsDir, definition.fileName);
11870
12150
  try {
11871
12151
  const existing = readFileIfExists(filePath);
11872
12152
  if (existing === managedContent) {
11873
12153
  continue;
11874
12154
  }
11875
- writeFileSync2(filePath, managedContent, "utf-8");
12155
+ writeFileSync3(filePath, managedContent, "utf-8");
11876
12156
  } catch {
11877
12157
  this.logger.warn("Failed to write default prompt file", {
11878
12158
  key: definition.key,
@@ -11880,12 +12160,12 @@ var PromptStore = class {
11880
12160
  });
11881
12161
  }
11882
12162
  }
11883
- const readmePath = join5(this.paths.defaultsDir, DEFAULTS_README_FILE);
12163
+ const readmePath = join6(this.paths.defaultsDir, DEFAULTS_README_FILE);
11884
12164
  const readmeContent = buildDefaultsReadmeContent();
11885
12165
  try {
11886
12166
  const existing = readFileIfExists(readmePath);
11887
12167
  if (existing !== readmeContent) {
11888
- writeFileSync2(readmePath, readmeContent, "utf-8");
12168
+ writeFileSync3(readmePath, readmeContent, "utf-8");
11889
12169
  }
11890
12170
  } catch {
11891
12171
  this.logger.warn("Failed to write defaults README", {
@@ -12353,8 +12633,8 @@ ${report}`;
12353
12633
 
12354
12634
  // lib/commands/export.ts
12355
12635
  import * as fs2 from "fs/promises";
12356
- import { existsSync as existsSync6 } from "fs";
12357
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, resolve } from "path";
12636
+ import { existsSync as existsSync7 } from "fs";
12637
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve } from "path";
12358
12638
  var ALL_TIERS = [1, 2, 3];
12359
12639
  var TIER_NAMES = {
12360
12640
  1: "Tier 1 \u2014 Capture",
@@ -12467,7 +12747,7 @@ function parseTiers(raw) {
12467
12747
  }
12468
12748
  function resolveDefaultOutputPath(sessionId, cwd2) {
12469
12749
  const short = (sessionId || "session").slice(0, 8);
12470
- return join6(cwd2, ".opencode", `acp-export-${short}.md`);
12750
+ return join7(cwd2, ".opencode", `acp-export-${short}.md`);
12471
12751
  }
12472
12752
  function formatTokens2(n) {
12473
12753
  if (!Number.isFinite(n) || n <= 0) return "0";
@@ -12598,11 +12878,11 @@ async function handleExportCommand(ctx, args) {
12598
12878
  const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
12599
12879
  try {
12600
12880
  const dir = dirname3(targetPath);
12601
- if (!existsSync6(dir)) {
12881
+ if (!existsSync7(dir)) {
12602
12882
  await fs2.mkdir(dir, { recursive: true });
12603
12883
  }
12604
12884
  const flag = options.append ? "a" : "w";
12605
- if (options.append && existsSync6(targetPath)) {
12885
+ if (options.append && existsSync7(targetPath)) {
12606
12886
  await fs2.appendFile(targetPath, `
12607
12887
 
12608
12888
  ---
@@ -13308,6 +13588,12 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
13308
13588
  });
13309
13589
  }
13310
13590
  }
13591
+ const canonicalizedReasoning = canonicalizeReasoningShape(output.messages);
13592
+ if (canonicalizedReasoning > 0) {
13593
+ logger.debug("reasoning-canonicalize: pinned historical thinking shape", {
13594
+ messages: canonicalizedReasoning
13595
+ });
13596
+ }
13311
13597
  ensureBuiltinFiltersRegistered();
13312
13598
  const effectiveLimit = resolveEffectiveContextLimit(state, config);
13313
13599
  applyMessageFilters(output.messages, config.messageFilters, logger, {
@@ -13317,6 +13603,20 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
13317
13603
  });
13318
13604
  cacheSystemPromptTokens(state, output.messages);
13319
13605
  assignMessageRefs(state, output.messages);
13606
+ const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
13607
+ if (imagesConfig.enabled) {
13608
+ const archivedImages = archiveSessionImages(
13609
+ state,
13610
+ output.messages,
13611
+ imagesConfig,
13612
+ logger
13613
+ );
13614
+ if (archivedImages > 0) {
13615
+ saveSessionState(state, logger).catch(() => {
13616
+ });
13617
+ }
13618
+ pruneImageArchive(state, imagesConfig, logger);
13619
+ }
13320
13620
  const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
13321
13621
  const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
13322
13622
  if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
@@ -13594,7 +13894,7 @@ function configureClientAuth(client) {
13594
13894
 
13595
13895
  // lib/update.ts
13596
13896
  import { readFile as readFile2, rm } from "fs/promises";
13597
- import { basename, dirname as dirname4, join as join7 } from "path";
13897
+ import { basename, dirname as dirname4, join as join8 } from "path";
13598
13898
  import { fileURLToPath } from "url";
13599
13899
  var PACKAGE_NAME = "opencode-acp";
13600
13900
  function startAutoUpdate(ctx, enabled, logger) {
@@ -13641,7 +13941,7 @@ async function checkAutoUpdate(signal, logger) {
13641
13941
  logger?.info("Auto-update skipped: package dir not found");
13642
13942
  return { updated: false };
13643
13943
  }
13644
- const pkg = await readPackageJson(join7(packageDir, "package.json"));
13944
+ const pkg = await readPackageJson(join8(packageDir, "package.json"));
13645
13945
  if (!pkg?.name || !pkg.version) {
13646
13946
  logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
13647
13947
  return { updated: false };
@@ -13682,7 +13982,7 @@ async function checkAutoUpdate(signal, logger) {
13682
13982
  async function findPackageDir(name) {
13683
13983
  let dir = dirname4(fileURLToPath(import.meta.url));
13684
13984
  for (; ; ) {
13685
- const pkg = await readPackageJson(join7(dir, "package.json"));
13985
+ const pkg = await readPackageJson(join8(dir, "package.json"));
13686
13986
  if (pkg?.name === name) return dir;
13687
13987
  const parent = dirname4(dir);
13688
13988
  if (parent === dir) return void 0;
@@ -13694,7 +13994,7 @@ async function updateTarget(packageDir, name) {
13694
13994
  const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
13695
13995
  if (basename(nodeModulesDir) !== "node_modules") return void 0;
13696
13996
  const wrapperDir = dirname4(nodeModulesDir);
13697
- const wrapperPkg = await readPackageJson(join7(wrapperDir, "package.json"));
13997
+ const wrapperPkg = await readPackageJson(join8(wrapperDir, "package.json"));
13698
13998
  const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
13699
13999
  if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
13700
14000
  return { removeDir: wrapperDir, spec };
@@ -13805,7 +14105,7 @@ var server = (async (ctx) => {
13805
14105
  }
13806
14106
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13807
14107
  logger.info("ACP plugin initialized", {
13808
- version: true ? "1.18.3" : "dev",
14108
+ version: true ? "1.18.4-pr.483.216" : "dev",
13809
14109
  workspace: ctx.directory,
13810
14110
  logLevel: logger.level,
13811
14111
  debug: config.debug,