opencode-acp 1.18.3 → 1.18.4-pr.480.215

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
  }
@@ -11106,10 +11326,10 @@ function hideFailedCompressCalls(messages) {
11106
11326
 
11107
11327
  // lib/logger.ts
11108
11328
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
11109
- import { join as join4 } from "path";
11110
- import { existsSync as existsSync4 } from "fs";
11329
+ import { join as join5 } from "path";
11330
+ import { existsSync as existsSync5 } from "fs";
11111
11331
  import { homedir as homedir3 } from "os";
11112
- var LOG_VERSION = true ? "1.18.3" : "dev";
11332
+ var LOG_VERSION = true ? "1.18.4-pr.480.215" : "dev";
11113
11333
  var LEVEL_RANK = {
11114
11334
  debug: 10,
11115
11335
  info: 20,
@@ -11129,8 +11349,8 @@ var Logger = class {
11129
11349
  constructor(enabled, level) {
11130
11350
  const resolved = level !== void 0 && LEVEL_RANK[level] !== void 0 ? level : void 0;
11131
11351
  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");
11352
+ const configHome = process.env.XDG_CONFIG_HOME || join5(homedir3(), ".config");
11353
+ this.logDir = join5(configHome, "opencode", "logs", "acp");
11134
11354
  }
11135
11355
  /** Back-compat view of the old flag: true only at full debug verbosity. */
11136
11356
  get enabled() {
@@ -11140,7 +11360,7 @@ var Logger = class {
11140
11360
  return LEVEL_RANK[level] >= LEVEL_RANK[this.level];
11141
11361
  }
11142
11362
  async ensureLogDir() {
11143
- if (!existsSync4(this.logDir)) {
11363
+ if (!existsSync5(this.logDir)) {
11144
11364
  await mkdir2(this.logDir, { recursive: true });
11145
11365
  }
11146
11366
  }
@@ -11192,11 +11412,11 @@ var Logger = class {
11192
11412
  const dataStr = this.formatData(data);
11193
11413
  const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""} | v=${LOG_VERSION}
11194
11414
  `;
11195
- const dailyLogDir = join4(this.logDir, "daily");
11196
- if (!existsSync4(dailyLogDir)) {
11415
+ const dailyLogDir = join5(this.logDir, "daily");
11416
+ if (!existsSync5(dailyLogDir)) {
11197
11417
  await mkdir2(dailyLogDir, { recursive: true });
11198
11418
  }
11199
- const logFile = join4(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
11419
+ const logFile = join5(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
11200
11420
  await writeFile2(logFile, logLine, { flag: "a" });
11201
11421
  } catch (error) {
11202
11422
  }
@@ -11307,18 +11527,18 @@ var Logger = class {
11307
11527
  async saveContext(sessionId, messages) {
11308
11528
  if (this.level !== "debug") return;
11309
11529
  try {
11310
- const contextDir = join4(this.logDir, "context", sessionId);
11311
- if (!existsSync4(contextDir)) {
11530
+ const contextDir = join5(this.logDir, "context", sessionId);
11531
+ if (!existsSync5(contextDir)) {
11312
11532
  await mkdir2(contextDir, { recursive: true });
11313
11533
  }
11314
11534
  const minimized = this.minimizeForDebug(messages).filter(
11315
11535
  (msg) => msg.parts && msg.parts.length > 0
11316
11536
  );
11317
11537
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11318
- const contextFile = join4(contextDir, `${timestamp}.json`);
11538
+ const contextFile = join5(contextDir, `${timestamp}.json`);
11319
11539
  await writeFile2(contextFile, JSON.stringify(minimized, null, 2));
11320
- const versionFile = join4(contextDir, "_version");
11321
- if (!existsSync4(versionFile)) {
11540
+ const versionFile = join5(contextDir, "_version");
11541
+ if (!existsSync5(versionFile)) {
11322
11542
  await writeFile2(versionFile, LOG_VERSION);
11323
11543
  }
11324
11544
  } catch (error) {
@@ -11327,8 +11547,8 @@ var Logger = class {
11327
11547
  };
11328
11548
 
11329
11549
  // 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";
11550
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3, statSync as statSync3 } from "fs";
11551
+ import { join as join6, dirname as dirname2 } from "path";
11332
11552
  import { homedir as homedir4 } from "os";
11333
11553
 
11334
11554
  // lib/prompts/system.ts
@@ -11636,10 +11856,10 @@ function createBundledRuntimePrompts(candidatesEnabled) {
11636
11856
  function findOpencodeDir2(startDir) {
11637
11857
  let current = startDir;
11638
11858
  while (current !== "/") {
11639
- const candidate = join5(current, ".opencode");
11640
- if (existsSync5(candidate)) {
11859
+ const candidate = join6(current, ".opencode");
11860
+ if (existsSync6(candidate)) {
11641
11861
  try {
11642
- if (statSync2(candidate).isDirectory()) {
11862
+ if (statSync3(candidate).isDirectory()) {
11643
11863
  return candidate;
11644
11864
  }
11645
11865
  } catch {
@@ -11654,13 +11874,13 @@ function findOpencodeDir2(startDir) {
11654
11874
  return null;
11655
11875
  }
11656
11876
  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;
11877
+ const configHome = process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config");
11878
+ const globalRoot = join6(configHome, "opencode", "acp-prompts");
11879
+ const defaultsDir = join6(globalRoot, "defaults");
11880
+ const globalOverridesDir = join6(globalRoot, "overrides");
11881
+ const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join6(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
11662
11882
  const opencodeDir = findOpencodeDir2(workingDirectory);
11663
- const projectOverridesDir = opencodeDir ? join5(opencodeDir, "acp-prompts", "overrides") : null;
11883
+ const projectOverridesDir = opencodeDir ? join6(opencodeDir, "acp-prompts", "overrides") : null;
11664
11884
  return {
11665
11885
  defaultsDir,
11666
11886
  globalOverridesDir,
@@ -11760,7 +11980,7 @@ function buildDefaultsReadmeContent() {
11760
11980
  `;
11761
11981
  }
11762
11982
  function readFileIfExists(filePath) {
11763
- if (!existsSync5(filePath)) {
11983
+ if (!existsSync6(filePath)) {
11764
11984
  return null;
11765
11985
  }
11766
11986
  try {
@@ -11833,23 +12053,23 @@ var PromptStore = class {
11833
12053
  const candidates = [];
11834
12054
  if (this.paths.projectOverridesDir) {
11835
12055
  candidates.push({
11836
- path: join5(this.paths.projectOverridesDir, fileName)
12056
+ path: join6(this.paths.projectOverridesDir, fileName)
11837
12057
  });
11838
12058
  }
11839
12059
  if (this.paths.configDirOverridesDir) {
11840
12060
  candidates.push({
11841
- path: join5(this.paths.configDirOverridesDir, fileName)
12061
+ path: join6(this.paths.configDirOverridesDir, fileName)
11842
12062
  });
11843
12063
  }
11844
12064
  candidates.push({
11845
- path: join5(this.paths.globalOverridesDir, fileName)
12065
+ path: join6(this.paths.globalOverridesDir, fileName)
11846
12066
  });
11847
12067
  return candidates;
11848
12068
  }
11849
12069
  ensureDefaultFiles() {
11850
12070
  try {
11851
- mkdirSync2(this.paths.defaultsDir, { recursive: true });
11852
- mkdirSync2(this.paths.globalOverridesDir, { recursive: true });
12071
+ mkdirSync3(this.paths.defaultsDir, { recursive: true });
12072
+ mkdirSync3(this.paths.globalOverridesDir, { recursive: true });
11853
12073
  } catch {
11854
12074
  this.logger.warn("Failed to initialize prompt directories", {
11855
12075
  defaultsDir: this.paths.defaultsDir,
@@ -11866,13 +12086,13 @@ var PromptStore = class {
11866
12086
  const managedContent = buildDefaultPromptFileContent(
11867
12087
  bundledEditable || bundledPrompts[definition.runtimeField]
11868
12088
  );
11869
- const filePath = join5(this.paths.defaultsDir, definition.fileName);
12089
+ const filePath = join6(this.paths.defaultsDir, definition.fileName);
11870
12090
  try {
11871
12091
  const existing = readFileIfExists(filePath);
11872
12092
  if (existing === managedContent) {
11873
12093
  continue;
11874
12094
  }
11875
- writeFileSync2(filePath, managedContent, "utf-8");
12095
+ writeFileSync3(filePath, managedContent, "utf-8");
11876
12096
  } catch {
11877
12097
  this.logger.warn("Failed to write default prompt file", {
11878
12098
  key: definition.key,
@@ -11880,12 +12100,12 @@ var PromptStore = class {
11880
12100
  });
11881
12101
  }
11882
12102
  }
11883
- const readmePath = join5(this.paths.defaultsDir, DEFAULTS_README_FILE);
12103
+ const readmePath = join6(this.paths.defaultsDir, DEFAULTS_README_FILE);
11884
12104
  const readmeContent = buildDefaultsReadmeContent();
11885
12105
  try {
11886
12106
  const existing = readFileIfExists(readmePath);
11887
12107
  if (existing !== readmeContent) {
11888
- writeFileSync2(readmePath, readmeContent, "utf-8");
12108
+ writeFileSync3(readmePath, readmeContent, "utf-8");
11889
12109
  }
11890
12110
  } catch {
11891
12111
  this.logger.warn("Failed to write defaults README", {
@@ -12353,8 +12573,8 @@ ${report}`;
12353
12573
 
12354
12574
  // lib/commands/export.ts
12355
12575
  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";
12576
+ import { existsSync as existsSync7 } from "fs";
12577
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve } from "path";
12358
12578
  var ALL_TIERS = [1, 2, 3];
12359
12579
  var TIER_NAMES = {
12360
12580
  1: "Tier 1 \u2014 Capture",
@@ -12467,7 +12687,7 @@ function parseTiers(raw) {
12467
12687
  }
12468
12688
  function resolveDefaultOutputPath(sessionId, cwd2) {
12469
12689
  const short = (sessionId || "session").slice(0, 8);
12470
- return join6(cwd2, ".opencode", `acp-export-${short}.md`);
12690
+ return join7(cwd2, ".opencode", `acp-export-${short}.md`);
12471
12691
  }
12472
12692
  function formatTokens2(n) {
12473
12693
  if (!Number.isFinite(n) || n <= 0) return "0";
@@ -12598,11 +12818,11 @@ async function handleExportCommand(ctx, args) {
12598
12818
  const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
12599
12819
  try {
12600
12820
  const dir = dirname3(targetPath);
12601
- if (!existsSync6(dir)) {
12821
+ if (!existsSync7(dir)) {
12602
12822
  await fs2.mkdir(dir, { recursive: true });
12603
12823
  }
12604
12824
  const flag = options.append ? "a" : "w";
12605
- if (options.append && existsSync6(targetPath)) {
12825
+ if (options.append && existsSync7(targetPath)) {
12606
12826
  await fs2.appendFile(targetPath, `
12607
12827
 
12608
12828
  ---
@@ -13317,6 +13537,20 @@ function createChatMessageTransformHandler(client, registry4, logger, config, pr
13317
13537
  });
13318
13538
  cacheSystemPromptTokens(state, output.messages);
13319
13539
  assignMessageRefs(state, output.messages);
13540
+ const imagesConfig = config.compress.images ?? DEFAULT_COMPRESS_IMAGES;
13541
+ if (imagesConfig.enabled) {
13542
+ const archivedImages = archiveSessionImages(
13543
+ state,
13544
+ output.messages,
13545
+ imagesConfig,
13546
+ logger
13547
+ );
13548
+ if (archivedImages > 0) {
13549
+ saveSessionState(state, logger).catch(() => {
13550
+ });
13551
+ }
13552
+ pruneImageArchive(state, imagesConfig, logger);
13553
+ }
13320
13554
  const activeBlockCountBefore = state.prune.messages.activeBlockIds.size;
13321
13555
  const compressionStateChanged = syncCompressionBlocks(state, logger, output.messages);
13322
13556
  if (compressionStateChanged || state.prune.messages.activeBlockIds.size !== activeBlockCountBefore) {
@@ -13594,7 +13828,7 @@ function configureClientAuth(client) {
13594
13828
 
13595
13829
  // lib/update.ts
13596
13830
  import { readFile as readFile2, rm } from "fs/promises";
13597
- import { basename, dirname as dirname4, join as join7 } from "path";
13831
+ import { basename, dirname as dirname4, join as join8 } from "path";
13598
13832
  import { fileURLToPath } from "url";
13599
13833
  var PACKAGE_NAME = "opencode-acp";
13600
13834
  function startAutoUpdate(ctx, enabled, logger) {
@@ -13641,7 +13875,7 @@ async function checkAutoUpdate(signal, logger) {
13641
13875
  logger?.info("Auto-update skipped: package dir not found");
13642
13876
  return { updated: false };
13643
13877
  }
13644
- const pkg = await readPackageJson(join7(packageDir, "package.json"));
13878
+ const pkg = await readPackageJson(join8(packageDir, "package.json"));
13645
13879
  if (!pkg?.name || !pkg.version) {
13646
13880
  logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
13647
13881
  return { updated: false };
@@ -13682,7 +13916,7 @@ async function checkAutoUpdate(signal, logger) {
13682
13916
  async function findPackageDir(name) {
13683
13917
  let dir = dirname4(fileURLToPath(import.meta.url));
13684
13918
  for (; ; ) {
13685
- const pkg = await readPackageJson(join7(dir, "package.json"));
13919
+ const pkg = await readPackageJson(join8(dir, "package.json"));
13686
13920
  if (pkg?.name === name) return dir;
13687
13921
  const parent = dirname4(dir);
13688
13922
  if (parent === dir) return void 0;
@@ -13694,7 +13928,7 @@ async function updateTarget(packageDir, name) {
13694
13928
  const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
13695
13929
  if (basename(nodeModulesDir) !== "node_modules") return void 0;
13696
13930
  const wrapperDir = dirname4(nodeModulesDir);
13697
- const wrapperPkg = await readPackageJson(join7(wrapperDir, "package.json"));
13931
+ const wrapperPkg = await readPackageJson(join8(wrapperDir, "package.json"));
13698
13932
  const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
13699
13933
  if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
13700
13934
  return { removeDir: wrapperDir, spec };
@@ -13805,7 +14039,7 @@ var server = (async (ctx) => {
13805
14039
  }
13806
14040
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
13807
14041
  logger.info("ACP plugin initialized", {
13808
- version: true ? "1.18.3" : "dev",
14042
+ version: true ? "1.18.4-pr.480.215" : "dev",
13809
14043
  workspace: ctx.directory,
13810
14044
  logLevel: logger.level,
13811
14045
  debug: config.debug,