oh-my-opencode 2.12.1 → 2.12.3
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 +32 -7
- package/README.ko.md +32 -7
- package/README.md +17 -8
- package/README.zh-cn.md +32 -7
- package/dist/agents/sisyphus-prompt-builder.d.ts +1 -0
- package/dist/cli/config-manager.d.ts +9 -0
- package/dist/cli/index.js +185 -81
- package/dist/features/claude-code-command-loader/loader.d.ts +5 -0
- package/dist/features/context-injector/collector.d.ts +11 -0
- package/dist/features/context-injector/collector.test.d.ts +1 -0
- package/dist/features/context-injector/index.d.ts +3 -0
- package/dist/features/context-injector/injector.d.ts +39 -0
- package/dist/features/context-injector/injector.test.d.ts +1 -0
- package/dist/features/context-injector/types.d.ts +83 -0
- package/dist/features/opencode-skill-loader/async-loader.d.ts +6 -0
- package/dist/features/opencode-skill-loader/async-loader.test.d.ts +1 -0
- package/dist/features/opencode-skill-loader/blocking.d.ts +2 -0
- package/dist/features/opencode-skill-loader/blocking.test.d.ts +1 -0
- package/dist/features/opencode-skill-loader/discover-worker.d.ts +1 -0
- package/dist/features/opencode-skill-loader/loader.d.ts +5 -0
- package/dist/features/opencode-skill-loader/types.d.ts +6 -0
- package/dist/features/skill-mcp-manager/env-cleaner.d.ts +2 -0
- package/dist/features/skill-mcp-manager/env-cleaner.test.d.ts +1 -0
- package/dist/features/skill-mcp-manager/manager.d.ts +8 -0
- package/dist/hooks/ralph-loop/types.d.ts +2 -0
- package/dist/hooks/session-recovery/index.d.ts +2 -0
- package/dist/hooks/session-recovery/index.test.d.ts +1 -0
- package/dist/hooks/tool-output-truncator.test.d.ts +1 -0
- package/dist/index.js +1176 -465
- package/dist/shared/frontmatter.d.ts +2 -0
- package/dist/shared/index.d.ts +3 -0
- package/dist/shared/opencode-config-dir.d.ts +19 -0
- package/dist/shared/opencode-config-dir.test.d.ts +1 -0
- package/dist/shared/opencode-version.d.ts +10 -0
- package/dist/shared/opencode-version.test.d.ts +1 -0
- package/dist/shared/permission-compat.d.ts +12 -0
- package/dist/shared/permission-compat.test.d.ts +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -549,7 +549,7 @@ var require_scan = __commonJS((exports, module) => {
|
|
|
549
549
|
|
|
550
550
|
// node_modules/picomatch/lib/parse.js
|
|
551
551
|
var require_parse = __commonJS((exports, module) => {
|
|
552
|
-
var
|
|
552
|
+
var constants2 = require_constants();
|
|
553
553
|
var utils = require_utils();
|
|
554
554
|
var {
|
|
555
555
|
MAX_LENGTH,
|
|
@@ -557,7 +557,7 @@ var require_parse = __commonJS((exports, module) => {
|
|
|
557
557
|
REGEX_NON_SPECIAL_CHARS,
|
|
558
558
|
REGEX_SPECIAL_CHARS_BACKREF,
|
|
559
559
|
REPLACEMENTS
|
|
560
|
-
} =
|
|
560
|
+
} = constants2;
|
|
561
561
|
var expandRange = (args, options) => {
|
|
562
562
|
if (typeof options.expandRange === "function") {
|
|
563
563
|
return options.expandRange(...args, options);
|
|
@@ -588,8 +588,8 @@ var require_parse = __commonJS((exports, module) => {
|
|
|
588
588
|
const bos = { type: "bos", value: "", output: opts.prepend || "" };
|
|
589
589
|
const tokens = [bos];
|
|
590
590
|
const capture = opts.capture ? "" : "?:";
|
|
591
|
-
const PLATFORM_CHARS =
|
|
592
|
-
const EXTGLOB_CHARS =
|
|
591
|
+
const PLATFORM_CHARS = constants2.globChars(opts.windows);
|
|
592
|
+
const EXTGLOB_CHARS = constants2.extglobChars(PLATFORM_CHARS);
|
|
593
593
|
const {
|
|
594
594
|
DOT_LITERAL,
|
|
595
595
|
PLUS_LITERAL,
|
|
@@ -1267,7 +1267,7 @@ var require_parse = __commonJS((exports, module) => {
|
|
|
1267
1267
|
NO_DOTS_SLASH,
|
|
1268
1268
|
STAR,
|
|
1269
1269
|
START_ANCHOR
|
|
1270
|
-
} =
|
|
1270
|
+
} = constants2.globChars(opts.windows);
|
|
1271
1271
|
const nodot = opts.dot ? NO_DOTS : NO_DOT;
|
|
1272
1272
|
const slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT;
|
|
1273
1273
|
const capture = opts.capture ? "" : "?:";
|
|
@@ -1325,7 +1325,7 @@ var require_picomatch = __commonJS((exports, module) => {
|
|
|
1325
1325
|
var scan = require_scan();
|
|
1326
1326
|
var parse3 = require_parse();
|
|
1327
1327
|
var utils = require_utils();
|
|
1328
|
-
var
|
|
1328
|
+
var constants2 = require_constants();
|
|
1329
1329
|
var isObject2 = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
1330
1330
|
var picomatch = (glob, options, returnState = false) => {
|
|
1331
1331
|
if (Array.isArray(glob)) {
|
|
@@ -1456,7 +1456,7 @@ var require_picomatch = __commonJS((exports, module) => {
|
|
|
1456
1456
|
return /$^/;
|
|
1457
1457
|
}
|
|
1458
1458
|
};
|
|
1459
|
-
picomatch.constants =
|
|
1459
|
+
picomatch.constants = constants2;
|
|
1460
1460
|
module.exports = picomatch;
|
|
1461
1461
|
});
|
|
1462
1462
|
|
|
@@ -7925,12 +7925,12 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
7925
7925
|
throw new Error(`Unknown format "${name}"`);
|
|
7926
7926
|
return f;
|
|
7927
7927
|
};
|
|
7928
|
-
function addFormats(ajv, list,
|
|
7928
|
+
function addFormats(ajv, list, fs10, exportName) {
|
|
7929
7929
|
var _a;
|
|
7930
7930
|
var _b;
|
|
7931
7931
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== undefined || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
|
|
7932
7932
|
for (const f of list)
|
|
7933
|
-
ajv.addFormat(f,
|
|
7933
|
+
ajv.addFormat(f, fs10[f]);
|
|
7934
7934
|
}
|
|
7935
7935
|
module.exports = exports = formatsPlugin;
|
|
7936
7936
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -7941,7 +7941,7 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
7941
7941
|
var require_windows = __commonJS((exports, module) => {
|
|
7942
7942
|
module.exports = isexe;
|
|
7943
7943
|
isexe.sync = sync;
|
|
7944
|
-
var
|
|
7944
|
+
var fs10 = __require("fs");
|
|
7945
7945
|
function checkPathExt(path7, options) {
|
|
7946
7946
|
var pathext = options.pathExt !== undefined ? options.pathExt : process.env.PATHEXT;
|
|
7947
7947
|
if (!pathext) {
|
|
@@ -7966,12 +7966,12 @@ var require_windows = __commonJS((exports, module) => {
|
|
|
7966
7966
|
return checkPathExt(path7, options);
|
|
7967
7967
|
}
|
|
7968
7968
|
function isexe(path7, options, cb) {
|
|
7969
|
-
|
|
7969
|
+
fs10.stat(path7, function(er, stat2) {
|
|
7970
7970
|
cb(er, er ? false : checkStat(stat2, path7, options));
|
|
7971
7971
|
});
|
|
7972
7972
|
}
|
|
7973
7973
|
function sync(path7, options) {
|
|
7974
|
-
return checkStat(
|
|
7974
|
+
return checkStat(fs10.statSync(path7), path7, options);
|
|
7975
7975
|
}
|
|
7976
7976
|
});
|
|
7977
7977
|
|
|
@@ -7979,14 +7979,14 @@ var require_windows = __commonJS((exports, module) => {
|
|
|
7979
7979
|
var require_mode = __commonJS((exports, module) => {
|
|
7980
7980
|
module.exports = isexe;
|
|
7981
7981
|
isexe.sync = sync;
|
|
7982
|
-
var
|
|
7982
|
+
var fs10 = __require("fs");
|
|
7983
7983
|
function isexe(path7, options, cb) {
|
|
7984
|
-
|
|
7984
|
+
fs10.stat(path7, function(er, stat2) {
|
|
7985
7985
|
cb(er, er ? false : checkStat(stat2, options));
|
|
7986
7986
|
});
|
|
7987
7987
|
}
|
|
7988
7988
|
function sync(path7, options) {
|
|
7989
|
-
return checkStat(
|
|
7989
|
+
return checkStat(fs10.statSync(path7), options);
|
|
7990
7990
|
}
|
|
7991
7991
|
function checkStat(stat2, options) {
|
|
7992
7992
|
return stat2.isFile() && checkMode(stat2, options);
|
|
@@ -8008,7 +8008,7 @@ var require_mode = __commonJS((exports, module) => {
|
|
|
8008
8008
|
|
|
8009
8009
|
// node_modules/isexe/index.js
|
|
8010
8010
|
var require_isexe = __commonJS((exports, module) => {
|
|
8011
|
-
var
|
|
8011
|
+
var fs10 = __require("fs");
|
|
8012
8012
|
var core3;
|
|
8013
8013
|
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
8014
8014
|
core3 = require_windows();
|
|
@@ -8247,16 +8247,16 @@ var require_shebang_command = __commonJS((exports, module) => {
|
|
|
8247
8247
|
|
|
8248
8248
|
// node_modules/cross-spawn/lib/util/readShebang.js
|
|
8249
8249
|
var require_readShebang = __commonJS((exports, module) => {
|
|
8250
|
-
var
|
|
8250
|
+
var fs10 = __require("fs");
|
|
8251
8251
|
var shebangCommand = require_shebang_command();
|
|
8252
8252
|
function readShebang(command) {
|
|
8253
8253
|
const size = 150;
|
|
8254
8254
|
const buffer = Buffer.alloc(size);
|
|
8255
8255
|
let fd;
|
|
8256
8256
|
try {
|
|
8257
|
-
fd =
|
|
8258
|
-
|
|
8259
|
-
|
|
8257
|
+
fd = fs10.openSync(command, "r");
|
|
8258
|
+
fs10.readSync(fd, buffer, 0, size, 0);
|
|
8259
|
+
fs10.closeSync(fd);
|
|
8260
8260
|
} catch (e) {}
|
|
8261
8261
|
return shebangCommand(buffer.toString());
|
|
8262
8262
|
}
|
|
@@ -8571,29 +8571,21 @@ function getMessageDir(sessionID) {
|
|
|
8571
8571
|
}
|
|
8572
8572
|
return null;
|
|
8573
8573
|
}
|
|
8574
|
-
function isAbortError(error) {
|
|
8575
|
-
if (!error)
|
|
8576
|
-
return false;
|
|
8577
|
-
if (typeof error === "object") {
|
|
8578
|
-
const errObj = error;
|
|
8579
|
-
const name = errObj.name;
|
|
8580
|
-
const message = errObj.message?.toLowerCase() ?? "";
|
|
8581
|
-
if (name === "MessageAbortedError" || name === "AbortError")
|
|
8582
|
-
return true;
|
|
8583
|
-
if (name === "DOMException" && message.includes("abort"))
|
|
8584
|
-
return true;
|
|
8585
|
-
if (message.includes("aborted") || message.includes("cancelled") || message.includes("interrupted"))
|
|
8586
|
-
return true;
|
|
8587
|
-
}
|
|
8588
|
-
if (typeof error === "string") {
|
|
8589
|
-
const lower = error.toLowerCase();
|
|
8590
|
-
return lower.includes("abort") || lower.includes("cancel") || lower.includes("interrupt");
|
|
8591
|
-
}
|
|
8592
|
-
return false;
|
|
8593
|
-
}
|
|
8594
8574
|
function getIncompleteCount(todos) {
|
|
8595
8575
|
return todos.filter((t) => t.status !== "completed" && t.status !== "cancelled").length;
|
|
8596
8576
|
}
|
|
8577
|
+
function isLastAssistantMessageAborted(messages) {
|
|
8578
|
+
if (!messages || messages.length === 0)
|
|
8579
|
+
return false;
|
|
8580
|
+
const assistantMessages = messages.filter((m) => m.info?.role === "assistant");
|
|
8581
|
+
if (assistantMessages.length === 0)
|
|
8582
|
+
return false;
|
|
8583
|
+
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
8584
|
+
const errorName = lastAssistant.info?.error?.name;
|
|
8585
|
+
if (!errorName)
|
|
8586
|
+
return false;
|
|
8587
|
+
return errorName === "MessageAbortedError" || errorName === "AbortError";
|
|
8588
|
+
}
|
|
8597
8589
|
function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
8598
8590
|
const { backgroundManager } = options;
|
|
8599
8591
|
const sessions = new Map;
|
|
@@ -8726,11 +8718,8 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8726
8718
|
const sessionID = props?.sessionID;
|
|
8727
8719
|
if (!sessionID)
|
|
8728
8720
|
return;
|
|
8729
|
-
const state2 = getState(sessionID);
|
|
8730
|
-
const isAbort = isAbortError(props?.error);
|
|
8731
|
-
state2.lastEventWasAbortError = isAbort;
|
|
8732
8721
|
cancelCountdown(sessionID);
|
|
8733
|
-
log(`[${HOOK_NAME}] session.error`, { sessionID
|
|
8722
|
+
log(`[${HOOK_NAME}] session.error`, { sessionID });
|
|
8734
8723
|
return;
|
|
8735
8724
|
}
|
|
8736
8725
|
if (event.type === "session.idle") {
|
|
@@ -8750,16 +8739,24 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8750
8739
|
log(`[${HOOK_NAME}] Skipped: in recovery`, { sessionID });
|
|
8751
8740
|
return;
|
|
8752
8741
|
}
|
|
8753
|
-
if (state2.lastEventWasAbortError) {
|
|
8754
|
-
state2.lastEventWasAbortError = false;
|
|
8755
|
-
log(`[${HOOK_NAME}] Skipped: abort error immediately before idle`, { sessionID });
|
|
8756
|
-
return;
|
|
8757
|
-
}
|
|
8758
8742
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
|
|
8759
8743
|
if (hasRunningBgTasks) {
|
|
8760
8744
|
log(`[${HOOK_NAME}] Skipped: background tasks running`, { sessionID });
|
|
8761
8745
|
return;
|
|
8762
8746
|
}
|
|
8747
|
+
try {
|
|
8748
|
+
const messagesResp = await ctx.client.session.messages({
|
|
8749
|
+
path: { id: sessionID },
|
|
8750
|
+
query: { directory: ctx.directory }
|
|
8751
|
+
});
|
|
8752
|
+
const messages = messagesResp.data ?? [];
|
|
8753
|
+
if (isLastAssistantMessageAborted(messages)) {
|
|
8754
|
+
log(`[${HOOK_NAME}] Skipped: last assistant message was aborted`, { sessionID });
|
|
8755
|
+
return;
|
|
8756
|
+
}
|
|
8757
|
+
} catch (err) {
|
|
8758
|
+
log(`[${HOOK_NAME}] Messages fetch failed, continuing`, { sessionID, error: String(err) });
|
|
8759
|
+
}
|
|
8763
8760
|
let todos = [];
|
|
8764
8761
|
try {
|
|
8765
8762
|
const response = await ctx.client.session.todo({ path: { id: sessionID } });
|
|
@@ -8786,11 +8783,8 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8786
8783
|
const role = info?.role;
|
|
8787
8784
|
if (!sessionID)
|
|
8788
8785
|
return;
|
|
8789
|
-
const state2 = sessions.get(sessionID);
|
|
8790
|
-
if (state2) {
|
|
8791
|
-
state2.lastEventWasAbortError = false;
|
|
8792
|
-
}
|
|
8793
8786
|
if (role === "user") {
|
|
8787
|
+
const state2 = sessions.get(sessionID);
|
|
8794
8788
|
if (state2?.countdownStartedAt) {
|
|
8795
8789
|
const elapsed = Date.now() - state2.countdownStartedAt;
|
|
8796
8790
|
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
|
|
@@ -8799,7 +8793,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8799
8793
|
}
|
|
8800
8794
|
}
|
|
8801
8795
|
cancelCountdown(sessionID);
|
|
8802
|
-
log(`[${HOOK_NAME}] User message: cleared abort state`, { sessionID });
|
|
8803
8796
|
}
|
|
8804
8797
|
if (role === "assistant") {
|
|
8805
8798
|
cancelCountdown(sessionID);
|
|
@@ -8811,10 +8804,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8811
8804
|
const sessionID = info?.sessionID;
|
|
8812
8805
|
const role = info?.role;
|
|
8813
8806
|
if (sessionID && role === "assistant") {
|
|
8814
|
-
const state2 = sessions.get(sessionID);
|
|
8815
|
-
if (state2) {
|
|
8816
|
-
state2.lastEventWasAbortError = false;
|
|
8817
|
-
}
|
|
8818
8807
|
cancelCountdown(sessionID);
|
|
8819
8808
|
}
|
|
8820
8809
|
return;
|
|
@@ -8822,10 +8811,6 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8822
8811
|
if (event.type === "tool.execute.before" || event.type === "tool.execute.after") {
|
|
8823
8812
|
const sessionID = props?.sessionID;
|
|
8824
8813
|
if (sessionID) {
|
|
8825
|
-
const state2 = sessions.get(sessionID);
|
|
8826
|
-
if (state2) {
|
|
8827
|
-
state2.lastEventWasAbortError = false;
|
|
8828
|
-
}
|
|
8829
8814
|
cancelCountdown(sessionID);
|
|
8830
8815
|
}
|
|
8831
8816
|
return;
|
|
@@ -8847,7 +8832,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8847
8832
|
}
|
|
8848
8833
|
// src/hooks/context-window-monitor.ts
|
|
8849
8834
|
var ANTHROPIC_DISPLAY_LIMIT = 1e6;
|
|
8850
|
-
var ANTHROPIC_ACTUAL_LIMIT = 200000;
|
|
8835
|
+
var ANTHROPIC_ACTUAL_LIMIT = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
|
|
8851
8836
|
var CONTEXT_WARNING_THRESHOLD = 0.7;
|
|
8852
8837
|
var CONTEXT_REMINDER = `[SYSTEM REMINDER - 1M Context Window]
|
|
8853
8838
|
|
|
@@ -9650,7 +9635,7 @@ function detectErrorType(error) {
|
|
|
9650
9635
|
if (message.includes("tool_use") && message.includes("tool_result")) {
|
|
9651
9636
|
return "tool_result_missing";
|
|
9652
9637
|
}
|
|
9653
|
-
if (message.includes("thinking") && (message.includes("first block") || message.includes("must start with") || message.includes("preceeding") || message.includes("expected") && message.includes("found"))) {
|
|
9638
|
+
if (message.includes("thinking") && (message.includes("first block") || message.includes("must start with") || message.includes("preceeding") || message.includes("final block") || message.includes("cannot be thinking") || message.includes("expected") && message.includes("found"))) {
|
|
9654
9639
|
return "thinking_block_order";
|
|
9655
9640
|
}
|
|
9656
9641
|
if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
|
|
@@ -10210,7 +10195,7 @@ ${result.message}`;
|
|
|
10210
10195
|
}
|
|
10211
10196
|
}
|
|
10212
10197
|
// src/shared/dynamic-truncator.ts
|
|
10213
|
-
var ANTHROPIC_ACTUAL_LIMIT2 = 200000;
|
|
10198
|
+
var ANTHROPIC_ACTUAL_LIMIT2 = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
|
|
10214
10199
|
var CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
10215
10200
|
var DEFAULT_TARGET_MAX_TOKENS = 50000;
|
|
10216
10201
|
function estimateTokens(text) {
|
|
@@ -10319,6 +10304,8 @@ function createDynamicTruncator(ctx) {
|
|
|
10319
10304
|
}
|
|
10320
10305
|
|
|
10321
10306
|
// src/hooks/tool-output-truncator.ts
|
|
10307
|
+
var DEFAULT_MAX_TOKENS = 50000;
|
|
10308
|
+
var WEBFETCH_MAX_TOKENS = 1e4;
|
|
10322
10309
|
var TRUNCATABLE_TOOLS = [
|
|
10323
10310
|
"grep",
|
|
10324
10311
|
"Grep",
|
|
@@ -10337,6 +10324,10 @@ var TRUNCATABLE_TOOLS = [
|
|
|
10337
10324
|
"webfetch",
|
|
10338
10325
|
"WebFetch"
|
|
10339
10326
|
];
|
|
10327
|
+
var TOOL_SPECIFIC_MAX_TOKENS = {
|
|
10328
|
+
webfetch: WEBFETCH_MAX_TOKENS,
|
|
10329
|
+
WebFetch: WEBFETCH_MAX_TOKENS
|
|
10330
|
+
};
|
|
10340
10331
|
function createToolOutputTruncatorHook(ctx, options) {
|
|
10341
10332
|
const truncator = createDynamicTruncator(ctx);
|
|
10342
10333
|
const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? false;
|
|
@@ -10344,7 +10335,8 @@ function createToolOutputTruncatorHook(ctx, options) {
|
|
|
10344
10335
|
if (!truncateAll && !TRUNCATABLE_TOOLS.includes(input.tool))
|
|
10345
10336
|
return;
|
|
10346
10337
|
try {
|
|
10347
|
-
const
|
|
10338
|
+
const targetMaxTokens = TOOL_SPECIFIC_MAX_TOKENS[input.tool] ?? DEFAULT_MAX_TOKENS;
|
|
10339
|
+
const { result, truncated } = await truncator.truncate(input.sessionID, output.output, { targetMaxTokens });
|
|
10348
10340
|
if (truncated) {
|
|
10349
10341
|
output.output = result;
|
|
10350
10342
|
}
|
|
@@ -12077,7 +12069,7 @@ var COMPACTION_COOLDOWN_MS = 60000;
|
|
|
12077
12069
|
|
|
12078
12070
|
// src/hooks/preemptive-compaction/index.ts
|
|
12079
12071
|
var CLAUDE_MODEL_PATTERN = /claude-(opus|sonnet|haiku)/i;
|
|
12080
|
-
var CLAUDE_DEFAULT_CONTEXT_LIMIT = 200000;
|
|
12072
|
+
var CLAUDE_DEFAULT_CONTEXT_LIMIT = process.env.ANTHROPIC_1M_CONTEXT === "true" || process.env.VERTEX_ANTHROPIC_1M_CONTEXT === "true" ? 1e6 : 200000;
|
|
12081
12073
|
function isSupportedModel(modelID) {
|
|
12082
12074
|
return CLAUDE_MODEL_PATTERN.test(modelID);
|
|
12083
12075
|
}
|
|
@@ -12410,6 +12402,16 @@ function extractPromptText(parts) {
|
|
|
12410
12402
|
}
|
|
12411
12403
|
|
|
12412
12404
|
// src/hooks/think-mode/switcher.ts
|
|
12405
|
+
function extractModelPrefix(modelID) {
|
|
12406
|
+
const slashIndex = modelID.indexOf("/");
|
|
12407
|
+
if (slashIndex === -1) {
|
|
12408
|
+
return { prefix: "", base: modelID };
|
|
12409
|
+
}
|
|
12410
|
+
return {
|
|
12411
|
+
prefix: modelID.slice(0, slashIndex + 1),
|
|
12412
|
+
base: modelID.slice(slashIndex + 1)
|
|
12413
|
+
};
|
|
12414
|
+
}
|
|
12413
12415
|
function normalizeModelID(modelID) {
|
|
12414
12416
|
return modelID.replace(/\.(\d+)/g, "-$1");
|
|
12415
12417
|
}
|
|
@@ -12495,20 +12497,27 @@ var THINKING_CAPABLE_MODELS = {
|
|
|
12495
12497
|
};
|
|
12496
12498
|
function getHighVariant(modelID) {
|
|
12497
12499
|
const normalized = normalizeModelID(modelID);
|
|
12498
|
-
|
|
12500
|
+
const { prefix, base } = extractModelPrefix(normalized);
|
|
12501
|
+
if (ALREADY_HIGH.has(base) || base.endsWith("-high")) {
|
|
12502
|
+
return null;
|
|
12503
|
+
}
|
|
12504
|
+
const highBase = HIGH_VARIANT_MAP[base];
|
|
12505
|
+
if (!highBase) {
|
|
12499
12506
|
return null;
|
|
12500
12507
|
}
|
|
12501
|
-
return
|
|
12508
|
+
return prefix + highBase;
|
|
12502
12509
|
}
|
|
12503
12510
|
function isAlreadyHighVariant(modelID) {
|
|
12504
12511
|
const normalized = normalizeModelID(modelID);
|
|
12505
|
-
|
|
12512
|
+
const { base } = extractModelPrefix(normalized);
|
|
12513
|
+
return ALREADY_HIGH.has(base) || base.endsWith("-high");
|
|
12506
12514
|
}
|
|
12507
12515
|
function isThinkingProvider(provider) {
|
|
12508
12516
|
return provider in THINKING_CONFIGS;
|
|
12509
12517
|
}
|
|
12510
12518
|
function getThinkingConfig(providerID, modelID) {
|
|
12511
12519
|
const normalized = normalizeModelID(modelID);
|
|
12520
|
+
const { base } = extractModelPrefix(normalized);
|
|
12512
12521
|
if (isAlreadyHighVariant(normalized)) {
|
|
12513
12522
|
return null;
|
|
12514
12523
|
}
|
|
@@ -12518,8 +12527,8 @@ function getThinkingConfig(providerID, modelID) {
|
|
|
12518
12527
|
}
|
|
12519
12528
|
const config = THINKING_CONFIGS[resolvedProvider];
|
|
12520
12529
|
const capablePatterns = THINKING_CAPABLE_MODELS[resolvedProvider];
|
|
12521
|
-
const
|
|
12522
|
-
const isCapable = capablePatterns.some((pattern) =>
|
|
12530
|
+
const baseLower = base.toLowerCase();
|
|
12531
|
+
const isCapable = capablePatterns.some((pattern) => baseLower.includes(pattern.toLowerCase()));
|
|
12523
12532
|
return isCapable ? config : null;
|
|
12524
12533
|
}
|
|
12525
12534
|
|
|
@@ -15213,16 +15222,16 @@ function parseFrontmatter(content) {
|
|
|
15213
15222
|
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n?---\r?\n([\s\S]*)$/;
|
|
15214
15223
|
const match = content.match(frontmatterRegex);
|
|
15215
15224
|
if (!match) {
|
|
15216
|
-
return { data: {}, body: content };
|
|
15225
|
+
return { data: {}, body: content, hadFrontmatter: false, parseError: false };
|
|
15217
15226
|
}
|
|
15218
15227
|
const yamlContent = match[1];
|
|
15219
15228
|
const body = match[2];
|
|
15220
15229
|
try {
|
|
15221
15230
|
const parsed = jsYaml.load(yamlContent, { schema: jsYaml.JSON_SCHEMA });
|
|
15222
15231
|
const data = parsed ?? {};
|
|
15223
|
-
return { data, body };
|
|
15232
|
+
return { data, body, hadFrontmatter: true, parseError: false };
|
|
15224
15233
|
} catch {
|
|
15225
|
-
return { data: {}, body };
|
|
15234
|
+
return { data: {}, body, hadFrontmatter: true, parseError: true };
|
|
15226
15235
|
}
|
|
15227
15236
|
}
|
|
15228
15237
|
// src/shared/command-executor.ts
|
|
@@ -16535,6 +16544,167 @@ function migrateConfigFile(configPath, rawConfig) {
|
|
|
16535
16544
|
}
|
|
16536
16545
|
return needsWrite;
|
|
16537
16546
|
}
|
|
16547
|
+
// src/shared/opencode-config-dir.ts
|
|
16548
|
+
import { existsSync as existsSync21 } from "fs";
|
|
16549
|
+
import { homedir as homedir6 } from "os";
|
|
16550
|
+
import { join as join26 } from "path";
|
|
16551
|
+
var TAURI_APP_IDENTIFIER = "ai.opencode.desktop";
|
|
16552
|
+
var TAURI_APP_IDENTIFIER_DEV = "ai.opencode.desktop.dev";
|
|
16553
|
+
function isDevBuild(version) {
|
|
16554
|
+
if (!version)
|
|
16555
|
+
return false;
|
|
16556
|
+
return version.includes("-dev") || version.includes(".dev");
|
|
16557
|
+
}
|
|
16558
|
+
function getTauriConfigDir(identifier) {
|
|
16559
|
+
const platform2 = process.platform;
|
|
16560
|
+
switch (platform2) {
|
|
16561
|
+
case "darwin":
|
|
16562
|
+
return join26(homedir6(), "Library", "Application Support", identifier);
|
|
16563
|
+
case "win32": {
|
|
16564
|
+
const appData = process.env.APPDATA || join26(homedir6(), "AppData", "Roaming");
|
|
16565
|
+
return join26(appData, identifier);
|
|
16566
|
+
}
|
|
16567
|
+
case "linux":
|
|
16568
|
+
default: {
|
|
16569
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join26(homedir6(), ".config");
|
|
16570
|
+
return join26(xdgConfig, identifier);
|
|
16571
|
+
}
|
|
16572
|
+
}
|
|
16573
|
+
}
|
|
16574
|
+
function getCliConfigDir() {
|
|
16575
|
+
if (process.platform === "win32") {
|
|
16576
|
+
const crossPlatformDir = join26(homedir6(), ".config", "opencode");
|
|
16577
|
+
const crossPlatformConfig = join26(crossPlatformDir, "opencode.json");
|
|
16578
|
+
if (existsSync21(crossPlatformConfig)) {
|
|
16579
|
+
return crossPlatformDir;
|
|
16580
|
+
}
|
|
16581
|
+
const appData = process.env.APPDATA || join26(homedir6(), "AppData", "Roaming");
|
|
16582
|
+
const appdataDir = join26(appData, "opencode");
|
|
16583
|
+
const appdataConfig = join26(appdataDir, "opencode.json");
|
|
16584
|
+
if (existsSync21(appdataConfig)) {
|
|
16585
|
+
return appdataDir;
|
|
16586
|
+
}
|
|
16587
|
+
return crossPlatformDir;
|
|
16588
|
+
}
|
|
16589
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || join26(homedir6(), ".config");
|
|
16590
|
+
return join26(xdgConfig, "opencode");
|
|
16591
|
+
}
|
|
16592
|
+
function getOpenCodeConfigDir(options) {
|
|
16593
|
+
const { binary: binary2, version, checkExisting = true } = options;
|
|
16594
|
+
if (binary2 === "opencode") {
|
|
16595
|
+
return getCliConfigDir();
|
|
16596
|
+
}
|
|
16597
|
+
const identifier = isDevBuild(version) ? TAURI_APP_IDENTIFIER_DEV : TAURI_APP_IDENTIFIER;
|
|
16598
|
+
const tauriDir = getTauriConfigDir(identifier);
|
|
16599
|
+
if (checkExisting) {
|
|
16600
|
+
const legacyDir = getCliConfigDir();
|
|
16601
|
+
const legacyConfig = join26(legacyDir, "opencode.json");
|
|
16602
|
+
const legacyConfigC = join26(legacyDir, "opencode.jsonc");
|
|
16603
|
+
if (existsSync21(legacyConfig) || existsSync21(legacyConfigC)) {
|
|
16604
|
+
return legacyDir;
|
|
16605
|
+
}
|
|
16606
|
+
}
|
|
16607
|
+
return tauriDir;
|
|
16608
|
+
}
|
|
16609
|
+
function getOpenCodeConfigPaths(options) {
|
|
16610
|
+
const configDir = getOpenCodeConfigDir(options);
|
|
16611
|
+
return {
|
|
16612
|
+
configDir,
|
|
16613
|
+
configJson: join26(configDir, "opencode.json"),
|
|
16614
|
+
configJsonc: join26(configDir, "opencode.jsonc"),
|
|
16615
|
+
packageJson: join26(configDir, "package.json"),
|
|
16616
|
+
omoConfig: join26(configDir, "oh-my-opencode.json")
|
|
16617
|
+
};
|
|
16618
|
+
}
|
|
16619
|
+
// src/shared/opencode-version.ts
|
|
16620
|
+
import { execSync } from "child_process";
|
|
16621
|
+
var PERMISSION_BREAKING_VERSION = "1.1.1";
|
|
16622
|
+
var NOT_CACHED = Symbol("NOT_CACHED");
|
|
16623
|
+
var cachedVersion = NOT_CACHED;
|
|
16624
|
+
function parseVersion(version) {
|
|
16625
|
+
const cleaned = version.replace(/^v/, "").split("-")[0];
|
|
16626
|
+
return cleaned.split(".").map((n) => parseInt(n, 10) || 0);
|
|
16627
|
+
}
|
|
16628
|
+
function compareVersions(a, b) {
|
|
16629
|
+
const partsA = parseVersion(a);
|
|
16630
|
+
const partsB = parseVersion(b);
|
|
16631
|
+
const maxLen = Math.max(partsA.length, partsB.length);
|
|
16632
|
+
for (let i2 = 0;i2 < maxLen; i2++) {
|
|
16633
|
+
const numA = partsA[i2] ?? 0;
|
|
16634
|
+
const numB = partsB[i2] ?? 0;
|
|
16635
|
+
if (numA < numB)
|
|
16636
|
+
return -1;
|
|
16637
|
+
if (numA > numB)
|
|
16638
|
+
return 1;
|
|
16639
|
+
}
|
|
16640
|
+
return 0;
|
|
16641
|
+
}
|
|
16642
|
+
function isVersionGte(a, b) {
|
|
16643
|
+
return compareVersions(a, b) >= 0;
|
|
16644
|
+
}
|
|
16645
|
+
function getOpenCodeVersion() {
|
|
16646
|
+
if (cachedVersion !== NOT_CACHED) {
|
|
16647
|
+
return cachedVersion;
|
|
16648
|
+
}
|
|
16649
|
+
try {
|
|
16650
|
+
const result = execSync("opencode --version", {
|
|
16651
|
+
encoding: "utf-8",
|
|
16652
|
+
timeout: 5000,
|
|
16653
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
16654
|
+
}).trim();
|
|
16655
|
+
const versionMatch = result.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
|
|
16656
|
+
cachedVersion = versionMatch?.[1] ?? null;
|
|
16657
|
+
return cachedVersion;
|
|
16658
|
+
} catch {
|
|
16659
|
+
cachedVersion = null;
|
|
16660
|
+
return null;
|
|
16661
|
+
}
|
|
16662
|
+
}
|
|
16663
|
+
function supportsNewPermissionSystem() {
|
|
16664
|
+
const version = getOpenCodeVersion();
|
|
16665
|
+
if (!version)
|
|
16666
|
+
return true;
|
|
16667
|
+
return isVersionGte(version, PERMISSION_BREAKING_VERSION);
|
|
16668
|
+
}
|
|
16669
|
+
// src/shared/permission-compat.ts
|
|
16670
|
+
function createAgentToolRestrictions(denyTools) {
|
|
16671
|
+
if (supportsNewPermissionSystem()) {
|
|
16672
|
+
return {
|
|
16673
|
+
permission: Object.fromEntries(denyTools.map((tool) => [tool, "deny"]))
|
|
16674
|
+
};
|
|
16675
|
+
}
|
|
16676
|
+
return {
|
|
16677
|
+
tools: Object.fromEntries(denyTools.map((tool) => [tool, false]))
|
|
16678
|
+
};
|
|
16679
|
+
}
|
|
16680
|
+
function migrateToolsToPermission(tools) {
|
|
16681
|
+
return Object.fromEntries(Object.entries(tools).map(([key, value]) => [
|
|
16682
|
+
key,
|
|
16683
|
+
value ? "allow" : "deny"
|
|
16684
|
+
]));
|
|
16685
|
+
}
|
|
16686
|
+
function migratePermissionToTools(permission) {
|
|
16687
|
+
return Object.fromEntries(Object.entries(permission).filter(([, value]) => value !== "ask").map(([key, value]) => [key, value === "allow"]));
|
|
16688
|
+
}
|
|
16689
|
+
function migrateAgentConfig(config) {
|
|
16690
|
+
const result = { ...config };
|
|
16691
|
+
if (supportsNewPermissionSystem()) {
|
|
16692
|
+
if (result.tools && typeof result.tools === "object") {
|
|
16693
|
+
const existingPermission = result.permission || {};
|
|
16694
|
+
const migratedPermission = migrateToolsToPermission(result.tools);
|
|
16695
|
+
result.permission = { ...migratedPermission, ...existingPermission };
|
|
16696
|
+
delete result.tools;
|
|
16697
|
+
}
|
|
16698
|
+
} else {
|
|
16699
|
+
if (result.permission && typeof result.permission === "object") {
|
|
16700
|
+
const existingTools = result.tools || {};
|
|
16701
|
+
const migratedTools = migratePermissionToTools(result.permission);
|
|
16702
|
+
result.tools = { ...migratedTools, ...existingTools };
|
|
16703
|
+
delete result.permission;
|
|
16704
|
+
}
|
|
16705
|
+
}
|
|
16706
|
+
return result;
|
|
16707
|
+
}
|
|
16538
16708
|
// src/hooks/think-mode/index.ts
|
|
16539
16709
|
var thinkModeState = new Map;
|
|
16540
16710
|
function createThinkModeHook() {
|
|
@@ -16598,8 +16768,8 @@ function createThinkModeHook() {
|
|
|
16598
16768
|
};
|
|
16599
16769
|
}
|
|
16600
16770
|
// src/hooks/claude-code-hooks/config.ts
|
|
16601
|
-
import { join as
|
|
16602
|
-
import { existsSync as
|
|
16771
|
+
import { join as join27 } from "path";
|
|
16772
|
+
import { existsSync as existsSync22 } from "fs";
|
|
16603
16773
|
function normalizeHookMatcher(raw) {
|
|
16604
16774
|
return {
|
|
16605
16775
|
matcher: raw.matcher ?? raw.pattern ?? "*",
|
|
@@ -16625,11 +16795,11 @@ function normalizeHooksConfig(raw) {
|
|
|
16625
16795
|
function getClaudeSettingsPaths(customPath) {
|
|
16626
16796
|
const claudeConfigDir = getClaudeConfigDir();
|
|
16627
16797
|
const paths = [
|
|
16628
|
-
|
|
16629
|
-
|
|
16630
|
-
|
|
16798
|
+
join27(claudeConfigDir, "settings.json"),
|
|
16799
|
+
join27(process.cwd(), ".claude", "settings.json"),
|
|
16800
|
+
join27(process.cwd(), ".claude", "settings.local.json")
|
|
16631
16801
|
];
|
|
16632
|
-
if (customPath &&
|
|
16802
|
+
if (customPath && existsSync22(customPath)) {
|
|
16633
16803
|
paths.unshift(customPath);
|
|
16634
16804
|
}
|
|
16635
16805
|
return paths;
|
|
@@ -16654,7 +16824,7 @@ async function loadClaudeHooksConfig(customSettingsPath) {
|
|
|
16654
16824
|
const paths = getClaudeSettingsPaths(customSettingsPath);
|
|
16655
16825
|
let mergedConfig = {};
|
|
16656
16826
|
for (const settingsPath of paths) {
|
|
16657
|
-
if (
|
|
16827
|
+
if (existsSync22(settingsPath)) {
|
|
16658
16828
|
try {
|
|
16659
16829
|
const content = await Bun.file(settingsPath).text();
|
|
16660
16830
|
const settings = JSON.parse(content);
|
|
@@ -16671,15 +16841,15 @@ async function loadClaudeHooksConfig(customSettingsPath) {
|
|
|
16671
16841
|
}
|
|
16672
16842
|
|
|
16673
16843
|
// src/hooks/claude-code-hooks/config-loader.ts
|
|
16674
|
-
import { existsSync as
|
|
16675
|
-
import { homedir as
|
|
16676
|
-
import { join as
|
|
16677
|
-
var USER_CONFIG_PATH =
|
|
16844
|
+
import { existsSync as existsSync23 } from "fs";
|
|
16845
|
+
import { homedir as homedir7 } from "os";
|
|
16846
|
+
import { join as join28 } from "path";
|
|
16847
|
+
var USER_CONFIG_PATH = join28(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
|
|
16678
16848
|
function getProjectConfigPath() {
|
|
16679
|
-
return
|
|
16849
|
+
return join28(process.cwd(), ".opencode", "opencode-cc-plugin.json");
|
|
16680
16850
|
}
|
|
16681
16851
|
async function loadConfigFromPath(path4) {
|
|
16682
|
-
if (!
|
|
16852
|
+
if (!existsSync23(path4)) {
|
|
16683
16853
|
return null;
|
|
16684
16854
|
}
|
|
16685
16855
|
try {
|
|
@@ -16859,16 +17029,16 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
|
|
|
16859
17029
|
}
|
|
16860
17030
|
|
|
16861
17031
|
// src/hooks/claude-code-hooks/transcript.ts
|
|
16862
|
-
import { join as
|
|
16863
|
-
import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as
|
|
17032
|
+
import { join as join29 } from "path";
|
|
17033
|
+
import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync24, writeFileSync as writeFileSync8, unlinkSync as unlinkSync5 } from "fs";
|
|
16864
17034
|
import { tmpdir as tmpdir5 } from "os";
|
|
16865
17035
|
import { randomUUID } from "crypto";
|
|
16866
|
-
var TRANSCRIPT_DIR =
|
|
17036
|
+
var TRANSCRIPT_DIR = join29(getClaudeConfigDir(), "transcripts");
|
|
16867
17037
|
function getTranscriptPath(sessionId) {
|
|
16868
|
-
return
|
|
17038
|
+
return join29(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
|
|
16869
17039
|
}
|
|
16870
17040
|
function ensureTranscriptDir() {
|
|
16871
|
-
if (!
|
|
17041
|
+
if (!existsSync24(TRANSCRIPT_DIR)) {
|
|
16872
17042
|
mkdirSync6(TRANSCRIPT_DIR, { recursive: true });
|
|
16873
17043
|
}
|
|
16874
17044
|
}
|
|
@@ -16955,7 +17125,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
16955
17125
|
}
|
|
16956
17126
|
};
|
|
16957
17127
|
entries.push(JSON.stringify(currentEntry));
|
|
16958
|
-
const tempPath =
|
|
17128
|
+
const tempPath = join29(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
|
|
16959
17129
|
writeFileSync8(tempPath, entries.join(`
|
|
16960
17130
|
`) + `
|
|
16961
17131
|
`);
|
|
@@ -16975,7 +17145,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
|
|
|
16975
17145
|
]
|
|
16976
17146
|
}
|
|
16977
17147
|
};
|
|
16978
|
-
const tempPath =
|
|
17148
|
+
const tempPath = join29(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
|
|
16979
17149
|
writeFileSync8(tempPath, JSON.stringify(currentEntry) + `
|
|
16980
17150
|
`);
|
|
16981
17151
|
return tempPath;
|
|
@@ -17187,10 +17357,10 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
|
|
|
17187
17357
|
}
|
|
17188
17358
|
|
|
17189
17359
|
// src/hooks/claude-code-hooks/todo.ts
|
|
17190
|
-
import { join as
|
|
17191
|
-
var TODO_DIR =
|
|
17360
|
+
import { join as join30 } from "path";
|
|
17361
|
+
var TODO_DIR = join30(getClaudeConfigDir(), "todos");
|
|
17192
17362
|
function getTodoPath(sessionId) {
|
|
17193
|
-
return
|
|
17363
|
+
return join30(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
|
|
17194
17364
|
}
|
|
17195
17365
|
|
|
17196
17366
|
// src/hooks/claude-code-hooks/stop.ts
|
|
@@ -17349,6 +17519,172 @@ setInterval(() => {
|
|
|
17349
17519
|
}
|
|
17350
17520
|
}, CACHE_TTL);
|
|
17351
17521
|
|
|
17522
|
+
// src/hooks/keyword-detector/constants.ts
|
|
17523
|
+
var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
|
|
17524
|
+
var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
|
|
17525
|
+
var KEYWORD_DETECTORS = [
|
|
17526
|
+
{
|
|
17527
|
+
pattern: /(ultrawork|ulw)/i,
|
|
17528
|
+
message: `<ultrawork-mode>
|
|
17529
|
+
|
|
17530
|
+
## TODO IS YOUR LIFELINE (NON-NEGOTIABLE)
|
|
17531
|
+
|
|
17532
|
+
**USE TodoWrite OBSESSIVELY. This is the #1 most important tool.**
|
|
17533
|
+
|
|
17534
|
+
### TODO Rules
|
|
17535
|
+
1. **BEFORE any action**: Create TODOs FIRST. Break down into atomic, granular steps.
|
|
17536
|
+
2. **Be excessively detailed**: 10 small TODOs > 3 vague TODOs. Err on the side of too many.
|
|
17537
|
+
3. **Real-time updates**: Mark \`in_progress\` before starting, \`completed\` IMMEDIATELY after. NEVER batch.
|
|
17538
|
+
4. **One at a time**: Only ONE TODO should be \`in_progress\` at any moment.
|
|
17539
|
+
5. **Sub-tasks**: Complex TODO? Break it into sub-TODOs. Keep granularity high.
|
|
17540
|
+
6. **Questions too**: User asks a question? TODO: "Answer with evidence: [question]"
|
|
17541
|
+
|
|
17542
|
+
### Example TODO Granularity
|
|
17543
|
+
BAD: "Implement user auth"
|
|
17544
|
+
GOOD:
|
|
17545
|
+
- "Read existing auth patterns in codebase"
|
|
17546
|
+
- "Create auth schema types"
|
|
17547
|
+
- "Implement login endpoint"
|
|
17548
|
+
- "Implement token validation middleware"
|
|
17549
|
+
- "Add auth tests - login success case"
|
|
17550
|
+
- "Add auth tests - login failure case"
|
|
17551
|
+
- "Verify LSP diagnostics clean"
|
|
17552
|
+
|
|
17553
|
+
**YOUR WORK IS INVISIBLE WITHOUT TODOs. USE THEM.**
|
|
17554
|
+
|
|
17555
|
+
## TDD WORKFLOW (MANDATORY when tests exist)
|
|
17556
|
+
|
|
17557
|
+
Check for test infrastructure FIRST. If exists, follow strictly:
|
|
17558
|
+
|
|
17559
|
+
1. **RED**: Write failing test FIRST \u2192 \`bun test\` must FAIL
|
|
17560
|
+
2. **GREEN**: Write MINIMAL code to pass \u2192 \`bun test\` must PASS
|
|
17561
|
+
3. **REFACTOR**: Clean up, tests stay green \u2192 \`bun test\` still PASS
|
|
17562
|
+
4. **REPEAT**: Next test case, loop until complete
|
|
17563
|
+
|
|
17564
|
+
**NEVER write implementation before test. NEVER delete failing tests.**
|
|
17565
|
+
|
|
17566
|
+
## AGENT DEPLOYMENT
|
|
17567
|
+
|
|
17568
|
+
Fire available agents in PARALLEL via background tasks. Use explore/librarian agents liberally (multiple concurrent if needed).
|
|
17569
|
+
|
|
17570
|
+
## EVIDENCE-BASED ANSWERS
|
|
17571
|
+
|
|
17572
|
+
- Every claim: code snippet + file path + line number
|
|
17573
|
+
- No "I think..." - find and SHOW actual code
|
|
17574
|
+
- Local search fails? \u2192 librarian for external sources
|
|
17575
|
+
- **NEVER acceptable**: "I couldn't find it"
|
|
17576
|
+
|
|
17577
|
+
## ZERO TOLERANCE FOR SHORTCUTS (RIGOROUS & HONEST EXECUTION)
|
|
17578
|
+
|
|
17579
|
+
**CORE PRINCIPLE**: Execute user's ORIGINAL INTENT with maximum rigor. No shortcuts. No compromises. No matter how large the task.
|
|
17580
|
+
|
|
17581
|
+
### ABSOLUTE PROHIBITIONS
|
|
17582
|
+
| Violation | Why It's Forbidden |
|
|
17583
|
+
|-----------|-------------------|
|
|
17584
|
+
| **Mocking/Stubbing** | Never use mocks, stubs, or fake implementations unless explicitly requested. Real implementation only. |
|
|
17585
|
+
| **Scope Reduction** | Never make "demo", "skeleton", "simplified", "basic", "minimal" versions. Deliver FULL implementation. |
|
|
17586
|
+
| **Partial Completion** | Never stop at 60-80% saying "you can extend this...", "as an exercise...", "you can add...". Finish 100%. |
|
|
17587
|
+
| **Lazy Placeholders** | Never use "// TODO", "...", "etc.", "and so on" in actual code. Complete everything. |
|
|
17588
|
+
| **Assumed Shortcuts** | Never skip requirements deemed "optional" or "can be added later". All requirements are mandatory. |
|
|
17589
|
+
| **Test Deletion** | Never delete or skip failing tests. Fix the code, not the tests. |
|
|
17590
|
+
| **Evidence-Free Claims** | Never say "I think...", "probably...", "should work...". Show actual code/output. |
|
|
17591
|
+
|
|
17592
|
+
### RIGOROUS EXECUTION MANDATE
|
|
17593
|
+
1. **Parse Original Intent**: What did the user ACTUALLY want? Not what's convenient. The REAL, COMPLETE request.
|
|
17594
|
+
2. **No Task Too Large**: If the task requires 100 files, modify 100 files. If it needs 1000 lines, write 1000 lines. Size is irrelevant.
|
|
17595
|
+
3. **Honest Assessment**: If you cannot complete something, say so BEFORE starting. Don't fake completion.
|
|
17596
|
+
4. **Evidence-Based Verification**: Every claim backed by code snippets, file paths, line numbers, and actual outputs.
|
|
17597
|
+
5. **Complete Verification**: Re-read original request after completion. Check EVERY requirement was met.
|
|
17598
|
+
|
|
17599
|
+
### FAILURE RECOVERY
|
|
17600
|
+
If you realize you've taken shortcuts:
|
|
17601
|
+
1. STOP immediately
|
|
17602
|
+
2. Identify what you skipped/faked
|
|
17603
|
+
3. Create TODOs for ALL remaining work
|
|
17604
|
+
4. Execute to TRUE completion - not "good enough"
|
|
17605
|
+
|
|
17606
|
+
**THE USER ASKED FOR X. DELIVER EXACTLY X. COMPLETELY. HONESTLY. NO MATTER THE SIZE.**
|
|
17607
|
+
|
|
17608
|
+
## SUCCESS = All TODOs Done + All Requirements Met + Evidence Provided
|
|
17609
|
+
|
|
17610
|
+
</ultrawork-mode>
|
|
17611
|
+
|
|
17612
|
+
---
|
|
17613
|
+
|
|
17614
|
+
`
|
|
17615
|
+
},
|
|
17616
|
+
{
|
|
17617
|
+
pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
|
|
17618
|
+
message: `[search-mode]
|
|
17619
|
+
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
|
|
17620
|
+
- explore agents (codebase patterns, file structures, ast-grep)
|
|
17621
|
+
- librarian agents (remote repos, official docs, GitHub examples)
|
|
17622
|
+
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
|
|
17623
|
+
NEVER stop at first result - be exhaustive.`
|
|
17624
|
+
},
|
|
17625
|
+
{
|
|
17626
|
+
pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
|
|
17627
|
+
message: `[analyze-mode]
|
|
17628
|
+
ANALYSIS MODE. Gather context before diving deep:
|
|
17629
|
+
|
|
17630
|
+
CONTEXT GATHERING (parallel):
|
|
17631
|
+
- 1-2 explore agents (codebase patterns, implementations)
|
|
17632
|
+
- 1-2 librarian agents (if external library involved)
|
|
17633
|
+
- Direct tools: Grep, AST-grep, LSP for targeted searches
|
|
17634
|
+
|
|
17635
|
+
IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
|
|
17636
|
+
- Consult oracle for strategic guidance
|
|
17637
|
+
|
|
17638
|
+
SYNTHESIZE findings before proceeding.`
|
|
17639
|
+
}
|
|
17640
|
+
];
|
|
17641
|
+
|
|
17642
|
+
// src/hooks/keyword-detector/detector.ts
|
|
17643
|
+
function removeCodeBlocks2(text) {
|
|
17644
|
+
return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
|
|
17645
|
+
}
|
|
17646
|
+
function detectKeywordsWithType(text) {
|
|
17647
|
+
const textWithoutCode = removeCodeBlocks2(text);
|
|
17648
|
+
const types3 = ["ultrawork", "search", "analyze"];
|
|
17649
|
+
return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
|
|
17650
|
+
matches: pattern.test(textWithoutCode),
|
|
17651
|
+
type: types3[index],
|
|
17652
|
+
message
|
|
17653
|
+
})).filter((result) => result.matches).map(({ type: type2, message }) => ({ type: type2, message }));
|
|
17654
|
+
}
|
|
17655
|
+
function extractPromptText2(parts) {
|
|
17656
|
+
return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
|
|
17657
|
+
}
|
|
17658
|
+
|
|
17659
|
+
// src/hooks/keyword-detector/index.ts
|
|
17660
|
+
function createKeywordDetectorHook(ctx) {
|
|
17661
|
+
return {
|
|
17662
|
+
"chat.message": async (input, output) => {
|
|
17663
|
+
const promptText = extractPromptText2(output.parts);
|
|
17664
|
+
const detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(promptText));
|
|
17665
|
+
if (detectedKeywords.length === 0) {
|
|
17666
|
+
return;
|
|
17667
|
+
}
|
|
17668
|
+
const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
|
|
17669
|
+
if (hasUltrawork) {
|
|
17670
|
+
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
|
|
17671
|
+
ctx.client.tui.showToast({
|
|
17672
|
+
body: {
|
|
17673
|
+
title: "Ultrawork Mode Activated",
|
|
17674
|
+
message: "Maximum precision engaged. All agents at your disposal.",
|
|
17675
|
+
variant: "success",
|
|
17676
|
+
duration: 3000
|
|
17677
|
+
}
|
|
17678
|
+
}).catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }));
|
|
17679
|
+
}
|
|
17680
|
+
log(`[keyword-detector] Detected ${detectedKeywords.length} keywords`, {
|
|
17681
|
+
sessionID: input.sessionID,
|
|
17682
|
+
types: detectedKeywords.map((k) => k.type)
|
|
17683
|
+
});
|
|
17684
|
+
}
|
|
17685
|
+
};
|
|
17686
|
+
}
|
|
17687
|
+
|
|
17352
17688
|
// src/hooks/claude-code-hooks/index.ts
|
|
17353
17689
|
var sessionFirstMessageProcessed = new Set;
|
|
17354
17690
|
var sessionErrorState = new Map;
|
|
@@ -17423,11 +17759,20 @@ function createClaudeCodeHooksHook(ctx, config = {}) {
|
|
|
17423
17759
|
log("chat.message injection skipped - interrupted during hooks", { sessionID: input.sessionID });
|
|
17424
17760
|
return;
|
|
17425
17761
|
}
|
|
17426
|
-
|
|
17427
|
-
|
|
17762
|
+
const detectedKeywords = detectKeywordsWithType(removeCodeBlocks2(prompt));
|
|
17763
|
+
const keywordMessages = detectedKeywords.map((k) => k.message);
|
|
17764
|
+
if (keywordMessages.length > 0) {
|
|
17765
|
+
log("[claude-code-hooks] Detected keywords", {
|
|
17766
|
+
sessionID: input.sessionID,
|
|
17767
|
+
types: detectedKeywords.map((k) => k.type)
|
|
17768
|
+
});
|
|
17769
|
+
}
|
|
17770
|
+
const allMessages = [...keywordMessages, ...result.messages];
|
|
17771
|
+
if (allMessages.length > 0) {
|
|
17772
|
+
const hookContent = allMessages.join(`
|
|
17428
17773
|
|
|
17429
17774
|
`);
|
|
17430
|
-
log(`[claude-code-hooks] Injecting ${result.messages.length} hook
|
|
17775
|
+
log(`[claude-code-hooks] Injecting ${allMessages.length} messages (${keywordMessages.length} keyword + ${result.messages.length} hook)`, { sessionID: input.sessionID, contentLength: hookContent.length, isFirstMessage });
|
|
17431
17776
|
if (isFirstMessage) {
|
|
17432
17777
|
const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
|
|
17433
17778
|
if (idx >= 0) {
|
|
@@ -17621,22 +17966,22 @@ ${result.message}`;
|
|
|
17621
17966
|
}
|
|
17622
17967
|
// src/hooks/rules-injector/index.ts
|
|
17623
17968
|
import { readFileSync as readFileSync15 } from "fs";
|
|
17624
|
-
import { homedir as
|
|
17969
|
+
import { homedir as homedir8 } from "os";
|
|
17625
17970
|
import { relative as relative3, resolve as resolve4 } from "path";
|
|
17626
17971
|
|
|
17627
17972
|
// src/hooks/rules-injector/finder.ts
|
|
17628
17973
|
import {
|
|
17629
|
-
existsSync as
|
|
17974
|
+
existsSync as existsSync25,
|
|
17630
17975
|
readdirSync as readdirSync10,
|
|
17631
17976
|
realpathSync,
|
|
17632
17977
|
statSync as statSync2
|
|
17633
17978
|
} from "fs";
|
|
17634
|
-
import { dirname as dirname4, join as
|
|
17979
|
+
import { dirname as dirname4, join as join32, relative } from "path";
|
|
17635
17980
|
|
|
17636
17981
|
// src/hooks/rules-injector/constants.ts
|
|
17637
|
-
import { join as
|
|
17982
|
+
import { join as join31 } from "path";
|
|
17638
17983
|
var OPENCODE_STORAGE6 = getOpenCodeStorageDir();
|
|
17639
|
-
var RULES_INJECTOR_STORAGE =
|
|
17984
|
+
var RULES_INJECTOR_STORAGE = join31(OPENCODE_STORAGE6, "rules-injector");
|
|
17640
17985
|
var PROJECT_MARKERS = [
|
|
17641
17986
|
".git",
|
|
17642
17987
|
"pyproject.toml",
|
|
@@ -17677,8 +18022,8 @@ function findProjectRoot(startPath) {
|
|
|
17677
18022
|
}
|
|
17678
18023
|
while (true) {
|
|
17679
18024
|
for (const marker of PROJECT_MARKERS) {
|
|
17680
|
-
const markerPath =
|
|
17681
|
-
if (
|
|
18025
|
+
const markerPath = join32(current, marker);
|
|
18026
|
+
if (existsSync25(markerPath)) {
|
|
17682
18027
|
return current;
|
|
17683
18028
|
}
|
|
17684
18029
|
}
|
|
@@ -17690,12 +18035,12 @@ function findProjectRoot(startPath) {
|
|
|
17690
18035
|
}
|
|
17691
18036
|
}
|
|
17692
18037
|
function findRuleFilesRecursive(dir, results) {
|
|
17693
|
-
if (!
|
|
18038
|
+
if (!existsSync25(dir))
|
|
17694
18039
|
return;
|
|
17695
18040
|
try {
|
|
17696
18041
|
const entries = readdirSync10(dir, { withFileTypes: true });
|
|
17697
18042
|
for (const entry of entries) {
|
|
17698
|
-
const fullPath =
|
|
18043
|
+
const fullPath = join32(dir, entry.name);
|
|
17699
18044
|
if (entry.isDirectory()) {
|
|
17700
18045
|
findRuleFilesRecursive(fullPath, results);
|
|
17701
18046
|
} else if (entry.isFile()) {
|
|
@@ -17720,7 +18065,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
17720
18065
|
let distance = 0;
|
|
17721
18066
|
while (true) {
|
|
17722
18067
|
for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
|
|
17723
|
-
const ruleDir =
|
|
18068
|
+
const ruleDir = join32(currentDir, parent, subdir);
|
|
17724
18069
|
const files = [];
|
|
17725
18070
|
findRuleFilesRecursive(ruleDir, files);
|
|
17726
18071
|
for (const filePath of files) {
|
|
@@ -17746,8 +18091,8 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
17746
18091
|
}
|
|
17747
18092
|
if (projectRoot) {
|
|
17748
18093
|
for (const ruleFile of PROJECT_RULE_FILES) {
|
|
17749
|
-
const filePath =
|
|
17750
|
-
if (
|
|
18094
|
+
const filePath = join32(projectRoot, ruleFile);
|
|
18095
|
+
if (existsSync25(filePath)) {
|
|
17751
18096
|
try {
|
|
17752
18097
|
const stat = statSync2(filePath);
|
|
17753
18098
|
if (stat.isFile()) {
|
|
@@ -17767,7 +18112,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
|
|
|
17767
18112
|
}
|
|
17768
18113
|
}
|
|
17769
18114
|
}
|
|
17770
|
-
const userRuleDir =
|
|
18115
|
+
const userRuleDir = join32(homeDir, USER_RULE_DIR);
|
|
17771
18116
|
const userFiles = [];
|
|
17772
18117
|
findRuleFilesRecursive(userRuleDir, userFiles);
|
|
17773
18118
|
for (const filePath of userFiles) {
|
|
@@ -17956,19 +18301,19 @@ function mergeGlobs(existing, newValue) {
|
|
|
17956
18301
|
|
|
17957
18302
|
// src/hooks/rules-injector/storage.ts
|
|
17958
18303
|
import {
|
|
17959
|
-
existsSync as
|
|
18304
|
+
existsSync as existsSync26,
|
|
17960
18305
|
mkdirSync as mkdirSync7,
|
|
17961
18306
|
readFileSync as readFileSync14,
|
|
17962
18307
|
writeFileSync as writeFileSync9,
|
|
17963
18308
|
unlinkSync as unlinkSync6
|
|
17964
18309
|
} from "fs";
|
|
17965
|
-
import { join as
|
|
18310
|
+
import { join as join33 } from "path";
|
|
17966
18311
|
function getStoragePath3(sessionID) {
|
|
17967
|
-
return
|
|
18312
|
+
return join33(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
17968
18313
|
}
|
|
17969
18314
|
function loadInjectedRules(sessionID) {
|
|
17970
18315
|
const filePath = getStoragePath3(sessionID);
|
|
17971
|
-
if (!
|
|
18316
|
+
if (!existsSync26(filePath))
|
|
17972
18317
|
return { contentHashes: new Set, realPaths: new Set };
|
|
17973
18318
|
try {
|
|
17974
18319
|
const content = readFileSync14(filePath, "utf-8");
|
|
@@ -17982,7 +18327,7 @@ function loadInjectedRules(sessionID) {
|
|
|
17982
18327
|
}
|
|
17983
18328
|
}
|
|
17984
18329
|
function saveInjectedRules(sessionID, data) {
|
|
17985
|
-
if (!
|
|
18330
|
+
if (!existsSync26(RULES_INJECTOR_STORAGE)) {
|
|
17986
18331
|
mkdirSync7(RULES_INJECTOR_STORAGE, { recursive: true });
|
|
17987
18332
|
}
|
|
17988
18333
|
const storageData = {
|
|
@@ -17995,7 +18340,7 @@ function saveInjectedRules(sessionID, data) {
|
|
|
17995
18340
|
}
|
|
17996
18341
|
function clearInjectedRules(sessionID) {
|
|
17997
18342
|
const filePath = getStoragePath3(sessionID);
|
|
17998
|
-
if (
|
|
18343
|
+
if (existsSync26(filePath)) {
|
|
17999
18344
|
unlinkSync6(filePath);
|
|
18000
18345
|
}
|
|
18001
18346
|
}
|
|
@@ -18025,7 +18370,7 @@ function createRulesInjectorHook(ctx) {
|
|
|
18025
18370
|
return;
|
|
18026
18371
|
const projectRoot = findProjectRoot(resolved);
|
|
18027
18372
|
const cache2 = getSessionCache(sessionID);
|
|
18028
|
-
const home =
|
|
18373
|
+
const home = homedir8();
|
|
18029
18374
|
const ruleFileCandidates = findRuleFiles(projectRoot, home, resolved);
|
|
18030
18375
|
const toInject = [];
|
|
18031
18376
|
for (const candidate of ruleFileCandidates) {
|
|
@@ -18450,13 +18795,17 @@ function invalidatePackage(packageName = PACKAGE_NAME) {
|
|
|
18450
18795
|
}
|
|
18451
18796
|
|
|
18452
18797
|
// src/cli/config-manager.ts
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18459
|
-
|
|
18798
|
+
var configContext = null;
|
|
18799
|
+
function getConfigContext() {
|
|
18800
|
+
if (!configContext) {
|
|
18801
|
+
const paths = getOpenCodeConfigPaths({ binary: "opencode", version: null });
|
|
18802
|
+
configContext = { binary: "opencode", version: null, paths };
|
|
18803
|
+
}
|
|
18804
|
+
return configContext;
|
|
18805
|
+
}
|
|
18806
|
+
function getConfigDir() {
|
|
18807
|
+
return getConfigContext().paths.configDir;
|
|
18808
|
+
}
|
|
18460
18809
|
var BUN_INSTALL_TIMEOUT_SECONDS = 60;
|
|
18461
18810
|
var BUN_INSTALL_TIMEOUT_MS = BUN_INSTALL_TIMEOUT_SECONDS * 1000;
|
|
18462
18811
|
async function runBunInstall() {
|
|
@@ -18466,7 +18815,7 @@ async function runBunInstall() {
|
|
|
18466
18815
|
async function runBunInstallWithDetails() {
|
|
18467
18816
|
try {
|
|
18468
18817
|
const proc = Bun.spawn(["bun", "install"], {
|
|
18469
|
-
cwd:
|
|
18818
|
+
cwd: getConfigDir(),
|
|
18470
18819
|
stdout: "pipe",
|
|
18471
18820
|
stderr: "pipe"
|
|
18472
18821
|
});
|
|
@@ -18524,9 +18873,9 @@ v${latestVersion} available. Restart OpenCode to apply.` : `OpenCode is now on S
|
|
|
18524
18873
|
return;
|
|
18525
18874
|
hasChecked = true;
|
|
18526
18875
|
setTimeout(async () => {
|
|
18527
|
-
const
|
|
18876
|
+
const cachedVersion2 = getCachedVersion();
|
|
18528
18877
|
const localDevVersion = getLocalDevVersion(ctx.directory);
|
|
18529
|
-
const displayVersion = localDevVersion ??
|
|
18878
|
+
const displayVersion = localDevVersion ?? cachedVersion2;
|
|
18530
18879
|
await showConfigErrorsIfAny(ctx);
|
|
18531
18880
|
if (localDevVersion) {
|
|
18532
18881
|
if (showStartupToast) {
|
|
@@ -18551,8 +18900,8 @@ async function runBackgroundUpdateCheck(ctx, autoUpdate, getToastMessage) {
|
|
|
18551
18900
|
log("[auto-update-checker] Plugin not found in config");
|
|
18552
18901
|
return;
|
|
18553
18902
|
}
|
|
18554
|
-
const
|
|
18555
|
-
const currentVersion =
|
|
18903
|
+
const cachedVersion2 = getCachedVersion();
|
|
18904
|
+
const currentVersion = cachedVersion2 ?? pluginInfo.pinnedVersion;
|
|
18556
18905
|
if (!currentVersion) {
|
|
18557
18906
|
log("[auto-update-checker] No version found (cached or pinned)");
|
|
18558
18907
|
return;
|
|
@@ -18671,7 +19020,7 @@ async function showLocalDevToast(ctx, version, isSisyphusEnabled) {
|
|
|
18671
19020
|
}
|
|
18672
19021
|
// src/hooks/agent-usage-reminder/storage.ts
|
|
18673
19022
|
import {
|
|
18674
|
-
existsSync as
|
|
19023
|
+
existsSync as existsSync30,
|
|
18675
19024
|
mkdirSync as mkdirSync8,
|
|
18676
19025
|
readFileSync as readFileSync18,
|
|
18677
19026
|
writeFileSync as writeFileSync12,
|
|
@@ -18731,7 +19080,7 @@ function getStoragePath4(sessionID) {
|
|
|
18731
19080
|
}
|
|
18732
19081
|
function loadAgentUsageState(sessionID) {
|
|
18733
19082
|
const filePath = getStoragePath4(sessionID);
|
|
18734
|
-
if (!
|
|
19083
|
+
if (!existsSync30(filePath))
|
|
18735
19084
|
return null;
|
|
18736
19085
|
try {
|
|
18737
19086
|
const content = readFileSync18(filePath, "utf-8");
|
|
@@ -18741,7 +19090,7 @@ function loadAgentUsageState(sessionID) {
|
|
|
18741
19090
|
}
|
|
18742
19091
|
}
|
|
18743
19092
|
function saveAgentUsageState(state2) {
|
|
18744
|
-
if (!
|
|
19093
|
+
if (!existsSync30(AGENT_USAGE_REMINDER_STORAGE)) {
|
|
18745
19094
|
mkdirSync8(AGENT_USAGE_REMINDER_STORAGE, { recursive: true });
|
|
18746
19095
|
}
|
|
18747
19096
|
const filePath = getStoragePath4(state2.sessionID);
|
|
@@ -18749,7 +19098,7 @@ function saveAgentUsageState(state2) {
|
|
|
18749
19098
|
}
|
|
18750
19099
|
function clearAgentUsageState(sessionID) {
|
|
18751
19100
|
const filePath = getStoragePath4(sessionID);
|
|
18752
|
-
if (
|
|
19101
|
+
if (existsSync30(filePath)) {
|
|
18753
19102
|
unlinkSync7(filePath);
|
|
18754
19103
|
}
|
|
18755
19104
|
}
|
|
@@ -18819,163 +19168,6 @@ function createAgentUsageReminderHook(_ctx) {
|
|
|
18819
19168
|
event: eventHandler
|
|
18820
19169
|
};
|
|
18821
19170
|
}
|
|
18822
|
-
// src/hooks/keyword-detector/constants.ts
|
|
18823
|
-
var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
|
|
18824
|
-
var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
|
|
18825
|
-
var KEYWORD_DETECTORS = [
|
|
18826
|
-
{
|
|
18827
|
-
pattern: /(ultrawork|ulw)/i,
|
|
18828
|
-
message: `<ultrawork-mode>
|
|
18829
|
-
[CODE RED] Maximum precision required. Ultrathink before acting.
|
|
18830
|
-
|
|
18831
|
-
YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
|
|
18832
|
-
TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
|
|
18833
|
-
|
|
18834
|
-
## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
|
|
18835
|
-
- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
|
|
18836
|
-
- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
|
|
18837
|
-
- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
|
|
18838
|
-
- **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
|
|
18839
|
-
- **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
|
|
18840
|
-
|
|
18841
|
-
## EXECUTION RULES
|
|
18842
|
-
- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
|
|
18843
|
-
- **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
|
|
18844
|
-
- **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
|
|
18845
|
-
- **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
|
|
18846
|
-
- **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
|
|
18847
|
-
|
|
18848
|
-
## WORKFLOW
|
|
18849
|
-
1. Analyze the request and identify required capabilities
|
|
18850
|
-
2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
|
|
18851
|
-
3. Always Use Plan agent with gathered context to create detailed work breakdown
|
|
18852
|
-
4. Execute with continuous verification against original requirements
|
|
18853
|
-
|
|
18854
|
-
## TDD (if test infrastructure exists)
|
|
18855
|
-
|
|
18856
|
-
1. Write spec (requirements)
|
|
18857
|
-
2. Write tests (failing)
|
|
18858
|
-
3. RED: tests fail
|
|
18859
|
-
4. Implement minimal code
|
|
18860
|
-
5. GREEN: tests pass
|
|
18861
|
-
6. Refactor if needed (must stay green)
|
|
18862
|
-
7. Next feature, repeat
|
|
18863
|
-
|
|
18864
|
-
## ZERO TOLERANCE FAILURES
|
|
18865
|
-
- **NO Scope Reduction**: Never make "demo", "skeleton", "simplified", "basic" versions - deliver FULL implementation
|
|
18866
|
-
- **NO MockUp Work**: When user asked you to do "port A", you must "port A", fully, 100%. No Extra feature, No reduced feature, no mock data, fully working 100% port.
|
|
18867
|
-
- **NO Partial Completion**: Never stop at 60-80% saying "you can extend this..." - finish 100%
|
|
18868
|
-
- **NO Assumed Shortcuts**: Never skip requirements you deem "optional" or "can be added later"
|
|
18869
|
-
- **NO Premature Stopping**: Never declare done until ALL TODOs are completed and verified
|
|
18870
|
-
- **NO TEST DELETION**: Never delete or skip failing tests to make the build pass. Fix the code, not the tests.
|
|
18871
|
-
|
|
18872
|
-
THE USER ASKED FOR X. DELIVER EXACTLY X. NOT A SUBSET. NOT A DEMO. NOT A STARTING POINT.
|
|
18873
|
-
|
|
18874
|
-
</ultrawork-mode>
|
|
18875
|
-
|
|
18876
|
-
---
|
|
18877
|
-
|
|
18878
|
-
`
|
|
18879
|
-
},
|
|
18880
|
-
{
|
|
18881
|
-
pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
|
|
18882
|
-
message: `[search-mode]
|
|
18883
|
-
MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
|
|
18884
|
-
- explore agents (codebase patterns, file structures, ast-grep)
|
|
18885
|
-
- librarian agents (remote repos, official docs, GitHub examples)
|
|
18886
|
-
Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
|
|
18887
|
-
NEVER stop at first result - be exhaustive.`
|
|
18888
|
-
},
|
|
18889
|
-
{
|
|
18890
|
-
pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
|
|
18891
|
-
message: `[analyze-mode]
|
|
18892
|
-
ANALYSIS MODE. Gather context before diving deep:
|
|
18893
|
-
|
|
18894
|
-
CONTEXT GATHERING (parallel):
|
|
18895
|
-
- 1-2 explore agents (codebase patterns, implementations)
|
|
18896
|
-
- 1-2 librarian agents (if external library involved)
|
|
18897
|
-
- Direct tools: Grep, AST-grep, LSP for targeted searches
|
|
18898
|
-
|
|
18899
|
-
IF COMPLEX (architecture, multi-system, debugging after 2+ failures):
|
|
18900
|
-
- Consult oracle for strategic guidance
|
|
18901
|
-
|
|
18902
|
-
SYNTHESIZE findings before proceeding.`
|
|
18903
|
-
}
|
|
18904
|
-
];
|
|
18905
|
-
|
|
18906
|
-
// src/hooks/keyword-detector/detector.ts
|
|
18907
|
-
function removeCodeBlocks2(text) {
|
|
18908
|
-
return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
|
|
18909
|
-
}
|
|
18910
|
-
function detectKeywordsWithType(text) {
|
|
18911
|
-
const textWithoutCode = removeCodeBlocks2(text);
|
|
18912
|
-
const types3 = ["ultrawork", "search", "analyze"];
|
|
18913
|
-
return KEYWORD_DETECTORS.map(({ pattern, message }, index) => ({
|
|
18914
|
-
matches: pattern.test(textWithoutCode),
|
|
18915
|
-
type: types3[index],
|
|
18916
|
-
message
|
|
18917
|
-
})).filter((result) => result.matches).map(({ type: type2, message }) => ({ type: type2, message }));
|
|
18918
|
-
}
|
|
18919
|
-
function extractPromptText2(parts) {
|
|
18920
|
-
return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
|
|
18921
|
-
}
|
|
18922
|
-
|
|
18923
|
-
// src/hooks/keyword-detector/index.ts
|
|
18924
|
-
var sessionFirstMessageProcessed2 = new Set;
|
|
18925
|
-
var sessionUltraworkNotified = new Set;
|
|
18926
|
-
function createKeywordDetectorHook(ctx) {
|
|
18927
|
-
return {
|
|
18928
|
-
"chat.message": async (input, output) => {
|
|
18929
|
-
const isFirstMessage = !sessionFirstMessageProcessed2.has(input.sessionID);
|
|
18930
|
-
sessionFirstMessageProcessed2.add(input.sessionID);
|
|
18931
|
-
const promptText = extractPromptText2(output.parts);
|
|
18932
|
-
const detectedKeywords = detectKeywordsWithType(promptText);
|
|
18933
|
-
const messages = detectedKeywords.map((k) => k.message);
|
|
18934
|
-
if (messages.length === 0) {
|
|
18935
|
-
return;
|
|
18936
|
-
}
|
|
18937
|
-
const hasUltrawork = detectedKeywords.some((k) => k.type === "ultrawork");
|
|
18938
|
-
if (hasUltrawork && !sessionUltraworkNotified.has(input.sessionID)) {
|
|
18939
|
-
sessionUltraworkNotified.add(input.sessionID);
|
|
18940
|
-
log(`[keyword-detector] Ultrawork mode activated`, { sessionID: input.sessionID });
|
|
18941
|
-
ctx.client.tui.showToast({
|
|
18942
|
-
body: {
|
|
18943
|
-
title: "Ultrawork Mode Activated",
|
|
18944
|
-
message: "Maximum precision engaged. All agents at your disposal.",
|
|
18945
|
-
variant: "success",
|
|
18946
|
-
duration: 3000
|
|
18947
|
-
}
|
|
18948
|
-
}).catch((err) => log(`[keyword-detector] Failed to show toast`, { error: err, sessionID: input.sessionID }));
|
|
18949
|
-
}
|
|
18950
|
-
const context = messages.join(`
|
|
18951
|
-
`);
|
|
18952
|
-
if (isFirstMessage) {
|
|
18953
|
-
log(`Keywords detected on first message, transforming parts directly`, { sessionID: input.sessionID, keywordCount: messages.length });
|
|
18954
|
-
const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
|
|
18955
|
-
if (idx >= 0) {
|
|
18956
|
-
output.parts[idx].text = `${context}
|
|
18957
|
-
|
|
18958
|
-
---
|
|
18959
|
-
|
|
18960
|
-
${output.parts[idx].text ?? ""}`;
|
|
18961
|
-
}
|
|
18962
|
-
return;
|
|
18963
|
-
}
|
|
18964
|
-
log(`Keywords detected: ${messages.length}`, { sessionID: input.sessionID });
|
|
18965
|
-
const message = output.message;
|
|
18966
|
-
log(`[keyword-detector] Injecting context for ${messages.length} keywords`, { sessionID: input.sessionID, contextLength: context.length });
|
|
18967
|
-
const success = injectHookMessage(input.sessionID, context, {
|
|
18968
|
-
agent: message.agent,
|
|
18969
|
-
model: message.model,
|
|
18970
|
-
path: message.path,
|
|
18971
|
-
tools: message.tools
|
|
18972
|
-
});
|
|
18973
|
-
if (success) {
|
|
18974
|
-
log("Keyword context injected", { sessionID: input.sessionID });
|
|
18975
|
-
}
|
|
18976
|
-
}
|
|
18977
|
-
};
|
|
18978
|
-
}
|
|
18979
19171
|
// src/hooks/non-interactive-env/constants.ts
|
|
18980
19172
|
var HOOK_NAME2 = "non-interactive-env";
|
|
18981
19173
|
var NON_INTERACTIVE_ENV = {
|
|
@@ -19058,7 +19250,8 @@ function shellEscape(value) {
|
|
|
19058
19250
|
return value;
|
|
19059
19251
|
}
|
|
19060
19252
|
function buildEnvPrefix(env) {
|
|
19061
|
-
|
|
19253
|
+
const exports = Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" ");
|
|
19254
|
+
return `export ${exports};`;
|
|
19062
19255
|
}
|
|
19063
19256
|
function createNonInteractiveEnvHook(_ctx) {
|
|
19064
19257
|
return {
|
|
@@ -19089,7 +19282,7 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
19089
19282
|
}
|
|
19090
19283
|
// src/hooks/interactive-bash-session/storage.ts
|
|
19091
19284
|
import {
|
|
19092
|
-
existsSync as
|
|
19285
|
+
existsSync as existsSync31,
|
|
19093
19286
|
mkdirSync as mkdirSync9,
|
|
19094
19287
|
readFileSync as readFileSync19,
|
|
19095
19288
|
writeFileSync as writeFileSync13,
|
|
@@ -19116,7 +19309,7 @@ function getStoragePath5(sessionID) {
|
|
|
19116
19309
|
}
|
|
19117
19310
|
function loadInteractiveBashSessionState(sessionID) {
|
|
19118
19311
|
const filePath = getStoragePath5(sessionID);
|
|
19119
|
-
if (!
|
|
19312
|
+
if (!existsSync31(filePath))
|
|
19120
19313
|
return null;
|
|
19121
19314
|
try {
|
|
19122
19315
|
const content = readFileSync19(filePath, "utf-8");
|
|
@@ -19131,7 +19324,7 @@ function loadInteractiveBashSessionState(sessionID) {
|
|
|
19131
19324
|
}
|
|
19132
19325
|
}
|
|
19133
19326
|
function saveInteractiveBashSessionState(state2) {
|
|
19134
|
-
if (!
|
|
19327
|
+
if (!existsSync31(INTERACTIVE_BASH_SESSION_STORAGE)) {
|
|
19135
19328
|
mkdirSync9(INTERACTIVE_BASH_SESSION_STORAGE, { recursive: true });
|
|
19136
19329
|
}
|
|
19137
19330
|
const filePath = getStoragePath5(state2.sessionID);
|
|
@@ -19144,7 +19337,7 @@ function saveInteractiveBashSessionState(state2) {
|
|
|
19144
19337
|
}
|
|
19145
19338
|
function clearInteractiveBashSessionState(sessionID) {
|
|
19146
19339
|
const filePath = getStoragePath5(sessionID);
|
|
19147
|
-
if (
|
|
19340
|
+
if (existsSync31(filePath)) {
|
|
19148
19341
|
unlinkSync8(filePath);
|
|
19149
19342
|
}
|
|
19150
19343
|
}
|
|
@@ -19475,10 +19668,10 @@ function createThinkingBlockValidatorHook() {
|
|
|
19475
19668
|
};
|
|
19476
19669
|
}
|
|
19477
19670
|
// src/hooks/ralph-loop/index.ts
|
|
19478
|
-
import { existsSync as
|
|
19671
|
+
import { existsSync as existsSync33, readFileSync as readFileSync21 } from "fs";
|
|
19479
19672
|
|
|
19480
19673
|
// src/hooks/ralph-loop/storage.ts
|
|
19481
|
-
import { existsSync as
|
|
19674
|
+
import { existsSync as existsSync32, readFileSync as readFileSync20, writeFileSync as writeFileSync14, unlinkSync as unlinkSync9, mkdirSync as mkdirSync10 } from "fs";
|
|
19482
19675
|
import { dirname as dirname6, join as join41 } from "path";
|
|
19483
19676
|
|
|
19484
19677
|
// src/hooks/ralph-loop/constants.ts
|
|
@@ -19493,7 +19686,7 @@ function getStateFilePath(directory, customPath) {
|
|
|
19493
19686
|
}
|
|
19494
19687
|
function readState(directory, customPath) {
|
|
19495
19688
|
const filePath = getStateFilePath(directory, customPath);
|
|
19496
|
-
if (!
|
|
19689
|
+
if (!existsSync32(filePath)) {
|
|
19497
19690
|
return null;
|
|
19498
19691
|
}
|
|
19499
19692
|
try {
|
|
@@ -19530,7 +19723,7 @@ function writeState(directory, state2, customPath) {
|
|
|
19530
19723
|
const filePath = getStateFilePath(directory, customPath);
|
|
19531
19724
|
try {
|
|
19532
19725
|
const dir = dirname6(filePath);
|
|
19533
|
-
if (!
|
|
19726
|
+
if (!existsSync32(dir)) {
|
|
19534
19727
|
mkdirSync10(dir, { recursive: true });
|
|
19535
19728
|
}
|
|
19536
19729
|
const sessionIdLine = state2.session_id ? `session_id: "${state2.session_id}"
|
|
@@ -19553,7 +19746,7 @@ ${state2.prompt}
|
|
|
19553
19746
|
function clearState(directory, customPath) {
|
|
19554
19747
|
const filePath = getStateFilePath(directory, customPath);
|
|
19555
19748
|
try {
|
|
19556
|
-
if (
|
|
19749
|
+
if (existsSync32(filePath)) {
|
|
19557
19750
|
unlinkSync9(filePath);
|
|
19558
19751
|
}
|
|
19559
19752
|
return true;
|
|
@@ -19585,11 +19778,14 @@ IMPORTANT:
|
|
|
19585
19778
|
|
|
19586
19779
|
Original task:
|
|
19587
19780
|
{{PROMPT}}`;
|
|
19781
|
+
var DEFAULT_API_TIMEOUT = 3000;
|
|
19588
19782
|
function createRalphLoopHook(ctx, options) {
|
|
19589
19783
|
const sessions = new Map;
|
|
19590
19784
|
const config = options?.config;
|
|
19591
19785
|
const stateDir = config?.state_dir;
|
|
19592
19786
|
const getTranscriptPath2 = options?.getTranscriptPath ?? getTranscriptPath;
|
|
19787
|
+
const apiTimeout = options?.apiTimeout ?? DEFAULT_API_TIMEOUT;
|
|
19788
|
+
const checkSessionExists = options?.checkSessionExists;
|
|
19593
19789
|
function getSessionState(sessionID) {
|
|
19594
19790
|
let state2 = sessions.get(sessionID);
|
|
19595
19791
|
if (!state2) {
|
|
@@ -19602,7 +19798,7 @@ function createRalphLoopHook(ctx, options) {
|
|
|
19602
19798
|
if (!transcriptPath)
|
|
19603
19799
|
return false;
|
|
19604
19800
|
try {
|
|
19605
|
-
if (!
|
|
19801
|
+
if (!existsSync33(transcriptPath))
|
|
19606
19802
|
return false;
|
|
19607
19803
|
const content = readFileSync21(transcriptPath, "utf-8");
|
|
19608
19804
|
const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
|
|
@@ -19616,28 +19812,26 @@ function createRalphLoopHook(ctx, options) {
|
|
|
19616
19812
|
}
|
|
19617
19813
|
async function detectCompletionInSessionMessages(sessionID, promise) {
|
|
19618
19814
|
try {
|
|
19619
|
-
const response = await
|
|
19620
|
-
|
|
19621
|
-
|
|
19622
|
-
|
|
19815
|
+
const response = await Promise.race([
|
|
19816
|
+
ctx.client.session.messages({
|
|
19817
|
+
path: { id: sessionID },
|
|
19818
|
+
query: { directory: ctx.directory }
|
|
19819
|
+
}),
|
|
19820
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("API timeout")), apiTimeout))
|
|
19821
|
+
]);
|
|
19623
19822
|
const messages = response.data ?? [];
|
|
19624
19823
|
if (!Array.isArray(messages))
|
|
19625
19824
|
return false;
|
|
19825
|
+
const assistantMessages = messages.filter((msg) => msg.info?.role === "assistant");
|
|
19826
|
+
const lastAssistant = assistantMessages[assistantMessages.length - 1];
|
|
19827
|
+
if (!lastAssistant?.parts)
|
|
19828
|
+
return false;
|
|
19626
19829
|
const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
|
|
19627
|
-
|
|
19628
|
-
|
|
19629
|
-
|
|
19630
|
-
for (const part of msg.parts || []) {
|
|
19631
|
-
if (part.type === "text" && part.text) {
|
|
19632
|
-
if (pattern.test(part.text)) {
|
|
19633
|
-
return true;
|
|
19634
|
-
}
|
|
19635
|
-
}
|
|
19636
|
-
}
|
|
19637
|
-
}
|
|
19638
|
-
return false;
|
|
19830
|
+
const responseText = lastAssistant.parts.filter((p) => p.type === "text").map((p) => p.text ?? "").join(`
|
|
19831
|
+
`);
|
|
19832
|
+
return pattern.test(responseText);
|
|
19639
19833
|
} catch (err) {
|
|
19640
|
-
log(`[${HOOK_NAME3}]
|
|
19834
|
+
log(`[${HOOK_NAME3}] Session messages check failed`, { sessionID, error: String(err) });
|
|
19641
19835
|
return false;
|
|
19642
19836
|
}
|
|
19643
19837
|
}
|
|
@@ -19693,17 +19887,35 @@ function createRalphLoopHook(ctx, options) {
|
|
|
19693
19887
|
return;
|
|
19694
19888
|
}
|
|
19695
19889
|
if (state2.session_id && state2.session_id !== sessionID) {
|
|
19890
|
+
if (checkSessionExists) {
|
|
19891
|
+
try {
|
|
19892
|
+
const originalSessionExists = await checkSessionExists(state2.session_id);
|
|
19893
|
+
if (!originalSessionExists) {
|
|
19894
|
+
clearState(ctx.directory, stateDir);
|
|
19895
|
+
log(`[${HOOK_NAME3}] Cleared orphaned state from deleted session`, {
|
|
19896
|
+
orphanedSessionId: state2.session_id,
|
|
19897
|
+
currentSessionId: sessionID
|
|
19898
|
+
});
|
|
19899
|
+
return;
|
|
19900
|
+
}
|
|
19901
|
+
} catch (err) {
|
|
19902
|
+
log(`[${HOOK_NAME3}] Failed to check session existence`, {
|
|
19903
|
+
sessionId: state2.session_id,
|
|
19904
|
+
error: String(err)
|
|
19905
|
+
});
|
|
19906
|
+
}
|
|
19907
|
+
}
|
|
19696
19908
|
return;
|
|
19697
19909
|
}
|
|
19698
|
-
const completionDetectedViaApi = await detectCompletionInSessionMessages(sessionID, state2.completion_promise);
|
|
19699
19910
|
const transcriptPath = getTranscriptPath2(sessionID);
|
|
19700
19911
|
const completionDetectedViaTranscript = detectCompletionPromise(transcriptPath, state2.completion_promise);
|
|
19701
|
-
|
|
19912
|
+
const completionDetectedViaApi = completionDetectedViaTranscript ? false : await detectCompletionInSessionMessages(sessionID, state2.completion_promise);
|
|
19913
|
+
if (completionDetectedViaTranscript || completionDetectedViaApi) {
|
|
19702
19914
|
log(`[${HOOK_NAME3}] Completion detected!`, {
|
|
19703
19915
|
sessionID,
|
|
19704
19916
|
iteration: state2.iteration,
|
|
19705
19917
|
promise: state2.completion_promise,
|
|
19706
|
-
detectedVia:
|
|
19918
|
+
detectedVia: completionDetectedViaTranscript ? "transcript_file" : "session_messages_api"
|
|
19707
19919
|
});
|
|
19708
19920
|
clearState(ctx.directory, stateDir);
|
|
19709
19921
|
await ctx.client.tui.showToast({
|
|
@@ -19780,6 +19992,18 @@ function createRalphLoopHook(ctx, options) {
|
|
|
19780
19992
|
}
|
|
19781
19993
|
if (event2.type === "session.error") {
|
|
19782
19994
|
const sessionID = props?.sessionID;
|
|
19995
|
+
const error = props?.error;
|
|
19996
|
+
if (error?.name === "MessageAbortedError") {
|
|
19997
|
+
if (sessionID) {
|
|
19998
|
+
const state2 = readState(ctx.directory, stateDir);
|
|
19999
|
+
if (state2?.session_id === sessionID) {
|
|
20000
|
+
clearState(ctx.directory, stateDir);
|
|
20001
|
+
log(`[${HOOK_NAME3}] User aborted, loop cleared`, { sessionID });
|
|
20002
|
+
}
|
|
20003
|
+
sessions.delete(sessionID);
|
|
20004
|
+
}
|
|
20005
|
+
return;
|
|
20006
|
+
}
|
|
19783
20007
|
if (sessionID) {
|
|
19784
20008
|
const sessionState = getSessionState(sessionID);
|
|
19785
20009
|
sessionState.isRecovering = true;
|
|
@@ -19849,11 +20073,12 @@ function extractPromptText3(parts) {
|
|
|
19849
20073
|
}
|
|
19850
20074
|
|
|
19851
20075
|
// src/hooks/auto-slash-command/executor.ts
|
|
19852
|
-
import { existsSync as
|
|
20076
|
+
import { existsSync as existsSync36, readdirSync as readdirSync12, readFileSync as readFileSync24 } from "fs";
|
|
19853
20077
|
import { join as join43, basename as basename2, dirname as dirname8 } from "path";
|
|
19854
20078
|
import { homedir as homedir13 } from "os";
|
|
19855
20079
|
// src/features/opencode-skill-loader/loader.ts
|
|
19856
|
-
import { existsSync as
|
|
20080
|
+
import { existsSync as existsSync34, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
20081
|
+
import { promises as fs9 } from "fs";
|
|
19857
20082
|
import { join as join42, basename } from "path";
|
|
19858
20083
|
import { homedir as homedir11 } from "os";
|
|
19859
20084
|
function parseSkillMcpConfigFromFrontmatter(content) {
|
|
@@ -19872,7 +20097,7 @@ function parseSkillMcpConfigFromFrontmatter(content) {
|
|
|
19872
20097
|
}
|
|
19873
20098
|
function loadMcpJsonFromDir(skillDir) {
|
|
19874
20099
|
const mcpJsonPath = join42(skillDir, "mcp.json");
|
|
19875
|
-
if (!
|
|
20100
|
+
if (!existsSync34(mcpJsonPath))
|
|
19876
20101
|
return;
|
|
19877
20102
|
try {
|
|
19878
20103
|
const content = readFileSync22(mcpJsonPath, "utf-8");
|
|
@@ -19899,7 +20124,66 @@ function parseAllowedTools(allowedTools) {
|
|
|
19899
20124
|
function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
|
|
19900
20125
|
try {
|
|
19901
20126
|
const content = readFileSync22(skillPath, "utf-8");
|
|
19902
|
-
const { data
|
|
20127
|
+
const { data } = parseFrontmatter(content);
|
|
20128
|
+
const frontmatterMcp = parseSkillMcpConfigFromFrontmatter(content);
|
|
20129
|
+
const mcpJsonMcp = loadMcpJsonFromDir(resolvedPath);
|
|
20130
|
+
const mcpConfig = mcpJsonMcp || frontmatterMcp;
|
|
20131
|
+
const skillName = data.name || defaultName;
|
|
20132
|
+
const originalDescription = data.description || "";
|
|
20133
|
+
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
20134
|
+
const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
|
|
20135
|
+
const lazyContent = {
|
|
20136
|
+
loaded: false,
|
|
20137
|
+
content: undefined,
|
|
20138
|
+
load: async () => {
|
|
20139
|
+
if (!lazyContent.loaded) {
|
|
20140
|
+
const fileContent = await fs9.readFile(skillPath, "utf-8");
|
|
20141
|
+
const { body } = parseFrontmatter(fileContent);
|
|
20142
|
+
lazyContent.content = `<skill-instruction>
|
|
20143
|
+
Base directory for this skill: ${resolvedPath}/
|
|
20144
|
+
File references (@path) in this skill are relative to this directory.
|
|
20145
|
+
|
|
20146
|
+
${body.trim()}
|
|
20147
|
+
</skill-instruction>
|
|
20148
|
+
|
|
20149
|
+
<user-request>
|
|
20150
|
+
$ARGUMENTS
|
|
20151
|
+
</user-request>`;
|
|
20152
|
+
lazyContent.loaded = true;
|
|
20153
|
+
}
|
|
20154
|
+
return lazyContent.content;
|
|
20155
|
+
}
|
|
20156
|
+
};
|
|
20157
|
+
const definition = {
|
|
20158
|
+
name: skillName,
|
|
20159
|
+
description: formattedDescription,
|
|
20160
|
+
template: "",
|
|
20161
|
+
model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
|
|
20162
|
+
agent: data.agent,
|
|
20163
|
+
subtask: data.subtask,
|
|
20164
|
+
argumentHint: data["argument-hint"]
|
|
20165
|
+
};
|
|
20166
|
+
return {
|
|
20167
|
+
name: skillName,
|
|
20168
|
+
path: skillPath,
|
|
20169
|
+
resolvedPath,
|
|
20170
|
+
definition,
|
|
20171
|
+
scope,
|
|
20172
|
+
license: data.license,
|
|
20173
|
+
compatibility: data.compatibility,
|
|
20174
|
+
metadata: data.metadata,
|
|
20175
|
+
allowedTools: parseAllowedTools(data["allowed-tools"]),
|
|
20176
|
+
mcpConfig,
|
|
20177
|
+
lazyContent
|
|
20178
|
+
};
|
|
20179
|
+
} catch {
|
|
20180
|
+
return null;
|
|
20181
|
+
}
|
|
20182
|
+
}
|
|
20183
|
+
async function loadSkillFromPathAsync(skillPath, resolvedPath, defaultName, scope) {
|
|
20184
|
+
try {
|
|
20185
|
+
const content = await fs9.readFile(skillPath, "utf-8");
|
|
20186
|
+
const { data } = parseFrontmatter(content);
|
|
19903
20187
|
const frontmatterMcp = parseSkillMcpConfigFromFrontmatter(content);
|
|
19904
20188
|
const mcpJsonMcp = loadMcpJsonFromDir(resolvedPath);
|
|
19905
20189
|
const mcpConfig = mcpJsonMcp || frontmatterMcp;
|
|
@@ -19907,7 +20191,14 @@ function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
|
|
|
19907
20191
|
const originalDescription = data.description || "";
|
|
19908
20192
|
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
19909
20193
|
const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
|
|
19910
|
-
const
|
|
20194
|
+
const lazyContent = {
|
|
20195
|
+
loaded: false,
|
|
20196
|
+
content: undefined,
|
|
20197
|
+
load: async () => {
|
|
20198
|
+
if (!lazyContent.loaded) {
|
|
20199
|
+
const fileContent = await fs9.readFile(skillPath, "utf-8");
|
|
20200
|
+
const { body } = parseFrontmatter(fileContent);
|
|
20201
|
+
lazyContent.content = `<skill-instruction>
|
|
19911
20202
|
Base directory for this skill: ${resolvedPath}/
|
|
19912
20203
|
File references (@path) in this skill are relative to this directory.
|
|
19913
20204
|
|
|
@@ -19917,10 +20208,15 @@ ${body.trim()}
|
|
|
19917
20208
|
<user-request>
|
|
19918
20209
|
$ARGUMENTS
|
|
19919
20210
|
</user-request>`;
|
|
20211
|
+
lazyContent.loaded = true;
|
|
20212
|
+
}
|
|
20213
|
+
return lazyContent.content;
|
|
20214
|
+
}
|
|
20215
|
+
};
|
|
19920
20216
|
const definition = {
|
|
19921
20217
|
name: skillName,
|
|
19922
20218
|
description: formattedDescription,
|
|
19923
|
-
template:
|
|
20219
|
+
template: "",
|
|
19924
20220
|
model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
|
|
19925
20221
|
agent: data.agent,
|
|
19926
20222
|
subtask: data.subtask,
|
|
@@ -19936,14 +20232,15 @@ $ARGUMENTS
|
|
|
19936
20232
|
compatibility: data.compatibility,
|
|
19937
20233
|
metadata: data.metadata,
|
|
19938
20234
|
allowedTools: parseAllowedTools(data["allowed-tools"]),
|
|
19939
|
-
mcpConfig
|
|
20235
|
+
mcpConfig,
|
|
20236
|
+
lazyContent
|
|
19940
20237
|
};
|
|
19941
20238
|
} catch {
|
|
19942
20239
|
return null;
|
|
19943
20240
|
}
|
|
19944
20241
|
}
|
|
19945
20242
|
function loadSkillsFromDir(skillsDir, scope) {
|
|
19946
|
-
if (!
|
|
20243
|
+
if (!existsSync34(skillsDir)) {
|
|
19947
20244
|
return [];
|
|
19948
20245
|
}
|
|
19949
20246
|
const entries = readdirSync11(skillsDir, { withFileTypes: true });
|
|
@@ -19956,14 +20253,14 @@ function loadSkillsFromDir(skillsDir, scope) {
|
|
|
19956
20253
|
const resolvedPath = resolveSymlink(entryPath);
|
|
19957
20254
|
const dirName = entry.name;
|
|
19958
20255
|
const skillMdPath = join42(resolvedPath, "SKILL.md");
|
|
19959
|
-
if (
|
|
20256
|
+
if (existsSync34(skillMdPath)) {
|
|
19960
20257
|
const skill = loadSkillFromPath(skillMdPath, resolvedPath, dirName, scope);
|
|
19961
20258
|
if (skill)
|
|
19962
20259
|
skills.push(skill);
|
|
19963
20260
|
continue;
|
|
19964
20261
|
}
|
|
19965
20262
|
const namedSkillMdPath = join42(resolvedPath, `${dirName}.md`);
|
|
19966
|
-
if (
|
|
20263
|
+
if (existsSync34(namedSkillMdPath)) {
|
|
19967
20264
|
const skill = loadSkillFromPath(namedSkillMdPath, resolvedPath, dirName, scope);
|
|
19968
20265
|
if (skill)
|
|
19969
20266
|
skills.push(skill);
|
|
@@ -19980,6 +20277,43 @@ function loadSkillsFromDir(skillsDir, scope) {
|
|
|
19980
20277
|
}
|
|
19981
20278
|
return skills;
|
|
19982
20279
|
}
|
|
20280
|
+
async function loadSkillsFromDirAsync(skillsDir, scope) {
|
|
20281
|
+
const entries = await fs9.readdir(skillsDir, { withFileTypes: true }).catch(() => []);
|
|
20282
|
+
const skills = [];
|
|
20283
|
+
for (const entry of entries) {
|
|
20284
|
+
if (entry.name.startsWith("."))
|
|
20285
|
+
continue;
|
|
20286
|
+
const entryPath = join42(skillsDir, entry.name);
|
|
20287
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
20288
|
+
const resolvedPath = resolveSymlink(entryPath);
|
|
20289
|
+
const dirName = entry.name;
|
|
20290
|
+
const skillMdPath = join42(resolvedPath, "SKILL.md");
|
|
20291
|
+
try {
|
|
20292
|
+
await fs9.access(skillMdPath);
|
|
20293
|
+
const skill = await loadSkillFromPathAsync(skillMdPath, resolvedPath, dirName, scope);
|
|
20294
|
+
if (skill)
|
|
20295
|
+
skills.push(skill);
|
|
20296
|
+
continue;
|
|
20297
|
+
} catch {}
|
|
20298
|
+
const namedSkillMdPath = join42(resolvedPath, `${dirName}.md`);
|
|
20299
|
+
try {
|
|
20300
|
+
await fs9.access(namedSkillMdPath);
|
|
20301
|
+
const skill = await loadSkillFromPathAsync(namedSkillMdPath, resolvedPath, dirName, scope);
|
|
20302
|
+
if (skill)
|
|
20303
|
+
skills.push(skill);
|
|
20304
|
+
continue;
|
|
20305
|
+
} catch {}
|
|
20306
|
+
continue;
|
|
20307
|
+
}
|
|
20308
|
+
if (isMarkdownFile(entry)) {
|
|
20309
|
+
const skillName = basename(entry.name, ".md");
|
|
20310
|
+
const skill = await loadSkillFromPathAsync(entryPath, skillsDir, skillName, scope);
|
|
20311
|
+
if (skill)
|
|
20312
|
+
skills.push(skill);
|
|
20313
|
+
}
|
|
20314
|
+
}
|
|
20315
|
+
return skills;
|
|
20316
|
+
}
|
|
19983
20317
|
function skillsToRecord(skills) {
|
|
19984
20318
|
const result = {};
|
|
19985
20319
|
for (const skill of skills) {
|
|
@@ -20034,24 +20368,24 @@ function discoverSkills(options = {}) {
|
|
|
20034
20368
|
const userSkills = loadSkillsFromDir(userDir, "user");
|
|
20035
20369
|
return [...opencodeProjectSkills, ...projectSkills, ...opencodeGlobalSkills, ...userSkills];
|
|
20036
20370
|
}
|
|
20037
|
-
function
|
|
20371
|
+
async function discoverUserClaudeSkillsAsync() {
|
|
20038
20372
|
const userSkillsDir = join42(getClaudeConfigDir(), "skills");
|
|
20039
|
-
return
|
|
20373
|
+
return loadSkillsFromDirAsync(userSkillsDir, "user");
|
|
20040
20374
|
}
|
|
20041
|
-
function
|
|
20375
|
+
async function discoverProjectClaudeSkillsAsync() {
|
|
20042
20376
|
const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
|
|
20043
|
-
return
|
|
20377
|
+
return loadSkillsFromDirAsync(projectSkillsDir, "project");
|
|
20044
20378
|
}
|
|
20045
|
-
function
|
|
20379
|
+
async function discoverOpencodeGlobalSkillsAsync() {
|
|
20046
20380
|
const opencodeSkillsDir = join42(homedir11(), ".config", "opencode", "skill");
|
|
20047
|
-
return
|
|
20381
|
+
return loadSkillsFromDirAsync(opencodeSkillsDir, "opencode");
|
|
20048
20382
|
}
|
|
20049
|
-
function
|
|
20383
|
+
async function discoverOpencodeProjectSkillsAsync() {
|
|
20050
20384
|
const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
|
|
20051
|
-
return
|
|
20385
|
+
return loadSkillsFromDirAsync(opencodeProjectDir, "opencode-project");
|
|
20052
20386
|
}
|
|
20053
20387
|
// src/features/opencode-skill-loader/merger.ts
|
|
20054
|
-
import { readFileSync as readFileSync23, existsSync as
|
|
20388
|
+
import { readFileSync as readFileSync23, existsSync as existsSync35 } from "fs";
|
|
20055
20389
|
import { dirname as dirname7, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
|
|
20056
20390
|
import { homedir as homedir12 } from "os";
|
|
20057
20391
|
var SCOPE_PRIORITY = {
|
|
@@ -20099,7 +20433,7 @@ function resolveFilePath2(from, configDir) {
|
|
|
20099
20433
|
}
|
|
20100
20434
|
function loadSkillFromFile(filePath) {
|
|
20101
20435
|
try {
|
|
20102
|
-
if (!
|
|
20436
|
+
if (!existsSync35(filePath))
|
|
20103
20437
|
return null;
|
|
20104
20438
|
const content = readFileSync23(filePath, "utf-8");
|
|
20105
20439
|
const { data, body } = parseFrontmatter(content);
|
|
@@ -20255,7 +20589,7 @@ function mergeSkills(builtinSkills, config, userClaudeSkills, userOpencodeSkills
|
|
|
20255
20589
|
}
|
|
20256
20590
|
// src/hooks/auto-slash-command/executor.ts
|
|
20257
20591
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
20258
|
-
if (!
|
|
20592
|
+
if (!existsSync36(commandsDir)) {
|
|
20259
20593
|
return [];
|
|
20260
20594
|
}
|
|
20261
20595
|
const entries = readdirSync12(commandsDir, { withFileTypes: true });
|
|
@@ -20478,6 +20812,147 @@ ${EDIT_ERROR_REMINDER}`;
|
|
|
20478
20812
|
}
|
|
20479
20813
|
};
|
|
20480
20814
|
}
|
|
20815
|
+
// src/features/context-injector/collector.ts
|
|
20816
|
+
var PRIORITY_ORDER = {
|
|
20817
|
+
critical: 0,
|
|
20818
|
+
high: 1,
|
|
20819
|
+
normal: 2,
|
|
20820
|
+
low: 3
|
|
20821
|
+
};
|
|
20822
|
+
var CONTEXT_SEPARATOR = `
|
|
20823
|
+
|
|
20824
|
+
---
|
|
20825
|
+
|
|
20826
|
+
`;
|
|
20827
|
+
|
|
20828
|
+
class ContextCollector {
|
|
20829
|
+
sessions = new Map;
|
|
20830
|
+
register(sessionID, options) {
|
|
20831
|
+
if (!this.sessions.has(sessionID)) {
|
|
20832
|
+
this.sessions.set(sessionID, new Map);
|
|
20833
|
+
}
|
|
20834
|
+
const sessionMap = this.sessions.get(sessionID);
|
|
20835
|
+
const key = `${options.source}:${options.id}`;
|
|
20836
|
+
const entry = {
|
|
20837
|
+
id: options.id,
|
|
20838
|
+
source: options.source,
|
|
20839
|
+
content: options.content,
|
|
20840
|
+
priority: options.priority ?? "normal",
|
|
20841
|
+
timestamp: Date.now(),
|
|
20842
|
+
metadata: options.metadata
|
|
20843
|
+
};
|
|
20844
|
+
sessionMap.set(key, entry);
|
|
20845
|
+
}
|
|
20846
|
+
getPending(sessionID) {
|
|
20847
|
+
const sessionMap = this.sessions.get(sessionID);
|
|
20848
|
+
if (!sessionMap || sessionMap.size === 0) {
|
|
20849
|
+
return {
|
|
20850
|
+
merged: "",
|
|
20851
|
+
entries: [],
|
|
20852
|
+
hasContent: false
|
|
20853
|
+
};
|
|
20854
|
+
}
|
|
20855
|
+
const entries = this.sortEntries([...sessionMap.values()]);
|
|
20856
|
+
const merged = entries.map((e) => e.content).join(CONTEXT_SEPARATOR);
|
|
20857
|
+
return {
|
|
20858
|
+
merged,
|
|
20859
|
+
entries,
|
|
20860
|
+
hasContent: entries.length > 0
|
|
20861
|
+
};
|
|
20862
|
+
}
|
|
20863
|
+
consume(sessionID) {
|
|
20864
|
+
const pending = this.getPending(sessionID);
|
|
20865
|
+
this.clear(sessionID);
|
|
20866
|
+
return pending;
|
|
20867
|
+
}
|
|
20868
|
+
clear(sessionID) {
|
|
20869
|
+
this.sessions.delete(sessionID);
|
|
20870
|
+
}
|
|
20871
|
+
hasPending(sessionID) {
|
|
20872
|
+
const sessionMap = this.sessions.get(sessionID);
|
|
20873
|
+
return sessionMap !== undefined && sessionMap.size > 0;
|
|
20874
|
+
}
|
|
20875
|
+
sortEntries(entries) {
|
|
20876
|
+
return entries.sort((a, b) => {
|
|
20877
|
+
const priorityDiff = PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority];
|
|
20878
|
+
if (priorityDiff !== 0)
|
|
20879
|
+
return priorityDiff;
|
|
20880
|
+
return a.timestamp - b.timestamp;
|
|
20881
|
+
});
|
|
20882
|
+
}
|
|
20883
|
+
}
|
|
20884
|
+
var contextCollector = new ContextCollector;
|
|
20885
|
+
// src/features/context-injector/injector.ts
|
|
20886
|
+
function createContextInjectorHook(collector) {
|
|
20887
|
+
return {
|
|
20888
|
+
"chat.message": async (_input, _output) => {}
|
|
20889
|
+
};
|
|
20890
|
+
}
|
|
20891
|
+
function createContextInjectorMessagesTransformHook(collector) {
|
|
20892
|
+
return {
|
|
20893
|
+
"experimental.chat.messages.transform": async (_input, output) => {
|
|
20894
|
+
const { messages } = output;
|
|
20895
|
+
if (messages.length === 0) {
|
|
20896
|
+
return;
|
|
20897
|
+
}
|
|
20898
|
+
let lastUserMessageIndex = -1;
|
|
20899
|
+
for (let i2 = messages.length - 1;i2 >= 0; i2--) {
|
|
20900
|
+
if (messages[i2].info.role === "user") {
|
|
20901
|
+
lastUserMessageIndex = i2;
|
|
20902
|
+
break;
|
|
20903
|
+
}
|
|
20904
|
+
}
|
|
20905
|
+
if (lastUserMessageIndex === -1) {
|
|
20906
|
+
return;
|
|
20907
|
+
}
|
|
20908
|
+
const lastUserMessage = messages[lastUserMessageIndex];
|
|
20909
|
+
const sessionID = lastUserMessage.info.sessionID;
|
|
20910
|
+
if (!sessionID) {
|
|
20911
|
+
return;
|
|
20912
|
+
}
|
|
20913
|
+
if (!collector.hasPending(sessionID)) {
|
|
20914
|
+
return;
|
|
20915
|
+
}
|
|
20916
|
+
const pending = collector.consume(sessionID);
|
|
20917
|
+
if (!pending.hasContent) {
|
|
20918
|
+
return;
|
|
20919
|
+
}
|
|
20920
|
+
const refInfo = lastUserMessage.info;
|
|
20921
|
+
const syntheticMessageId = `synthetic_ctx_${Date.now()}`;
|
|
20922
|
+
const syntheticPartId = `synthetic_ctx_part_${Date.now()}`;
|
|
20923
|
+
const now = Date.now();
|
|
20924
|
+
const syntheticMessage = {
|
|
20925
|
+
info: {
|
|
20926
|
+
id: syntheticMessageId,
|
|
20927
|
+
sessionID,
|
|
20928
|
+
role: "user",
|
|
20929
|
+
time: { created: now },
|
|
20930
|
+
agent: refInfo.agent ?? "Sisyphus",
|
|
20931
|
+
model: refInfo.model ?? { providerID: "unknown", modelID: "unknown" },
|
|
20932
|
+
path: refInfo.path ?? { cwd: "/", root: "/" }
|
|
20933
|
+
},
|
|
20934
|
+
parts: [
|
|
20935
|
+
{
|
|
20936
|
+
id: syntheticPartId,
|
|
20937
|
+
sessionID,
|
|
20938
|
+
messageID: syntheticMessageId,
|
|
20939
|
+
type: "text",
|
|
20940
|
+
text: pending.merged,
|
|
20941
|
+
synthetic: true,
|
|
20942
|
+
time: { start: now, end: now }
|
|
20943
|
+
}
|
|
20944
|
+
]
|
|
20945
|
+
};
|
|
20946
|
+
messages.splice(lastUserMessageIndex, 0, syntheticMessage);
|
|
20947
|
+
log("[context-injector] Injected synthetic message from collector", {
|
|
20948
|
+
sessionID,
|
|
20949
|
+
insertIndex: lastUserMessageIndex,
|
|
20950
|
+
contextLength: pending.merged.length,
|
|
20951
|
+
newMessageCount: messages.length
|
|
20952
|
+
});
|
|
20953
|
+
}
|
|
20954
|
+
};
|
|
20955
|
+
}
|
|
20481
20956
|
// src/auth/antigravity/constants.ts
|
|
20482
20957
|
var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
20483
20958
|
var ANTIGRAVITY_CLIENT_SECRET = "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";
|
|
@@ -22325,7 +22800,7 @@ function createBuiltinSkills() {
|
|
|
22325
22800
|
return [playwrightSkill];
|
|
22326
22801
|
}
|
|
22327
22802
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
22328
|
-
import { existsSync as
|
|
22803
|
+
import { existsSync as existsSync37, readFileSync as readFileSync25 } from "fs";
|
|
22329
22804
|
import { join as join44 } from "path";
|
|
22330
22805
|
|
|
22331
22806
|
// src/features/claude-code-mcp-loader/env-expander.ts
|
|
@@ -22401,7 +22876,7 @@ function getMcpConfigPaths() {
|
|
|
22401
22876
|
];
|
|
22402
22877
|
}
|
|
22403
22878
|
async function loadMcpConfigFile(filePath) {
|
|
22404
|
-
if (!
|
|
22879
|
+
if (!existsSync37(filePath)) {
|
|
22405
22880
|
return null;
|
|
22406
22881
|
}
|
|
22407
22882
|
try {
|
|
@@ -22416,7 +22891,7 @@ function getSystemMcpServerNames() {
|
|
|
22416
22891
|
const names = new Set;
|
|
22417
22892
|
const paths = getMcpConfigPaths();
|
|
22418
22893
|
for (const { path: path7 } of paths) {
|
|
22419
|
-
if (!
|
|
22894
|
+
if (!existsSync37(path7))
|
|
22420
22895
|
continue;
|
|
22421
22896
|
try {
|
|
22422
22897
|
const content = readFileSync25(path7, "utf-8");
|
|
@@ -22529,7 +23004,18 @@ var LSP_INSTALL_HINTS = {
|
|
|
22529
23004
|
"lua-ls": "See https://github.com/LuaLS/lua-language-server",
|
|
22530
23005
|
php: "npm install -g intelephense",
|
|
22531
23006
|
dart: "Included with Dart SDK",
|
|
22532
|
-
"terraform-ls": "See https://github.com/hashicorp/terraform-ls"
|
|
23007
|
+
"terraform-ls": "See https://github.com/hashicorp/terraform-ls",
|
|
23008
|
+
terraform: "See https://github.com/hashicorp/terraform-ls",
|
|
23009
|
+
prisma: "npm install -g prisma",
|
|
23010
|
+
"ocaml-lsp": "opam install ocaml-lsp-server",
|
|
23011
|
+
texlab: "See https://github.com/latex-lsp/texlab",
|
|
23012
|
+
dockerfile: "npm install -g dockerfile-language-server-nodejs",
|
|
23013
|
+
gleam: "See https://gleam.run/getting-started/installing/",
|
|
23014
|
+
"clojure-lsp": "See https://clojure-lsp.io/installation/",
|
|
23015
|
+
nixd: "nix profile install nixpkgs#nixd",
|
|
23016
|
+
tinymist: "See https://github.com/Myriad-Dreamin/tinymist",
|
|
23017
|
+
"haskell-language-server": "ghcup install hls",
|
|
23018
|
+
bash: "npm install -g bash-language-server"
|
|
22533
23019
|
};
|
|
22534
23020
|
var BUILTIN_SERVERS = {
|
|
22535
23021
|
typescript: {
|
|
@@ -22634,6 +23120,10 @@ var BUILTIN_SERVERS = {
|
|
|
22634
23120
|
command: ["astro-ls", "--stdio"],
|
|
22635
23121
|
extensions: [".astro"]
|
|
22636
23122
|
},
|
|
23123
|
+
bash: {
|
|
23124
|
+
command: ["bash-language-server", "start"],
|
|
23125
|
+
extensions: [".sh", ".bash", ".zsh", ".ksh"]
|
|
23126
|
+
},
|
|
22637
23127
|
"bash-ls": {
|
|
22638
23128
|
command: ["bash-language-server", "start"],
|
|
22639
23129
|
extensions: [".sh", ".bash", ".zsh", ".ksh"]
|
|
@@ -22658,9 +23148,49 @@ var BUILTIN_SERVERS = {
|
|
|
22658
23148
|
command: ["dart", "language-server", "--lsp"],
|
|
22659
23149
|
extensions: [".dart"]
|
|
22660
23150
|
},
|
|
23151
|
+
terraform: {
|
|
23152
|
+
command: ["terraform-ls", "serve"],
|
|
23153
|
+
extensions: [".tf", ".tfvars"]
|
|
23154
|
+
},
|
|
22661
23155
|
"terraform-ls": {
|
|
22662
23156
|
command: ["terraform-ls", "serve"],
|
|
22663
23157
|
extensions: [".tf", ".tfvars"]
|
|
23158
|
+
},
|
|
23159
|
+
prisma: {
|
|
23160
|
+
command: ["prisma", "language-server"],
|
|
23161
|
+
extensions: [".prisma"]
|
|
23162
|
+
},
|
|
23163
|
+
"ocaml-lsp": {
|
|
23164
|
+
command: ["ocamllsp"],
|
|
23165
|
+
extensions: [".ml", ".mli"]
|
|
23166
|
+
},
|
|
23167
|
+
texlab: {
|
|
23168
|
+
command: ["texlab"],
|
|
23169
|
+
extensions: [".tex", ".bib"]
|
|
23170
|
+
},
|
|
23171
|
+
dockerfile: {
|
|
23172
|
+
command: ["docker-langserver", "--stdio"],
|
|
23173
|
+
extensions: [".dockerfile"]
|
|
23174
|
+
},
|
|
23175
|
+
gleam: {
|
|
23176
|
+
command: ["gleam", "lsp"],
|
|
23177
|
+
extensions: [".gleam"]
|
|
23178
|
+
},
|
|
23179
|
+
"clojure-lsp": {
|
|
23180
|
+
command: ["clojure-lsp", "listen"],
|
|
23181
|
+
extensions: [".clj", ".cljs", ".cljc", ".edn"]
|
|
23182
|
+
},
|
|
23183
|
+
nixd: {
|
|
23184
|
+
command: ["nixd"],
|
|
23185
|
+
extensions: [".nix"]
|
|
23186
|
+
},
|
|
23187
|
+
tinymist: {
|
|
23188
|
+
command: ["tinymist"],
|
|
23189
|
+
extensions: [".typ", ".typc"]
|
|
23190
|
+
},
|
|
23191
|
+
"haskell-language-server": {
|
|
23192
|
+
command: ["haskell-language-server-wrapper", "--lsp"],
|
|
23193
|
+
extensions: [".hs", ".lhs"]
|
|
22664
23194
|
}
|
|
22665
23195
|
};
|
|
22666
23196
|
var EXT_TO_LANG = {
|
|
@@ -22776,6 +23306,14 @@ var EXT_TO_LANG = {
|
|
|
22776
23306
|
".tf": "terraform",
|
|
22777
23307
|
".tfvars": "terraform-vars",
|
|
22778
23308
|
".hcl": "hcl",
|
|
23309
|
+
".nix": "nix",
|
|
23310
|
+
".typ": "typst",
|
|
23311
|
+
".typc": "typst",
|
|
23312
|
+
".ets": "typescript",
|
|
23313
|
+
".lhs": "haskell",
|
|
23314
|
+
".kt": "kotlin",
|
|
23315
|
+
".kts": "kotlin",
|
|
23316
|
+
".prisma": "prisma",
|
|
22779
23317
|
".h": "c",
|
|
22780
23318
|
".hpp": "cpp",
|
|
22781
23319
|
".hh": "cpp",
|
|
@@ -22788,11 +23326,11 @@ var EXT_TO_LANG = {
|
|
|
22788
23326
|
".gql": "graphql"
|
|
22789
23327
|
};
|
|
22790
23328
|
// src/tools/lsp/config.ts
|
|
22791
|
-
import { existsSync as
|
|
23329
|
+
import { existsSync as existsSync38, readFileSync as readFileSync26 } from "fs";
|
|
22792
23330
|
import { join as join45 } from "path";
|
|
22793
23331
|
import { homedir as homedir14 } from "os";
|
|
22794
23332
|
function loadJsonFile(path7) {
|
|
22795
|
-
if (!
|
|
23333
|
+
if (!existsSync38(path7))
|
|
22796
23334
|
return null;
|
|
22797
23335
|
try {
|
|
22798
23336
|
return JSON.parse(readFileSync26(path7, "utf-8"));
|
|
@@ -22918,7 +23456,7 @@ function isServerInstalled(command) {
|
|
|
22918
23456
|
return false;
|
|
22919
23457
|
const cmd = command[0];
|
|
22920
23458
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
22921
|
-
if (
|
|
23459
|
+
if (existsSync38(cmd))
|
|
22922
23460
|
return true;
|
|
22923
23461
|
}
|
|
22924
23462
|
const isWindows2 = process.platform === "win32";
|
|
@@ -22927,7 +23465,7 @@ function isServerInstalled(command) {
|
|
|
22927
23465
|
const pathSeparator = isWindows2 ? ";" : ":";
|
|
22928
23466
|
const paths = pathEnv.split(pathSeparator);
|
|
22929
23467
|
for (const p of paths) {
|
|
22930
|
-
if (
|
|
23468
|
+
if (existsSync38(join45(p, cmd)) || existsSync38(join45(p, cmd + ext))) {
|
|
22931
23469
|
return true;
|
|
22932
23470
|
}
|
|
22933
23471
|
}
|
|
@@ -22941,7 +23479,7 @@ function isServerInstalled(command) {
|
|
|
22941
23479
|
join45(homedir14(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
|
|
22942
23480
|
];
|
|
22943
23481
|
for (const p of additionalPaths) {
|
|
22944
|
-
if (
|
|
23482
|
+
if (existsSync38(p)) {
|
|
22945
23483
|
return true;
|
|
22946
23484
|
}
|
|
22947
23485
|
}
|
|
@@ -23540,16 +24078,16 @@ ${msg}`);
|
|
|
23540
24078
|
// src/tools/lsp/utils.ts
|
|
23541
24079
|
import { extname as extname2, resolve as resolve7 } from "path";
|
|
23542
24080
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23543
|
-
import { existsSync as
|
|
24081
|
+
import { existsSync as existsSync39, readFileSync as readFileSync28, writeFileSync as writeFileSync15 } from "fs";
|
|
23544
24082
|
function findWorkspaceRoot(filePath) {
|
|
23545
24083
|
let dir = resolve7(filePath);
|
|
23546
|
-
if (!
|
|
24084
|
+
if (!existsSync39(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
23547
24085
|
dir = __require("path").dirname(dir);
|
|
23548
24086
|
}
|
|
23549
24087
|
const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
23550
24088
|
while (dir !== "/") {
|
|
23551
24089
|
for (const marker of markers) {
|
|
23552
|
-
if (
|
|
24090
|
+
if (existsSync39(__require("path").join(dir, marker))) {
|
|
23553
24091
|
return dir;
|
|
23554
24092
|
}
|
|
23555
24093
|
}
|
|
@@ -36513,11 +37051,11 @@ var lsp_code_action_resolve = tool({
|
|
|
36513
37051
|
// src/tools/ast-grep/constants.ts
|
|
36514
37052
|
import { createRequire as createRequire4 } from "module";
|
|
36515
37053
|
import { dirname as dirname9, join as join47 } from "path";
|
|
36516
|
-
import { existsSync as
|
|
37054
|
+
import { existsSync as existsSync41, statSync as statSync4 } from "fs";
|
|
36517
37055
|
|
|
36518
37056
|
// src/tools/ast-grep/downloader.ts
|
|
36519
37057
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
36520
|
-
import { existsSync as
|
|
37058
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync11, chmodSync as chmodSync2, unlinkSync as unlinkSync10 } from "fs";
|
|
36521
37059
|
import { join as join46 } from "path";
|
|
36522
37060
|
import { homedir as homedir15 } from "os";
|
|
36523
37061
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -36556,7 +37094,7 @@ function getBinaryName3() {
|
|
|
36556
37094
|
}
|
|
36557
37095
|
function getCachedBinaryPath2() {
|
|
36558
37096
|
const binaryPath = join46(getCacheDir3(), getBinaryName3());
|
|
36559
|
-
return
|
|
37097
|
+
return existsSync40(binaryPath) ? binaryPath : null;
|
|
36560
37098
|
}
|
|
36561
37099
|
async function extractZip2(archivePath, destDir) {
|
|
36562
37100
|
const proc = process.platform === "win32" ? spawn6([
|
|
@@ -36583,7 +37121,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
36583
37121
|
const cacheDir = getCacheDir3();
|
|
36584
37122
|
const binaryName = getBinaryName3();
|
|
36585
37123
|
const binaryPath = join46(cacheDir, binaryName);
|
|
36586
|
-
if (
|
|
37124
|
+
if (existsSync40(binaryPath)) {
|
|
36587
37125
|
return binaryPath;
|
|
36588
37126
|
}
|
|
36589
37127
|
const { arch, os: os6 } = platformInfo;
|
|
@@ -36591,7 +37129,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
36591
37129
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
36592
37130
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
36593
37131
|
try {
|
|
36594
|
-
if (!
|
|
37132
|
+
if (!existsSync40(cacheDir)) {
|
|
36595
37133
|
mkdirSync11(cacheDir, { recursive: true });
|
|
36596
37134
|
}
|
|
36597
37135
|
const response2 = await fetch(downloadUrl, { redirect: "follow" });
|
|
@@ -36602,10 +37140,10 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
36602
37140
|
const arrayBuffer = await response2.arrayBuffer();
|
|
36603
37141
|
await Bun.write(archivePath, arrayBuffer);
|
|
36604
37142
|
await extractZip2(archivePath, cacheDir);
|
|
36605
|
-
if (
|
|
37143
|
+
if (existsSync40(archivePath)) {
|
|
36606
37144
|
unlinkSync10(archivePath);
|
|
36607
37145
|
}
|
|
36608
|
-
if (process.platform !== "win32" &&
|
|
37146
|
+
if (process.platform !== "win32" && existsSync40(binaryPath)) {
|
|
36609
37147
|
chmodSync2(binaryPath, 493);
|
|
36610
37148
|
}
|
|
36611
37149
|
console.log(`[oh-my-opencode] ast-grep binary ready.`);
|
|
@@ -36657,7 +37195,7 @@ function findSgCliPathSync() {
|
|
|
36657
37195
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
36658
37196
|
const cliDir = dirname9(cliPkgPath);
|
|
36659
37197
|
const sgPath = join47(cliDir, binaryName);
|
|
36660
|
-
if (
|
|
37198
|
+
if (existsSync41(sgPath) && isValidBinary(sgPath)) {
|
|
36661
37199
|
return sgPath;
|
|
36662
37200
|
}
|
|
36663
37201
|
} catch {}
|
|
@@ -36669,7 +37207,7 @@ function findSgCliPathSync() {
|
|
|
36669
37207
|
const pkgDir = dirname9(pkgPath);
|
|
36670
37208
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
36671
37209
|
const binaryPath = join47(pkgDir, astGrepName);
|
|
36672
|
-
if (
|
|
37210
|
+
if (existsSync41(binaryPath) && isValidBinary(binaryPath)) {
|
|
36673
37211
|
return binaryPath;
|
|
36674
37212
|
}
|
|
36675
37213
|
} catch {}
|
|
@@ -36677,7 +37215,7 @@ function findSgCliPathSync() {
|
|
|
36677
37215
|
if (process.platform === "darwin") {
|
|
36678
37216
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
36679
37217
|
for (const path7 of homebrewPaths) {
|
|
36680
|
-
if (
|
|
37218
|
+
if (existsSync41(path7) && isValidBinary(path7)) {
|
|
36681
37219
|
return path7;
|
|
36682
37220
|
}
|
|
36683
37221
|
}
|
|
@@ -36732,11 +37270,11 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
36732
37270
|
|
|
36733
37271
|
// src/tools/ast-grep/cli.ts
|
|
36734
37272
|
var {spawn: spawn7 } = globalThis.Bun;
|
|
36735
|
-
import { existsSync as
|
|
37273
|
+
import { existsSync as existsSync42 } from "fs";
|
|
36736
37274
|
var resolvedCliPath3 = null;
|
|
36737
37275
|
var initPromise2 = null;
|
|
36738
37276
|
async function getAstGrepPath() {
|
|
36739
|
-
if (resolvedCliPath3 !== null &&
|
|
37277
|
+
if (resolvedCliPath3 !== null && existsSync42(resolvedCliPath3)) {
|
|
36740
37278
|
return resolvedCliPath3;
|
|
36741
37279
|
}
|
|
36742
37280
|
if (initPromise2) {
|
|
@@ -36744,7 +37282,7 @@ async function getAstGrepPath() {
|
|
|
36744
37282
|
}
|
|
36745
37283
|
initPromise2 = (async () => {
|
|
36746
37284
|
const syncPath = findSgCliPathSync();
|
|
36747
|
-
if (syncPath &&
|
|
37285
|
+
if (syncPath && existsSync42(syncPath)) {
|
|
36748
37286
|
resolvedCliPath3 = syncPath;
|
|
36749
37287
|
setSgCliPath(syncPath);
|
|
36750
37288
|
return syncPath;
|
|
@@ -36778,7 +37316,7 @@ async function runSg(options) {
|
|
|
36778
37316
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
36779
37317
|
args.push(...paths);
|
|
36780
37318
|
let cliPath = getSgCliPath();
|
|
36781
|
-
if (!
|
|
37319
|
+
if (!existsSync42(cliPath) && cliPath !== "sg") {
|
|
36782
37320
|
const downloadedPath = await getAstGrepPath();
|
|
36783
37321
|
if (downloadedPath) {
|
|
36784
37322
|
cliPath = downloadedPath;
|
|
@@ -37042,12 +37580,12 @@ var ast_grep_replace = tool({
|
|
|
37042
37580
|
var {spawn: spawn9 } = globalThis.Bun;
|
|
37043
37581
|
|
|
37044
37582
|
// src/tools/grep/constants.ts
|
|
37045
|
-
import { existsSync as
|
|
37583
|
+
import { existsSync as existsSync44 } from "fs";
|
|
37046
37584
|
import { join as join49, dirname as dirname10 } from "path";
|
|
37047
37585
|
import { spawnSync } from "child_process";
|
|
37048
37586
|
|
|
37049
37587
|
// src/tools/grep/downloader.ts
|
|
37050
|
-
import { existsSync as
|
|
37588
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync12, chmodSync as chmodSync3, unlinkSync as unlinkSync11, readdirSync as readdirSync13 } from "fs";
|
|
37051
37589
|
import { join as join48 } from "path";
|
|
37052
37590
|
var {spawn: spawn8 } = globalThis.Bun;
|
|
37053
37591
|
function findFileRecursive(dir, filename) {
|
|
@@ -37157,7 +37695,7 @@ async function downloadAndInstallRipgrep() {
|
|
|
37157
37695
|
}
|
|
37158
37696
|
const installDir = getInstallDir();
|
|
37159
37697
|
const rgPath = getRgPath();
|
|
37160
|
-
if (
|
|
37698
|
+
if (existsSync43(rgPath)) {
|
|
37161
37699
|
return rgPath;
|
|
37162
37700
|
}
|
|
37163
37701
|
mkdirSync12(installDir, { recursive: true });
|
|
@@ -37174,12 +37712,12 @@ async function downloadAndInstallRipgrep() {
|
|
|
37174
37712
|
if (process.platform !== "win32") {
|
|
37175
37713
|
chmodSync3(rgPath, 493);
|
|
37176
37714
|
}
|
|
37177
|
-
if (!
|
|
37715
|
+
if (!existsSync43(rgPath)) {
|
|
37178
37716
|
throw new Error("ripgrep binary not found after extraction");
|
|
37179
37717
|
}
|
|
37180
37718
|
return rgPath;
|
|
37181
37719
|
} finally {
|
|
37182
|
-
if (
|
|
37720
|
+
if (existsSync43(archivePath)) {
|
|
37183
37721
|
try {
|
|
37184
37722
|
unlinkSync11(archivePath);
|
|
37185
37723
|
} catch {}
|
|
@@ -37188,7 +37726,7 @@ async function downloadAndInstallRipgrep() {
|
|
|
37188
37726
|
}
|
|
37189
37727
|
function getInstalledRipgrepPath() {
|
|
37190
37728
|
const rgPath = getRgPath();
|
|
37191
|
-
return
|
|
37729
|
+
return existsSync43(rgPath) ? rgPath : null;
|
|
37192
37730
|
}
|
|
37193
37731
|
|
|
37194
37732
|
// src/tools/grep/constants.ts
|
|
@@ -37219,7 +37757,7 @@ function getOpenCodeBundledRg() {
|
|
|
37219
37757
|
join49(execDir, "..", "libexec", rgName)
|
|
37220
37758
|
];
|
|
37221
37759
|
for (const candidate of candidates) {
|
|
37222
|
-
if (
|
|
37760
|
+
if (existsSync44(candidate)) {
|
|
37223
37761
|
return candidate;
|
|
37224
37762
|
}
|
|
37225
37763
|
}
|
|
@@ -37672,10 +38210,10 @@ var glob = tool({
|
|
|
37672
38210
|
}
|
|
37673
38211
|
});
|
|
37674
38212
|
// src/tools/slashcommand/tools.ts
|
|
37675
|
-
import { existsSync as
|
|
38213
|
+
import { existsSync as existsSync45, readdirSync as readdirSync14, readFileSync as readFileSync29 } from "fs";
|
|
37676
38214
|
import { join as join50, basename as basename3, dirname as dirname11 } from "path";
|
|
37677
38215
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
37678
|
-
if (!
|
|
38216
|
+
if (!existsSync45(commandsDir)) {
|
|
37679
38217
|
return [];
|
|
37680
38218
|
}
|
|
37681
38219
|
const entries = readdirSync14(commandsDir, { withFileTypes: true });
|
|
@@ -37924,11 +38462,11 @@ Has Todos: Yes (12 items, 8 completed)
|
|
|
37924
38462
|
Has Transcript: Yes (234 entries)`;
|
|
37925
38463
|
|
|
37926
38464
|
// src/tools/session-manager/storage.ts
|
|
37927
|
-
import { existsSync as
|
|
38465
|
+
import { existsSync as existsSync46, readdirSync as readdirSync15 } from "fs";
|
|
37928
38466
|
import { readdir, readFile } from "fs/promises";
|
|
37929
38467
|
import { join as join52 } from "path";
|
|
37930
38468
|
async function getMainSessions(options) {
|
|
37931
|
-
if (!
|
|
38469
|
+
if (!existsSync46(SESSION_STORAGE))
|
|
37932
38470
|
return [];
|
|
37933
38471
|
const sessions = [];
|
|
37934
38472
|
try {
|
|
@@ -37960,7 +38498,7 @@ async function getMainSessions(options) {
|
|
|
37960
38498
|
return sessions.sort((a, b) => b.time.updated - a.time.updated);
|
|
37961
38499
|
}
|
|
37962
38500
|
async function getAllSessions() {
|
|
37963
|
-
if (!
|
|
38501
|
+
if (!existsSync46(MESSAGE_STORAGE4))
|
|
37964
38502
|
return [];
|
|
37965
38503
|
const sessions = [];
|
|
37966
38504
|
async function scanDirectory(dir) {
|
|
@@ -37985,16 +38523,16 @@ async function getAllSessions() {
|
|
|
37985
38523
|
return [...new Set(sessions)];
|
|
37986
38524
|
}
|
|
37987
38525
|
function getMessageDir9(sessionID) {
|
|
37988
|
-
if (!
|
|
38526
|
+
if (!existsSync46(MESSAGE_STORAGE4))
|
|
37989
38527
|
return "";
|
|
37990
38528
|
const directPath = join52(MESSAGE_STORAGE4, sessionID);
|
|
37991
|
-
if (
|
|
38529
|
+
if (existsSync46(directPath)) {
|
|
37992
38530
|
return directPath;
|
|
37993
38531
|
}
|
|
37994
38532
|
try {
|
|
37995
38533
|
for (const dir of readdirSync15(MESSAGE_STORAGE4)) {
|
|
37996
38534
|
const sessionPath = join52(MESSAGE_STORAGE4, dir, sessionID);
|
|
37997
|
-
if (
|
|
38535
|
+
if (existsSync46(sessionPath)) {
|
|
37998
38536
|
return sessionPath;
|
|
37999
38537
|
}
|
|
38000
38538
|
}
|
|
@@ -38008,7 +38546,7 @@ function sessionExists(sessionID) {
|
|
|
38008
38546
|
}
|
|
38009
38547
|
async function readSessionMessages(sessionID) {
|
|
38010
38548
|
const messageDir = getMessageDir9(sessionID);
|
|
38011
|
-
if (!messageDir || !
|
|
38549
|
+
if (!messageDir || !existsSync46(messageDir))
|
|
38012
38550
|
return [];
|
|
38013
38551
|
const messages = [];
|
|
38014
38552
|
try {
|
|
@@ -38044,7 +38582,7 @@ async function readSessionMessages(sessionID) {
|
|
|
38044
38582
|
}
|
|
38045
38583
|
async function readParts2(messageID) {
|
|
38046
38584
|
const partDir = join52(PART_STORAGE4, messageID);
|
|
38047
|
-
if (!
|
|
38585
|
+
if (!existsSync46(partDir))
|
|
38048
38586
|
return [];
|
|
38049
38587
|
const parts = [];
|
|
38050
38588
|
try {
|
|
@@ -38065,7 +38603,7 @@ async function readParts2(messageID) {
|
|
|
38065
38603
|
return parts.sort((a, b) => a.id.localeCompare(b.id));
|
|
38066
38604
|
}
|
|
38067
38605
|
async function readSessionTodos(sessionID) {
|
|
38068
|
-
if (!
|
|
38606
|
+
if (!existsSync46(TODO_DIR2))
|
|
38069
38607
|
return [];
|
|
38070
38608
|
try {
|
|
38071
38609
|
const allFiles = await readdir(TODO_DIR2);
|
|
@@ -38092,10 +38630,10 @@ async function readSessionTodos(sessionID) {
|
|
|
38092
38630
|
return [];
|
|
38093
38631
|
}
|
|
38094
38632
|
async function readSessionTranscript(sessionID) {
|
|
38095
|
-
if (!
|
|
38633
|
+
if (!existsSync46(TRANSCRIPT_DIR2))
|
|
38096
38634
|
return 0;
|
|
38097
38635
|
const transcriptFile = join52(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
38098
|
-
if (!
|
|
38636
|
+
if (!existsSync46(transcriptFile))
|
|
38099
38637
|
return 0;
|
|
38100
38638
|
try {
|
|
38101
38639
|
const content = await readFile(transcriptFile, "utf-8");
|
|
@@ -38475,6 +39013,12 @@ async function getTmuxPath() {
|
|
|
38475
39013
|
function getCachedTmuxPath() {
|
|
38476
39014
|
return tmuxPath;
|
|
38477
39015
|
}
|
|
39016
|
+
function startBackgroundCheck2() {
|
|
39017
|
+
if (!initPromise3) {
|
|
39018
|
+
initPromise3 = getTmuxPath();
|
|
39019
|
+
initPromise3.catch(() => {});
|
|
39020
|
+
}
|
|
39021
|
+
}
|
|
38478
39022
|
|
|
38479
39023
|
// src/tools/interactive-bash/tools.ts
|
|
38480
39024
|
function tokenizeCommand2(cmd) {
|
|
@@ -38602,7 +39146,12 @@ function formatSkillsXml(skills) {
|
|
|
38602
39146
|
${skillsXml}
|
|
38603
39147
|
</available_skills>`;
|
|
38604
39148
|
}
|
|
38605
|
-
function extractSkillBody(skill) {
|
|
39149
|
+
async function extractSkillBody(skill) {
|
|
39150
|
+
if (skill.lazyContent) {
|
|
39151
|
+
const fullTemplate = await skill.lazyContent.load();
|
|
39152
|
+
const templateMatch2 = fullTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
39153
|
+
return templateMatch2 ? templateMatch2[1].trim() : fullTemplate;
|
|
39154
|
+
}
|
|
38606
39155
|
if (skill.path) {
|
|
38607
39156
|
const content = readFileSync30(skill.path, "utf-8");
|
|
38608
39157
|
const { body } = parseFrontmatter(content);
|
|
@@ -38686,7 +39235,7 @@ function createSkillTool(options = {}) {
|
|
|
38686
39235
|
const available = skills.map((s) => s.name).join(", ");
|
|
38687
39236
|
throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`);
|
|
38688
39237
|
}
|
|
38689
|
-
const body = extractSkillBody(skill);
|
|
39238
|
+
const body = await extractSkillBody(skill);
|
|
38690
39239
|
const dir = skill.path ? dirname12(skill.path) : skill.resolvedPath || process.cwd();
|
|
38691
39240
|
const output = [
|
|
38692
39241
|
`## Skill: ${skill.name}`,
|
|
@@ -38852,7 +39401,7 @@ function createSkillMcpTool(options) {
|
|
|
38852
39401
|
});
|
|
38853
39402
|
}
|
|
38854
39403
|
// src/tools/background-task/tools.ts
|
|
38855
|
-
import { existsSync as
|
|
39404
|
+
import { existsSync as existsSync47, readdirSync as readdirSync16 } from "fs";
|
|
38856
39405
|
import { join as join53 } from "path";
|
|
38857
39406
|
|
|
38858
39407
|
// src/tools/background-task/constants.ts
|
|
@@ -38864,14 +39413,14 @@ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=
|
|
|
38864
39413
|
|
|
38865
39414
|
// src/tools/background-task/tools.ts
|
|
38866
39415
|
function getMessageDir10(sessionID) {
|
|
38867
|
-
if (!
|
|
39416
|
+
if (!existsSync47(MESSAGE_STORAGE))
|
|
38868
39417
|
return null;
|
|
38869
39418
|
const directPath = join53(MESSAGE_STORAGE, sessionID);
|
|
38870
|
-
if (
|
|
39419
|
+
if (existsSync47(directPath))
|
|
38871
39420
|
return directPath;
|
|
38872
39421
|
for (const dir of readdirSync16(MESSAGE_STORAGE)) {
|
|
38873
39422
|
const sessionPath = join53(MESSAGE_STORAGE, dir, sessionID);
|
|
38874
|
-
if (
|
|
39423
|
+
if (existsSync47(sessionPath))
|
|
38875
39424
|
return sessionPath;
|
|
38876
39425
|
}
|
|
38877
39426
|
return null;
|
|
@@ -39457,18 +40006,18 @@ var builtinTools = {
|
|
|
39457
40006
|
session_info
|
|
39458
40007
|
};
|
|
39459
40008
|
// src/features/background-agent/manager.ts
|
|
39460
|
-
import { existsSync as
|
|
40009
|
+
import { existsSync as existsSync48, readdirSync as readdirSync17 } from "fs";
|
|
39461
40010
|
import { join as join54 } from "path";
|
|
39462
40011
|
var TASK_TTL_MS = 30 * 60 * 1000;
|
|
39463
40012
|
function getMessageDir11(sessionID) {
|
|
39464
|
-
if (!
|
|
40013
|
+
if (!existsSync48(MESSAGE_STORAGE))
|
|
39465
40014
|
return null;
|
|
39466
40015
|
const directPath = join54(MESSAGE_STORAGE, sessionID);
|
|
39467
|
-
if (
|
|
40016
|
+
if (existsSync48(directPath))
|
|
39468
40017
|
return directPath;
|
|
39469
40018
|
for (const dir of readdirSync17(MESSAGE_STORAGE)) {
|
|
39470
40019
|
const sessionPath = join54(MESSAGE_STORAGE, dir, sessionID);
|
|
39471
|
-
if (
|
|
40020
|
+
if (existsSync48(sessionPath))
|
|
39472
40021
|
return sessionPath;
|
|
39473
40022
|
}
|
|
39474
40023
|
return null;
|
|
@@ -42387,21 +42936,89 @@ function isElectron() {
|
|
|
42387
42936
|
return "type" in process2;
|
|
42388
42937
|
}
|
|
42389
42938
|
|
|
42939
|
+
// src/features/skill-mcp-manager/env-cleaner.ts
|
|
42940
|
+
var EXCLUDED_ENV_PATTERNS = [
|
|
42941
|
+
/^NPM_CONFIG_/i,
|
|
42942
|
+
/^npm_config_/,
|
|
42943
|
+
/^YARN_/,
|
|
42944
|
+
/^PNPM_/,
|
|
42945
|
+
/^NO_UPDATE_NOTIFIER$/
|
|
42946
|
+
];
|
|
42947
|
+
function createCleanMcpEnvironment(customEnv = {}) {
|
|
42948
|
+
const cleanEnv = {};
|
|
42949
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
42950
|
+
if (value === undefined)
|
|
42951
|
+
continue;
|
|
42952
|
+
const shouldExclude = EXCLUDED_ENV_PATTERNS.some((pattern) => pattern.test(key));
|
|
42953
|
+
if (!shouldExclude) {
|
|
42954
|
+
cleanEnv[key] = value;
|
|
42955
|
+
}
|
|
42956
|
+
}
|
|
42957
|
+
Object.assign(cleanEnv, customEnv);
|
|
42958
|
+
return cleanEnv;
|
|
42959
|
+
}
|
|
42960
|
+
|
|
42390
42961
|
// src/features/skill-mcp-manager/manager.ts
|
|
42391
42962
|
class SkillMcpManager {
|
|
42392
42963
|
clients = new Map;
|
|
42964
|
+
pendingConnections = new Map;
|
|
42965
|
+
cleanupRegistered = false;
|
|
42966
|
+
cleanupInterval = null;
|
|
42967
|
+
IDLE_TIMEOUT = 5 * 60 * 1000;
|
|
42393
42968
|
getClientKey(info) {
|
|
42394
42969
|
return `${info.sessionID}:${info.skillName}:${info.serverName}`;
|
|
42395
42970
|
}
|
|
42971
|
+
registerProcessCleanup() {
|
|
42972
|
+
if (this.cleanupRegistered)
|
|
42973
|
+
return;
|
|
42974
|
+
this.cleanupRegistered = true;
|
|
42975
|
+
const cleanup = async () => {
|
|
42976
|
+
for (const [, managed] of this.clients) {
|
|
42977
|
+
try {
|
|
42978
|
+
await managed.client.close();
|
|
42979
|
+
} catch {}
|
|
42980
|
+
try {
|
|
42981
|
+
await managed.transport.close();
|
|
42982
|
+
} catch {}
|
|
42983
|
+
}
|
|
42984
|
+
this.clients.clear();
|
|
42985
|
+
this.pendingConnections.clear();
|
|
42986
|
+
};
|
|
42987
|
+
process.on("SIGINT", async () => {
|
|
42988
|
+
await cleanup();
|
|
42989
|
+
process.exit(0);
|
|
42990
|
+
});
|
|
42991
|
+
process.on("SIGTERM", async () => {
|
|
42992
|
+
await cleanup();
|
|
42993
|
+
process.exit(0);
|
|
42994
|
+
});
|
|
42995
|
+
if (process.platform === "win32") {
|
|
42996
|
+
process.on("SIGBREAK", async () => {
|
|
42997
|
+
await cleanup();
|
|
42998
|
+
process.exit(0);
|
|
42999
|
+
});
|
|
43000
|
+
}
|
|
43001
|
+
}
|
|
42396
43002
|
async getOrCreateClient(info, config3) {
|
|
42397
43003
|
const key = this.getClientKey(info);
|
|
42398
43004
|
const existing = this.clients.get(key);
|
|
42399
43005
|
if (existing) {
|
|
43006
|
+
existing.lastUsedAt = Date.now();
|
|
42400
43007
|
return existing.client;
|
|
42401
43008
|
}
|
|
43009
|
+
const pending = this.pendingConnections.get(key);
|
|
43010
|
+
if (pending) {
|
|
43011
|
+
return pending;
|
|
43012
|
+
}
|
|
42402
43013
|
const expandedConfig = expandEnvVarsInObject(config3);
|
|
42403
|
-
const
|
|
42404
|
-
|
|
43014
|
+
const connectionPromise = this.createClient(info, expandedConfig);
|
|
43015
|
+
this.pendingConnections.set(key, connectionPromise);
|
|
43016
|
+
try {
|
|
43017
|
+
const client2 = await connectionPromise;
|
|
43018
|
+
return client2;
|
|
43019
|
+
} finally {
|
|
43020
|
+
this.pendingConnections.delete(key);
|
|
43021
|
+
}
|
|
42405
43022
|
}
|
|
42406
43023
|
async createClient(info, config3) {
|
|
42407
43024
|
const key = this.getClientKey(info);
|
|
@@ -42418,14 +43035,8 @@ class SkillMcpManager {
|
|
|
42418
43035
|
}
|
|
42419
43036
|
const command = config3.command;
|
|
42420
43037
|
const args = config3.args || [];
|
|
42421
|
-
const mergedEnv =
|
|
42422
|
-
|
|
42423
|
-
if (value !== undefined)
|
|
42424
|
-
mergedEnv[key2] = value;
|
|
42425
|
-
}
|
|
42426
|
-
if (config3.env) {
|
|
42427
|
-
Object.assign(mergedEnv, config3.env);
|
|
42428
|
-
}
|
|
43038
|
+
const mergedEnv = createCleanMcpEnvironment(config3.env);
|
|
43039
|
+
this.registerProcessCleanup();
|
|
42429
43040
|
const transport = new StdioClientTransport({
|
|
42430
43041
|
command,
|
|
42431
43042
|
args,
|
|
@@ -42436,6 +43047,9 @@ class SkillMcpManager {
|
|
|
42436
43047
|
try {
|
|
42437
43048
|
await client2.connect(transport);
|
|
42438
43049
|
} catch (error45) {
|
|
43050
|
+
try {
|
|
43051
|
+
await transport.close();
|
|
43052
|
+
} catch {}
|
|
42439
43053
|
const errorMessage = error45 instanceof Error ? error45.message : String(error45);
|
|
42440
43054
|
throw new Error(`Failed to connect to MCP server "${info.serverName}".
|
|
42441
43055
|
|
|
@@ -42447,7 +43061,8 @@ class SkillMcpManager {
|
|
|
42447
43061
|
` + ` - Check if the MCP server package exists
|
|
42448
43062
|
` + ` - Verify the args are correct for this server`);
|
|
42449
43063
|
}
|
|
42450
|
-
this.clients.set(key, { client: client2, transport, skillName: info.skillName });
|
|
43064
|
+
this.clients.set(key, { client: client2, transport, skillName: info.skillName, lastUsedAt: Date.now() });
|
|
43065
|
+
this.startCleanupTimer();
|
|
42451
43066
|
return client2;
|
|
42452
43067
|
}
|
|
42453
43068
|
async disconnectSession(sessionID) {
|
|
@@ -42455,22 +43070,56 @@ class SkillMcpManager {
|
|
|
42455
43070
|
for (const [key, managed] of this.clients.entries()) {
|
|
42456
43071
|
if (key.startsWith(`${sessionID}:`)) {
|
|
42457
43072
|
keysToRemove.push(key);
|
|
43073
|
+
this.clients.delete(key);
|
|
42458
43074
|
try {
|
|
42459
43075
|
await managed.client.close();
|
|
42460
43076
|
} catch {}
|
|
43077
|
+
try {
|
|
43078
|
+
await managed.transport.close();
|
|
43079
|
+
} catch {}
|
|
42461
43080
|
}
|
|
42462
43081
|
}
|
|
42463
|
-
for (const key of keysToRemove) {
|
|
42464
|
-
this.clients.delete(key);
|
|
42465
|
-
}
|
|
42466
43082
|
}
|
|
42467
43083
|
async disconnectAll() {
|
|
42468
|
-
|
|
43084
|
+
this.stopCleanupTimer();
|
|
43085
|
+
const clients = Array.from(this.clients.values());
|
|
43086
|
+
this.clients.clear();
|
|
43087
|
+
for (const managed of clients) {
|
|
42469
43088
|
try {
|
|
42470
43089
|
await managed.client.close();
|
|
42471
43090
|
} catch {}
|
|
43091
|
+
try {
|
|
43092
|
+
await managed.transport.close();
|
|
43093
|
+
} catch {}
|
|
43094
|
+
}
|
|
43095
|
+
}
|
|
43096
|
+
startCleanupTimer() {
|
|
43097
|
+
if (this.cleanupInterval)
|
|
43098
|
+
return;
|
|
43099
|
+
this.cleanupInterval = setInterval(() => {
|
|
43100
|
+
this.cleanupIdleClients();
|
|
43101
|
+
}, 60000);
|
|
43102
|
+
this.cleanupInterval.unref();
|
|
43103
|
+
}
|
|
43104
|
+
stopCleanupTimer() {
|
|
43105
|
+
if (this.cleanupInterval) {
|
|
43106
|
+
clearInterval(this.cleanupInterval);
|
|
43107
|
+
this.cleanupInterval = null;
|
|
43108
|
+
}
|
|
43109
|
+
}
|
|
43110
|
+
async cleanupIdleClients() {
|
|
43111
|
+
const now = Date.now();
|
|
43112
|
+
for (const [key, managed] of this.clients) {
|
|
43113
|
+
if (now - managed.lastUsedAt > this.IDLE_TIMEOUT) {
|
|
43114
|
+
this.clients.delete(key);
|
|
43115
|
+
try {
|
|
43116
|
+
await managed.client.close();
|
|
43117
|
+
} catch {}
|
|
43118
|
+
try {
|
|
43119
|
+
await managed.transport.close();
|
|
43120
|
+
} catch {}
|
|
43121
|
+
}
|
|
42472
43122
|
}
|
|
42473
|
-
this.clients.clear();
|
|
42474
43123
|
}
|
|
42475
43124
|
async listTools(info, context) {
|
|
42476
43125
|
const client2 = await this.getOrCreateClientWithRetry(info, context.config);
|
|
@@ -42509,10 +43158,13 @@ class SkillMcpManager {
|
|
|
42509
43158
|
const key = this.getClientKey(info);
|
|
42510
43159
|
const existing = this.clients.get(key);
|
|
42511
43160
|
if (existing) {
|
|
43161
|
+
this.clients.delete(key);
|
|
42512
43162
|
try {
|
|
42513
43163
|
await existing.client.close();
|
|
42514
43164
|
} catch {}
|
|
42515
|
-
|
|
43165
|
+
try {
|
|
43166
|
+
await existing.transport.close();
|
|
43167
|
+
} catch {}
|
|
42516
43168
|
return await this.getOrCreateClient(info, config3);
|
|
42517
43169
|
}
|
|
42518
43170
|
throw error45;
|
|
@@ -42526,7 +43178,7 @@ class SkillMcpManager {
|
|
|
42526
43178
|
}
|
|
42527
43179
|
}
|
|
42528
43180
|
// src/plugin-config.ts
|
|
42529
|
-
import * as
|
|
43181
|
+
import * as fs10 from "fs";
|
|
42530
43182
|
import * as path7 from "path";
|
|
42531
43183
|
|
|
42532
43184
|
// src/mcp/types.ts
|
|
@@ -42746,8 +43398,8 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
42746
43398
|
// src/plugin-config.ts
|
|
42747
43399
|
function loadConfigFromPath2(configPath, ctx) {
|
|
42748
43400
|
try {
|
|
42749
|
-
if (
|
|
42750
|
-
const content =
|
|
43401
|
+
if (fs10.existsSync(configPath)) {
|
|
43402
|
+
const content = fs10.readFileSync(configPath, "utf-8");
|
|
42751
43403
|
const rawConfig = parseJsonc(content);
|
|
42752
43404
|
migrateConfigFile(configPath, rawConfig);
|
|
42753
43405
|
const result = OhMyOpenCodeConfigSchema.safeParse(rawConfig);
|
|
@@ -43651,12 +44303,18 @@ Organize your final answer in three tiers:
|
|
|
43651
44303
|
|
|
43652
44304
|
Your response goes directly to the user with no intermediate processing. Make your final message self-contained: a clear recommendation they can act on immediately, covering both what to do and why.`;
|
|
43653
44305
|
function createOracleAgent(model = DEFAULT_MODEL2) {
|
|
44306
|
+
const restrictions = createAgentToolRestrictions([
|
|
44307
|
+
"write",
|
|
44308
|
+
"edit",
|
|
44309
|
+
"task",
|
|
44310
|
+
"background_task"
|
|
44311
|
+
]);
|
|
43654
44312
|
const base = {
|
|
43655
44313
|
description: "Expert technical advisor with deep reasoning for architecture decisions, code analysis, and engineering guidance.",
|
|
43656
44314
|
mode: "subagent",
|
|
43657
44315
|
model,
|
|
43658
44316
|
temperature: 0.1,
|
|
43659
|
-
|
|
44317
|
+
...restrictions,
|
|
43660
44318
|
prompt: ORACLE_SYSTEM_PROMPT
|
|
43661
44319
|
};
|
|
43662
44320
|
if (isGptModel(model)) {
|
|
@@ -43685,12 +44343,17 @@ var LIBRARIAN_PROMPT_METADATA = {
|
|
|
43685
44343
|
]
|
|
43686
44344
|
};
|
|
43687
44345
|
function createLibrarianAgent(model = DEFAULT_MODEL3) {
|
|
44346
|
+
const restrictions = createAgentToolRestrictions([
|
|
44347
|
+
"write",
|
|
44348
|
+
"edit",
|
|
44349
|
+
"background_task"
|
|
44350
|
+
]);
|
|
43688
44351
|
return {
|
|
43689
44352
|
description: "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
|
|
43690
44353
|
mode: "subagent",
|
|
43691
44354
|
model,
|
|
43692
44355
|
temperature: 0.1,
|
|
43693
|
-
|
|
44356
|
+
...restrictions,
|
|
43694
44357
|
prompt: `# THE LIBRARIAN
|
|
43695
44358
|
|
|
43696
44359
|
You are **THE LIBRARIAN**, a specialized open-source codebase understanding agent.
|
|
@@ -43947,12 +44610,17 @@ var EXPLORE_PROMPT_METADATA = {
|
|
|
43947
44610
|
]
|
|
43948
44611
|
};
|
|
43949
44612
|
function createExploreAgent(model = DEFAULT_MODEL4) {
|
|
44613
|
+
const restrictions = createAgentToolRestrictions([
|
|
44614
|
+
"write",
|
|
44615
|
+
"edit",
|
|
44616
|
+
"background_task"
|
|
44617
|
+
]);
|
|
43950
44618
|
return {
|
|
43951
44619
|
description: 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.',
|
|
43952
44620
|
mode: "subagent",
|
|
43953
44621
|
model,
|
|
43954
44622
|
temperature: 0.1,
|
|
43955
|
-
|
|
44623
|
+
...restrictions,
|
|
43956
44624
|
prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
|
|
43957
44625
|
|
|
43958
44626
|
## Your Mission
|
|
@@ -44053,11 +44721,12 @@ var FRONTEND_PROMPT_METADATA = {
|
|
|
44053
44721
|
]
|
|
44054
44722
|
};
|
|
44055
44723
|
function createFrontendUiUxEngineerAgent(model = DEFAULT_MODEL5) {
|
|
44724
|
+
const restrictions = createAgentToolRestrictions(["background_task"]);
|
|
44056
44725
|
return {
|
|
44057
44726
|
description: "A designer-turned-developer who crafts stunning UI/UX even without design mockups. Code may be a bit messy, but the visual output is always fire.",
|
|
44058
44727
|
mode: "subagent",
|
|
44059
44728
|
model,
|
|
44060
|
-
|
|
44729
|
+
...restrictions,
|
|
44061
44730
|
prompt: `# Role: Designer-Turned-Developer
|
|
44062
44731
|
|
|
44063
44732
|
You are a designer who learned to code. You see what pure developers miss\u2014spacing, color harmony, micro-interactions, that indefinable "feel" that makes interfaces memorable. Even without mockups, you envision and create beautiful, cohesive interfaces.
|
|
@@ -44146,11 +44815,12 @@ var DOCUMENT_WRITER_PROMPT_METADATA = {
|
|
|
44146
44815
|
]
|
|
44147
44816
|
};
|
|
44148
44817
|
function createDocumentWriterAgent(model = DEFAULT_MODEL6) {
|
|
44818
|
+
const restrictions = createAgentToolRestrictions(["background_task"]);
|
|
44149
44819
|
return {
|
|
44150
44820
|
description: "A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.",
|
|
44151
44821
|
mode: "subagent",
|
|
44152
44822
|
model,
|
|
44153
|
-
|
|
44823
|
+
...restrictions,
|
|
44154
44824
|
prompt: `<role>
|
|
44155
44825
|
You are a TECHNICAL WRITER with deep engineering background who transforms complex codebases into crystal-clear documentation. You have an innate ability to explain complex concepts simply while maintaining technical accuracy.
|
|
44156
44826
|
|
|
@@ -44358,12 +45028,18 @@ var MULTIMODAL_LOOKER_PROMPT_METADATA = {
|
|
|
44358
45028
|
triggers: []
|
|
44359
45029
|
};
|
|
44360
45030
|
function createMultimodalLookerAgent(model = DEFAULT_MODEL7) {
|
|
45031
|
+
const restrictions = createAgentToolRestrictions([
|
|
45032
|
+
"write",
|
|
45033
|
+
"edit",
|
|
45034
|
+
"bash",
|
|
45035
|
+
"background_task"
|
|
45036
|
+
]);
|
|
44361
45037
|
return {
|
|
44362
45038
|
description: "Analyze media files (PDFs, images, diagrams) that require interpretation beyond raw text. Extracts specific information or summaries from documents, describes visual content. Use when you need analyzed/extracted data rather than literal file contents.",
|
|
44363
45039
|
mode: "subagent",
|
|
44364
45040
|
model,
|
|
44365
45041
|
temperature: 0.1,
|
|
44366
|
-
|
|
45042
|
+
...restrictions,
|
|
44367
45043
|
prompt: `You interpret media files that cannot be read as plain text.
|
|
44368
45044
|
|
|
44369
45045
|
Your job: examine the attached file and extract ONLY what was requested.
|
|
@@ -44494,10 +45170,10 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
|
|
|
44494
45170
|
return result;
|
|
44495
45171
|
}
|
|
44496
45172
|
// src/features/claude-code-command-loader/loader.ts
|
|
44497
|
-
import { existsSync as
|
|
45173
|
+
import { existsSync as existsSync50, readdirSync as readdirSync18, readFileSync as readFileSync32, realpathSync as realpathSync2 } from "fs";
|
|
44498
45174
|
import { join as join56, basename as basename5 } from "path";
|
|
44499
45175
|
function loadCommandsFromDir(commandsDir, scope, visited = new Set, prefix = "") {
|
|
44500
|
-
if (!
|
|
45176
|
+
if (!existsSync50(commandsDir)) {
|
|
44501
45177
|
return [];
|
|
44502
45178
|
}
|
|
44503
45179
|
let realPath;
|
|
@@ -44981,7 +45657,7 @@ function loadBuiltinCommands(disabledCommands) {
|
|
|
44981
45657
|
return commands;
|
|
44982
45658
|
}
|
|
44983
45659
|
// src/features/claude-code-agent-loader/loader.ts
|
|
44984
|
-
import { existsSync as
|
|
45660
|
+
import { existsSync as existsSync51, readdirSync as readdirSync19, readFileSync as readFileSync33 } from "fs";
|
|
44985
45661
|
import { join as join57, basename as basename6 } from "path";
|
|
44986
45662
|
function parseToolsConfig(toolsStr) {
|
|
44987
45663
|
if (!toolsStr)
|
|
@@ -44996,7 +45672,7 @@ function parseToolsConfig(toolsStr) {
|
|
|
44996
45672
|
return result;
|
|
44997
45673
|
}
|
|
44998
45674
|
function loadAgentsFromDir(agentsDir, scope) {
|
|
44999
|
-
if (!
|
|
45675
|
+
if (!existsSync51(agentsDir)) {
|
|
45000
45676
|
return [];
|
|
45001
45677
|
}
|
|
45002
45678
|
const entries = readdirSync19(agentsDir, { withFileTypes: true });
|
|
@@ -45052,7 +45728,7 @@ function loadProjectAgents() {
|
|
|
45052
45728
|
return result;
|
|
45053
45729
|
}
|
|
45054
45730
|
// src/features/claude-code-plugin-loader/loader.ts
|
|
45055
|
-
import { existsSync as
|
|
45731
|
+
import { existsSync as existsSync52, readdirSync as readdirSync20, readFileSync as readFileSync34 } from "fs";
|
|
45056
45732
|
import { homedir as homedir16 } from "os";
|
|
45057
45733
|
import { join as join58, basename as basename7 } from "path";
|
|
45058
45734
|
var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
|
|
@@ -45088,7 +45764,7 @@ function resolvePluginPaths(obj, pluginRoot) {
|
|
|
45088
45764
|
}
|
|
45089
45765
|
function loadInstalledPlugins() {
|
|
45090
45766
|
const dbPath = getInstalledPluginsPath();
|
|
45091
|
-
if (!
|
|
45767
|
+
if (!existsSync52(dbPath)) {
|
|
45092
45768
|
return null;
|
|
45093
45769
|
}
|
|
45094
45770
|
try {
|
|
@@ -45107,7 +45783,7 @@ function getClaudeSettingsPath() {
|
|
|
45107
45783
|
}
|
|
45108
45784
|
function loadClaudeSettings() {
|
|
45109
45785
|
const settingsPath = getClaudeSettingsPath();
|
|
45110
|
-
if (!
|
|
45786
|
+
if (!existsSync52(settingsPath)) {
|
|
45111
45787
|
return null;
|
|
45112
45788
|
}
|
|
45113
45789
|
try {
|
|
@@ -45120,7 +45796,7 @@ function loadClaudeSettings() {
|
|
|
45120
45796
|
}
|
|
45121
45797
|
function loadPluginManifest(installPath) {
|
|
45122
45798
|
const manifestPath = join58(installPath, ".claude-plugin", "plugin.json");
|
|
45123
|
-
if (!
|
|
45799
|
+
if (!existsSync52(manifestPath)) {
|
|
45124
45800
|
return null;
|
|
45125
45801
|
}
|
|
45126
45802
|
try {
|
|
@@ -45171,7 +45847,7 @@ function discoverInstalledPlugins(options) {
|
|
|
45171
45847
|
continue;
|
|
45172
45848
|
}
|
|
45173
45849
|
const { installPath, scope, version: version2 } = installation;
|
|
45174
|
-
if (!
|
|
45850
|
+
if (!existsSync52(installPath)) {
|
|
45175
45851
|
errors3.push({
|
|
45176
45852
|
pluginKey,
|
|
45177
45853
|
installPath,
|
|
@@ -45189,21 +45865,21 @@ function discoverInstalledPlugins(options) {
|
|
|
45189
45865
|
pluginKey,
|
|
45190
45866
|
manifest: manifest ?? undefined
|
|
45191
45867
|
};
|
|
45192
|
-
if (
|
|
45868
|
+
if (existsSync52(join58(installPath, "commands"))) {
|
|
45193
45869
|
loadedPlugin.commandsDir = join58(installPath, "commands");
|
|
45194
45870
|
}
|
|
45195
|
-
if (
|
|
45871
|
+
if (existsSync52(join58(installPath, "agents"))) {
|
|
45196
45872
|
loadedPlugin.agentsDir = join58(installPath, "agents");
|
|
45197
45873
|
}
|
|
45198
|
-
if (
|
|
45874
|
+
if (existsSync52(join58(installPath, "skills"))) {
|
|
45199
45875
|
loadedPlugin.skillsDir = join58(installPath, "skills");
|
|
45200
45876
|
}
|
|
45201
45877
|
const hooksPath = join58(installPath, "hooks", "hooks.json");
|
|
45202
|
-
if (
|
|
45878
|
+
if (existsSync52(hooksPath)) {
|
|
45203
45879
|
loadedPlugin.hooksPath = hooksPath;
|
|
45204
45880
|
}
|
|
45205
45881
|
const mcpPath = join58(installPath, ".mcp.json");
|
|
45206
|
-
if (
|
|
45882
|
+
if (existsSync52(mcpPath)) {
|
|
45207
45883
|
loadedPlugin.mcpPath = mcpPath;
|
|
45208
45884
|
}
|
|
45209
45885
|
plugins.push(loadedPlugin);
|
|
@@ -45214,7 +45890,7 @@ function discoverInstalledPlugins(options) {
|
|
|
45214
45890
|
function loadPluginCommands(plugins) {
|
|
45215
45891
|
const commands2 = {};
|
|
45216
45892
|
for (const plugin2 of plugins) {
|
|
45217
|
-
if (!plugin2.commandsDir || !
|
|
45893
|
+
if (!plugin2.commandsDir || !existsSync52(plugin2.commandsDir))
|
|
45218
45894
|
continue;
|
|
45219
45895
|
const entries = readdirSync20(plugin2.commandsDir, { withFileTypes: true });
|
|
45220
45896
|
for (const entry of entries) {
|
|
@@ -45256,7 +45932,7 @@ $ARGUMENTS
|
|
|
45256
45932
|
function loadPluginSkillsAsCommands(plugins) {
|
|
45257
45933
|
const skills = {};
|
|
45258
45934
|
for (const plugin2 of plugins) {
|
|
45259
|
-
if (!plugin2.skillsDir || !
|
|
45935
|
+
if (!plugin2.skillsDir || !existsSync52(plugin2.skillsDir))
|
|
45260
45936
|
continue;
|
|
45261
45937
|
const entries = readdirSync20(plugin2.skillsDir, { withFileTypes: true });
|
|
45262
45938
|
for (const entry of entries) {
|
|
@@ -45267,7 +45943,7 @@ function loadPluginSkillsAsCommands(plugins) {
|
|
|
45267
45943
|
continue;
|
|
45268
45944
|
const resolvedPath = resolveSymlink(skillPath);
|
|
45269
45945
|
const skillMdPath = join58(resolvedPath, "SKILL.md");
|
|
45270
|
-
if (!
|
|
45946
|
+
if (!existsSync52(skillMdPath))
|
|
45271
45947
|
continue;
|
|
45272
45948
|
try {
|
|
45273
45949
|
const content = readFileSync34(skillMdPath, "utf-8");
|
|
@@ -45317,7 +45993,7 @@ function parseToolsConfig2(toolsStr) {
|
|
|
45317
45993
|
function loadPluginAgents(plugins) {
|
|
45318
45994
|
const agents = {};
|
|
45319
45995
|
for (const plugin2 of plugins) {
|
|
45320
|
-
if (!plugin2.agentsDir || !
|
|
45996
|
+
if (!plugin2.agentsDir || !existsSync52(plugin2.agentsDir))
|
|
45321
45997
|
continue;
|
|
45322
45998
|
const entries = readdirSync20(plugin2.agentsDir, { withFileTypes: true });
|
|
45323
45999
|
for (const entry of entries) {
|
|
@@ -45353,7 +46029,7 @@ function loadPluginAgents(plugins) {
|
|
|
45353
46029
|
async function loadPluginMcpServers(plugins) {
|
|
45354
46030
|
const servers = {};
|
|
45355
46031
|
for (const plugin2 of plugins) {
|
|
45356
|
-
if (!plugin2.mcpPath || !
|
|
46032
|
+
if (!plugin2.mcpPath || !existsSync52(plugin2.mcpPath))
|
|
45357
46033
|
continue;
|
|
45358
46034
|
try {
|
|
45359
46035
|
const content = await Bun.file(plugin2.mcpPath).text();
|
|
@@ -45385,7 +46061,7 @@ async function loadPluginMcpServers(plugins) {
|
|
|
45385
46061
|
function loadPluginHooksConfigs(plugins) {
|
|
45386
46062
|
const configs = [];
|
|
45387
46063
|
for (const plugin2 of plugins) {
|
|
45388
|
-
if (!plugin2.hooksPath || !
|
|
46064
|
+
if (!plugin2.hooksPath || !existsSync52(plugin2.hooksPath))
|
|
45389
46065
|
continue;
|
|
45390
46066
|
try {
|
|
45391
46067
|
const content = readFileSync34(plugin2.hooksPath, "utf-8");
|
|
@@ -45401,11 +46077,13 @@ function loadPluginHooksConfigs(plugins) {
|
|
|
45401
46077
|
}
|
|
45402
46078
|
async function loadAllPluginComponents(options) {
|
|
45403
46079
|
const { plugins, errors: errors3 } = discoverInstalledPlugins(options);
|
|
45404
|
-
const commands2 =
|
|
45405
|
-
|
|
45406
|
-
|
|
45407
|
-
|
|
45408
|
-
|
|
46080
|
+
const [commands2, skills, agents, mcpServers, hooksConfigs] = await Promise.all([
|
|
46081
|
+
Promise.resolve(loadPluginCommands(plugins)),
|
|
46082
|
+
Promise.resolve(loadPluginSkillsAsCommands(plugins)),
|
|
46083
|
+
Promise.resolve(loadPluginAgents(plugins)),
|
|
46084
|
+
loadPluginMcpServers(plugins),
|
|
46085
|
+
Promise.resolve(loadPluginHooksConfigs(plugins))
|
|
46086
|
+
]);
|
|
45409
46087
|
log(`Loaded ${plugins.length} plugins with ${Object.keys(commands2).length} commands, ${Object.keys(skills).length} skills, ${Object.keys(agents).length} agents, ${Object.keys(mcpServers).length} MCP servers`);
|
|
45410
46088
|
return {
|
|
45411
46089
|
commands: commands2,
|
|
@@ -45565,9 +46243,21 @@ function createConfigHandler(deps) {
|
|
|
45565
46243
|
log(`Plugin load errors`, { errors: pluginComponents.errors });
|
|
45566
46244
|
}
|
|
45567
46245
|
const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents, ctx.directory, config3.model);
|
|
45568
|
-
const
|
|
45569
|
-
const
|
|
45570
|
-
const
|
|
46246
|
+
const rawUserAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
|
|
46247
|
+
const rawProjectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
|
|
46248
|
+
const rawPluginAgents = pluginComponents.agents;
|
|
46249
|
+
const userAgents = Object.fromEntries(Object.entries(rawUserAgents).map(([k, v]) => [
|
|
46250
|
+
k,
|
|
46251
|
+
v ? migrateAgentConfig(v) : v
|
|
46252
|
+
]));
|
|
46253
|
+
const projectAgents = Object.fromEntries(Object.entries(rawProjectAgents).map(([k, v]) => [
|
|
46254
|
+
k,
|
|
46255
|
+
v ? migrateAgentConfig(v) : v
|
|
46256
|
+
]));
|
|
46257
|
+
const pluginAgents = Object.fromEntries(Object.entries(rawPluginAgents).map(([k, v]) => [
|
|
46258
|
+
k,
|
|
46259
|
+
v ? migrateAgentConfig(v) : v
|
|
46260
|
+
]));
|
|
45571
46261
|
const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
|
|
45572
46262
|
const builderEnabled = pluginConfig.sisyphus_agent?.default_builder_enabled ?? false;
|
|
45573
46263
|
const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
|
|
@@ -45580,18 +46270,21 @@ function createConfigHandler(deps) {
|
|
|
45580
46270
|
};
|
|
45581
46271
|
if (builderEnabled) {
|
|
45582
46272
|
const { name: _buildName, ...buildConfigWithoutName } = configAgent?.build ?? {};
|
|
46273
|
+
const migratedBuildConfig = migrateAgentConfig(buildConfigWithoutName);
|
|
45583
46274
|
const openCodeBuilderOverride = pluginConfig.agents?.["OpenCode-Builder"];
|
|
45584
46275
|
const openCodeBuilderBase = {
|
|
45585
|
-
...
|
|
46276
|
+
...migratedBuildConfig,
|
|
45586
46277
|
description: `${configAgent?.build?.description ?? "Build agent"} (OpenCode default)`
|
|
45587
46278
|
};
|
|
45588
46279
|
agentConfig["OpenCode-Builder"] = openCodeBuilderOverride ? { ...openCodeBuilderBase, ...openCodeBuilderOverride } : openCodeBuilderBase;
|
|
45589
46280
|
}
|
|
45590
46281
|
if (plannerEnabled) {
|
|
45591
46282
|
const { name: _planName, ...planConfigWithoutName } = configAgent?.plan ?? {};
|
|
46283
|
+
const migratedPlanConfig = migrateAgentConfig(planConfigWithoutName);
|
|
45592
46284
|
const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
|
|
45593
46285
|
const plannerSisyphusBase = {
|
|
45594
|
-
...
|
|
46286
|
+
...migratedPlanConfig,
|
|
46287
|
+
mode: "primary",
|
|
45595
46288
|
prompt: PLAN_SYSTEM_PROMPT,
|
|
45596
46289
|
permission: PLAN_PERMISSION,
|
|
45597
46290
|
description: `${configAgent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
|
|
@@ -45605,7 +46298,12 @@ function createConfigHandler(deps) {
|
|
|
45605
46298
|
if (key === "plan" && replacePlan)
|
|
45606
46299
|
return false;
|
|
45607
46300
|
return true;
|
|
45608
|
-
}))
|
|
46301
|
+
}).map(([key, value]) => [
|
|
46302
|
+
key,
|
|
46303
|
+
value ? migrateAgentConfig(value) : value
|
|
46304
|
+
])) : {};
|
|
46305
|
+
const migratedBuild = configAgent?.build ? migrateAgentConfig(configAgent.build) : {};
|
|
46306
|
+
const planDemoteConfig = replacePlan ? { disable: true } : undefined;
|
|
45609
46307
|
config3.agent = {
|
|
45610
46308
|
...agentConfig,
|
|
45611
46309
|
...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
|
|
@@ -45613,8 +46311,8 @@ function createConfigHandler(deps) {
|
|
|
45613
46311
|
...projectAgents,
|
|
45614
46312
|
...pluginAgents,
|
|
45615
46313
|
...filteredConfigAgents,
|
|
45616
|
-
build: { ...
|
|
45617
|
-
...
|
|
46314
|
+
build: { ...migratedBuild, mode: "subagent", hidden: true },
|
|
46315
|
+
...planDemoteConfig ? { plan: planDemoteConfig } : {}
|
|
45618
46316
|
};
|
|
45619
46317
|
} else {
|
|
45620
46318
|
config3.agent = {
|
|
@@ -45691,6 +46389,7 @@ function createConfigHandler(deps) {
|
|
|
45691
46389
|
}
|
|
45692
46390
|
// src/index.ts
|
|
45693
46391
|
var OhMyOpenCodePlugin = async (ctx) => {
|
|
46392
|
+
startBackgroundCheck2();
|
|
45694
46393
|
const pluginConfig = loadPluginConfig(ctx.directory, ctx);
|
|
45695
46394
|
const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
|
|
45696
46395
|
const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
|
|
@@ -45726,12 +46425,17 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45726
46425
|
autoUpdate: pluginConfig.auto_update ?? true
|
|
45727
46426
|
}) : null;
|
|
45728
46427
|
const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook(ctx) : null;
|
|
46428
|
+
const contextInjector = createContextInjectorHook(contextCollector);
|
|
46429
|
+
const contextInjectorMessagesTransform = createContextInjectorMessagesTransformHook(contextCollector);
|
|
45729
46430
|
const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
|
|
45730
46431
|
const nonInteractiveEnv = isHookEnabled("non-interactive-env") ? createNonInteractiveEnvHook(ctx) : null;
|
|
45731
46432
|
const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
|
|
45732
46433
|
const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
|
|
45733
46434
|
const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
|
|
45734
|
-
const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, {
|
|
46435
|
+
const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, {
|
|
46436
|
+
config: pluginConfig.ralph_loop,
|
|
46437
|
+
checkSessionExists: async (sessionId) => sessionExists(sessionId)
|
|
46438
|
+
}) : null;
|
|
45735
46439
|
const autoSlashCommand = isHookEnabled("auto-slash-command") ? createAutoSlashCommandHook() : null;
|
|
45736
46440
|
const editErrorRecovery = isHookEnabled("edit-error-recovery") ? createEditErrorRecoveryHook(ctx) : null;
|
|
45737
46441
|
const backgroundManager = new BackgroundManager(ctx);
|
|
@@ -45758,7 +46462,13 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45758
46462
|
return true;
|
|
45759
46463
|
});
|
|
45760
46464
|
const includeClaudeSkills = pluginConfig.claude_code?.skills !== false;
|
|
45761
|
-
const
|
|
46465
|
+
const [userSkills, globalSkills, projectSkills, opencodeProjectSkills] = await Promise.all([
|
|
46466
|
+
includeClaudeSkills ? discoverUserClaudeSkillsAsync() : Promise.resolve([]),
|
|
46467
|
+
discoverOpencodeGlobalSkillsAsync(),
|
|
46468
|
+
includeClaudeSkills ? discoverProjectClaudeSkillsAsync() : Promise.resolve([]),
|
|
46469
|
+
discoverOpencodeProjectSkillsAsync()
|
|
46470
|
+
]);
|
|
46471
|
+
const mergedSkills = mergeSkills(builtinSkills, pluginConfig.skills, userSkills, globalSkills, projectSkills, opencodeProjectSkills);
|
|
45762
46472
|
const skillMcpManager = new SkillMcpManager;
|
|
45763
46473
|
const getSessionIDForMcp = () => getMainSessionID() || "";
|
|
45764
46474
|
const skillTool = createSkillTool({
|
|
@@ -45772,7 +46482,6 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45772
46482
|
getSessionID: getSessionIDForMcp
|
|
45773
46483
|
});
|
|
45774
46484
|
const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
|
|
45775
|
-
const tmuxAvailable = await getTmuxPath();
|
|
45776
46485
|
const configHandler = createConfigHandler({
|
|
45777
46486
|
ctx,
|
|
45778
46487
|
pluginConfig,
|
|
@@ -45787,11 +46496,12 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45787
46496
|
look_at: lookAt,
|
|
45788
46497
|
skill: skillTool,
|
|
45789
46498
|
skill_mcp: skillMcpTool,
|
|
45790
|
-
|
|
46499
|
+
interactive_bash
|
|
45791
46500
|
},
|
|
45792
46501
|
"chat.message": async (input, output) => {
|
|
45793
46502
|
await claudeCodeHooks["chat.message"]?.(input, output);
|
|
45794
46503
|
await keywordDetector?.["chat.message"]?.(input, output);
|
|
46504
|
+
await contextInjector["chat.message"]?.(input, output);
|
|
45795
46505
|
await autoSlashCommand?.["chat.message"]?.(input, output);
|
|
45796
46506
|
if (ralphLoop) {
|
|
45797
46507
|
const parts = output.parts;
|
|
@@ -45823,6 +46533,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45823
46533
|
}
|
|
45824
46534
|
},
|
|
45825
46535
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
46536
|
+
await contextInjectorMessagesTransform?.["experimental.chat.messages.transform"]?.(input, output);
|
|
45826
46537
|
await thinkingBlockValidator?.["experimental.chat.messages.transform"]?.(input, output);
|
|
45827
46538
|
await emptyMessageSanitizer?.["experimental.chat.messages.transform"]?.(input, output);
|
|
45828
46539
|
},
|