oh-my-opencode 2.12.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 +113 -24
- 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);
|
|
@@ -8705,6 +8707,7 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8705
8707
|
cancelCountdown(sessionID);
|
|
8706
8708
|
let secondsRemaining = COUNTDOWN_SECONDS;
|
|
8707
8709
|
showCountdownToast(secondsRemaining, incompleteCount);
|
|
8710
|
+
state2.countdownStartedAt = Date.now();
|
|
8708
8711
|
state2.countdownInterval = setInterval(() => {
|
|
8709
8712
|
secondsRemaining--;
|
|
8710
8713
|
if (secondsRemaining > 0) {
|
|
@@ -8788,6 +8791,13 @@ function createTodoContinuationEnforcer(ctx, options = {}) {
|
|
|
8788
8791
|
state2.lastEventWasAbortError = false;
|
|
8789
8792
|
}
|
|
8790
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
|
+
}
|
|
8791
8801
|
cancelCountdown(sessionID);
|
|
8792
8802
|
log(`[${HOOK_NAME}] User message: cleared abort state`, { sessionID });
|
|
8793
8803
|
}
|
|
@@ -11844,7 +11854,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
11844
11854
|
setTimeout(async () => {
|
|
11845
11855
|
try {
|
|
11846
11856
|
await client.session.prompt_async({
|
|
11847
|
-
path: { sessionID },
|
|
11857
|
+
path: { id: sessionID },
|
|
11848
11858
|
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
11849
11859
|
query: { directory }
|
|
11850
11860
|
});
|
|
@@ -11910,7 +11920,7 @@ async function executeCompact(sessionID, msg, autoCompactState, client, director
|
|
|
11910
11920
|
setTimeout(async () => {
|
|
11911
11921
|
try {
|
|
11912
11922
|
await client.session.prompt_async({
|
|
11913
|
-
path: { sessionID },
|
|
11923
|
+
path: { id: sessionID },
|
|
11914
11924
|
body: { parts: [{ type: "text", text: "Continue" }] },
|
|
11915
11925
|
query: { directory }
|
|
11916
11926
|
});
|
|
@@ -19039,6 +19049,17 @@ function detectBannedCommand(command) {
|
|
|
19039
19049
|
}
|
|
19040
19050
|
return;
|
|
19041
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
|
+
}
|
|
19042
19063
|
function createNonInteractiveEnvHook(_ctx) {
|
|
19043
19064
|
return {
|
|
19044
19065
|
"tool.execute.before": async (input, output) => {
|
|
@@ -19049,18 +19070,19 @@ function createNonInteractiveEnvHook(_ctx) {
|
|
|
19049
19070
|
if (!command) {
|
|
19050
19071
|
return;
|
|
19051
19072
|
}
|
|
19052
|
-
output.args.env = {
|
|
19053
|
-
...process.env,
|
|
19054
|
-
...output.args.env,
|
|
19055
|
-
...NON_INTERACTIVE_ENV
|
|
19056
|
-
};
|
|
19057
19073
|
const bannedCmd = detectBannedCommand(command);
|
|
19058
19074
|
if (bannedCmd) {
|
|
19059
19075
|
output.message = `\u26A0\uFE0F Warning: '${bannedCmd}' is an interactive command that may hang in non-interactive environments.`;
|
|
19060
19076
|
}
|
|
19061
|
-
|
|
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`, {
|
|
19062
19084
|
sessionID: input.sessionID,
|
|
19063
|
-
|
|
19085
|
+
envPrefix
|
|
19064
19086
|
});
|
|
19065
19087
|
}
|
|
19066
19088
|
};
|
|
@@ -19828,7 +19850,7 @@ function extractPromptText3(parts) {
|
|
|
19828
19850
|
|
|
19829
19851
|
// src/hooks/auto-slash-command/executor.ts
|
|
19830
19852
|
import { existsSync as existsSync35, readdirSync as readdirSync12, readFileSync as readFileSync24 } from "fs";
|
|
19831
|
-
import { join as join43, basename as basename2, dirname as
|
|
19853
|
+
import { join as join43, basename as basename2, dirname as dirname8 } from "path";
|
|
19832
19854
|
import { homedir as homedir13 } from "os";
|
|
19833
19855
|
// src/features/opencode-skill-loader/loader.ts
|
|
19834
19856
|
import { existsSync as existsSync33, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
@@ -20030,7 +20052,7 @@ function discoverOpencodeProjectSkills() {
|
|
|
20030
20052
|
}
|
|
20031
20053
|
// src/features/opencode-skill-loader/merger.ts
|
|
20032
20054
|
import { readFileSync as readFileSync23, existsSync as existsSync34 } from "fs";
|
|
20033
|
-
import { dirname as
|
|
20055
|
+
import { dirname as dirname7, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
|
|
20034
20056
|
import { homedir as homedir12 } from "os";
|
|
20035
20057
|
var SCOPE_PRIORITY = {
|
|
20036
20058
|
builtin: 1,
|
|
@@ -20103,7 +20125,7 @@ function configEntryToLoaded(name, entry, configDir) {
|
|
|
20103
20125
|
return null;
|
|
20104
20126
|
}
|
|
20105
20127
|
const description = entry.description || fileMetadata.description || "";
|
|
20106
|
-
const resolvedPath = entry.from ?
|
|
20128
|
+
const resolvedPath = entry.from ? dirname7(resolveFilePath2(entry.from, configDir)) : configDir || process.cwd();
|
|
20107
20129
|
const wrappedTemplate = `<skill-instruction>
|
|
20108
20130
|
Base directory for this skill: ${resolvedPath}/
|
|
20109
20131
|
File references (@path) in this skill are relative to this directory.
|
|
@@ -20333,7 +20355,7 @@ async function formatCommandTemplate(cmd, args) {
|
|
|
20333
20355
|
`);
|
|
20334
20356
|
sections.push(`## Command Instructions
|
|
20335
20357
|
`);
|
|
20336
|
-
const commandDir = cmd.path ?
|
|
20358
|
+
const commandDir = cmd.path ? dirname8(cmd.path) : process.cwd();
|
|
20337
20359
|
const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
|
|
20338
20360
|
const resolvedContent = await resolveCommandsInText(withFileRefs);
|
|
20339
20361
|
sections.push(resolvedContent.trim());
|
|
@@ -20424,6 +20446,38 @@ ${AUTO_SLASH_COMMAND_TAG_CLOSE}`;
|
|
|
20424
20446
|
}
|
|
20425
20447
|
};
|
|
20426
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
|
+
}
|
|
20427
20481
|
// src/auth/antigravity/constants.ts
|
|
20428
20482
|
var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
|
|
20429
20483
|
var ANTIGRAVITY_CLIENT_SECRET = "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";
|
|
@@ -36458,7 +36512,7 @@ var lsp_code_action_resolve = tool({
|
|
|
36458
36512
|
});
|
|
36459
36513
|
// src/tools/ast-grep/constants.ts
|
|
36460
36514
|
import { createRequire as createRequire4 } from "module";
|
|
36461
|
-
import { dirname as
|
|
36515
|
+
import { dirname as dirname9, join as join47 } from "path";
|
|
36462
36516
|
import { existsSync as existsSync40, statSync as statSync4 } from "fs";
|
|
36463
36517
|
|
|
36464
36518
|
// src/tools/ast-grep/downloader.ts
|
|
@@ -36601,7 +36655,7 @@ function findSgCliPathSync() {
|
|
|
36601
36655
|
try {
|
|
36602
36656
|
const require2 = createRequire4(import.meta.url);
|
|
36603
36657
|
const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
|
|
36604
|
-
const cliDir =
|
|
36658
|
+
const cliDir = dirname9(cliPkgPath);
|
|
36605
36659
|
const sgPath = join47(cliDir, binaryName);
|
|
36606
36660
|
if (existsSync40(sgPath) && isValidBinary(sgPath)) {
|
|
36607
36661
|
return sgPath;
|
|
@@ -36612,7 +36666,7 @@ function findSgCliPathSync() {
|
|
|
36612
36666
|
try {
|
|
36613
36667
|
const require2 = createRequire4(import.meta.url);
|
|
36614
36668
|
const pkgPath = require2.resolve(`${platformPkg}/package.json`);
|
|
36615
|
-
const pkgDir =
|
|
36669
|
+
const pkgDir = dirname9(pkgPath);
|
|
36616
36670
|
const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
|
|
36617
36671
|
const binaryPath = join47(pkgDir, astGrepName);
|
|
36618
36672
|
if (existsSync40(binaryPath) && isValidBinary(binaryPath)) {
|
|
@@ -36989,7 +37043,7 @@ var {spawn: spawn9 } = globalThis.Bun;
|
|
|
36989
37043
|
|
|
36990
37044
|
// src/tools/grep/constants.ts
|
|
36991
37045
|
import { existsSync as existsSync43 } from "fs";
|
|
36992
|
-
import { join as join49, dirname as
|
|
37046
|
+
import { join as join49, dirname as dirname10 } from "path";
|
|
36993
37047
|
import { spawnSync } from "child_process";
|
|
36994
37048
|
|
|
36995
37049
|
// src/tools/grep/downloader.ts
|
|
@@ -37154,7 +37208,7 @@ function findExecutable(name) {
|
|
|
37154
37208
|
}
|
|
37155
37209
|
function getOpenCodeBundledRg() {
|
|
37156
37210
|
const execPath = process.execPath;
|
|
37157
|
-
const execDir =
|
|
37211
|
+
const execDir = dirname10(execPath);
|
|
37158
37212
|
const isWindows2 = process.platform === "win32";
|
|
37159
37213
|
const rgName = isWindows2 ? "rg.exe" : "rg";
|
|
37160
37214
|
const candidates = [
|
|
@@ -37619,7 +37673,7 @@ var glob = tool({
|
|
|
37619
37673
|
});
|
|
37620
37674
|
// src/tools/slashcommand/tools.ts
|
|
37621
37675
|
import { existsSync as existsSync44, readdirSync as readdirSync14, readFileSync as readFileSync29 } from "fs";
|
|
37622
|
-
import { join as join50, basename as basename3, dirname as
|
|
37676
|
+
import { join as join50, basename as basename3, dirname as dirname11 } from "path";
|
|
37623
37677
|
function discoverCommandsFromDir2(commandsDir, scope) {
|
|
37624
37678
|
if (!existsSync44(commandsDir)) {
|
|
37625
37679
|
return [];
|
|
@@ -37725,7 +37779,7 @@ async function formatLoadedCommand(cmd) {
|
|
|
37725
37779
|
`);
|
|
37726
37780
|
sections.push(`## Command Instructions
|
|
37727
37781
|
`);
|
|
37728
|
-
const commandDir = cmd.path ?
|
|
37782
|
+
const commandDir = cmd.path ? dirname11(cmd.path) : process.cwd();
|
|
37729
37783
|
const withFileRefs = await resolveFileReferencesInText(cmd.content || "", commandDir);
|
|
37730
37784
|
const resolvedContent = await resolveCommandsInText(withFileRefs);
|
|
37731
37785
|
sections.push(resolvedContent.trim());
|
|
@@ -38511,7 +38565,7 @@ var TOOL_DESCRIPTION_PREFIX = `Load a skill to get detailed instructions for a s
|
|
|
38511
38565
|
Skills provide specialized knowledge and step-by-step guidance.
|
|
38512
38566
|
Use this when a task matches an available skill's description.`;
|
|
38513
38567
|
// src/tools/skill/tools.ts
|
|
38514
|
-
import { dirname as
|
|
38568
|
+
import { dirname as dirname12 } from "path";
|
|
38515
38569
|
import { readFileSync as readFileSync30 } from "fs";
|
|
38516
38570
|
function loadedSkillToInfo(skill) {
|
|
38517
38571
|
return {
|
|
@@ -38633,7 +38687,7 @@ function createSkillTool(options = {}) {
|
|
|
38633
38687
|
throw new Error(`Skill "${args.name}" not found. Available skills: ${available || "none"}`);
|
|
38634
38688
|
}
|
|
38635
38689
|
const body = extractSkillBody(skill);
|
|
38636
|
-
const dir = skill.path ?
|
|
38690
|
+
const dir = skill.path ? dirname12(skill.path) : skill.resolvedPath || process.cwd();
|
|
38637
38691
|
const output = [
|
|
38638
38692
|
`## Skill: ${skill.name}`,
|
|
38639
38693
|
"",
|
|
@@ -39405,6 +39459,7 @@ var builtinTools = {
|
|
|
39405
39459
|
// src/features/background-agent/manager.ts
|
|
39406
39460
|
import { existsSync as existsSync47, readdirSync as readdirSync17 } from "fs";
|
|
39407
39461
|
import { join as join54 } from "path";
|
|
39462
|
+
var TASK_TTL_MS = 30 * 60 * 1000;
|
|
39408
39463
|
function getMessageDir11(sessionID) {
|
|
39409
39464
|
if (!existsSync47(MESSAGE_STORAGE))
|
|
39410
39465
|
return null;
|
|
@@ -39670,11 +39725,11 @@ class BackgroundManager {
|
|
|
39670
39725
|
},
|
|
39671
39726
|
query: { directory: this.directory }
|
|
39672
39727
|
});
|
|
39673
|
-
this.clearNotificationsForTask(taskId);
|
|
39674
39728
|
log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
|
|
39675
39729
|
} catch (error45) {
|
|
39676
39730
|
log("[background-agent] prompt failed:", String(error45));
|
|
39677
39731
|
} finally {
|
|
39732
|
+
this.clearNotificationsForTask(taskId);
|
|
39678
39733
|
this.tasks.delete(taskId);
|
|
39679
39734
|
log("[background-agent] Removed completed task from memory:", taskId);
|
|
39680
39735
|
}
|
|
@@ -39699,7 +39754,38 @@ class BackgroundManager {
|
|
|
39699
39754
|
}
|
|
39700
39755
|
return false;
|
|
39701
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
|
+
}
|
|
39702
39787
|
async pollRunningTasks() {
|
|
39788
|
+
this.pruneStaleTasksAndNotifications();
|
|
39703
39789
|
const statusResult = await this.client.session.status();
|
|
39704
39790
|
const allStatuses = statusResult.data ?? {};
|
|
39705
39791
|
for (const task of this.tasks.values()) {
|
|
@@ -42511,7 +42597,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
42511
42597
|
"preemptive-compaction",
|
|
42512
42598
|
"compaction-context-injector",
|
|
42513
42599
|
"claude-code-hooks",
|
|
42514
|
-
"auto-slash-command"
|
|
42600
|
+
"auto-slash-command",
|
|
42601
|
+
"edit-error-recovery"
|
|
42515
42602
|
]);
|
|
42516
42603
|
var BuiltinCommandNameSchema = exports_external.enum([
|
|
42517
42604
|
"init-deep"
|
|
@@ -45646,6 +45733,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45646
45733
|
const thinkingBlockValidator = isHookEnabled("thinking-block-validator") ? createThinkingBlockValidatorHook() : null;
|
|
45647
45734
|
const ralphLoop = isHookEnabled("ralph-loop") ? createRalphLoopHook(ctx, { config: pluginConfig.ralph_loop }) : null;
|
|
45648
45735
|
const autoSlashCommand = isHookEnabled("auto-slash-command") ? createAutoSlashCommandHook() : null;
|
|
45736
|
+
const editErrorRecovery = isHookEnabled("edit-error-recovery") ? createEditErrorRecoveryHook(ctx) : null;
|
|
45649
45737
|
const backgroundManager = new BackgroundManager(ctx);
|
|
45650
45738
|
const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
|
|
45651
45739
|
if (sessionRecovery && todoContinuationEnforcer) {
|
|
@@ -45840,6 +45928,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
|
|
|
45840
45928
|
await emptyTaskResponseDetector?.["tool.execute.after"](input, output);
|
|
45841
45929
|
await agentUsageReminder?.["tool.execute.after"](input, output);
|
|
45842
45930
|
await interactiveBashSession?.["tool.execute.after"](input, output);
|
|
45931
|
+
await editErrorRecovery?.["tool.execute.after"](input, output);
|
|
45843
45932
|
}
|
|
45844
45933
|
};
|
|
45845
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;
|