opencode-acp 1.18.4-pr.488.221 → 1.18.5-pr.416.226

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
@@ -2596,6 +2596,209 @@ import { existsSync as existsSync2 } from "fs";
2596
2596
  import { homedir as homedir2 } from "os";
2597
2597
  import { isAbsolute, join as join2 } from "path";
2598
2598
 
2599
+ // lib/message-ids.ts
2600
+ var MESSAGE_REF_REGEX = /^m(\d{4,5})$/;
2601
+ var BLOCK_REF_REGEX = /^b([1-9]\d*)$/;
2602
+ var MESSAGE_ID_TAG_NAME = "dcp-message-id";
2603
+ var MESSAGE_REF_WIDTH = 5;
2604
+ var MESSAGE_REF_MIN_INDEX = 1;
2605
+ var MESSAGE_REF_MAX_INDEX = 99999;
2606
+ function formatMessageRef(index) {
2607
+ if (!Number.isInteger(index) || index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
2608
+ throw new Error(
2609
+ `Message ID index out of bounds: ${index}. Supported range is ${MESSAGE_REF_MIN_INDEX}-${MESSAGE_REF_MAX_INDEX}.`
2610
+ );
2611
+ }
2612
+ return `m${index.toString().padStart(MESSAGE_REF_WIDTH, "0")}`;
2613
+ }
2614
+ function formatBlockRef(blockId) {
2615
+ if (!Number.isInteger(blockId) || blockId < 1) {
2616
+ throw new Error(`Invalid block ID: ${blockId}`);
2617
+ }
2618
+ return `b${blockId}`;
2619
+ }
2620
+ function parseMessageRef(ref) {
2621
+ const normalized = ref.trim().toLowerCase();
2622
+ const match = normalized.match(MESSAGE_REF_REGEX);
2623
+ if (!match) {
2624
+ return null;
2625
+ }
2626
+ const index = Number.parseInt(match[1], 10);
2627
+ if (!Number.isInteger(index)) {
2628
+ return null;
2629
+ }
2630
+ if (index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
2631
+ return null;
2632
+ }
2633
+ return index;
2634
+ }
2635
+ function migrateMessageRef(ref) {
2636
+ const index = parseMessageRef(ref);
2637
+ return index === null ? ref : formatMessageRef(index);
2638
+ }
2639
+ function parseBlockRef(ref) {
2640
+ const normalized = ref.trim().toLowerCase();
2641
+ const match = normalized.match(BLOCK_REF_REGEX);
2642
+ if (!match) {
2643
+ return null;
2644
+ }
2645
+ const id = Number.parseInt(match[1], 10);
2646
+ return Number.isInteger(id) ? id : null;
2647
+ }
2648
+ function parseBoundaryId(id) {
2649
+ const normalized = id.trim().toLowerCase();
2650
+ const messageIndex = parseMessageRef(normalized);
2651
+ if (messageIndex !== null) {
2652
+ return {
2653
+ kind: "message",
2654
+ ref: formatMessageRef(messageIndex),
2655
+ index: messageIndex
2656
+ };
2657
+ }
2658
+ const blockId = parseBlockRef(normalized);
2659
+ if (blockId !== null) {
2660
+ return {
2661
+ kind: "compressed-block",
2662
+ ref: formatBlockRef(blockId),
2663
+ blockId
2664
+ };
2665
+ }
2666
+ return null;
2667
+ }
2668
+ function escapeXmlAttribute(value) {
2669
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2670
+ }
2671
+ function formatMessageIdTag(ref, attributes) {
2672
+ const serializedAttributes = Object.entries(attributes || {}).sort(([left], [right]) => left.localeCompare(right)).map(([name, value]) => {
2673
+ if (name.trim().length === 0 || typeof value !== "string" || value.length === 0) {
2674
+ return "";
2675
+ }
2676
+ return ` ${name}="${escapeXmlAttribute(value)}"`;
2677
+ }).join("");
2678
+ return `
2679
+ <${MESSAGE_ID_TAG_NAME}${serializedAttributes}>${ref}</${MESSAGE_ID_TAG_NAME}>`;
2680
+ }
2681
+ function formatTokenSize(tokens) {
2682
+ if (tokens < 1e3) return String(tokens);
2683
+ if (tokens < 1e4) return `${(tokens / 1e3).toFixed(1)}K`;
2684
+ return `${Math.round(tokens / 1e3)}K`;
2685
+ }
2686
+ function classifyMessageType(parts) {
2687
+ let hasTool = false;
2688
+ let hasText = false;
2689
+ let hasReasoning = false;
2690
+ const toolNames = [];
2691
+ for (const part of parts) {
2692
+ if (part.type === "tool") {
2693
+ hasTool = true;
2694
+ if (typeof part.tool === "string" && !toolNames.includes(part.tool)) {
2695
+ toolNames.push(part.tool);
2696
+ }
2697
+ } else if (part.type === "text") {
2698
+ hasText = true;
2699
+ } else if (part.type === "reasoning") {
2700
+ hasReasoning = true;
2701
+ }
2702
+ }
2703
+ if (hasTool) {
2704
+ return toolNames.length > 0 ? `tool:${toolNames.join(",")}` : "tool";
2705
+ }
2706
+ if (hasReasoning && !hasText) return "reasoning";
2707
+ return "text";
2708
+ }
2709
+ function assignMessageRefs(state, messages) {
2710
+ let assigned = 0;
2711
+ let skippedSubAgentPrompt = false;
2712
+ for (const message of messages) {
2713
+ if (isIgnoredUserMessage(message)) {
2714
+ continue;
2715
+ }
2716
+ if (state.isSubAgent && !skippedSubAgentPrompt && message.info.role === "user") {
2717
+ skippedSubAgentPrompt = true;
2718
+ continue;
2719
+ }
2720
+ const rawMessageId = message.info.id;
2721
+ if (typeof rawMessageId !== "string" || rawMessageId.length === 0) {
2722
+ continue;
2723
+ }
2724
+ if (rawMessageId.startsWith("msg_dcp_summary_") || rawMessageId.startsWith("msg_dcp_text_")) {
2725
+ continue;
2726
+ }
2727
+ const existingRef = state.messageIds.byRawId.get(rawMessageId);
2728
+ if (existingRef) {
2729
+ if (state.messageIds.byRef.get(existingRef) !== rawMessageId) {
2730
+ state.messageIds.byRef.set(existingRef, rawMessageId);
2731
+ }
2732
+ continue;
2733
+ }
2734
+ const ref = allocateNextMessageRef(state);
2735
+ state.messageIds.byRawId.set(rawMessageId, ref);
2736
+ state.messageIds.byRef.set(ref, rawMessageId);
2737
+ assigned++;
2738
+ }
2739
+ return assigned;
2740
+ }
2741
+ function repairNonMonotonicMessageRefs(state, messages) {
2742
+ const eligible = messages.filter((message) => {
2743
+ if (isIgnoredUserMessage(message)) return false;
2744
+ const id = message.info.id;
2745
+ return typeof id === "string" && id.length > 0 && !id.startsWith("msg_dcp_summary_") && !id.startsWith("msg_dcp_text_");
2746
+ });
2747
+ let previous = 0;
2748
+ let corrupted = false;
2749
+ for (const message of eligible) {
2750
+ const ref = state.messageIds.byRawId.get(message.info.id);
2751
+ const parsed = ref ? parseMessageRef(ref) : null;
2752
+ if (parsed === null) continue;
2753
+ if (parsed <= previous) {
2754
+ corrupted = true;
2755
+ break;
2756
+ }
2757
+ previous = parsed;
2758
+ }
2759
+ if (!corrupted) return false;
2760
+ const oldRawIds = [...state.messageIds.byRawId.keys()];
2761
+ state.messageIds.byRawId.clear();
2762
+ state.messageIds.byRef.clear();
2763
+ state.messageIds.nextRef = 1;
2764
+ for (const message of eligible) {
2765
+ const rawId = message.info.id;
2766
+ const ref = allocateNextMessageRef(state);
2767
+ state.messageIds.byRawId.set(rawId, ref);
2768
+ state.messageIds.byRef.set(ref, rawId);
2769
+ }
2770
+ for (const rawId of oldRawIds) {
2771
+ if (state.messageIds.byRawId.has(rawId)) continue;
2772
+ const ref = allocateNextMessageRef(state);
2773
+ state.messageIds.byRawId.set(rawId, ref);
2774
+ state.messageIds.byRef.set(ref, rawId);
2775
+ }
2776
+ for (const block of state.prune.messages.blocksById.values()) {
2777
+ const ids = [.../* @__PURE__ */ new Set([...block.effectiveMessageIds, ...block.directMessageIds])].filter((id) => state.messageIds.byRawId.has(id)).sort(
2778
+ (left, right) => (parseMessageRef(state.messageIds.byRawId.get(left)) ?? 0) - (parseMessageRef(state.messageIds.byRawId.get(right)) ?? 0)
2779
+ );
2780
+ if (ids.length === 0) continue;
2781
+ block.startId = state.messageIds.byRawId.get(ids[0]);
2782
+ block.endId = state.messageIds.byRawId.get(ids[ids.length - 1]);
2783
+ }
2784
+ state.prune.messages.structureVersion = (state.prune.messages.structureVersion ?? 0) + 1;
2785
+ return true;
2786
+ }
2787
+ function allocateNextMessageRef(state) {
2788
+ let candidate = Number.isInteger(state.messageIds.nextRef) ? Math.max(MESSAGE_REF_MIN_INDEX, state.messageIds.nextRef) : MESSAGE_REF_MIN_INDEX;
2789
+ while (candidate <= MESSAGE_REF_MAX_INDEX) {
2790
+ const ref = formatMessageRef(candidate);
2791
+ if (!state.messageIds.byRef.has(ref)) {
2792
+ state.messageIds.nextRef = candidate + 1;
2793
+ return ref;
2794
+ }
2795
+ candidate++;
2796
+ }
2797
+ throw new Error(
2798
+ `Message ID alias capacity exceeded. Cannot allocate more than ${formatMessageRef(MESSAGE_REF_MAX_INDEX)} aliases in this session.`
2799
+ );
2800
+ }
2801
+
2599
2802
  // lib/state/utils.ts
