oh-my-opencode 2.7.2 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +10 -4
- package/README.ko.md +10 -4
- package/README.md +10 -4
- package/README.zh-cn.md +10 -4
- package/dist/cli/ast-grep-napi.linux-x64-gnu-jfv8414z.node +0 -0
- package/dist/cli/ast-grep-napi.linux-x64-musl-8cj2e5cf.node +0 -0
- package/dist/cli/doctor/checks/auth.d.ts +7 -0
- package/dist/cli/doctor/checks/config.d.ts +8 -0
- package/dist/cli/doctor/checks/dependencies.d.ts +8 -0
- package/dist/cli/doctor/checks/dependencies.test.d.ts +1 -0
- package/dist/cli/doctor/checks/index.d.ts +10 -0
- package/dist/cli/doctor/checks/lsp.d.ts +8 -0
- package/dist/cli/doctor/checks/lsp.test.d.ts +1 -0
- package/dist/cli/doctor/checks/mcp.d.ts +6 -0
- package/dist/cli/doctor/checks/mcp.test.d.ts +1 -0
- package/dist/cli/doctor/checks/opencode.d.ts +10 -0
- package/dist/cli/doctor/checks/opencode.test.d.ts +1 -0
- package/dist/cli/doctor/checks/plugin.d.ts +4 -0
- package/dist/cli/doctor/checks/plugin.test.d.ts +1 -0
- package/dist/cli/doctor/checks/version.d.ts +4 -0
- package/dist/cli/doctor/checks/version.test.d.ts +1 -0
- package/dist/cli/doctor/constants.d.ts +39 -0
- package/dist/cli/doctor/formatter.d.ts +12 -0
- package/dist/cli/doctor/formatter.test.d.ts +1 -0
- package/dist/cli/doctor/index.d.ts +5 -0
- package/dist/cli/doctor/runner.d.ts +7 -0
- package/dist/cli/doctor/runner.test.d.ts +1 -0
- package/dist/cli/doctor/types.d.ts +91 -0
- package/dist/cli/index.js +14180 -76
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +96 -4
- package/dist/features/builtin-commands/templates/ralph-loop.d.ts +2 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/builtin-skills/index.d.ts +2 -0
- package/dist/features/builtin-skills/skills.d.ts +2 -0
- package/dist/features/builtin-skills/types.d.ts +13 -0
- package/dist/features/{claude-code-skill-loader → opencode-skill-loader}/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/loader.d.ts +41 -0
- package/dist/features/opencode-skill-loader/merger.d.ts +7 -0
- package/dist/features/opencode-skill-loader/types.d.ts +25 -0
- package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/executor.d.ts +1 -1
- package/dist/hooks/anthropic-context-window-limit-recovery/executor.test.d.ts +1 -0
- package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/index.d.ts +3 -2
- package/dist/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.d.ts +1 -0
- package/dist/hooks/index.d.ts +2 -1
- package/dist/hooks/ralph-loop/constants.d.ts +5 -0
- package/dist/hooks/ralph-loop/index.d.ts +20 -0
- package/dist/hooks/ralph-loop/index.test.d.ts +1 -0
- package/dist/hooks/ralph-loop/storage.d.ts +6 -0
- package/dist/hooks/ralph-loop/types.d.ts +13 -0
- package/dist/hooks/think-mode/index.test.d.ts +1 -0
- package/dist/hooks/think-mode/switcher.d.ts +54 -1
- package/dist/hooks/think-mode/switcher.test.d.ts +1 -0
- package/dist/index.js +1287 -353
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/interactive-bash/constants.d.ts +1 -1
- package/dist/tools/look-at/constants.d.ts +1 -1
- package/dist/tools/lsp/utils.d.ts +1 -0
- package/dist/tools/skill/constants.d.ts +3 -0
- package/dist/tools/skill/index.d.ts +3 -0
- package/dist/tools/skill/tools.d.ts +10 -0
- package/dist/tools/skill/types.d.ts +20 -0
- package/dist/tools/slashcommand/types.d.ts +3 -3
- package/package.json +1 -1
- package/dist/features/claude-code-skill-loader/loader.d.ts +0 -3
- package/dist/features/claude-code-skill-loader/types.d.ts +0 -13
- /package/dist/{hooks/anthropic-auto-compact/executor.test.d.ts → cli/doctor/checks/auth.test.d.ts} +0 -0
- /package/dist/{hooks/anthropic-auto-compact/pruning-deduplication.test.d.ts → cli/doctor/checks/config.test.d.ts} +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/parser.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-deduplication.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-executor.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-purge-errors.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-storage.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-supersede.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/pruning-types.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/storage.d.ts +0 -0
- /package/dist/hooks/{anthropic-auto-compact → anthropic-context-window-limit-recovery}/types.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -6406,7 +6406,7 @@ function createEmptyTaskResponseDetectorHook(_ctx) {
|
|
|
6406
6406
|
}
|
|
6407
6407
|
};
|
|
6408
6408
|
}
|
|
6409
|
-
// src/hooks/anthropic-
|
|
6409
|
+
// src/hooks/anthropic-context-window-limit-recovery/parser.ts
|
|
6410
6410
|
var TOKEN_LIMIT_PATTERNS = [
|
|
6411
6411
|
/(\d+)\s*tokens?\s*>\s*(\d+)\s*maximum/i,
|
|
6412
6412
|
/prompt.*?(\d+).*?tokens.*?exceeds.*?(\d+)/i,
|
|
@@ -6571,7 +6571,7 @@ function parseAnthropicTokenLimitError(err) {
|
|
|
6571
6571
|
return null;
|
|
6572
6572
|
}
|
|
6573
6573
|
|
|
6574
|
-
// src/hooks/anthropic-
|
|
6574
|
+
// src/hooks/anthropic-context-window-limit-recovery/types.ts
|
|
6575
6575
|
var RETRY_CONFIG = {
|
|
6576
6576
|
maxAttempts: 2,
|
|
6577
6577
|
initialDelayMs: 2000,
|
|
@@ -6589,17 +6589,17 @@ var TRUNCATE_CONFIG = {
|
|
|
6589
6589
|
charsPerToken: 4
|
|
6590
6590
|
};
|
|
6591
6591
|
|
|
6592
|
-
// src/hooks/anthropic-
|
|
6592
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts
|
|
6593
6593
|
import { existsSync as existsSync15, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
|
|
6594
6594
|
import { join as join20 } from "path";
|
|
6595
6595
|
|
|
6596
|
-
// src/hooks/anthropic-
|
|
6596
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts
|
|
6597
6597
|
var CHARS_PER_TOKEN = 4;
|
|
6598
6598
|
function estimateTokens2(text) {
|
|
6599
6599
|
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
6600
6600
|
}
|
|
6601
6601
|
|
|
6602
|
-
// src/hooks/anthropic-
|
|
6602
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts
|
|
6603
6603
|
function createToolSignature(toolName, input) {
|
|
6604
6604
|
const sortedInput = sortObject(input);
|
|
6605
6605
|
return `${toolName}::${JSON.stringify(sortedInput)}`;
|
|
@@ -6734,7 +6734,7 @@ function findToolOutput(messages, callID) {
|
|
|
6734
6734
|
return null;
|
|
6735
6735
|
}
|
|
6736
6736
|
|
|
6737
|
-
// src/hooks/anthropic-
|
|
6737
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-supersede.ts
|
|
6738
6738
|
import { existsSync as existsSync16, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
|
|
6739
6739
|
import { join as join21 } from "path";
|
|
6740
6740
|
function getMessageDir4(sessionID) {
|
|
@@ -6896,7 +6896,7 @@ function findToolInput(messages, callID) {
|
|
|
6896
6896
|
return null;
|
|
6897
6897
|
}
|
|
6898
6898
|
|
|
6899
|
-
// src/hooks/anthropic-
|
|
6899
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-purge-errors.ts
|
|
6900
6900
|
import { existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
6901
6901
|
import { join as join22 } from "path";
|
|
6902
6902
|
function getMessageDir5(sessionID) {
|
|
@@ -7001,7 +7001,7 @@ function executePurgeErrors(sessionID, state2, config, protectedTools) {
|
|
|
7001
7001
|
return prunedCount;
|
|
7002
7002
|
}
|
|
7003
7003
|
|
|
7004
|
-
// src/hooks/anthropic-
|
|
7004
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-storage.ts
|
|
7005
7005
|
import { existsSync as existsSync18, readdirSync as readdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
7006
7006
|
import { join as join23 } from "path";
|
|
7007
7007
|
function getMessageDir6(sessionID) {
|
|
@@ -7070,7 +7070,7 @@ async function applyPruning(sessionID, state2) {
|
|
|
7070
7070
|
return totalTokensSaved;
|
|
7071
7071
|
}
|
|
7072
7072
|
|
|
7073
|
-
// src/hooks/anthropic-
|
|
7073
|
+
// src/hooks/anthropic-context-window-limit-recovery/pruning-executor.ts
|
|
7074
7074
|
var DEFAULT_PROTECTED_TOOLS = new Set([
|
|
7075
7075
|
"task",
|
|
7076
7076
|
"todowrite",
|
|
@@ -7151,7 +7151,7 @@ async function executeDynamicContextPruning(sessionID, config, client) {
|
|
|
7151
7151
|
return result;
|
|
7152
7152
|
}
|
|
7153
7153
|
|
|
7154
|
-
// src/hooks/anthropic-
|
|
7154
|
+
// src/hooks/anthropic-context-window-limit-recovery/storage.ts
|
|
7155
7155
|
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
7156
7156
|
import { join as join24 } from "path";
|
|
7157
7157
|
var OPENCODE_STORAGE5 = getOpenCodeStorageDir();
|
|
@@ -7293,7 +7293,7 @@ function truncateUntilTargetTokens(sessionID, currentTokens, maxTokens, targetRa
|
|
|
7293
7293
|
};
|
|
7294
7294
|
}
|
|
7295
7295
|
|
|
7296
|
-
// src/hooks/anthropic-
|
|
7296
|
+
// src/hooks/anthropic-context-window-limit-recovery/executor.ts
|
|
7297
7297
|
var PLACEHOLDER_TEXT = "[user interrupted]";
|
|
7298
7298
|
function getOrCreateRetryState(autoCompactState, sessionID) {
|
|
7299
7299
|
let state2 = autoCompactState.retryStateBySession.get(sessionID);
|
|
@@ -7492,7 +7492,7 @@ async function fixEmptyMessages(sessionID, autoCompactState, client, messageInde
|
|
|
7492
7492
|
}
|
|
7493
7493
|
return fixed;
|
|
7494
7494
|
}
|
|
7495
|
-
async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental) {
|
|
7495
|
+
async function executeCompact(sessionID, msg, autoCompactState, client, directory, experimental, dcpForCompaction) {
|
|
7496
7496
|
if (autoCompactState.compactionInProgress.has(sessionID)) {
|
|
7497
7497
|
await client.tui.showToast({
|
|
7498
7498
|
body: {
|
|
@@ -7509,14 +7509,14 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7509
7509
|
const errorData = autoCompactState.errorDataBySession.get(sessionID);
|
|
7510
7510
|
const truncateState = getOrCreateTruncateState(autoCompactState, sessionID);
|
|
7511
7511
|
const dcpState = getOrCreateDcpState(autoCompactState, sessionID);
|
|
7512
|
-
if (
|
|
7512
|
+
if (dcpForCompaction !== false && !dcpState.attempted && errorData?.currentTokens && errorData?.maxTokens && errorData.currentTokens > errorData.maxTokens) {
|
|
7513
7513
|
dcpState.attempted = true;
|
|
7514
7514
|
log("[auto-compact] DCP triggered FIRST on token limit error", {
|
|
7515
7515
|
sessionID,
|
|
7516
7516
|
currentTokens: errorData.currentTokens,
|
|
7517
7517
|
maxTokens: errorData.maxTokens
|
|
7518
7518
|
});
|
|
7519
|
-
const dcpConfig = experimental
|
|
7519
|
+
const dcpConfig = experimental?.dynamic_context_pruning ?? {
|
|
7520
7520
|
enabled: true,
|
|
7521
7521
|
notification: "detailed",
|
|
7522
7522
|
protected_tools: ["task", "todowrite", "todoread", "lsp_rename", "lsp_code_action_resolve"]
|
|
@@ -7677,7 +7677,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7677
7677
|
const fixed = await fixEmptyMessages(sessionID, autoCompactState, client, errorData.messageIndex);
|
|
7678
7678
|
if (fixed) {
|
|
7679
7679
|
setTimeout(() => {
|
|
7680
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
7680
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental, dcpForCompaction);
|
|
7681
7681
|
}, 500);
|
|
7682
7682
|
return;
|
|
7683
7683
|
}
|
|
@@ -7733,7 +7733,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7733
7733
|
const delay = RETRY_CONFIG.initialDelayMs * Math.pow(RETRY_CONFIG.backoffFactor, retryState.attempt - 1);
|
|
7734
7734
|
const cappedDelay = Math.min(delay, RETRY_CONFIG.maxDelayMs);
|
|
7735
7735
|
setTimeout(() => {
|
|
7736
|
-
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental);
|
|
7736
|
+
executeCompact(sessionID, msg, autoCompactState, client, directory, experimental, dcpForCompaction);
|
|
7737
7737
|
}, cappedDelay);
|
|
7738
7738
|
return;
|
|
7739
7739
|
}
|
|
@@ -7812,8 +7812,8 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
7812
7812
|
}
|
|
7813
7813
|
}
|
|
7814
7814
|
|
|
7815
|
-
// src/hooks/anthropic-
|
|
7816
|
-
function
|
|
7815
|
+
// src/hooks/anthropic-context-window-limit-recovery/index.ts
|
|
7816
|
+
function createRecoveryState() {
|
|
7817
7817
|
return {
|
|
7818
7818
|
pendingCompact: new Set,
|
|
7819
7819
|
errorDataBySession: new Map,
|
|
@@ -7825,9 +7825,10 @@ function createAutoCompactState() {
|
|
|
7825
7825
|
compactionInProgress: new Set
|
|
7826
7826
|
};
|
|
7827
7827
|
}
|
|
7828
|
-
function
|
|
7829
|
-
const autoCompactState =
|
|
7828
|
+
function createAnthropicContextWindowLimitRecoveryHook(ctx, options) {
|
|
7829
|
+
const autoCompactState = createRecoveryState();
|
|
7830
7830
|
const experimental = options?.experimental;
|
|
7831
|
+
const dcpForCompaction = options?.dcpForCompaction;
|
|
7831
7832
|
const eventHandler = async ({ event }) => {
|
|
7832
7833
|
const props = event.properties;
|
|
7833
7834
|
if (event.type === "session.deleted") {
|
|
@@ -7869,7 +7870,7 @@ function createAnthropicAutoCompactHook(ctx, options) {
|
|
|
7869
7870
|
}
|
|
7870
7871
|
}).catch(() => {});
|
|
7871
7872
|
setTimeout(() => {
|
|
7872
|
-
executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
|
|
7873
|
+
executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental, dcpForCompaction);
|
|
7873
7874
|
}, 300);
|
|
7874
7875
|
}
|
|
7875
7876
|
return;
|
|
@@ -7912,7 +7913,7 @@ function createAnthropicAutoCompactHook(ctx, options) {
|
|
|
7912
7913
|
duration: 3000
|
|
7913
7914
|
}
|
|
7914
7915
|
}).catch(() => {});
|
|
7915
|
-
await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental);
|
|
7916
|
+
await executeCompact(sessionID, { providerID, modelID }, autoCompactState, ctx.client, ctx.directory, experimental, dcpForCompaction);
|
|
7916
7917
|
}
|
|
7917
7918
|
};
|
|
7918
7919
|
return {
|
|
@@ -8263,43 +8264,45 @@ function extractPromptText(parts) {
|
|
|
8263
8264
|
}
|
|
8264
8265
|
|
|
8265
8266
|
// src/hooks/think-mode/switcher.ts
|
|
8267
|
+
function normalizeModelID(modelID) {
|
|
8268
|
+
return modelID.replace(/\.(\d+)/g, "-$1");
|
|
8269
|
+
}
|
|
8270
|
+
function resolveProvider(providerID, modelID) {
|
|
8271
|
+
if (providerID === "github-copilot") {
|
|
8272
|
+
const modelLower = modelID.toLowerCase();
|
|
8273
|
+
if (modelLower.includes("claude"))
|
|
8274
|
+
return "anthropic";
|
|
8275
|
+
if (modelLower.includes("gemini"))
|
|
8276
|
+
return "google";
|
|
8277
|
+
if (modelLower.includes("gpt") || modelLower.includes("o1") || modelLower.includes("o3")) {
|
|
8278
|
+
return "openai";
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
8281
|
+
return providerID;
|
|
8282
|
+
}
|
|
8266
8283
|
var HIGH_VARIANT_MAP = {
|
|
8267
8284
|
"claude-sonnet-4-5": "claude-sonnet-4-5-high",
|
|
8268
8285
|
"claude-opus-4-5": "claude-opus-4-5-high",
|
|
8269
8286
|
"gemini-3-pro": "gemini-3-pro-high",
|
|
8270
8287
|
"gemini-3-pro-low": "gemini-3-pro-high",
|
|
8288
|
+
"gemini-3-pro-preview": "gemini-3-pro-preview-high",
|
|
8289
|
+
"gemini-3-flash": "gemini-3-flash-high",
|
|
8290
|
+
"gemini-3-flash-preview": "gemini-3-flash-preview-high",
|
|
8271
8291
|
"gpt-5": "gpt-5-high",
|
|
8272
8292
|
"gpt-5-mini": "gpt-5-mini-high",
|
|
8273
8293
|
"gpt-5-nano": "gpt-5-nano-high",
|
|
8274
8294
|
"gpt-5-pro": "gpt-5-pro-high",
|
|
8275
8295
|
"gpt-5-chat-latest": "gpt-5-chat-latest-high",
|
|
8276
|
-
"gpt-5
|
|
8277
|
-
"gpt-5
|
|
8278
|
-
"gpt-5
|
|
8279
|
-
"gpt-5
|
|
8280
|
-
"gpt-5
|
|
8281
|
-
"gpt-5
|
|
8282
|
-
"gpt-5
|
|
8283
|
-
"gpt-5
|
|
8296
|
+
"gpt-5-1": "gpt-5-1-high",
|
|
8297
|
+
"gpt-5-1-chat-latest": "gpt-5-1-chat-latest-high",
|
|
8298
|
+
"gpt-5-1-codex": "gpt-5-1-codex-high",
|
|
8299
|
+
"gpt-5-1-codex-mini": "gpt-5-1-codex-mini-high",
|
|
8300
|
+
"gpt-5-1-codex-max": "gpt-5-1-codex-max-high",
|
|
8301
|
+
"gpt-5-2": "gpt-5-2-high",
|
|
8302
|
+
"gpt-5-2-chat-latest": "gpt-5-2-chat-latest-high",
|
|
8303
|
+
"gpt-5-2-pro": "gpt-5-2-pro-high"
|
|
8284
8304
|
};
|
|
8285
|
-
var ALREADY_HIGH = new Set(
|
|
8286
|
-
"claude-sonnet-4-5-high",
|
|
8287
|
-
"claude-opus-4-5-high",
|
|
8288
|
-
"gemini-3-pro-high",
|
|
8289
|
-
"gpt-5-high",
|
|
8290
|
-
"gpt-5-mini-high",
|
|
8291
|
-
"gpt-5-nano-high",
|
|
8292
|
-
"gpt-5-pro-high",
|
|
8293
|
-
"gpt-5-chat-latest-high",
|
|
8294
|
-
"gpt-5.1-high",
|
|
8295
|
-
"gpt-5.1-chat-latest-high",
|
|
8296
|
-
"gpt-5.1-codex-high",
|
|
8297
|
-
"gpt-5.1-codex-mini-high",
|
|
8298
|
-
"gpt-5.1-codex-max-high",
|
|
8299
|
-
"gpt-5.2-high",
|
|
8300
|
-
"gpt-5.2-chat-latest-high",
|
|
8301
|
-
"gpt-5.2-pro-high"
|
|
8302
|
-
]);
|
|
8305
|
+
var ALREADY_HIGH = new Set(Object.values(HIGH_VARIANT_MAP));
|
|
8303
8306
|
var THINKING_CONFIGS = {
|
|
8304
8307
|
anthropic: {
|
|
8305
8308
|
thinking: {
|
|
@@ -8332,33 +8335,44 @@ var THINKING_CONFIGS = {
|
|
|
8332
8335
|
}
|
|
8333
8336
|
}
|
|
8334
8337
|
}
|
|
8338
|
+
},
|
|
8339
|
+
openai: {
|
|
8340
|
+
reasoning_effort: "high"
|
|
8335
8341
|
}
|
|
8336
8342
|
};
|
|
8337
8343
|
var THINKING_CAPABLE_MODELS = {
|
|
8338
8344
|
anthropic: ["claude-sonnet-4", "claude-opus-4", "claude-3"],
|
|
8339
8345
|
"amazon-bedrock": ["claude", "anthropic"],
|
|
8340
8346
|
google: ["gemini-2", "gemini-3"],
|
|
8341
|
-
"google-vertex": ["gemini-2", "gemini-3"]
|
|
8347
|
+
"google-vertex": ["gemini-2", "gemini-3"],
|
|
8348
|
+
openai: ["gpt-5", "o1", "o3"]
|
|
8342
8349
|
};
|
|
8343
8350
|
function getHighVariant(modelID) {
|
|
8344
|
-
|
|
8351
|
+
const normalized = normalizeModelID(modelID);
|
|
8352
|
+
if (ALREADY_HIGH.has(normalized)) {
|
|
8345
8353
|
return null;
|
|
8346
8354
|
}
|
|
8347
|
-
return HIGH_VARIANT_MAP[
|
|
8355
|
+
return HIGH_VARIANT_MAP[normalized] ?? null;
|
|
8348
8356
|
}
|
|
8349
8357
|
function isAlreadyHighVariant(modelID) {
|
|
8350
|
-
|
|
8358
|
+
const normalized = normalizeModelID(modelID);
|
|
8359
|
+
return ALREADY_HIGH.has(normalized) || normalized.endsWith("-high");
|
|
8360
|
+
}
|
|
8361
|
+
function isThinkingProvider(provider) {
|
|
8362
|
+
return provider in THINKING_CONFIGS;
|
|
8351
8363
|
}
|
|
8352
8364
|
function getThinkingConfig(providerID, modelID) {
|
|
8353
|
-
|
|
8365
|
+
const normalized = normalizeModelID(modelID);
|
|
8366
|
+
if (isAlreadyHighVariant(normalized)) {
|
|
8354
8367
|
return null;
|
|
8355
8368
|
}
|
|
8356
|
-
const
|
|
8357
|
-
|
|
8358
|
-
if (!config || !capablePatterns) {
|
|
8369
|
+
const resolvedProvider = resolveProvider(providerID, modelID);
|
|
8370
|
+
if (!isThinkingProvider(resolvedProvider)) {
|
|
8359
8371
|
return null;
|
|
8360
8372
|
}
|
|
8361
|
-
const
|
|
8373
|
+
const config = THINKING_CONFIGS[resolvedProvider];
|
|
8374
|
+
const capablePatterns = THINKING_CAPABLE_MODELS[resolvedProvider];
|
|
8375
|
+
const modelLower = normalized.toLowerCase();
|
|
8362
8376
|
const isCapable = capablePatterns.some((pattern) => modelLower.includes(pattern.toLowerCase()));
|
|
8363
8377
|
return isCapable ? config : null;
|
|
8364
8378
|
}
|
|
@@ -11125,6 +11139,297 @@ function createThinkingBlockValidatorHook() {
|
|
|
11125
11139
|
}
|
|
11126
11140
|
};
|
|
11127
11141
|
}
|
|
11142
|
+
// src/hooks/ralph-loop/index.ts
|
|
11143
|
+
import { existsSync as existsSync31, readFileSync as readFileSync21 } from "fs";
|
|
11144
|
+
|
|
11145
|
+
// src/hooks/ralph-loop/storage.ts
|
|
11146
|
+
import { existsSync as existsSync30, readFileSync as readFileSync20, writeFileSync as writeFileSync13, unlinkSync as unlinkSync9, mkdirSync as mkdirSync10 } from "fs";
|
|
11147
|
+
import { dirname as dirname6, join as join40 } from "path";
|
|
11148
|
+
|
|
11149
|
+
// src/hooks/ralph-loop/constants.ts
|
|
11150
|
+
var HOOK_NAME3 = "ralph-loop";
|
|
11151
|
+
var DEFAULT_STATE_FILE = ".sisyphus/ralph-loop.local.md";
|
|
11152
|
+
var DEFAULT_MAX_ITERATIONS = 100;
|
|
11153
|
+
var DEFAULT_COMPLETION_PROMISE = "DONE";
|
|
11154
|
+
|
|
11155
|
+
// src/hooks/ralph-loop/storage.ts
|
|
11156
|
+
function getStateFilePath(directory, customPath) {
|
|
11157
|
+
return customPath ? join40(directory, customPath) : join40(directory, DEFAULT_STATE_FILE);
|
|
11158
|
+
}
|
|
11159
|
+
function readState(directory, customPath) {
|
|
11160
|
+
const filePath = getStateFilePath(directory, customPath);
|
|
11161
|
+
if (!existsSync30(filePath)) {
|
|
11162
|
+
return null;
|
|
11163
|
+
}
|
|
11164
|
+
try {
|
|
11165
|
+
const content = readFileSync20(filePath, "utf-8");
|
|
11166
|
+
const { data, body } = parseFrontmatter(content);
|
|
11167
|
+
const active = data.active;
|
|
11168
|
+
const iteration = data.iteration;
|
|
11169
|
+
if (active === undefined || iteration === undefined) {
|
|
11170
|
+
return null;
|
|
11171
|
+
}
|
|
11172
|
+
const isActive = active === true || active === "true";
|
|
11173
|
+
const iterationNum = typeof iteration === "number" ? iteration : Number(iteration);
|
|
11174
|
+
if (isNaN(iterationNum)) {
|
|
11175
|
+
return null;
|
|
11176
|
+
}
|
|
11177
|
+
const stripQuotes = (val) => {
|
|
11178
|
+
const str = String(val ?? "");
|
|
11179
|
+
return str.replace(/^["']|["']$/g, "");
|
|
11180
|
+
};
|
|
11181
|
+
return {
|
|
11182
|
+
active: isActive,
|
|
11183
|
+
iteration: iterationNum,
|
|
11184
|
+
max_iterations: Number(data.max_iterations) || DEFAULT_MAX_ITERATIONS,
|
|
11185
|
+
completion_promise: stripQuotes(data.completion_promise) || DEFAULT_COMPLETION_PROMISE,
|
|
11186
|
+
started_at: stripQuotes(data.started_at) || new Date().toISOString(),
|
|
11187
|
+
prompt: body.trim(),
|
|
11188
|
+
session_id: data.session_id ? stripQuotes(data.session_id) : undefined
|
|
11189
|
+
};
|
|
11190
|
+
} catch {
|
|
11191
|
+
return null;
|
|
11192
|
+
}
|
|
11193
|
+
}
|
|
11194
|
+
function writeState(directory, state2, customPath) {
|
|
11195
|
+
const filePath = getStateFilePath(directory, customPath);
|
|
11196
|
+
try {
|
|
11197
|
+
const dir = dirname6(filePath);
|
|
11198
|
+
if (!existsSync30(dir)) {
|
|
11199
|
+
mkdirSync10(dir, { recursive: true });
|
|
11200
|
+
}
|
|
11201
|
+
const sessionIdLine = state2.session_id ? `session_id: "${state2.session_id}"
|
|
11202
|
+
` : "";
|
|
11203
|
+
const content = `---
|
|
11204
|
+
active: ${state2.active}
|
|
11205
|
+
iteration: ${state2.iteration}
|
|
11206
|
+
max_iterations: ${state2.max_iterations}
|
|
11207
|
+
completion_promise: "${state2.completion_promise}"
|
|
11208
|
+
started_at: "${state2.started_at}"
|
|
11209
|
+
${sessionIdLine}---
|
|
11210
|
+
${state2.prompt}
|
|
11211
|
+
`;
|
|
11212
|
+
writeFileSync13(filePath, content, "utf-8");
|
|
11213
|
+
return true;
|
|
11214
|
+
} catch {
|
|
11215
|
+
return false;
|
|
11216
|
+
}
|
|
11217
|
+
}
|
|
11218
|
+
function clearState(directory, customPath) {
|
|
11219
|
+
const filePath = getStateFilePath(directory, customPath);
|
|
11220
|
+
try {
|
|
11221
|
+
if (existsSync30(filePath)) {
|
|
11222
|
+
unlinkSync9(filePath);
|
|
11223
|
+
}
|
|
11224
|
+
return true;
|
|
11225
|
+
} catch {
|
|
11226
|
+
return false;
|
|
11227
|
+
}
|
|
11228
|
+
}
|
|
11229
|
+
function incrementIteration(directory, customPath) {
|
|
11230
|
+
const state2 = readState(directory, customPath);
|
|
11231
|
+
if (!state2)
|
|
11232
|
+
return null;
|
|
11233
|
+
state2.iteration += 1;
|
|
11234
|
+
if (writeState(directory, state2, customPath)) {
|
|
11235
|
+
return state2;
|
|
11236
|
+
}
|
|
11237
|
+
return null;
|
|
11238
|
+
}
|
|
11239
|
+
|
|
11240
|
+
// src/hooks/ralph-loop/index.ts
|
|
11241
|
+
var CONTINUATION_PROMPT2 = `[RALPH LOOP - ITERATION {{ITERATION}}/{{MAX}}]
|
|
11242
|
+
|
|
11243
|
+
Your previous attempt did not output the completion promise. Continue working on the task.
|
|
11244
|
+
|
|
11245
|
+
IMPORTANT:
|
|
11246
|
+
- Review your progress so far
|
|
11247
|
+
- Continue from where you left off
|
|
11248
|
+
- When FULLY complete, output: <promise>{{PROMISE}}</promise>
|
|
11249
|
+
- Do not stop until the task is truly done
|
|
11250
|
+
|
|
11251
|
+
Original task:
|
|
11252
|
+
{{PROMPT}}`;
|
|
11253
|
+
function createRalphLoopHook(ctx, options) {
|
|
11254
|
+
const sessions = new Map;
|
|
11255
|
+
const config = options?.config;
|
|
11256
|
+
const stateDir = config?.state_dir;
|
|
11257
|
+
function getSessionState(sessionID) {
|
|
11258
|
+
let state2 = sessions.get(sessionID);
|
|
11259
|
+
if (!state2) {
|
|
11260
|
+
state2 = {};
|
|
11261
|
+
sessions.set(sessionID, state2);
|
|
11262
|
+
}
|
|
11263
|
+
return state2;
|
|
11264
|
+
}
|
|
11265
|
+
function detectCompletionPromise(transcriptPath, promise) {
|
|
11266
|
+
if (!transcriptPath)
|
|
11267
|
+
return false;
|
|
11268
|
+
try {
|
|
11269
|
+
if (!existsSync31(transcriptPath))
|
|
11270
|
+
return false;
|
|
11271
|
+
const content = readFileSync21(transcriptPath, "utf-8");
|
|
11272
|
+
const pattern = new RegExp(`<promise>\\s*${escapeRegex(promise)}\\s*</promise>`, "is");
|
|
11273
|
+
return pattern.test(content);
|
|
11274
|
+
} catch {
|
|
11275
|
+
return false;
|
|
11276
|
+
}
|
|
11277
|
+
}
|
|
11278
|
+
function escapeRegex(str) {
|
|
11279
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
11280
|
+
}
|
|
11281
|
+
const startLoop = (sessionID, prompt, loopOptions) => {
|
|
11282
|
+
const state2 = {
|
|
11283
|
+
active: true,
|
|
11284
|
+
iteration: 1,
|
|
11285
|
+
max_iterations: loopOptions?.maxIterations ?? config?.default_max_iterations ?? DEFAULT_MAX_ITERATIONS,
|
|
11286
|
+
completion_promise: loopOptions?.completionPromise ?? DEFAULT_COMPLETION_PROMISE,
|
|
11287
|
+
started_at: new Date().toISOString(),
|
|
11288
|
+
prompt,
|
|
11289
|
+
session_id: sessionID
|
|
11290
|
+
};
|
|
11291
|
+
const success = writeState(ctx.directory, state2, stateDir);
|
|
11292
|
+
if (success) {
|
|
11293
|
+
log(`[${HOOK_NAME3}] Loop started`, {
|
|
11294
|
+
sessionID,
|
|
11295
|
+
maxIterations: state2.max_iterations,
|
|
11296
|
+
completionPromise: state2.completion_promise
|
|
11297
|
+
});
|
|
11298
|
+
}
|
|
11299
|
+
return success;
|
|
11300
|
+
};
|
|
11301
|
+
const cancelLoop = (sessionID) => {
|
|
11302
|
+
const state2 = readState(ctx.directory, stateDir);
|
|
11303
|
+
if (!state2 || state2.session_id !== sessionID) {
|
|
11304
|
+
return false;
|
|
11305
|
+
}
|
|
11306
|
+
const success = clearState(ctx.directory, stateDir);
|
|
11307
|
+
if (success) {
|
|
11308
|
+
log(`[${HOOK_NAME3}] Loop cancelled`, { sessionID, iteration: state2.iteration });
|
|
11309
|
+
}
|
|
11310
|
+
return success;
|
|
11311
|
+
};
|
|
11312
|
+
const getState = () => {
|
|
11313
|
+
return readState(ctx.directory, stateDir);
|
|
11314
|
+
};
|
|
11315
|
+
const event = async ({
|
|
11316
|
+
event: event2
|
|
11317
|
+
}) => {
|
|
11318
|
+
const props = event2.properties;
|
|
11319
|
+
if (event2.type === "session.idle") {
|
|
11320
|
+
const sessionID = props?.sessionID;
|
|
11321
|
+
if (!sessionID)
|
|
11322
|
+
return;
|
|
11323
|
+
const sessionState = getSessionState(sessionID);
|
|
11324
|
+
if (sessionState.isRecovering) {
|
|
11325
|
+
log(`[${HOOK_NAME3}] Skipped: in recovery`, { sessionID });
|
|
11326
|
+
return;
|
|
11327
|
+
}
|
|
11328
|
+
const state2 = readState(ctx.directory, stateDir);
|
|
11329
|
+
if (!state2 || !state2.active) {
|
|
11330
|
+
return;
|
|
11331
|
+
}
|
|
11332
|
+
if (state2.session_id && state2.session_id !== sessionID) {
|
|
11333
|
+
return;
|
|
11334
|
+
}
|
|
11335
|
+
const transcriptPath = props?.transcriptPath;
|
|
11336
|
+
if (detectCompletionPromise(transcriptPath, state2.completion_promise)) {
|
|
11337
|
+
log(`[${HOOK_NAME3}] Completion detected!`, {
|
|
11338
|
+
sessionID,
|
|
11339
|
+
iteration: state2.iteration,
|
|
11340
|
+
promise: state2.completion_promise
|
|
11341
|
+
});
|
|
11342
|
+
clearState(ctx.directory, stateDir);
|
|
11343
|
+
await ctx.client.tui.showToast({
|
|
11344
|
+
body: {
|
|
11345
|
+
title: "Ralph Loop Complete!",
|
|
11346
|
+
message: `Task completed after ${state2.iteration} iteration(s)`,
|
|
11347
|
+
variant: "success",
|
|
11348
|
+
duration: 5000
|
|
11349
|
+
}
|
|
11350
|
+
}).catch(() => {});
|
|
11351
|
+
return;
|
|
11352
|
+
}
|
|
11353
|
+
if (state2.iteration >= state2.max_iterations) {
|
|
11354
|
+
log(`[${HOOK_NAME3}] Max iterations reached`, {
|
|
11355
|
+
sessionID,
|
|
11356
|
+
iteration: state2.iteration,
|
|
11357
|
+
max: state2.max_iterations
|
|
11358
|
+
});
|
|
11359
|
+
clearState(ctx.directory, stateDir);
|
|
11360
|
+
await ctx.client.tui.showToast({
|
|
11361
|
+
body: {
|
|
11362
|
+
title: "Ralph Loop Stopped",
|
|
11363
|
+
message: `Max iterations (${state2.max_iterations}) reached without completion`,
|
|
11364
|
+
variant: "warning",
|
|
11365
|
+
duration: 5000
|
|
11366
|
+
}
|
|
11367
|
+
}).catch(() => {});
|
|
11368
|
+
return;
|
|
11369
|
+
}
|
|
11370
|
+
const newState = incrementIteration(ctx.directory, stateDir);
|
|
11371
|
+
if (!newState) {
|
|
11372
|
+
log(`[${HOOK_NAME3}] Failed to increment iteration`, { sessionID });
|
|
11373
|
+
return;
|
|
11374
|
+
}
|
|
11375
|
+
log(`[${HOOK_NAME3}] Continuing loop`, {
|
|
11376
|
+
sessionID,
|
|
11377
|
+
iteration: newState.iteration,
|
|
11378
|
+
max: newState.max_iterations
|
|
11379
|
+
});
|
|
11380
|
+
const continuationPrompt = CONTINUATION_PROMPT2.replace("{{ITERATION}}", String(newState.iteration)).replace("{{MAX}}", String(newState.max_iterations)).replace("{{PROMISE}}", newState.completion_promise).replace("{{PROMPT}}", newState.prompt);
|
|
11381
|
+
await ctx.client.tui.showToast({
|
|
11382
|
+
body: {
|
|
11383
|
+
title: "Ralph Loop",
|
|
11384
|
+
message: `Iteration ${newState.iteration}/${newState.max_iterations}`,
|
|
11385
|
+
variant: "info",
|
|
11386
|
+
duration: 2000
|
|
11387
|
+
}
|
|
11388
|
+
}).catch(() => {});
|
|
11389
|
+
try {
|
|
11390
|
+
await ctx.client.session.prompt({
|
|
11391
|
+
path: { id: sessionID },
|
|
11392
|
+
body: {
|
|
11393
|
+
parts: [{ type: "text", text: continuationPrompt }]
|
|
11394
|
+
},
|
|
11395
|
+
query: { directory: ctx.directory }
|
|
11396
|
+
});
|
|
11397
|
+
} catch (err) {
|
|
11398
|
+
log(`[${HOOK_NAME3}] Failed to inject continuation`, {
|
|
11399
|
+
sessionID,
|
|
11400
|
+
error: String(err)
|
|
11401
|
+
});
|
|
11402
|
+
}
|
|
11403
|
+
}
|
|
11404
|
+
if (event2.type === "session.deleted") {
|
|
11405
|
+
const sessionInfo = props?.info;
|
|
11406
|
+
if (sessionInfo?.id) {
|
|
11407
|
+
const state2 = readState(ctx.directory, stateDir);
|
|
11408
|
+
if (state2?.session_id === sessionInfo.id) {
|
|
11409
|
+
clearState(ctx.directory, stateDir);
|
|
11410
|
+
log(`[${HOOK_NAME3}] Session deleted, loop cleared`, { sessionID: sessionInfo.id });
|
|
11411
|
+
}
|
|
11412
|
+
sessions.delete(sessionInfo.id);
|
|
11413
|
+
}
|
|
11414
|
+
}
|
|
11415
|
+
if (event2.type === "session.error") {
|
|
11416
|
+
const sessionID = props?.sessionID;
|
|
11417
|
+
if (sessionID) {
|
|
11418
|
+
const sessionState = getSessionState(sessionID);
|
|
11419
|
+
sessionState.isRecovering = true;
|
|
11420
|
+
setTimeout(() => {
|
|
11421
|
+
sessionState.isRecovering = false;
|
|
11422
|
+
}, 5000);
|
|
11423
|
+
}
|
|
11424
|
+
}
|
|
11425
|
+
};
|
|
11426
|
+
return {
|
|
11427
|
+
event,
|
|
11428
|
+
startLoop,
|
|
11429
|
+
cancelLoop,
|
|
11430
|
+
getState
|
|
11431
|
+
};
|
|
11432
|
+
}
|
|
11128
11433
|
// src/auth/antigravity/constants.ts
|
|
11129
11434
|
var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
11130
11435
|
var ANTIGRAVITY_CLIENT_SECRET = "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";
|
|
@@ -12955,10 +13260,10 @@ async function createGoogleAntigravityAuthPlugin({
|
|
|
12955
13260
|
};
|
|
12956
13261
|
}
|
|
12957
13262
|
// src/features/claude-code-command-loader/loader.ts
|
|
12958
|
-
import { existsSync as
|
|
12959
|
-
import { join as
|
|
13263
|
+
import { existsSync as existsSync32, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
13264
|
+
import { join as join41, basename } from "path";
|
|
12960
13265
|
function loadCommandsFromDir(commandsDir, scope) {
|
|
12961
|
-
if (!
|
|
13266
|
+
if (!existsSync32(commandsDir)) {
|
|
12962
13267
|
return [];
|
|
12963
13268
|
}
|
|
12964
13269
|
const entries = readdirSync11(commandsDir, { withFileTypes: true });
|
|
@@ -12966,10 +13271,10 @@ function loadCommandsFromDir(commandsDir, scope) {
|
|
|
12966
13271
|
for (const entry of entries) {
|
|
12967
13272
|
if (!isMarkdownFile(entry))
|
|
12968
13273
|
continue;
|
|
12969
|
-
const commandPath =
|
|
13274
|
+
const commandPath = join41(commandsDir, entry.name);
|
|
12970
13275
|
const commandName = basename(entry.name, ".md");
|
|
12971
13276
|
try {
|
|
12972
|
-
const content =
|
|
13277
|
+
const content = readFileSync22(commandPath, "utf-8");
|
|
12973
13278
|
const { data, body } = parseFrontmatter(content);
|
|
12974
13279
|
const wrappedTemplate = `<command-instruction>
|
|
12975
13280
|
${body.trim()}
|
|
@@ -13009,23 +13314,23 @@ function commandsToRecord(commands) {
|
|
|
13009
13314
|
return result;
|
|
13010
13315
|
}
|
|
13011
13316
|
function loadUserCommands() {
|
|
13012
|
-
const userCommandsDir =
|
|
13317
|
+
const userCommandsDir = join41(getClaudeConfigDir(), "commands");
|
|
13013
13318
|
const commands = loadCommandsFromDir(userCommandsDir, "user");
|
|
13014
13319
|
return commandsToRecord(commands);
|
|
13015
13320
|
}
|
|
13016
13321
|
function loadProjectCommands() {
|
|
13017
|
-
const projectCommandsDir =
|
|
13322
|
+
const projectCommandsDir = join41(process.cwd(), ".claude", "commands");
|
|
13018
13323
|
const commands = loadCommandsFromDir(projectCommandsDir, "project");
|
|
13019
13324
|
return commandsToRecord(commands);
|
|
13020
13325
|
}
|
|
13021
13326
|
function loadOpencodeGlobalCommands() {
|
|
13022
13327
|
const { homedir: homedir9 } = __require("os");
|
|
13023
|
-
const opencodeCommandsDir =
|
|
13328
|
+
const opencodeCommandsDir = join41(homedir9(), ".config", "opencode", "command");
|
|
13024
13329
|
const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
|
|
13025
13330
|
return commandsToRecord(commands);
|
|
13026
13331
|
}
|
|
13027
13332
|
function loadOpencodeProjectCommands() {
|
|
13028
|
-
const opencodeProjectDir =
|
|
13333
|
+
const opencodeProjectDir = join41(process.cwd(), ".opencode", "command");
|
|
13029
13334
|
const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
13030
13335
|
return commandsToRecord(commands);
|
|
13031
13336
|
}
|
|
@@ -13425,6 +13730,45 @@ Hierarchy:
|
|
|
13425
13730
|
- **LSP without fallback**: Always have explore agent backup if LSP unavailable
|
|
13426
13731
|
- **Over-referencing**: Don't trace refs for EVERY symbol - focus on exports only`;
|
|
13427
13732
|
|
|
13733
|
+
// src/features/builtin-commands/templates/ralph-loop.ts
|
|
13734
|
+
var RALPH_LOOP_TEMPLATE = `You are starting a Ralph Loop - a self-referential development loop that runs until task completion.
|
|
13735
|
+
|
|
13736
|
+
## How Ralph Loop Works
|
|
13737
|
+
|
|
13738
|
+
1. You will work on the task continuously
|
|
13739
|
+
2. When you believe the task is FULLY complete, output: \`<promise>{{COMPLETION_PROMISE}}</promise>\`
|
|
13740
|
+
3. If you don't output the promise, the loop will automatically inject another prompt to continue
|
|
13741
|
+
4. Maximum iterations: Configurable (default 100)
|
|
13742
|
+
|
|
13743
|
+
## Rules
|
|
13744
|
+
|
|
13745
|
+
- Focus on completing the task fully, not partially
|
|
13746
|
+
- Don't output the completion promise until the task is truly done
|
|
13747
|
+
- Each iteration should make meaningful progress toward the goal
|
|
13748
|
+
- If stuck, try different approaches
|
|
13749
|
+
- Use todos to track your progress
|
|
13750
|
+
|
|
13751
|
+
## Exit Conditions
|
|
13752
|
+
|
|
13753
|
+
1. **Completion**: Output \`<promise>DONE</promise>\` (or custom promise text) when fully complete
|
|
13754
|
+
2. **Max Iterations**: Loop stops automatically at limit
|
|
13755
|
+
3. **Cancel**: User runs \`/cancel-ralph\` command
|
|
13756
|
+
|
|
13757
|
+
## Your Task
|
|
13758
|
+
|
|
13759
|
+
Parse the arguments below and begin working on the task. The format is:
|
|
13760
|
+
\`"task description" [--completion-promise=TEXT] [--max-iterations=N]\`
|
|
13761
|
+
|
|
13762
|
+
Default completion promise is "DONE" and default max iterations is 100.`;
|
|
13763
|
+
var CANCEL_RALPH_TEMPLATE = `Cancel the currently active Ralph Loop.
|
|
13764
|
+
|
|
13765
|
+
This will:
|
|
13766
|
+
1. Stop the loop from continuing
|
|
13767
|
+
2. Clear the loop state file
|
|
13768
|
+
3. Allow the session to end normally
|
|
13769
|
+
|
|
13770
|
+
Check if a loop is active and cancel it. Inform the user of the result.`;
|
|
13771
|
+
|
|
13428
13772
|
// src/features/builtin-commands/commands.ts
|
|
13429
13773
|
var BUILTIN_COMMAND_DEFINITIONS = {
|
|
13430
13774
|
"init-deep": {
|
|
@@ -13437,6 +13781,23 @@ ${INIT_DEEP_TEMPLATE}
|
|
|
13437
13781
|
$ARGUMENTS
|
|
13438
13782
|
</user-request>`,
|
|
13439
13783
|
argumentHint: "[--create-new] [--max-depth=N]"
|
|
13784
|
+
},
|
|
13785
|
+
"ralph-loop": {
|
|
13786
|
+
description: "(builtin) Start self-referential development loop until completion",
|
|
13787
|
+
template: `<command-instruction>
|
|
13788
|
+
${RALPH_LOOP_TEMPLATE}
|
|
13789
|
+
</command-instruction>
|
|
13790
|
+
|
|
13791
|
+
<user-task>
|
|
13792
|
+
$ARGUMENTS
|
|
13793
|
+
</user-task>`,
|
|
13794
|
+
argumentHint: '"task description" [--completion-promise=TEXT] [--max-iterations=N]'
|
|
13795
|
+
},
|
|
13796
|
+
"cancel-ralph": {
|
|
13797
|
+
description: "(builtin) Cancel active Ralph Loop",
|
|
13798
|
+
template: `<command-instruction>
|
|
13799
|
+
${CANCEL_RALPH_TEMPLATE}
|
|
13800
|
+
</command-instruction>`
|
|
13440
13801
|
}
|
|
13441
13802
|
};
|
|
13442
13803
|
function loadBuiltinCommands(disabledCommands) {
|
|
@@ -13452,9 +13813,373 @@ function loadBuiltinCommands(disabledCommands) {
|
|
|
13452
13813
|
}
|
|
13453
13814
|
return commands;
|
|
13454
13815
|
}
|
|
13816
|
+
// src/features/opencode-skill-loader/loader.ts
|
|
13817
|
+
import { existsSync as existsSync33, readdirSync as readdirSync12, readFileSync as readFileSync23 } from "fs";
|
|
13818
|
+
import { join as join42, basename as basename2 } from "path";
|
|
13819
|
+
import { homedir as homedir9 } from "os";
|
|
13820
|
+
function parseAllowedTools(allowedTools) {
|
|
13821
|
+
if (!allowedTools)
|
|
13822
|
+
return;
|
|
13823
|
+
return allowedTools.split(/\s+/).filter(Boolean);
|
|
13824
|
+
}
|
|
13825
|
+
function loadSkillFromPath(skillPath, resolvedPath, defaultName, scope) {
|
|
13826
|
+
try {
|
|
13827
|
+
const content = readFileSync23(skillPath, "utf-8");
|
|
13828
|
+
const { data, body } = parseFrontmatter(content);
|
|
13829
|
+
const skillName = data.name || defaultName;
|
|
13830
|
+
const originalDescription = data.description || "";
|
|
13831
|
+
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
13832
|
+
const formattedDescription = `(${scope} - Skill) ${originalDescription}`;
|
|
13833
|
+
const wrappedTemplate = `<skill-instruction>
|
|
13834
|
+
Base directory for this skill: ${resolvedPath}/
|
|
13835
|
+
File references (@path) in this skill are relative to this directory.
|
|
13836
|
+
|
|
13837
|
+
${body.trim()}
|
|
13838
|
+
</skill-instruction>
|
|
13839
|
+
|
|
13840
|
+
<user-request>
|
|
13841
|
+
$ARGUMENTS
|
|
13842
|
+
</user-request>`;
|
|
13843
|
+
const definition = {
|
|
13844
|
+
name: skillName,
|
|
13845
|
+
description: formattedDescription,
|
|
13846
|
+
template: wrappedTemplate,
|
|
13847
|
+
model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
|
|
13848
|
+
agent: data.agent,
|
|
13849
|
+
subtask: data.subtask,
|
|
13850
|
+
argumentHint: data["argument-hint"]
|
|
13851
|
+
};
|
|
13852
|
+
return {
|
|
13853
|
+
name: skillName,
|
|
13854
|
+
path: skillPath,
|
|
13855
|
+
resolvedPath,
|
|
13856
|
+
definition,
|
|
13857
|
+
scope,
|
|
13858
|
+
license: data.license,
|
|
13859
|
+
compatibility: data.compatibility,
|
|
13860
|
+
metadata: data.metadata,
|
|
13861
|
+
allowedTools: parseAllowedTools(data["allowed-tools"])
|
|
13862
|
+
};
|
|
13863
|
+
} catch {
|
|
13864
|
+
return null;
|
|
13865
|
+
}
|
|
13866
|
+
}
|
|
13867
|
+
function loadSkillsFromDir(skillsDir, scope) {
|
|
13868
|
+
if (!existsSync33(skillsDir)) {
|
|
13869
|
+
return [];
|
|
13870
|
+
}
|
|
13871
|
+
const entries = readdirSync12(skillsDir, { withFileTypes: true });
|
|
13872
|
+
const skills = [];
|
|
13873
|
+
for (const entry of entries) {
|
|
13874
|
+
if (entry.name.startsWith("."))
|
|
13875
|
+
continue;
|
|
13876
|
+
const entryPath = join42(skillsDir, entry.name);
|
|
13877
|
+
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
13878
|
+
const resolvedPath = resolveSymlink(entryPath);
|
|
13879
|
+
const dirName = entry.name;
|
|
13880
|
+
const skillMdPath = join42(resolvedPath, "SKILL.md");
|
|
13881
|
+
if (existsSync33(skillMdPath)) {
|
|
13882
|
+
const skill = loadSkillFromPath(skillMdPath, resolvedPath, dirName, scope);
|
|
13883
|
+
if (skill)
|
|
13884
|
+
skills.push(skill);
|
|
13885
|
+
continue;
|
|
13886
|
+
}
|
|
13887
|
+
const namedSkillMdPath = join42(resolvedPath, `${dirName}.md`);
|
|
13888
|
+
if (existsSync33(namedSkillMdPath)) {
|
|
13889
|
+
const skill = loadSkillFromPath(namedSkillMdPath, resolvedPath, dirName, scope);
|
|
13890
|
+
if (skill)
|
|
13891
|
+
skills.push(skill);
|
|
13892
|
+
continue;
|
|
13893
|
+
}
|
|
13894
|
+
continue;
|
|
13895
|
+
}
|
|
13896
|
+
if (isMarkdownFile(entry)) {
|
|
13897
|
+
const skillName = basename2(entry.name, ".md");
|
|
13898
|
+
const skill = loadSkillFromPath(entryPath, skillsDir, skillName, scope);
|
|
13899
|
+
if (skill)
|
|
13900
|
+
skills.push(skill);
|
|
13901
|
+
}
|
|
13902
|
+
}
|
|
13903
|
+
return skills;
|
|
13904
|
+
}
|
|
13905
|
+
function skillsToRecord(skills) {
|
|
13906
|
+
const result = {};
|
|
13907
|
+
for (const skill of skills) {
|
|
13908
|
+
result[skill.name] = skill.definition;
|
|
13909
|
+
}
|
|
13910
|
+
return result;
|
|
13911
|
+
}
|
|
13912
|
+
function loadUserSkills() {
|
|
13913
|
+
const userSkillsDir = join42(getClaudeConfigDir(), "skills");
|
|
13914
|
+
const skills = loadSkillsFromDir(userSkillsDir, "user");
|
|
13915
|
+
return skillsToRecord(skills);
|
|
13916
|
+
}
|
|
13917
|
+
function loadProjectSkills() {
|
|
13918
|
+
const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
|
|
13919
|
+
const skills = loadSkillsFromDir(projectSkillsDir, "project");
|
|
13920
|
+
return skillsToRecord(skills);
|
|
13921
|
+
}
|
|
13922
|
+
function loadOpencodeGlobalSkills() {
|
|
13923
|
+
const opencodeSkillsDir = join42(homedir9(), ".config", "opencode", "skill");
|
|
13924
|
+
const skills = loadSkillsFromDir(opencodeSkillsDir, "opencode");
|
|
13925
|
+
return skillsToRecord(skills);
|
|
13926
|
+
}
|
|
13927
|
+
function loadOpencodeProjectSkills() {
|
|
13928
|
+
const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
|
|
13929
|
+
const skills = loadSkillsFromDir(opencodeProjectDir, "opencode-project");
|
|
13930
|
+
return skillsToRecord(skills);
|
|
13931
|
+
}
|
|
13932
|
+
function discoverAllSkills() {
|
|
13933
|
+
const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
|
|
13934
|
+
const projectDir = join42(process.cwd(), ".claude", "skills");
|
|
13935
|
+
const opencodeGlobalDir = join42(homedir9(), ".config", "opencode", "skill");
|
|
13936
|
+
const userDir = join42(getClaudeConfigDir(), "skills");
|
|
13937
|
+
const opencodeProjectSkills = loadSkillsFromDir(opencodeProjectDir, "opencode-project");
|
|
13938
|
+
const projectSkills = loadSkillsFromDir(projectDir, "project");
|
|
13939
|
+
const opencodeGlobalSkills = loadSkillsFromDir(opencodeGlobalDir, "opencode");
|
|
13940
|
+
const userSkills = loadSkillsFromDir(userDir, "user");
|
|
13941
|
+
return [...opencodeProjectSkills, ...projectSkills, ...opencodeGlobalSkills, ...userSkills];
|
|
13942
|
+
}
|
|
13943
|
+
function discoverSkills(options = {}) {
|
|
13944
|
+
const { includeClaudeCodePaths = true } = options;
|
|
13945
|
+
const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
|
|
13946
|
+
const opencodeGlobalDir = join42(homedir9(), ".config", "opencode", "skill");
|
|
13947
|
+
const opencodeProjectSkills = loadSkillsFromDir(opencodeProjectDir, "opencode-project");
|
|
13948
|
+
const opencodeGlobalSkills = loadSkillsFromDir(opencodeGlobalDir, "opencode");
|
|
13949
|
+
if (!includeClaudeCodePaths) {
|
|
13950
|
+
return [...opencodeProjectSkills, ...opencodeGlobalSkills];
|
|
13951
|
+
}
|
|
13952
|
+
const projectDir = join42(process.cwd(), ".claude", "skills");
|
|
13953
|
+
const userDir = join42(getClaudeConfigDir(), "skills");
|
|
13954
|
+
const projectSkills = loadSkillsFromDir(projectDir, "project");
|
|
13955
|
+
const userSkills = loadSkillsFromDir(userDir, "user");
|
|
13956
|
+
return [...opencodeProjectSkills, ...projectSkills, ...opencodeGlobalSkills, ...userSkills];
|
|
13957
|
+
}
|
|
13958
|
+
function discoverUserClaudeSkills() {
|
|
13959
|
+
const userSkillsDir = join42(getClaudeConfigDir(), "skills");
|
|
13960
|
+
return loadSkillsFromDir(userSkillsDir, "user");
|
|
13961
|
+
}
|
|
13962
|
+
function discoverProjectClaudeSkills() {
|
|
13963
|
+
const projectSkillsDir = join42(process.cwd(), ".claude", "skills");
|
|
13964
|
+
return loadSkillsFromDir(projectSkillsDir, "project");
|
|
13965
|
+
}
|
|
13966
|
+
function discoverOpencodeGlobalSkills() {
|
|
13967
|
+
const opencodeSkillsDir = join42(homedir9(), ".config", "opencode", "skill");
|
|
13968
|
+
return loadSkillsFromDir(opencodeSkillsDir, "opencode");
|
|
13969
|
+
}
|
|
13970
|
+
function discoverOpencodeProjectSkills() {
|
|
13971
|
+
const opencodeProjectDir = join42(process.cwd(), ".opencode", "skill");
|
|
13972
|
+
return loadSkillsFromDir(opencodeProjectDir, "opencode-project");
|
|
13973
|
+
}
|
|
13974
|
+
// src/features/opencode-skill-loader/merger.ts
|
|
13975
|
+
import { readFileSync as readFileSync24, existsSync as existsSync34 } from "fs";
|
|
13976
|
+
import { dirname as dirname8, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
|
|
13977
|
+
import { homedir as homedir10 } from "os";
|
|
13978
|
+
var SCOPE_PRIORITY = {
|
|
13979
|
+
builtin: 1,
|
|
13980
|
+
config: 2,
|
|
13981
|
+
user: 3,
|
|
13982
|
+
opencode: 4,
|
|
13983
|
+
project: 5,
|
|
13984
|
+
"opencode-project": 6
|
|
13985
|
+
};
|
|
13986
|
+
function builtinToLoaded(builtin) {
|
|
13987
|
+
const definition = {
|
|
13988
|
+
name: builtin.name,
|
|
13989
|
+
description: `(builtin - Skill) ${builtin.description}`,
|
|
13990
|
+
template: builtin.template,
|
|
13991
|
+
model: builtin.model,
|
|
13992
|
+
agent: builtin.agent,
|
|
13993
|
+
subtask: builtin.subtask,
|
|
13994
|
+
argumentHint: builtin.argumentHint
|
|
13995
|
+
};
|
|
13996
|
+
return {
|
|
13997
|
+
name: builtin.name,
|
|
13998
|
+
definition,
|
|
13999
|
+
scope: "builtin",
|
|
14000
|
+
license: builtin.license,
|
|
14001
|
+
compatibility: builtin.compatibility,
|
|
14002
|
+
metadata: builtin.metadata,
|
|
14003
|
+
allowedTools: builtin.allowedTools
|
|
14004
|
+
};
|
|
14005
|
+
}
|
|
14006
|
+
function resolveFilePath2(from, configDir) {
|
|
14007
|
+
let filePath = from;
|
|
14008
|
+
if (filePath.startsWith("{file:") && filePath.endsWith("}")) {
|
|
14009
|
+
filePath = filePath.slice(6, -1);
|
|
14010
|
+
}
|
|
14011
|
+
if (filePath.startsWith("~/")) {
|
|
14012
|
+
return resolve5(homedir10(), filePath.slice(2));
|
|
14013
|
+
}
|
|
14014
|
+
if (isAbsolute2(filePath)) {
|
|
14015
|
+
return filePath;
|
|
14016
|
+
}
|
|
14017
|
+
const baseDir = configDir || process.cwd();
|
|
14018
|
+
return resolve5(baseDir, filePath);
|
|
14019
|
+
}
|
|
14020
|
+
function loadSkillFromFile(filePath) {
|
|
14021
|
+
try {
|
|
14022
|
+
if (!existsSync34(filePath))
|
|
14023
|
+
return null;
|
|
14024
|
+
const content = readFileSync24(filePath, "utf-8");
|
|
14025
|
+
const { data, body } = parseFrontmatter(content);
|
|
14026
|
+
return { template: body, metadata: data };
|
|
14027
|
+
} catch {
|
|
14028
|
+
return null;
|
|
14029
|
+
}
|
|
14030
|
+
}
|
|
14031
|
+
function configEntryToLoaded(name, entry, configDir) {
|
|
14032
|
+
let template = entry.template || "";
|
|
14033
|
+
let fileMetadata = {};
|
|
14034
|
+
if (entry.from) {
|
|
14035
|
+
const filePath = resolveFilePath2(entry.from, configDir);
|
|
14036
|
+
const loaded = loadSkillFromFile(filePath);
|
|
14037
|
+
if (loaded) {
|
|
14038
|
+
template = loaded.template;
|
|
14039
|
+
fileMetadata = loaded.metadata;
|
|
14040
|
+
} else {
|
|
14041
|
+
return null;
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14044
|
+
if (!template && !entry.from) {
|
|
14045
|
+
return null;
|
|
14046
|
+
}
|
|
14047
|
+
const description = entry.description || fileMetadata.description || "";
|
|
14048
|
+
const resolvedPath = entry.from ? dirname8(resolveFilePath2(entry.from, configDir)) : configDir || process.cwd();
|
|
14049
|
+
const wrappedTemplate = `<skill-instruction>
|
|
14050
|
+
Base directory for this skill: ${resolvedPath}/
|
|
14051
|
+
File references (@path) in this skill are relative to this directory.
|
|
14052
|
+
|
|
14053
|
+
${template.trim()}
|
|
14054
|
+
</skill-instruction>
|
|
14055
|
+
|
|
14056
|
+
<user-request>
|
|
14057
|
+
$ARGUMENTS
|
|
14058
|
+
</user-request>`;
|
|
14059
|
+
const definition = {
|
|
14060
|
+
name,
|
|
14061
|
+
description: `(config - Skill) ${description}`,
|
|
14062
|
+
template: wrappedTemplate,
|
|
14063
|
+
model: sanitizeModelField(entry.model || fileMetadata.model, "opencode"),
|
|
14064
|
+
agent: entry.agent || fileMetadata.agent,
|
|
14065
|
+
subtask: entry.subtask ?? fileMetadata.subtask,
|
|
14066
|
+
argumentHint: entry["argument-hint"] || fileMetadata["argument-hint"]
|
|
14067
|
+
};
|
|
14068
|
+
const allowedTools = entry["allowed-tools"] || (fileMetadata["allowed-tools"] ? fileMetadata["allowed-tools"].split(/\s+/).filter(Boolean) : undefined);
|
|
14069
|
+
return {
|
|
14070
|
+
name,
|
|
14071
|
+
path: entry.from ? resolveFilePath2(entry.from, configDir) : undefined,
|
|
14072
|
+
resolvedPath,
|
|
14073
|
+
definition,
|
|
14074
|
+
scope: "config",
|
|
14075
|
+
license: entry.license || fileMetadata.license,
|
|
14076
|
+
compatibility: entry.compatibility || fileMetadata.compatibility,
|
|
14077
|
+
metadata: entry.metadata || fileMetadata.metadata,
|
|
14078
|
+
allowedTools
|
|
14079
|
+
};
|
|
14080
|
+
}
|
|
14081
|
+
function normalizeConfig(config) {
|
|
14082
|
+
if (!config) {
|
|
14083
|
+
return { sources: [], enable: [], disable: [], entries: {} };
|
|
14084
|
+
}
|
|
14085
|
+
if (Array.isArray(config)) {
|
|
14086
|
+
return { sources: [], enable: config, disable: [], entries: {} };
|
|
14087
|
+
}
|
|
14088
|
+
const { sources = [], enable = [], disable = [], ...entries } = config;
|
|
14089
|
+
return { sources, enable, disable, entries };
|
|
14090
|
+
}
|
|
14091
|
+
function mergeSkillDefinitions(base, patch) {
|
|
14092
|
+
const mergedMetadata = base.metadata || patch.metadata ? deepMerge(base.metadata || {}, patch.metadata || {}) : undefined;
|
|
14093
|
+
const mergedTools = base.allowedTools || patch["allowed-tools"] ? [...base.allowedTools || [], ...patch["allowed-tools"] || []] : undefined;
|
|
14094
|
+
const description = patch.description || base.definition.description?.replace(/^\([^)]+\) /, "");
|
|
14095
|
+
return {
|
|
14096
|
+
...base,
|
|
14097
|
+
definition: {
|
|
14098
|
+
...base.definition,
|
|
14099
|
+
description: `(${base.scope} - Skill) ${description}`,
|
|
14100
|
+
model: patch.model || base.definition.model,
|
|
14101
|
+
agent: patch.agent || base.definition.agent,
|
|
14102
|
+
subtask: patch.subtask ?? base.definition.subtask,
|
|
14103
|
+
argumentHint: patch["argument-hint"] || base.definition.argumentHint
|
|
14104
|
+
},
|
|
14105
|
+
license: patch.license || base.license,
|
|
14106
|
+
compatibility: patch.compatibility || base.compatibility,
|
|
14107
|
+
metadata: mergedMetadata,
|
|
14108
|
+
allowedTools: mergedTools ? [...new Set(mergedTools)] : undefined
|
|
14109
|
+
};
|
|
14110
|
+
}
|
|
14111
|
+
function mergeSkills(builtinSkills, config, userClaudeSkills, userOpencodeSkills, projectClaudeSkills, projectOpencodeSkills, options = {}) {
|
|
14112
|
+
const skillMap = new Map;
|
|
14113
|
+
for (const builtin of builtinSkills) {
|
|
14114
|
+
const loaded = builtinToLoaded(builtin);
|
|
14115
|
+
skillMap.set(loaded.name, loaded);
|
|
14116
|
+
}
|
|
14117
|
+
const normalizedConfig = normalizeConfig(config);
|
|
14118
|
+
for (const [name, entry] of Object.entries(normalizedConfig.entries)) {
|
|
14119
|
+
if (entry === false)
|
|
14120
|
+
continue;
|
|
14121
|
+
if (entry === true)
|
|
14122
|
+
continue;
|
|
14123
|
+
if (entry.disable)
|
|
14124
|
+
continue;
|
|
14125
|
+
const loaded = configEntryToLoaded(name, entry, options.configDir);
|
|
14126
|
+
if (loaded) {
|
|
14127
|
+
const existing = skillMap.get(name);
|
|
14128
|
+
if (existing && !entry.template && !entry.from) {
|
|
14129
|
+
skillMap.set(name, mergeSkillDefinitions(existing, entry));
|
|
14130
|
+
} else {
|
|
14131
|
+
skillMap.set(name, loaded);
|
|
14132
|
+
}
|
|
14133
|
+
}
|
|
14134
|
+
}
|
|
14135
|
+
const fileSystemSkills = [
|
|
14136
|
+
...userClaudeSkills,
|
|
14137
|
+
...userOpencodeSkills,
|
|
14138
|
+
...projectClaudeSkills,
|
|
14139
|
+
...projectOpencodeSkills
|
|
14140
|
+
];
|
|
14141
|
+
for (const skill of fileSystemSkills) {
|
|
14142
|
+
const existing = skillMap.get(skill.name);
|
|
14143
|
+
if (!existing || SCOPE_PRIORITY[skill.scope] > SCOPE_PRIORITY[existing.scope]) {
|
|
14144
|
+
skillMap.set(skill.name, skill);
|
|
14145
|
+
}
|
|
14146
|
+
}
|
|
14147
|
+
for (const [name, entry] of Object.entries(normalizedConfig.entries)) {
|
|
14148
|
+
if (entry === true)
|
|
14149
|
+
continue;
|
|
14150
|
+
if (entry === false) {
|
|
14151
|
+
skillMap.delete(name);
|
|
14152
|
+
continue;
|
|
14153
|
+
}
|
|
14154
|
+
if (entry.disable) {
|
|
14155
|
+
skillMap.delete(name);
|
|
14156
|
+
continue;
|
|
14157
|
+
}
|
|
14158
|
+
const existing = skillMap.get(name);
|
|
14159
|
+
if (existing && !entry.template && !entry.from) {
|
|
14160
|
+
skillMap.set(name, mergeSkillDefinitions(existing, entry));
|
|
14161
|
+
}
|
|
14162
|
+
}
|
|
14163
|
+
for (const name of normalizedConfig.disable) {
|
|
14164
|
+
skillMap.delete(name);
|
|
14165
|
+
}
|
|
14166
|
+
if (normalizedConfig.enable.length > 0) {
|
|
14167
|
+
const enableSet = new Set(normalizedConfig.enable);
|
|
14168
|
+
for (const name of skillMap.keys()) {
|
|
14169
|
+
if (!enableSet.has(name)) {
|
|
14170
|
+
skillMap.delete(name);
|
|
14171
|
+
}
|
|
14172
|
+
}
|
|
14173
|
+
}
|
|
14174
|
+
return Array.from(skillMap.values());
|
|
14175
|
+
}
|
|
14176
|
+
// src/features/builtin-skills/skills.ts
|
|
14177
|
+
function createBuiltinSkills() {
|
|
14178
|
+
return [];
|
|
14179
|
+
}
|
|
13455
14180
|
// src/features/claude-code-agent-loader/loader.ts
|
|
13456
|
-
import { existsSync as
|
|
13457
|
-
import { join as
|
|
14181
|
+
import { existsSync as existsSync35, readdirSync as readdirSync13, readFileSync as readFileSync25 } from "fs";
|
|
14182
|
+
import { join as join43, basename as basename3 } from "path";
|
|
13458
14183
|
function parseToolsConfig(toolsStr) {
|
|
13459
14184
|
if (!toolsStr)
|
|
13460
14185
|
return;
|
|
@@ -13468,18 +14193,18 @@ function parseToolsConfig(toolsStr) {
|
|
|
13468
14193
|
return result;
|
|
13469
14194
|
}
|
|
13470
14195
|
function loadAgentsFromDir(agentsDir, scope) {
|
|
13471
|
-
if (!
|
|
14196
|
+
if (!existsSync35(agentsDir)) {
|
|
13472
14197
|
return [];
|
|
13473
14198
|
}
|
|
13474
|
-
const entries =
|
|
14199
|
+
const entries = readdirSync13(agentsDir, { withFileTypes: true });
|
|
13475
14200
|
const agents = [];
|
|
13476
14201
|
for (const entry of entries) {
|
|
13477
14202
|
if (!isMarkdownFile(entry))
|
|
13478
14203
|
continue;
|
|
13479
|
-
const agentPath =
|
|
13480
|
-
const agentName =
|
|
14204
|
+
const agentPath = join43(agentsDir, entry.name);
|
|
14205
|
+
const agentName = basename3(entry.name, ".md");
|
|
13481
14206
|
try {
|
|
13482
|
-
const content =
|
|
14207
|
+
const content = readFileSync25(agentPath, "utf-8");
|
|
13483
14208
|
const { data, body } = parseFrontmatter(content);
|
|
13484
14209
|
const name = data.name || agentName;
|
|
13485
14210
|
const originalDescription = data.description || "";
|
|
@@ -13506,7 +14231,7 @@ function loadAgentsFromDir(agentsDir, scope) {
|
|
|
13506
14231
|
return agents;
|
|
13507
14232
|
}
|
|
13508
14233
|
function loadUserAgents() {
|
|
13509
|
-
const userAgentsDir =
|
|
14234
|
+
const userAgentsDir = join43(getClaudeConfigDir(), "agents");
|
|
13510
14235
|
const agents = loadAgentsFromDir(userAgentsDir, "user");
|
|
13511
14236
|
const result = {};
|
|
13512
14237
|
for (const agent of agents) {
|
|
@@ -13515,7 +14240,7 @@ function loadUserAgents() {
|
|
|
13515
14240
|
return result;
|
|
13516
14241
|
}
|
|
13517
14242
|
function loadProjectAgents() {
|
|
13518
|
-
const projectAgentsDir =
|
|
14243
|
+
const projectAgentsDir = join43(process.cwd(), ".claude", "agents");
|
|
13519
14244
|
const agents = loadAgentsFromDir(projectAgentsDir, "project");
|
|
13520
14245
|
const result = {};
|
|
13521
14246
|
for (const agent of agents) {
|
|
@@ -13524,8 +14249,8 @@ function loadProjectAgents() {
|
|
|
13524
14249
|
return result;
|
|
13525
14250
|
}
|
|
13526
14251
|
// src/features/claude-code-mcp-loader/loader.ts
|
|
13527
|
-
import { existsSync as
|
|
13528
|
-
import { join as
|
|
14252
|
+
import { existsSync as existsSync36 } from "fs";
|
|
14253
|
+
import { join as join44 } from "path";
|
|
13529
14254
|
|
|
13530
14255
|
// src/features/claude-code-mcp-loader/env-expander.ts
|
|
13531
14256
|
function expandEnvVars(value) {
|
|
@@ -13594,13 +14319,13 @@ function getMcpConfigPaths() {
|
|
|
13594
14319
|
const claudeConfigDir = getClaudeConfigDir();
|
|
13595
14320
|
const cwd = process.cwd();
|
|
13596
14321
|
return [
|
|
13597
|
-
{ path:
|
|
13598
|
-
{ path:
|
|
13599
|
-
{ path:
|
|
14322
|
+
{ path: join44(claudeConfigDir, ".mcp.json"), scope: "user" },
|
|
14323
|
+
{ path: join44(cwd, ".mcp.json"), scope: "project" },
|
|
14324
|
+
{ path: join44(cwd, ".claude", ".mcp.json"), scope: "local" }
|
|
13600
14325
|
];
|
|
13601
14326
|
}
|
|
13602
14327
|
async function loadMcpConfigFile(filePath) {
|
|
13603
|
-
if (!
|
|
14328
|
+
if (!existsSync36(filePath)) {
|
|
13604
14329
|
return null;
|
|
13605
14330
|
}
|
|
13606
14331
|
try {
|
|
@@ -13641,18 +14366,18 @@ async function loadMcpConfigs() {
|
|
|
13641
14366
|
return { servers, loadedServers };
|
|
13642
14367
|
}
|
|
13643
14368
|
// src/features/claude-code-plugin-loader/loader.ts
|
|
13644
|
-
import { existsSync as
|
|
13645
|
-
import { homedir as
|
|
13646
|
-
import { join as
|
|
14369
|
+
import { existsSync as existsSync37, readdirSync as readdirSync14, readFileSync as readFileSync26 } from "fs";
|
|
14370
|
+
import { homedir as homedir11 } from "os";
|
|
14371
|
+
import { join as join45, basename as basename4 } from "path";
|
|
13647
14372
|
var CLAUDE_PLUGIN_ROOT_VAR = "${CLAUDE_PLUGIN_ROOT}";
|
|
13648
14373
|
function getPluginsBaseDir() {
|
|
13649
14374
|
if (process.env.CLAUDE_PLUGINS_HOME) {
|
|
13650
14375
|
return process.env.CLAUDE_PLUGINS_HOME;
|
|
13651
14376
|
}
|
|
13652
|
-
return
|
|
14377
|
+
return join45(homedir11(), ".claude", "plugins");
|
|
13653
14378
|
}
|
|
13654
14379
|
function getInstalledPluginsPath() {
|
|
13655
|
-
return
|
|
14380
|
+
return join45(getPluginsBaseDir(), "installed_plugins.json");
|
|
13656
14381
|
}
|
|
13657
14382
|
function resolvePluginPath(path7, pluginRoot) {
|
|
13658
14383
|
return path7.replace(CLAUDE_PLUGIN_ROOT_VAR, pluginRoot);
|
|
@@ -13677,11 +14402,11 @@ function resolvePluginPaths(obj, pluginRoot) {
|
|
|
13677
14402
|
}
|
|
13678
14403
|
function loadInstalledPlugins() {
|
|
13679
14404
|
const dbPath = getInstalledPluginsPath();
|
|
13680
|
-
if (!
|
|
14405
|
+
if (!existsSync37(dbPath)) {
|
|
13681
14406
|
return null;
|
|
13682
14407
|
}
|
|
13683
14408
|
try {
|
|
13684
|
-
const content =
|
|
14409
|
+
const content = readFileSync26(dbPath, "utf-8");
|
|
13685
14410
|
return JSON.parse(content);
|
|
13686
14411
|
} catch (error) {
|
|
13687
14412
|
log("Failed to load installed plugins database", error);
|
|
@@ -13692,15 +14417,15 @@ function getClaudeSettingsPath() {
|
|
|
13692
14417
|
if (process.env.CLAUDE_SETTINGS_PATH) {
|
|
13693
14418
|
return process.env.CLAUDE_SETTINGS_PATH;
|
|
13694
14419
|
}
|
|
13695
|
-
return
|
|
14420
|
+
return join45(homedir11(), ".claude", "settings.json");
|
|
13696
14421
|
}
|
|
13697
14422
|
function loadClaudeSettings() {
|
|
13698
14423
|
const settingsPath = getClaudeSettingsPath();
|
|
13699
|
-
if (!
|
|
14424
|
+
if (!existsSync37(settingsPath)) {
|
|
13700
14425
|
return null;
|
|
13701
14426
|
}
|
|
13702
14427
|
try {
|
|
13703
|
-
const content =
|
|
14428
|
+
const content = readFileSync26(settingsPath, "utf-8");
|
|
13704
14429
|
return JSON.parse(content);
|
|
13705
14430
|
} catch (error) {
|
|
13706
14431
|
log("Failed to load Claude settings", error);
|
|
@@ -13708,12 +14433,12 @@ function loadClaudeSettings() {
|
|
|
13708
14433
|
}
|
|
13709
14434
|
}
|
|
13710
14435
|
function loadPluginManifest(installPath) {
|
|
13711
|
-
const manifestPath =
|
|
13712
|
-
if (!
|
|
14436
|
+
const manifestPath = join45(installPath, ".claude-plugin", "plugin.json");
|
|
14437
|
+
if (!existsSync37(manifestPath)) {
|
|
13713
14438
|
return null;
|
|
13714
14439
|
}
|
|
13715
14440
|
try {
|
|
13716
|
-
const content =
|
|
14441
|
+
const content = readFileSync26(manifestPath, "utf-8");
|
|
13717
14442
|
return JSON.parse(content);
|
|
13718
14443
|
} catch (error) {
|
|
13719
14444
|
log(`Failed to load plugin manifest from ${manifestPath}`, error);
|
|
@@ -13760,7 +14485,7 @@ function discoverInstalledPlugins(options) {
|
|
|
13760
14485
|
continue;
|
|
13761
14486
|
}
|
|
13762
14487
|
const { installPath, scope, version } = installation;
|
|
13763
|
-
if (!
|
|
14488
|
+
if (!existsSync37(installPath)) {
|
|
13764
14489
|
errors.push({
|
|
13765
14490
|
pluginKey,
|
|
13766
14491
|
installPath,
|
|
@@ -13778,21 +14503,21 @@ function discoverInstalledPlugins(options) {
|
|
|
13778
14503
|
pluginKey,
|
|
13779
14504
|
manifest: manifest ?? undefined
|
|
13780
14505
|
};
|
|
13781
|
-
if (
|
|
13782
|
-
loadedPlugin.commandsDir =
|
|
14506
|
+
if (existsSync37(join45(installPath, "commands"))) {
|
|
14507
|
+
loadedPlugin.commandsDir = join45(installPath, "commands");
|
|
13783
14508
|
}
|
|
13784
|
-
if (
|
|
13785
|
-
loadedPlugin.agentsDir =
|
|
14509
|
+
if (existsSync37(join45(installPath, "agents"))) {
|
|
14510
|
+
loadedPlugin.agentsDir = join45(installPath, "agents");
|
|
13786
14511
|
}
|
|
13787
|
-
if (
|
|
13788
|
-
loadedPlugin.skillsDir =
|
|
14512
|
+
if (existsSync37(join45(installPath, "skills"))) {
|
|
14513
|
+
loadedPlugin.skillsDir = join45(installPath, "skills");
|
|
13789
14514
|
}
|
|
13790
|
-
const hooksPath =
|
|
13791
|
-
if (
|
|
14515
|
+
const hooksPath = join45(installPath, "hooks", "hooks.json");
|
|
14516
|
+
if (existsSync37(hooksPath)) {
|
|
13792
14517
|
loadedPlugin.hooksPath = hooksPath;
|
|
13793
14518
|
}
|
|
13794
|
-
const mcpPath =
|
|
13795
|
-
if (
|
|
14519
|
+
const mcpPath = join45(installPath, ".mcp.json");
|
|
14520
|
+
if (existsSync37(mcpPath)) {
|
|
13796
14521
|
loadedPlugin.mcpPath = mcpPath;
|
|
13797
14522
|
}
|
|
13798
14523
|
plugins.push(loadedPlugin);
|
|
@@ -13803,17 +14528,17 @@ function discoverInstalledPlugins(options) {
|
|
|
13803
14528
|
function loadPluginCommands(plugins) {
|
|
13804
14529
|
const commands2 = {};
|
|
13805
14530
|
for (const plugin2 of plugins) {
|
|
13806
|
-
if (!plugin2.commandsDir || !
|
|
14531
|
+
if (!plugin2.commandsDir || !existsSync37(plugin2.commandsDir))
|
|
13807
14532
|
continue;
|
|
13808
|
-
const entries =
|
|
14533
|
+
const entries = readdirSync14(plugin2.commandsDir, { withFileTypes: true });
|
|
13809
14534
|
for (const entry of entries) {
|
|
13810
14535
|
if (!isMarkdownFile(entry))
|
|
13811
14536
|
continue;
|
|
13812
|
-
const commandPath =
|
|
13813
|
-
const commandName =
|
|
14537
|
+
const commandPath = join45(plugin2.commandsDir, entry.name);
|
|
14538
|
+
const commandName = basename4(entry.name, ".md");
|
|
13814
14539
|
const namespacedName = `${plugin2.name}:${commandName}`;
|
|
13815
14540
|
try {
|
|
13816
|
-
const content =
|
|
14541
|
+
const content = readFileSync26(commandPath, "utf-8");
|
|
13817
14542
|
const { data, body } = parseFrontmatter(content);
|
|
13818
14543
|
const wrappedTemplate = `<command-instruction>
|
|
13819
14544
|
${body.trim()}
|
|
@@ -13843,21 +14568,21 @@ $ARGUMENTS
|
|
|
13843
14568
|
function loadPluginSkillsAsCommands(plugins) {
|
|
13844
14569
|
const skills = {};
|
|
13845
14570
|
for (const plugin2 of plugins) {
|
|
13846
|
-
if (!plugin2.skillsDir || !
|
|
14571
|
+
if (!plugin2.skillsDir || !existsSync37(plugin2.skillsDir))
|
|
13847
14572
|
continue;
|
|
13848
|
-
const entries =
|
|
14573
|
+
const entries = readdirSync14(plugin2.skillsDir, { withFileTypes: true });
|
|
13849
14574
|
for (const entry of entries) {
|
|
13850
14575
|
if (entry.name.startsWith("."))
|
|
13851
14576
|
continue;
|
|
13852
|
-
const skillPath =
|
|
14577
|
+
const skillPath = join45(plugin2.skillsDir, entry.name);
|
|
13853
14578
|
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
13854
14579
|
continue;
|
|
13855
14580
|
const resolvedPath = resolveSymlink(skillPath);
|
|
13856
|
-
const skillMdPath =
|
|
13857
|
-
if (!
|
|
14581
|
+
const skillMdPath = join45(resolvedPath, "SKILL.md");
|
|
14582
|
+
if (!existsSync37(skillMdPath))
|
|
13858
14583
|
continue;
|
|
13859
14584
|
try {
|
|
13860
|
-
const content =
|
|
14585
|
+
const content = readFileSync26(skillMdPath, "utf-8");
|
|
13861
14586
|
const { data, body } = parseFrontmatter(content);
|
|
13862
14587
|
const skillName = data.name || entry.name;
|
|
13863
14588
|
const namespacedName = `${plugin2.name}:${skillName}`;
|
|
@@ -13902,17 +14627,17 @@ function parseToolsConfig2(toolsStr) {
|
|
|
13902
14627
|
function loadPluginAgents(plugins) {
|
|
13903
14628
|
const agents = {};
|
|
13904
14629
|
for (const plugin2 of plugins) {
|
|
13905
|
-
if (!plugin2.agentsDir || !
|
|
14630
|
+
if (!plugin2.agentsDir || !existsSync37(plugin2.agentsDir))
|
|
13906
14631
|
continue;
|
|
13907
|
-
const entries =
|
|
14632
|
+
const entries = readdirSync14(plugin2.agentsDir, { withFileTypes: true });
|
|
13908
14633
|
for (const entry of entries) {
|
|
13909
14634
|
if (!isMarkdownFile(entry))
|
|
13910
14635
|
continue;
|
|
13911
|
-
const agentPath =
|
|
13912
|
-
const agentName =
|
|
14636
|
+
const agentPath = join45(plugin2.agentsDir, entry.name);
|
|
14637
|
+
const agentName = basename4(entry.name, ".md");
|
|
13913
14638
|
const namespacedName = `${plugin2.name}:${agentName}`;
|
|
13914
14639
|
try {
|
|
13915
|
-
const content =
|
|
14640
|
+
const content = readFileSync26(agentPath, "utf-8");
|
|
13916
14641
|
const { data, body } = parseFrontmatter(content);
|
|
13917
14642
|
const name = data.name || agentName;
|
|
13918
14643
|
const originalDescription = data.description || "";
|
|
@@ -13938,7 +14663,7 @@ function loadPluginAgents(plugins) {
|
|
|
13938
14663
|
async function loadPluginMcpServers(plugins) {
|
|
13939
14664
|
const servers = {};
|
|
13940
14665
|
for (const plugin2 of plugins) {
|
|
13941
|
-
if (!plugin2.mcpPath || !
|
|
14666
|
+
if (!plugin2.mcpPath || !existsSync37(plugin2.mcpPath))
|
|
13942
14667
|
continue;
|
|
13943
14668
|
try {
|
|
13944
14669
|
const content = await Bun.file(plugin2.mcpPath).text();
|
|
@@ -13970,10 +14695,10 @@ async function loadPluginMcpServers(plugins) {
|
|
|
13970
14695
|
function loadPluginHooksConfigs(plugins) {
|
|
13971
14696
|
const configs = [];
|
|
13972
14697
|
for (const plugin2 of plugins) {
|
|
13973
|
-
if (!plugin2.hooksPath || !
|
|
14698
|
+
if (!plugin2.hooksPath || !existsSync37(plugin2.hooksPath))
|
|
13974
14699
|
continue;
|
|
13975
14700
|
try {
|
|
13976
|
-
const content =
|
|
14701
|
+
const content = readFileSync26(plugin2.hooksPath, "utf-8");
|
|
13977
14702
|
let config = JSON.parse(content);
|
|
13978
14703
|
config = resolvePluginPaths(config, plugin2.installPath);
|
|
13979
14704
|
configs.push(config);
|
|
@@ -14327,14 +15052,14 @@ var EXT_TO_LANG = {
|
|
|
14327
15052
|
".gql": "graphql"
|
|
14328
15053
|
};
|
|
14329
15054
|
// src/tools/lsp/config.ts
|
|
14330
|
-
import { existsSync as
|
|
14331
|
-
import { join as
|
|
14332
|
-
import { homedir as
|
|
15055
|
+
import { existsSync as existsSync38, readFileSync as readFileSync27 } from "fs";
|
|
15056
|
+
import { join as join46 } from "path";
|
|
15057
|
+
import { homedir as homedir12 } from "os";
|
|
14333
15058
|
function loadJsonFile(path7) {
|
|
14334
|
-
if (!
|
|
15059
|
+
if (!existsSync38(path7))
|
|
14335
15060
|
return null;
|
|
14336
15061
|
try {
|
|
14337
|
-
return JSON.parse(
|
|
15062
|
+
return JSON.parse(readFileSync27(path7, "utf-8"));
|
|
14338
15063
|
} catch {
|
|
14339
15064
|
return null;
|
|
14340
15065
|
}
|
|
@@ -14342,9 +15067,9 @@ function loadJsonFile(path7) {
|
|
|
14342
15067
|
function getConfigPaths2() {
|
|
14343
15068
|
const cwd = process.cwd();
|
|
14344
15069
|
return {
|
|
14345
|
-
project:
|
|
14346
|
-
user:
|
|
14347
|
-
opencode:
|
|
15070
|
+
project: join46(cwd, ".opencode", "oh-my-opencode.json"),
|
|
15071
|
+
user: join46(homedir12(), ".config", "opencode", "oh-my-opencode.json"),
|
|
15072
|
+
opencode: join46(homedir12(), ".config", "opencode", "opencode.json")
|
|
14348
15073
|
};
|
|
14349
15074
|
}
|
|
14350
15075
|
function loadAllConfigs() {
|
|
@@ -14457,7 +15182,7 @@ function isServerInstalled(command) {
|
|
|
14457
15182
|
return false;
|
|
14458
15183
|
const cmd = command[0];
|
|
14459
15184
|
if (cmd.includes("/") || cmd.includes("\\")) {
|
|
14460
|
-
if (
|
|
15185
|
+
if (existsSync38(cmd))
|
|
14461
15186
|
return true;
|
|
14462
15187
|
}
|
|
14463
15188
|
const isWindows2 = process.platform === "win32";
|
|
@@ -14466,21 +15191,21 @@ function isServerInstalled(command) {
|
|
|
14466
15191
|
const pathSeparator = isWindows2 ? ";" : ":";
|
|
14467
15192
|
const paths = pathEnv.split(pathSeparator);
|
|
14468
15193
|
for (const p of paths) {
|
|
14469
|
-
if (
|
|
15194
|
+
if (existsSync38(join46(p, cmd)) || existsSync38(join46(p, cmd + ext))) {
|
|
14470
15195
|
return true;
|
|
14471
15196
|
}
|
|
14472
15197
|
}
|
|
14473
15198
|
const cwd = process.cwd();
|
|
14474
15199
|
const additionalPaths = [
|
|
14475
|
-
|
|
14476
|
-
|
|
14477
|
-
|
|
14478
|
-
|
|
14479
|
-
|
|
14480
|
-
|
|
15200
|
+
join46(cwd, "node_modules", ".bin", cmd),
|
|
15201
|
+
join46(cwd, "node_modules", ".bin", cmd + ext),
|
|
15202
|
+
join46(homedir12(), ".config", "opencode", "bin", cmd),
|
|
15203
|
+
join46(homedir12(), ".config", "opencode", "bin", cmd + ext),
|
|
15204
|
+
join46(homedir12(), ".config", "opencode", "node_modules", ".bin", cmd),
|
|
15205
|
+
join46(homedir12(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
|
|
14481
15206
|
];
|
|
14482
15207
|
for (const p of additionalPaths) {
|
|
14483
|
-
if (
|
|
15208
|
+
if (existsSync38(p)) {
|
|
14484
15209
|
return true;
|
|
14485
15210
|
}
|
|
14486
15211
|
}
|
|
@@ -14533,8 +15258,8 @@ function getAllServers() {
|
|
|
14533
15258
|
}
|
|
14534
15259
|
// src/tools/lsp/client.ts
|
|
14535
15260
|
var {spawn: spawn5 } = globalThis.Bun;
|
|
14536
|
-
import { readFileSync as
|
|
14537
|
-
import { extname, resolve as
|
|
15261
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
15262
|
+
import { extname, resolve as resolve6 } from "path";
|
|
14538
15263
|
class LSPServerManager {
|
|
14539
15264
|
static instance;
|
|
14540
15265
|
clients = new Map;
|
|
@@ -14714,7 +15439,7 @@ class LSPClient {
|
|
|
14714
15439
|
}
|
|
14715
15440
|
this.startReading();
|
|
14716
15441
|
this.startStderrReading();
|
|
14717
|
-
await new Promise((
|
|
15442
|
+
await new Promise((resolve7) => setTimeout(resolve7, 100));
|
|
14718
15443
|
if (this.proc.exitCode !== null) {
|
|
14719
15444
|
const stderr = this.stderrBuffer.join(`
|
|
14720
15445
|
`);
|
|
@@ -14851,8 +15576,8 @@ stderr: ${stderr}` : ""));
|
|
|
14851
15576
|
\r
|
|
14852
15577
|
`;
|
|
14853
15578
|
this.proc.stdin.write(header + msg);
|
|
14854
|
-
return new Promise((
|
|
14855
|
-
this.pending.set(id, { resolve:
|
|
15579
|
+
return new Promise((resolve7, reject) => {
|
|
15580
|
+
this.pending.set(id, { resolve: resolve7, reject });
|
|
14856
15581
|
setTimeout(() => {
|
|
14857
15582
|
if (this.pending.has(id)) {
|
|
14858
15583
|
this.pending.delete(id);
|
|
@@ -14960,10 +15685,10 @@ ${msg}`);
|
|
|
14960
15685
|
await new Promise((r) => setTimeout(r, 300));
|
|
14961
15686
|
}
|
|
14962
15687
|
async openFile(filePath) {
|
|
14963
|
-
const absPath =
|
|
15688
|
+
const absPath = resolve6(filePath);
|
|
14964
15689
|
if (this.openedFiles.has(absPath))
|
|
14965
15690
|
return;
|
|
14966
|
-
const text =
|
|
15691
|
+
const text = readFileSync28(absPath, "utf-8");
|
|
14967
15692
|
const ext = extname(absPath);
|
|
14968
15693
|
const languageId = getLanguageId(ext);
|
|
14969
15694
|
this.notify("textDocument/didOpen", {
|
|
@@ -14978,7 +15703,7 @@ ${msg}`);
|
|
|
14978
15703
|
await new Promise((r) => setTimeout(r, 1000));
|
|
14979
15704
|
}
|
|
14980
15705
|
async hover(filePath, line, character) {
|
|
14981
|
-
const absPath =
|
|
15706
|
+
const absPath = resolve6(filePath);
|
|
14982
15707
|
await this.openFile(absPath);
|
|
14983
15708
|
return this.send("textDocument/hover", {
|
|
14984
15709
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -14986,7 +15711,7 @@ ${msg}`);
|
|
|
14986
15711
|
});
|
|
14987
15712
|
}
|
|
14988
15713
|
async definition(filePath, line, character) {
|
|
14989
|
-
const absPath =
|
|
15714
|
+
const absPath = resolve6(filePath);
|
|
14990
15715
|
await this.openFile(absPath);
|
|
14991
15716
|
return this.send("textDocument/definition", {
|
|
14992
15717
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -14994,7 +15719,7 @@ ${msg}`);
|
|
|
14994
15719
|
});
|
|
14995
15720
|
}
|
|
14996
15721
|
async references(filePath, line, character, includeDeclaration = true) {
|
|
14997
|
-
const absPath =
|
|
15722
|
+
const absPath = resolve6(filePath);
|
|
14998
15723
|
await this.openFile(absPath);
|
|
14999
15724
|
return this.send("textDocument/references", {
|
|
15000
15725
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -15003,7 +15728,7 @@ ${msg}`);
|
|
|
15003
15728
|
});
|
|
15004
15729
|
}
|
|
15005
15730
|
async documentSymbols(filePath) {
|
|
15006
|
-
const absPath =
|
|
15731
|
+
const absPath = resolve6(filePath);
|
|
15007
15732
|
await this.openFile(absPath);
|
|
15008
15733
|
return this.send("textDocument/documentSymbol", {
|
|
15009
15734
|
textDocument: { uri: `file://${absPath}` }
|
|
@@ -15013,7 +15738,7 @@ ${msg}`);
|
|
|
15013
15738
|
return this.send("workspace/symbol", { query });
|
|
15014
15739
|
}
|
|
15015
15740
|
async diagnostics(filePath) {
|
|
15016
|
-
const absPath =
|
|
15741
|
+
const absPath = resolve6(filePath);
|
|
15017
15742
|
const uri = `file://${absPath}`;
|
|
15018
15743
|
await this.openFile(absPath);
|
|
15019
15744
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -15028,7 +15753,7 @@ ${msg}`);
|
|
|
15028
15753
|
return { items: this.diagnosticsStore.get(uri) ?? [] };
|
|
15029
15754
|
}
|
|
15030
15755
|
async prepareRename(filePath, line, character) {
|
|
15031
|
-
const absPath =
|
|
15756
|
+
const absPath = resolve6(filePath);
|
|
15032
15757
|
await this.openFile(absPath);
|
|
15033
15758
|
return this.send("textDocument/prepareRename", {
|
|
15034
15759
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -15036,7 +15761,7 @@ ${msg}`);
|
|
|
15036
15761
|
});
|
|
15037
15762
|
}
|
|
15038
15763
|
async rename(filePath, line, character, newName) {
|
|
15039
|
-
const absPath =
|
|
15764
|
+
const absPath = resolve6(filePath);
|
|
15040
15765
|
await this.openFile(absPath);
|
|
15041
15766
|
return this.send("textDocument/rename", {
|
|
15042
15767
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -15045,7 +15770,7 @@ ${msg}`);
|
|
|
15045
15770
|
});
|
|
15046
15771
|
}
|
|
15047
15772
|
async codeAction(filePath, startLine, startChar, endLine, endChar, only) {
|
|
15048
|
-
const absPath =
|
|
15773
|
+
const absPath = resolve6(filePath);
|
|
15049
15774
|
await this.openFile(absPath);
|
|
15050
15775
|
return this.send("textDocument/codeAction", {
|
|
15051
15776
|
textDocument: { uri: `file://${absPath}` },
|
|
@@ -15077,23 +15802,27 @@ ${msg}`);
|
|
|
15077
15802
|
}
|
|
15078
15803
|
}
|
|
15079
15804
|
// src/tools/lsp/utils.ts
|
|
15080
|
-
import { extname as extname2, resolve as
|
|
15081
|
-
import {
|
|
15805
|
+
import { extname as extname2, resolve as resolve7 } from "path";
|
|
15806
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15807
|
+
import { existsSync as existsSync39, readFileSync as readFileSync29, writeFileSync as writeFileSync14 } from "fs";
|
|
15082
15808
|
function findWorkspaceRoot(filePath) {
|
|
15083
|
-
let dir =
|
|
15084
|
-
if (!
|
|
15809
|
+
let dir = resolve7(filePath);
|
|
15810
|
+
if (!existsSync39(dir) || !__require("fs").statSync(dir).isDirectory()) {
|
|
15085
15811
|
dir = __require("path").dirname(dir);
|
|
15086
15812
|
}
|
|
15087
15813
|
const markers = [".git", "package.json", "pyproject.toml", "Cargo.toml", "go.mod", "pom.xml", "build.gradle"];
|
|
15088
15814
|
while (dir !== "/") {
|
|
15089
15815
|
for (const marker of markers) {
|
|
15090
|
-
if (
|
|
15816
|
+
if (existsSync39(__require("path").join(dir, marker))) {
|
|
15091
15817
|
return dir;
|
|
15092
15818
|
}
|
|
15093
15819
|
}
|
|
15094
15820
|
dir = __require("path").dirname(dir);
|
|
15095
15821
|
}
|
|
15096
|
-
return __require("path").dirname(
|
|
15822
|
+
return __require("path").dirname(resolve7(filePath));
|
|
15823
|
+
}
|
|
15824
|
+
function uriToPath(uri) {
|
|
15825
|
+
return fileURLToPath2(uri);
|
|
15097
15826
|
}
|
|
15098
15827
|
function formatServerLookupError(result) {
|
|
15099
15828
|
if (result.status === "not_installed") {
|
|
@@ -15125,13 +15854,12 @@ function formatServerLookupError(result) {
|
|
|
15125
15854
|
` "command": ["my-lsp", "--stdio"],`,
|
|
15126
15855
|
` "extensions": ["${result.extension}"]`,
|
|
15127
15856
|
` }`,
|
|
15128
|
-
` }
|
|
15129
|
-
` }`
|
|
15857
|
+
` }`
|
|
15130
15858
|
].join(`
|
|
15131
15859
|
`);
|
|
15132
15860
|
}
|
|
15133
15861
|
async function withLspClient(filePath, fn) {
|
|
15134
|
-
const absPath =
|
|
15862
|
+
const absPath = resolve7(filePath);
|
|
15135
15863
|
const ext = extname2(absPath);
|
|
15136
15864
|
const result = findServerForExtension(ext);
|
|
15137
15865
|
if (result.status !== "found") {
|
|
@@ -15173,12 +15901,12 @@ function formatHoverResult(result) {
|
|
|
15173
15901
|
}
|
|
15174
15902
|
function formatLocation(loc) {
|
|
15175
15903
|
if ("targetUri" in loc) {
|
|
15176
|
-
const uri2 = loc.targetUri
|
|
15904
|
+
const uri2 = uriToPath(loc.targetUri);
|
|
15177
15905
|
const line2 = loc.targetRange.start.line + 1;
|
|
15178
15906
|
const char2 = loc.targetRange.start.character;
|
|
15179
15907
|
return `${uri2}:${line2}:${char2}`;
|
|
15180
15908
|
}
|
|
15181
|
-
const uri = loc.uri
|
|
15909
|
+
const uri = uriToPath(loc.uri);
|
|
15182
15910
|
const line = loc.range.start.line + 1;
|
|
15183
15911
|
const char = loc.range.start.character;
|
|
15184
15912
|
return `${uri}:${line}:${char}`;
|
|
@@ -15281,7 +16009,7 @@ function formatCodeActions(actions) {
|
|
|
15281
16009
|
}
|
|
15282
16010
|
function applyTextEditsToFile(filePath, edits) {
|
|
15283
16011
|
try {
|
|
15284
|
-
let content =
|
|
16012
|
+
let content = readFileSync29(filePath, "utf-8");
|
|
15285
16013
|
const lines = content.split(`
|
|
15286
16014
|
`);
|
|
15287
16015
|
const sortedEdits = [...edits].sort((a, b) => {
|
|
@@ -15306,7 +16034,7 @@ function applyTextEditsToFile(filePath, edits) {
|
|
|
15306
16034
|
`));
|
|
15307
16035
|
}
|
|
15308
16036
|
}
|
|
15309
|
-
|
|
16037
|
+
writeFileSync14(filePath, lines.join(`
|
|
15310
16038
|
`), "utf-8");
|
|
15311
16039
|
return { success: true, editCount: edits.length };
|
|
15312
16040
|
} catch (err) {
|
|
@@ -15320,7 +16048,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
15320
16048
|
const result = { success: true, filesModified: [], totalEdits: 0, errors: [] };
|
|
15321
16049
|
if (edit.changes) {
|
|
15322
16050
|
for (const [uri, edits] of Object.entries(edit.changes)) {
|
|
15323
|
-
const filePath = uri
|
|
16051
|
+
const filePath = uriToPath(uri);
|
|
15324
16052
|
const applyResult = applyTextEditsToFile(filePath, edits);
|
|
15325
16053
|
if (applyResult.success) {
|
|
15326
16054
|
result.filesModified.push(filePath);
|
|
@@ -15336,8 +16064,8 @@ function applyWorkspaceEdit(edit) {
|
|
|
15336
16064
|
if ("kind" in change) {
|
|
15337
16065
|
if (change.kind === "create") {
|
|
15338
16066
|
try {
|
|
15339
|
-
const filePath = change.uri
|
|
15340
|
-
|
|
16067
|
+
const filePath = uriToPath(change.uri);
|
|
16068
|
+
writeFileSync14(filePath, "", "utf-8");
|
|
15341
16069
|
result.filesModified.push(filePath);
|
|
15342
16070
|
} catch (err) {
|
|
15343
16071
|
result.success = false;
|
|
@@ -15345,10 +16073,10 @@ function applyWorkspaceEdit(edit) {
|
|
|
15345
16073
|
}
|
|
15346
16074
|
} else if (change.kind === "rename") {
|
|
15347
16075
|
try {
|
|
15348
|
-
const oldPath = change.oldUri
|
|
15349
|
-
const newPath = change.newUri
|
|
15350
|
-
const content =
|
|
15351
|
-
|
|
16076
|
+
const oldPath = uriToPath(change.oldUri);
|
|
16077
|
+
const newPath = uriToPath(change.newUri);
|
|
16078
|
+
const content = readFileSync29(oldPath, "utf-8");
|
|
16079
|
+
writeFileSync14(newPath, content, "utf-8");
|
|
15352
16080
|
__require("fs").unlinkSync(oldPath);
|
|
15353
16081
|
result.filesModified.push(newPath);
|
|
15354
16082
|
} catch (err) {
|
|
@@ -15357,7 +16085,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
15357
16085
|
}
|
|
15358
16086
|
} else if (change.kind === "delete") {
|
|
15359
16087
|
try {
|
|
15360
|
-
const filePath = change.uri
|
|
16088
|
+
const filePath = uriToPath(change.uri);
|
|
15361
16089
|
__require("fs").unlinkSync(filePath);
|
|
15362
16090
|
result.filesModified.push(filePath);
|
|
15363
16091
|
} catch (err) {
|
|
@@ -15366,7 +16094,7 @@ function applyWorkspaceEdit(edit) {
|
|
|
15366
16094
|
}
|
|
15367
16095
|
}
|
|
15368
16096
|
} else {
|
|
15369
|
-
const filePath = change.textDocument.uri
|
|
16097
|
+
const filePath = uriToPath(change.textDocument.uri);
|
|
15370
16098
|
const applyResult = applyTextEditsToFile(filePath, change.edits);
|
|
15371
16099
|
if (applyResult.success) {
|
|
15372
16100
|
result.filesModified.push(filePath);
|
|
@@ -25483,10 +26211,10 @@ function _property(property, schema, params) {
|
|
|
25483
26211
|
...normalizeParams(params)
|
|
25484
26212
|
});
|
|
25485
26213
|
}
|
|
25486
|
-
function _mime(
|
|
26214
|
+
function _mime(types14, params) {
|
|
25487
26215
|
return new $ZodCheckMimeType({
|
|
25488
26216
|
check: "mime_type",
|
|
25489
|
-
mime:
|
|
26217
|
+
mime: types14,
|
|
25490
26218
|
...normalizeParams(params)
|
|
25491
26219
|
});
|
|
25492
26220
|
}
|
|
@@ -27396,7 +28124,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
|
|
|
27396
28124
|
ZodType.init(inst, def);
|
|
27397
28125
|
inst.min = (size, params) => inst.check(_minSize(size, params));
|
|
27398
28126
|
inst.max = (size, params) => inst.check(_maxSize(size, params));
|
|
27399
|
-
inst.mime = (
|
|
28127
|
+
inst.mime = (types14, params) => inst.check(_mime(Array.isArray(types14) ? types14 : [types14], params));
|
|
27400
28128
|
});
|
|
27401
28129
|
function file(params) {
|
|
27402
28130
|
return _file(ZodFile, params);
|
|
@@ -28048,14 +28776,14 @@ var lsp_code_action_resolve = tool({
|
|
|
28048
28776
|
});
|
|
28049
28777
|
// src/tools/ast-grep/constants.ts
|
|
28050
28778
|
import { createRequire as createRequire4 } from "module";
|
|
28051
|
-
import { dirname as
|
|
28052
|
-
import { existsSync as
|
|
28779
|
+
import { dirname as dirname9, join as join48 } from "path";
|
|
28780
|
+
import { existsSync as existsSync41, statSync as statSync4 } from "fs";
|
|
28053
28781
|
|
|
28054
28782
|
// src/tools/ast-grep/downloader.ts
|
|
28055
28783
|
var {spawn: spawn6 } = globalThis.Bun;
|
|
28056
|
-
import { existsSync as
|
|
28057
|
-
import { join as
|
|
28058
|
-
import { homedir as
|
|
28784
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync11, chmodSync as chmodSync2, unlinkSync as unlinkSync10 } from "fs";
|
|
28785
|
+
import { join as join47 } from "path";
|
|
28786
|
+
import { homedir as homedir13 } from "os";
|
|
28059
28787
|
import { createRequire as createRequire3 } from "module";
|
|
28060
28788
|
var REPO2 = "ast-grep/ast-grep";
|
|
28061
28789
|
var DEFAULT_VERSION = "0.40.0";
|
|
@@ -28080,19 +28808,19 @@ var PLATFORM_MAP2 = {
|
|
|
28080
28808
|
function getCacheDir3() {
|
|
28081
28809
|
if (process.platform === "win32") {
|
|
28082
28810
|
const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
|
|
28083
|
-
const base2 = localAppData ||
|
|
28084
|
-
return
|
|
28811
|
+
const base2 = localAppData || join47(homedir13(), "AppData", "Local");
|
|
28812
|
+
return join47(base2, "oh-my-opencode", "bin");
|
|
28085
28813
|
}
|
|
28086
28814
|
const xdgCache = process.env.XDG_CACHE_HOME;
|
|
28087
|
-
const base = xdgCache ||
|
|
28088
|
-
return
|
|
28815
|
+
const base = xdgCache || join47(homedir13(), ".cache");
|
|
28816
|
+
return join47(base, "oh-my-opencode", "bin");
|
|
28089
28817
|
}
|
|
28090
28818
|
function getBinaryName3() {
|
|
28091
28819
|
return process.platform === "win32" ? "sg.exe" : "sg";
|
|
28092
28820
|
}
|
|
28093
28821
|
function getCachedBinaryPath2() {
|
|
28094
|
-
const binaryPath =
|
|
28095
|
-
return
|
|
28822
|
+
const binaryPath = join47(getCacheDir3(), getBinaryName3());
|
|
28823
|
+
return existsSync40(binaryPath) ? binaryPath : null;
|
|
28096
28824
|
}
|
|
28097
28825
|
async function extractZip2(archivePath, destDir) {
|
|
28098
28826
|
const proc = process.platform === "win32" ? spawn6([
|
|
@@ -28118,8 +28846,8 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
28118
28846
|
}
|
|
28119
28847
|
const cacheDir = getCacheDir3();
|
|
28120
28848
|
const binaryName = getBinaryName3();
|
|
28121
|
-
const binaryPath =
|
|
28122
|
-
if (
|
|
28849
|
+
const binaryPath = join47(cacheDir, binaryName);
|
|
28850
|
+
if (existsSync40(binaryPath)) {
|
|
28123
28851
|
return binaryPath;
|
|
28124
28852
|
}
|
|
28125
28853
|
const { arch, os: os5 } = platformInfo;
|
|
@@ -28127,21 +28855,21 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
|
|
|
28127
28855
|
const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
|
|
28128
28856
|
console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
|
|
28129
28857
|
try {
|
|
28130
|
-
if (!
|
|
28131
|
-
|
|
28858
|
+
if (!existsSync40(cacheDir)) {
|
|
28859
|
+
mkdirSync11(cacheDir, { recursive: true });
|
|
28132
28860
|
}
|
|
28133
28861
|
const response2 = await fetch(downloadUrl, { redirect: "follow" });
|
|
28134
28862
|
if (!response2.ok) {
|
|
28135
28863
|
throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
|
|
28136
28864
|
}
|
|
28137
|
-
const archivePath =
|
|
28865
|
+
const archivePath = join47(cacheDir, assetName);
|
|
28138
28866
|
const arrayBuffer = await response2.arrayBuffer();
|
|
28139
28867
|
await Bun.write(archivePath, arrayBuffer);
|
|
28140
28868
|
await extractZip2(archivePath, cacheDir);
|
|
28141
|
-
if (
|
|
28142
|
-
|
|
28869
|
+
if (existsSync40(archivePath)) {
|
|
28870
|
+
unlinkSync10(archivePath);
|
|
28143
28871
|
}
|
|
28144
|
-
if (process.platform !== "win32" &&
|
|
28872
|
+
if (process.platform !== "win32" && existsSync40(binaryPath)) {
|
|
28145
28873
|
chmodSync2(binaryPath, 493);
|
|
28146
28874
|
}
|
|
28147
28875
|
console.log(`[oh-my-opencode] ast-grep binary ready.`);
|
|
@@ -28191,9 +28919,9 @@ function findSgCliPathSync() {
|
|
|
28191
28919
|
try {
|
|
28192
28920
|
const require2 = createRequire4(import.meta.url);
|
|
28193
28921
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
28194
|
-
const cliDir =
|
|
28195
|
-
const sgPath =
|
|
28196
|
-
if (
|
|
28922
|
+
const cliDir = dirname9(cliPkgPath);
|
|
28923
|
+
const sgPath = join48(cliDir, binaryName);
|
|
28924
|
+
if (existsSync41(sgPath) && isValidBinary(sgPath)) {
|
|
28197
28925
|
return sgPath;
|
|
28198
28926
|
}
|
|
28199
28927
|
} catch {}
|
|
@@ -28202,10 +28930,10 @@ function findSgCliPathSync() {
|
|
|
28202
28930
|
try {
|
|
28203
28931
|
const require2 = createRequire4(import.meta.url);
|
|
28204
28932
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
28205
|
-
const pkgDir =
|
|
28933
|
+
const pkgDir = dirname9(pkgPath);
|
|
28206
28934
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
28207
|
-
const binaryPath =
|
|
28208
|
-
if (
|
|
28935
|
+
const binaryPath = join48(pkgDir, astGrepName);
|
|
28936
|
+
if (existsSync41(binaryPath) && isValidBinary(binaryPath)) {
|
|
28209
28937
|
return binaryPath;
|
|
28210
28938
|
}
|
|
28211
28939
|
} catch {}
|
|
@@ -28213,7 +28941,7 @@ function findSgCliPathSync() {
|
|
|
28213
28941
|
if (process.platform === "darwin") {
|
|
28214
28942
|
const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
|
|
28215
28943
|
for (const path7 of homebrewPaths) {
|
|
28216
|
-
if (
|
|
28944
|
+
if (existsSync41(path7) && isValidBinary(path7)) {
|
|
28217
28945
|
return path7;
|
|
28218
28946
|
}
|
|
28219
28947
|
}
|
|
@@ -28268,11 +28996,11 @@ var DEFAULT_MAX_MATCHES = 500;
|
|
|
28268
28996
|
|
|
28269
28997
|
// src/tools/ast-grep/cli.ts
|
|
28270
28998
|
var {spawn: spawn7 } = globalThis.Bun;
|
|
28271
|
-
import { existsSync as
|
|
28999
|
+
import { existsSync as existsSync42 } from "fs";
|
|
28272
29000
|
var resolvedCliPath3 = null;
|
|
28273
29001
|
var initPromise2 = null;
|
|
28274
29002
|
async function getAstGrepPath() {
|
|
28275
|
-
if (resolvedCliPath3 !== null &&
|
|
29003
|
+
if (resolvedCliPath3 !== null && existsSync42(resolvedCliPath3)) {
|
|
28276
29004
|
return resolvedCliPath3;
|
|
28277
29005
|
}
|
|
28278
29006
|
if (initPromise2) {
|
|
@@ -28280,7 +29008,7 @@ async function getAstGrepPath() {
|
|
|
28280
29008
|
}
|
|
28281
29009
|
initPromise2 = (async () => {
|
|
28282
29010
|
const syncPath = findSgCliPathSync();
|
|
28283
|
-
if (syncPath &&
|
|
29011
|
+
if (syncPath && existsSync42(syncPath)) {
|
|
28284
29012
|
resolvedCliPath3 = syncPath;
|
|
28285
29013
|
setSgCliPath(syncPath);
|
|
28286
29014
|
return syncPath;
|
|
@@ -28314,7 +29042,7 @@ async function runSg(options) {
|
|
|
28314
29042
|
const paths = options.paths && options.paths.length > 0 ? options.paths : ["."];
|
|
28315
29043
|
args.push(...paths);
|
|
28316
29044
|
let cliPath = getSgCliPath();
|
|
28317
|
-
if (!
|
|
29045
|
+
if (!existsSync42(cliPath) && cliPath !== "sg") {
|
|
28318
29046
|
const downloadedPath = await getAstGrepPath();
|
|
28319
29047
|
if (downloadedPath) {
|
|
28320
29048
|
cliPath = downloadedPath;
|
|
@@ -28578,20 +29306,20 @@ var ast_grep_replace = tool({
|
|
|
28578
29306
|
var {spawn: spawn9 } = globalThis.Bun;
|
|
28579
29307
|
|
|
28580
29308
|
// src/tools/grep/constants.ts
|
|
28581
|
-
import { existsSync as
|
|
28582
|
-
import { join as
|
|
29309
|
+
import { existsSync as existsSync44 } from "fs";
|
|
29310
|
+
import { join as join50, dirname as dirname10 } from "path";
|
|
28583
29311
|
import { spawnSync } from "child_process";
|
|
28584
29312
|
|
|
28585
29313
|
// src/tools/grep/downloader.ts
|
|
28586
|
-
import { existsSync as
|
|
28587
|
-
import { join as
|
|
29314
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync12, chmodSync as chmodSync3, unlinkSync as unlinkSync11, readdirSync as readdirSync15 } from "fs";
|
|
29315
|
+
import { join as join49 } from "path";
|
|
28588
29316
|
var {spawn: spawn8 } = globalThis.Bun;
|
|
28589
29317
|
function findFileRecursive(dir, filename) {
|
|
28590
29318
|
try {
|
|
28591
|
-
const entries =
|
|
29319
|
+
const entries = readdirSync15(dir, { withFileTypes: true, recursive: true });
|
|
28592
29320
|
for (const entry of entries) {
|
|
28593
29321
|
if (entry.isFile() && entry.name === filename) {
|
|
28594
|
-
return
|
|
29322
|
+
return join49(entry.parentPath ?? dir, entry.name);
|
|
28595
29323
|
}
|
|
28596
29324
|
}
|
|
28597
29325
|
} catch {
|
|
@@ -28612,11 +29340,11 @@ function getPlatformKey() {
|
|
|
28612
29340
|
}
|
|
28613
29341
|
function getInstallDir() {
|
|
28614
29342
|
const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
|
|
28615
|
-
return
|
|
29343
|
+
return join49(homeDir, ".cache", "oh-my-opencode", "bin");
|
|
28616
29344
|
}
|
|
28617
29345
|
function getRgPath() {
|
|
28618
29346
|
const isWindows2 = process.platform === "win32";
|
|
28619
|
-
return
|
|
29347
|
+
return join49(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
|
|
28620
29348
|
}
|
|
28621
29349
|
async function downloadFile(url2, destPath) {
|
|
28622
29350
|
const response2 = await fetch(url2);
|
|
@@ -28653,7 +29381,7 @@ async function extractZipWindows(archivePath, destDir) {
|
|
|
28653
29381
|
}
|
|
28654
29382
|
const foundPath = findFileRecursive(destDir, "rg.exe");
|
|
28655
29383
|
if (foundPath) {
|
|
28656
|
-
const destPath =
|
|
29384
|
+
const destPath = join49(destDir, "rg.exe");
|
|
28657
29385
|
if (foundPath !== destPath) {
|
|
28658
29386
|
const { renameSync } = await import("fs");
|
|
28659
29387
|
renameSync(foundPath, destPath);
|
|
@@ -28671,7 +29399,7 @@ async function extractZipUnix(archivePath, destDir) {
|
|
|
28671
29399
|
}
|
|
28672
29400
|
const foundPath = findFileRecursive(destDir, "rg");
|
|
28673
29401
|
if (foundPath) {
|
|
28674
|
-
const destPath =
|
|
29402
|
+
const destPath = join49(destDir, "rg");
|
|
28675
29403
|
if (foundPath !== destPath) {
|
|
28676
29404
|
const { renameSync } = await import("fs");
|
|
28677
29405
|
renameSync(foundPath, destPath);
|
|
@@ -28693,13 +29421,13 @@ async function downloadAndInstallRipgrep() {
|
|
|
28693
29421
|
}
|
|
28694
29422
|
const installDir = getInstallDir();
|
|
28695
29423
|
const rgPath = getRgPath();
|
|
28696
|
-
if (
|
|
29424
|
+
if (existsSync43(rgPath)) {
|
|
28697
29425
|
return rgPath;
|
|
28698
29426
|
}
|
|
28699
|
-
|
|
29427
|
+
mkdirSync12(installDir, { recursive: true });
|
|
28700
29428
|
const filename = `ripgrep-${RG_VERSION}-${config3.platform}.${config3.extension}`;
|
|
28701
29429
|
const url2 = `https://github.com/BurntSushi/ripgrep/releases/download/${RG_VERSION}/${filename}`;
|
|
28702
|
-
const archivePath =
|
|
29430
|
+
const archivePath = join49(installDir, filename);
|
|
28703
29431
|
try {
|
|
28704
29432
|
await downloadFile(url2, archivePath);
|
|
28705
29433
|
if (config3.extension === "tar.gz") {
|
|
@@ -28710,21 +29438,21 @@ async function downloadAndInstallRipgrep() {
|
|
|
28710
29438
|
if (process.platform !== "win32") {
|
|
28711
29439
|
chmodSync3(rgPath, 493);
|
|
28712
29440
|
}
|
|
28713
|
-
if (!
|
|
29441
|
+
if (!existsSync43(rgPath)) {
|
|
28714
29442
|
throw new Error("ripgrep binary not found after extraction");
|
|
28715
29443
|
}
|
|
28716
29444
|
return rgPath;
|
|
28717
29445
|
} finally {
|
|
28718
|
-
if (
|
|
29446
|
+
if (existsSync43(archivePath)) {
|
|
28719
29447
|
try {
|
|
28720
|
-
|
|
29448
|
+
unlinkSync11(archivePath);
|
|
28721
29449
|
} catch {}
|
|
28722
29450
|
}
|
|
28723
29451
|
}
|
|
28724
29452
|
}
|
|
28725
29453
|
function getInstalledRipgrepPath() {
|
|
28726
29454
|
const rgPath = getRgPath();
|
|
28727
|
-
return
|
|
29455
|
+
return existsSync43(rgPath) ? rgPath : null;
|
|
28728
29456
|
}
|
|
28729
29457
|
|
|
28730
29458
|
// src/tools/grep/constants.ts
|
|
@@ -28744,18 +29472,18 @@ function findExecutable(name) {
|
|
|
28744
29472
|
}
|
|
28745
29473
|
function getOpenCodeBundledRg() {
|
|
28746
29474
|
const execPath = process.execPath;
|
|
28747
|
-
const execDir =
|
|
29475
|
+
const execDir = dirname10(execPath);
|
|
28748
29476
|
const isWindows2 = process.platform === "win32";
|
|
28749
29477
|
const rgName = isWindows2 ? "rg.exe" : "rg";
|
|
28750
29478
|
const candidates = [
|
|
28751
|
-
|
|
28752
|
-
|
|
28753
|
-
|
|
28754
|
-
|
|
28755
|
-
|
|
29479
|
+
join50(getDataDir(), "opencode", "bin", rgName),
|
|
29480
|
+
join50(execDir, rgName),
|
|
29481
|
+
join50(execDir, "bin", rgName),
|
|
29482
|
+
join50(execDir, "..", "bin", rgName),
|
|
29483
|
+
join50(execDir, "..", "libexec", rgName)
|
|
28756
29484
|
];
|
|
28757
29485
|
for (const candidate of candidates) {
|
|
28758
|
-
if (
|
|
29486
|
+
if (existsSync44(candidate)) {
|
|
28759
29487
|
return candidate;
|
|
28760
29488
|
}
|
|
28761
29489
|
}
|
|
@@ -29208,21 +29936,21 @@ var glob = tool({
|
|
|
29208
29936
|
}
|
|
29209
29937
|
});
|
|
29210
29938
|
// src/tools/slashcommand/tools.ts
|
|
29211
|
-
import { existsSync as
|
|
29212
|
-
import { join as
|
|
29939
|
+
import { existsSync as existsSync45, readdirSync as readdirSync16, readFileSync as readFileSync30 } from "fs";
|
|
29940
|
+
import { join as join51, basename as basename5, dirname as dirname11 } from "path";
|
|
29213
29941
|
function discoverCommandsFromDir(commandsDir, scope) {
|
|
29214
|
-
if (!
|
|
29942
|
+
if (!existsSync45(commandsDir)) {
|
|
29215
29943
|
return [];
|
|
29216
29944
|
}
|
|
29217
|
-
const entries =
|
|
29945
|
+
const entries = readdirSync16(commandsDir, { withFileTypes: true });
|
|
29218
29946
|
const commands2 = [];
|
|
29219
29947
|
for (const entry of entries) {
|
|
29220
29948
|
if (!isMarkdownFile(entry))
|
|
29221
29949
|
continue;
|
|
29222
|
-
const commandPath =
|
|
29223
|
-
const commandName =
|
|
29950
|
+
const commandPath = join51(commandsDir, entry.name);
|
|
29951
|
+
const commandName = basename5(entry.name, ".md");
|
|
29224
29952
|
try {
|
|
29225
|
-
const content =
|
|
29953
|
+
const content = readFileSync30(commandPath, "utf-8");
|
|
29226
29954
|
const { data, body } = parseFrontmatter(content);
|
|
29227
29955
|
const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
|
|
29228
29956
|
const metadata = {
|
|
@@ -29247,19 +29975,40 @@ function discoverCommandsFromDir(commandsDir, scope) {
|
|
|
29247
29975
|
return commands2;
|
|
29248
29976
|
}
|
|
29249
29977
|
function discoverCommandsSync() {
|
|
29250
|
-
const { homedir:
|
|
29251
|
-
const userCommandsDir =
|
|
29252
|
-
const projectCommandsDir =
|
|
29253
|
-
const opencodeGlobalDir =
|
|
29254
|
-
const opencodeProjectDir =
|
|
29978
|
+
const { homedir: homedir14 } = __require("os");
|
|
29979
|
+
const userCommandsDir = join51(getClaudeConfigDir(), "commands");
|
|
29980
|
+
const projectCommandsDir = join51(process.cwd(), ".claude", "commands");
|
|
29981
|
+
const opencodeGlobalDir = join51(homedir14(), ".config", "opencode", "command");
|
|
29982
|
+
const opencodeProjectDir = join51(process.cwd(), ".opencode", "command");
|
|
29255
29983
|
const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
|
|
29256
29984
|
const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
|
|
29257
29985
|
const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
|
|
29258
29986
|
const opencodeProjectCommands = discoverCommandsFromDir(opencodeProjectDir, "opencode-project");
|
|
29259
29987
|
return [...opencodeProjectCommands, ...projectCommands, ...opencodeGlobalCommands, ...userCommands];
|
|
29260
29988
|
}
|
|
29989
|
+
function skillToCommandInfo(skill) {
|
|
29990
|
+
return {
|
|
29991
|
+
name: skill.name,
|
|
29992
|
+
path: skill.path,
|
|
29993
|
+
metadata: {
|
|
29994
|
+
name: skill.name,
|
|
29995
|
+
description: skill.definition.description || "",
|
|
29996
|
+
argumentHint: skill.definition.argumentHint,
|
|
29997
|
+
model: skill.definition.model,
|
|
29998
|
+
agent: skill.definition.agent,
|
|
29999
|
+
subtask: skill.definition.subtask
|
|
30000
|
+
},
|
|
30001
|
+
content: skill.definition.template,
|
|
30002
|
+
scope: skill.scope
|
|
30003
|
+
};
|
|
30004
|
+
}
|
|
29261
30005
|
var availableCommands = discoverCommandsSync();
|
|
29262
|
-
var
|
|
30006
|
+
var availableSkills = discoverAllSkills();
|
|
30007
|
+
var availableItems = [
|
|
30008
|
+
...availableCommands,
|
|
30009
|
+
...availableSkills.map(skillToCommandInfo)
|
|
30010
|
+
];
|
|
30011
|
+
var commandListForDescription = availableItems.map((cmd) => {
|
|
29263
30012
|
const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
|
|
29264
30013
|
return `- /${cmd.name}${hint}: ${cmd.metadata.description} (${cmd.scope})`;
|
|
29265
30014
|
}).join(`
|
|
@@ -29294,93 +30043,78 @@ async function formatLoadedCommand(cmd) {
|
|
|
29294
30043
|
`);
|
|
29295
30044
|
sections.push(`## Command Instructions
|
|
29296
30045
|
`);
|
|
29297
|
-
const commandDir =
|
|
29298
|
-
const withFileRefs = await resolveFileReferencesInText(cmd.content, commandDir);
|
|
30046
|
+
const commandDir = cmd.path ? dirname11(cmd.path) : process.cwd();
|
|
30047
|
+
const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
|
|
29299
30048
|
const resolvedContent = await resolveCommandsInText(withFileRefs);
|
|
29300
30049
|
sections.push(resolvedContent.trim());
|
|
29301
30050
|
return sections.join(`
|
|
29302
30051
|
`);
|
|
29303
30052
|
}
|
|
29304
|
-
function formatCommandList(
|
|
29305
|
-
if (
|
|
29306
|
-
return "No commands found.";
|
|
30053
|
+
function formatCommandList(items) {
|
|
30054
|
+
if (items.length === 0) {
|
|
30055
|
+
return "No commands or skills found.";
|
|
29307
30056
|
}
|
|
29308
|
-
const lines = [`# Available Commands
|
|
30057
|
+
const lines = [`# Available Commands & Skills
|
|
29309
30058
|
`];
|
|
29310
|
-
for (const cmd of
|
|
30059
|
+
for (const cmd of items) {
|
|
29311
30060
|
const hint = cmd.metadata.argumentHint ? ` ${cmd.metadata.argumentHint}` : "";
|
|
29312
30061
|
lines.push(`- **/${cmd.name}${hint}**: ${cmd.metadata.description || "(no description)"} (${cmd.scope})`);
|
|
29313
30062
|
}
|
|
29314
30063
|
lines.push(`
|
|
29315
|
-
**Total**: ${
|
|
30064
|
+
**Total**: ${items.length} items`);
|
|
29316
30065
|
return lines.join(`
|
|
29317
30066
|
`);
|
|
29318
30067
|
}
|
|
29319
30068
|
var slashcommand = tool({
|
|
29320
|
-
description: `
|
|
29321
|
-
|
|
29322
|
-
|
|
29323
|
-
|
|
29324
|
-
|
|
29325
|
-
|
|
29326
|
-
|
|
29327
|
-
|
|
29328
|
-
|
|
29329
|
-
Important:
|
|
29330
|
-
- Only use commands listed in Available Commands below
|
|
29331
|
-
- Do not invoke a command that is already running
|
|
29332
|
-
- **CRITICAL**: When user's message starts with '/' (e.g., "/commit", "/plan"), you MUST immediately invoke this tool with that command. Do NOT attempt to handle the command manually.
|
|
29333
|
-
|
|
29334
|
-
Commands are loaded from (priority order, highest wins):
|
|
29335
|
-
- .opencode/command/ (opencode-project - OpenCode project-specific commands)
|
|
29336
|
-
- ./.claude/commands/ (project - Claude Code project-specific commands)
|
|
29337
|
-
- ~/.config/opencode/command/ (opencode - OpenCode global commands)
|
|
29338
|
-
- $CLAUDE_CONFIG_DIR/commands/ or ~/.claude/commands/ (user - Claude Code global commands)
|
|
29339
|
-
|
|
29340
|
-
Each command is a markdown file with:
|
|
29341
|
-
- YAML frontmatter: description, argument-hint, model, agent, subtask (optional)
|
|
29342
|
-
- Markdown body: The command instructions/prompt
|
|
29343
|
-
- File references: @path/to/file (relative to command file location)
|
|
29344
|
-
- Shell injection: \`!\`command\`\` (executes and injects output)
|
|
29345
|
-
|
|
29346
|
-
Available Commands:
|
|
29347
|
-
${commandListForDescription}`,
|
|
30069
|
+
description: `Load a skill to get detailed instructions for a specific task.
|
|
30070
|
+
|
|
30071
|
+
Skills provide specialized knowledge and step-by-step guidance.
|
|
30072
|
+
Use this when a task matches an available skill's description.
|
|
30073
|
+
|
|
30074
|
+
<available_skills>
|
|
30075
|
+
${commandListForDescription}
|
|
30076
|
+
</available_skills>`,
|
|
29348
30077
|
args: {
|
|
29349
30078
|
command: tool.schema.string().describe("The slash command to execute (without the leading slash). E.g., 'commit', 'plan', 'execute'.")
|
|
29350
30079
|
},
|
|
29351
30080
|
async execute(args) {
|
|
29352
30081
|
const commands2 = discoverCommandsSync();
|
|
30082
|
+
const skills = discoverAllSkills();
|
|
30083
|
+
const allItems = [
|
|
30084
|
+
...commands2,
|
|
30085
|
+
...skills.map(skillToCommandInfo)
|
|
30086
|
+
];
|
|
29353
30087
|
if (!args.command) {
|
|
29354
|
-
return formatCommandList(
|
|
30088
|
+
return formatCommandList(allItems) + `
|
|
29355
30089
|
|
|
29356
|
-
Provide a command name to execute.`;
|
|
30090
|
+
Provide a command or skill name to execute.`;
|
|
29357
30091
|
}
|
|
29358
30092
|
const cmdName = args.command.replace(/^\//, "");
|
|
29359
|
-
const exactMatch =
|
|
30093
|
+
const exactMatch = allItems.find((cmd) => cmd.name.toLowerCase() === cmdName.toLowerCase());
|
|
29360
30094
|
if (exactMatch) {
|
|
29361
30095
|
return await formatLoadedCommand(exactMatch);
|
|
29362
30096
|
}
|
|
29363
|
-
const partialMatches =
|
|
30097
|
+
const partialMatches = allItems.filter((cmd) => cmd.name.toLowerCase().includes(cmdName.toLowerCase()));
|
|
29364
30098
|
if (partialMatches.length > 0) {
|
|
29365
30099
|
const matchList = partialMatches.map((cmd) => `/${cmd.name}`).join(", ");
|
|
29366
30100
|
return `No exact match for "/${cmdName}". Did you mean: ${matchList}?
|
|
29367
30101
|
|
|
29368
|
-
` + formatCommandList(
|
|
30102
|
+
` + formatCommandList(allItems);
|
|
29369
30103
|
}
|
|
29370
|
-
return `Command "/${cmdName}" not found.
|
|
30104
|
+
return `Command or skill "/${cmdName}" not found.
|
|
29371
30105
|
|
|
29372
|
-
` + formatCommandList(
|
|
30106
|
+
` + formatCommandList(allItems) + `
|
|
29373
30107
|
|
|
29374
|
-
Try a different
|
|
30108
|
+
Try a different name.`;
|
|
29375
30109
|
}
|
|
29376
30110
|
});
|
|
29377
30111
|
// src/tools/session-manager/constants.ts
|
|
29378
|
-
import { join as
|
|
30112
|
+
import { join as join52 } from "path";
|
|
29379
30113
|
var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
|
|
29380
|
-
var MESSAGE_STORAGE4 =
|
|
29381
|
-
var PART_STORAGE4 =
|
|
29382
|
-
var TODO_DIR2 =
|
|
29383
|
-
var TRANSCRIPT_DIR2 =
|
|
30114
|
+
var MESSAGE_STORAGE4 = join52(OPENCODE_STORAGE9, "message");
|
|
30115
|
+
var PART_STORAGE4 = join52(OPENCODE_STORAGE9, "part");
|
|
30116
|
+
var TODO_DIR2 = join52(getClaudeConfigDir(), "todos");
|
|
30117
|
+
var TRANSCRIPT_DIR2 = join52(getClaudeConfigDir(), "transcripts");
|
|
29384
30118
|
var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
|
|
29385
30119
|
|
|
29386
30120
|
Returns a list of available session IDs with metadata including message count, date range, and agents used.
|
|
@@ -29453,11 +30187,11 @@ Has Todos: Yes (12 items, 8 completed)
|
|
|
29453
30187
|
Has Transcript: Yes (234 entries)`;
|
|
29454
30188
|
|
|
29455
30189
|
// src/tools/session-manager/storage.ts
|
|
29456
|
-
import { existsSync as
|
|
30190
|
+
import { existsSync as existsSync46, readdirSync as readdirSync17 } from "fs";
|
|
29457
30191
|
import { readdir, readFile } from "fs/promises";
|
|
29458
|
-
import { join as
|
|
30192
|
+
import { join as join53 } from "path";
|
|
29459
30193
|
async function getAllSessions() {
|
|
29460
|
-
if (!
|
|
30194
|
+
if (!existsSync46(MESSAGE_STORAGE4))
|
|
29461
30195
|
return [];
|
|
29462
30196
|
const sessions = [];
|
|
29463
30197
|
async function scanDirectory(dir) {
|
|
@@ -29465,7 +30199,7 @@ async function getAllSessions() {
|
|
|
29465
30199
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
29466
30200
|
for (const entry of entries) {
|
|
29467
30201
|
if (entry.isDirectory()) {
|
|
29468
|
-
const sessionPath =
|
|
30202
|
+
const sessionPath = join53(dir, entry.name);
|
|
29469
30203
|
const files = await readdir(sessionPath);
|
|
29470
30204
|
if (files.some((f) => f.endsWith(".json"))) {
|
|
29471
30205
|
sessions.push(entry.name);
|
|
@@ -29482,16 +30216,16 @@ async function getAllSessions() {
|
|
|
29482
30216
|
return [...new Set(sessions)];
|
|
29483
30217
|
}
|
|
29484
30218
|
function getMessageDir9(sessionID) {
|
|
29485
|
-
if (!
|
|
30219
|
+
if (!existsSync46(MESSAGE_STORAGE4))
|
|
29486
30220
|
return "";
|
|
29487
|
-
const directPath =
|
|
29488
|
-
if (
|
|
30221
|
+
const directPath = join53(MESSAGE_STORAGE4, sessionID);
|
|
30222
|
+
if (existsSync46(directPath)) {
|
|
29489
30223
|
return directPath;
|
|
29490
30224
|
}
|
|
29491
30225
|
try {
|
|
29492
|
-
for (const dir of
|
|
29493
|
-
const sessionPath =
|
|
29494
|
-
if (
|
|
30226
|
+
for (const dir of readdirSync17(MESSAGE_STORAGE4)) {
|
|
30227
|
+
const sessionPath = join53(MESSAGE_STORAGE4, dir, sessionID);
|
|
30228
|
+
if (existsSync46(sessionPath)) {
|
|
29495
30229
|
return sessionPath;
|
|
29496
30230
|
}
|
|
29497
30231
|
}
|
|
@@ -29505,7 +30239,7 @@ function sessionExists(sessionID) {
|
|
|
29505
30239
|
}
|
|
29506
30240
|
async function readSessionMessages(sessionID) {
|
|
29507
30241
|
const messageDir = getMessageDir9(sessionID);
|
|
29508
|
-
if (!messageDir || !
|
|
30242
|
+
if (!messageDir || !existsSync46(messageDir))
|
|
29509
30243
|
return [];
|
|
29510
30244
|
const messages = [];
|
|
29511
30245
|
try {
|
|
@@ -29514,7 +30248,7 @@ async function readSessionMessages(sessionID) {
|
|
|
29514
30248
|
if (!file2.endsWith(".json"))
|
|
29515
30249
|
continue;
|
|
29516
30250
|
try {
|
|
29517
|
-
const content = await readFile(
|
|
30251
|
+
const content = await readFile(join53(messageDir, file2), "utf-8");
|
|
29518
30252
|
const meta = JSON.parse(content);
|
|
29519
30253
|
const parts = await readParts2(meta.id);
|
|
29520
30254
|
messages.push({
|
|
@@ -29540,8 +30274,8 @@ async function readSessionMessages(sessionID) {
|
|
|
29540
30274
|
});
|
|
29541
30275
|
}
|
|
29542
30276
|
async function readParts2(messageID) {
|
|
29543
|
-
const partDir =
|
|
29544
|
-
if (!
|
|
30277
|
+
const partDir = join53(PART_STORAGE4, messageID);
|
|
30278
|
+
if (!existsSync46(partDir))
|
|
29545
30279
|
return [];
|
|
29546
30280
|
const parts = [];
|
|
29547
30281
|
try {
|
|
@@ -29550,7 +30284,7 @@ async function readParts2(messageID) {
|
|
|
29550
30284
|
if (!file2.endsWith(".json"))
|
|
29551
30285
|
continue;
|
|
29552
30286
|
try {
|
|
29553
|
-
const content = await readFile(
|
|
30287
|
+
const content = await readFile(join53(partDir, file2), "utf-8");
|
|
29554
30288
|
parts.push(JSON.parse(content));
|
|
29555
30289
|
} catch {
|
|
29556
30290
|
continue;
|
|
@@ -29562,14 +30296,14 @@ async function readParts2(messageID) {
|
|
|
29562
30296
|
return parts.sort((a, b) => a.id.localeCompare(b.id));
|
|
29563
30297
|
}
|
|
29564
30298
|
async function readSessionTodos(sessionID) {
|
|
29565
|
-
if (!
|
|
30299
|
+
if (!existsSync46(TODO_DIR2))
|
|
29566
30300
|
return [];
|
|
29567
30301
|
try {
|
|
29568
30302
|
const allFiles = await readdir(TODO_DIR2);
|
|
29569
30303
|
const todoFiles = allFiles.filter((f) => f.includes(sessionID) && f.endsWith(".json"));
|
|
29570
30304
|
for (const file2 of todoFiles) {
|
|
29571
30305
|
try {
|
|
29572
|
-
const content = await readFile(
|
|
30306
|
+
const content = await readFile(join53(TODO_DIR2, file2), "utf-8");
|
|
29573
30307
|
const data = JSON.parse(content);
|
|
29574
30308
|
if (Array.isArray(data)) {
|
|
29575
30309
|
return data.map((item) => ({
|
|
@@ -29589,10 +30323,10 @@ async function readSessionTodos(sessionID) {
|
|
|
29589
30323
|
return [];
|
|
29590
30324
|
}
|
|
29591
30325
|
async function readSessionTranscript(sessionID) {
|
|
29592
|
-
if (!
|
|
30326
|
+
if (!existsSync46(TRANSCRIPT_DIR2))
|
|
29593
30327
|
return 0;
|
|
29594
|
-
const transcriptFile =
|
|
29595
|
-
if (!
|
|
30328
|
+
const transcriptFile = join53(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
|
|
30329
|
+
if (!existsSync46(transcriptFile))
|
|
29596
30330
|
return 0;
|
|
29597
30331
|
try {
|
|
29598
30332
|
const content = await readFile(transcriptFile, "utf-8");
|
|
@@ -29913,6 +30647,8 @@ var BLOCKED_TMUX_SUBCOMMANDS = [
|
|
|
29913
30647
|
];
|
|
29914
30648
|
var INTERACTIVE_BASH_DESCRIPTION = `Execute tmux commands. Use "omo-{name}" session pattern.
|
|
29915
30649
|
|
|
30650
|
+
For: server processes, long-running tasks, background jobs, interactive CLI tools.
|
|
30651
|
+
|
|
29916
30652
|
Blocked (use bash instead): capture-pane, save-buffer, show-buffer, pipe-pane.`;
|
|
29917
30653
|
|
|
29918
30654
|
// src/tools/interactive-bash/utils.ts
|
|
@@ -30050,9 +30786,91 @@ var interactive_bash = tool({
|
|
|
30050
30786
|
}
|
|
30051
30787
|
}
|
|
30052
30788
|
});
|
|
30789
|
+
// src/tools/skill/constants.ts
|
|
30790
|
+
var TOOL_DESCRIPTION_NO_SKILLS = "Load a skill to get detailed instructions for a specific task. No skills are currently available.";
|
|
30791
|
+
var TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a specific task.
|
|
30792
|
+
|
|
30793
|
+
Skills provide specialized knowledge and step-by-step guidance.
|
|
30794
|
+
Use this when a task matches an available skill's description.`;
|
|
30795
|
+
// src/tools/skill/tools.ts
|
|
30796
|
+
import { dirname as dirname12 } from "path";
|
|
30797
|
+
import { readFileSync as readFileSync31 } from "fs";
|
|
30798
|
+
function loadedSkillToInfo(skill) {
|
|
30799
|
+
return {
|
|
30800
|
+
name: skill.name,
|
|
30801
|
+
description: skill.definition.description || "",
|
|
30802
|
+
location: skill.path,
|
|
30803
|
+
scope: skill.scope,
|
|
30804
|
+
license: skill.license,
|
|
30805
|
+
compatibility: skill.compatibility,
|
|
30806
|
+
metadata: skill.metadata,
|
|
30807
|
+
allowedTools: skill.allowedTools
|
|
30808
|
+
};
|
|
30809
|
+
}
|
|
30810
|
+
function formatSkillsXml(skills) {
|
|
30811
|
+
if (skills.length === 0)
|
|
30812
|
+
return "";
|
|
30813
|
+
const skillsXml = skills.map((skill) => {
|
|
30814
|
+
const lines = [
|
|
30815
|
+
" <skill>",
|
|
30816
|
+
` <name>${skill.name}</name>`,
|
|
30817
|
+
` <description>${skill.description}</description>`
|
|
30818
|
+
];
|
|
30819
|
+
if (skill.compatibility) {
|
|
30820
|
+
lines.push(` <compatibility>${skill.compatibility}</compatibility>`);
|
|
30821
|
+
}
|
|
30822
|
+
lines.push(" </skill>");
|
|
30823
|
+
return lines.join(`
|
|
30824
|
+
`);
|
|
30825
|
+
}).join(`
|
|
30826
|
+
`);
|
|
30827
|
+
return `
|
|
30828
|
+
|
|
30829
|
+
<available_skills>
|
|
30830
|
+
${skillsXml}
|
|
30831
|
+
</available_skills>`;
|
|
30832
|
+
}
|
|
30833
|
+
function extractSkillBody(skill) {
|
|
30834
|
+
if (skill.path) {
|
|
30835
|
+
const content = readFileSync31(skill.path, "utf-8");
|
|
30836
|
+
const { body } = parseFrontmatter(content);
|
|
30837
|
+
return body.trim();
|
|
30838
|
+
}
|
|
30839
|
+
const templateMatch = skill.definition.template?.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
30840
|
+
return templateMatch ? templateMatch[1].trim() : skill.definition.template || "";
|
|
30841
|
+
}
|
|
30842
|
+
function createSkillTool(options = {}) {
|
|
30843
|
+
const skills = options.skills ?? discoverSkills({ includeClaudeCodePaths: !options.opencodeOnly });
|
|
30844
|
+
const skillInfos = skills.map(loadedSkillToInfo);
|
|
30845
|
+
const description = skillInfos.length === 0 ? TOOL_DESCRIPTION_NO_SKILLS : TOOL_DESCRIPTION_PREFIX + formatSkillsXml(skillInfos);
|
|
30846
|
+
return tool({
|
|
30847
|
+
description,
|
|
30848
|
+
args: {
|
|
30849
|
+
name: tool.schema.string().describe("The skill identifier from available_skills (e.g., 'code-review')")
|
|
30850
|
+
},
|
|
30851
|
+
async execute(args) {
|
|
30852
|
+
const skill = options.skills ? skills.find((s) => s.name === args.name) : skills.find((s) => s.name === args.name);
|
|
30853
|
+
if (!skill) {
|
|
30854
|
+
const available = skills.map((s) => s.name).join(", ");
|
|
30855
|
+
throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`);
|
|
30856
|
+
}
|
|
30857
|
+
const body = extractSkillBody(skill);
|
|
30858
|
+
const dir = skill.path ? dirname12(skill.path) : skill.resolvedPath || process.cwd();
|
|
30859
|
+
return [
|
|
30860
|
+
`## Skill: ${skill.name}`,
|
|
30861
|
+
"",
|
|
30862
|
+
`**Base directory**: ${dir}`,
|
|
30863
|
+
"",
|
|
30864
|
+
body
|
|
30865
|
+
].join(`
|
|
30866
|
+
`);
|
|
30867
|
+
}
|
|
30868
|
+
});
|
|
30869
|
+
}
|
|
30870
|
+
var skill = createSkillTool();
|
|
30053
30871
|
// src/tools/background-task/tools.ts
|
|
30054
|
-
import { existsSync as
|
|
30055
|
-
import { join as
|
|
30872
|
+
import { existsSync as existsSync47, readdirSync as readdirSync18 } from "fs";
|
|
30873
|
+
import { join as join54 } from "path";
|
|
30056
30874
|
|
|
30057
30875
|
// src/tools/background-task/constants.ts
|
|
30058
30876
|
var BACKGROUND_TASK_DESCRIPTION = `Run agent task in background. Returns task_id immediately; notifies on completion.
|
|
@@ -30063,14 +30881,14 @@ var BACKGROUND_CANCEL_DESCRIPTION = `Cancel running background task(s). Use all=
|
|
|
30063
30881
|
|
|
30064
30882
|
// src/tools/background-task/tools.ts
|
|
30065
30883
|
function getMessageDir10(sessionID) {
|
|
30066
|
-
if (!
|
|
30884
|
+
if (!existsSync47(MESSAGE_STORAGE))
|
|
30067
30885
|
return null;
|
|
30068
|
-
const directPath =
|
|
30069
|
-
if (
|
|
30886
|
+
const directPath = join54(MESSAGE_STORAGE, sessionID);
|
|
30887
|
+
if (existsSync47(directPath))
|
|
30070
30888
|
return directPath;
|
|
30071
|
-
for (const dir of
|
|
30072
|
-
const sessionPath =
|
|
30073
|
-
if (
|
|
30889
|
+
for (const dir of readdirSync18(MESSAGE_STORAGE)) {
|
|
30890
|
+
const sessionPath = join54(MESSAGE_STORAGE, dir, sessionID);
|
|
30891
|
+
if (existsSync47(sessionPath))
|
|
30074
30892
|
return sessionPath;
|
|
30075
30893
|
}
|
|
30076
30894
|
return null;
|
|
@@ -30137,7 +30955,7 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
|
|
|
30137
30955
|
});
|
|
30138
30956
|
}
|
|
30139
30957
|
function delay(ms) {
|
|
30140
|
-
return new Promise((
|
|
30958
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
30141
30959
|
}
|
|
30142
30960
|
function truncateText(text, maxLength) {
|
|
30143
30961
|
if (text.length <= maxLength)
|
|
@@ -30512,9 +31330,9 @@ session_id: ${sessionID}
|
|
|
30512
31330
|
}
|
|
30513
31331
|
// src/tools/look-at/constants.ts
|
|
30514
31332
|
var MULTIMODAL_LOOKER_AGENT = "multimodal-looker";
|
|
30515
|
-
var LOOK_AT_DESCRIPTION = `Analyze media files (PDFs, images, diagrams)
|
|
31333
|
+
var LOOK_AT_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.`;
|
|
30516
31334
|
// src/tools/look-at/tools.ts
|
|
30517
|
-
import { extname as extname3, basename as
|
|
31335
|
+
import { extname as extname3, basename as basename6 } from "path";
|
|
30518
31336
|
import { pathToFileURL } from "url";
|
|
30519
31337
|
function inferMimeType(filePath) {
|
|
30520
31338
|
const ext = extname3(filePath).toLowerCase();
|
|
@@ -30522,20 +31340,34 @@ function inferMimeType(filePath) {
|
|
|
30522
31340
|
".jpg": "image/jpeg",
|
|
30523
31341
|
".jpeg": "image/jpeg",
|
|
30524
31342
|
".png": "image/png",
|
|
30525
|
-
".gif": "image/gif",
|
|
30526
31343
|
".webp": "image/webp",
|
|
30527
|
-
".
|
|
30528
|
-
".
|
|
30529
|
-
".
|
|
31344
|
+
".heic": "image/heic",
|
|
31345
|
+
".heif": "image/heif",
|
|
31346
|
+
".mp4": "video/mp4",
|
|
31347
|
+
".mpeg": "video/mpeg",
|
|
31348
|
+
".mpg": "video/mpeg",
|
|
31349
|
+
".mov": "video/mov",
|
|
31350
|
+
".avi": "video/avi",
|
|
31351
|
+
".flv": "video/x-flv",
|
|
31352
|
+
".webm": "video/webm",
|
|
31353
|
+
".wmv": "video/wmv",
|
|
31354
|
+
".3gpp": "video/3gpp",
|
|
31355
|
+
".3gp": "video/3gpp",
|
|
31356
|
+
".wav": "audio/wav",
|
|
31357
|
+
".mp3": "audio/mp3",
|
|
31358
|
+
".aiff": "audio/aiff",
|
|
31359
|
+
".aac": "audio/aac",
|
|
31360
|
+
".ogg": "audio/ogg",
|
|
31361
|
+
".flac": "audio/flac",
|
|
30530
31362
|
".pdf": "application/pdf",
|
|
30531
31363
|
".txt": "text/plain",
|
|
30532
|
-
".
|
|
31364
|
+
".csv": "text/csv",
|
|
31365
|
+
".md": "text/md",
|
|
31366
|
+
".html": "text/html",
|
|
30533
31367
|
".json": "application/json",
|
|
30534
31368
|
".xml": "application/xml",
|
|
30535
|
-
".html": "text/html",
|
|
30536
|
-
".css": "text/css",
|
|
30537
31369
|
".js": "text/javascript",
|
|
30538
|
-
".
|
|
31370
|
+
".py": "text/x-python"
|
|
30539
31371
|
};
|
|
30540
31372
|
return mimeTypes[ext] || "application/octet-stream";
|
|
30541
31373
|
}
|
|
@@ -30549,7 +31381,7 @@ function createLookAt(ctx) {
|
|
|
30549
31381
|
async execute(args, toolContext) {
|
|
30550
31382
|
log(`[look_at] Analyzing file: ${args.file_path}, goal: ${args.goal}`);
|
|
30551
31383
|
const mimeType = inferMimeType(args.file_path);
|
|
30552
|
-
const filename =
|
|
31384
|
+
const filename = basename6(args.file_path);
|
|
30553
31385
|
const prompt = `Analyze this file and extract the requested information.
|
|
30554
31386
|
|
|
30555
31387
|
Goal: ${args.goal}
|
|
@@ -30642,17 +31474,17 @@ var builtinTools = {
|
|
|
30642
31474
|
session_info
|
|
30643
31475
|
};
|
|
30644
31476
|
// src/features/background-agent/manager.ts
|
|
30645
|
-
import { existsSync as
|
|
30646
|
-
import { join as
|
|
31477
|
+
import { existsSync as existsSync48, readdirSync as readdirSync19 } from "fs";
|
|
31478
|
+
import { join as join55 } from "path";
|
|
30647
31479
|
function getMessageDir11(sessionID) {
|
|
30648
|
-
if (!
|
|
31480
|
+
if (!existsSync48(MESSAGE_STORAGE))
|
|
30649
31481
|
return null;
|
|
30650
|
-
const directPath =
|
|
30651
|
-
if (
|
|
31482
|
+
const directPath = join55(MESSAGE_STORAGE, sessionID);
|
|
31483
|
+
if (existsSync48(directPath))
|
|
30652
31484
|
return directPath;
|
|
30653
|
-
for (const dir of
|
|
30654
|
-
const sessionPath =
|
|
30655
|
-
if (
|
|
31485
|
+
for (const dir of readdirSync19(MESSAGE_STORAGE)) {
|
|
31486
|
+
const sessionPath = join55(MESSAGE_STORAGE, dir, sessionID);
|
|
31487
|
+
if (existsSync48(sessionPath))
|
|
30656
31488
|
return sessionPath;
|
|
30657
31489
|
}
|
|
30658
31490
|
return null;
|
|
@@ -31091,7 +31923,7 @@ var HookNameSchema = exports_external.enum([
|
|
|
31091
31923
|
"directory-readme-injector",
|
|
31092
31924
|
"empty-task-response-detector",
|
|
31093
31925
|
"think-mode",
|
|
31094
|
-
"anthropic-
|
|
31926
|
+
"anthropic-context-window-limit-recovery",
|
|
31095
31927
|
"rules-injector",
|
|
31096
31928
|
"background-notification",
|
|
31097
31929
|
"auto-update-checker",
|
|
@@ -31101,7 +31933,9 @@ var HookNameSchema = exports_external.enum([
|
|
|
31101
31933
|
"non-interactive-env",
|
|
31102
31934
|
"interactive-bash-session",
|
|
31103
31935
|
"empty-message-sanitizer",
|
|
31104
|
-
"thinking-block-validator"
|
|
31936
|
+
"thinking-block-validator",
|
|
31937
|
+
"ralph-loop",
|
|
31938
|
+
"dcp-for-compaction"
|
|
31105
31939
|
]);
|
|
31106
31940
|
var BuiltinCommandNameSchema = exports_external.enum([
|
|
31107
31941
|
"init-deep"
|
|
@@ -31187,8 +32021,46 @@ var ExperimentalConfigSchema = exports_external.object({
|
|
|
31187
32021
|
preemptive_compaction: exports_external.boolean().optional(),
|
|
31188
32022
|
preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
|
|
31189
32023
|
truncate_all_tool_outputs: exports_external.boolean().default(true),
|
|
31190
|
-
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional()
|
|
31191
|
-
|
|
32024
|
+
dynamic_context_pruning: DynamicContextPruningConfigSchema.optional()
|
|
32025
|
+
});
|
|
32026
|
+
var SkillSourceSchema = exports_external.union([
|
|
32027
|
+
exports_external.string(),
|
|
32028
|
+
exports_external.object({
|
|
32029
|
+
path: exports_external.string(),
|
|
32030
|
+
recursive: exports_external.boolean().optional(),
|
|
32031
|
+
glob: exports_external.string().optional()
|
|
32032
|
+
})
|
|
32033
|
+
]);
|
|
32034
|
+
var SkillDefinitionSchema = exports_external.object({
|
|
32035
|
+
description: exports_external.string().optional(),
|
|
32036
|
+
template: exports_external.string().optional(),
|
|
32037
|
+
from: exports_external.string().optional(),
|
|
32038
|
+
model: exports_external.string().optional(),
|
|
32039
|
+
agent: exports_external.string().optional(),
|
|
32040
|
+
subtask: exports_external.boolean().optional(),
|
|
32041
|
+
"argument-hint": exports_external.string().optional(),
|
|
32042
|
+
license: exports_external.string().optional(),
|
|
32043
|
+
compatibility: exports_external.string().optional(),
|
|
32044
|
+
metadata: exports_external.record(exports_external.string(), exports_external.unknown()).optional(),
|
|
32045
|
+
"allowed-tools": exports_external.array(exports_external.string()).optional(),
|
|
32046
|
+
disable: exports_external.boolean().optional()
|
|
32047
|
+
});
|
|
32048
|
+
var SkillEntrySchema = exports_external.union([
|
|
32049
|
+
exports_external.boolean(),
|
|
32050
|
+
SkillDefinitionSchema
|
|
32051
|
+
]);
|
|
32052
|
+
var SkillsConfigSchema = exports_external.union([
|
|
32053
|
+
exports_external.array(exports_external.string()),
|
|
32054
|
+
exports_external.record(exports_external.string(), SkillEntrySchema).and(exports_external.object({
|
|
32055
|
+
sources: exports_external.array(SkillSourceSchema).optional(),
|
|
32056
|
+
enable: exports_external.array(exports_external.string()).optional(),
|
|
32057
|
+
disable: exports_external.array(exports_external.string()).optional()
|
|
32058
|
+
}).partial())
|
|
32059
|
+
]);
|
|
32060
|
+
var RalphLoopConfigSchema = exports_external.object({
|
|
32061
|
+
enabled: exports_external.boolean().default(false),
|
|
32062
|
+
default_max_iterations: exports_external.number().min(1).max(1000).default(100),
|
|
32063
|
+
state_dir: exports_external.string().optional()
|
|
31192
32064
|
});
|
|
31193
32065
|
var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
31194
32066
|
$schema: exports_external.string().optional(),
|
|
@@ -31202,7 +32074,9 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
|
|
|
31202
32074
|
sisyphus_agent: SisyphusAgentConfigSchema.optional(),
|
|
31203
32075
|
comment_checker: CommentCheckerConfigSchema.optional(),
|
|
31204
32076
|
experimental: ExperimentalConfigSchema.optional(),
|
|
31205
|
-
auto_update: exports_external.boolean().optional()
|
|
32077
|
+
auto_update: exports_external.boolean().optional(),
|
|
32078
|
+
skills: SkillsConfigSchema.optional(),
|
|
32079
|
+
ralph_loop: RalphLoopConfigSchema.optional()
|
|
31206
32080
|
});
|
|
31207
32081
|
// src/agents/plan-prompt.ts
|
|
31208
32082
|
var PLAN_SYSTEM_PROMPT = `<system-reminder>
|
|
@@ -31434,7 +32308,10 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31434
32308
|
const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {
|
|
31435
32309
|
disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
|
|
31436
32310
|
});
|
|
31437
|
-
const
|
|
32311
|
+
const anthropicContextWindowLimitRecovery = isHookEnabled("anthropic-context-window-limit-recovery") ? createAnthropicContextWindowLimitRecoveryHook(ctx, {
|
|
32312
|
+
experimental: pluginConfig.experimental,
|
|
32313
|
+
dcpForCompaction: isHookEnabled("dcp-for-compaction")
|
|
32314
|
+
}) : null;
|
|
31438
32315
|
const compactionContextInjector = createCompactionContextInjector();
|
|
31439
32316
|
const preemptiveCompaction = createPreemptiveCompactionHook(ctx, {
|
|
31440
32317
|
experimental: pluginConfig.experimental,
|
|
@@ -31453,6 +32330,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31453
32330
|
const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
|
|
31454
32331
|
const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
|
|
31455
32332
|
const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
|
|
32333
|
+
const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, { config: pluginConfig.ralph_loop }) : null;
|
|
31456
32334
|
const backgroundManager = new BackgroundManager(ctx);
|
|
31457
32335
|
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
|
|
31458
32336
|
if (sessionRecovery && todoContinuationEnforcer) {
|
|
@@ -31463,6 +32341,10 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31463
32341
|
const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
|
|
31464
32342
|
const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
|
|
31465
32343
|
const lookAt = createLookAt(ctx);
|
|
32344
|
+
const builtinSkills = createBuiltinSkills();
|
|
32345
|
+
const includeClaudeSkills = pluginConfig.claude_code?.skills !== false;
|
|
32346
|
+
const mergedSkills = mergeSkills(builtinSkills, pluginConfig.skills, includeClaudeSkills ? discoverUserClaudeSkills() : [], discoverOpencodeGlobalSkills(), includeClaudeSkills ? discoverProjectClaudeSkills() : [], discoverOpencodeProjectSkills());
|
|
32347
|
+
const skillTool = createSkillTool({ skills: mergedSkills });
|
|
31466
32348
|
const googleAuthHooks = pluginConfig.google_auth !== false ? await createGoogleAntigravityAuthPlugin(ctx) : null;
|
|
31467
32349
|
const tmuxAvailable = await getTmuxPath();
|
|
31468
32350
|
return {
|
|
@@ -31472,11 +32354,35 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31472
32354
|
...backgroundTools,
|
|
31473
32355
|
call_omo_agent: callOmoAgent,
|
|
31474
32356
|
look_at: lookAt,
|
|
32357
|
+
skill: skillTool,
|
|
31475
32358
|
...tmuxAvailable ? { interactive_bash } : {}
|
|
31476
32359
|
},
|
|
31477
32360
|
"chat.message": async (input, output) => {
|
|
31478
32361
|
await claudeCodeHooks["chat.message"]?.(input, output);
|
|
31479
32362
|
await keywordDetector?.["chat.message"]?.(input, output);
|
|
32363
|
+
if (ralphLoop) {
|
|
32364
|
+
const parts = output.parts;
|
|
32365
|
+
const promptText = parts?.filter((p) => p.type === "text" && p.text).map((p) => p.text).join(`
|
|
32366
|
+
`).trim() || "";
|
|
32367
|
+
const isRalphLoopTemplate = promptText.includes("You are starting a Ralph Loop") && promptText.includes("<user-task>");
|
|
32368
|
+
const isCancelRalphTemplate = promptText.includes("Cancel the currently active Ralph Loop");
|
|
32369
|
+
if (isRalphLoopTemplate) {
|
|
32370
|
+
const taskMatch = promptText.match(/<user-task>\s*([\s\S]*?)\s*<\/user-task>/i);
|
|
32371
|
+
const rawTask = taskMatch?.[1]?.trim() || "";
|
|
32372
|
+
const quotedMatch = rawTask.match(/^["'](.+?)["']/);
|
|
32373
|
+
const prompt = quotedMatch?.[1] || rawTask.split(/\s+--/)[0]?.trim() || "Complete the task as instructed";
|
|
32374
|
+
const maxIterMatch = rawTask.match(/--max-iterations=(\d+)/i);
|
|
32375
|
+
const promiseMatch = rawTask.match(/--completion-promise=["']?([^"'\s]+)["']?/i);
|
|
32376
|
+
log("[ralph-loop] Starting loop from chat.message", { sessionID: input.sessionID, prompt });
|
|
32377
|
+
ralphLoop.startLoop(input.sessionID, prompt, {
|
|
32378
|
+
maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
|
|
32379
|
+
completionPromise: promiseMatch?.[1]
|
|
32380
|
+
});
|
|
32381
|
+
} else if (isCancelRalphTemplate) {
|
|
32382
|
+
log("[ralph-loop] Cancelling loop from chat.message", { sessionID: input.sessionID });
|
|
32383
|
+
ralphLoop.cancelLoop(input.sessionID);
|
|
32384
|
+
}
|
|
32385
|
+
}
|
|
31480
32386
|
},
|
|
31481
32387
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
31482
32388
|
await thinkingBlockValidator?.["experimental.chat.messages.transform"]?.(input, output);
|
|
@@ -31611,14 +32517,23 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31611
32517
|
const systemCommands = config3.command ?? {};
|
|
31612
32518
|
const projectCommands = pluginConfig.claude_code?.commands ?? true ? loadProjectCommands() : {};
|
|
31613
32519
|
const opencodeProjectCommands = loadOpencodeProjectCommands();
|
|
32520
|
+
const userSkills = pluginConfig.claude_code?.skills ?? true ? loadUserSkills() : {};
|
|
32521
|
+
const projectSkills = pluginConfig.claude_code?.skills ?? true ? loadProjectSkills() : {};
|
|
32522
|
+
const opencodeGlobalSkills = loadOpencodeGlobalSkills();
|
|
32523
|
+
const opencodeProjectSkills = loadOpencodeProjectSkills();
|
|
31614
32524
|
config3.command = {
|
|
31615
32525
|
...builtinCommands,
|
|
31616
32526
|
...userCommands,
|
|
32527
|
+
...userSkills,
|
|
31617
32528
|
...opencodeGlobalCommands,
|
|
32529
|
+
...opencodeGlobalSkills,
|
|
31618
32530
|
...systemCommands,
|
|
31619
32531
|
...projectCommands,
|
|
32532
|
+
...projectSkills,
|
|
31620
32533
|
...opencodeProjectCommands,
|
|
31621
|
-
...
|
|
32534
|
+
...opencodeProjectSkills,
|
|
32535
|
+
...pluginComponents.commands,
|
|
32536
|
+
...pluginComponents.skills
|
|
31622
32537
|
};
|
|
31623
32538
|
},
|
|
31624
32539
|
event: async (input) => {
|
|
@@ -31632,10 +32547,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31632
32547
|
await directoryReadmeInjector?.event(input);
|
|
31633
32548
|
await rulesInjector?.event(input);
|
|
31634
32549
|
await thinkMode?.event(input);
|
|
31635
|
-
await
|
|
32550
|
+
await anthropicContextWindowLimitRecovery?.event(input);
|
|
31636
32551
|
await preemptiveCompaction?.event(input);
|
|
31637
32552
|
await agentUsageReminder?.event(input);
|
|
31638
32553
|
await interactiveBashSession?.event(input);
|
|
32554
|
+
await ralphLoop?.event(input);
|
|
31639
32555
|
const { event } = input;
|
|
31640
32556
|
const props = event.properties;
|
|
31641
32557
|
if (event.type === "session.created") {
|
|
@@ -31688,6 +32604,24 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
31688
32604
|
...isExploreOrLibrarian ? { call_omo_agent: false } : {}
|
|
31689
32605
|
};
|
|
31690
32606
|
}
|
|
32607
|
+
if (ralphLoop && input.tool === "slashcommand") {
|
|
32608
|
+
const args = output.args;
|
|
32609
|
+
const command = args?.command?.replace(/^\//, "").toLowerCase();
|
|
32610
|
+
const sessionID = input.sessionID || getMainSessionID();
|
|
32611
|
+
if (command === "ralph-loop" && sessionID) {
|
|
32612
|
+
const rawArgs = args?.command?.replace(/^\/?(ralph-loop)\s*/i, "") || "";
|
|
32613
|
+
const taskMatch = rawArgs.match(/^["'](.+?)["']/);
|
|
32614
|
+
const prompt = taskMatch?.[1] || rawArgs.split(/\s+--/)[0]?.trim() || "Complete the task as instructed";
|
|
32615
|
+
const maxIterMatch = rawArgs.match(/--max-iterations=(\d+)/i);
|
|
32616
|
+
const promiseMatch = rawArgs.match(/--completion-promise=["']?([^"'\s]+)["']?/i);
|
|
32617
|
+
ralphLoop.startLoop(sessionID, prompt, {
|
|
32618
|
+
maxIterations: maxIterMatch ? parseInt(maxIterMatch[1], 10) : undefined,
|
|
32619
|
+
completionPromise: promiseMatch?.[1]
|
|
32620
|
+
});
|
|
32621
|
+
} else if (command === "cancel-ralph" && sessionID) {
|
|
32622
|
+
ralphLoop.cancelLoop(sessionID);
|
|
32623
|
+
}
|
|
32624
|
+
}
|
|
31691
32625
|
},
|
|
31692
32626
|
"tool.execute.after": async (input, output) => {
|
|
31693
32627
|
await claudeCodeHooks["tool.execute.after"](input, output);
|