oh-my-opencode 2.5.3 → 2.5.4
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/README.ja.md +14 -15
- package/README.ko.md +14 -15
- package/README.md +14 -15
- package/README.zh-cn.md +14 -15
- package/dist/cli/index.js +185 -25
- package/dist/cli/run/events.d.ts +4 -0
- package/dist/cli/run/types.d.ts +26 -0
- package/dist/config/schema.d.ts +5 -7
- package/dist/features/background-agent/types.d.ts +8 -0
- package/dist/hooks/anthropic-auto-compact/executor.test.d.ts +1 -0
- package/dist/index.js +347 -294
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3216,7 +3216,7 @@ async function getContextWindowUsage(ctx, sessionID) {
|
|
|
3216
3216
|
return null;
|
|
3217
3217
|
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
3218
3218
|
const lastTokens = lastAssistant.tokens;
|
|
3219
|
-
const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0);
|
|
3219
|
+
const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0) + (lastTokens?.output ?? 0);
|
|
3220
3220
|
const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - usedTokens;
|
|
3221
3221
|
return {
|
|
3222
3222
|
usedTokens,
|
|
@@ -3663,6 +3663,24 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
3663
3663
|
errorSessions.delete(sessionID);
|
|
3664
3664
|
return;
|
|
3665
3665
|
}
|
|
3666
|
+
let freshTodos = [];
|
|
3667
|
+
try {
|
|
3668
|
+
log(`[${HOOK_NAME}] Re-verifying todos after countdown`, { sessionID });
|
|
3669
|
+
const response = await ctx.client.session.todo({
|
|
3670
|
+
path: { id: sessionID }
|
|
3671
|
+
});
|
|
3672
|
+
freshTodos = response.data ?? response;
|
|
3673
|
+
log(`[${HOOK_NAME}] Fresh todo count`, { sessionID, todosCount: freshTodos?.length ?? 0 });
|
|
3674
|
+
} catch (err) {
|
|
3675
|
+
log(`[${HOOK_NAME}] Failed to re-verify todos`, { sessionID, error: String(err) });
|
|
3676
|
+
return;
|
|
3677
|
+
}
|
|
3678
|
+
const freshIncomplete = freshTodos.filter((t) => t.status !== "completed" && t.status !== "cancelled");
|
|
3679
|
+
if (freshIncomplete.length === 0) {
|
|
3680
|
+
log(`[${HOOK_NAME}] Abort: no incomplete todos after countdown`, { sessionID, total: freshTodos.length });
|
|
3681
|
+
return;
|
|
3682
|
+
}
|
|
3683
|
+
log(`[${HOOK_NAME}] Confirmed incomplete todos, proceeding with injection`, { sessionID, incomplete: freshIncomplete.length, total: freshTodos.length });
|
|
3666
3684
|
remindedSessions.add(sessionID);
|
|
3667
3685
|
try {
|
|
3668
3686
|
const messageDir = getMessageDir(sessionID);
|
|
@@ -3683,7 +3701,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
3683
3701
|
type: "text",
|
|
3684
3702
|
text: `${CONTINUATION_PROMPT}
|
|
3685
3703
|
|
|
3686
|
-
[Status: ${
|
|
3704
|
+
[Status: ${freshTodos.length - freshIncomplete.length}/${freshTodos.length} completed, ${freshIncomplete.length} remaining]`
|
|
3687
3705
|
}
|
|
3688
3706
|
]
|
|
3689
3707
|
},
|
|
@@ -5082,6 +5100,7 @@ function clearInjectedPaths(sessionID) {
|
|
|
5082
5100
|
function createDirectoryAgentsInjectorHook(ctx) {
|
|
5083
5101
|
const sessionCaches = new Map;
|
|
5084
5102
|
const pendingBatchReads = new Map;
|
|
5103
|
+
const truncator = createDynamicTruncator(ctx);
|
|
5085
5104
|
function getSessionCache(sessionID) {
|
|
5086
5105
|
if (!sessionCaches.has(sessionID)) {
|
|
5087
5106
|
sessionCaches.set(sessionID, loadInjectedPaths(sessionID));
|
|
@@ -5114,7 +5133,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
5114
5133
|
}
|
|
5115
5134
|
return found.reverse();
|
|
5116
5135
|
}
|
|
5117
|
-
function processFilePathForInjection(filePath, sessionID, output) {
|
|
5136
|
+
async function processFilePathForInjection(filePath, sessionID, output) {
|
|
5118
5137
|
const resolved = resolveFilePath2(filePath);
|
|
5119
5138
|
if (!resolved)
|
|
5120
5139
|
return;
|
|
@@ -5127,10 +5146,14 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
5127
5146
|
continue;
|
|
5128
5147
|
try {
|
|
5129
5148
|
const content = readFileSync5(agentsPath, "utf-8");
|
|
5149
|
+
const { result, truncated } = await truncator.truncate(sessionID, content);
|
|
5150
|
+
const truncationNotice = truncated ? `
|
|
5151
|
+
|
|
5152
|
+
[Note: Content was truncated to save context window space. For full context, please read the file directly: ${agentsPath}]` : "";
|
|
5130
5153
|
output.output += `
|
|
5131
5154
|
|
|
5132
5155
|
[Directory Context: ${agentsPath}]
|
|
5133
|
-
${
|
|
5156
|
+
${result}${truncationNotice}`;
|
|
5134
5157
|
cache.add(agentsDir);
|
|
5135
5158
|
} catch {}
|
|
5136
5159
|
}
|
|
@@ -5155,14 +5178,14 @@ ${content}`;
|
|
|
5155
5178
|
const toolExecuteAfter = async (input, output) => {
|
|
5156
5179
|
const toolName = input.tool.toLowerCase();
|
|
5157
5180
|
if (toolName === "read") {
|
|
5158
|
-
processFilePathForInjection(output.title, input.sessionID, output);
|
|
5181
|
+
await processFilePathForInjection(output.title, input.sessionID, output);
|
|
5159
5182
|
return;
|
|
5160
5183
|
}
|
|
5161
5184
|
if (toolName === "batch") {
|
|
5162
5185
|
const filePaths = pendingBatchReads.get(input.callID);
|
|
5163
5186
|
if (filePaths) {
|
|
5164
5187
|
for (const filePath of filePaths) {
|
|
5165
|
-
processFilePathForInjection(filePath, input.sessionID, output);
|
|
5188
|
+
await processFilePathForInjection(filePath, input.sessionID, output);
|
|
5166
5189
|
}
|
|
5167
5190
|
pendingBatchReads.delete(input.callID);
|
|
5168
5191
|
}
|
|
@@ -5249,6 +5272,7 @@ function clearInjectedPaths2(sessionID) {
|
|
|
5249
5272
|
function createDirectoryReadmeInjectorHook(ctx) {
|
|
5250
5273
|
const sessionCaches = new Map;
|
|
5251
5274
|
const pendingBatchReads = new Map;
|
|
5275
|
+
const truncator = createDynamicTruncator(ctx);
|
|
5252
5276
|
function getSessionCache(sessionID) {
|
|
5253
5277
|
if (!sessionCaches.has(sessionID)) {
|
|
5254
5278
|
sessionCaches.set(sessionID, loadInjectedPaths2(sessionID));
|
|
@@ -5281,7 +5305,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
5281
5305
|
}
|
|
5282
5306
|
return found.reverse();
|
|
5283
5307
|
}
|
|
5284
|
-
function processFilePathForInjection(filePath, sessionID, output) {
|
|
5308
|
+
async function processFilePathForInjection(filePath, sessionID, output) {
|
|
5285
5309
|
const resolved = resolveFilePath2(filePath);
|
|
5286
5310
|
if (!resolved)
|
|
5287
5311
|
return;
|
|
@@ -5294,10 +5318,14 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
5294
5318
|
continue;
|
|
5295
5319
|
try {
|
|
5296
5320
|
const content = readFileSync7(readmePath, "utf-8");
|
|
5321
|
+
const { result, truncated } = await truncator.truncate(sessionID, content);
|
|
5322
|
+
const truncationNotice = truncated ? `
|
|
5323
|
+
|
|
5324
|
+
[Note: Content was truncated to save context window space. For full context, please read the file directly: ${readmePath}]` : "";
|
|
5297
5325
|
output.output += `
|
|
5298
5326
|
|
|
5299
5327
|
[Project README: ${readmePath}]
|
|
5300
|
-
${
|
|
5328
|
+
${result}${truncationNotice}`;
|
|
5301
5329
|
cache.add(readmeDir);
|
|
5302
5330
|
} catch {}
|
|
5303
5331
|
}
|
|
@@ -5322,14 +5350,14 @@ ${content}`;
|
|
|
5322
5350
|
const toolExecuteAfter = async (input, output) => {
|
|
5323
5351
|
const toolName = input.tool.toLowerCase();
|
|
5324
5352
|
if (toolName === "read") {
|
|
5325
|
-
processFilePathForInjection(output.title, input.sessionID, output);
|
|
5353
|
+
await processFilePathForInjection(output.title, input.sessionID, output);
|
|
5326
5354
|
return;
|
|
5327
5355
|
}
|
|
5328
5356
|
if (toolName === "batch") {
|
|
5329
5357
|
const filePaths = pendingBatchReads.get(input.callID);
|
|
5330
5358
|
if (filePaths) {
|
|
5331
5359
|
for (const filePath of filePaths) {
|
|
5332
|
-
processFilePathForInjection(filePath, input.sessionID, output);
|
|
5360
|
+
await processFilePathForInjection(filePath, input.sessionID, output);
|
|
5333
5361
|
}
|
|
5334
5362
|
pendingBatchReads.delete(input.callID);
|
|
5335
5363
|
}
|
|
@@ -5875,244 +5903,250 @@ async function fixEmptyMessages(sessionID, autoCompactState, client, messageInde
|
|
|
5875
5903
|
}
|
|
5876
5904
|
async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
|
|
5877
5905
|
if (autoCompactState.compactionInProgress.has(sessionID)) {
|
|
5906
|
+
await client.tui.showToast({
|
|
5907
|
+
body: {
|
|
5908
|
+
title: "Compact In Progress",
|
|
5909
|
+
message: "Recovery already running. Please wait or start new session if stuck.",
|
|
5910
|
+
variant: "warning",
|
|
5911
|
+
duration: 5000
|
|
5912
|
+
}
|
|
5913
|
+
}).catch(() => {});
|
|
5878
5914
|
return;
|
|
5879
5915
|
}
|
|
5880
5916
|
autoCompactState.compactionInProgress.add(sessionID);
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
}
|
|
5916
|
-
} else {
|
|
5917
|
-
await client.tui.showToast({
|
|
5918
|
-
body: {
|
|
5919
|
-
title: "Truncation Skipped",
|
|
5920
|
-
message: "No tool outputs found to truncate.",
|
|
5921
|
-
variant: "warning",
|
|
5922
|
-
duration: 3000
|
|
5917
|
+
try {
|
|
5918
|
+
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
5919
|
+
const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
|
|
5920
|
+
if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5921
|
+
log("[auto-compact] aggressive truncation triggered (experimental)", {
|
|
5922
|
+
currentTokens: errorData.currentTokens,
|
|
5923
|
+
maxTokens: errorData.maxTokens,
|
|
5924
|
+
targetRatio: TRUNCATE_CONFIG.targetTokenRatio
|
|
5925
|
+
});
|
|
5926
|
+
const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
|
|
5927
|
+
if (aggressiveResult.truncatedCount > 0) {
|
|
5928
|
+
truncateState.truncateAttempt += aggressiveResult.truncatedCount;
|
|
5929
|
+
const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
|
|
5930
|
+
const statusMsg = aggressiveResult.sufficient ? `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)})` : `Truncated ${aggressiveResult.truncatedCount} outputs (${formatBytes(aggressiveResult.totalBytesRemoved)}) but need ${formatBytes(aggressiveResult.targetBytesToRemove)}. Falling back to summarize/revert...`;
|
|
5931
|
+
await client.tui.showToast({
|
|
5932
|
+
body: {
|
|
5933
|
+
title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
|
|
5934
|
+
message: `${statusMsg}: ${toolNames}`,
|
|
5935
|
+
variant: "warning",
|
|
5936
|
+
duration: 4000
|
|
5937
|
+
}
|
|
5938
|
+
}).catch(() => {});
|
|
5939
|
+
log("[auto-compact] aggressive truncation completed", aggressiveResult);
|
|
5940
|
+
if (aggressiveResult.sufficient) {
|
|
5941
|
+
setTimeout(async () => {
|
|
5942
|
+
try {
|
|
5943
|
+
await client.session.prompt_async({
|
|
5944
|
+
path: { sessionID },
|
|
5945
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
5946
|
+
query: { directory }
|
|
5947
|
+
});
|
|
5948
|
+
} catch {}
|
|
5949
|
+
}, 500);
|
|
5950
|
+
return;
|
|
5923
5951
|
}
|
|
5924
|
-
}
|
|
5925
|
-
}
|
|
5926
|
-
}
|
|
5927
|
-
let skipSummarize = false;
|
|
5928
|
-
if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5929
|
-
const largest = findLargestToolResult(sessionID);
|
|
5930
|
-
if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
|
|
5931
|
-
const result = truncateToolResult(largest.partPath);
|
|
5932
|
-
if (result.success) {
|
|
5933
|
-
truncateState.truncateAttempt++;
|
|
5934
|
-
truncateState.lastTruncatedPartId = largest.partId;
|
|
5952
|
+
} else {
|
|
5935
5953
|
await client.tui.showToast({
|
|
5936
5954
|
body: {
|
|
5937
|
-
title: "
|
|
5938
|
-
message:
|
|
5955
|
+
title: "Truncation Skipped",
|
|
5956
|
+
message: "No tool outputs found to truncate.",
|
|
5939
5957
|
variant: "warning",
|
|
5940
5958
|
duration: 3000
|
|
5941
5959
|
}
|
|
5942
5960
|
}).catch(() => {});
|
|
5943
|
-
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5944
|
-
setTimeout(async () => {
|
|
5945
|
-
try {
|
|
5946
|
-
await client.session.prompt_async({
|
|
5947
|
-
path: { sessionID },
|
|
5948
|
-
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
5949
|
-
query: { directory }
|
|
5950
|
-
});
|
|
5951
|
-
} catch {}
|
|
5952
|
-
}, 500);
|
|
5953
|
-
return;
|
|
5954
5961
|
}
|
|
5955
|
-
} else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
5956
|
-
skipSummarize = true;
|
|
5957
|
-
await client.tui.showToast({
|
|
5958
|
-
body: {
|
|
5959
|
-
title: "Summarize Skipped",
|
|
5960
|
-
message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
|
|
5961
|
-
variant: "warning",
|
|
5962
|
-
duration: 3000
|
|
5963
|
-
}
|
|
5964
|
-
}).catch(() => {});
|
|
5965
|
-
} else if (!errorData?.currentTokens) {
|
|
5966
|
-
await client.tui.showToast({
|
|
5967
|
-
body: {
|
|
5968
|
-
title: "Truncation Skipped",
|
|
5969
|
-
message: "No large tool outputs found.",
|
|
5970
|
-
variant: "warning",
|
|
5971
|
-
duration: 3000
|
|
5972
|
-
}
|
|
5973
|
-
}).catch(() => {});
|
|
5974
5962
|
}
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5963
|
+
let skipSummarize = false;
|
|
5964
|
+
if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5965
|
+
const largest = findLargestToolResult(sessionID);
|
|
5966
|
+
if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
|
|
5967
|
+
const result = truncateToolResult(largest.partPath);
|
|
5968
|
+
if (result.success) {
|
|
5969
|
+
truncateState.truncateAttempt++;
|
|
5970
|
+
truncateState.lastTruncatedPartId = largest.partId;
|
|
5971
|
+
await client.tui.showToast({
|
|
5972
|
+
body: {
|
|
5973
|
+
title: "Truncating Large Output",
|
|
5974
|
+
message: `Truncated ${result.toolName} (${formatBytes(result.originalSize ?? 0)}). Retrying...`,
|
|
5975
|
+
variant: "warning",
|
|
5976
|
+
duration: 3000
|
|
5977
|
+
}
|
|
5978
|
+
}).catch(() => {});
|
|
5979
|
+
setTimeout(async () => {
|
|
5980
|
+
try {
|
|
5981
|
+
await client.session.prompt_async({
|
|
5982
|
+
path: { sessionID },
|
|
5983
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
5984
|
+
query: { directory }
|
|
5985
|
+
});
|
|
5986
|
+
} catch {}
|
|
5987
|
+
}, 500);
|
|
5988
|
+
return;
|
|
5995
5989
|
}
|
|
5996
|
-
}
|
|
5997
|
-
|
|
5998
|
-
return;
|
|
5999
|
-
}
|
|
6000
|
-
}
|
|
6001
|
-
if (Date.now() - retryState.lastAttemptTime > 300000) {
|
|
6002
|
-
retryState.attempt = 0;
|
|
6003
|
-
autoCompactState.fallbackStateBySession.delete(sessionID);
|
|
6004
|
-
autoCompactState.truncateStateBySession.delete(sessionID);
|
|
6005
|
-
}
|
|
6006
|
-
if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
|
|
6007
|
-
retryState.attempt++;
|
|
6008
|
-
retryState.lastAttemptTime = Date.now();
|
|
6009
|
-
const providerID = msg.providerID;
|
|
6010
|
-
const modelID = msg.modelID;
|
|
6011
|
-
if (providerID && modelID) {
|
|
6012
|
-
try {
|
|
5990
|
+
} else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
5991
|
+
skipSummarize = true;
|
|
6013
5992
|
await client.tui.showToast({
|
|
6014
5993
|
body: {
|
|
6015
|
-
title: "
|
|
6016
|
-
message: `
|
|
5994
|
+
title: "Summarize Skipped",
|
|
5995
|
+
message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
|
|
5996
|
+
variant: "warning",
|
|
5997
|
+
duration: 3000
|
|
5998
|
+
}
|
|
5999
|
+
}).catch(() => {});
|
|
6000
|
+
} else if (!errorData?.currentTokens) {
|
|
6001
|
+
await client.tui.showToast({
|
|
6002
|
+
body: {
|
|
6003
|
+
title: "Truncation Skipped",
|
|
6004
|
+
message: "No large tool outputs found.",
|
|
6017
6005
|
variant: "warning",
|
|
6018
6006
|
duration: 3000
|
|
6019
6007
|
}
|
|
6020
6008
|
}).catch(() => {});
|
|
6021
|
-
await client.session.summarize({
|
|
6022
|
-
path: { id: sessionID },
|
|
6023
|
-
body: { providerID, modelID },
|
|
6024
|
-
query: { directory }
|
|
6025
|
-
});
|
|
6026
|
-
autoCompactState.compactionInProgress.delete(sessionID);
|
|
6027
|
-
setTimeout(async () => {
|
|
6028
|
-
try {
|
|
6029
|
-
await client.session.prompt_async({
|
|
6030
|
-
path: { sessionID },
|
|
6031
|
-
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
6032
|
-
query: { directory }
|
|
6033
|
-
});
|
|
6034
|
-
} catch {}
|
|
6035
|
-
}, 500);
|
|
6036
|
-
return;
|
|
6037
|
-
} catch {
|
|
6038
|
-
autoCompactState.compactionInProgress.delete(sessionID);
|
|
6039
|
-
const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
|
|
6040
|
-
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
|
|
6041
|
-
setTimeout(() => {
|
|
6042
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
6043
|
-
}, cappedDelay);
|
|
6044
|
-
return;
|
|
6045
6009
|
}
|
|
6046
|
-
}
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6010
|
+
}
|
|
6011
|
+
const retryState = getOrCreateRetryState(autoCompactState, sessionID);
|
|
6012
|
+
if (errorData?.errorType?.includes("non-empty content")) {
|
|
6013
|
+
const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
|
|
6014
|
+
if (attempt < 3) {
|
|
6015
|
+
const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
|
|
6016
|
+
if (fixed) {
|
|
6017
|
+
setTimeout(() => {
|
|
6018
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
6019
|
+
}, 500);
|
|
6020
|
+
return;
|
|
6053
6021
|
}
|
|
6054
|
-
}
|
|
6022
|
+
} else {
|
|
6023
|
+
await client.tui.showToast({
|
|
6024
|
+
body: {
|
|
6025
|
+
title: "Recovery Failed",
|
|
6026
|
+
message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
|
|
6027
|
+
variant: "error",
|
|
6028
|
+
duration: 1e4
|
|
6029
|
+
}
|
|
6030
|
+
}).catch(() => {});
|
|
6031
|
+
return;
|
|
6032
|
+
}
|
|
6055
6033
|
}
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6034
|
+
if (Date.now() - retryState.lastAttemptTime > 300000) {
|
|
6035
|
+
retryState.attempt = 0;
|
|
6036
|
+
autoCompactState.fallbackStateBySession.delete(sessionID);
|
|
6037
|
+
autoCompactState.truncateStateBySession.delete(sessionID);
|
|
6038
|
+
}
|
|
6039
|
+
if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
|
|
6040
|
+
retryState.attempt++;
|
|
6041
|
+
retryState.lastAttemptTime = Date.now();
|
|
6042
|
+
const providerID = msg.providerID;
|
|
6043
|
+
const modelID = msg.modelID;
|
|
6044
|
+
if (providerID && modelID) {
|
|
6045
|
+
try {
|
|
6046
|
+
await client.tui.showToast({
|
|
6047
|
+
body: {
|
|
6048
|
+
title: "Auto Compact",
|
|
6049
|
+
message: `Summarizing session (attempt ${retryState.attempt}/${RETRY_CONFIG.maxAttempts})...`,
|
|
6050
|
+
variant: "warning",
|
|
6051
|
+
duration: 3000
|
|
6052
|
+
}
|
|
6053
|
+
}).catch(() => {});
|
|
6054
|
+
await client.session.summarize({
|
|
6055
|
+
path: { id: sessionID },
|
|
6056
|
+
body: { providerID, modelID },
|
|
6057
|
+
query: { directory }
|
|
6058
|
+
});
|
|
6059
|
+
setTimeout(async () => {
|
|
6060
|
+
try {
|
|
6061
|
+
await client.session.prompt_async({
|
|
6062
|
+
path: { sessionID },
|
|
6063
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
6064
|
+
query: { directory }
|
|
6065
|
+
});
|
|
6066
|
+
} catch {}
|
|
6067
|
+
}, 500);
|
|
6068
|
+
return;
|
|
6069
|
+
} catch {
|
|
6070
|
+
const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
|
|
6071
|
+
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
|
|
6072
|
+
setTimeout(() => {
|
|
6073
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
6074
|
+
}, cappedDelay);
|
|
6075
|
+
return;
|
|
6076
|
+
}
|
|
6077
|
+
} else {
|
|
6062
6078
|
await client.tui.showToast({
|
|
6063
6079
|
body: {
|
|
6064
|
-
title: "
|
|
6065
|
-
message: "
|
|
6080
|
+
title: "Summarize Skipped",
|
|
6081
|
+
message: "Missing providerID or modelID. Skipping to revert...",
|
|
6066
6082
|
variant: "warning",
|
|
6067
6083
|
duration: 3000
|
|
6068
6084
|
}
|
|
6069
6085
|
}).catch(() => {});
|
|
6070
|
-
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
|
|
6089
|
+
if (fallbackState.revertAttempt < FALLBACK_CONFIG.maxRevertAttempts) {
|
|
6090
|
+
const pair = await getLastMessagePair(sessionID, client, directory);
|
|
6091
|
+
if (pair) {
|
|
6092
|
+
try {
|
|
6093
|
+
await client.tui.showToast({
|
|
6094
|
+
body: {
|
|
6095
|
+
title: "Emergency Recovery",
|
|
6096
|
+
message: "Removing last message pair...",
|
|
6097
|
+
variant: "warning",
|
|
6098
|
+
duration: 3000
|
|
6099
|
+
}
|
|
6100
|
+
}).catch(() => {});
|
|
6101
|
+
if (pair.assistantMessageID) {
|
|
6102
|
+
await client.session.revert({
|
|
6103
|
+
path: { id: sessionID },
|
|
6104
|
+
body: { messageID: pair.assistantMessageID },
|
|
6105
|
+
query: { directory }
|
|
6106
|
+
});
|
|
6107
|
+
}
|
|
6071
6108
|
await client.session.revert({
|
|
6072
6109
|
path: { id: sessionID },
|
|
6073
|
-
body: { messageID: pair.
|
|
6110
|
+
body: { messageID: pair.userMessageID },
|
|
6074
6111
|
query: { directory }
|
|
6075
6112
|
});
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
message: "Could not find last message pair to revert.",
|
|
6101
|
-
variant: "warning",
|
|
6102
|
-
duration: 3000
|
|
6103
|
-
}
|
|
6104
|
-
}).catch(() => {});
|
|
6113
|
+
fallbackState.revertAttempt++;
|
|
6114
|
+
fallbackState.lastRevertedMessageID = pair.userMessageID;
|
|
6115
|
+
clearSessionState(autoCompactState, sessionID);
|
|
6116
|
+
setTimeout(async () => {
|
|
6117
|
+
try {
|
|
6118
|
+
await client.session.prompt_async({
|
|
6119
|
+
path: { sessionID },
|
|
6120
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
6121
|
+
query: { directory }
|
|
6122
|
+
});
|
|
6123
|
+
} catch {}
|
|
6124
|
+
}, 500);
|
|
6125
|
+
return;
|
|
6126
|
+
} catch {}
|
|
6127
|
+
} else {
|
|
6128
|
+
await client.tui.showToast({
|
|
6129
|
+
body: {
|
|
6130
|
+
title: "Revert Skipped",
|
|
6131
|
+
message: "Could not find last message pair to revert.",
|
|
6132
|
+
variant: "warning",
|
|
6133
|
+
duration: 3000
|
|
6134
|
+
}
|
|
6135
|
+
}).catch(() => {});
|
|
6136
|
+
}
|
|
6105
6137
|
}
|
|
6138
|
+
clearSessionState(autoCompactState, sessionID);
|
|
6139
|
+
await client.tui.showToast({
|
|
6140
|
+
body: {
|
|
6141
|
+
title: "Auto Compact Failed",
|
|
6142
|
+
message: "All recovery attempts failed. Please start a new session.",
|
|
6143
|
+
variant: "error",
|
|
6144
|
+
duration: 5000
|
|
6145
|
+
}
|
|
6146
|
+
}).catch(() => {});
|
|
6147
|
+
} finally {
|
|
6148
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
6106
6149
|
}
|
|
6107
|
-
clearSessionState(autoCompactState, sessionID);
|
|
6108
|
-
await client.tui.showToast({
|
|
6109
|
-
body: {
|
|
6110
|
-
title: "Auto Compact Failed",
|
|
6111
|
-
message: "All recovery attempts failed. Please start a new session.",
|
|
6112
|
-
variant: "error",
|
|
6113
|
-
duration: 5000
|
|
6114
|
-
}
|
|
6115
|
-
}).catch(() => {});
|
|
6116
6150
|
}
|
|
6117
6151
|
|
|
6118
6152
|
// src/hooks/anthropic-auto-compact/index.ts
|
|
@@ -8094,6 +8128,7 @@ var TRACKED_TOOLS = ["read", "write", "edit", "multiedit"];
|
|
|
8094
8128
|
function createRulesInjectorHook(ctx) {
|
|
8095
8129
|
const sessionCaches = new Map;
|
|
8096
8130
|
const pendingBatchFiles = new Map;
|
|
8131
|
+
const truncator = createDynamicTruncator(ctx);
|
|
8097
8132
|
function getSessionCache(sessionID) {
|
|
8098
8133
|
if (!sessionCaches.has(sessionID)) {
|
|
8099
8134
|
sessionCaches.set(sessionID, loadInjectedRules(sessionID));
|
|
@@ -8107,7 +8142,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
8107
8142
|
return path5;
|
|
8108
8143
|
return resolve4(ctx.directory, path5);
|
|
8109
8144
|
}
|
|
8110
|
-
function processFilePathForInjection(filePath, sessionID, output) {
|
|
8145
|
+
async function processFilePathForInjection(filePath, sessionID, output) {
|
|
8111
8146
|
const resolved = resolveFilePath2(filePath);
|
|
8112
8147
|
if (!resolved)
|
|
8113
8148
|
return;
|
|
@@ -8143,11 +8178,15 @@ function createRulesInjectorHook(ctx) {
|
|
|
8143
8178
|
return;
|
|
8144
8179
|
toInject.sort((a, b) => a.distance - b.distance);
|
|
8145
8180
|
for (const rule of toInject) {
|
|
8181
|
+
const { result, truncated } = await truncator.truncate(sessionID, rule.content);
|
|
8182
|
+
const truncationNotice = truncated ? `
|
|
8183
|
+
|
|
8184
|
+
[Note: Content was truncated to save context window space. For full context, please read the file directly: ${rule.relativePath}]` : "";
|
|
8146
8185
|
output.output += `
|
|
8147
8186
|
|
|
8148
8187
|
[Rule: ${rule.relativePath}]
|
|
8149
8188
|
[Match: ${rule.matchReason}]
|
|
8150
|
-
${
|
|
8189
|
+
${result}${truncationNotice}`;
|
|
8151
8190
|
}
|
|
8152
8191
|
saveInjectedRules(sessionID, cache2);
|
|
8153
8192
|
}
|
|
@@ -8177,14 +8216,14 @@ ${rule.content}`;
|
|
|
8177
8216
|
const toolExecuteAfter = async (input, output) => {
|
|
8178
8217
|
const toolName = input.tool.toLowerCase();
|
|
8179
8218
|
if (TRACKED_TOOLS.includes(toolName)) {
|
|
8180
|
-
processFilePathForInjection(output.title, input.sessionID, output);
|
|
8219
|
+
await processFilePathForInjection(output.title, input.sessionID, output);
|
|
8181
8220
|
return;
|
|
8182
8221
|
}
|
|
8183
8222
|
if (toolName === "batch") {
|
|
8184
8223
|
const filePaths = pendingBatchFiles.get(input.callID);
|
|
8185
8224
|
if (filePaths) {
|
|
8186
8225
|
for (const filePath of filePaths) {
|
|
8187
|
-
processFilePathForInjection(filePath, input.sessionID, output);
|
|
8226
|
+
await processFilePathForInjection(filePath, input.sessionID, output);
|
|
8188
8227
|
}
|
|
8189
8228
|
pendingBatchFiles.delete(input.callID);
|
|
8190
8229
|
}
|
|
@@ -8836,6 +8875,7 @@ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
|
|
8836
8875
|
|
|
8837
8876
|
## ZERO TOLERANCE FAILURES
|
|
8838
8877
|
- **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
|
|
8878
|
+
- **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
|
|
8839
8879
|
- **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
|
|
8840
8880
|
- **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
|
|
8841
8881
|
- **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
|
|
@@ -27085,6 +27125,10 @@ var interactive_bash = tool({
|
|
|
27085
27125
|
}
|
|
27086
27126
|
}
|
|
27087
27127
|
});
|
|
27128
|
+
// src/tools/background-task/tools.ts
|
|
27129
|
+
import { existsSync as existsSync38, readdirSync as readdirSync13 } from "fs";
|
|
27130
|
+
import { join as join47 } from "path";
|
|
27131
|
+
|
|
27088
27132
|
// src/tools/background-task/constants.ts
|
|
27089
27133
|
var BACKGROUND_TASK_DESCRIPTION = `Run agent task in background. Returns task_id immediately; notifies on completion.
|
|
27090
27134
|
|
|
@@ -27093,6 +27137,19 @@ var BACKGROUND_OUTPUT_DESCRIPTION = `Get output from background task. System not
|
|
|
27093
27137
|
var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=true to cancel ALL before final answer.`;
|
|
27094
27138
|
|
|
27095
27139
|
// src/tools/background-task/tools.ts
|
|
27140
|
+
function getMessageDir6(sessionID) {
|
|
27141
|
+
if (!existsSync38(MESSAGE_STORAGE))
|
|
27142
|
+
return null;
|
|
27143
|
+
const directPath = join47(MESSAGE_STORAGE, sessionID);
|
|
27144
|
+
if (existsSync38(directPath))
|
|
27145
|
+
return directPath;
|
|
27146
|
+
for (const dir of readdirSync13(MESSAGE_STORAGE)) {
|
|
27147
|
+
const sessionPath = join47(MESSAGE_STORAGE, dir, sessionID);
|
|
27148
|
+
if (existsSync38(sessionPath))
|
|
27149
|
+
return sessionPath;
|
|
27150
|
+
}
|
|
27151
|
+
return null;
|
|
27152
|
+
}
|
|
27096
27153
|
function formatDuration(start, end) {
|
|
27097
27154
|
const duration3 = (end ?? new Date).getTime() - start.getTime();
|
|
27098
27155
|
const seconds = Math.floor(duration3 / 1000);
|
|
@@ -27119,12 +27176,16 @@ function createBackgroundTask(manager) {
|
|
|
27119
27176
|
return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
|
|
27120
27177
|
}
|
|
27121
27178
|
try {
|
|
27179
|
+
const messageDir = getMessageDir6(toolContext.sessionID);
|
|
27180
|
+
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
27181
|
+
const parentModel = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
|
|
27122
27182
|
const task = await manager.launch({
|
|
27123
27183
|
description: args.description,
|
|
27124
27184
|
prompt: args.prompt,
|
|
27125
27185
|
agent: args.agent.trim(),
|
|
27126
27186
|
parentSessionID: toolContext.sessionID,
|
|
27127
|
-
parentMessageID: toolContext.messageID
|
|
27187
|
+
parentMessageID: toolContext.messageID,
|
|
27188
|
+
parentModel
|
|
27128
27189
|
});
|
|
27129
27190
|
return `Background task launched successfully.
|
|
27130
27191
|
|
|
@@ -27641,17 +27702,17 @@ var builtinTools = {
|
|
|
27641
27702
|
session_info
|
|
27642
27703
|
};
|
|
27643
27704
|
// src/features/background-agent/manager.ts
|
|
27644
|
-
import { existsSync as
|
|
27645
|
-
import { join as
|
|
27646
|
-
function
|
|
27647
|
-
if (!
|
|
27705
|
+
import { existsSync as existsSync39, readdirSync as readdirSync14 } from "fs";
|
|
27706
|
+
import { join as join48 } from "path";
|
|
27707
|
+
function getMessageDir7(sessionID) {
|
|
27708
|
+
if (!existsSync39(MESSAGE_STORAGE))
|
|
27648
27709
|
return null;
|
|
27649
|
-
const directPath =
|
|
27650
|
-
if (
|
|
27710
|
+
const directPath = join48(MESSAGE_STORAGE, sessionID);
|
|
27711
|
+
if (existsSync39(directPath))
|
|
27651
27712
|
return directPath;
|
|
27652
|
-
for (const dir of
|
|
27653
|
-
const sessionPath =
|
|
27654
|
-
if (
|
|
27713
|
+
for (const dir of readdirSync14(MESSAGE_STORAGE)) {
|
|
27714
|
+
const sessionPath = join48(MESSAGE_STORAGE, dir, sessionID);
|
|
27715
|
+
if (existsSync39(sessionPath))
|
|
27655
27716
|
return sessionPath;
|
|
27656
27717
|
}
|
|
27657
27718
|
return null;
|
|
@@ -27697,7 +27758,8 @@ class BackgroundManager {
|
|
|
27697
27758
|
progress: {
|
|
27698
27759
|
toolCalls: 0,
|
|
27699
27760
|
lastUpdate: new Date
|
|
27700
|
-
}
|
|
27761
|
+
},
|
|
27762
|
+
parentModel: input.parentModel
|
|
27701
27763
|
};
|
|
27702
27764
|
this.tasks.set(task.id, task);
|
|
27703
27765
|
this.startPolling();
|
|
@@ -27887,12 +27949,15 @@ class BackgroundManager {
|
|
|
27887
27949
|
log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
|
|
27888
27950
|
setTimeout(async () => {
|
|
27889
27951
|
try {
|
|
27890
|
-
const messageDir =
|
|
27952
|
+
const messageDir = getMessageDir7(task.parentSessionID);
|
|
27891
27953
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
27954
|
+
const modelContext = task.parentModel ?? prevMessage?.model;
|
|
27955
|
+
const modelField = modelContext?.providerID && modelContext?.modelID ? { providerID: modelContext.providerID, modelID: modelContext.modelID } : undefined;
|
|
27892
27956
|
await this.client.session.prompt({
|
|
27893
27957
|
path: { id: task.parentSessionID },
|
|
27894
27958
|
body: {
|
|
27895
27959
|
agent: prevMessage?.agent,
|
|
27960
|
+
model: modelField,
|
|
27896
27961
|
parts: [{ type: "text", text: message }]
|
|
27897
27962
|
},
|
|
27898
27963
|
query: { directory: this.directory }
|
|
@@ -28055,7 +28120,7 @@ var OverridableAgentNameSchema = exports_external.enum([
|
|
|
28055
28120
|
"build",
|
|
28056
28121
|
"plan",
|
|
28057
28122
|
"Sisyphus",
|
|
28058
|
-
"Builder
|
|
28123
|
+
"OpenCode-Builder",
|
|
28059
28124
|
"Planner-Sisyphus",
|
|
28060
28125
|
"oracle",
|
|
28061
28126
|
"librarian",
|
|
@@ -28104,7 +28169,7 @@ var AgentOverridesSchema = exports_external.object({
|
|
|
28104
28169
|
build: AgentOverrideConfigSchema.optional(),
|
|
28105
28170
|
plan: AgentOverrideConfigSchema.optional(),
|
|
28106
28171
|
Sisyphus: AgentOverrideConfigSchema.optional(),
|
|
28107
|
-
"Builder
|
|
28172
|
+
"OpenCode-Builder": AgentOverrideConfigSchema.optional(),
|
|
28108
28173
|
"Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
|
|
28109
28174
|
oracle: AgentOverrideConfigSchema.optional(),
|
|
28110
28175
|
librarian: AgentOverrideConfigSchema.optional(),
|
|
@@ -28122,9 +28187,8 @@ var ClaudeCodeConfigSchema = exports_external.object({
|
|
|
28122
28187
|
});
|
|
28123
28188
|
var SisyphusAgentConfigSchema = exports_external.object({
|
|
28124
28189
|
disabled: exports_external.boolean().optional(),
|
|
28125
|
-
|
|
28190
|
+
default_builder_enabled: exports_external.boolean().optional(),
|
|
28126
28191
|
planner_enabled: exports_external.boolean().optional(),
|
|
28127
|
-
replace_build: exports_external.boolean().optional(),
|
|
28128
28192
|
replace_plan: exports_external.boolean().optional()
|
|
28129
28193
|
});
|
|
28130
28194
|
var ExperimentalConfigSchema = exports_external.object({
|
|
@@ -28217,50 +28281,6 @@ var PLAN_PERMISSION = {
|
|
|
28217
28281
|
webfetch: "allow"
|
|
28218
28282
|
};
|
|
28219
28283
|
|
|
28220
|
-
// src/agents/build-prompt.ts
|
|
28221
|
-
var BUILD_SYSTEM_PROMPT = `<system-reminder>
|
|
28222
|
-
# Build Mode - System Reminder
|
|
28223
|
-
|
|
28224
|
-
BUILD MODE ACTIVE - you are in EXECUTION phase. Your responsibility is to:
|
|
28225
|
-
- Implement features and make code changes
|
|
28226
|
-
- Execute commands and run tests
|
|
28227
|
-
- Fix bugs and refactor code
|
|
28228
|
-
- Deploy and build systems
|
|
28229
|
-
- Make all necessary file modifications
|
|
28230
|
-
|
|
28231
|
-
You have FULL permissions to edit files, run commands, and make system changes.
|
|
28232
|
-
This is the implementation phase - execute decisively and thoroughly.
|
|
28233
|
-
|
|
28234
|
-
---
|
|
28235
|
-
|
|
28236
|
-
## Responsibility
|
|
28237
|
-
|
|
28238
|
-
Your current responsibility is to implement, build, and execute. You should:
|
|
28239
|
-
- Write and modify code to accomplish the user's goals
|
|
28240
|
-
- Run tests and builds to verify your changes
|
|
28241
|
-
- Fix errors and issues that arise
|
|
28242
|
-
- Use all available tools to complete the task efficiently
|
|
28243
|
-
- Delegate to specialized agents when appropriate for better results
|
|
28244
|
-
|
|
28245
|
-
**NOTE:** You should ask the user for clarification when requirements are ambiguous,
|
|
28246
|
-
but once the path is clear, execute confidently. The goal is to deliver working,
|
|
28247
|
-
tested, production-ready solutions.
|
|
28248
|
-
|
|
28249
|
-
---
|
|
28250
|
-
|
|
28251
|
-
## Important
|
|
28252
|
-
|
|
28253
|
-
The user wants you to execute and implement. You SHOULD make edits, run necessary
|
|
28254
|
-
tools, and make changes to accomplish the task. Use your full capabilities to
|
|
28255
|
-
deliver excellent results.
|
|
28256
|
-
</system-reminder>
|
|
28257
|
-
`;
|
|
28258
|
-
var BUILD_PERMISSION = {
|
|
28259
|
-
edit: "ask",
|
|
28260
|
-
bash: "ask",
|
|
28261
|
-
webfetch: "allow"
|
|
28262
|
-
};
|
|
28263
|
-
|
|
28264
28284
|
// src/index.ts
|
|
28265
28285
|
import * as fs7 from "fs";
|
|
28266
28286
|
import * as path8 from "path";
|
|
@@ -28316,7 +28336,7 @@ function migrateConfigFile(configPath, rawConfig) {
|
|
|
28316
28336
|
}
|
|
28317
28337
|
return needsWrite;
|
|
28318
28338
|
}
|
|
28319
|
-
function loadConfigFromPath2(configPath) {
|
|
28339
|
+
function loadConfigFromPath2(configPath, ctx) {
|
|
28320
28340
|
try {
|
|
28321
28341
|
if (fs7.existsSync(configPath)) {
|
|
28322
28342
|
const content = fs7.readFileSync(configPath, "utf-8");
|
|
@@ -28327,6 +28347,21 @@ function loadConfigFromPath2(configPath) {
|
|
|
28327
28347
|
const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
28328
28348
|
log(`Config validation error in ${configPath}:`, result.error.issues);
|
|
28329
28349
|
addConfigLoadError({ path: configPath, error: `Validation error: ${errorMsg}` });
|
|
28350
|
+
const errorList = result.error.issues.map((issue2) => `\u2022 ${issue2.path.join(".")}: ${issue2.message}`).join(`
|
|
28351
|
+
`);
|
|
28352
|
+
ctx.client.tui.showToast({
|
|
28353
|
+
body: {
|
|
28354
|
+
title: "\u274C OhMyOpenCode: Config Validation Failed",
|
|
28355
|
+
message: `Failed to load ${configPath}
|
|
28356
|
+
|
|
28357
|
+
Validation errors:
|
|
28358
|
+
${errorList}
|
|
28359
|
+
|
|
28360
|
+
Config will be ignored. Please fix the errors above.`,
|
|
28361
|
+
variant: "error",
|
|
28362
|
+
duration: 1e4
|
|
28363
|
+
}
|
|
28364
|
+
}).catch(() => {});
|
|
28330
28365
|
return null;
|
|
28331
28366
|
}
|
|
28332
28367
|
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
|
|
@@ -28336,6 +28371,21 @@ function loadConfigFromPath2(configPath) {
|
|
|
28336
28371
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
28337
28372
|
log(`Error loading config from ${configPath}:`, err);
|
|
28338
28373
|
addConfigLoadError({ path: configPath, error: errorMsg });
|
|
28374
|
+
const hint = err instanceof SyntaxError ? `
|
|
28375
|
+
|
|
28376
|
+
Hint: Check for syntax errors in your JSON file (missing commas, quotes, brackets, etc.)` : "";
|
|
28377
|
+
ctx.client.tui.showToast({
|
|
28378
|
+
body: {
|
|
28379
|
+
title: "\u274C OhMyOpenCode: Config Load Failed",
|
|
28380
|
+
message: `Failed to load ${configPath}
|
|
28381
|
+
|
|
28382
|
+
Error: ${errorMsg}${hint}
|
|
28383
|
+
|
|
28384
|
+
Config will be ignored. Please fix the error above.`,
|
|
28385
|
+
variant: "error",
|
|
28386
|
+
duration: 1e4
|
|
28387
|
+
}
|
|
28388
|
+
}).catch(() => {});
|
|
28339
28389
|
}
|
|
28340
28390
|
return null;
|
|
28341
28391
|
}
|
|
@@ -28365,11 +28415,11 @@ function mergeConfigs(base, override) {
|
|
|
28365
28415
|
claude_code: deepMerge(base.claude_code, override.claude_code)
|
|
28366
28416
|
};
|
|
28367
28417
|
}
|
|
28368
|
-
function loadPluginConfig(directory) {
|
|
28418
|
+
function loadPluginConfig(directory, ctx) {
|
|
28369
28419
|
const userConfigPath = path8.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
|
|
28370
28420
|
const projectConfigPath = path8.join(directory, ".opencode", "oh-my-opencode.json");
|
|
28371
|
-
let config3 = loadConfigFromPath2(userConfigPath) ?? {};
|
|
28372
|
-
const projectConfig = loadConfigFromPath2(projectConfigPath);
|
|
28421
|
+
let config3 = loadConfigFromPath2(userConfigPath, ctx) ?? {};
|
|
28422
|
+
const projectConfig = loadConfigFromPath2(projectConfigPath, ctx);
|
|
28373
28423
|
if (projectConfig) {
|
|
28374
28424
|
config3 = mergeConfigs(config3, projectConfig);
|
|
28375
28425
|
}
|
|
@@ -28383,7 +28433,7 @@ function loadPluginConfig(directory) {
|
|
|
28383
28433
|
return config3;
|
|
28384
28434
|
}
|
|
28385
28435
|
var OhMyOpenCodePlugin = async (ctx) => {
|
|
28386
|
-
const pluginConfig = loadPluginConfig(ctx.directory);
|
|
28436
|
+
const pluginConfig = loadPluginConfig(ctx.directory, ctx);
|
|
28387
28437
|
const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
|
|
28388
28438
|
const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
|
|
28389
28439
|
const modelContextLimitsCache = new Map;
|
|
@@ -28477,9 +28527,8 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
28477
28527
|
const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
|
|
28478
28528
|
const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
|
|
28479
28529
|
const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
|
|
28480
|
-
const builderEnabled = pluginConfig.sisyphus_agent?.
|
|
28530
|
+
const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
|
28481
28531
|
const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
|
|
28482
|
-
const replaceBuild = pluginConfig.sisyphus_agent?.replace_build ?? true;
|
|
28483
28532
|
const replacePlan = pluginConfig.sisyphus_agent?.replace_plan ?? true;
|
|
28484
28533
|
if (isSisyphusEnabled && builtinAgents.Sisyphus) {
|
|
28485
28534
|
const agentConfig = {
|
|
@@ -28487,15 +28536,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
28487
28536
|
};
|
|
28488
28537
|
if (builderEnabled) {
|
|
28489
28538
|
const { name: _buildName, ...buildConfigWithoutName } = config3.agent?.build ?? {};
|
|
28490
|
-
const
|
|
28491
|
-
const
|
|
28539
|
+
const openCodeBuilderOverride = pluginConfig.agents?.["OpenCode-Builder"];
|
|
28540
|
+
const openCodeBuilderBase = {
|
|
28492
28541
|
...buildConfigWithoutName,
|
|
28493
|
-
|
|
28494
|
-
permission: BUILD_PERMISSION,
|
|
28495
|
-
description: `${config3.agent?.build?.description ?? "Build agent"} (OhMyOpenCode version)`,
|
|
28496
|
-
color: config3.agent?.build?.color ?? "#32CD32"
|
|
28542
|
+
description: `${config3.agent?.build?.description ?? "Build agent"} (OpenCode default)`
|
|
28497
28543
|
};
|
|
28498
|
-
agentConfig["Builder
|
|
28544
|
+
agentConfig["OpenCode-Builder"] = openCodeBuilderOverride ? { ...openCodeBuilderBase, ...openCodeBuilderOverride } : openCodeBuilderBase;
|
|
28499
28545
|
}
|
|
28500
28546
|
if (plannerEnabled) {
|
|
28501
28547
|
const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
|
|
@@ -28509,13 +28555,20 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
28509
28555
|
};
|
|
28510
28556
|
agentConfig["Planner-Sisyphus"] = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
|
|
28511
28557
|
}
|
|
28558
|
+
const filteredConfigAgents = config3.agent ? Object.fromEntries(Object.entries(config3.agent).filter(([key]) => {
|
|
28559
|
+
if (key === "build")
|
|
28560
|
+
return false;
|
|
28561
|
+
if (key === "plan" && replacePlan)
|
|
28562
|
+
return false;
|
|
28563
|
+
return true;
|
|
28564
|
+
})) : {};
|
|
28512
28565
|
config3.agent = {
|
|
28513
28566
|
...agentConfig,
|
|
28514
28567
|
...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
|
|
28515
28568
|
...userAgents,
|
|
28516
28569
|
...projectAgents,
|
|
28517
|
-
...
|
|
28518
|
-
|
|
28570
|
+
...filteredConfigAgents,
|
|
28571
|
+
build: { ...config3.agent?.build, mode: "subagent" },
|
|
28519
28572
|
...replacePlan ? { plan: { ...config3.agent?.plan, mode: "subagent" } } : {}
|
|
28520
28573
|
};
|
|
28521
28574
|
} else {
|