2600
2803
  var isMessageCompacted = (state, msg) => {
2601
2804
  if (!isMessageWithInfo(msg)) {
@@ -2747,8 +2950,10 @@ function loadPruneMessagesState(persisted) {
2747
2950
  mode: block.mode === "range" || block.mode === "message" ? block.mode : void 0,
2748
2951
  topic: typeof block.topic === "string" ? block.topic : "",
2749
2952
  batchTopic: typeof block.batchTopic === "string" ? block.batchTopic : typeof block.topic === "string" ? block.topic : "",
2750
- startId: typeof block.startId === "string" ? block.startId : "",
2751
- endId: typeof block.endId === "string" ? block.endId : "",
2953
+ // [Issue #415] Migrate legacy 4-digit boundary refs (pre-1.1.0 state)
2954
+ // to canonical 5-digit form, matching the messageIds ref migration.
2955
+ startId: typeof block.startId === "string" ? migrateMessageRef(block.startId) : "",
2956
+ endId: typeof block.endId === "string" ? migrateMessageRef(block.endId) : "",
2752
2957
  anchorMessageId: typeof block.anchorMessageId === "string" ? block.anchorMessageId : "",
2753
2958
  compressMessageId: typeof block.compressMessageId === "string" ? block.compressMessageId : "",
2754
2959
  compressCallId: typeof block.compressCallId === "string" ? block.compressCallId : void 0,
@@ -2975,8 +3180,8 @@ function saveSessionState(sessionState, logger, sessionName) {
2975
3180
  queue = { pending: [], waiters: [], draining: false };
2976
3181
  saveQueues.set(key, queue);
2977
3182
  }
2978
- const promise = new Promise((resolve2, reject) => {
2979
- queue.waiters.push({ resolve: resolve2, reject });
3183
+ const promise = new Promise((resolve3, reject) => {
3184
+ queue.waiters.push({ resolve: resolve3, reject });
2980
3185
  });
2981
3186
  queue.pending.push({
2982
3187
  sessionId: sessionState.sessionId,
@@ -3100,205 +3305,6 @@ import { existsSync as existsSync3 } from "fs";
3100
3305
  import { join as join3 } from "path";
3101
3306
  import { cwd } from "process";
3102
3307
 
3103
- // lib/message-ids.ts
3104
- var MESSAGE_REF_REGEX = /^m(\d{4,5})$/;
3105
- var BLOCK_REF_REGEX = /^b([1-9]\d*)$/;
3106
- var MESSAGE_ID_TAG_NAME = "dcp-message-id";
3107
- var MESSAGE_REF_WIDTH = 5;
3108
- var MESSAGE_REF_MIN_INDEX = 1;
3109
- var MESSAGE_REF_MAX_INDEX = 99999;
3110
- function formatMessageRef(index) {
3111
- if (!Number.isInteger(index) || index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
3112
- throw new Error(
3113
- `Message ID index out of bounds: ${index}. Supported range is ${MESSAGE_REF_MIN_INDEX}-${MESSAGE_REF_MAX_INDEX}.`
3114
- );
3115
- }
3116
- return `m${index.toString().padStart(MESSAGE_REF_WIDTH, "0")}`;
3117
- }
3118
- function formatBlockRef(blockId) {
3119
- if (!Number.isInteger(blockId) || blockId < 1) {
3120
- throw new Error(`Invalid block ID: ${blockId}`);
3121
- }
3122
- return `b${blockId}`;
3123
- }
3124
- function parseMessageRef(ref) {
3125
- const normalized = ref.trim().toLowerCase();
3126
- const match = normalized.match(MESSAGE_REF_REGEX);
3127
- if (!match) {
3128
- return null;
3129
- }
3130
- const index = Number.parseInt(match[1], 10);
3131
- if (!Number.isInteger(index)) {
3132
- return null;
3133
- }
3134
- if (index < MESSAGE_REF_MIN_INDEX || index > MESSAGE_REF_MAX_INDEX) {
3135
- return null;
3136
- }
3137
- return index;
3138
- }
3139
- function parseBlockRef(ref) {
3140
- const normalized = ref.trim().toLowerCase();
3141
- const match = normalized.match(BLOCK_REF_REGEX);
3142
- if (!match) {
3143
- return null;
3144
- }
3145
- const id = Number.parseInt(match[1], 10);
3146
- return Number.isInteger(id) ? id : null;
3147
- }
3148
- function parseBoundaryId(id) {
3149
- const normalized = id.trim().toLowerCase();
3150
- const messageIndex = parseMessageRef(normalized);
3151
- if (messageIndex !== null) {
3152
- return {
3153
- kind: "message",
3154
- ref: formatMessageRef(messageIndex),
3155
- index: messageIndex
3156
- };
3157
- }
3158
- const blockId = parseBlockRef(normalized);
3159
- if (blockId !== null) {
3160
- return {
3161
- kind: "compressed-block",
3162
- ref: formatBlockRef(blockId),
3163
- blockId
3164
- };
3165
- }
3166
- return null;
3167
- }
3168
- function escapeXmlAttribute(value) {
3169
- return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3170
- }
3171
- function formatMessageIdTag(ref, attributes) {
3172
- const serializedAttributes = Object.entries(attributes || {}).sort(([left], [right]) => left.localeCompare(right)).map(([name, value]) => {
3173
- if (name.trim().length === 0 || typeof value !== "string" || value.length === 0) {
3174
- return "";
3175
- }
3176
- return ` ${name}="${escapeXmlAttribute(value)}"`;
3177
- }).join("");
3178
- return `
3179
- <${MESSAGE_ID_TAG_NAME}${serializedAttributes}>${ref}</${MESSAGE_ID_TAG_NAME}>`;
3180
- }
3181
- function formatTokenSize(tokens) {
3182
- if (tokens < 1e3) return String(tokens);
3183
- if (tokens < 1e4) return `${(tokens / 1e3).toFixed(1)}K`;
3184
- return `${Math.round(tokens / 1e3)}K`;
3185
- }
3186
- function classifyMessageType(parts) {
3187
- let hasTool = false;
3188
- let hasText = false;
3189
- let hasReasoning = false;
3190
- const toolNames = [];
3191
- for (const part of parts) {
3192
- if (part.type === "tool") {
3193
- hasTool = true;
3194
- if (typeof part.tool === "string" && !toolNames.includes(part.tool)) {
3195
- toolNames.push(part.tool);
3196
- }
3197
- } else if (part.type === "text") {
3198
- hasText = true;
3199
- } else if (part.type === "reasoning") {
3200
- hasReasoning = true;
3201
- }
3202
- }
3203
- if (hasTool) {
3204
- return toolNames.length > 0 ? `tool:${toolNames.join(",")}` : "tool";
3205
- }
3206
- if (hasReasoning && !hasText) return "reasoning";
3207
- return "text";
3208
- }
3209
- function assignMessageRefs(state, messages) {
3210
- let assigned = 0;
3211
- let skippedSubAgentPrompt = false;
3212
- for (const message of messages) {
3213
- if (isIgnoredUserMessage(message)) {
3214
- continue;
3215
- }
3216
- if (state.isSubAgent && !skippedSubAgentPrompt && message.info.role === "user") {
3217
- skippedSubAgentPrompt = true;
3218
- continue;
3219
- }
3220
- const rawMessageId = message.info.id;
3221
- if (typeof rawMessageId !== "string" || rawMessageId.length === 0) {
3222
- continue;
3223
- }
3224
- if (rawMessageId.startsWith("msg_dcp_summary_") || rawMessageId.startsWith("msg_dcp_text_")) {
3225
- continue;
3226
- }
3227
- const existingRef = state.messageIds.byRawId.get(rawMessageId);
3228
- if (existingRef) {
3229
- if (state.messageIds.byRef.get(existingRef) !== rawMessageId) {
3230
- state.messageIds.byRef.set(existingRef, rawMessageId);
3231
- }
3232
- continue;
3233
- }
3234
- const ref = allocateNextMessageRef(state);
3235
- state.messageIds.byRawId.set(rawMessageId, ref);
3236
- state.messageIds.byRef.set(ref, rawMessageId);
3237
- assigned++;
3238
- }
3239
- return assigned;
3240
- }
3241
- function repairNonMonotonicMessageRefs(state, messages) {
3242
- const eligible = messages.filter((message) => {
3243
- if (isIgnoredUserMessage(message)) return false;
3244
- const id = message.info.id;
3245
- return typeof id === "string" && id.length > 0 && !id.startsWith("msg_dcp_summary_") && !id.startsWith("msg_dcp_text_");
3246
- });
3247
- let previous = 0;
3248
- let corrupted = false;
3249
- for (const message of eligible) {
3250
- const ref = state.messageIds.byRawId.get(message.info.id);
3251
- const parsed = ref ? parseMessageRef(ref) : null;
3252
- if (parsed === null) continue;
3253
- if (parsed <= previous) {
3254
- corrupted = true;
3255
- break;
3256
- }
3257
- previous = parsed;
3258
- }
3259
- if (!corrupted) return false;
3260
- const oldRawIds = [...state.messageIds.byRawId.keys()];
3261
- state.messageIds.byRawId.clear();
3262
- state.messageIds.byRef.clear();
3263
- state.messageIds.nextRef = 1;
3264
- for (const message of eligible) {
3265
- const rawId = message.info.id;
3266
- const ref = allocateNextMessageRef(state);
3267
- state.messageIds.byRawId.set(rawId, ref);
3268
- state.messageIds.byRef.set(ref, rawId);
3269
- }
3270
- for (const rawId of oldRawIds) {
3271
- if (state.messageIds.byRawId.has(rawId)) continue;
3272
- const ref = allocateNextMessageRef(state);
3273
- state.messageIds.byRawId.set(rawId, ref);
3274
- state.messageIds.byRef.set(ref, rawId);
3275
- }
3276
- for (const block of state.prune.messages.blocksById.values()) {
3277
- const ids = [.../* @__PURE__ */ new Set([...block.effectiveMessageIds, ...block.directMessageIds])].filter((id) => state.messageIds.byRawId.has(id)).sort(
3278
- (left, right) => (parseMessageRef(state.messageIds.byRawId.get(left)) ?? 0) - (parseMessageRef(state.messageIds.byRawId.get(right)) ?? 0)
3279
- );
3280
- if (ids.length === 0) continue;
3281
- block.startId = state.messageIds.byRawId.get(ids[0]);
3282
- block.endId = state.messageIds.byRawId.get(ids[ids.length - 1]);
3283
- }
3284
- state.prune.messages.structureVersion = (state.prune.messages.structureVersion ?? 0) + 1;
3285
- return true;
3286
- }
3287
- function allocateNextMessageRef(state) {
3288
- let candidate = Number.isInteger(state.messageIds.nextRef) ? Math.max(MESSAGE_REF_MIN_INDEX, state.messageIds.nextRef) : MESSAGE_REF_MIN_INDEX;
3289
- while (candidate <= MESSAGE_REF_MAX_INDEX) {
3290
- const ref = formatMessageRef(candidate);
3291
- if (!state.messageIds.byRef.has(ref)) {
3292
- state.messageIds.nextRef = candidate + 1;
3293
- return ref;
3294
- }
3295
- candidate++;
3296
- }
3297
- throw new Error(
3298
- `Message ID alias capacity exceeded. Cannot allocate more than ${formatMessageRef(MESSAGE_REF_MAX_INDEX)} aliases in this session.`
3299
- );
3300
- }
3301
-
3302
3308
  // lib/compress/state.ts
3303
3309
  var DEFAULT_PROMOTION_THRESHOLD = 5;
3304
3310
  var COMPRESSED_BLOCK_HEADER = "[Compressed conversation section]";
@@ -4770,7 +4776,11 @@ function restoreForkCompressionState(state, forkMessages, parent, parentMessages
4770
4776
  directMessageIds,
4771
4777
  directToolIds,
4772
4778
  effectiveMessageIds,
4773
- effectiveToolIds
4779
+ effectiveToolIds,
4780
+ // [Issue #415] The parent record is raw persisted JSON; legacy
4781
+ // 4-digit boundary refs must be normalized like every other ref.
4782
+ startId: typeof block.startId === "string" ? migrateMessageRef(block.startId) : block.startId,
4783
+ endId: typeof block.endId === "string" ? migrateMessageRef(block.endId) : block.endId
4774
4784
  });
4775
4785
  }
4776
4786
  const activeParentIds = new Set(parent.prune.messages.activeBlockIds);
@@ -10283,6 +10293,102 @@ function extractTextContent(msg) {
10283
10293
  return textParts.join(" ").replace(/\s+/g, " ").trim();
10284
10294
  }
10285
10295
 
10296
+ // lib/compress/tofile-target.ts
10297
+ import * as fsp from "fs/promises";
10298
+ import { homedir as homedir3, tmpdir } from "os";
10299
+ import { dirname as dirname2, join as join5, relative, resolve } from "path";
10300
+ async function resolveSafeToFileTarget(targetPath, options) {
10301
+ const osTmp = tmpdir();
10302
+ const allowedDirs = options?.allowedDirs ?? [osTmp, join5(homedir3(), ".cache", "opencode")];
10303
+ const dirList = allowedDirs.join(" or ");
10304
+ if (typeof targetPath !== "string" || targetPath.length === 0) {
10305
+ return {
10306
+ ok: false,
10307
+ error: `Error: toFile path must be under ${dirList}. Got: ${targetPath}`
10308
+ };
10309
+ }
10310
+ const resolvedPath = resolve(targetPath);
10311
+ const lexicallyAllowed = allowedDirs.some((dir) => {
10312
+ const rel = relative(resolve(dir), resolvedPath);
10313
+ return rel === "" || !rel.startsWith("..") && !rel.startsWith("/") && !isWinAbsolute(rel);
10314
+ });
10315
+ if (!lexicallyAllowed) {
10316
+ return {
10317
+ ok: false,
10318
+ error: `Error: toFile path must be under ${dirList}. Got: ${targetPath}`
10319
+ };
10320
+ }
10321
+ let realAncestor = null;
10322
+ const tail = [];
10323
+ let current = resolvedPath;
10324
+ while (true) {
10325
+ try {
10326
+ realAncestor = await fsp.realpath(current);
10327
+ break;
10328
+ } catch (err) {
10329
+ const code = err.code;
10330
+ if (code !== "ENOENT" && code !== "ENOTDIR") {
10331
+ return {
10332
+ ok: false,
10333
+ error: `Error: cannot validate toFile path: ${err.message}`
10334
+ };
10335
+ }
10336
+ const parent = dirname2(current);
10337
+ if (parent === current) {
10338
+ return {
10339
+ ok: false,
10340
+ error: `Error: cannot validate toFile path: no existing ancestor found`
10341
+ };
10342
+ }
10343
+ tail.unshift(basename(current));
10344
+ current = parent;
10345
+ }
10346
+ }
10347
+ const physicalPath = tail.length > 0 ? join5(realAncestor, ...tail) : realAncestor;
10348
+ const realAllowedDirs = [];
10349
+ for (const dir of allowedDirs) {
10350
+ try {
10351
+ realAllowedDirs.push(await fsp.realpath(resolve(dir)));
10352
+ } catch {
10353
+ }
10354
+ }
10355
+ const physicallyAllowed = realAllowedDirs.some((dir) => containsOrIs(dir, physicalPath));
10356
+ if (!physicallyAllowed) {
10357
+ return {
10358
+ ok: false,
10359
+ error: `Error: toFile path resolves outside the allowed directories via a symlink. Got: ${targetPath}`
10360
+ };
10361
+ }
10362
+ try {
10363
+ const stat = await fsp.lstat(resolvedPath);
10364
+ if (stat.isSymbolicLink()) {
10365
+ return {
10366
+ ok: false,
10367
+ error: `Error: toFile target exists as a symlink and will not be followed. Got: ${targetPath}`
10368
+ };
10369
+ }
10370
+ } catch (err) {
10371
+ const code = err.code;
10372
+ if (code !== "ENOENT") {
10373
+ return {
10374
+ ok: false,
10375
+ error: `Error: cannot validate toFile path: ${err.message}`
10376
+ };
10377
+ }
10378
+ }
10379
+ return { ok: true, filePath: resolvedPath };
10380
+ }
10381
+ function isWinAbsolute(p) {
10382
+ return /^[a-zA-Z]:[\\/]/.test(p);
10383
+ }
10384
+ function basename(p) {
10385
+ return p.split(/[\\/]/).filter(Boolean).pop() ?? p;
10386
+ }
10387
+ function containsOrIs(root, p) {
10388
+ const rel = relative(root, p);
10389
+ return rel === "" || !rel.startsWith("..") && !rel.startsWith("/") && !isWinAbsolute(rel);
10390
+ }
10391
+
10286
10392
  // lib/compress/decompress.ts
10287
10393
  async function prepareDecompressSession(ctx, toolCtx) {
10288
10394
  await toolCtx.ask({
@@ -10316,7 +10422,13 @@ function resolveTargets(args, state, rawMessages, logger) {
10316
10422
  if (mode.mode === "block") {
10317
10423
  return resolveSingleBlockTarget(messagesState, args.blockId);
10318
10424
  }
10319
- return resolveRangeTarget(state, rawMessages, args.startId, args.endId, logger);
10425
+ return resolveRangeTarget(
10426
+ state,
10427
+ rawMessages,
10428
+ args.startId,
10429
+ args.endId,
10430
+ logger
10431
+ );
10320
10432
  }
10321
10433
  function resolveSingleBlockTarget(messagesState, blockIdArg) {
10322
10434
  const targetBlockId = parseBlockIdArg(blockIdArg);
@@ -10428,11 +10540,21 @@ IMPORTANT:
10428
10540
  - Do NOT call this tool in parallel with compress \u2014 their state mutations may conflict.`;
10429
10541
  function buildSchema2() {
10430
10542
  return {
10431
- blockId: tool3.schema.string().optional().describe('Block reference to decompress (e.g., "b0", "b2"). Mutually exclusive with startId/endId.'),
10432
- startId: tool3.schema.string().optional().describe('Range start: message ref (e.g., "m00150") or block ref (e.g., "b2"). Used with endId.'),
10433
- endId: tool3.schema.string().optional().describe('Range end: message ref (e.g., "m00200") or block ref (e.g., "b5"). Used with startId.'),
10434
- toFile: tool3.schema.string().optional().describe("If provided, writes restored content to this file path instead of inflating context. Block stays compressed. Path must be under /tmp or ~/.cache/opencode/. Example: '/tmp/block52.txt'"),
10435
- full: tool3.schema.boolean().optional().describe("If true, restores ALL content down to original messages (multi-level decompress). Default: false \u2014 restores one tier up (e.g., decompressing a T2 block restores T1 summaries, not raw messages). Use full:true only when you need the exact original content and have context budget for it.")
10543
+ blockId: tool3.schema.string().optional().describe(
10544
+ 'Block reference to decompress (e.g., "b0", "b2"). Mutually exclusive with startId/endId.'
10545
+ ),
10546
+ startId: tool3.schema.string().optional().describe(
10547
+ 'Range start: message ref (e.g., "m00150") or block ref (e.g., "b2"). Used with endId.'
10548
+ ),
10549
+ endId: tool3.schema.string().optional().describe(
10550
+ 'Range end: message ref (e.g., "m00200") or block ref (e.g., "b5"). Used with startId.'
10551
+ ),
10552
+ toFile: tool3.schema.string().optional().describe(
10553
+ "If provided, writes restored content to this file path instead of inflating context. Block stays compressed. Path must be under /tmp or ~/.cache/opencode/. Example: '/tmp/block52.txt'"
10554
+ ),
10555
+ full: tool3.schema.boolean().optional().describe(
10556
+ "If true, restores ALL content down to original messages (multi-level decompress). Default: false \u2014 restores one tier up (e.g., decompressing a T2 block restores T1 summaries, not raw messages). Use full:true only when you need the exact original content and have context budget for it."
10557
+ )
10436
10558
  };
10437
10559
  }
10438
10560
  function extractMessageId(m) {
@@ -10473,7 +10595,12 @@ function createDecompressTool(factoryCtx) {
10473
10595
  const contextUsageBefore = effectiveLimitBefore ? Math.round(
10474
10596
  getCurrentTokenUsage(ctx.state, rawMessages) / effectiveLimitBefore.limit * 100
10475
10597
  ) : void 0;
10476
- const resolved = resolveTargets(args, ctx.state, rawMessages, ctx.logger);
10598
+ const resolved = resolveTargets(
10599
+ args,
10600
+ ctx.state,
10601
+ rawMessages,
10602
+ ctx.logger
10603
+ );
10477
10604
  if (!resolved.ok) {
10478
10605
  return resolved.error;
10479
10606
  }
@@ -10489,19 +10616,9 @@ function createDecompressTool(factoryCtx) {
10489
10616
  }
10490
10617
  if (args.toFile) {
10491
10618
  const targetPath = args.toFile;
10492
- const os = await import("os");
10493
- const path = await import("path");
10494
- const allowedDirs = [
10495
- os.tmpdir() + "/",
10496
- path.join(os.homedir(), ".cache", "opencode") + "/"
10497
- ];
10498
- const resolvedPath = path.resolve(targetPath);
10499
- const isAllowed = allowedDirs.some((dir) => {
10500
- const rel = path.relative(dir, resolvedPath);
10501
- return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
10502
- });
10503
- if (!isAllowed) {
10504
- return `Error: toFile path must be under ${os.tmpdir()} or ~/.cache/opencode/. Got: ${targetPath}`;
10619
+ const safe = await resolveSafeToFileTarget(targetPath);
10620
+ if (!safe.ok) {
10621
+ return safe.error;
10505
10622
  }
10506
10623
  const msgIdSet = /* @__PURE__ */ new Set();
10507
10624
  for (const block of activeBlocks) {
@@ -10511,7 +10628,8 @@ function createDecompressTool(factoryCtx) {
10511
10628
  }
10512
10629
  const blockMessages = rawMessages.filter((m) => msgIdSet.has(extractMessageId(m)));
10513
10630
  const lines2 = blockMessages.map(extractMessageText2);
10514
- const { writeFile: writeFile4 } = await import("fs/promises");
10631
+ const fsp2 = await import("fs/promises");
10632
+ const { constants } = await import("fs");
10515
10633
  let fileContent;
10516
10634
  let sourceNote;
10517
10635
  if (lines2.length > 0) {
@@ -10521,9 +10639,19 @@ function createDecompressTool(factoryCtx) {
10521
10639
  fileContent = targets[0]?.blocks[0]?.summary ?? "(no content available)";
10522
10640
  sourceNote = `no original messages found; wrote block summary (${targets.length} block${targets.length === 1 ? "" : "s"})`;
10523
10641
  }
10524
- await writeFile4(targetPath, fileContent, "utf-8");
10642
+ const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | (process.platform === "win32" ? 0 : constants.O_NOFOLLOW);
10643
+ try {
10644
+ const handle = await fsp2.open(safe.filePath, flags, 384);
10645
+ try {
10646
+ await handle.write(Buffer.from(fileContent, "utf-8"));
10647
+ } finally {
10648
+ await handle.close();
10649
+ }
10650
+ } catch (err) {
10651
+ return `Error: toFile write failed: ${err.message}`;
10652
+ }
10525
10653
  const displayIds2 = targets.map((t) => `b${t.displayId}`).join(", ");
10526
- return `Block(s) ${displayIds2} content (${sourceNote}, ${fileContent.length} chars) written to ${targetPath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
10654
+ return `Block(s) ${displayIds2} content (${sourceNote}, ${fileContent.length} chars) written to ${safe.filePath}. Block(s) stay compressed \u2014 context unchanged. Use read tool to access specific parts.`;
10527
10655
  }
10528
10656
  const availability = checkDecompressSourceAvailability(
10529
10657
  messagesState,
@@ -10620,7 +10748,9 @@ function isLiveBlock(block) {
10620
10748
  return block.active && !block.deactivatedByUser && !block.deactivatedByUserDeep;
10621
10749
  }
10622
10750
  function rangeKey(startId, endId) {
10623
- return `${startId}::${endId}`;
10751
+ const s = typeof startId === "string" ? migrateMessageRef(startId) : startId;
10752
+ const e = typeof endId === "string" ? migrateMessageRef(endId) : endId;
10753
+ return `${s}::${e}`;
10624
10754
  }
10625
10755
  function rewriteCompressInput(part, liveKeys) {
10626
10756
  if (part.type !== "tool") return null;
@@ -11355,10 +11485,10 @@ function hideFailedCompressCalls(messages) {
11355
11485
 
11356
11486
  // lib/logger.ts
11357
11487
  import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
11358
- import { join as join5 } from "path";
11488
+ import { join as join6 } from "path";
11359
11489
  import { existsSync as existsSync5 } from "fs";
11360
- import { homedir as homedir3 } from "os";
11361
- var LOG_VERSION = true ? "1.18.4-pr.488.221" : "dev";
11490
+ import { homedir as homedir4 } from "os";
11491
+ var LOG_VERSION = true ? "1.18.5-pr.416.226" : "dev";
11362
11492
  var LEVEL_RANK = {
11363
11493
  debug: 10,
11364
11494
  info: 20,
@@ -11378,8 +11508,8 @@ var Logger = class {
11378
11508
  constructor(enabled, level) {
11379
11509
  const resolved = level !== void 0 && LEVEL_RANK[level] !== void 0 ? level : void 0;
11380
11510
  this.level = resolved ?? (enabled ? "debug" : "warn");
11381
- const configHome = process.env.XDG_CONFIG_HOME || join5(homedir3(), ".config");
11382
- this.logDir = join5(configHome, "opencode", "logs", "acp");
11511
+ const configHome = process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config");
11512
+ this.logDir = join6(configHome, "opencode", "logs", "acp");
11383
11513
  }
11384
11514
  /** Back-compat view of the old flag: true only at full debug verbosity. */
11385
11515
  get enabled() {
@@ -11441,11 +11571,11 @@ var Logger = class {
11441
11571
  const dataStr = this.formatData(data);
11442
11572
  const logLine = `${timestamp} ${level.padEnd(5)} ${component}: ${message}${dataStr ? " | " + dataStr : ""} | v=${LOG_VERSION}
11443
11573
  `;
11444
- const dailyLogDir = join5(this.logDir, "daily");
11574
+ const dailyLogDir = join6(this.logDir, "daily");
11445
11575
  if (!existsSync5(dailyLogDir)) {
11446
11576
  await mkdir2(dailyLogDir, { recursive: true });
11447
11577
  }
11448
- const logFile = join5(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
11578
+ const logFile = join6(dailyLogDir, `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.log`);
11449
11579
  await writeFile2(logFile, logLine, { flag: "a" });
11450
11580
  } catch (error) {
11451
11581
  }
@@ -11556,7 +11686,7 @@ var Logger = class {
11556
11686
  async saveContext(sessionId, messages) {
11557
11687
  if (this.level !== "debug") return;
11558
11688
  try {
11559
- const contextDir = join5(this.logDir, "context", sessionId);
11689
+ const contextDir = join6(this.logDir, "context", sessionId);
11560
11690
  if (!existsSync5(contextDir)) {
11561
11691
  await mkdir2(contextDir, { recursive: true });
11562
11692
  }
@@ -11564,9 +11694,9 @@ var Logger = class {
11564
11694
  (msg) => msg.parts && msg.parts.length > 0
11565
11695
  );
11566
11696
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
11567
- const contextFile = join5(contextDir, `${timestamp}.json`);
11697
+ const contextFile = join6(contextDir, `${timestamp}.json`);
11568
11698
  await writeFile2(contextFile, JSON.stringify(minimized, null, 2));
11569
- const versionFile = join5(contextDir, "_version");
11699
+ const versionFile = join6(contextDir, "_version");
11570
11700
  if (!existsSync5(versionFile)) {
11571
11701
  await writeFile2(versionFile, LOG_VERSION);
11572
11702
  }
@@ -11577,8 +11707,8 @@ var Logger = class {
11577
11707
 
11578
11708
  // lib/prompts/store.ts
11579
11709
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3, statSync as statSync3 } from "fs";
11580
- import { join as join6, dirname as dirname2 } from "path";
11581
- import { homedir as homedir4 } from "os";
11710
+ import { join as join7, dirname as dirname3 } from "path";
11711
+ import { homedir as homedir5 } from "os";
11582
11712
 
11583
11713
  // lib/prompts/system.ts
11584
11714
  var ACP_STATUS_TOOL_LINE_RANGES = `- \`acp_status\` \u2014 Context status with compressible ranges. No args = overview + ranges. \`scope:"uncompressed"\` for range view; add \`view:"messages"\` for per-message listing with \`tool\`/\`sort\` filters. \`scope:"compressed"\` for block details.`;
@@ -11885,7 +12015,7 @@ function createBundledRuntimePrompts(candidatesEnabled) {
11885
12015
  function findOpencodeDir2(startDir) {
11886
12016
  let current = startDir;
11887
12017
  while (current !== "/") {
11888
- const candidate = join6(current, ".opencode");
12018
+ const candidate = join7(current, ".opencode");
11889
12019
  if (existsSync6(candidate)) {
11890
12020
  try {
11891
12021
  if (statSync3(candidate).isDirectory()) {
@@ -11894,7 +12024,7 @@ function findOpencodeDir2(startDir) {
11894
12024
  } catch {
11895
12025
  }
11896
12026
  }
11897
- const parent = dirname2(current);
12027
+ const parent = dirname3(current);
11898
12028
  if (parent === current) {
11899
12029
  break;
11900
12030
  }
@@ -11903,13 +12033,13 @@ function findOpencodeDir2(startDir) {
11903
12033
  return null;
11904
12034
  }
11905
12035
  function resolvePromptPaths(workingDirectory) {
11906
- const configHome = process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config");
11907
- const globalRoot = join6(configHome, "opencode", "acp-prompts");
11908
- const defaultsDir = join6(globalRoot, "defaults");
11909
- const globalOverridesDir = join6(globalRoot, "overrides");
11910
- const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join6(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
12036
+ const configHome = process.env.XDG_CONFIG_HOME || join7(homedir5(), ".config");
12037
+ const globalRoot = join7(configHome, "opencode", "acp-prompts");
12038
+ const defaultsDir = join7(globalRoot, "defaults");
12039
+ const globalOverridesDir = join7(globalRoot, "overrides");
12040
+ const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ? join7(process.env.OPENCODE_CONFIG_DIR, "acp-prompts", "overrides") : null;
11911
12041
  const opencodeDir = findOpencodeDir2(workingDirectory);
11912
- const projectOverridesDir = opencodeDir ? join6(opencodeDir, "acp-prompts", "overrides") : null;
12042
+ const projectOverridesDir = opencodeDir ? join7(opencodeDir, "acp-prompts", "overrides") : null;
11913
12043
  return {
11914
12044
  defaultsDir,
11915
12045
  globalOverridesDir,
@@ -12082,16 +12212,16 @@ var PromptStore = class {
12082
12212
  const candidates = [];
12083
12213
  if (this.paths.projectOverridesDir) {
12084
12214
  candidates.push({
12085
- path: join6(this.paths.projectOverridesDir, fileName)
12215
+ path: join7(this.paths.projectOverridesDir, fileName)
12086
12216
  });
12087
12217
  }
12088
12218
  if (this.paths.configDirOverridesDir) {
12089
12219
  candidates.push({
12090
- path: join6(this.paths.configDirOverridesDir, fileName)
12220
+ path: join7(this.paths.configDirOverridesDir, fileName)
12091
12221
  });
12092
12222
  }
12093
12223
  candidates.push({
12094
- path: join6(this.paths.globalOverridesDir, fileName)
12224
+ path: join7(this.paths.globalOverridesDir, fileName)
12095
12225
  });
12096
12226
  return candidates;
12097
12227
  }
@@ -12115,7 +12245,7 @@ var PromptStore = class {
12115
12245
  const managedContent = buildDefaultPromptFileContent(
12116
12246
  bundledEditable || bundledPrompts[definition.runtimeField]
12117
12247
  );
12118
- const filePath = join6(this.paths.defaultsDir, definition.fileName);
12248
+ const filePath = join7(this.paths.defaultsDir, definition.fileName);
12119
12249
  try {
12120
12250
  const existing = readFileIfExists(filePath);
12121
12251
  if (existing === managedContent) {
@@ -12129,7 +12259,7 @@ var PromptStore = class {
12129
12259
  });
12130
12260
  }
12131
12261
  }
12132
- const readmePath = join6(this.paths.defaultsDir, DEFAULTS_README_FILE);
12262
+ const readmePath = join7(this.paths.defaultsDir, DEFAULTS_README_FILE);
12133
12263
  const readmeContent = buildDefaultsReadmeContent();
12134
12264
  try {
12135
12265
  const existing = readFileIfExists(readmePath);
@@ -12672,7 +12802,7 @@ ${report}`;
12672
12802
  // lib/commands/export.ts
12673
12803
  import * as fs2 from "fs/promises";
12674
12804
  import { existsSync as existsSync7 } from "fs";
12675
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join7, resolve } from "path";
12805
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join8, resolve as resolve2 } from "path";
12676
12806
  var ALL_TIERS = [1, 2, 3];
12677
12807
  var TIER_NAMES = {
12678
12808
  1: "Tier 1 \u2014 Capture",
@@ -12785,7 +12915,7 @@ function parseTiers(raw) {
12785
12915
  }
12786
12916
  function resolveDefaultOutputPath(sessionId, cwd2) {
12787
12917
  const short = (sessionId || "session").slice(0, 8);
12788
- return join7(cwd2, ".opencode", `acp-export-${short}.md`);
12918
+ return join8(cwd2, ".opencode", `acp-export-${short}.md`);
12789
12919
  }
12790
12920
  function formatTokens2(n) {
12791
12921
  if (!Number.isFinite(n) || n <= 0) return "0";
@@ -12913,9 +13043,9 @@ async function handleExportCommand(ctx, args) {
12913
13043
  return;
12914
13044
  }
12915
13045
  const cwd2 = ctx.workingDirectory || process.cwd();
12916
- const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
13046
+ const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve2(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
12917
13047
  try {
12918
- const dir = dirname3(targetPath);
13048
+ const dir = dirname4(targetPath);
12919
13049
  if (!existsSync7(dir)) {
12920
13050
  await fs2.mkdir(dir, { recursive: true });
12921
13051
  }
@@ -13945,7 +14075,7 @@ function configureClientAuth(client) {
13945
14075
 
13946
14076
  // lib/update.ts
13947
14077
  import { readFile as readFile2, rm } from "fs/promises";
13948
- import { basename, dirname as dirname4, join as join8 } from "path";
14078
+ import { basename as basename2, dirname as dirname5, join as join9 } from "path";
13949
14079
  import { fileURLToPath } from "url";
13950
14080
  var PACKAGE_NAME = "opencode-acp";
13951
14081
  function startAutoUpdate(ctx, enabled, logger) {
@@ -13992,7 +14122,7 @@ async function checkAutoUpdate(signal, logger) {
13992
14122
  logger?.info("Auto-update skipped: package dir not found");
13993
14123
  return { updated: false };
13994
14124
  }
13995
- const pkg = await readPackageJson(join8(packageDir, "package.json"));
14125
+ const pkg = await readPackageJson(join9(packageDir, "package.json"));
13996
14126
  if (!pkg?.name || !pkg.version) {
13997
14127
  logger?.info("Auto-update skipped: package.json unreadable", { packageDir });
13998
14128
  return { updated: false };
@@ -14031,21 +14161,21 @@ async function checkAutoUpdate(signal, logger) {
14031
14161
  return { updated: true, name: pkg.name, current: pkg.version, latest };
14032
14162
  }
14033
14163
  async function findPackageDir(name) {
14034
- let dir = dirname4(fileURLToPath(import.meta.url));
14164
+ let dir = dirname5(fileURLToPath(import.meta.url));
14035
14165
  for (; ; ) {
14036
- const pkg = await readPackageJson(join8(dir, "package.json"));
14166
+ const pkg = await readPackageJson(join9(dir, "package.json"));
14037
14167
  if (pkg?.name === name) return dir;
14038
- const parent = dirname4(dir);
14168
+ const parent = dirname5(dir);
14039
14169
  if (parent === dir) return void 0;
14040
14170
  dir = parent;
14041
14171
  }
14042
14172
  }
14043
14173
  async function updateTarget(packageDir, name) {
14044
- const packageParent = dirname4(packageDir);
14045
- const nodeModulesDir = basename(packageParent).startsWith("@") ? dirname4(packageParent) : packageParent;
14046
- if (basename(nodeModulesDir) !== "node_modules") return void 0;
14047
- const wrapperDir = dirname4(nodeModulesDir);
14048
- const wrapperPkg = await readPackageJson(join8(wrapperDir, "package.json"));
14174
+ const packageParent = dirname5(packageDir);
14175
+ const nodeModulesDir = basename2(packageParent).startsWith("@") ? dirname5(packageParent) : packageParent;
14176
+ if (basename2(nodeModulesDir) !== "node_modules") return void 0;
14177
+ const wrapperDir = dirname5(nodeModulesDir);
14178
+ const wrapperPkg = await readPackageJson(join9(wrapperDir, "package.json"));
14049
14179
  const spec = wrapperSpec(wrapperDir, name) ?? wrapperPkg?.dependencies?.[name];
14050
14180
  if (!spec || !isAutoUpdatableSpec(spec)) return void 0;
14051
14181
  return { removeDir: wrapperDir, spec };
@@ -14053,13 +14183,13 @@ async function updateTarget(packageDir, name) {
14053
14183
  function wrapperSpec(wrapperDir, name) {
14054
14184
  if (name.startsWith("@")) {
14055
14185
  const [scope, pkg] = name.split("/");
14056
- if (!scope || !pkg || basename(dirname4(wrapperDir)) !== scope) return void 0;
14186
+ if (!scope || !pkg || basename2(dirname5(wrapperDir)) !== scope) return void 0;
14057
14187
  const prefix2 = `${pkg}@`;
14058
- const base2 = basename(wrapperDir);
14188
+ const base2 = basename2(wrapperDir);
14059
14189
  return base2.startsWith(prefix2) ? base2.slice(prefix2.length) : void 0;
14060
14190
  }
14061
14191
  const prefix = `${name}@`;
14062
- const base = basename(wrapperDir);
14192
+ const base = basename2(wrapperDir);
14063
14193
  return base.startsWith(prefix) ? base.slice(prefix.length) : void 0;
14064
14194
  }
14065
14195
  function isAutoUpdatableSpec(spec) {
@@ -14156,7 +14286,7 @@ var server = (async (ctx) => {
14156
14286
  }
14157
14287
  const logger = new Logger(config.debug, config.debug ? "debug" : config.logLevel);
14158
14288
  logger.info("ACP plugin initialized", {
14159
- version: true ? "1.18.4-pr.488.221" : "dev",
14289
+ version: true ? "1.18.5-pr.416.226" : "dev",
14160
14290
  workspace: ctx.directory,
14161
14291
  logLevel: logger.level,
14162
14292
  debug: config.debug,