oh-my-opencode 2.2.1 → 2.3.0
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 +59 -25
- package/README.ko.md +61 -27
- package/README.md +59 -26
- package/README.zh-cn.md +867 -0
- package/dist/agents/{omo.d.ts → sisyphus.d.ts} +1 -1
- package/dist/agents/types.d.ts +1 -1
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +33 -22
- package/dist/features/background-agent/manager.d.ts +1 -0
- package/dist/features/background-agent/manager.test.d.ts +1 -0
- package/dist/hooks/anthropic-auto-compact/executor.d.ts +2 -1
- package/dist/hooks/anthropic-auto-compact/index.d.ts +5 -1
- package/dist/hooks/anthropic-auto-compact/storage.d.ts +12 -0
- package/dist/hooks/anthropic-auto-compact/types.d.ts +5 -2
- package/dist/hooks/auto-update-checker/types.d.ts +1 -0
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/session-recovery/index.d.ts +5 -1
- package/dist/hooks/session-recovery/types.d.ts +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +722 -473
- package/dist/shared/config-errors.d.ts +7 -0
- package/dist/shared/config-path.d.ts +14 -0
- package/dist/shared/index.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -222,8 +222,8 @@ var require_utils = __commonJS((exports) => {
|
|
|
222
222
|
}
|
|
223
223
|
return output;
|
|
224
224
|
};
|
|
225
|
-
exports.basename = (
|
|
226
|
-
const segs =
|
|
225
|
+
exports.basename = (path4, { windows } = {}) => {
|
|
226
|
+
const segs = path4.split(windows ? /[\\/]/ : "/");
|
|
227
227
|
const last = segs[segs.length - 1];
|
|
228
228
|
if (last === "") {
|
|
229
229
|
return segs[segs.length - 2];
|
|
@@ -1474,20 +1474,25 @@ var require_picomatch2 = __commonJS((exports, module) => {
|
|
|
1474
1474
|
module.exports = picomatch;
|
|
1475
1475
|
});
|
|
1476
1476
|
|
|
1477
|
-
// src/agents/
|
|
1478
|
-
var
|
|
1479
|
-
You are
|
|
1477
|
+
// src/agents/sisyphus.ts
|
|
1478
|
+
var SISYPHUS_SYSTEM_PROMPT = `<Role>
|
|
1479
|
+
You are "Sisyphus" - Powerful AI Agent with orchestration capabilities from OhMyOpenCode.
|
|
1480
|
+
Named by [YeonGyu Kim](https://github.com/code-yeongyu).
|
|
1480
1481
|
|
|
1481
|
-
**
|
|
1482
|
-
|
|
1482
|
+
**Why Sisyphus?**: Humans roll their boulder every day. So do you. We're not so different\u2014your code should be indistinguishable from a senior engineer's.
|
|
1483
|
+
|
|
1484
|
+
**Identity**: SF Bay Area engineer. Work, delegate, verify, ship. No AI slop.
|
|
1483
1485
|
|
|
1484
1486
|
**Core Competencies**:
|
|
1485
1487
|
- Parsing implicit requirements from explicit requests
|
|
1486
1488
|
- Adapting to codebase maturity (disciplined vs chaotic)
|
|
1487
1489
|
- Delegating specialized work to the right subagents
|
|
1488
1490
|
- Parallel execution for maximum throughput
|
|
1491
|
+
- Follows user instructions. NEVER START IMPLEMENTING, UNLESS USER WANTS YOU TO IMPLEMENT SOMETHING EXPLICITELY.
|
|
1492
|
+
- KEEP IN MIND: YOUR TODO CREATION WOULD BE TRACKED BY HOOK([SYSTEM REMINDER - TODO CONTINUATION]), BUT IF NOT USER REQUESTED YOU TO WORK, NEVER START WORK.
|
|
1489
1493
|
|
|
1490
1494
|
**Operating Mode**: You NEVER work alone when specialists are available. Frontend work \u2192 delegate. Deep research \u2192 parallel background agents (async subagents). Complex architecture \u2192 consult Oracle.
|
|
1495
|
+
|
|
1491
1496
|
</Role>
|
|
1492
1497
|
|
|
1493
1498
|
<Behavior_Instructions>
|
|
@@ -1812,7 +1817,8 @@ Briefly announce "Consulting Oracle for [reason]" before invocation.
|
|
|
1812
1817
|
|
|
1813
1818
|
### Workflow (NON-NEGOTIABLE)
|
|
1814
1819
|
|
|
1815
|
-
1. **IMMEDIATELY on receiving request**: \`todowrite\` to plan atomic steps
|
|
1820
|
+
1. **IMMEDIATELY on receiving request**: \`todowrite\` to plan atomic steps.
|
|
1821
|
+
- ONLY ADD TODOS TO IMPLEMENT SOMETHING, ONLY WHEN USER WANTS YOU TO IMPLEMENT SOMETHING.
|
|
1816
1822
|
2. **Before starting each step**: Mark \`in_progress\` (only ONE at a time)
|
|
1817
1823
|
3. **After completing each step**: Mark \`completed\` IMMEDIATELY (NEVER batch)
|
|
1818
1824
|
4. **If scope changes**: Update todos before proceeding
|
|
@@ -1911,9 +1917,10 @@ If the user's approach seems problematic:
|
|
|
1911
1917
|
- Prefer small, focused changes over large refactors
|
|
1912
1918
|
- When uncertain about scope, ask
|
|
1913
1919
|
</Constraints>
|
|
1920
|
+
|
|
1914
1921
|
`;
|
|
1915
|
-
var
|
|
1916
|
-
description: "
|
|
1922
|
+
var sisyphusAgent = {
|
|
1923
|
+
description: "Sisyphus - Powerful AI orchestrator from OhMyOpenCode. Plans obsessively with todos, assesses search complexity before exploration, delegates strategically to specialized agents. Uses explore for internal code (parallel-friendly), librarian only for external docs, and always delegates UI work to frontend engineer.",
|
|
1917
1924
|
mode: "primary",
|
|
1918
1925
|
model: "anthropic/claude-opus-4-5",
|
|
1919
1926
|
thinking: {
|
|
@@ -1921,7 +1928,7 @@ var omoAgent = {
|
|
|
1921
1928
|
budgetTokens: 32000
|
|
1922
1929
|
},
|
|
1923
1930
|
maxTokens: 64000,
|
|
1924
|
-
prompt:
|
|
1931
|
+
prompt: SISYPHUS_SYSTEM_PROMPT,
|
|
1925
1932
|
color: "#00CED1"
|
|
1926
1933
|
};
|
|
1927
1934
|
|
|
@@ -3139,9 +3146,29 @@ function createDynamicTruncator(ctx) {
|
|
|
3139
3146
|
truncateSync: (output, maxTokens, preserveHeaderLines) => truncateToTokenLimit(output, maxTokens, preserveHeaderLines)
|
|
3140
3147
|
};
|
|
3141
3148
|
}
|
|
3149
|
+
// src/shared/config-path.ts
|
|
3150
|
+
import * as path2 from "path";
|
|
3151
|
+
import * as os2 from "os";
|
|
3152
|
+
function getUserConfigDir() {
|
|
3153
|
+
if (process.platform === "win32") {
|
|
3154
|
+
return process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming");
|
|
3155
|
+
}
|
|
3156
|
+
return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
|
|
3157
|
+
}
|
|
3158
|
+
// src/shared/config-errors.ts
|
|
3159
|
+
var configLoadErrors = [];
|
|
3160
|
+
function getConfigLoadErrors() {
|
|
3161
|
+
return configLoadErrors;
|
|
3162
|
+
}
|
|
3163
|
+
function clearConfigLoadErrors() {
|
|
3164
|
+
configLoadErrors = [];
|
|
3165
|
+
}
|
|
3166
|
+
function addConfigLoadError(error) {
|
|
3167
|
+
configLoadErrors.push(error);
|
|
3168
|
+
}
|
|
3142
3169
|
// src/agents/utils.ts
|
|
3143
3170
|
var allBuiltinAgents = {
|
|
3144
|
-
|
|
3171
|
+
Sisyphus: sisyphusAgent,
|
|
3145
3172
|
oracle: oracleAgent,
|
|
3146
3173
|
librarian: librarianAgent,
|
|
3147
3174
|
explore: exploreAgent,
|
|
@@ -3188,7 +3215,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
3188
3215
|
continue;
|
|
3189
3216
|
}
|
|
3190
3217
|
let finalConfig = config;
|
|
3191
|
-
if ((agentName === "
|
|
3218
|
+
if ((agentName === "Sisyphus" || agentName === "librarian") && directory && config.prompt) {
|
|
3192
3219
|
const envContext = createEnvContext(directory);
|
|
3193
3220
|
finalConfig = {
|
|
3194
3221
|
...config,
|
|
@@ -3196,7 +3223,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
3196
3223
|
};
|
|
3197
3224
|
}
|
|
3198
3225
|
const override = agentOverrides[agentName];
|
|
3199
|
-
if (agentName === "
|
|
3226
|
+
if (agentName === "Sisyphus" && systemDefaultModel && !override?.model) {
|
|
3200
3227
|
finalConfig = {
|
|
3201
3228
|
...finalConfig,
|
|
3202
3229
|
model: systemDefaultModel
|
|
@@ -3212,19 +3239,19 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
3212
3239
|
}
|
|
3213
3240
|
// src/hooks/todo-continuation-enforcer.ts
|
|
3214
3241
|
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "fs";
|
|
3215
|
-
import { join as
|
|
3242
|
+
import { join as join6 } from "path";
|
|
3216
3243
|
|
|
3217
3244
|
// src/features/hook-message-injector/injector.ts
|
|
3218
3245
|
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
|
|
3219
|
-
import { join as
|
|
3246
|
+
import { join as join5 } from "path";
|
|
3220
3247
|
|
|
3221
3248
|
// src/features/hook-message-injector/constants.ts
|
|
3222
|
-
import { join as
|
|
3223
|
-
import { homedir as
|
|
3224
|
-
var xdgData = process.env.XDG_DATA_HOME ||
|
|
3225
|
-
var OPENCODE_STORAGE =
|
|
3226
|
-
var MESSAGE_STORAGE =
|
|
3227
|
-
var PART_STORAGE =
|
|
3249
|
+
import { join as join4 } from "path";
|
|
3250
|
+
import { homedir as homedir3 } from "os";
|
|
3251
|
+
var xdgData = process.env.XDG_DATA_HOME || join4(homedir3(), ".local", "share");
|
|
3252
|
+
var OPENCODE_STORAGE = join4(xdgData, "opencode", "storage");
|
|
3253
|
+
var MESSAGE_STORAGE = join4(OPENCODE_STORAGE, "message");
|
|
3254
|
+
var PART_STORAGE = join4(OPENCODE_STORAGE, "part");
|
|
3228
3255
|
|
|
3229
3256
|
// src/features/hook-message-injector/injector.ts
|
|
3230
3257
|
function findNearestMessageWithFields(messageDir) {
|
|
@@ -3232,7 +3259,7 @@ function findNearestMessageWithFields(messageDir) {
|
|
|
3232
3259
|
const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3233
3260
|
for (const file of files) {
|
|
3234
3261
|
try {
|
|
3235
|
-
const content = readFileSync2(
|
|
3262
|
+
const content = readFileSync2(join5(messageDir, file), "utf-8");
|
|
3236
3263
|
const msg = JSON.parse(content);
|
|
3237
3264
|
if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
|
|
3238
3265
|
return msg;
|
|
@@ -3260,12 +3287,12 @@ function getOrCreateMessageDir(sessionID) {
|
|
|
3260
3287
|
if (!existsSync3(MESSAGE_STORAGE)) {
|
|
3261
3288
|
mkdirSync(MESSAGE_STORAGE, { recursive: true });
|
|
3262
3289
|
}
|
|
3263
|
-
const directPath =
|
|
3290
|
+
const directPath = join5(MESSAGE_STORAGE, sessionID);
|
|
3264
3291
|
if (existsSync3(directPath)) {
|
|
3265
3292
|
return directPath;
|
|
3266
3293
|
}
|
|
3267
3294
|
for (const dir of readdirSync(MESSAGE_STORAGE)) {
|
|
3268
|
-
const sessionPath =
|
|
3295
|
+
const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
|
|
3269
3296
|
if (existsSync3(sessionPath)) {
|
|
3270
3297
|
return sessionPath;
|
|
3271
3298
|
}
|
|
@@ -3319,12 +3346,12 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
|
|
|
3319
3346
|
sessionID
|
|
3320
3347
|
};
|
|
3321
3348
|
try {
|
|
3322
|
-
writeFileSync(
|
|
3323
|
-
const partDir =
|
|
3349
|
+
writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
|
|
3350
|
+
const partDir = join5(PART_STORAGE, messageID);
|
|
3324
3351
|
if (!existsSync3(partDir)) {
|
|
3325
3352
|
mkdirSync(partDir, { recursive: true });
|
|
3326
3353
|
}
|
|
3327
|
-
writeFileSync(
|
|
3354
|
+
writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
|
|
3328
3355
|
return true;
|
|
3329
3356
|
} catch {
|
|
3330
3357
|
return false;
|
|
@@ -3342,11 +3369,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
3342
3369
|
function getMessageDir(sessionID) {
|
|
3343
3370
|
if (!existsSync4(MESSAGE_STORAGE))
|
|
3344
3371
|
return null;
|
|
3345
|
-
const directPath =
|
|
3372
|
+
const directPath = join6(MESSAGE_STORAGE, sessionID);
|
|
3346
3373
|
if (existsSync4(directPath))
|
|
3347
3374
|
return directPath;
|
|
3348
3375
|
for (const dir of readdirSync2(MESSAGE_STORAGE)) {
|
|
3349
|
-
const sessionPath =
|
|
3376
|
+
const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
|
|
3350
3377
|
if (existsSync4(sessionPath))
|
|
3351
3378
|
return sessionPath;
|
|
3352
3379
|
}
|
|
@@ -3462,6 +3489,12 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
3462
3489
|
try {
|
|
3463
3490
|
const messageDir = getMessageDir(sessionID);
|
|
3464
3491
|
const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
|
|
3492
|
+
const agentHasWritePermission = !prevMessage?.tools || prevMessage.tools.write !== false && prevMessage.tools.edit !== false;
|
|
3493
|
+
if (!agentHasWritePermission) {
|
|
3494
|
+
log(`[${HOOK_NAME}] Skipped: previous agent lacks write permission`, { sessionID, agent: prevMessage?.agent, tools: prevMessage?.tools });
|
|
3495
|
+
remindedSessions.delete(sessionID);
|
|
3496
|
+
return;
|
|
3497
|
+
}
|
|
3465
3498
|
log(`[${HOOK_NAME}] Injecting continuation prompt`, { sessionID, agent: prevMessage?.agent });
|
|
3466
3499
|
await ctx.client.session.prompt({
|
|
3467
3500
|
path: { id: sessionID },
|
|
@@ -3483,7 +3516,7 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
3483
3516
|
log(`[${HOOK_NAME}] Prompt injection failed`, { sessionID, error: String(err) });
|
|
3484
3517
|
remindedSessions.delete(sessionID);
|
|
3485
3518
|
}
|
|
3486
|
-
},
|
|
3519
|
+
}, 5000);
|
|
3487
3520
|
pendingTimers.set(sessionID, timer);
|
|
3488
3521
|
}
|
|
3489
3522
|
if (event.type === "message.updated") {
|
|
@@ -3827,20 +3860,20 @@ function createSessionNotification(ctx, config = {}) {
|
|
|
3827
3860
|
}
|
|
3828
3861
|
// src/hooks/session-recovery/storage.ts
|
|
3829
3862
|
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3830
|
-
import { join as
|
|
3863
|
+
import { join as join8 } from "path";
|
|
3831
3864
|
|
|
3832
3865
|
// src/hooks/session-recovery/constants.ts
|
|
3833
|
-
import { join as
|
|
3866
|
+
import { join as join7 } from "path";
|
|
3834
3867
|
|
|
3835
3868
|
// node_modules/xdg-basedir/index.js
|
|
3836
|
-
import
|
|
3837
|
-
import
|
|
3838
|
-
var homeDirectory =
|
|
3869
|
+
import os3 from "os";
|
|
3870
|
+
import path3 from "path";
|
|
3871
|
+
var homeDirectory = os3.homedir();
|
|
3839
3872
|
var { env } = process;
|
|
3840
|
-
var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ?
|
|
3841
|
-
var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ?
|
|
3842
|
-
var xdgState = env.XDG_STATE_HOME || (homeDirectory ?
|
|
3843
|
-
var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ?
|
|
3873
|
+
var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "share") : undefined);
|
|
3874
|
+
var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path3.join(homeDirectory, ".config") : undefined);
|
|
3875
|
+
var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "state") : undefined);
|
|
3876
|
+
var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path3.join(homeDirectory, ".cache") : undefined);
|
|
3844
3877
|
var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
|
|
3845
3878
|
var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
|
|
3846
3879
|
if (xdgData2) {
|
|
@@ -3852,9 +3885,9 @@ if (xdgConfig) {
|
|
|
3852
3885
|
}
|
|
3853
3886
|
|
|
3854
3887
|
// src/hooks/session-recovery/constants.ts
|
|
3855
|
-
var OPENCODE_STORAGE2 =
|
|
3856
|
-
var MESSAGE_STORAGE2 =
|
|
3857
|
-
var PART_STORAGE2 =
|
|
3888
|
+
var OPENCODE_STORAGE2 = join7(xdgData2 ?? "", "opencode", "storage");
|
|
3889
|
+
var MESSAGE_STORAGE2 = join7(OPENCODE_STORAGE2, "message");
|
|
3890
|
+
var PART_STORAGE2 = join7(OPENCODE_STORAGE2, "part");
|
|
3858
3891
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
3859
3892
|
var META_TYPES = new Set(["step-start", "step-finish"]);
|
|
3860
3893
|
var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
@@ -3868,12 +3901,12 @@ function generatePartId2() {
|
|
|
3868
3901
|
function getMessageDir2(sessionID) {
|
|
3869
3902
|
if (!existsSync5(MESSAGE_STORAGE2))
|
|
3870
3903
|
return "";
|
|
3871
|
-
const directPath =
|
|
3904
|
+
const directPath = join8(MESSAGE_STORAGE2, sessionID);
|
|
3872
3905
|
if (existsSync5(directPath)) {
|
|
3873
3906
|
return directPath;
|
|
3874
3907
|
}
|
|
3875
3908
|
for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
|
|
3876
|
-
const sessionPath =
|
|
3909
|
+
const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
|
|
3877
3910
|
if (existsSync5(sessionPath)) {
|
|
3878
3911
|
return sessionPath;
|
|
3879
3912
|
}
|
|
@@ -3889,7 +3922,7 @@ function readMessages(sessionID) {
|
|
|
3889
3922
|
if (!file.endsWith(".json"))
|
|
3890
3923
|
continue;
|
|
3891
3924
|
try {
|
|
3892
|
-
const content = readFileSync3(
|
|
3925
|
+
const content = readFileSync3(join8(messageDir, file), "utf-8");
|
|
3893
3926
|
messages.push(JSON.parse(content));
|
|
3894
3927
|
} catch {
|
|
3895
3928
|
continue;
|
|
@@ -3904,7 +3937,7 @@ function readMessages(sessionID) {
|
|
|
3904
3937
|
});
|
|
3905
3938
|
}
|
|
3906
3939
|
function readParts(messageID) {
|
|
3907
|
-
const partDir =
|
|
3940
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
3908
3941
|
if (!existsSync5(partDir))
|
|
3909
3942
|
return [];
|
|
3910
3943
|
const parts = [];
|
|
@@ -3912,7 +3945,7 @@ function readParts(messageID) {
|
|
|
3912
3945
|
if (!file.endsWith(".json"))
|
|
3913
3946
|
continue;
|
|
3914
3947
|
try {
|
|
3915
|
-
const content = readFileSync3(
|
|
3948
|
+
const content = readFileSync3(join8(partDir, file), "utf-8");
|
|
3916
3949
|
parts.push(JSON.parse(content));
|
|
3917
3950
|
} catch {
|
|
3918
3951
|
continue;
|
|
@@ -3942,7 +3975,7 @@ function messageHasContent(messageID) {
|
|
|
3942
3975
|
return parts.some(hasContent);
|
|
3943
3976
|
}
|
|
3944
3977
|
function injectTextPart(sessionID, messageID, text) {
|
|
3945
|
-
const partDir =
|
|
3978
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
3946
3979
|
if (!existsSync5(partDir)) {
|
|
3947
3980
|
mkdirSync2(partDir, { recursive: true });
|
|
3948
3981
|
}
|
|
@@ -3956,7 +3989,7 @@ function injectTextPart(sessionID, messageID, text) {
|
|
|
3956
3989
|
synthetic: true
|
|
3957
3990
|
};
|
|
3958
3991
|
try {
|
|
3959
|
-
writeFileSync2(
|
|
3992
|
+
writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
3960
3993
|
return true;
|
|
3961
3994
|
} catch {
|
|
3962
3995
|
return false;
|
|
@@ -3972,19 +4005,6 @@ function findEmptyMessages(sessionID) {
|
|
|
3972
4005
|
}
|
|
3973
4006
|
return emptyIds;
|
|
3974
4007
|
}
|
|
3975
|
-
function findEmptyMessageByIndex(sessionID, targetIndex) {
|
|
3976
|
-
const messages = readMessages(sessionID);
|
|
3977
|
-
const indicesToTry = [targetIndex, targetIndex - 1, targetIndex - 2];
|
|
3978
|
-
for (const idx of indicesToTry) {
|
|
3979
|
-
if (idx < 0 || idx >= messages.length)
|
|
3980
|
-
continue;
|
|
3981
|
-
const targetMsg = messages[idx];
|
|
3982
|
-
if (!messageHasContent(targetMsg.id)) {
|
|
3983
|
-
return targetMsg.id;
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
return null;
|
|
3987
|
-
}
|
|
3988
4008
|
function findMessagesWithThinkingBlocks(sessionID) {
|
|
3989
4009
|
const messages = readMessages(sessionID);
|
|
3990
4010
|
const result = [];
|
|
@@ -3999,23 +4019,6 @@ function findMessagesWithThinkingBlocks(sessionID) {
|
|
|
3999
4019
|
}
|
|
4000
4020
|
return result;
|
|
4001
4021
|
}
|
|
4002
|
-
function findMessagesWithThinkingOnly(sessionID) {
|
|
4003
|
-
const messages = readMessages(sessionID);
|
|
4004
|
-
const result = [];
|
|
4005
|
-
for (const msg of messages) {
|
|
4006
|
-
if (msg.role !== "assistant")
|
|
4007
|
-
continue;
|
|
4008
|
-
const parts = readParts(msg.id);
|
|
4009
|
-
if (parts.length === 0)
|
|
4010
|
-
continue;
|
|
4011
|
-
const hasThinking = parts.some((p) => THINKING_TYPES.has(p.type));
|
|
4012
|
-
const hasTextContent = parts.some(hasContent);
|
|
4013
|
-
if (hasThinking && !hasTextContent) {
|
|
4014
|
-
result.push(msg.id);
|
|
4015
|
-
}
|
|
4016
|
-
}
|
|
4017
|
-
return result;
|
|
4018
|
-
}
|
|
4019
4022
|
function findMessagesWithOrphanThinking(sessionID) {
|
|
4020
4023
|
const messages = readMessages(sessionID);
|
|
4021
4024
|
const result = [];
|
|
@@ -4036,7 +4039,7 @@ function findMessagesWithOrphanThinking(sessionID) {
|
|
|
4036
4039
|
return result;
|
|
4037
4040
|
}
|
|
4038
4041
|
function prependThinkingPart(sessionID, messageID) {
|
|
4039
|
-
const partDir =
|
|
4042
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
4040
4043
|
if (!existsSync5(partDir)) {
|
|
4041
4044
|
mkdirSync2(partDir, { recursive: true });
|
|
4042
4045
|
}
|
|
@@ -4050,14 +4053,14 @@ function prependThinkingPart(sessionID, messageID) {
|
|
|
4050
4053
|
synthetic: true
|
|
4051
4054
|
};
|
|
4052
4055
|
try {
|
|
4053
|
-
writeFileSync2(
|
|
4056
|
+
writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
4054
4057
|
return true;
|
|
4055
4058
|
} catch {
|
|
4056
4059
|
return false;
|
|
4057
4060
|
}
|
|
4058
4061
|
}
|
|
4059
4062
|
function stripThinkingParts(messageID) {
|
|
4060
|
-
const partDir =
|
|
4063
|
+
const partDir = join8(PART_STORAGE2, messageID);
|
|
4061
4064
|
if (!existsSync5(partDir))
|
|
4062
4065
|
return false;
|
|
4063
4066
|
let anyRemoved = false;
|
|
@@ -4065,7 +4068,7 @@ function stripThinkingParts(messageID) {
|
|
|
4065
4068
|
if (!file.endsWith(".json"))
|
|
4066
4069
|
continue;
|
|
4067
4070
|
try {
|
|
4068
|
-
const filePath =
|
|
4071
|
+
const filePath = join8(partDir, file);
|
|
4069
4072
|
const content = readFileSync3(filePath, "utf-8");
|
|
4070
4073
|
const part = JSON.parse(content);
|
|
4071
4074
|
if (THINKING_TYPES.has(part.type)) {
|
|
@@ -4078,50 +4081,6 @@ function stripThinkingParts(messageID) {
|
|
|
4078
4081
|
}
|
|
4079
4082
|
return anyRemoved;
|
|
4080
4083
|
}
|
|
4081
|
-
function replaceEmptyTextParts(messageID, replacementText) {
|
|
4082
|
-
const partDir = join7(PART_STORAGE2, messageID);
|
|
4083
|
-
if (!existsSync5(partDir))
|
|
4084
|
-
return false;
|
|
4085
|
-
let anyReplaced = false;
|
|
4086
|
-
for (const file of readdirSync3(partDir)) {
|
|
4087
|
-
if (!file.endsWith(".json"))
|
|
4088
|
-
continue;
|
|
4089
|
-
try {
|
|
4090
|
-
const filePath = join7(partDir, file);
|
|
4091
|
-
const content = readFileSync3(filePath, "utf-8");
|
|
4092
|
-
const part = JSON.parse(content);
|
|
4093
|
-
if (part.type === "text") {
|
|
4094
|
-
const textPart = part;
|
|
4095
|
-
if (!textPart.text?.trim()) {
|
|
4096
|
-
textPart.text = replacementText;
|
|
4097
|
-
textPart.synthetic = true;
|
|
4098
|
-
writeFileSync2(filePath, JSON.stringify(textPart, null, 2));
|
|
4099
|
-
anyReplaced = true;
|
|
4100
|
-
}
|
|
4101
|
-
}
|
|
4102
|
-
} catch {
|
|
4103
|
-
continue;
|
|
4104
|
-
}
|
|
4105
|
-
}
|
|
4106
|
-
return anyReplaced;
|
|
4107
|
-
}
|
|
4108
|
-
function findMessagesWithEmptyTextParts(sessionID) {
|
|
4109
|
-
const messages = readMessages(sessionID);
|
|
4110
|
-
const result = [];
|
|
4111
|
-
for (const msg of messages) {
|
|
4112
|
-
const parts = readParts(msg.id);
|
|
4113
|
-
const hasEmptyTextPart = parts.some((p) => {
|
|
4114
|
-
if (p.type !== "text")
|
|
4115
|
-
return false;
|
|
4116
|
-
const textPart = p;
|
|
4117
|
-
return !textPart.text?.trim();
|
|
4118
|
-
});
|
|
4119
|
-
if (hasEmptyTextPart) {
|
|
4120
|
-
result.push(msg.id);
|
|
4121
|
-
}
|
|
4122
|
-
}
|
|
4123
|
-
return result;
|
|
4124
|
-
}
|
|
4125
4084
|
function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
|
|
4126
4085
|
const messages = readMessages(sessionID);
|
|
4127
4086
|
if (targetIndex < 0 || targetIndex >= messages.length)
|
|
@@ -4142,6 +4101,37 @@ function findMessageByIndexNeedingThinking(sessionID, targetIndex) {
|
|
|
4142
4101
|
}
|
|
4143
4102
|
|
|
4144
4103
|
// src/hooks/session-recovery/index.ts
|
|
4104
|
+
var RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]";
|
|
4105
|
+
function findLastUserMessage(messages) {
|
|
4106
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
4107
|
+
if (messages[i].info?.role === "user") {
|
|
4108
|
+
return messages[i];
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
return;
|
|
4112
|
+
}
|
|
4113
|
+
function extractResumeConfig(userMessage, sessionID) {
|
|
4114
|
+
return {
|
|
4115
|
+
sessionID,
|
|
4116
|
+
agent: userMessage?.info?.agent,
|
|
4117
|
+
model: userMessage?.info?.model
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4120
|
+
async function resumeSession(client, config) {
|
|
4121
|
+
try {
|
|
4122
|
+
await client.session.prompt({
|
|
4123
|
+
path: { id: config.sessionID },
|
|
4124
|
+
body: {
|
|
4125
|
+
parts: [{ type: "text", text: RECOVERY_RESUME_TEXT }],
|
|
4126
|
+
agent: config.agent,
|
|
4127
|
+
model: config.model
|
|
4128
|
+
}
|
|
4129
|
+
});
|
|
4130
|
+
return true;
|
|
4131
|
+
} catch {
|
|
4132
|
+
return false;
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4145
4135
|
function getErrorMessage(error) {
|
|
4146
4136
|
if (!error)
|
|
4147
4137
|
return "";
|
|
@@ -4184,9 +4174,6 @@ function detectErrorType(error) {
|
|
|
4184
4174
|
if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
|
|
4185
4175
|
return "thinking_disabled_violation";
|
|
4186
4176
|
}
|
|
4187
|
-
if (message.includes("non-empty content") || message.includes("must have non-empty content") || message.includes("content") && message.includes("is empty") || message.includes("content field") && message.includes("empty")) {
|
|
4188
|
-
return "empty_content_message";
|
|
4189
|
-
}
|
|
4190
4177
|
return null;
|
|
4191
4178
|
}
|
|
4192
4179
|
function extractToolUseIds(parts) {
|
|
@@ -4255,55 +4242,9 @@ async function recoverThinkingDisabledViolation(_client, sessionID, _failedAssis
|
|
|
4255
4242
|
}
|
|
4256
4243
|
return anySuccess;
|
|
4257
4244
|
}
|
|
4258
|
-
|
|
4259
|
-
async function recoverEmptyContentMessage(_client, sessionID, failedAssistantMsg, _directory, error) {
|
|
4260
|
-
const targetIndex = extractMessageIndex(error);
|
|
4261
|
-
const failedID = failedAssistantMsg.info?.id;
|
|
4262
|
-
let anySuccess = false;
|
|
4263
|
-
const messagesWithEmptyText = findMessagesWithEmptyTextParts(sessionID);
|
|
4264
|
-
for (const messageID of messagesWithEmptyText) {
|
|
4265
|
-
if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
|
|
4266
|
-
anySuccess = true;
|
|
4267
|
-
}
|
|
4268
|
-
}
|
|
4269
|
-
const thinkingOnlyIDs = findMessagesWithThinkingOnly(sessionID);
|
|
4270
|
-
for (const messageID of thinkingOnlyIDs) {
|
|
4271
|
-
if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
|
|
4272
|
-
anySuccess = true;
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
if (targetIndex !== null) {
|
|
4276
|
-
const targetMessageID = findEmptyMessageByIndex(sessionID, targetIndex);
|
|
4277
|
-
if (targetMessageID) {
|
|
4278
|
-
if (replaceEmptyTextParts(targetMessageID, PLACEHOLDER_TEXT)) {
|
|
4279
|
-
return true;
|
|
4280
|
-
}
|
|
4281
|
-
if (injectTextPart(sessionID, targetMessageID, PLACEHOLDER_TEXT)) {
|
|
4282
|
-
return true;
|
|
4283
|
-
}
|
|
4284
|
-
}
|
|
4285
|
-
}
|
|
4286
|
-
if (failedID) {
|
|
4287
|
-
if (replaceEmptyTextParts(failedID, PLACEHOLDER_TEXT)) {
|
|
4288
|
-
return true;
|
|
4289
|
-
}
|
|
4290
|
-
if (injectTextPart(sessionID, failedID, PLACEHOLDER_TEXT)) {
|
|
4291
|
-
return true;
|
|
4292
|
-
}
|
|
4293
|
-
}
|
|
4294
|
-
const emptyMessageIDs = findEmptyMessages(sessionID);
|
|
4295
|
-
for (const messageID of emptyMessageIDs) {
|
|
4296
|
-
if (replaceEmptyTextParts(messageID, PLACEHOLDER_TEXT)) {
|
|
4297
|
-
anySuccess = true;
|
|
4298
|
-
}
|
|
4299
|
-
if (injectTextPart(sessionID, messageID, PLACEHOLDER_TEXT)) {
|
|
4300
|
-
anySuccess = true;
|
|
4301
|
-
}
|
|
4302
|
-
}
|
|
4303
|
-
return anySuccess;
|
|
4304
|
-
}
|
|
4305
|
-
function createSessionRecoveryHook(ctx) {
|
|
4245
|
+
function createSessionRecoveryHook(ctx, options) {
|
|
4306
4246
|
const processingErrors = new Set;
|
|
4247
|
+
const experimental = options?.experimental;
|
|
4307
4248
|
let onAbortCallback = null;
|
|
4308
4249
|
let onRecoveryCompleteCallback = null;
|
|
4309
4250
|
const setOnAbortCallback = (callback) => {
|
|
@@ -4345,14 +4286,12 @@ function createSessionRecoveryHook(ctx) {
|
|
|
4345
4286
|
const toastTitles = {
|
|
4346
4287
|
tool_result_missing: "Tool Crash Recovery",
|
|
4347
4288
|
thinking_block_order: "Thinking Block Recovery",
|
|
4348
|
-
thinking_disabled_violation: "Thinking Strip Recovery"
|
|
4349
|
-
empty_content_message: "Empty Message Recovery"
|
|
4289
|
+
thinking_disabled_violation: "Thinking Strip Recovery"
|
|
4350
4290
|
};
|
|
4351
4291
|
const toastMessages = {
|
|
4352
4292
|
tool_result_missing: "Injecting cancelled tool results...",
|
|
4353
4293
|
thinking_block_order: "Fixing message structure...",
|
|
4354
|
-
thinking_disabled_violation: "Stripping thinking blocks..."
|
|
4355
|
-
empty_content_message: "Fixing empty message..."
|
|
4294
|
+
thinking_disabled_violation: "Stripping thinking blocks..."
|
|
4356
4295
|
};
|
|
4357
4296
|
await ctx.client.tui.showToast({
|
|
4358
4297
|
body: {
|
|
@@ -4367,10 +4306,18 @@ function createSessionRecoveryHook(ctx) {
|
|
|
4367
4306
|
success = await recoverToolResultMissing(ctx.client, sessionID, failedMsg);
|
|
4368
4307
|
} else if (errorType === "thinking_block_order") {
|
|
4369
4308
|
success = await recoverThinkingBlockOrder(ctx.client, sessionID, failedMsg, ctx.directory, info.error);
|
|
4309
|
+
if (success && experimental?.auto_resume) {
|
|
4310
|
+
const lastUser = findLastUserMessage(msgs ?? []);
|
|
4311
|
+
const resumeConfig = extractResumeConfig(lastUser, sessionID);
|
|
4312
|
+
await resumeSession(ctx.client, resumeConfig);
|
|
4313
|
+
}
|
|
4370
4314
|
} else if (errorType === "thinking_disabled_violation") {
|
|
4371
4315
|
success = await recoverThinkingDisabledViolation(ctx.client, sessionID, failedMsg);
|
|
4372
|
-
|
|
4373
|
-
|
|
4316
|
+
if (success && experimental?.auto_resume) {
|
|
4317
|
+
const lastUser = findLastUserMessage(msgs ?? []);
|
|
4318
|
+
const resumeConfig = extractResumeConfig(lastUser, sessionID);
|
|
4319
|
+
await resumeSession(ctx.client, resumeConfig);
|
|
4320
|
+
}
|
|
4374
4321
|
}
|
|
4375
4322
|
return success;
|
|
4376
4323
|
} catch (err) {
|
|
@@ -4393,7 +4340,7 @@ function createSessionRecoveryHook(ctx) {
|
|
|
4393
4340
|
// src/hooks/comment-checker/cli.ts
|
|
4394
4341
|
var {spawn: spawn3 } = globalThis.Bun;
|
|
4395
4342
|
import { createRequire as createRequire2 } from "module";
|
|
4396
|
-
import { dirname, join as
|
|
4343
|
+
import { dirname, join as join10 } from "path";
|
|
4397
4344
|
import { existsSync as existsSync7 } from "fs";
|
|
4398
4345
|
import * as fs2 from "fs";
|
|
4399
4346
|
import { tmpdir as tmpdir3 } from "os";
|
|
@@ -4401,11 +4348,11 @@ import { tmpdir as tmpdir3 } from "os";
|
|
|
4401
4348
|
// src/hooks/comment-checker/downloader.ts
|
|
4402
4349
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
4403
4350
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
|
|
4404
|
-
import { join as
|
|
4405
|
-
import { homedir as
|
|
4351
|
+
import { join as join9 } from "path";
|
|
4352
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
|
|
4406
4353
|
import { createRequire } from "module";
|
|
4407
4354
|
var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
4408
|
-
var DEBUG_FILE =
|
|
4355
|
+
var DEBUG_FILE = join9(tmpdir2(), "comment-checker-debug.log");
|
|
4409
4356
|
function debugLog(...args) {
|
|
4410
4357
|
if (DEBUG) {
|
|
4411
4358
|
const msg = `[${new Date().toISOString()}] [comment-checker:downloader] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -4423,14 +4370,14 @@ var PLATFORM_MAP = {
|
|
|
4423
4370
|
};
|
|
4424
4371
|
function getCacheDir() {
|
|
4425
4372
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
4426
|
-
const base = xdgCache2 ||
|
|
4427
|
-
return
|
|
4373
|
+
const base = xdgCache2 || join9(homedir4(), ".cache");
|
|
4374
|
+
return join9(base, "oh-my-opencode", "bin");
|
|
4428
4375
|
}
|
|
4429
4376
|
function getBinaryName() {
|
|
4430
4377
|
return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
|
|
4431
4378
|
}
|
|
4432
4379
|
function getCachedBinaryPath() {
|
|
4433
|
-
const binaryPath =
|
|
4380
|
+
const binaryPath = join9(getCacheDir(), getBinaryName());
|
|
4434
4381
|
return existsSync6(binaryPath) ? binaryPath : null;
|
|
4435
4382
|
}
|
|
4436
4383
|
function getPackageVersion() {
|
|
@@ -4478,14 +4425,14 @@ async function downloadCommentChecker() {
|
|
|
4478
4425
|
}
|
|
4479
4426
|
const cacheDir = getCacheDir();
|
|
4480
4427
|
const binaryName = getBinaryName();
|
|
4481
|
-
const binaryPath =
|
|
4428
|
+
const binaryPath = join9(cacheDir, binaryName);
|
|
4482
4429
|
if (existsSync6(binaryPath)) {
|
|
4483
4430
|
debugLog("Binary already cached at:", binaryPath);
|
|
4484
4431
|
return binaryPath;
|
|
4485
4432
|
}
|
|
4486
4433
|
const version = getPackageVersion();
|
|
4487
|
-
const { os:
|
|
4488
|
-
const assetName = `comment-checker_v${version}_${
|
|
4434
|
+
const { os: os4, arch, ext } = platformInfo;
|
|
4435
|
+
const assetName = `comment-checker_v${version}_${os4}_${arch}.${ext}`;
|
|
4489
4436
|
const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
|
|
4490
4437
|
debugLog(`Downloading from: ${downloadUrl}`);
|
|
4491
4438
|
console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
|
|
@@ -4497,7 +4444,7 @@ async function downloadCommentChecker() {
|
|
|
4497
4444
|
if (!response.ok) {
|
|
4498
4445
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
4499
4446
|
}
|
|
4500
|
-
const archivePath =
|
|
4447
|
+
const archivePath = join9(cacheDir, assetName);
|
|
4501
4448
|
const arrayBuffer = await response.arrayBuffer();
|
|
4502
4449
|
await Bun.write(archivePath, arrayBuffer);
|
|
4503
4450
|
debugLog(`Downloaded archive to: ${archivePath}`);
|
|
@@ -4533,7 +4480,7 @@ async function ensureCommentCheckerBinary() {
|
|
|
4533
4480
|
|
|
4534
4481
|
// src/hooks/comment-checker/cli.ts
|
|
4535
4482
|
var DEBUG2 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
4536
|
-
var DEBUG_FILE2 =
|
|
4483
|
+
var DEBUG_FILE2 = join10(tmpdir3(), "comment-checker-debug.log");
|
|
4537
4484
|
function debugLog2(...args) {
|
|
4538
4485
|
if (DEBUG2) {
|
|
4539
4486
|
const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -4550,7 +4497,7 @@ function findCommentCheckerPathSync() {
|
|
|
4550
4497
|
const require2 = createRequire2(import.meta.url);
|
|
4551
4498
|
const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
|
|
4552
4499
|
const cliDir = dirname(cliPkgPath);
|
|
4553
|
-
const binaryPath =
|
|
4500
|
+
const binaryPath = join10(cliDir, "bin", binaryName);
|
|
4554
4501
|
if (existsSync7(binaryPath)) {
|
|
4555
4502
|
debugLog2("found binary in main package:", binaryPath);
|
|
4556
4503
|
return binaryPath;
|
|
@@ -4597,8 +4544,8 @@ async function getCommentCheckerPath() {
|
|
|
4597
4544
|
function startBackgroundInit() {
|
|
4598
4545
|
if (!initPromise) {
|
|
4599
4546
|
initPromise = getCommentCheckerPath();
|
|
4600
|
-
initPromise.then((
|
|
4601
|
-
debugLog2("background init complete:",
|
|
4547
|
+
initPromise.then((path4) => {
|
|
4548
|
+
debugLog2("background init complete:", path4 || "no binary");
|
|
4602
4549
|
}).catch((err) => {
|
|
4603
4550
|
debugLog2("background init error:", err);
|
|
4604
4551
|
});
|
|
@@ -4647,9 +4594,9 @@ async function runCommentChecker(input, cliPath) {
|
|
|
4647
4594
|
import * as fs3 from "fs";
|
|
4648
4595
|
import { existsSync as existsSync8 } from "fs";
|
|
4649
4596
|
import { tmpdir as tmpdir4 } from "os";
|
|
4650
|
-
import { join as
|
|
4597
|
+
import { join as join11 } from "path";
|
|
4651
4598
|
var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
4652
|
-
var DEBUG_FILE3 =
|
|
4599
|
+
var DEBUG_FILE3 = join11(tmpdir4(), "comment-checker-debug.log");
|
|
4653
4600
|
function debugLog3(...args) {
|
|
4654
4601
|
if (DEBUG3) {
|
|
4655
4602
|
const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
@@ -4673,8 +4620,8 @@ function createCommentCheckerHooks() {
|
|
|
4673
4620
|
debugLog3("createCommentCheckerHooks called");
|
|
4674
4621
|
startBackgroundInit();
|
|
4675
4622
|
cliPathPromise = getCommentCheckerPath();
|
|
4676
|
-
cliPathPromise.then((
|
|
4677
|
-
debugLog3("CLI path resolved:",
|
|
4623
|
+
cliPathPromise.then((path4) => {
|
|
4624
|
+
debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
|
|
4678
4625
|
}).catch((err) => {
|
|
4679
4626
|
debugLog3("CLI path resolution error:", err);
|
|
4680
4627
|
});
|
|
@@ -4795,7 +4742,7 @@ function createToolOutputTruncatorHook(ctx) {
|
|
|
4795
4742
|
}
|
|
4796
4743
|
// src/hooks/directory-agents-injector/index.ts
|
|
4797
4744
|
import { existsSync as existsSync10, readFileSync as readFileSync5 } from "fs";
|
|
4798
|
-
import { dirname as dirname2, join as
|
|
4745
|
+
import { dirname as dirname2, join as join14, resolve as resolve2 } from "path";
|
|
4799
4746
|
|
|
4800
4747
|
// src/hooks/directory-agents-injector/storage.ts
|
|
4801
4748
|
import {
|
|
@@ -4805,17 +4752,17 @@ import {
|
|
|
4805
4752
|
writeFileSync as writeFileSync3,
|
|
4806
4753
|
unlinkSync as unlinkSync3
|
|
4807
4754
|
} from "fs";
|
|
4808
|
-
import { join as
|
|
4755
|
+
import { join as join13 } from "path";
|
|
4809
4756
|
|
|
4810
4757
|
// src/hooks/directory-agents-injector/constants.ts
|
|
4811
|
-
import { join as
|
|
4812
|
-
var OPENCODE_STORAGE3 =
|
|
4813
|
-
var AGENTS_INJECTOR_STORAGE =
|
|
4758
|
+
import { join as join12 } from "path";
|
|
4759
|
+
var OPENCODE_STORAGE3 = join12(xdgData2 ?? "", "opencode", "storage");
|
|
4760
|
+
var AGENTS_INJECTOR_STORAGE = join12(OPENCODE_STORAGE3, "directory-agents");
|
|
4814
4761
|
var AGENTS_FILENAME = "AGENTS.md";
|
|
4815
4762
|
|
|
4816
4763
|
// src/hooks/directory-agents-injector/storage.ts
|
|
4817
4764
|
function getStoragePath(sessionID) {
|
|
4818
|
-
return
|
|
4765
|
+
return join13(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
4819
4766
|
}
|
|
4820
4767
|
function loadInjectedPaths(sessionID) {
|
|
4821
4768
|
const filePath = getStoragePath(sessionID);
|
|
@@ -4867,7 +4814,7 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
4867
4814
|
const found = [];
|
|
4868
4815
|
let current = startDir;
|
|
4869
4816
|
while (true) {
|
|
4870
|
-
const agentsPath =
|
|
4817
|
+
const agentsPath = join14(current, AGENTS_FILENAME);
|
|
4871
4818
|
if (existsSync10(agentsPath)) {
|
|
4872
4819
|
found.push(agentsPath);
|
|
4873
4820
|
}
|
|
@@ -4904,10 +4851,10 @@ function createDirectoryAgentsInjectorHook(ctx) {
|
|
|
4904
4851
|
}
|
|
4905
4852
|
if (toInject.length === 0)
|
|
4906
4853
|
return;
|
|
4907
|
-
for (const { path:
|
|
4854
|
+
for (const { path: path4, content } of toInject) {
|
|
4908
4855
|
output.output += `
|
|
4909
4856
|
|
|
4910
|
-
[Directory Context: ${
|
|
4857
|
+
[Directory Context: ${path4}]
|
|
4911
4858
|
${content}`;
|
|
4912
4859
|
}
|
|
4913
4860
|
saveInjectedPaths(input.sessionID, cache);
|
|
@@ -4936,7 +4883,7 @@ ${content}`;
|
|
|
4936
4883
|
}
|
|
4937
4884
|
// src/hooks/directory-readme-injector/index.ts
|
|
4938
4885
|
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
4939
|
-
import { dirname as dirname3, join as
|
|
4886
|
+
import { dirname as dirname3, join as join17, resolve as resolve3 } from "path";
|
|
4940
4887
|
|
|
4941
4888
|
// src/hooks/directory-readme-injector/storage.ts
|
|
4942
4889
|
import {
|
|
@@ -4946,17 +4893,17 @@ import {
|
|
|
4946
4893
|
writeFileSync as writeFileSync4,
|
|
4947
4894
|
unlinkSync as unlinkSync4
|
|
4948
4895
|
} from "fs";
|
|
4949
|
-
import { join as
|
|
4896
|
+
import { join as join16 } from "path";
|
|
4950
4897
|
|
|
4951
4898
|
// src/hooks/directory-readme-injector/constants.ts
|
|
4952
|
-
import { join as
|
|
4953
|
-
var OPENCODE_STORAGE4 =
|
|
4954
|
-
var README_INJECTOR_STORAGE =
|
|
4899
|
+
import { join as join15 } from "path";
|
|
4900
|
+
var OPENCODE_STORAGE4 = join15(xdgData2 ?? "", "opencode", "storage");
|
|
4901
|
+
var README_INJECTOR_STORAGE = join15(OPENCODE_STORAGE4, "directory-readme");
|
|
4955
4902
|
var README_FILENAME = "README.md";
|
|
4956
4903
|
|
|
4957
4904
|
// src/hooks/directory-readme-injector/storage.ts
|
|
4958
4905
|
function getStoragePath2(sessionID) {
|
|
4959
|
-
return
|
|
4906
|
+
return join16(README_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
4960
4907
|
}
|
|
4961
4908
|
function loadInjectedPaths2(sessionID) {
|
|
4962
4909
|
const filePath = getStoragePath2(sessionID);
|
|
@@ -5008,7 +4955,7 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
5008
4955
|
const found = [];
|
|
5009
4956
|
let current = startDir;
|
|
5010
4957
|
while (true) {
|
|
5011
|
-
const readmePath =
|
|
4958
|
+
const readmePath = join17(current, README_FILENAME);
|
|
5012
4959
|
if (existsSync12(readmePath)) {
|
|
5013
4960
|
found.push(readmePath);
|
|
5014
4961
|
}
|
|
@@ -5045,10 +4992,10 @@ function createDirectoryReadmeInjectorHook(ctx) {
|
|
|
5045
4992
|
}
|
|
5046
4993
|
if (toInject.length === 0)
|
|
5047
4994
|
return;
|
|
5048
|
-
for (const { path:
|
|
4995
|
+
for (const { path: path4, content } of toInject) {
|
|
5049
4996
|
output.output += `
|
|
5050
4997
|
|
|
5051
|
-
[Project README: ${
|
|
4998
|
+
[Project README: ${path4}]
|
|
5052
4999
|
${content}`;
|
|
5053
5000
|
}
|
|
5054
5001
|
saveInjectedPaths2(input.sessionID, cache);
|
|
@@ -5111,7 +5058,8 @@ var TOKEN_LIMIT_KEYWORDS = [
|
|
|
5111
5058
|
"max_tokens",
|
|
5112
5059
|
"token limit",
|
|
5113
5060
|
"context length",
|
|
5114
|
-
"too many tokens"
|
|
5061
|
+
"too many tokens",
|
|
5062
|
+
"non-empty content"
|
|
5115
5063
|
];
|
|
5116
5064
|
function extractTokensFromMessage(message) {
|
|
5117
5065
|
for (const pattern of TOKEN_LIMIT_PATTERNS) {
|
|
@@ -5130,6 +5078,13 @@ function isTokenLimitError(text) {
|
|
|
5130
5078
|
}
|
|
5131
5079
|
function parseAnthropicTokenLimitError(err) {
|
|
5132
5080
|
if (typeof err === "string") {
|
|
5081
|
+
if (err.toLowerCase().includes("non-empty content")) {
|
|
5082
|
+
return {
|
|
5083
|
+
currentTokens: 0,
|
|
5084
|
+
maxTokens: 0,
|
|
5085
|
+
errorType: "non-empty content"
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5133
5088
|
if (isTokenLimitError(err)) {
|
|
5134
5089
|
const tokens = extractTokensFromMessage(err);
|
|
5135
5090
|
return {
|
|
@@ -5225,6 +5180,13 @@ function parseAnthropicTokenLimitError(err) {
|
|
|
5225
5180
|
};
|
|
5226
5181
|
}
|
|
5227
5182
|
}
|
|
5183
|
+
if (combinedText.toLowerCase().includes("non-empty content")) {
|
|
5184
|
+
return {
|
|
5185
|
+
currentTokens: 0,
|
|
5186
|
+
maxTokens: 0,
|
|
5187
|
+
errorType: "non-empty content"
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5228
5190
|
if (isTokenLimitError(combinedText)) {
|
|
5229
5191
|
return {
|
|
5230
5192
|
currentTokens: 0,
|
|
@@ -5247,26 +5209,35 @@ var FALLBACK_CONFIG = {
|
|
|
5247
5209
|
minMessagesRequired: 2
|
|
5248
5210
|
};
|
|
5249
5211
|
var TRUNCATE_CONFIG = {
|
|
5250
|
-
maxTruncateAttempts:
|
|
5251
|
-
minOutputSizeToTruncate:
|
|
5212
|
+
maxTruncateAttempts: 20,
|
|
5213
|
+
minOutputSizeToTruncate: 500,
|
|
5214
|
+
targetTokenRatio: 0.5,
|
|
5215
|
+
charsPerToken: 4
|
|
5252
5216
|
};
|
|
5253
5217
|
|
|
5254
5218
|
// src/hooks/anthropic-auto-compact/storage.ts
|
|
5255
5219
|
import { existsSync as existsSync13, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
|
|
5256
|
-
import {
|
|
5257
|
-
|
|
5258
|
-
var
|
|
5259
|
-
|
|
5220
|
+
import { homedir as homedir5 } from "os";
|
|
5221
|
+
import { join as join18 } from "path";
|
|
5222
|
+
var OPENCODE_STORAGE5 = join18(xdgData2 ?? "", "opencode", "storage");
|
|
5223
|
+
if (process.platform === "darwin" && !existsSync13(OPENCODE_STORAGE5)) {
|
|
5224
|
+
const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
|
|
5225
|
+
if (existsSync13(localShare)) {
|
|
5226
|
+
OPENCODE_STORAGE5 = localShare;
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
var MESSAGE_STORAGE3 = join18(OPENCODE_STORAGE5, "message");
|
|
5230
|
+
var PART_STORAGE3 = join18(OPENCODE_STORAGE5, "part");
|
|
5260
5231
|
var TRUNCATION_MESSAGE = "[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large and has been truncated to recover the session. Please re-run this tool if you need the full output.]";
|
|
5261
5232
|
function getMessageDir3(sessionID) {
|
|
5262
5233
|
if (!existsSync13(MESSAGE_STORAGE3))
|
|
5263
5234
|
return "";
|
|
5264
|
-
const directPath =
|
|
5235
|
+
const directPath = join18(MESSAGE_STORAGE3, sessionID);
|
|
5265
5236
|
if (existsSync13(directPath)) {
|
|
5266
5237
|
return directPath;
|
|
5267
5238
|
}
|
|
5268
5239
|
for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
|
|
5269
|
-
const sessionPath =
|
|
5240
|
+
const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
|
|
5270
5241
|
if (existsSync13(sessionPath)) {
|
|
5271
5242
|
return sessionPath;
|
|
5272
5243
|
}
|
|
@@ -5290,14 +5261,14 @@ function findToolResultsBySize(sessionID) {
|
|
|
5290
5261
|
const messageIds = getMessageIds(sessionID);
|
|
5291
5262
|
const results = [];
|
|
5292
5263
|
for (const messageID of messageIds) {
|
|
5293
|
-
const partDir =
|
|
5264
|
+
const partDir = join18(PART_STORAGE3, messageID);
|
|
5294
5265
|
if (!existsSync13(partDir))
|
|
5295
5266
|
continue;
|
|
5296
5267
|
for (const file of readdirSync4(partDir)) {
|
|
5297
5268
|
if (!file.endsWith(".json"))
|
|
5298
5269
|
continue;
|
|
5299
5270
|
try {
|
|
5300
|
-
const partPath =
|
|
5271
|
+
const partPath = join18(partDir, file);
|
|
5301
5272
|
const content = readFileSync8(partPath, "utf-8");
|
|
5302
5273
|
const part = JSON.parse(content);
|
|
5303
5274
|
if (part.type === "tool" && part.state?.output && !part.truncated) {
|
|
@@ -5342,6 +5313,56 @@ function truncateToolResult(partPath) {
|
|
|
5342
5313
|
return { success: false };
|
|
5343
5314
|
}
|
|
5344
5315
|
}
|
|
5316
|
+
function truncateUntilTargetTokens(sessionID, currentTokens, maxTokens, targetRatio = 0.8, charsPerToken = 4) {
|
|
5317
|
+
const targetTokens = Math.floor(maxTokens * targetRatio);
|
|
5318
|
+
const tokensToReduce = currentTokens - targetTokens;
|
|
5319
|
+
const charsToReduce = tokensToReduce * charsPerToken;
|
|
5320
|
+
if (tokensToReduce <= 0) {
|
|
5321
|
+
return {
|
|
5322
|
+
success: true,
|
|
5323
|
+
sufficient: true,
|
|
5324
|
+
truncatedCount: 0,
|
|
5325
|
+
totalBytesRemoved: 0,
|
|
5326
|
+
targetBytesToRemove: 0,
|
|
5327
|
+
truncatedTools: []
|
|
5328
|
+
};
|
|
5329
|
+
}
|
|
5330
|
+
const results = findToolResultsBySize(sessionID);
|
|
5331
|
+
if (results.length === 0) {
|
|
5332
|
+
return {
|
|
5333
|
+
success: false,
|
|
5334
|
+
sufficient: false,
|
|
5335
|
+
truncatedCount: 0,
|
|
5336
|
+
totalBytesRemoved: 0,
|
|
5337
|
+
targetBytesToRemove: charsToReduce,
|
|
5338
|
+
truncatedTools: []
|
|
5339
|
+
};
|
|
5340
|
+
}
|
|
5341
|
+
let totalRemoved = 0;
|
|
5342
|
+
let truncatedCount = 0;
|
|
5343
|
+
const truncatedTools = [];
|
|
5344
|
+
for (const result of results) {
|
|
5345
|
+
const truncateResult = truncateToolResult(result.partPath);
|
|
5346
|
+
if (truncateResult.success) {
|
|
5347
|
+
truncatedCount++;
|
|
5348
|
+
const removedSize = truncateResult.originalSize ?? result.outputSize;
|
|
5349
|
+
totalRemoved += removedSize;
|
|
5350
|
+
truncatedTools.push({
|
|
5351
|
+
toolName: truncateResult.toolName ?? result.toolName,
|
|
5352
|
+
originalSize: removedSize
|
|
5353
|
+
});
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
const sufficient = totalRemoved >= charsToReduce;
|
|
5357
|
+
return {
|
|
5358
|
+
success: truncatedCount > 0,
|
|
5359
|
+
sufficient,
|
|
5360
|
+
truncatedCount,
|
|
5361
|
+
totalBytesRemoved: totalRemoved,
|
|
5362
|
+
targetBytesToRemove: charsToReduce,
|
|
5363
|
+
truncatedTools
|
|
5364
|
+
};
|
|
5365
|
+
}
|
|
5345
5366
|
|
|
5346
5367
|
// src/hooks/anthropic-auto-compact/executor.ts
|
|
5347
5368
|
function getOrCreateRetryState(autoCompactState, sessionID) {
|
|
@@ -5440,14 +5461,97 @@ function clearSessionState(autoCompactState, sessionID) {
|
|
|
5440
5461
|
autoCompactState.retryStateBySession.delete(sessionID);
|
|
5441
5462
|
autoCompactState.fallbackStateBySession.delete(sessionID);
|
|
5442
5463
|
autoCompactState.truncateStateBySession.delete(sessionID);
|
|
5464
|
+
autoCompactState.emptyContentAttemptBySession.delete(sessionID);
|
|
5443
5465
|
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5444
5466
|
}
|
|
5445
|
-
|
|
5467
|
+
function getOrCreateEmptyContentAttempt(autoCompactState, sessionID) {
|
|
5468
|
+
return autoCompactState.emptyContentAttemptBySession.get(sessionID) ?? 0;
|
|
5469
|
+
}
|
|
5470
|
+
async function fixEmptyMessages(sessionID, autoCompactState, client) {
|
|
5471
|
+
const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
|
|
5472
|
+
autoCompactState.emptyContentAttemptBySession.set(sessionID, attempt + 1);
|
|
5473
|
+
const emptyMessageIds = findEmptyMessages(sessionID);
|
|
5474
|
+
if (emptyMessageIds.length === 0) {
|
|
5475
|
+
await client.tui.showToast({
|
|
5476
|
+
body: {
|
|
5477
|
+
title: "Empty Content Error",
|
|
5478
|
+
message: "No empty messages found in storage. Cannot auto-recover.",
|
|
5479
|
+
variant: "error",
|
|
5480
|
+
duration: 5000
|
|
5481
|
+
}
|
|
5482
|
+
}).catch(() => {});
|
|
5483
|
+
return false;
|
|
5484
|
+
}
|
|
5485
|
+
let fixed = false;
|
|
5486
|
+
for (const messageID of emptyMessageIds) {
|
|
5487
|
+
const success = injectTextPart(sessionID, messageID, "[user interrupted]");
|
|
5488
|
+
if (success)
|
|
5489
|
+
fixed = true;
|
|
5490
|
+
}
|
|
5491
|
+
if (fixed) {
|
|
5492
|
+
await client.tui.showToast({
|
|
5493
|
+
body: {
|
|
5494
|
+
title: "Session Recovery",
|
|
5495
|
+
message: `Fixed ${emptyMessageIds.length} empty messages. Retrying...`,
|
|
5496
|
+
variant: "warning",
|
|
5497
|
+
duration: 3000
|
|
5498
|
+
}
|
|
5499
|
+
}).catch(() => {});
|
|
5500
|
+
}
|
|
5501
|
+
return fixed;
|
|
5502
|
+
}
|
|
5503
|
+
async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
|
|
5446
5504
|
if (autoCompactState.compactionInProgress.has(sessionID)) {
|
|
5447
5505
|
return;
|
|
5448
5506
|
}
|
|
5449
5507
|
autoCompactState.compactionInProgress.add(sessionID);
|
|
5508
|
+
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
5450
5509
|
const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
|
|
5510
|
+
if (experimental?.aggressive_truncation && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens && truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5511
|
+
log("[auto-compact] aggressive truncation triggered (experimental)", {
|
|
5512
|
+
currentTokens: errorData.currentTokens,
|
|
5513
|
+
maxTokens: errorData.maxTokens,
|
|
5514
|
+
targetRatio: TRUNCATE_CONFIG.targetTokenRatio
|
|
5515
|
+
});
|
|
5516
|
+
const aggressiveResult = truncateUntilTargetTokens(sessionID, errorData.currentTokens, errorData.maxTokens, TRUNCATE_CONFIG.targetTokenRatio, TRUNCATE_CONFIG.charsPerToken);
|
|
5517
|
+
if (aggressiveResult.truncatedCount > 0) {
|
|
5518
|
+
truncateState.truncateAttempt += aggressiveResult.truncatedCount;
|
|
5519
|
+
const toolNames = aggressiveResult.truncatedTools.map((t) => t.toolName).join(", ");
|
|
5520
|
+
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...`;
|
|
5521
|
+
await client.tui.showToast({
|
|
5522
|
+
body: {
|
|
5523
|
+
title: aggressiveResult.sufficient ? "Aggressive Truncation" : "Partial Truncation",
|
|
5524
|
+
message: `${statusMsg}: ${toolNames}`,
|
|
5525
|
+
variant: "warning",
|
|
5526
|
+
duration: 4000
|
|
5527
|
+
}
|
|
5528
|
+
}).catch(() => {});
|
|
5529
|
+
log("[auto-compact] aggressive truncation completed", aggressiveResult);
|
|
5530
|
+
if (aggressiveResult.sufficient) {
|
|
5531
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5532
|
+
setTimeout(async () => {
|
|
5533
|
+
try {
|
|
5534
|
+
await client.session.prompt_async({
|
|
5535
|
+
path: { sessionID },
|
|
5536
|
+
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
5537
|
+
query: { directory }
|
|
5538
|
+
});
|
|
5539
|
+
} catch {}
|
|
5540
|
+
}, 500);
|
|
5541
|
+
return;
|
|
5542
|
+
}
|
|
5543
|
+
} else {
|
|
5544
|
+
await client.tui.showToast({
|
|
5545
|
+
body: {
|
|
5546
|
+
title: "Truncation Skipped",
|
|
5547
|
+
message: "No tool outputs found to truncate.",
|
|
5548
|
+
variant: "warning",
|
|
5549
|
+
duration: 3000
|
|
5550
|
+
}
|
|
5551
|
+
}).catch(() => {});
|
|
5552
|
+
}
|
|
5553
|
+
}
|
|
5554
|
+
let skipSummarize = false;
|
|
5451
5555
|
if (truncateState.truncateAttempt < TRUNCATE_CONFIG.maxTruncateAttempts) {
|
|
5452
5556
|
const largest = findLargestToolResult(sessionID);
|
|
5453
5557
|
if (largest && largest.outputSize >= TRUNCATE_CONFIG.minOutputSizeToTruncate) {
|
|
@@ -5475,10 +5579,58 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5475
5579
|
}, 500);
|
|
5476
5580
|
return;
|
|
5477
5581
|
}
|
|
5582
|
+
} else if (errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
5583
|
+
skipSummarize = true;
|
|
5584
|
+
await client.tui.showToast({
|
|
5585
|
+
body: {
|
|
5586
|
+
title: "Summarize Skipped",
|
|
5587
|
+
message: `Over token limit (${errorData.currentTokens}/${errorData.maxTokens}) with nothing to truncate. Going to revert...`,
|
|
5588
|
+
variant: "warning",
|
|
5589
|
+
duration: 3000
|
|
5590
|
+
}
|
|
5591
|
+
}).catch(() => {});
|
|
5592
|
+
} else if (!errorData?.currentTokens) {
|
|
5593
|
+
await client.tui.showToast({
|
|
5594
|
+
body: {
|
|
5595
|
+
title: "Truncation Skipped",
|
|
5596
|
+
message: "No large tool outputs found.",
|
|
5597
|
+
variant: "warning",
|
|
5598
|
+
duration: 3000
|
|
5599
|
+
}
|
|
5600
|
+
}).catch(() => {});
|
|
5478
5601
|
}
|
|
5479
5602
|
}
|
|
5480
5603
|
const retryState = getOrCreateRetryState(autoCompactState, sessionID);
|
|
5481
|
-
if (
|
|
5604
|
+
if (experimental?.empty_message_recovery && errorData?.errorType?.includes("non-empty content")) {
|
|
5605
|
+
const attempt = getOrCreateEmptyContentAttempt(autoCompactState, sessionID);
|
|
5606
|
+
if (attempt < 3) {
|
|
5607
|
+
const fixed = await fixEmptyMessages(sessionID, autoCompactState, client);
|
|
5608
|
+
if (fixed) {
|
|
5609
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5610
|
+
setTimeout(() => {
|
|
5611
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
5612
|
+
}, 500);
|
|
5613
|
+
return;
|
|
5614
|
+
}
|
|
5615
|
+
} else {
|
|
5616
|
+
await client.tui.showToast({
|
|
5617
|
+
body: {
|
|
5618
|
+
title: "Recovery Failed",
|
|
5619
|
+
message: "Max recovery attempts (3) reached for empty content error. Please start a new session.",
|
|
5620
|
+
variant: "error",
|
|
5621
|
+
duration: 1e4
|
|
5622
|
+
}
|
|
5623
|
+
}).catch(() => {});
|
|
5624
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5625
|
+
return;
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
if (Date.now() - retryState.lastAttemptTime > 300000) {
|
|
5629
|
+
retryState.attempt = 0;
|
|
5630
|
+
autoCompactState.fallbackStateBySession.delete(sessionID);
|
|
5631
|
+
autoCompactState.truncateStateBySession.delete(sessionID);
|
|
5632
|
+
}
|
|
5633
|
+
if (!skipSummarize && retryState.attempt < RETRY_CONFIG.maxAttempts) {
|
|
5482
5634
|
retryState.attempt++;
|
|
5483
5635
|
retryState.lastAttemptTime = Date.now();
|
|
5484
5636
|
const providerID = msg.providerID;
|
|
@@ -5498,7 +5650,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5498
5650
|
body: { providerID, modelID },
|
|
5499
5651
|
query: { directory }
|
|
5500
5652
|
});
|
|
5501
|
-
|
|
5653
|
+
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5502
5654
|
setTimeout(async () => {
|
|
5503
5655
|
try {
|
|
5504
5656
|
await client.session.prompt_async({
|
|
@@ -5514,10 +5666,19 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5514
5666
|
const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
|
|
5515
5667
|
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
|
|
5516
5668
|
setTimeout(() => {
|
|
5517
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory);
|
|
5669
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
5518
5670
|
}, cappedDelay);
|
|
5519
5671
|
return;
|
|
5520
5672
|
}
|
|
5673
|
+
} else {
|
|
5674
|
+
await client.tui.showToast({
|
|
5675
|
+
body: {
|
|
5676
|
+
title: "Summarize Skipped",
|
|
5677
|
+
message: "Missing providerID or modelID. Skipping to revert...",
|
|
5678
|
+
variant: "warning",
|
|
5679
|
+
duration: 3000
|
|
5680
|
+
}
|
|
5681
|
+
}).catch(() => {});
|
|
5521
5682
|
}
|
|
5522
5683
|
}
|
|
5523
5684
|
const fallbackState = getOrCreateFallbackState(autoCompactState, sessionID);
|
|
@@ -5551,10 +5712,19 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
5551
5712
|
truncateState.truncateAttempt = 0;
|
|
5552
5713
|
autoCompactState.compactionInProgress.delete(sessionID);
|
|
5553
5714
|
setTimeout(() => {
|
|
5554
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory);
|
|
5715
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
5555
5716
|
}, 1000);
|
|
5556
5717
|
return;
|
|
5557
5718
|
} catch {}
|
|
5719
|
+
} else {
|
|
5720
|
+
await client.tui.showToast({
|
|
5721
|
+
body: {
|
|
5722
|
+
title: "Revert Skipped",
|
|
5723
|
+
message: "Could not find last message pair to revert.",
|
|
5724
|
+
variant: "warning",
|
|
5725
|
+
duration: 3000
|
|
5726
|
+
}
|
|
5727
|
+
}).catch(() => {});
|
|
5558
5728
|
}
|
|
5559
5729
|
}
|
|
5560
5730
|
clearSessionState(autoCompactState, sessionID);
|
|
@@ -5576,11 +5746,13 @@ function createAutoCompactState() {
|
|
|
5576
5746
|
retryStateBySession: new Map,
|
|
5577
5747
|
fallbackStateBySession: new Map,
|
|
5578
5748
|
truncateStateBySession: new Map,
|
|
5749
|
+
emptyContentAttemptBySession: new Map,
|
|
5579
5750
|
compactionInProgress: new Set
|
|
5580
5751
|
};
|
|
5581
5752
|
}
|
|
5582
|
-
function createAnthropicAutoCompactHook(ctx) {
|
|
5753
|
+
function createAnthropicAutoCompactHook(ctx, options) {
|
|
5583
5754
|
const autoCompactState = createAutoCompactState();
|
|
5755
|
+
const experimental = options?.experimental;
|
|
5584
5756
|
const eventHandler = async ({ event }) => {
|
|
5585
5757
|
const props = event.properties;
|
|
5586
5758
|
if (event.type === "session.deleted") {
|
|
@@ -5591,15 +5763,18 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5591
5763
|
autoCompactState.retryStateBySession.delete(sessionInfo.id);
|
|
5592
5764
|
autoCompactState.fallbackStateBySession.delete(sessionInfo.id);
|
|
5593
5765
|
autoCompactState.truncateStateBySession.delete(sessionInfo.id);
|
|
5766
|
+
autoCompactState.emptyContentAttemptBySession.delete(sessionInfo.id);
|
|
5594
5767
|
autoCompactState.compactionInProgress.delete(sessionInfo.id);
|
|
5595
5768
|
}
|
|
5596
5769
|
return;
|
|
5597
5770
|
}
|
|
5598
5771
|
if (event.type === "session.error") {
|
|
5599
5772
|
const sessionID = props?.sessionID;
|
|
5773
|
+
log("[auto-compact] session.error received", { sessionID, error: props?.error });
|
|
5600
5774
|
if (!sessionID)
|
|
5601
5775
|
return;
|
|
5602
5776
|
const parsed = parseAnthropicTokenLimitError(props?.error);
|
|
5777
|
+
log("[auto-compact] parsed result", { parsed, hasError: !!props?.error });
|
|
5603
5778
|
if (parsed) {
|
|
5604
5779
|
autoCompactState.pendingCompact.add(sessionID);
|
|
5605
5780
|
autoCompactState.errorDataBySession.set(sessionID, parsed);
|
|
@@ -5618,7 +5793,7 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5618
5793
|
}
|
|
5619
5794
|
}).catch(() => {});
|
|
5620
5795
|
setTimeout(() => {
|
|
5621
|
-
executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory);
|
|
5796
|
+
executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
|
|
5622
5797
|
}, 300);
|
|
5623
5798
|
}
|
|
5624
5799
|
return;
|
|
@@ -5627,7 +5802,9 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5627
5802
|
const info = props?.info;
|
|
5628
5803
|
const sessionID = info?.sessionID;
|
|
5629
5804
|
if (sessionID && info?.role === "assistant" && info.error) {
|
|
5805
|
+
log("[auto-compact] message.updated with error", { sessionID, error: info.error });
|
|
5630
5806
|
const parsed = parseAnthropicTokenLimitError(info.error);
|
|
5807
|
+
log("[auto-compact] message.updated parsed result", { parsed });
|
|
5631
5808
|
if (parsed) {
|
|
5632
5809
|
parsed.providerID = info.providerID;
|
|
5633
5810
|
parsed.modelID = info.modelID;
|
|
@@ -5659,7 +5836,7 @@ function createAnthropicAutoCompactHook(ctx) {
|
|
|
5659
5836
|
duration: 3000
|
|
5660
5837
|
}
|
|
5661
5838
|
}).catch(() => {});
|
|
5662
|
-
await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory);
|
|
5839
|
+
await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
|
|
5663
5840
|
}
|
|
5664
5841
|
};
|
|
5665
5842
|
return {
|
|
@@ -5933,8 +6110,8 @@ function createThinkModeHook() {
|
|
|
5933
6110
|
};
|
|
5934
6111
|
}
|
|
5935
6112
|
// src/hooks/claude-code-hooks/config.ts
|
|
5936
|
-
import { homedir as
|
|
5937
|
-
import { join as
|
|
6113
|
+
import { homedir as homedir6 } from "os";
|
|
6114
|
+
import { join as join19 } from "path";
|
|
5938
6115
|
import { existsSync as existsSync14 } from "fs";
|
|
5939
6116
|
function normalizeHookMatcher(raw) {
|
|
5940
6117
|
return {
|
|
@@ -5958,11 +6135,11 @@ function normalizeHooksConfig(raw) {
|
|
|
5958
6135
|
return result;
|
|
5959
6136
|
}
|
|
5960
6137
|
function getClaudeSettingsPaths(customPath) {
|
|
5961
|
-
const home =
|
|
6138
|
+
const home = homedir6();
|
|
5962
6139
|
const paths = [
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
6140
|
+
join19(home, ".claude", "settings.json"),
|
|
6141
|
+
join19(process.cwd(), ".claude", "settings.json"),
|
|
6142
|
+
join19(process.cwd(), ".claude", "settings.local.json")
|
|
5966
6143
|
];
|
|
5967
6144
|
if (customPath && existsSync14(customPath)) {
|
|
5968
6145
|
paths.unshift(customPath);
|
|
@@ -6006,21 +6183,21 @@ async function loadClaudeHooksConfig(customSettingsPath) {
|
|
|
6006
6183
|
|
|
6007
6184
|
// src/hooks/claude-code-hooks/config-loader.ts
|
|
6008
6185
|
import { existsSync as existsSync15 } from "fs";
|
|
6009
|
-
import { homedir as
|
|
6010
|
-
import { join as
|
|
6011
|
-
var USER_CONFIG_PATH =
|
|
6186
|
+
import { homedir as homedir7 } from "os";
|
|
6187
|
+
import { join as join20 } from "path";
|
|
6188
|
+
var USER_CONFIG_PATH = join20(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
|
|
6012
6189
|
function getProjectConfigPath() {
|
|
6013
|
-
return
|
|
6190
|
+
return join20(process.cwd(), ".opencode", "opencode-cc-plugin.json");
|
|
6014
6191
|
}
|
|
6015
|
-
async function loadConfigFromPath(
|
|
6016
|
-
if (!existsSync15(
|
|
6192
|
+
async function loadConfigFromPath(path4) {
|
|
6193
|
+
if (!existsSync15(path4)) {
|
|
6017
6194
|
return null;
|
|
6018
6195
|
}
|
|
6019
6196
|
try {
|
|
6020
|
-
const content = await Bun.file(
|
|
6197
|
+
const content = await Bun.file(path4).text();
|
|
6021
6198
|
return JSON.parse(content);
|
|
6022
6199
|
} catch (error) {
|
|
6023
|
-
log("Failed to load config", { path:
|
|
6200
|
+
log("Failed to load config", { path: path4, error });
|
|
6024
6201
|
return null;
|
|
6025
6202
|
}
|
|
6026
6203
|
}
|
|
@@ -6192,13 +6369,13 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
|
|
|
6192
6369
|
}
|
|
6193
6370
|
|
|
6194
6371
|
// src/hooks/claude-code-hooks/transcript.ts
|
|
6195
|
-
import { join as
|
|
6372
|
+
import { join as join21 } from "path";
|
|
6196
6373
|
import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync16, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
|
|
6197
|
-
import { homedir as
|
|
6374
|
+
import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
|
|
6198
6375
|
import { randomUUID } from "crypto";
|
|
6199
|
-
var TRANSCRIPT_DIR =
|
|
6376
|
+
var TRANSCRIPT_DIR = join21(homedir8(), ".claude", "transcripts");
|
|
6200
6377
|
function getTranscriptPath(sessionId) {
|
|
6201
|
-
return
|
|
6378
|
+
return join21(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
6202
6379
|
}
|
|
6203
6380
|
function ensureTranscriptDir() {
|
|
6204
6381
|
if (!existsSync16(TRANSCRIPT_DIR)) {
|
|
@@ -6207,10 +6384,10 @@ function ensureTranscriptDir() {
|
|
|
6207
6384
|
}
|
|
6208
6385
|
function appendTranscriptEntry(sessionId, entry) {
|
|
6209
6386
|
ensureTranscriptDir();
|
|
6210
|
-
const
|
|
6387
|
+
const path4 = getTranscriptPath(sessionId);
|
|
6211
6388
|
const line = JSON.stringify(entry) + `
|
|
6212
6389
|
`;
|
|
6213
|
-
appendFileSync5(
|
|
6390
|
+
appendFileSync5(path4, line);
|
|
6214
6391
|
}
|
|
6215
6392
|
function recordToolUse(sessionId, toolName, toolInput) {
|
|
6216
6393
|
appendTranscriptEntry(sessionId, {
|
|
@@ -6288,7 +6465,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
6288
6465
|
}
|
|
6289
6466
|
};
|
|
6290
6467
|
entries.push(JSON.stringify(currentEntry));
|
|
6291
|
-
const tempPath =
|
|
6468
|
+
const tempPath = join21(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
|
|
6292
6469
|
writeFileSync6(tempPath, entries.join(`
|
|
6293
6470
|
`) + `
|
|
6294
6471
|
`);
|
|
@@ -6308,7 +6485,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
6308
6485
|
]
|
|
6309
6486
|
}
|
|
6310
6487
|
};
|
|
6311
|
-
const tempPath =
|
|
6488
|
+
const tempPath = join21(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
|
|
6312
6489
|
writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
|
|
6313
6490
|
`);
|
|
6314
6491
|
return tempPath;
|
|
@@ -6317,11 +6494,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
6317
6494
|
}
|
|
6318
6495
|
}
|
|
6319
6496
|
}
|
|
6320
|
-
function deleteTempTranscript(
|
|
6321
|
-
if (!
|
|
6497
|
+
function deleteTempTranscript(path4) {
|
|
6498
|
+
if (!path4)
|
|
6322
6499
|
return;
|
|
6323
6500
|
try {
|
|
6324
|
-
unlinkSync5(
|
|
6501
|
+
unlinkSync5(path4);
|
|
6325
6502
|
} catch {}
|
|
6326
6503
|
}
|
|
6327
6504
|
|
|
@@ -6520,11 +6697,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
|
|
|
6520
6697
|
}
|
|
6521
6698
|
|
|
6522
6699
|
// src/hooks/claude-code-hooks/todo.ts
|
|
6523
|
-
import { join as
|
|
6524
|
-
import { homedir as
|
|
6525
|
-
var TODO_DIR =
|
|
6700
|
+
import { join as join22 } from "path";
|
|
6701
|
+
import { homedir as homedir9 } from "os";
|
|
6702
|
+
var TODO_DIR = join22(homedir9(), ".claude", "todos");
|
|
6526
6703
|
function getTodoPath(sessionId) {
|
|
6527
|
-
return
|
|
6704
|
+
return join22(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
|
|
6528
6705
|
}
|
|
6529
6706
|
|
|
6530
6707
|
// src/hooks/claude-code-hooks/stop.ts
|
|
@@ -6857,7 +7034,7 @@ ${result.message}`;
|
|
|
6857
7034
|
}
|
|
6858
7035
|
// src/hooks/rules-injector/index.ts
|
|
6859
7036
|
import { readFileSync as readFileSync10 } from "fs";
|
|
6860
|
-
import { homedir as
|
|
7037
|
+
import { homedir as homedir10 } from "os";
|
|
6861
7038
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
6862
7039
|
|
|
6863
7040
|
// src/hooks/rules-injector/finder.ts
|
|
@@ -6867,12 +7044,12 @@ import {
|
|
|
6867
7044
|
realpathSync,
|
|
6868
7045
|
statSync as statSync2
|
|
6869
7046
|
} from "fs";
|
|
6870
|
-
import { dirname as dirname4, join as
|
|
7047
|
+
import { dirname as dirname4, join as join24, relative } from "path";
|
|
6871
7048
|
|
|
6872
7049
|
// src/hooks/rules-injector/constants.ts
|
|
6873
|
-
import { join as
|
|
6874
|
-
var OPENCODE_STORAGE6 =
|
|
6875
|
-
var RULES_INJECTOR_STORAGE =
|
|
7050
|
+
import { join as join23 } from "path";
|
|
7051
|
+
var OPENCODE_STORAGE6 = join23(xdgData2 ?? "", "opencode", "storage");
|
|
7052
|
+
var RULES_INJECTOR_STORAGE = join23(OPENCODE_STORAGE6, "rules-injector");
|
|
6876
7053
|
var PROJECT_MARKERS = [
|
|
6877
7054
|
".git",
|
|
6878
7055
|
"pyproject.toml",
|
|
@@ -6899,7 +7076,7 @@ function findProjectRoot(startPath) {
|
|
|
6899
7076
|
}
|
|
6900
7077
|
while (true) {
|
|
6901
7078
|
for (const marker of PROJECT_MARKERS) {
|
|
6902
|
-
const markerPath =
|
|
7079
|
+
const markerPath = join24(current, marker);
|
|
6903
7080
|
if (existsSync17(markerPath)) {
|
|
6904
7081
|
return current;
|
|
6905
7082
|
}
|
|
@@ -6917,7 +7094,7 @@ function findRuleFilesRecursive(dir, results) {
|
|
|
6917
7094
|
try {
|
|
6918
7095
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
6919
7096
|
for (const entry of entries) {
|
|
6920
|
-
const fullPath =
|
|
7097
|
+
const fullPath = join24(dir, entry.name);
|
|
6921
7098
|
if (entry.isDirectory()) {
|
|
6922
7099
|
findRuleFilesRecursive(fullPath, results);
|
|
6923
7100
|
} else if (entry.isFile()) {
|
|
@@ -6943,7 +7120,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
6943
7120
|
let distance = 0;
|
|
6944
7121
|
while (true) {
|
|
6945
7122
|
for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
|
|
6946
|
-
const ruleDir =
|
|
7123
|
+
const ruleDir = join24(currentDir, parent, subdir);
|
|
6947
7124
|
const files = [];
|
|
6948
7125
|
findRuleFilesRecursive(ruleDir, files);
|
|
6949
7126
|
for (const filePath of files) {
|
|
@@ -6967,7 +7144,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
6967
7144
|
currentDir = parentDir;
|
|
6968
7145
|
distance++;
|
|
6969
7146
|
}
|
|
6970
|
-
const userRuleDir =
|
|
7147
|
+
const userRuleDir = join24(homeDir, USER_RULE_DIR);
|
|
6971
7148
|
const userFiles = [];
|
|
6972
7149
|
findRuleFilesRecursive(userRuleDir, userFiles);
|
|
6973
7150
|
for (const filePath of userFiles) {
|
|
@@ -7162,9 +7339,9 @@ import {
|
|
|
7162
7339
|
writeFileSync as writeFileSync7,
|
|
7163
7340
|
unlinkSync as unlinkSync6
|
|
7164
7341
|
} from "fs";
|
|
7165
|
-
import { join as
|
|
7342
|
+
import { join as join25 } from "path";
|
|
7166
7343
|
function getStoragePath3(sessionID) {
|
|
7167
|
-
return
|
|
7344
|
+
return join25(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
7168
7345
|
}
|
|
7169
7346
|
function loadInjectedRules(sessionID) {
|
|
7170
7347
|
const filePath = getStoragePath3(sessionID);
|
|
@@ -7225,7 +7402,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
7225
7402
|
return;
|
|
7226
7403
|
const projectRoot = findProjectRoot(filePath);
|
|
7227
7404
|
const cache2 = getSessionCache(input.sessionID);
|
|
7228
|
-
const home =
|
|
7405
|
+
const home = homedir10();
|
|
7229
7406
|
const ruleFileCandidates = findRuleFiles(projectRoot, home, filePath);
|
|
7230
7407
|
const toInject = [];
|
|
7231
7408
|
for (const candidate of ruleFileCandidates) {
|
|
@@ -7296,33 +7473,33 @@ function createBackgroundNotificationHook(manager) {
|
|
|
7296
7473
|
}
|
|
7297
7474
|
// src/hooks/auto-update-checker/checker.ts
|
|
7298
7475
|
import * as fs4 from "fs";
|
|
7299
|
-
import * as
|
|
7476
|
+
import * as path5 from "path";
|
|
7300
7477
|
import { fileURLToPath } from "url";
|
|
7301
7478
|
|
|
7302
7479
|
// src/hooks/auto-update-checker/constants.ts
|
|
7303
|
-
import * as
|
|
7304
|
-
import * as
|
|
7480
|
+
import * as path4 from "path";
|
|
7481
|
+
import * as os4 from "os";
|
|
7305
7482
|
var PACKAGE_NAME = "oh-my-opencode";
|
|
7306
7483
|
var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
|
|
7307
7484
|
var NPM_FETCH_TIMEOUT = 5000;
|
|
7308
7485
|
function getCacheDir2() {
|
|
7309
7486
|
if (process.platform === "win32") {
|
|
7310
|
-
return
|
|
7487
|
+
return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
|
|
7311
7488
|
}
|
|
7312
|
-
return
|
|
7489
|
+
return path4.join(os4.homedir(), ".cache", "opencode");
|
|
7313
7490
|
}
|
|
7314
7491
|
var CACHE_DIR = getCacheDir2();
|
|
7315
|
-
var VERSION_FILE =
|
|
7316
|
-
var INSTALLED_PACKAGE_JSON =
|
|
7317
|
-
function
|
|
7492
|
+
var VERSION_FILE = path4.join(CACHE_DIR, "version");
|
|
7493
|
+
var INSTALLED_PACKAGE_JSON = path4.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
|
|
7494
|
+
function getUserConfigDir2() {
|
|
7318
7495
|
if (process.platform === "win32") {
|
|
7319
|
-
return process.env.APPDATA ??
|
|
7496
|
+
return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
|
|
7320
7497
|
}
|
|
7321
|
-
return process.env.XDG_CONFIG_HOME ??
|
|
7498
|
+
return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
|
|
7322
7499
|
}
|
|
7323
|
-
var USER_CONFIG_DIR =
|
|
7324
|
-
var USER_OPENCODE_CONFIG =
|
|
7325
|
-
var USER_OPENCODE_CONFIG_JSONC =
|
|
7500
|
+
var USER_CONFIG_DIR = getUserConfigDir2();
|
|
7501
|
+
var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.json");
|
|
7502
|
+
var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
|
|
7326
7503
|
|
|
7327
7504
|
// src/hooks/auto-update-checker/checker.ts
|
|
7328
7505
|
function isLocalDevMode(directory) {
|
|
@@ -7333,8 +7510,8 @@ function stripJsonComments(json) {
|
|
|
7333
7510
|
}
|
|
7334
7511
|
function getConfigPaths(directory) {
|
|
7335
7512
|
return [
|
|
7336
|
-
|
|
7337
|
-
|
|
7513
|
+
path5.join(directory, ".opencode", "opencode.json"),
|
|
7514
|
+
path5.join(directory, ".opencode", "opencode.jsonc"),
|
|
7338
7515
|
USER_OPENCODE_CONFIG,
|
|
7339
7516
|
USER_OPENCODE_CONFIG_JSONC
|
|
7340
7517
|
];
|
|
@@ -7365,9 +7542,9 @@ function getLocalDevPath(directory) {
|
|
|
7365
7542
|
function findPackageJsonUp(startPath) {
|
|
7366
7543
|
try {
|
|
7367
7544
|
const stat = fs4.statSync(startPath);
|
|
7368
|
-
let dir = stat.isDirectory() ? startPath :
|
|
7545
|
+
let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
|
|
7369
7546
|
for (let i = 0;i < 10; i++) {
|
|
7370
|
-
const pkgPath =
|
|
7547
|
+
const pkgPath = path5.join(dir, "package.json");
|
|
7371
7548
|
if (fs4.existsSync(pkgPath)) {
|
|
7372
7549
|
try {
|
|
7373
7550
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -7376,7 +7553,7 @@ function findPackageJsonUp(startPath) {
|
|
|
7376
7553
|
return pkgPath;
|
|
7377
7554
|
} catch {}
|
|
7378
7555
|
}
|
|
7379
|
-
const parent =
|
|
7556
|
+
const parent = path5.dirname(dir);
|
|
7380
7557
|
if (parent === dir)
|
|
7381
7558
|
break;
|
|
7382
7559
|
dir = parent;
|
|
@@ -7433,7 +7610,7 @@ function getCachedVersion() {
|
|
|
7433
7610
|
}
|
|
7434
7611
|
} catch {}
|
|
7435
7612
|
try {
|
|
7436
|
-
const currentDir =
|
|
7613
|
+
const currentDir = path5.dirname(fileURLToPath(import.meta.url));
|
|
7437
7614
|
const pkgPath = findPackageJsonUp(currentDir);
|
|
7438
7615
|
if (pkgPath) {
|
|
7439
7616
|
const content = fs4.readFileSync(pkgPath, "utf-8");
|
|
@@ -7495,11 +7672,11 @@ async function checkForUpdate(directory) {
|
|
|
7495
7672
|
|
|
7496
7673
|
// src/hooks/auto-update-checker/cache.ts
|
|
7497
7674
|
import * as fs5 from "fs";
|
|
7498
|
-
import * as
|
|
7675
|
+
import * as path6 from "path";
|
|
7499
7676
|
function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
7500
7677
|
try {
|
|
7501
|
-
const pkgDir =
|
|
7502
|
-
const pkgJsonPath =
|
|
7678
|
+
const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
|
|
7679
|
+
const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
|
|
7503
7680
|
let packageRemoved = false;
|
|
7504
7681
|
let dependencyRemoved = false;
|
|
7505
7682
|
if (fs5.existsSync(pkgDir)) {
|
|
@@ -7530,7 +7707,27 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
|
7530
7707
|
|
|
7531
7708
|
// src/hooks/auto-update-checker/index.ts
|
|
7532
7709
|
function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
7533
|
-
const { showStartupToast = true } = options;
|
|
7710
|
+
const { showStartupToast = true, isSisyphusEnabled = false } = options;
|
|
7711
|
+
const getToastMessage = (isUpdate, latestVersion) => {
|
|
7712
|
+
if (isSisyphusEnabled) {
|
|
7713
|
+
return isUpdate ? `Sisyphus on steroids is steering OpenCode.
|
|
7714
|
+
v${latestVersion} available. Restart to apply.` : `Sisyphus on steroids is steering OpenCode.`;
|
|
7715
|
+
}
|
|
7716
|
+
return isUpdate ? `OpenCode is now on Steroids. oMoMoMoMo...
|
|
7717
|
+
v${latestVersion} available. Restart OpenCode to apply.` : `OpenCode is now on Steroids. oMoMoMoMo...`;
|
|
7718
|
+
};
|
|
7719
|
+
const showVersionToast = async (version) => {
|
|
7720
|
+
const displayVersion = version ?? "unknown";
|
|
7721
|
+
await ctx.client.tui.showToast({
|
|
7722
|
+
body: {
|
|
7723
|
+
title: `OhMyOpenCode ${displayVersion}`,
|
|
7724
|
+
message: getToastMessage(false),
|
|
7725
|
+
variant: "info",
|
|
7726
|
+
duration: 5000
|
|
7727
|
+
}
|
|
7728
|
+
}).catch(() => {});
|
|
7729
|
+
log(`[auto-update-checker] Startup toast shown: v${displayVersion}`);
|
|
7730
|
+
};
|
|
7534
7731
|
let hasChecked = false;
|
|
7535
7732
|
return {
|
|
7536
7733
|
event: async ({ event }) => {
|
|
@@ -7548,21 +7745,21 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
7548
7745
|
log("[auto-update-checker] Skipped: local development mode");
|
|
7549
7746
|
if (showStartupToast) {
|
|
7550
7747
|
const version = getLocalDevVersion(ctx.directory) ?? getCachedVersion();
|
|
7551
|
-
await showVersionToast(
|
|
7748
|
+
await showVersionToast(version);
|
|
7552
7749
|
}
|
|
7553
7750
|
return;
|
|
7554
7751
|
}
|
|
7555
7752
|
if (result.isPinned) {
|
|
7556
7753
|
log(`[auto-update-checker] Skipped: version pinned to ${result.currentVersion}`);
|
|
7557
7754
|
if (showStartupToast) {
|
|
7558
|
-
await showVersionToast(
|
|
7755
|
+
await showVersionToast(result.currentVersion);
|
|
7559
7756
|
}
|
|
7560
7757
|
return;
|
|
7561
7758
|
}
|
|
7562
7759
|
if (!result.needsUpdate) {
|
|
7563
7760
|
log("[auto-update-checker] No update needed");
|
|
7564
7761
|
if (showStartupToast) {
|
|
7565
|
-
await showVersionToast(
|
|
7762
|
+
await showVersionToast(result.currentVersion);
|
|
7566
7763
|
}
|
|
7567
7764
|
return;
|
|
7568
7765
|
}
|
|
@@ -7570,8 +7767,7 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
|
|
|
7570
7767
|
await ctx.client.tui.showToast({
|
|
7571
7768
|
body: {
|
|
7572
7769
|
title: `OhMyOpenCode ${result.latestVersion}`,
|
|
7573
|
-
message:
|
|
7574
|
-
v${result.latestVersion} available. Restart OpenCode to apply.`,
|
|
7770
|
+
message: getToastMessage(true, result.latestVersion ?? undefined),
|
|
7575
7771
|
variant: "info",
|
|
7576
7772
|
duration: 8000
|
|
7577
7773
|
}
|
|
@@ -7580,20 +7776,27 @@ v${result.latestVersion} available. Restart OpenCode to apply.`,
|
|
|
7580
7776
|
} catch (err) {
|
|
7581
7777
|
log("[auto-update-checker] Error during update check:", err);
|
|
7582
7778
|
}
|
|
7779
|
+
await showConfigErrorsIfAny(ctx);
|
|
7583
7780
|
}
|
|
7584
7781
|
};
|
|
7585
7782
|
}
|
|
7586
|
-
async function
|
|
7587
|
-
const
|
|
7783
|
+
async function showConfigErrorsIfAny(ctx) {
|
|
7784
|
+
const errors = getConfigLoadErrors();
|
|
7785
|
+
if (errors.length === 0)
|
|
7786
|
+
return;
|
|
7787
|
+
const errorMessages = errors.map((e) => `${e.path}: ${e.error}`).join(`
|
|
7788
|
+
`);
|
|
7588
7789
|
await ctx.client.tui.showToast({
|
|
7589
7790
|
body: {
|
|
7590
|
-
title:
|
|
7591
|
-
message:
|
|
7592
|
-
|
|
7593
|
-
|
|
7791
|
+
title: "Config Load Error",
|
|
7792
|
+
message: `Failed to load config:
|
|
7793
|
+
${errorMessages}`,
|
|
7794
|
+
variant: "error",
|
|
7795
|
+
duration: 1e4
|
|
7594
7796
|
}
|
|
7595
7797
|
}).catch(() => {});
|
|
7596
|
-
log(`[auto-update-checker]
|
|
7798
|
+
log(`[auto-update-checker] Config load errors shown: ${errors.length} error(s)`);
|
|
7799
|
+
clearConfigLoadErrors();
|
|
7597
7800
|
}
|
|
7598
7801
|
// src/hooks/agent-usage-reminder/storage.ts
|
|
7599
7802
|
import {
|
|
@@ -7603,12 +7806,12 @@ import {
|
|
|
7603
7806
|
writeFileSync as writeFileSync9,
|
|
7604
7807
|
unlinkSync as unlinkSync7
|
|
7605
7808
|
} from "fs";
|
|
7606
|
-
import { join as
|
|
7809
|
+
import { join as join30 } from "path";
|
|
7607
7810
|
|
|
7608
7811
|
// src/hooks/agent-usage-reminder/constants.ts
|
|
7609
|
-
import { join as
|
|
7610
|
-
var OPENCODE_STORAGE7 =
|
|
7611
|
-
var AGENT_USAGE_REMINDER_STORAGE =
|
|
7812
|
+
import { join as join29 } from "path";
|
|
7813
|
+
var OPENCODE_STORAGE7 = join29(xdgData2 ?? "", "opencode", "storage");
|
|
7814
|
+
var AGENT_USAGE_REMINDER_STORAGE = join29(OPENCODE_STORAGE7, "agent-usage-reminder");
|
|
7612
7815
|
var TARGET_TOOLS = new Set([
|
|
7613
7816
|
"grep",
|
|
7614
7817
|
"safe_grep",
|
|
@@ -7653,7 +7856,7 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
|
|
|
7653
7856
|
|
|
7654
7857
|
// src/hooks/agent-usage-reminder/storage.ts
|
|
7655
7858
|
function getStoragePath4(sessionID) {
|
|
7656
|
-
return
|
|
7859
|
+
return join30(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
|
|
7657
7860
|
}
|
|
7658
7861
|
function loadAgentUsageState(sessionID) {
|
|
7659
7862
|
const filePath = getStoragePath4(sessionID);
|
|
@@ -7888,12 +8091,12 @@ import {
|
|
|
7888
8091
|
writeFileSync as writeFileSync10,
|
|
7889
8092
|
unlinkSync as unlinkSync8
|
|
7890
8093
|
} from "fs";
|
|
7891
|
-
import { join as
|
|
8094
|
+
import { join as join32 } from "path";
|
|
7892
8095
|
|
|
7893
8096
|
// src/hooks/interactive-bash-session/constants.ts
|
|
7894
|
-
import { join as
|
|
7895
|
-
var OPENCODE_STORAGE8 =
|
|
7896
|
-
var INTERACTIVE_BASH_SESSION_STORAGE =
|
|
8097
|
+
import { join as join31 } from "path";
|
|
8098
|
+
var OPENCODE_STORAGE8 = join31(xdgData2 ?? "", "opencode", "storage");
|
|
8099
|
+
var INTERACTIVE_BASH_SESSION_STORAGE = join31(OPENCODE_STORAGE8, "interactive-bash-session");
|
|
7897
8100
|
var OMO_SESSION_PREFIX = "omo-";
|
|
7898
8101
|
function buildSessionReminderMessage(sessions) {
|
|
7899
8102
|
if (sessions.length === 0)
|
|
@@ -7905,7 +8108,7 @@ function buildSessionReminderMessage(sessions) {
|
|
|
7905
8108
|
|
|
7906
8109
|
// src/hooks/interactive-bash-session/storage.ts
|
|
7907
8110
|
function getStoragePath5(sessionID) {
|
|
7908
|
-
return
|
|
8111
|
+
return join32(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
|
|
7909
8112
|
}
|
|
7910
8113
|
function loadInteractiveBashSessionState(sessionID) {
|
|
7911
8114
|
const filePath = getStoragePath5(sessionID);
|
|
@@ -8115,7 +8318,7 @@ function createInteractiveBashSessionHook(_ctx) {
|
|
|
8115
8318
|
};
|
|
8116
8319
|
}
|
|
8117
8320
|
// src/hooks/empty-message-sanitizer/index.ts
|
|
8118
|
-
var
|
|
8321
|
+
var PLACEHOLDER_TEXT = "[user interrupted]";
|
|
8119
8322
|
function hasTextContent(part) {
|
|
8120
8323
|
if (part.type === "text") {
|
|
8121
8324
|
const text = part.text;
|
|
@@ -8144,7 +8347,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
8144
8347
|
if (part.type === "text") {
|
|
8145
8348
|
const textPart = part;
|
|
8146
8349
|
if (!textPart.text || !textPart.text.trim()) {
|
|
8147
|
-
textPart.text =
|
|
8350
|
+
textPart.text = PLACEHOLDER_TEXT;
|
|
8148
8351
|
textPart.synthetic = true;
|
|
8149
8352
|
injected = true;
|
|
8150
8353
|
break;
|
|
@@ -8158,7 +8361,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
8158
8361
|
messageID: message.info.id,
|
|
8159
8362
|
sessionID: message.info.sessionID ?? "",
|
|
8160
8363
|
type: "text",
|
|
8161
|
-
text:
|
|
8364
|
+
text: PLACEHOLDER_TEXT,
|
|
8162
8365
|
synthetic: true
|
|
8163
8366
|
};
|
|
8164
8367
|
if (insertIndex === -1) {
|
|
@@ -8172,7 +8375,7 @@ function createEmptyMessageSanitizerHook() {
|
|
|
8172
8375
|
if (part.type === "text") {
|
|
8173
8376
|
const textPart = part;
|
|
8174
8377
|
if (textPart.text !== undefined && textPart.text.trim() === "") {
|
|
8175
|
-
textPart.text =
|
|
8378
|
+
textPart.text = PLACEHOLDER_TEXT;
|
|
8176
8379
|
textPart.synthetic = true;
|
|
8177
8380
|
}
|
|
8178
8381
|
}
|
|
@@ -9882,8 +10085,8 @@ async function createGoogleAntigravityAuthPlugin({
|
|
|
9882
10085
|
}
|
|
9883
10086
|
// src/features/claude-code-command-loader/loader.ts
|
|
9884
10087
|
import { existsSync as existsSync23, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
|
|
9885
|
-
import { homedir as
|
|
9886
|
-
import { join as
|
|
10088
|
+
import { homedir as homedir12 } from "os";
|
|
10089
|
+
import { join as join33, basename } from "path";
|
|
9887
10090
|
function loadCommandsFromDir(commandsDir, scope) {
|
|
9888
10091
|
if (!existsSync23(commandsDir)) {
|
|
9889
10092
|
return [];
|
|
@@ -9893,7 +10096,7 @@ function loadCommandsFromDir(commandsDir, scope) {
|
|
|
9893
10096
|
for (const entry of entries) {
|
|
9894
10097
|
if (!isMarkdownFile(entry))
|
|
9895
10098
|
continue;
|
|
9896
|
-
const commandPath =
|
|
10099
|
+
const commandPath = join33(commandsDir, entry.name);
|
|
9897
10100
|
const commandName = basename(entry.name, ".md");
|
|
9898
10101
|
try {
|
|
9899
10102
|
const content = readFileSync15(commandPath, "utf-8");
|
|
@@ -9936,29 +10139,29 @@ function commandsToRecord(commands) {
|
|
|
9936
10139
|
return result;
|
|
9937
10140
|
}
|
|
9938
10141
|
function loadUserCommands() {
|
|
9939
|
-
const userCommandsDir =
|
|
10142
|
+
const userCommandsDir = join33(homedir12(), ".claude", "commands");
|
|
9940
10143
|
const commands = loadCommandsFromDir(userCommandsDir, "user");
|
|
9941
10144
|
return commandsToRecord(commands);
|
|
9942
10145
|
}
|
|
9943
10146
|
function loadProjectCommands() {
|
|
9944
|
-
const projectCommandsDir =
|
|
10147
|
+
const projectCommandsDir = join33(process.cwd(), ".claude", "commands");
|
|
9945
10148
|
const commands = loadCommandsFromDir(projectCommandsDir, "project");
|
|
9946
10149
|
return commandsToRecord(commands);
|
|
9947
10150
|
}
|
|
9948
10151
|
function loadOpencodeGlobalCommands() {
|
|
9949
|
-
const opencodeCommandsDir =
|
|
10152
|
+
const opencodeCommandsDir = join33(homedir12(), ".config", "opencode", "command");
|
|
9950
10153
|
const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
|
|
9951
10154
|
return commandsToRecord(commands);
|
|
9952
10155
|
}
|
|
9953
10156
|
function loadOpencodeProjectCommands() {
|
|
9954
|
-
const opencodeProjectDir =
|
|
10157
|
+
const opencodeProjectDir = join33(process.cwd(), ".opencode", "command");
|
|
9955
10158
|
const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
9956
10159
|
return commandsToRecord(commands);
|
|
9957
10160
|
}
|
|
9958
10161
|
// src/features/claude-code-skill-loader/loader.ts
|
|
9959
10162
|
import { existsSync as existsSync24, readdirSync as readdirSync7, readFileSync as readFileSync16 } from "fs";
|
|
9960
|
-
import { homedir as
|
|
9961
|
-
import { join as
|
|
10163
|
+
import { homedir as homedir13 } from "os";
|
|
10164
|
+
import { join as join34 } from "path";
|
|
9962
10165
|
function loadSkillsFromDir(skillsDir, scope) {
|
|
9963
10166
|
if (!existsSync24(skillsDir)) {
|
|
9964
10167
|
return [];
|
|
@@ -9968,11 +10171,11 @@ function loadSkillsFromDir(skillsDir, scope) {
|
|
|
9968
10171
|
for (const entry of entries) {
|
|
9969
10172
|
if (entry.name.startsWith("."))
|
|
9970
10173
|
continue;
|
|
9971
|
-
const skillPath =
|
|
10174
|
+
const skillPath = join34(skillsDir, entry.name);
|
|
9972
10175
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
9973
10176
|
continue;
|
|
9974
10177
|
const resolvedPath = resolveSymlink(skillPath);
|
|
9975
|
-
const skillMdPath =
|
|
10178
|
+
const skillMdPath = join34(resolvedPath, "SKILL.md");
|
|
9976
10179
|
if (!existsSync24(skillMdPath))
|
|
9977
10180
|
continue;
|
|
9978
10181
|
try {
|
|
@@ -10010,7 +10213,7 @@ $ARGUMENTS
|
|
|
10010
10213
|
return skills;
|
|
10011
10214
|
}
|
|
10012
10215
|
function loadUserSkillsAsCommands() {
|
|
10013
|
-
const userSkillsDir =
|
|
10216
|
+
const userSkillsDir = join34(homedir13(), ".claude", "skills");
|
|
10014
10217
|
const skills = loadSkillsFromDir(userSkillsDir, "user");
|
|
10015
10218
|
return skills.reduce((acc, skill) => {
|
|
10016
10219
|
acc[skill.name] = skill.definition;
|
|
@@ -10018,7 +10221,7 @@ function loadUserSkillsAsCommands() {
|
|
|
10018
10221
|
}, {});
|
|
10019
10222
|
}
|
|
10020
10223
|
function loadProjectSkillsAsCommands() {
|
|
10021
|
-
const projectSkillsDir =
|
|
10224
|
+
const projectSkillsDir = join34(process.cwd(), ".claude", "skills");
|
|
10022
10225
|
const skills = loadSkillsFromDir(projectSkillsDir, "project");
|
|
10023
10226
|
return skills.reduce((acc, skill) => {
|
|
10024
10227
|
acc[skill.name] = skill.definition;
|
|
@@ -10027,8 +10230,8 @@ function loadProjectSkillsAsCommands() {
|
|
|
10027
10230
|
}
|
|
10028
10231
|
// src/features/claude-code-agent-loader/loader.ts
|
|
10029
10232
|
import { existsSync as existsSync25, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
|
|
10030
|
-
import { homedir as
|
|
10031
|
-
import { join as
|
|
10233
|
+
import { homedir as homedir14 } from "os";
|
|
10234
|
+
import { join as join35, basename as basename2 } from "path";
|
|
10032
10235
|
function parseToolsConfig(toolsStr) {
|
|
10033
10236
|
if (!toolsStr)
|
|
10034
10237
|
return;
|
|
@@ -10050,7 +10253,7 @@ function loadAgentsFromDir(agentsDir, scope) {
|
|
|
10050
10253
|
for (const entry of entries) {
|
|
10051
10254
|
if (!isMarkdownFile(entry))
|
|
10052
10255
|
continue;
|
|
10053
|
-
const agentPath =
|
|
10256
|
+
const agentPath = join35(agentsDir, entry.name);
|
|
10054
10257
|
const agentName = basename2(entry.name, ".md");
|
|
10055
10258
|
try {
|
|
10056
10259
|
const content = readFileSync17(agentPath, "utf-8");
|
|
@@ -10080,7 +10283,7 @@ function loadAgentsFromDir(agentsDir, scope) {
|
|
|
10080
10283
|
return agents;
|
|
10081
10284
|
}
|
|
10082
10285
|
function loadUserAgents() {
|
|
10083
|
-
const userAgentsDir =
|
|
10286
|
+
const userAgentsDir = join35(homedir14(), ".claude", "agents");
|
|
10084
10287
|
const agents = loadAgentsFromDir(userAgentsDir, "user");
|
|
10085
10288
|
const result = {};
|
|
10086
10289
|
for (const agent of agents) {
|
|
@@ -10089,7 +10292,7 @@ function loadUserAgents() {
|
|
|
10089
10292
|
return result;
|
|
10090
10293
|
}
|
|
10091
10294
|
function loadProjectAgents() {
|
|
10092
|
-
const projectAgentsDir =
|
|
10295
|
+
const projectAgentsDir = join35(process.cwd(), ".claude", "agents");
|
|
10093
10296
|
const agents = loadAgentsFromDir(projectAgentsDir, "project");
|
|
10094
10297
|
const result = {};
|
|
10095
10298
|
for (const agent of agents) {
|
|
@@ -10099,8 +10302,8 @@ function loadProjectAgents() {
|
|
|
10099
10302
|
}
|
|
10100
10303
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
10101
10304
|
import { existsSync as existsSync26 } from "fs";
|
|
10102
|
-
import { homedir as
|
|
10103
|
-
import { join as
|
|
10305
|
+
import { homedir as homedir15 } from "os";
|
|
10306
|
+
import { join as join36 } from "path";
|
|
10104
10307
|
|
|
10105
10308
|
// src/features/claude-code-mcp-loader/env-expander.ts
|
|
10106
10309
|
function expandEnvVars(value) {
|
|
@@ -10166,12 +10369,12 @@ function transformMcpServer(name, server) {
|
|
|
10166
10369
|
|
|
10167
10370
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
10168
10371
|
function getMcpConfigPaths() {
|
|
10169
|
-
const home =
|
|
10372
|
+
const home = homedir15();
|
|
10170
10373
|
const cwd = process.cwd();
|
|
10171
10374
|
return [
|
|
10172
|
-
{ path:
|
|
10173
|
-
{ path:
|
|
10174
|
-
{ path:
|
|
10375
|
+
{ path: join36(home, ".claude", ".mcp.json"), scope: "user" },
|
|
10376
|
+
{ path: join36(cwd, ".mcp.json"), scope: "project" },
|
|
10377
|
+
{ path: join36(cwd, ".claude", ".mcp.json"), scope: "local" }
|
|
10175
10378
|
];
|
|
10176
10379
|
}
|
|
10177
10380
|
async function loadMcpConfigFile(filePath) {
|
|
@@ -10190,13 +10393,13 @@ async function loadMcpConfigs() {
|
|
|
10190
10393
|
const servers = {};
|
|
10191
10394
|
const loadedServers = [];
|
|
10192
10395
|
const paths = getMcpConfigPaths();
|
|
10193
|
-
for (const { path:
|
|
10194
|
-
const config = await loadMcpConfigFile(
|
|
10396
|
+
for (const { path: path7, scope } of paths) {
|
|
10397
|
+
const config = await loadMcpConfigFile(path7);
|
|
10195
10398
|
if (!config?.mcpServers)
|
|
10196
10399
|
continue;
|
|
10197
10400
|
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
10198
10401
|
if (serverConfig.disabled) {
|
|
10199
|
-
log(`Skipping disabled MCP server "${name}"`, { path:
|
|
10402
|
+
log(`Skipping disabled MCP server "${name}"`, { path: path7 });
|
|
10200
10403
|
continue;
|
|
10201
10404
|
}
|
|
10202
10405
|
try {
|
|
@@ -10207,7 +10410,7 @@ async function loadMcpConfigs() {
|
|
|
10207
10410
|
loadedServers.splice(existingIndex, 1);
|
|
10208
10411
|
}
|
|
10209
10412
|
loadedServers.push({ name, scope, config: transformed });
|
|
10210
|
-
log(`Loaded MCP server "${name}" from ${scope}`, { path:
|
|
10413
|
+
log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
|
|
10211
10414
|
} catch (error) {
|
|
10212
10415
|
log(`Failed to transform MCP server "${name}"`, error);
|
|
10213
10416
|
}
|
|
@@ -10404,13 +10607,13 @@ var EXT_TO_LANG = {
|
|
|
10404
10607
|
};
|
|
10405
10608
|
// src/tools/lsp/config.ts
|
|
10406
10609
|
import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
|
|
10407
|
-
import { join as
|
|
10408
|
-
import { homedir as
|
|
10409
|
-
function loadJsonFile(
|
|
10410
|
-
if (!existsSync27(
|
|
10610
|
+
import { join as join37 } from "path";
|
|
10611
|
+
import { homedir as homedir16 } from "os";
|
|
10612
|
+
function loadJsonFile(path7) {
|
|
10613
|
+
if (!existsSync27(path7))
|
|
10411
10614
|
return null;
|
|
10412
10615
|
try {
|
|
10413
|
-
return JSON.parse(readFileSync18(
|
|
10616
|
+
return JSON.parse(readFileSync18(path7, "utf-8"));
|
|
10414
10617
|
} catch {
|
|
10415
10618
|
return null;
|
|
10416
10619
|
}
|
|
@@ -10418,9 +10621,9 @@ function loadJsonFile(path6) {
|
|
|
10418
10621
|
function getConfigPaths2() {
|
|
10419
10622
|
const cwd = process.cwd();
|
|
10420
10623
|
return {
|
|
10421
|
-
project:
|
|
10422
|
-
user:
|
|
10423
|
-
opencode:
|
|
10624
|
+
project: join37(cwd, ".opencode", "oh-my-opencode.json"),
|
|
10625
|
+
user: join37(homedir16(), ".config", "opencode", "oh-my-opencode.json"),
|
|
10626
|
+
opencode: join37(homedir16(), ".config", "opencode", "opencode.json")
|
|
10424
10627
|
};
|
|
10425
10628
|
}
|
|
10426
10629
|
function loadAllConfigs() {
|
|
@@ -10513,7 +10716,7 @@ function isServerInstalled(command) {
|
|
|
10513
10716
|
const pathEnv = process.env.PATH || "";
|
|
10514
10717
|
const paths = pathEnv.split(":");
|
|
10515
10718
|
for (const p of paths) {
|
|
10516
|
-
if (existsSync27(
|
|
10719
|
+
if (existsSync27(join37(p, cmd))) {
|
|
10517
10720
|
return true;
|
|
10518
10721
|
}
|
|
10519
10722
|
}
|
|
@@ -12122,10 +12325,10 @@ function mergeDefs(...defs) {
|
|
|
12122
12325
|
function cloneDef(schema) {
|
|
12123
12326
|
return mergeDefs(schema._zod.def);
|
|
12124
12327
|
}
|
|
12125
|
-
function getElementAtPath(obj,
|
|
12126
|
-
if (!
|
|
12328
|
+
function getElementAtPath(obj, path7) {
|
|
12329
|
+
if (!path7)
|
|
12127
12330
|
return obj;
|
|
12128
|
-
return
|
|
12331
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
12129
12332
|
}
|
|
12130
12333
|
function promiseAllObject(promisesObj) {
|
|
12131
12334
|
const keys = Object.keys(promisesObj);
|
|
@@ -12484,11 +12687,11 @@ function aborted(x, startIndex = 0) {
|
|
|
12484
12687
|
}
|
|
12485
12688
|
return false;
|
|
12486
12689
|
}
|
|
12487
|
-
function prefixIssues(
|
|
12690
|
+
function prefixIssues(path7, issues) {
|
|
12488
12691
|
return issues.map((iss) => {
|
|
12489
12692
|
var _a;
|
|
12490
12693
|
(_a = iss).path ?? (_a.path = []);
|
|
12491
|
-
iss.path.unshift(
|
|
12694
|
+
iss.path.unshift(path7);
|
|
12492
12695
|
return iss;
|
|
12493
12696
|
});
|
|
12494
12697
|
}
|
|
@@ -12656,7 +12859,7 @@ function treeifyError(error, _mapper) {
|
|
|
12656
12859
|
return issue2.message;
|
|
12657
12860
|
};
|
|
12658
12861
|
const result = { errors: [] };
|
|
12659
|
-
const processError = (error2,
|
|
12862
|
+
const processError = (error2, path7 = []) => {
|
|
12660
12863
|
var _a, _b;
|
|
12661
12864
|
for (const issue2 of error2.issues) {
|
|
12662
12865
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -12666,7 +12869,7 @@ function treeifyError(error, _mapper) {
|
|
|
12666
12869
|
} else if (issue2.code === "invalid_element") {
|
|
12667
12870
|
processError({ issues: issue2.issues }, issue2.path);
|
|
12668
12871
|
} else {
|
|
12669
|
-
const fullpath = [...
|
|
12872
|
+
const fullpath = [...path7, ...issue2.path];
|
|
12670
12873
|
if (fullpath.length === 0) {
|
|
12671
12874
|
result.errors.push(mapper(issue2));
|
|
12672
12875
|
continue;
|
|
@@ -12698,8 +12901,8 @@ function treeifyError(error, _mapper) {
|
|
|
12698
12901
|
}
|
|
12699
12902
|
function toDotPath(_path) {
|
|
12700
12903
|
const segs = [];
|
|
12701
|
-
const
|
|
12702
|
-
for (const seg of
|
|
12904
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
12905
|
+
for (const seg of path7) {
|
|
12703
12906
|
if (typeof seg === "number")
|
|
12704
12907
|
segs.push(`[${seg}]`);
|
|
12705
12908
|
else if (typeof seg === "symbol")
|
|
@@ -24042,14 +24245,14 @@ var lsp_code_action_resolve = tool({
|
|
|
24042
24245
|
});
|
|
24043
24246
|
// src/tools/ast-grep/constants.ts
|
|
24044
24247
|
import { createRequire as createRequire4 } from "module";
|
|
24045
|
-
import { dirname as dirname6, join as
|
|
24248
|
+
import { dirname as dirname6, join as join39 } from "path";
|
|
24046
24249
|
import { existsSync as existsSync30, statSync as statSync4 } from "fs";
|
|
24047
24250
|
|
|
24048
24251
|
// src/tools/ast-grep/downloader.ts
|
|
24049
24252
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
24050
24253
|
import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
|
|
24051
|
-
import { join as
|
|
24052
|
-
import { homedir as
|
|
24254
|
+
import { join as join38 } from "path";
|
|
24255
|
+
import { homedir as homedir17 } from "os";
|
|
24053
24256
|
import { createRequire as createRequire3 } from "module";
|
|
24054
24257
|
var REPO2 = "ast-grep/ast-grep";
|
|
24055
24258
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -24074,18 +24277,18 @@ var PLATFORM_MAP2 = {
|
|
|
24074
24277
|
function getCacheDir3() {
|
|
24075
24278
|
if (process.platform === "win32") {
|
|
24076
24279
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
24077
|
-
const base2 = localAppData ||
|
|
24078
|
-
return
|
|
24280
|
+
const base2 = localAppData || join38(homedir17(), "AppData", "Local");
|
|
24281
|
+
return join38(base2, "oh-my-opencode", "bin");
|
|
24079
24282
|
}
|
|
24080
24283
|
const xdgCache2 = process.env.XDG_CACHE_HOME;
|
|
24081
|
-
const base = xdgCache2 ||
|
|
24082
|
-
return
|
|
24284
|
+
const base = xdgCache2 || join38(homedir17(), ".cache");
|
|
24285
|
+
return join38(base, "oh-my-opencode", "bin");
|
|
24083
24286
|
}
|
|
24084
24287
|
function getBinaryName3() {
|
|
24085
24288
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
24086
24289
|
}
|
|
24087
24290
|
function getCachedBinaryPath2() {
|
|
24088
|
-
const binaryPath =
|
|
24291
|
+
const binaryPath = join38(getCacheDir3(), getBinaryName3());
|
|
24089
24292
|
return existsSync29(binaryPath) ? binaryPath : null;
|
|
24090
24293
|
}
|
|
24091
24294
|
async function extractZip2(archivePath, destDir) {
|
|
@@ -24112,12 +24315,12 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
24112
24315
|
}
|
|
24113
24316
|
const cacheDir = getCacheDir3();
|
|
24114
24317
|
const binaryName = getBinaryName3();
|
|
24115
|
-
const binaryPath =
|
|
24318
|
+
const binaryPath = join38(cacheDir, binaryName);
|
|
24116
24319
|
if (existsSync29(binaryPath)) {
|
|
24117
24320
|
return binaryPath;
|
|
24118
24321
|
}
|
|
24119
|
-
const { arch, os:
|
|
24120
|
-
const assetName = `app-${arch}-${
|
|
24322
|
+
const { arch, os: os5 } = platformInfo;
|
|
24323
|
+
const assetName = `app-${arch}-${os5}.zip`;
|
|
24121
24324
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
24122
24325
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
24123
24326
|
try {
|
|
@@ -24128,7 +24331,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
24128
24331
|
if (!response2.ok) {
|
|
24129
24332
|
throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
|
|
24130
24333
|
}
|
|
24131
|
-
const archivePath =
|
|
24334
|
+
const archivePath = join38(cacheDir, assetName);
|
|
24132
24335
|
const arrayBuffer = await response2.arrayBuffer();
|
|
24133
24336
|
await Bun.write(archivePath, arrayBuffer);
|
|
24134
24337
|
await extractZip2(archivePath, cacheDir);
|
|
@@ -24186,7 +24389,7 @@ function findSgCliPathSync() {
|
|
|
24186
24389
|
const require2 = createRequire4(import.meta.url);
|
|
24187
24390
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
24188
24391
|
const cliDir = dirname6(cliPkgPath);
|
|
24189
|
-
const sgPath =
|
|
24392
|
+
const sgPath = join39(cliDir, binaryName);
|
|
24190
24393
|
if (existsSync30(sgPath) && isValidBinary(sgPath)) {
|
|
24191
24394
|
return sgPath;
|
|
24192
24395
|
}
|
|
@@ -24198,7 +24401,7 @@ function findSgCliPathSync() {
|
|
|
24198
24401
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
24199
24402
|
const pkgDir = dirname6(pkgPath);
|
|
24200
24403
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
24201
|
-
const binaryPath =
|
|
24404
|
+
const binaryPath = join39(pkgDir, astGrepName);
|
|
24202
24405
|
if (existsSync30(binaryPath) && isValidBinary(binaryPath)) {
|
|
24203
24406
|
return binaryPath;
|
|
24204
24407
|
}
|
|
@@ -24206,9 +24409,9 @@ function findSgCliPathSync() {
|
|
|
24206
24409
|
}
|
|
24207
24410
|
if (process.platform === "darwin") {
|
|
24208
24411
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
24209
|
-
for (const
|
|
24210
|
-
if (existsSync30(
|
|
24211
|
-
return
|
|
24412
|
+
for (const path7 of homebrewPaths) {
|
|
24413
|
+
if (existsSync30(path7) && isValidBinary(path7)) {
|
|
24414
|
+
return path7;
|
|
24212
24415
|
}
|
|
24213
24416
|
}
|
|
24214
24417
|
}
|
|
@@ -24226,8 +24429,8 @@ function getSgCliPath() {
|
|
|
24226
24429
|
}
|
|
24227
24430
|
return "sg";
|
|
24228
24431
|
}
|
|
24229
|
-
function setSgCliPath(
|
|
24230
|
-
resolvedCliPath2 =
|
|
24432
|
+
function setSgCliPath(path7) {
|
|
24433
|
+
resolvedCliPath2 = path7;
|
|
24231
24434
|
}
|
|
24232
24435
|
var SG_CLI_PATH = getSgCliPath();
|
|
24233
24436
|
var CLI_LANGUAGES = [
|
|
@@ -24574,19 +24777,19 @@ var {spawn: spawn7 } = globalThis.Bun;
|
|
|
24574
24777
|
|
|
24575
24778
|
// src/tools/grep/constants.ts
|
|
24576
24779
|
import { existsSync as existsSync33 } from "fs";
|
|
24577
|
-
import { join as
|
|
24780
|
+
import { join as join41, dirname as dirname7 } from "path";
|
|
24578
24781
|
import { spawnSync } from "child_process";
|
|
24579
24782
|
|
|
24580
24783
|
// src/tools/grep/downloader.ts
|
|
24581
24784
|
import { existsSync as existsSync32, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync9 } from "fs";
|
|
24582
|
-
import { join as
|
|
24785
|
+
import { join as join40 } from "path";
|
|
24583
24786
|
function getInstallDir() {
|
|
24584
24787
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
24585
|
-
return
|
|
24788
|
+
return join40(homeDir, ".cache", "oh-my-opencode", "bin");
|
|
24586
24789
|
}
|
|
24587
24790
|
function getRgPath() {
|
|
24588
24791
|
const isWindows2 = process.platform === "win32";
|
|
24589
|
-
return
|
|
24792
|
+
return join40(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
|
|
24590
24793
|
}
|
|
24591
24794
|
function getInstalledRipgrepPath() {
|
|
24592
24795
|
const rgPath = getRgPath();
|
|
@@ -24613,10 +24816,10 @@ function getOpenCodeBundledRg() {
|
|
|
24613
24816
|
const isWindows2 = process.platform === "win32";
|
|
24614
24817
|
const rgName = isWindows2 ? "rg.exe" : "rg";
|
|
24615
24818
|
const candidates = [
|
|
24616
|
-
|
|
24617
|
-
|
|
24618
|
-
|
|
24619
|
-
|
|
24819
|
+
join41(execDir, rgName),
|
|
24820
|
+
join41(execDir, "bin", rgName),
|
|
24821
|
+
join41(execDir, "..", "bin", rgName),
|
|
24822
|
+
join41(execDir, "..", "libexec", rgName)
|
|
24620
24823
|
];
|
|
24621
24824
|
for (const candidate of candidates) {
|
|
24622
24825
|
if (existsSync33(candidate)) {
|
|
@@ -25023,8 +25226,8 @@ var glob = tool({
|
|
|
25023
25226
|
});
|
|
25024
25227
|
// src/tools/slashcommand/tools.ts
|
|
25025
25228
|
import { existsSync as existsSync34, readdirSync as readdirSync10, readFileSync as readFileSync21 } from "fs";
|
|
25026
|
-
import { homedir as
|
|
25027
|
-
import { join as
|
|
25229
|
+
import { homedir as homedir18 } from "os";
|
|
25230
|
+
import { join as join42, basename as basename3, dirname as dirname8 } from "path";
|
|
25028
25231
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
25029
25232
|
if (!existsSync34(commandsDir)) {
|
|
25030
25233
|
return [];
|
|
@@ -25034,7 +25237,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
25034
25237
|
for (const entry of entries) {
|
|
25035
25238
|
if (!isMarkdownFile(entry))
|
|
25036
25239
|
continue;
|
|
25037
|
-
const commandPath =
|
|
25240
|
+
const commandPath = join42(commandsDir, entry.name);
|
|
25038
25241
|
const commandName = basename3(entry.name, ".md");
|
|
25039
25242
|
try {
|
|
25040
25243
|
const content = readFileSync21(commandPath, "utf-8");
|
|
@@ -25062,10 +25265,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
25062
25265
|
return commands;
|
|
25063
25266
|
}
|
|
25064
25267
|
function discoverCommandsSync() {
|
|
25065
|
-
const userCommandsDir =
|
|
25066
|
-
const projectCommandsDir =
|
|
25067
|
-
const opencodeGlobalDir =
|
|
25068
|
-
const opencodeProjectDir =
|
|
25268
|
+
const userCommandsDir = join42(homedir18(), ".claude", "commands");
|
|
25269
|
+
const projectCommandsDir = join42(process.cwd(), ".claude", "commands");
|
|
25270
|
+
const opencodeGlobalDir = join42(homedir18(), ".config", "opencode", "command");
|
|
25271
|
+
const opencodeProjectDir = join42(process.cwd(), ".opencode", "command");
|
|
25069
25272
|
const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
|
|
25070
25273
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
25071
25274
|
const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
|
|
@@ -25198,8 +25401,8 @@ var SkillFrontmatterSchema = exports_external.object({
|
|
|
25198
25401
|
});
|
|
25199
25402
|
// src/tools/skill/tools.ts
|
|
25200
25403
|
import { existsSync as existsSync35, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
25201
|
-
import { homedir as
|
|
25202
|
-
import { join as
|
|
25404
|
+
import { homedir as homedir19 } from "os";
|
|
25405
|
+
import { join as join43, basename as basename4 } from "path";
|
|
25203
25406
|
function parseSkillFrontmatter(data) {
|
|
25204
25407
|
return {
|
|
25205
25408
|
name: typeof data.name === "string" ? data.name : "",
|
|
@@ -25218,10 +25421,10 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
25218
25421
|
for (const entry of entries) {
|
|
25219
25422
|
if (entry.name.startsWith("."))
|
|
25220
25423
|
continue;
|
|
25221
|
-
const skillPath =
|
|
25424
|
+
const skillPath = join43(skillsDir, entry.name);
|
|
25222
25425
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
25223
25426
|
const resolvedPath = resolveSymlink(skillPath);
|
|
25224
|
-
const skillMdPath =
|
|
25427
|
+
const skillMdPath = join43(resolvedPath, "SKILL.md");
|
|
25225
25428
|
if (!existsSync35(skillMdPath))
|
|
25226
25429
|
continue;
|
|
25227
25430
|
try {
|
|
@@ -25240,8 +25443,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
25240
25443
|
return skills;
|
|
25241
25444
|
}
|
|
25242
25445
|
function discoverSkillsSync() {
|
|
25243
|
-
const userSkillsDir =
|
|
25244
|
-
const projectSkillsDir =
|
|
25446
|
+
const userSkillsDir = join43(homedir19(), ".claude", "skills");
|
|
25447
|
+
const projectSkillsDir = join43(process.cwd(), ".claude", "skills");
|
|
25245
25448
|
const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
|
|
25246
25449
|
const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
|
|
25247
25450
|
return [...projectSkills, ...userSkills];
|
|
@@ -25251,7 +25454,7 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
|
|
|
25251
25454
|
`);
|
|
25252
25455
|
async function parseSkillMd(skillPath) {
|
|
25253
25456
|
const resolvedPath = resolveSymlink(skillPath);
|
|
25254
|
-
const skillMdPath =
|
|
25457
|
+
const skillMdPath = join43(resolvedPath, "SKILL.md");
|
|
25255
25458
|
if (!existsSync35(skillMdPath)) {
|
|
25256
25459
|
return null;
|
|
25257
25460
|
}
|
|
@@ -25267,9 +25470,9 @@ async function parseSkillMd(skillPath) {
|
|
|
25267
25470
|
allowedTools: frontmatter2["allowed-tools"],
|
|
25268
25471
|
metadata: frontmatter2.metadata
|
|
25269
25472
|
};
|
|
25270
|
-
const referencesDir =
|
|
25271
|
-
const scriptsDir =
|
|
25272
|
-
const assetsDir =
|
|
25473
|
+
const referencesDir = join43(resolvedPath, "references");
|
|
25474
|
+
const scriptsDir = join43(resolvedPath, "scripts");
|
|
25475
|
+
const assetsDir = join43(resolvedPath, "assets");
|
|
25273
25476
|
const references = existsSync35(referencesDir) ? readdirSync11(referencesDir).filter((f) => !f.startsWith(".")) : [];
|
|
25274
25477
|
const scripts = existsSync35(scriptsDir) ? readdirSync11(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
|
|
25275
25478
|
const assets = existsSync35(assetsDir) ? readdirSync11(assetsDir).filter((f) => !f.startsWith(".")) : [];
|
|
@@ -25296,7 +25499,7 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
25296
25499
|
for (const entry of entries) {
|
|
25297
25500
|
if (entry.name.startsWith("."))
|
|
25298
25501
|
continue;
|
|
25299
|
-
const skillPath =
|
|
25502
|
+
const skillPath = join43(skillsDir, entry.name);
|
|
25300
25503
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
25301
25504
|
const skillInfo = await parseSkillMd(skillPath);
|
|
25302
25505
|
if (skillInfo) {
|
|
@@ -25307,8 +25510,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
25307
25510
|
return skills;
|
|
25308
25511
|
}
|
|
25309
25512
|
async function discoverSkills() {
|
|
25310
|
-
const userSkillsDir =
|
|
25311
|
-
const projectSkillsDir =
|
|
25513
|
+
const userSkillsDir = join43(homedir19(), ".claude", "skills");
|
|
25514
|
+
const projectSkillsDir = join43(process.cwd(), ".claude", "skills");
|
|
25312
25515
|
const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
|
|
25313
25516
|
const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
|
|
25314
25517
|
return [...projectSkills, ...userSkills];
|
|
@@ -25337,7 +25540,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
|
|
|
25337
25540
|
const referencesLoaded = [];
|
|
25338
25541
|
if (includeRefs && skill.references.length > 0) {
|
|
25339
25542
|
for (const ref of skill.references) {
|
|
25340
|
-
const refPath =
|
|
25543
|
+
const refPath = join43(skill.path, "references", ref);
|
|
25341
25544
|
try {
|
|
25342
25545
|
let content = readFileSync22(refPath, "utf-8");
|
|
25343
25546
|
content = await resolveCommandsInText(content);
|
|
@@ -25461,12 +25664,12 @@ async function findTmuxPath() {
|
|
|
25461
25664
|
return null;
|
|
25462
25665
|
}
|
|
25463
25666
|
const stdout = await new Response(proc.stdout).text();
|
|
25464
|
-
const
|
|
25667
|
+
const path7 = stdout.trim().split(`
|
|
25465
25668
|
`)[0];
|
|
25466
|
-
if (!
|
|
25669
|
+
if (!path7) {
|
|
25467
25670
|
return null;
|
|
25468
25671
|
}
|
|
25469
|
-
const verifyProc = spawn9([
|
|
25672
|
+
const verifyProc = spawn9([path7, "-V"], {
|
|
25470
25673
|
stdout: "pipe",
|
|
25471
25674
|
stderr: "pipe"
|
|
25472
25675
|
});
|
|
@@ -25474,7 +25677,7 @@ async function findTmuxPath() {
|
|
|
25474
25677
|
if (verifyExitCode !== 0) {
|
|
25475
25678
|
return null;
|
|
25476
25679
|
}
|
|
25477
|
-
return
|
|
25680
|
+
return path7;
|
|
25478
25681
|
} catch {
|
|
25479
25682
|
return null;
|
|
25480
25683
|
}
|
|
@@ -25487,9 +25690,9 @@ async function getTmuxPath() {
|
|
|
25487
25690
|
return initPromise3;
|
|
25488
25691
|
}
|
|
25489
25692
|
initPromise3 = (async () => {
|
|
25490
|
-
const
|
|
25491
|
-
tmuxPath =
|
|
25492
|
-
return
|
|
25693
|
+
const path7 = await findTmuxPath();
|
|
25694
|
+
tmuxPath = path7;
|
|
25695
|
+
return path7;
|
|
25493
25696
|
})();
|
|
25494
25697
|
return initPromise3;
|
|
25495
25698
|
}
|
|
@@ -25809,7 +26012,7 @@ function createBackgroundCancel(manager, client2) {
|
|
|
25809
26012
|
return `\u274C Invalid arguments: Either provide a taskId or set all=true to cancel all running tasks.`;
|
|
25810
26013
|
}
|
|
25811
26014
|
if (cancelAll) {
|
|
25812
|
-
const tasks = manager.
|
|
26015
|
+
const tasks = manager.getAllDescendantTasks(toolContext.sessionID);
|
|
25813
26016
|
const runningTasks = tasks.filter((t) => t.status === "running");
|
|
25814
26017
|
if (runningTasks.length === 0) {
|
|
25815
26018
|
return `\u2705 No running background tasks to cancel.`;
|
|
@@ -26104,15 +26307,15 @@ var builtinTools = {
|
|
|
26104
26307
|
};
|
|
26105
26308
|
// src/features/background-agent/manager.ts
|
|
26106
26309
|
import { existsSync as existsSync36, readdirSync as readdirSync12 } from "fs";
|
|
26107
|
-
import { join as
|
|
26310
|
+
import { join as join44 } from "path";
|
|
26108
26311
|
function getMessageDir4(sessionID) {
|
|
26109
26312
|
if (!existsSync36(MESSAGE_STORAGE))
|
|
26110
26313
|
return null;
|
|
26111
|
-
const directPath =
|
|
26314
|
+
const directPath = join44(MESSAGE_STORAGE, sessionID);
|
|
26112
26315
|
if (existsSync36(directPath))
|
|
26113
26316
|
return directPath;
|
|
26114
26317
|
for (const dir of readdirSync12(MESSAGE_STORAGE)) {
|
|
26115
|
-
const sessionPath =
|
|
26318
|
+
const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
|
|
26116
26319
|
if (existsSync36(sessionPath))
|
|
26117
26320
|
return sessionPath;
|
|
26118
26321
|
}
|
|
@@ -26204,6 +26407,16 @@ class BackgroundManager {
|
|
|
26204
26407
|
}
|
|
26205
26408
|
return result;
|
|
26206
26409
|
}
|
|
26410
|
+
getAllDescendantTasks(sessionID) {
|
|
26411
|
+
const result = [];
|
|
26412
|
+
const directChildren = this.getTasksByParentSession(sessionID);
|
|
26413
|
+
for (const child of directChildren) {
|
|
26414
|
+
result.push(child);
|
|
26415
|
+
const descendants = this.getAllDescendantTasks(child.sessionID);
|
|
26416
|
+
result.push(...descendants);
|
|
26417
|
+
}
|
|
26418
|
+
return result;
|
|
26419
|
+
}
|
|
26207
26420
|
findBySession(sessionID) {
|
|
26208
26421
|
for (const task of this.tasks.values()) {
|
|
26209
26422
|
if (task.sessionID === sessionID) {
|
|
@@ -26495,7 +26708,7 @@ var AgentPermissionSchema = exports_external.object({
|
|
|
26495
26708
|
external_directory: PermissionValue.optional()
|
|
26496
26709
|
});
|
|
26497
26710
|
var BuiltinAgentNameSchema = exports_external.enum([
|
|
26498
|
-
"
|
|
26711
|
+
"Sisyphus",
|
|
26499
26712
|
"oracle",
|
|
26500
26713
|
"librarian",
|
|
26501
26714
|
"explore",
|
|
@@ -26506,8 +26719,8 @@ var BuiltinAgentNameSchema = exports_external.enum([
|
|
|
26506
26719
|
var OverridableAgentNameSchema = exports_external.enum([
|
|
26507
26720
|
"build",
|
|
26508
26721
|
"plan",
|
|
26509
|
-
"
|
|
26510
|
-
"
|
|
26722
|
+
"Sisyphus",
|
|
26723
|
+
"Planner-Sisyphus",
|
|
26511
26724
|
"oracle",
|
|
26512
26725
|
"librarian",
|
|
26513
26726
|
"explore",
|
|
@@ -26553,8 +26766,8 @@ var AgentOverrideConfigSchema = exports_external.object({
|
|
|
26553
26766
|
var AgentOverridesSchema = exports_external.object({
|
|
26554
26767
|
build: AgentOverrideConfigSchema.optional(),
|
|
26555
26768
|
plan: AgentOverrideConfigSchema.optional(),
|
|
26556
|
-
|
|
26557
|
-
"
|
|
26769
|
+
Sisyphus: AgentOverrideConfigSchema.optional(),
|
|
26770
|
+
"Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
|
|
26558
26771
|
oracle: AgentOverrideConfigSchema.optional(),
|
|
26559
26772
|
librarian: AgentOverrideConfigSchema.optional(),
|
|
26560
26773
|
explore: AgentOverrideConfigSchema.optional(),
|
|
@@ -26569,9 +26782,14 @@ var ClaudeCodeConfigSchema = exports_external.object({
|
|
|
26569
26782
|
agents: exports_external.boolean().optional(),
|
|
26570
26783
|
hooks: exports_external.boolean().optional()
|
|
26571
26784
|
});
|
|
26572
|
-
var
|
|
26785
|
+
var SisyphusAgentConfigSchema = exports_external.object({
|
|
26573
26786
|
disabled: exports_external.boolean().optional()
|
|
26574
26787
|
});
|
|
26788
|
+
var ExperimentalConfigSchema = exports_external.object({
|
|
26789
|
+
aggressive_truncation: exports_external.boolean().optional(),
|
|
26790
|
+
empty_message_recovery: exports_external.boolean().optional(),
|
|
26791
|
+
auto_resume: exports_external.boolean().optional()
|
|
26792
|
+
});
|
|
26575
26793
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
26576
26794
|
$schema: exports_external.string().optional(),
|
|
26577
26795
|
disabled_mcps: exports_external.array(McpNameSchema).optional(),
|
|
@@ -26580,7 +26798,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
26580
26798
|
agents: AgentOverridesSchema.optional(),
|
|
26581
26799
|
claude_code: ClaudeCodeConfigSchema.optional(),
|
|
26582
26800
|
google_auth: exports_external.boolean().optional(),
|
|
26583
|
-
|
|
26801
|
+
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
|
26802
|
+
experimental: ExperimentalConfigSchema.optional()
|
|
26584
26803
|
});
|
|
26585
26804
|
// src/agents/plan-prompt.ts
|
|
26586
26805
|
var PLAN_SYSTEM_PROMPT = `<system-reminder>
|
|
@@ -26655,16 +26874,14 @@ var PLAN_PERMISSION = {
|
|
|
26655
26874
|
|
|
26656
26875
|
// src/index.ts
|
|
26657
26876
|
import * as fs6 from "fs";
|
|
26658
|
-
import * as
|
|
26659
|
-
import * as os4 from "os";
|
|
26660
|
-
function getUserConfigDir2() {
|
|
26661
|
-
if (process.platform === "win32") {
|
|
26662
|
-
return process.env.APPDATA || path6.join(os4.homedir(), "AppData", "Roaming");
|
|
26663
|
-
}
|
|
26664
|
-
return process.env.XDG_CONFIG_HOME || path6.join(os4.homedir(), ".config");
|
|
26665
|
-
}
|
|
26877
|
+
import * as path7 from "path";
|
|
26666
26878
|
var AGENT_NAME_MAP = {
|
|
26667
|
-
omo: "
|
|
26879
|
+
omo: "Sisyphus",
|
|
26880
|
+
OmO: "Sisyphus",
|
|
26881
|
+
"OmO-Plan": "Planner-Sisyphus",
|
|
26882
|
+
"omo-plan": "Planner-Sisyphus",
|
|
26883
|
+
sisyphus: "Sisyphus",
|
|
26884
|
+
"planner-sisyphus": "Planner-Sisyphus",
|
|
26668
26885
|
build: "build",
|
|
26669
26886
|
oracle: "oracle",
|
|
26670
26887
|
librarian: "librarian",
|
|
@@ -26673,32 +26890,63 @@ var AGENT_NAME_MAP = {
|
|
|
26673
26890
|
"document-writer": "document-writer",
|
|
26674
26891
|
"multimodal-looker": "multimodal-looker"
|
|
26675
26892
|
};
|
|
26676
|
-
function
|
|
26677
|
-
const
|
|
26893
|
+
function migrateAgentNames(agents) {
|
|
26894
|
+
const migrated = {};
|
|
26895
|
+
let changed = false;
|
|
26678
26896
|
for (const [key, value] of Object.entries(agents)) {
|
|
26679
|
-
const
|
|
26680
|
-
|
|
26897
|
+
const newKey = AGENT_NAME_MAP[key.toLowerCase()] ?? AGENT_NAME_MAP[key] ?? key;
|
|
26898
|
+
if (newKey !== key) {
|
|
26899
|
+
changed = true;
|
|
26900
|
+
}
|
|
26901
|
+
migrated[newKey] = value;
|
|
26681
26902
|
}
|
|
26682
|
-
return
|
|
26903
|
+
return { migrated, changed };
|
|
26904
|
+
}
|
|
26905
|
+
function migrateConfigFile(configPath, rawConfig) {
|
|
26906
|
+
let needsWrite = false;
|
|
26907
|
+
if (rawConfig.agents && typeof rawConfig.agents === "object") {
|
|
26908
|
+
const { migrated, changed } = migrateAgentNames(rawConfig.agents);
|
|
26909
|
+
if (changed) {
|
|
26910
|
+
rawConfig.agents = migrated;
|
|
26911
|
+
needsWrite = true;
|
|
26912
|
+
}
|
|
26913
|
+
}
|
|
26914
|
+
if (rawConfig.omo_agent) {
|
|
26915
|
+
rawConfig.sisyphus_agent = rawConfig.omo_agent;
|
|
26916
|
+
delete rawConfig.omo_agent;
|
|
26917
|
+
needsWrite = true;
|
|
26918
|
+
}
|
|
26919
|
+
if (needsWrite) {
|
|
26920
|
+
try {
|
|
26921
|
+
fs6.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + `
|
|
26922
|
+
`, "utf-8");
|
|
26923
|
+
log(`Migrated config file: ${configPath} (OmO \u2192 Sisyphus)`);
|
|
26924
|
+
} catch (err) {
|
|
26925
|
+
log(`Failed to write migrated config to ${configPath}:`, err);
|
|
26926
|
+
}
|
|
26927
|
+
}
|
|
26928
|
+
return needsWrite;
|
|
26683
26929
|
}
|
|
26684
26930
|
function loadConfigFromPath2(configPath) {
|
|
26685
26931
|
try {
|
|
26686
26932
|
if (fs6.existsSync(configPath)) {
|
|
26687
26933
|
const content = fs6.readFileSync(configPath, "utf-8");
|
|
26688
26934
|
const rawConfig = JSON.parse(content);
|
|
26689
|
-
|
|
26690
|
-
rawConfig.agents = normalizeAgentNames(rawConfig.agents);
|
|
26691
|
-
}
|
|
26935
|
+
migrateConfigFile(configPath, rawConfig);
|
|
26692
26936
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
26693
26937
|
if (!result.success) {
|
|
26938
|
+
const errorMsg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
26694
26939
|
log(`Config validation error in ${configPath}:`, result.error.issues);
|
|
26940
|
+
addConfigLoadError({ path: configPath, error: `Validation error: ${errorMsg}` });
|
|
26695
26941
|
return null;
|
|
26696
26942
|
}
|
|
26697
26943
|
log(`Config loaded from ${configPath}`, { agents: result.data.agents });
|
|
26698
26944
|
return result.data;
|
|
26699
26945
|
}
|
|
26700
26946
|
} catch (err) {
|
|
26947
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
26701
26948
|
log(`Error loading config from ${configPath}:`, err);
|
|
26949
|
+
addConfigLoadError({ path: configPath, error: errorMsg });
|
|
26702
26950
|
}
|
|
26703
26951
|
return null;
|
|
26704
26952
|
}
|
|
@@ -26729,8 +26977,8 @@ function mergeConfigs(base, override) {
|
|
|
26729
26977
|
};
|
|
26730
26978
|
}
|
|
26731
26979
|
function loadPluginConfig(directory) {
|
|
26732
|
-
const userConfigPath =
|
|
26733
|
-
const projectConfigPath =
|
|
26980
|
+
const userConfigPath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
|
|
26981
|
+
const projectConfigPath = path7.join(directory, ".opencode", "oh-my-opencode.json");
|
|
26734
26982
|
let config3 = loadConfigFromPath2(userConfigPath) ?? {};
|
|
26735
26983
|
const projectConfig = loadConfigFromPath2(projectConfigPath);
|
|
26736
26984
|
if (projectConfig) {
|
|
@@ -26751,7 +26999,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26751
26999
|
const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
|
|
26752
27000
|
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
|
|
26753
27001
|
const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
|
|
26754
|
-
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx) : null;
|
|
27002
|
+
const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
26755
27003
|
const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
|
|
26756
27004
|
if (sessionRecovery && todoContinuationEnforcer) {
|
|
26757
27005
|
sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
|
|
@@ -26766,10 +27014,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26766
27014
|
const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {
|
|
26767
27015
|
disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
|
|
26768
27016
|
});
|
|
26769
|
-
const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx) : null;
|
|
27017
|
+
const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx, { experimental: pluginConfig.experimental }) : null;
|
|
26770
27018
|
const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
|
|
26771
27019
|
const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx, {
|
|
26772
|
-
showStartupToast: isHookEnabled("startup-toast")
|
|
27020
|
+
showStartupToast: isHookEnabled("startup-toast"),
|
|
27021
|
+
isSisyphusEnabled: pluginConfig.sisyphus_agent?.disabled !== true
|
|
26773
27022
|
}) : null;
|
|
26774
27023
|
const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook() : null;
|
|
26775
27024
|
const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
|
|
@@ -26803,22 +27052,22 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
26803
27052
|
const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
|
|
26804
27053
|
const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
|
|
26805
27054
|
const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
|
|
26806
|
-
const
|
|
26807
|
-
if (
|
|
27055
|
+
const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
|
|
27056
|
+
if (isSisyphusEnabled && builtinAgents.Sisyphus) {
|
|
26808
27057
|
const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
|
|
26809
|
-
const
|
|
26810
|
-
const
|
|
27058
|
+
const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
|
|
27059
|
+
const plannerSisyphusBase = {
|
|
26811
27060
|
...planConfigWithoutName,
|
|
26812
27061
|
prompt: PLAN_SYSTEM_PROMPT,
|
|
26813
27062
|
permission: PLAN_PERMISSION,
|
|
26814
27063
|
description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
|
|
26815
27064
|
color: config3.agent?.plan?.color ?? "#6495ED"
|
|
26816
27065
|
};
|
|
26817
|
-
const
|
|
27066
|
+
const plannerSisyphusConfig = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
|
|
26818
27067
|
config3.agent = {
|
|
26819
|
-
|
|
26820
|
-
"
|
|
26821
|
-
...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "
|
|
27068
|
+
Sisyphus: builtinAgents.Sisyphus,
|
|
27069
|
+
"Planner-Sisyphus": plannerSisyphusConfig,
|
|
27070
|
+
...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
|
|
26822
27071
|
...userAgents,
|
|
26823
27072
|
...projectAgents,
|
|
26824
27073
|
...config3.agent,
|