oh-my-opencode 2.11.0 → 2.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +3 -2
- package/dist/config/schema.d.ts +2 -0
- package/dist/features/background-agent/manager.d.ts +1 -0
- package/dist/hooks/edit-error-recovery/index.d.ts +31 -0
- package/dist/hooks/edit-error-recovery/index.test.d.ts +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/non-interactive-env/index.test.d.ts +1 -0
- package/dist/index.js +152 -42
- package/dist/tools/ast-grep/utils.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2657,7 +2657,7 @@ var require_napi = __commonJS((exports, module) => {
|
|
|
2657
2657
|
var require_package = __commonJS((exports, module) => {
|
|
2658
2658
|
module.exports = {
|
|
2659
2659
|
name: "oh-my-opencode",
|
|
2660
|
-
version: "2.
|
|
2660
|
+
version: "2.12.0",
|
|
2661
2661
|
description: "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
|
|
2662
2662
|
main: "dist/index.js",
|
|
2663
2663
|
types: "dist/index.d.ts",
|
|
@@ -22575,7 +22575,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
22575
22575
|
"preemptive-compaction",
|
|
22576
22576
|
"compaction-context-injector",
|
|
22577
22577
|
"claude-code-hooks",
|
|
22578
|
-
"auto-slash-command"
|
|
22578
|
+
"auto-slash-command",
|
|
22579
|
+
"edit-error-recovery"
|
|
22579
22580
|
]);
|
|
22580
22581
|
var BuiltinCommandNameSchema = exports_external.enum([
|
|
22581
22582
|
"init-deep"
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -61,6 +61,7 @@ export declare const HookNameSchema: z.ZodEnum<{
|
|
|
61
61
|
"compaction-context-injector": "compaction-context-injector";
|
|
62
62
|
"claude-code-hooks": "claude-code-hooks";
|
|
63
63
|
"auto-slash-command": "auto-slash-command";
|
|
64
|
+
"edit-error-recovery": "edit-error-recovery";
|
|
64
65
|
}>;
|
|
65
66
|
export declare const BuiltinCommandNameSchema: z.ZodEnum<{
|
|
66
67
|
"init-deep": "init-deep";
|
|
@@ -816,6 +817,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
|
|
|
816
817
|
"compaction-context-injector": "compaction-context-injector";
|
|
817
818
|
"claude-code-hooks": "claude-code-hooks";
|
|
818
819
|
"auto-slash-command": "auto-slash-command";
|
|
820
|
+
"edit-error-recovery": "edit-error-recovery";
|
|
819
821
|
}>>>;
|
|
820
822
|
disabled_commands: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
821
823
|
"init-deep": "init-deep";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { PluginInput } from "@opencode-ai/plugin";
|
|
2
|
+
/**
|
|
3
|
+
* Known Edit tool error patterns that indicate the AI made a mistake
|
|
4
|
+
*/
|
|
5
|
+
export declare const EDIT_ERROR_PATTERNS: readonly ["oldString and newString must be different", "oldString not found", "oldString found multiple times"];
|
|
6
|
+
/**
|
|
7
|
+
* System reminder injected when Edit tool fails due to AI mistake
|
|
8
|
+
* Short, direct, and commanding - forces immediate corrective action
|
|
9
|
+
*/
|
|
10
|
+
export declare const EDIT_ERROR_REMINDER = "\n[EDIT ERROR - IMMEDIATE ACTION REQUIRED]\n\nYou made an Edit mistake. STOP and do this NOW:\n\n1. READ the file immediately to see its ACTUAL current state\n2. VERIFY what the content really looks like (your assumption was wrong)\n3. APOLOGIZE briefly to the user for the error\n4. CONTINUE with corrected action based on the real file content\n\nDO NOT attempt another edit until you've read and verified the file state.\n";
|
|
11
|
+
/**
|
|
12
|
+
* Detects Edit tool errors caused by AI mistakes and injects a recovery reminder
|
|
13
|
+
*
|
|
14
|
+
* This hook catches common Edit tool failures:
|
|
15
|
+
* - oldString and newString must be different (trying to "edit" to same content)
|
|
16
|
+
* - oldString not found (wrong assumption about file content)
|
|
17
|
+
* - oldString found multiple times (ambiguous match, need more context)
|
|
18
|
+
*
|
|
19
|
+
* @see https://github.com/sst/opencode/issues/4718
|
|
20
|
+
*/
|
|
21
|
+
export declare function createEditErrorRecoveryHook(_ctx: PluginInput): {
|
|
22
|
+
"tool.execute.after": (input: {
|
|
23
|
+
tool: string;
|
|
24
|
+
sessionID: string;
|
|
25
|
+
callID: string;
|
|
26
|
+
}, output: {
|
|
27
|
+
title: string;
|
|
28
|
+
output: string;
|
|
29
|
+
metadata: unknown;
|
|
30
|
+
}) => Promise<void>;
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -23,3 +23,4 @@ export { createEmptyMessageSanitizerHook } from "./empty-message-sanitizer";
|
|
|
23
23
|
export { createThinkingBlockValidatorHook } from "./thinking-block-validator";
|
|
24
24
|
export { createRalphLoopHook, type RalphLoopHook } from "./ralph-loop";
|
|
25
25
|
export { createAutoSlashCommandHook } from "./auto-slash-command";
|
|
26
|
+
export { createEditErrorRecoveryHook } from "./edit-error-recovery";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -8557,6 +8557,7 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
8557
8557
|
- Do not stop until all tasks are done`;
|
|
8558
8558
|
var COUNTDOWN_SECONDS = 2;
|
|
8559
8559
|
var TOAST_DURATION_MS = 900;
|
|
8560
|
+
var COUNTDOWN_GRACE_PERIOD_MS = 500;
|
|
8560
8561
|
function getMessageDir(sessionID) {
|
|
8561
8562
|
if (!existsSync2(MESSAGE_STORAGE))
|
|
8562
8563
|
return null;
|
|
@@ -8616,6 +8617,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8616
8617
|
clearInterval(state2.countdownInterval);
|
|
8617
8618
|
state2.countdownInterval = undefined;
|
|
8618
8619
|
}
|
|
8620
|
+
state2.countdownStartedAt = undefined;
|
|
8619
8621
|
}
|
|
8620
8622
|
function cleanup(sessionID) {
|
|
8621
8623
|
cancelCountdown(sessionID);
|
|
@@ -8683,12 +8685,14 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8683
8685
|
const prompt = `${CONTINUATION_PROMPT}
|
|
8684
8686
|
|
|
8685
8687
|
[Status: ${todos.length - freshIncompleteCount}/${todos.length} completed, ${freshIncompleteCount} remaining]`;
|
|
8688
|
+
const modelField = prevMessage?.model?.providerID && prevMessage?.model?.modelID ? { providerID: prevMessage.model.providerID, modelID: prevMessage.model.modelID } : undefined;
|
|
8686
8689
|
try {
|
|
8687
|
-
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: prevMessage?.agent, incompleteCount: freshIncompleteCount });
|
|
8690
|
+
log(`[${HOOK_NAME}] Injecting continuation`, { sessionID, agent: prevMessage?.agent, model: modelField, incompleteCount: freshIncompleteCount });
|
|
8688
8691
|
await ctx.client.session.prompt({
|
|
8689
8692
|
path: { id: sessionID },
|
|
8690
8693
|
body: {
|
|
8691
8694
|
agent: prevMessage?.agent,
|
|
8695
|
+
model: modelField,
|
|
8692
8696
|
parts: [{ type: "text", text: prompt }]
|
|
8693
8697
|
},
|
|
8694
8698
|
query: { directory: ctx.directory }
|
|
@@ -8703,6 +8707,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8703
8707
|
cancelCountdown(sessionID);
|
|
8704
8708
|
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
8705
8709
|
showCountdownToast(secondsRemaining, incompleteCount);
|
|
8710
|
+
state2.countdownStartedAt = Date.now();
|
|
8706
8711
|
state2.countdownInterval = setInterval(() => {
|
|
8707
8712
|
secondsRemaining--;
|
|
8708
8713
|
if (secondsRemaining > 0) {
|
|
@@ -8786,6 +8791,13 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8786
8791
|
state2.lastEventWasAbortError = false;
|
|
8787
8792
|
}
|
|
8788
8793
|
if (role === "user") {
|
|
8794
|
+
if (state2?.countdownStartedAt) {
|
|
8795
|
+
const elapsed = Date.now() - state2.countdownStartedAt;
|
|
8796
|
+
if (elapsed < COUNTDOWN_GRACE_PERIOD_MS) {
|
|
8797
|
+
log(`[${HOOK_NAME}] Ignoring user message in grace period`, { sessionID, elapsed });
|
|
8798
|
+
return;
|
|
8799
|
+
}
|
|
8800
|
+
}
|
|
8789
8801
|
cancelCountdown(sessionID);
|
|
8790
8802
|
log(`[${HOOK_NAME}] User message: cleared abort state`, { sessionID });
|
|
8791
8803
|
}
|
|
@@ -10726,9 +10738,19 @@ var TOKEN_LIMIT_KEYWORDS = [
|
|
|
10726
10738
|
"token limit",
|
|
10727
10739
|
"context length",
|
|
10728
10740
|
"too many tokens",
|
|
10729
|
-
"non-empty content"
|
|
10730
|
-
|
|
10741
|
+
"non-empty content"
|
|
10742
|
+
];
|
|
10743
|
+
var THINKING_BLOCK_ERROR_PATTERNS = [
|
|
10744
|
+
/thinking.*first block/i,
|
|
10745
|
+
/first block.*thinking/i,
|
|
10746
|
+
/must.*start.*thinking/i,
|
|
10747
|
+
/thinking.*redacted_thinking/i,
|
|
10748
|
+
/expected.*thinking.*found/i,
|
|
10749
|
+
/thinking.*disabled.*cannot.*contain/i
|
|
10731
10750
|
];
|
|
10751
|
+
function isThinkingBlockError(text) {
|
|
10752
|
+
return THINKING_BLOCK_ERROR_PATTERNS.some((pattern) => pattern.test(text));
|
|
10753
|
+
}
|
|
10732
10754
|
var MESSAGE_INDEX_PATTERN = /messages\.(\d+)/;
|
|
10733
10755
|
function extractTokensFromMessage(message) {
|
|
10734
10756
|
for (const pattern of TOKEN_LIMIT_PATTERNS) {
|
|
@@ -10749,6 +10771,9 @@ function extractMessageIndex2(text) {
|
|
|
10749
10771
|
return;
|
|
10750
10772
|
}
|
|
10751
10773
|
function isTokenLimitError(text) {
|
|
10774
|
+
if (isThinkingBlockError(text)) {
|
|
10775
|
+
return false;
|
|
10776
|
+
}
|
|
10752
10777
|
const lower = text.toLowerCase();
|
|
10753
10778
|
return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
|
|
10754
10779
|
}
|
|
@@ -11829,7 +11854,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
11829
11854
|
setTimeout(async () => {
|
|
11830
11855
|
try {
|
|
11831
11856
|
await client.session.prompt_async({
|
|
11832
|
-
path: { sessionID },
|
|
11857
|
+
path: { id: sessionID },
|
|
11833
11858
|
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
11834
11859
|
query: { directory }
|
|
11835
11860
|
});
|
|
@@ -11895,7 +11920,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
11895
11920
|
setTimeout(async () => {
|
|
11896
11921
|
try {
|
|
11897
11922
|
await client.session.prompt_async({
|
|
11898
|
-
path: { sessionID },
|
|
11923
|
+
path: { id: sessionID },
|
|
11899
11924
|
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
11900
11925
|
query: { directory }
|
|
11901
11926
|
});
|
|
@@ -17381,10 +17406,6 @@ function createClaudeCodeHooksHook(ctx, config = {}) {
|
|
|
17381
17406
|
} catch {}
|
|
17382
17407
|
const isFirstMessage = !sessionFirstMessageProcessed.has(input.sessionID);
|
|
17383
17408
|
sessionFirstMessageProcessed.add(input.sessionID);
|
|
17384
|
-
if (isFirstMessage) {
|
|
17385
|
-
log("Skipping UserPromptSubmit hooks on first message for title generation", { sessionID: input.sessionID });
|
|
17386
|
-
return;
|
|
17387
|
-
}
|
|
17388
17409
|
if (!isHookDisabled(config, "UserPromptSubmit")) {
|
|
17389
17410
|
const userPromptCtx = {
|
|
17390
17411
|
sessionId: input.sessionID,
|
|
@@ -17406,17 +17427,27 @@ function createClaudeCodeHooksHook(ctx, config = {}) {
|
|
|
17406
17427
|
const hookContent = result.messages.join(`
|
|
17407
17428
|
|
|
17408
17429
|
`);
|
|
17409
|
-
log(`[claude-code-hooks] Injecting ${result.messages.length} hook messages`, { sessionID: input.sessionID, contentLength: hookContent.length });
|
|
17410
|
-
|
|
17411
|
-
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17430
|
+
log(`[claude-code-hooks] Injecting ${result.messages.length} hook messages`, { sessionID: input.sessionID, contentLength: hookContent.length, isFirstMessage });
|
|
17431
|
+
if (isFirstMessage) {
|
|
17432
|
+
const idx = output.parts.findIndex((p) => p.type === "text" && p.text);
|
|
17433
|
+
if (idx >= 0) {
|
|
17434
|
+
output.parts[idx].text = `${hookContent}
|
|
17435
|
+
|
|
17436
|
+
${output.parts[idx].text ?? ""}`;
|
|
17437
|
+
log("UserPromptSubmit hooks prepended to first message parts directly", { sessionID: input.sessionID });
|
|
17438
|
+
}
|
|
17439
|
+
} else {
|
|
17440
|
+
const message = output.message;
|
|
17441
|
+
const success = injectHookMessage(input.sessionID, hookContent, {
|
|
17442
|
+
agent: message.agent,
|
|
17443
|
+
model: message.model,
|
|
17444
|
+
path: message.path ?? { cwd: ctx.directory, root: "/" },
|
|
17445
|
+
tools: message.tools
|
|
17446
|
+
});
|
|
17447
|
+
log(success ? "Hook message injected via file system" : "File injection failed", {
|
|
17448
|
+
sessionID: input.sessionID
|
|
17449
|
+
});
|
|
17450
|
+
}
|
|
17420
17451
|
}
|
|
17421
17452
|
}
|
|
17422
17453
|
},
|
|
@@ -19018,6 +19049,17 @@ function detectBannedCommand(command) {
|
|
|
19018
19049
|
}
|
|
19019
19050
|
return;
|
|
19020
19051
|
}
|
|
19052
|
+
function shellEscape(value) {
|
|
19053
|
+
if (value === "")
|
|
19054
|
+
return "''";
|
|
19055
|
+
if (/[^a-zA-Z0-9_\-.:\/]/.test(value)) {
|
|
19056
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
19057
|
+
}
|
|
19058
|
+
return value;
|
|
19059
|
+
}
|
|
19060
|
+
function buildEnvPrefix(env) {
|
|
19061
|
+
return Object.entries(env).map(([key, value]) => `${key}=${shellEscape(value)}`).join(" \\\n");
|
|
19062
|
+
}
|
|
19021
19063
|
function createNonInteractiveEnvHook(_ctx) {
|
|
19022
19064
|
return {
|
|
19023
19065
|
"tool.execute.before": async (input, output) => {
|
|
@@ -19028,18 +19070,19 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
19028
19070
|
if (!command) {
|
|
19029
19071
|
return;
|
|
19030
19072
|
}
|
|
19031
|
-
output.args.env = {
|
|
19032
|
-
...process.env,
|
|
19033
|
-
...output.args.env,
|
|
19034
|
-
...NON_INTERACTIVE_ENV
|
|
19035
|
-
};
|
|
19036
19073
|
const bannedCmd = detectBannedCommand(command);
|
|
19037
19074
|
if (bannedCmd) {
|
|
19038
19075
|
output.message = `\u26A0\uFE0F Warning: '${bannedCmd}' is an interactive command that may hang in non-interactive environments.`;
|
|
19039
19076
|
}
|
|
19040
|
-
|
|
19077
|
+
const isGitCommand = /\bgit\b/.test(command);
|
|
19078
|
+
if (!isGitCommand) {
|
|
19079
|
+
return;
|
|
19080
|
+
}
|
|
19081
|
+
const envPrefix = buildEnvPrefix(NON_INTERACTIVE_ENV);
|
|
19082
|
+
output.args.command = `${envPrefix} ${command}`;
|
|
19083
|
+
log(`[${HOOK_NAME2}] Prepended non-interactive env vars to git command`, {
|
|
19041
19084
|
sessionID: input.sessionID,
|
|
19042
|
-
|
|
19085
|
+
envPrefix
|
|
19043
19086
|
});
|
|
19044
19087
|
}
|
|
19045
19088
|
};
|
|
@@ -19807,7 +19850,7 @@ function extractPromptText3(parts) {
|
|
|
19807
19850
|
|
|
19808
19851
|
// src/hooks/auto-slash-command/executor.ts
|
|
19809
19852
|
import { existsSync as existsSync35, readdirSync as readdirSync12, readFileSync as readFileSync24 } from "fs";
|
|
19810
|
-
import { join as join43, basename as basename2, dirname as
|
|
19853
|
+
import { join as join43, basename as basename2, dirname as dirname8 } from "path";
|
|
19811
19854
|
import { homedir as homedir13 } from "os";
|
|
19812
19855
|
// src/features/opencode-skill-loader/loader.ts
|
|
19813
19856
|
import { existsSync as existsSync33, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
@@ -20009,7 +20052,7 @@ function discoverOpencodeProjectSkills() {
|
|
|
20009
20052
|
}
|
|
20010
20053
|
// src/features/opencode-skill-loader/merger.ts
|
|
20011
20054
|
import { readFileSync as readFileSync23, existsSync as existsSync34 } from "fs";
|
|
20012
|
-
import { dirname as
|
|
20055
|
+
import { dirname as dirname7, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
|
|
20013
20056
|
import { homedir as homedir12 } from "os";
|
|
20014
20057
|
var SCOPE_PRIORITY = {
|
|
20015
20058
|
builtin: 1,
|
|
@@ -20082,7 +20125,7 @@ function configEntryToLoaded(name, entry, configDir) {
|
|
|
20082
20125
|
return null;
|
|
20083
20126
|
}
|
|
20084
20127
|
const description = entry.description || fileMetadata.description || "";
|
|
20085
|
-
const resolvedPath = entry.from ?
|
|
20128
|
+
const resolvedPath = entry.from ? dirname7(resolveFilePath2(entry.from, configDir)) : configDir || process.cwd();
|
|
20086
20129
|
const wrappedTemplate = `<skill-instruction>
|
|
20087
20130
|
Base directory for this skill: ${resolvedPath}/
|
|
20088
20131
|
File references (@path) in this skill are relative to this directory.
|
|
@@ -20312,7 +20355,7 @@ async function formatCommandTemplate(cmd, args) {
|
|
|
20312
20355
|
`);
|
|
20313
20356
|
sections.push(`## Command Instructions
|
|
20314
20357
|
`);
|
|
20315
|
-
const commandDir = cmd.path ?
|
|
20358
|
+
const commandDir = cmd.path ? dirname8(cmd.path) : process.cwd();
|
|
20316
20359
|
const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
|
|
20317
20360
|
const resolvedContent = await resolveCommandsInText(withFileRefs);
|
|
20318
20361
|
sections.push(resolvedContent.trim());
|
|
@@ -20403,6 +20446,38 @@ ${AUTO_SLASH_COMMAND_TAG_CLOSE}`;
|
|
|
20403
20446
|
}
|
|
20404
20447
|
};
|
|
20405
20448
|
}
|
|
20449
|
+
// src/hooks/edit-error-recovery/index.ts
|
|
20450
|
+
var EDIT_ERROR_PATTERNS = [
|
|
20451
|
+
"oldString and newString must be different",
|
|
20452
|
+
"oldString not found",
|
|
20453
|
+
"oldString found multiple times"
|
|
20454
|
+
];
|
|
20455
|
+
var EDIT_ERROR_REMINDER = `
|
|
20456
|
+
[EDIT ERROR - IMMEDIATE ACTION REQUIRED]
|
|
20457
|
+
|
|
20458
|
+
You made an Edit mistake. STOP and do this NOW:
|
|
20459
|
+
|
|
20460
|
+
1. READ the file immediately to see its ACTUAL current state
|
|
20461
|
+
2. VERIFY what the content really looks like (your assumption was wrong)
|
|
20462
|
+
3. APOLOGIZE briefly to the user for the error
|
|
20463
|
+
4. CONTINUE with corrected action based on the real file content
|
|
20464
|
+
|
|
20465
|
+
DO NOT attempt another edit until you've read and verified the file state.
|
|
20466
|
+
`;
|
|
20467
|
+
function createEditErrorRecoveryHook(_ctx) {
|
|
20468
|
+
return {
|
|
20469
|
+
"tool.execute.after": async (input, output) => {
|
|
20470
|
+
if (input.tool.toLowerCase() !== "edit")
|
|
20471
|
+
return;
|
|
20472
|
+
const outputLower = output.output.toLowerCase();
|
|
20473
|
+
const hasEditError = EDIT_ERROR_PATTERNS.some((pattern) => outputLower.includes(pattern.toLowerCase()));
|
|
20474
|
+
if (hasEditError) {
|
|
20475
|
+
output.output += `
|
|
20476
|
+
${EDIT_ERROR_REMINDER}`;
|
|
20477
|
+
}
|
|
20478
|
+
}
|
|
20479
|
+
};
|
|
20480
|
+
}
|
|
20406
20481
|
// src/auth/antigravity/constants.ts
|
|
20407
20482
|
var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
20408
20483
|
var ANTIGRAVITY_CLIENT_SECRET = "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";
|
|
@@ -36437,7 +36512,7 @@ var lsp_code_action_resolve = tool({
|
|
|
36437
36512
|
});
|
|
36438
36513
|
// src/tools/ast-grep/constants.ts
|
|
36439
36514
|
import { createRequire as createRequire4 } from "module";
|
|
36440
|
-
import { dirname as
|
|
36515
|
+
import { dirname as dirname9, join as join47 } from "path";
|
|
36441
36516
|
import { existsSync as existsSync40, statSync as statSync4 } from "fs";
|
|
36442
36517
|
|
|
36443
36518
|
// src/tools/ast-grep/downloader.ts
|
|
@@ -36580,7 +36655,7 @@ function findSgCliPathSync() {
|
|
|
36580
36655
|
try {
|
|
36581
36656
|
const require2 = createRequire4(import.meta.url);
|
|
36582
36657
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
36583
|
-
const cliDir =
|
|
36658
|
+
const cliDir = dirname9(cliPkgPath);
|
|
36584
36659
|
const sgPath = join47(cliDir, binaryName);
|
|
36585
36660
|
if (existsSync40(sgPath) && isValidBinary(sgPath)) {
|
|
36586
36661
|
return sgPath;
|
|
@@ -36591,7 +36666,7 @@ function findSgCliPathSync() {
|
|
|
36591
36666
|
try {
|
|
36592
36667
|
const require2 = createRequire4(import.meta.url);
|
|
36593
36668
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
36594
|
-
const pkgDir =
|
|
36669
|
+
const pkgDir = dirname9(pkgPath);
|
|
36595
36670
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
36596
36671
|
const binaryPath = join47(pkgDir, astGrepName);
|
|
36597
36672
|
if (existsSync40(binaryPath) && isValidBinary(binaryPath)) {
|
|
@@ -36968,7 +37043,7 @@ var {spawn: spawn9 } = globalThis.Bun;
|
|
|
36968
37043
|
|
|
36969
37044
|
// src/tools/grep/constants.ts
|
|
36970
37045
|
import { existsSync as existsSync43 } from "fs";
|
|
36971
|
-
import { join as join49, dirname as
|
|
37046
|
+
import { join as join49, dirname as dirname10 } from "path";
|
|
36972
37047
|
import { spawnSync } from "child_process";
|
|
36973
37048
|
|
|
36974
37049
|
// src/tools/grep/downloader.ts
|
|
@@ -37133,7 +37208,7 @@ function findExecutable(name) {
|
|
|
37133
37208
|
}
|
|
37134
37209
|
function getOpenCodeBundledRg() {
|
|
37135
37210
|
const execPath = process.execPath;
|
|
37136
|
-
const execDir =
|
|
37211
|
+
const execDir = dirname10(execPath);
|
|
37137
37212
|
const isWindows2 = process.platform === "win32";
|
|
37138
37213
|
const rgName = isWindows2 ? "rg.exe" : "rg";
|
|
37139
37214
|
const candidates = [
|
|
@@ -37598,7 +37673,7 @@ var glob = tool({
|
|
|
37598
37673
|
});
|
|
37599
37674
|
// src/tools/slashcommand/tools.ts
|
|
37600
37675
|
import { existsSync as existsSync44, readdirSync as readdirSync14, readFileSync as readFileSync29 } from "fs";
|
|
37601
|
-
import { join as join50, basename as basename3, dirname as
|
|
37676
|
+
import { join as join50, basename as basename3, dirname as dirname11 } from "path";
|
|
37602
37677
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
37603
37678
|
if (!existsSync44(commandsDir)) {
|
|
37604
37679
|
return [];
|
|
@@ -37704,7 +37779,7 @@ async function formatLoadedCommand(cmd) {
|
|
|
37704
37779
|
`);
|
|
37705
37780
|
sections.push(`## Command Instructions
|
|
37706
37781
|
`);
|
|
37707
|
-
const commandDir = cmd.path ?
|
|
37782
|
+
const commandDir = cmd.path ? dirname11(cmd.path) : process.cwd();
|
|
37708
37783
|
const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
|
|
37709
37784
|
const resolvedContent = await resolveCommandsInText(withFileRefs);
|
|
37710
37785
|
sections.push(resolvedContent.trim());
|
|
@@ -38490,7 +38565,7 @@ var TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a s
|
|
|
38490
38565
|
Skills provide specialized knowledge and step-by-step guidance.
|
|
38491
38566
|
Use this when a task matches an available skill's description.`;
|
|
38492
38567
|
// src/tools/skill/tools.ts
|
|
38493
|
-
import { dirname as
|
|
38568
|
+
import { dirname as dirname12 } from "path";
|
|
38494
38569
|
import { readFileSync as readFileSync30 } from "fs";
|
|
38495
38570
|
function loadedSkillToInfo(skill) {
|
|
38496
38571
|
return {
|
|
@@ -38612,7 +38687,7 @@ function createSkillTool(options = {}) {
|
|
|
38612
38687
|
throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`);
|
|
38613
38688
|
}
|
|
38614
38689
|
const body = extractSkillBody(skill);
|
|
38615
|
-
const dir = skill.path ?
|
|
38690
|
+
const dir = skill.path ? dirname12(skill.path) : skill.resolvedPath || process.cwd();
|
|
38616
38691
|
const output = [
|
|
38617
38692
|
`## Skill: ${skill.name}`,
|
|
38618
38693
|
"",
|
|
@@ -39384,6 +39459,7 @@ var builtinTools = {
|
|
|
39384
39459
|
// src/features/background-agent/manager.ts
|
|
39385
39460
|
import { existsSync as existsSync47, readdirSync as readdirSync17 } from "fs";
|
|
39386
39461
|
import { join as join54 } from "path";
|
|
39462
|
+
var TASK_TTL_MS = 30 * 60 * 1000;
|
|
39387
39463
|
function getMessageDir11(sessionID) {
|
|
39388
39464
|
if (!existsSync47(MESSAGE_STORAGE))
|
|
39389
39465
|
return null;
|
|
@@ -39649,11 +39725,11 @@ class BackgroundManager {
|
|
|
39649
39725
|
},
|
|
39650
39726
|
query: { directory: this.directory }
|
|
39651
39727
|
});
|
|
39652
|
-
this.clearNotificationsForTask(taskId);
|
|
39653
39728
|
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
|
|
39654
39729
|
} catch (error45) {
|
|
39655
39730
|
log("[background-agent] prompt failed:", String(error45));
|
|
39656
39731
|
} finally {
|
|
39732
|
+
this.clearNotificationsForTask(taskId);
|
|
39657
39733
|
this.tasks.delete(taskId);
|
|
39658
39734
|
log("[background-agent] Removed completed task from memory:", taskId);
|
|
39659
39735
|
}
|
|
@@ -39678,7 +39754,38 @@ class BackgroundManager {
|
|
|
39678
39754
|
}
|
|
39679
39755
|
return false;
|
|
39680
39756
|
}
|
|
39757
|
+
pruneStaleTasksAndNotifications() {
|
|
39758
|
+
const now = Date.now();
|
|
39759
|
+
for (const [taskId, task] of this.tasks.entries()) {
|
|
39760
|
+
const age = now - task.startedAt.getTime();
|
|
39761
|
+
if (age > TASK_TTL_MS) {
|
|
39762
|
+
log("[background-agent] Pruning stale task:", { taskId, age: Math.round(age / 1000) + "s" });
|
|
39763
|
+
task.status = "error";
|
|
39764
|
+
task.error = "Task timed out after 30 minutes";
|
|
39765
|
+
task.completedAt = new Date;
|
|
39766
|
+
this.clearNotificationsForTask(taskId);
|
|
39767
|
+
this.tasks.delete(taskId);
|
|
39768
|
+
subagentSessions.delete(task.sessionID);
|
|
39769
|
+
}
|
|
39770
|
+
}
|
|
39771
|
+
for (const [sessionID, notifications] of this.notifications.entries()) {
|
|
39772
|
+
if (notifications.length === 0) {
|
|
39773
|
+
this.notifications.delete(sessionID);
|
|
39774
|
+
continue;
|
|
39775
|
+
}
|
|
39776
|
+
const validNotifications = notifications.filter((task) => {
|
|
39777
|
+
const age = now - task.startedAt.getTime();
|
|
39778
|
+
return age <= TASK_TTL_MS;
|
|
39779
|
+
});
|
|
39780
|
+
if (validNotifications.length === 0) {
|
|
39781
|
+
this.notifications.delete(sessionID);
|
|
39782
|
+
} else if (validNotifications.length !== notifications.length) {
|
|
39783
|
+
this.notifications.set(sessionID, validNotifications);
|
|
39784
|
+
}
|
|
39785
|
+
}
|
|
39786
|
+
}
|
|
39681
39787
|
async pollRunningTasks() {
|
|
39788
|
+
this.pruneStaleTasksAndNotifications();
|
|
39682
39789
|
const statusResult = await this.client.session.status();
|
|
39683
39790
|
const allStatuses = statusResult.data ?? {};
|
|
39684
39791
|
for (const task of this.tasks.values()) {
|
|
@@ -42490,7 +42597,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
42490
42597
|
"preemptive-compaction",
|
|
42491
42598
|
"compaction-context-injector",
|
|
42492
42599
|
"claude-code-hooks",
|
|
42493
|
-
"auto-slash-command"
|
|
42600
|
+
"auto-slash-command",
|
|
42601
|
+
"edit-error-recovery"
|
|
42494
42602
|
]);
|
|
42495
42603
|
var BuiltinCommandNameSchema = exports_external.enum([
|
|
42496
42604
|
"init-deep"
|
|
@@ -45625,6 +45733,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45625
45733
|
const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
|
|
45626
45734
|
const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, { config: pluginConfig.ralph_loop }) : null;
|
|
45627
45735
|
const autoSlashCommand = isHookEnabled("auto-slash-command") ? createAutoSlashCommandHook() : null;
|
|
45736
|
+
const editErrorRecovery = isHookEnabled("edit-error-recovery") ? createEditErrorRecoveryHook(ctx) : null;
|
|
45628
45737
|
const backgroundManager = new BackgroundManager(ctx);
|
|
45629
45738
|
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
|
|
45630
45739
|
if (sessionRecovery && todoContinuationEnforcer) {
|
|
@@ -45819,6 +45928,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45819
45928
|
await emptyTaskResponseDetector?.["tool.execute.after"](input, output);
|
|
45820
45929
|
await agentUsageReminder?.["tool.execute.after"](input, output);
|
|
45821
45930
|
await interactiveBashSession?.["tool.execute.after"](input, output);
|
|
45931
|
+
await editErrorRecovery?.["tool.execute.after"](input, output);
|
|
45822
45932
|
}
|
|
45823
45933
|
};
|
|
45824
45934
|
};
|
|
@@ -2,4 +2,4 @@ import type { AnalyzeResult, SgResult } from "./types";
|
|
|
2
2
|
export declare function formatSearchResult(result: SgResult): string;
|
|
3
3
|
export declare function formatReplaceResult(result: SgResult, isDryRun: boolean): string;
|
|
4
4
|
export declare function formatAnalyzeResult(results: AnalyzeResult[], extractedMetaVars: boolean): string;
|
|
5
|
-
export declare function formatTransformResult(
|
|
5
|
+
export declare function formatTransformResult(_original: string, transformed: string, editCount: number): string;
|