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 +401 -271
- package/dist/index.js.map +1 -1
- package/dist/lib/compress/decompress.d.ts.map +1 -1
- package/dist/lib/compress/hide-consumed.d.ts.map +1 -1
- package/dist/lib/compress/tofile-target.d.ts +31 -0
- package/dist/lib/compress/tofile-target.d.ts.map +1 -0
- package/dist/lib/message-ids.d.ts +8 -0
- package/dist/lib/message-ids.d.ts.map +1 -1
- package/dist/lib/state/rebuild.d.ts.map +1 -1
- package/dist/lib/state/utils.d.ts.map +1 -1
- package/package.json +1 -1
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
-
|
|
2751
|
-
|
|
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((
|
|
2979
|
-
queue.waiters.push({ resolve:
|
|
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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
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(
|
|
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(
|
|
10432
|
-
|
|
10433
|
-
|
|
10434
|
-
|
|
10435
|
-
|
|
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(
|
|
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
|
|
10493
|
-
|
|
10494
|
-
|
|
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
|
|
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
|
-
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
11488
|
+
import { join as join6 } from "path";
|
|
11359
11489
|
import { existsSync as existsSync5 } from "fs";
|
|
11360
|
-
import { homedir as
|
|
11361
|
-
var LOG_VERSION = true ? "1.18.
|
|
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 ||
|
|
11382
|
-
this.logDir =
|
|
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 =
|
|
11574
|
+
const dailyLogDir = join6(this.logDir, "daily");
|
|
11445
11575
|
if (!existsSync5(dailyLogDir)) {
|
|
11446
11576
|
await mkdir2(dailyLogDir, { recursive: true });
|
|
11447
11577
|
}
|
|
11448
|
-
const logFile =
|
|
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 =
|
|
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 =
|
|
11697
|
+
const contextFile = join6(contextDir, `${timestamp}.json`);
|
|
11568
11698
|
await writeFile2(contextFile, JSON.stringify(minimized, null, 2));
|
|
11569
|
-
const versionFile =
|
|
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
|
|
11581
|
-
import { homedir as
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
11907
|
-
const globalRoot =
|
|
11908
|
-
const defaultsDir =
|
|
11909
|
-
const globalOverridesDir =
|
|
11910
|
-
const configDirOverridesDir = process.env.OPENCODE_CONFIG_DIR ?
|
|
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 ?
|
|
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:
|
|
12215
|
+
path: join7(this.paths.projectOverridesDir, fileName)
|
|
12086
12216
|
});
|
|
12087
12217
|
}
|
|
12088
12218
|
if (this.paths.configDirOverridesDir) {
|
|
12089
12219
|
candidates.push({
|
|
12090
|
-
path:
|
|
12220
|
+
path: join7(this.paths.configDirOverridesDir, fileName)
|
|
12091
12221
|
});
|
|
12092
12222
|
}
|
|
12093
12223
|
candidates.push({
|
|
12094
|
-
path:
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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 :
|
|
13046
|
+
const targetPath = options.outputPath !== "" ? isAbsolute2(options.outputPath) ? options.outputPath : resolve2(cwd2, options.outputPath) : resolveDefaultOutputPath(sessionId, cwd2);
|
|
12917
13047
|
try {
|
|
12918
|
-
const dir =
|
|
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
|
|
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(
|
|
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 =
|
|
14164
|
+
let dir = dirname5(fileURLToPath(import.meta.url));
|
|
14035
14165
|
for (; ; ) {
|
|
14036
|
-
const pkg = await readPackageJson(
|
|
14166
|
+
const pkg = await readPackageJson(join9(dir, "package.json"));
|
|
14037
14167
|
if (pkg?.name === name) return dir;
|
|
14038
|
-
const parent =
|
|
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 =
|
|
14045
|
-
const nodeModulesDir =
|
|
14046
|
-
if (
|
|
14047
|
-
const wrapperDir =
|
|
14048
|
-
const wrapperPkg = await readPackageJson(
|
|
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 ||
|
|
14186
|
+
if (!scope || !pkg || basename2(dirname5(wrapperDir)) !== scope) return void 0;
|
|
14057
14187
|
const prefix2 = `${pkg}@`;
|
|
14058
|
-
const base2 =
|
|
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 =
|
|
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.
|
|
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,
|