oh-my-opencode 2.5.1 → 2.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE.md +82 -0
  2. package/README.ja.md +79 -40
  3. package/README.ko.md +78 -39
  4. package/README.md +79 -40
  5. package/README.zh-cn.md +79 -40
  6. package/dist/agents/build-prompt.d.ts +31 -0
  7. package/dist/agents/document-writer.d.ts +1 -0
  8. package/dist/agents/explore.d.ts +1 -0
  9. package/dist/agents/frontend-ui-ux-engineer.d.ts +1 -0
  10. package/dist/agents/librarian.d.ts +1 -0
  11. package/dist/agents/multimodal-looker.d.ts +1 -0
  12. package/dist/agents/types.d.ts +3 -1
  13. package/dist/cli/config-manager.d.ts +0 -1
  14. package/dist/cli/index.js +1664 -3
  15. package/dist/cli/run/completion.d.ts +2 -0
  16. package/dist/cli/run/completion.test.d.ts +1 -0
  17. package/dist/cli/run/events.d.ts +7 -0
  18. package/dist/cli/run/events.test.d.ts +1 -0
  19. package/dist/cli/run/index.d.ts +2 -0
  20. package/dist/cli/run/runner.d.ts +2 -0
  21. package/dist/cli/run/types.d.ts +45 -0
  22. package/dist/config/schema.d.ts +126 -2
  23. package/dist/hooks/claude-code-hooks/config-loader.d.ts +1 -0
  24. package/dist/hooks/claude-code-hooks/index.d.ts +5 -0
  25. package/dist/hooks/claude-code-hooks/pre-compact.d.ts +16 -0
  26. package/dist/hooks/claude-code-hooks/types.d.ts +17 -1
  27. package/dist/hooks/non-interactive-env/detector.d.ts +1 -0
  28. package/dist/hooks/non-interactive-env/index.d.ts +1 -0
  29. package/dist/hooks/session-notification.test.d.ts +1 -0
  30. package/dist/hooks/todo-continuation-enforcer.d.ts +5 -1
  31. package/dist/index.js +1143 -339
  32. package/dist/shared/data-path.d.ts +15 -0
  33. package/dist/shared/index.d.ts +1 -0
  34. package/dist/tools/index.d.ts +52 -0
  35. package/dist/tools/session-manager/constants.d.ts +11 -0
  36. package/dist/tools/session-manager/index.d.ts +3 -0
  37. package/dist/tools/session-manager/storage.d.ts +8 -0
  38. package/dist/tools/session-manager/storage.test.d.ts +1 -0
  39. package/dist/tools/session-manager/tools.d.ts +52 -0
  40. package/dist/tools/session-manager/tools.test.d.ts +1 -0
  41. package/dist/tools/session-manager/types.d.ts +71 -0
  42. package/dist/tools/session-manager/utils.d.ts +11 -0
  43. package/dist/tools/session-manager/utils.test.d.ts +1 -0
  44. package/package.json +3 -2
  45. package/LICENSE +0 -21
package/dist/index.js CHANGED
@@ -222,8 +222,8 @@ var require_utils = __commonJS((exports) => {
222
222
  }
223
223
  return output;
224
224
  };
225
- exports.basename = (path4, { windows } = {}) => {
226
- const segs = path4.split(windows ? /[\\/]/ : "/");
225
+ exports.basename = (path5, { windows } = {}) => {
226
+ const segs = path5.split(windows ? /[\\/]/ : "/");
227
227
  const last = segs[segs.length - 1];
228
228
  if (last === "") {
229
229
  return segs[segs.length - 2];
@@ -1508,6 +1508,8 @@ Named by [YeonGyu Kim](https://github.com/code-yeongyu).
1508
1508
  ### Key Triggers (check BEFORE classification):
1509
1509
  - External library/source mentioned \u2192 fire \`librarian\` background
1510
1510
  - 2+ modules involved \u2192 fire \`explore\` background
1511
+ - **GitHub mention (@mention in issue/PR)** \u2192 This is a WORK REQUEST. Plan full cycle: investigate \u2192 implement \u2192 create PR
1512
+ - **"Look into" + "create PR"** \u2192 Not just research. Full implementation cycle expected.
1511
1513
 
1512
1514
  ### Step 1: Classify Request Type
1513
1515
 
@@ -1517,6 +1519,7 @@ Named by [YeonGyu Kim](https://github.com/code-yeongyu).
1517
1519
  | **Explicit** | Specific file/line, clear command | Execute directly |
1518
1520
  | **Exploratory** | "How does X work?", "Find Y" | Fire explore (1-3) + tools in parallel |
1519
1521
  | **Open-ended** | "Improve", "Refactor", "Add feature" | Assess codebase first |
1522
+ | **GitHub Work** | Mentioned in issue, "look into X and create PR" | **Full cycle**: investigate \u2192 implement \u2192 verify \u2192 create PR (see GitHub Workflow section) |
1520
1523
  | **Ambiguous** | Unclear scope, multiple interpretations | Ask ONE clarifying question |
1521
1524
 
1522
1525
  ### Step 2: Check for Ambiguity
@@ -1664,7 +1667,7 @@ STOP searching when:
1664
1667
  ## Phase 2B - Implementation
1665
1668
 
1666
1669
  ### Pre-Implementation:
1667
- 1. If task has 2+ steps \u2192 Create todo list IMMEDIATELY, IN SUPER DETAIL.
1670
+ 1. If task has 2+ steps \u2192 Create todo list IMMEDIATELY, IN SUPER DETAIL. No announcements\u2014just create it.
1668
1671
  2. Mark current task \`in_progress\` before starting
1669
1672
  3. Mark \`completed\` as soon as done (don't batch) - OBSESSIVELY TRACK YOUR WORK USING TODO TOOLS
1670
1673
 
@@ -1736,6 +1739,41 @@ AFTER THE WORK YOU DELEGATED SEEMS DONE, ALWAYS VERIFY THE RESULTS AS FOLLOWING:
1736
1739
 
1737
1740
  **Vague prompts = rejected. Be exhaustive.**
1738
1741
 
1742
+ ### GitHub Workflow (CRITICAL - When mentioned in issues/PRs):
1743
+
1744
+ When you're mentioned in GitHub issues or asked to "look into" something and "create PR":
1745
+
1746
+ **This is NOT just investigation. This is a COMPLETE WORK CYCLE.**
1747
+
1748
+ #### Pattern Recognition:
1749
+ - "@sisyphus look into X"
1750
+ - "look into X and create PR"
1751
+ - "investigate Y and make PR"
1752
+ - Mentioned in issue comments
1753
+
1754
+ #### Required Workflow (NON-NEGOTIABLE):
1755
+ 1. **Investigate**: Understand the problem thoroughly
1756
+ - Read issue/PR context completely
1757
+ - Search codebase for relevant code
1758
+ - Identify root cause and scope
1759
+ 2. **Implement**: Make the necessary changes
1760
+ - Follow existing codebase patterns
1761
+ - Add tests if applicable
1762
+ - Verify with lsp_diagnostics
1763
+ 3. **Verify**: Ensure everything works
1764
+ - Run build if exists
1765
+ - Run tests if exists
1766
+ - Check for regressions
1767
+ 4. **Create PR**: Complete the cycle
1768
+ - Use \`gh pr create\` with meaningful title and description
1769
+ - Reference the original issue number
1770
+ - Summarize what was changed and why
1771
+
1772
+ **EMPHASIS**: "Look into" does NOT mean "just investigate and report back."
1773
+ It means "investigate, understand, implement a solution, and create a PR."
1774
+
1775
+ **If the user says "look into X and create PR", they expect a PR, not just analysis.**
1776
+
1739
1777
  ### Code Changes:
1740
1778
  - Match existing patterns (if codebase is disciplined)
1741
1779
  - Propose approach first (if codebase is chaotic)
@@ -1831,6 +1869,8 @@ Oracle is an expensive, high-quality reasoning model. Use it wisely.
1831
1869
 
1832
1870
  ### Usage Pattern:
1833
1871
  Briefly announce "Consulting Oracle for [reason]" before invocation.
1872
+
1873
+ **Exception**: This is the ONLY case where you announce before acting. For all other work, start immediately without status updates.
1834
1874
  </Oracle_Usage>
1835
1875
 
1836
1876
  <Task_Management>
@@ -1894,6 +1934,7 @@ Should I proceed with [recommendation], or would you prefer differently?
1894
1934
  ## Communication Style
1895
1935
 
1896
1936
  ### Be Concise
1937
+ - Start work immediately. No acknowledgments ("I'm on it", "Let me...", "I'll start...")
1897
1938
  - Answer directly without preamble
1898
1939
  - Don't summarize what you did unless asked
1899
1940
  - Don't explain your code unless asked
@@ -1908,6 +1949,16 @@ Never start responses with:
1908
1949
 
1909
1950
  Just respond directly to the substance.
1910
1951
 
1952
+ ### No Status Updates
1953
+ Never start responses with casual acknowledgments:
1954
+ - "Hey I'm on it..."
1955
+ - "I'm working on this..."
1956
+ - "Let me start by..."
1957
+ - "I'll get to work on..."
1958
+ - "I'm going to..."
1959
+
1960
+ Just start working. Use todos for progress tracking\u2014that's what they're for.
1961
+
1911
1962
  ### When User is Wrong
1912
1963
  If the user's approach seems problematic:
1913
1964
  - Don't blindly implement it
@@ -2051,13 +2102,15 @@ function createOracleAgent(model = DEFAULT_MODEL2) {
2051
2102
  var oracleAgent = createOracleAgent();
2052
2103
 
2053
2104
  // src/agents/librarian.ts
2054
- var librarianAgent = {
2055
- description: "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
2056
- mode: "subagent",
2057
- model: "anthropic/claude-sonnet-4-5",
2058
- temperature: 0.1,
2059
- tools: { write: false, edit: false, background_task: false },
2060
- prompt: `# THE LIBRARIAN
2105
+ var DEFAULT_MODEL3 = "anthropic/claude-sonnet-4-5";
2106
+ function createLibrarianAgent(model = DEFAULT_MODEL3) {
2107
+ return {
2108
+ description: "Specialized codebase understanding agent for multi-repository analysis, searching remote codebases, retrieving official documentation, and finding implementation examples using GitHub CLI, Context7, and Web Search. MUST BE USED when users ask to look up code in remote repositories, explain library internals, or find usage examples in open source.",
2109
+ mode: "subagent",
2110
+ model,
2111
+ temperature: 0.1,
2112
+ tools: { write: false, edit: false, background_task: false },
2113
+ prompt: `# THE LIBRARIAN
2061
2114
 
2062
2115
  You are **THE LIBRARIAN**, a specialized open-source codebase understanding agent.
2063
2116
 
@@ -2287,16 +2340,20 @@ grep_app_searchGitHub(query: "useQuery")
2287
2340
  5. **BE CONCISE**: Facts > opinions, evidence > speculation
2288
2341
 
2289
2342
  `
2290
- };
2343
+ };
2344
+ }
2345
+ var librarianAgent = createLibrarianAgent();
2291
2346
 
2292
2347
  // src/agents/explore.ts
2293
- var exploreAgent = {
2294
- description: 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.',
2295
- mode: "subagent",
2296
- model: "opencode/grok-code",
2297
- temperature: 0.1,
2298
- tools: { write: false, edit: false, background_task: false },
2299
- prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
2348
+ var DEFAULT_MODEL4 = "opencode/grok-code";
2349
+ function createExploreAgent(model = DEFAULT_MODEL4) {
2350
+ return {
2351
+ description: 'Contextual grep for codebases. Answers "Where is X?", "Which file has Y?", "Find the code that does Z". Fire multiple in parallel for broad searches. Specify thoroughness: "quick" for basic, "medium" for moderate, "very thorough" for comprehensive analysis.',
2352
+ mode: "subagent",
2353
+ model,
2354
+ temperature: 0.1,
2355
+ tools: { write: false, edit: false, background_task: false },
2356
+ prompt: `You are a codebase search specialist. Your job: find files and code, return actionable results.
2300
2357
 
2301
2358
  ## Your Mission
2302
2359
 
@@ -2385,15 +2442,19 @@ grep_app searches millions of public GitHub repos instantly \u2014 use it for ex
2385
2442
  3. **Cross-validate with local tools** (grep, ast_grep_search, LSP) before trusting results
2386
2443
 
2387
2444
  Flood with parallel calls. Trust only cross-validated results.`
2388
- };
2445
+ };
2446
+ }
2447
+ var exploreAgent = createExploreAgent();
2389
2448
 
2390
2449
  // src/agents/frontend-ui-ux-engineer.ts
2391
- var frontendUiUxEngineerAgent = {
2392
- description: "A designer-turned-developer who crafts stunning UI/UX even without design mockups. Code may be a bit messy, but the visual output is always fire.",
2393
- mode: "subagent",
2394
- model: "google/gemini-3-pro-preview",
2395
- tools: { background_task: false },
2396
- prompt: `# Role: Designer-Turned-Developer
2450
+ var DEFAULT_MODEL5 = "google/gemini-3-pro-preview";
2451
+ function createFrontendUiUxEngineerAgent(model = DEFAULT_MODEL5) {
2452
+ return {
2453
+ description: "A designer-turned-developer who crafts stunning UI/UX even without design mockups. Code may be a bit messy, but the visual output is always fire.",
2454
+ mode: "subagent",
2455
+ model,
2456
+ tools: { background_task: false },
2457
+ prompt: `# Role: Designer-Turned-Developer
2397
2458
 
2398
2459
  You are a designer who learned to code. You see what pure developers miss\u2014spacing, color harmony, micro-interactions, that indefinable "feel" that makes interfaces memorable. Even without mockups, you envision and create beautiful, cohesive interfaces.
2399
2460
 
@@ -2466,15 +2527,19 @@ Match implementation complexity to aesthetic vision:
2466
2527
  - **Minimalist** \u2192 Restraint, precision, careful spacing and typography
2467
2528
 
2468
2529
  Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. You are capable of extraordinary creative work\u2014don't hold back.`
2469
- };
2530
+ };
2531
+ }
2532
+ var frontendUiUxEngineerAgent = createFrontendUiUxEngineerAgent();
2470
2533
 
2471
2534
  // src/agents/document-writer.ts
2472
- var documentWriterAgent = {
2473
- description: "A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.",
2474
- mode: "subagent",
2475
- model: "google/gemini-3-flash-preview",
2476
- tools: { background_task: false },
2477
- prompt: `<role>
2535
+ var DEFAULT_MODEL6 = "google/gemini-3-flash-preview";
2536
+ function createDocumentWriterAgent(model = DEFAULT_MODEL6) {
2537
+ return {
2538
+ description: "A technical writer who crafts clear, comprehensive documentation. Specializes in README files, API docs, architecture docs, and user guides. MUST BE USED when executing documentation tasks from ai-todo list plans.",
2539
+ mode: "subagent",
2540
+ model,
2541
+ tools: { background_task: false },
2542
+ prompt: `<role>
2478
2543
  You are a TECHNICAL WRITER with deep engineering background who transforms complex codebases into crystal-clear documentation. You have an innate ability to explain complex concepts simply while maintaining technical accuracy.
2479
2544
 
2480
2545
  You approach every documentation task with both a developer's understanding and a reader's empathy. Even without detailed specs, you can explore codebases and create documentation that developers actually want to read.
@@ -2668,16 +2733,20 @@ STOP HERE - DO NOT CONTINUE TO NEXT TASK
2668
2733
 
2669
2734
  You are a technical writer who creates documentation that developers actually want to read.
2670
2735
  </guide>`
2671
- };
2736
+ };
2737
+ }
2738
+ var documentWriterAgent = createDocumentWriterAgent();
2672
2739
 
2673
2740
  // src/agents/multimodal-looker.ts
2674
- var multimodalLookerAgent = {
2675
- 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.",
2676
- mode: "subagent",
2677
- model: "google/gemini-3-flash",
2678
- temperature: 0.1,
2679
- tools: { write: false, edit: false, bash: false, background_task: false },
2680
- prompt: `You interpret media files that cannot be read as plain text.
2741
+ var DEFAULT_MODEL7 = "google/gemini-3-flash";
2742
+ function createMultimodalLookerAgent(model = DEFAULT_MODEL7) {
2743
+ return {
2744
+ 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.",
2745
+ mode: "subagent",
2746
+ model,
2747
+ temperature: 0.1,
2748
+ tools: { write: false, edit: false, bash: false, background_task: false },
2749
+ prompt: `You interpret media files that cannot be read as plain text.
2681
2750
 
2682
2751
  Your job: examine the attached file and extract ONLY what was requested.
2683
2752
 
@@ -2709,7 +2778,9 @@ Response rules:
2709
2778
  - Be thorough on the goal, concise on everything else
2710
2779
 
2711
2780
  Your output goes straight to the main agent for continued work.`
2712
- };
2781
+ };
2782
+ }
2783
+ var multimodalLookerAgent = createMultimodalLookerAgent();
2713
2784
  // src/shared/frontmatter.ts
2714
2785
  function parseFrontmatter(content) {
2715
2786
  const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
@@ -3198,6 +3269,18 @@ function getUserConfigDir() {
3198
3269
  }
3199
3270
  return process.env.XDG_CONFIG_HOME || path2.join(os2.homedir(), ".config");
3200
3271
  }
3272
+ // src/shared/data-path.ts
3273
+ import * as path3 from "path";
3274
+ import * as os3 from "os";
3275
+ function getDataDir() {
3276
+ if (process.platform === "win32") {
3277
+ return process.env.LOCALAPPDATA ?? path3.join(os3.homedir(), "AppData", "Local");
3278
+ }
3279
+ return process.env.XDG_DATA_HOME ?? path3.join(os3.homedir(), ".local", "share");
3280
+ }
3281
+ function getOpenCodeStorageDir() {
3282
+ return path3.join(getDataDir(), "opencode", "storage");
3283
+ }
3201
3284
  // src/shared/config-errors.ts
3202
3285
  var configLoadErrors = [];
3203
3286
  function getConfigLoadErrors() {
@@ -3213,11 +3296,11 @@ function addConfigLoadError(error) {
3213
3296
  var agentSources = {
3214
3297
  Sisyphus: createSisyphusAgent,
3215
3298
  oracle: createOracleAgent,
3216
- librarian: librarianAgent,
3217
- explore: exploreAgent,
3218
- "frontend-ui-ux-engineer": frontendUiUxEngineerAgent,
3219
- "document-writer": documentWriterAgent,
3220
- "multimodal-looker": multimodalLookerAgent
3299
+ librarian: createLibrarianAgent,
3300
+ explore: createExploreAgent,
3301
+ "frontend-ui-ux-engineer": createFrontendUiUxEngineerAgent,
3302
+ "document-writer": createDocumentWriterAgent,
3303
+ "multimodal-looker": createMultimodalLookerAgent
3221
3304
  };
3222
3305
  function isFactory(source) {
3223
3306
  return typeof source === "function";
@@ -3254,7 +3337,13 @@ Here is some useful information about the environment you are running in:
3254
3337
  </env>`;
3255
3338
  }
3256
3339
  function mergeAgentConfig(base, override) {
3257
- return deepMerge(base, override);
3340
+ const { prompt_append, ...rest } = override;
3341
+ const merged = deepMerge(base, rest);
3342
+ if (prompt_append && merged.prompt) {
3343
+ merged.prompt = merged.prompt + `
3344
+ ` + prompt_append;
3345
+ }
3346
+ return merged;
3258
3347
  }
3259
3348
  function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel) {
3260
3349
  const result = {};
@@ -3279,7 +3368,7 @@ function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory
3279
3368
  }
3280
3369
  // src/hooks/todo-continuation-enforcer.ts
3281
3370
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
3282
- import { join as join6 } from "path";
3371
+ import { join as join7 } from "path";
3283
3372
 
3284
3373
  // src/features/claude-code-session-state/state.ts
3285
3374
  var subagentSessions = new Set;
@@ -3292,15 +3381,13 @@ function getMainSessionID() {
3292
3381
  }
3293
3382
  // src/features/hook-message-injector/injector.ts
3294
3383
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync2, readdirSync, writeFileSync } from "fs";
3295
- import { join as join5 } from "path";
3384
+ import { join as join6 } from "path";
3296
3385
 
3297
3386
  // src/features/hook-message-injector/constants.ts
3298
- import { join as join4 } from "path";
3299
- import { homedir as homedir3 } from "os";
3300
- var xdgData = process.env.XDG_DATA_HOME || join4(homedir3(), ".local", "share");
3301
- var OPENCODE_STORAGE = join4(xdgData, "opencode", "storage");
3302
- var MESSAGE_STORAGE = join4(OPENCODE_STORAGE, "message");
3303
- var PART_STORAGE = join4(OPENCODE_STORAGE, "part");
3387
+ import { join as join5 } from "path";
3388
+ var OPENCODE_STORAGE = getOpenCodeStorageDir();
3389
+ var MESSAGE_STORAGE = join5(OPENCODE_STORAGE, "message");
3390
+ var PART_STORAGE = join5(OPENCODE_STORAGE, "part");
3304
3391
 
3305
3392
  // src/features/hook-message-injector/injector.ts
3306
3393
  function findNearestMessageWithFields(messageDir) {
@@ -3308,7 +3395,7 @@ function findNearestMessageWithFields(messageDir) {
3308
3395
  const files = readdirSync(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
3309
3396
  for (const file of files) {
3310
3397
  try {
3311
- const content = readFileSync2(join5(messageDir, file), "utf-8");
3398
+ const content = readFileSync2(join6(messageDir, file), "utf-8");
3312
3399
  const msg = JSON.parse(content);
3313
3400
  if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
3314
3401
  return msg;
@@ -3336,12 +3423,12 @@ function getOrCreateMessageDir(sessionID) {
3336
3423
  if (!existsSync4(MESSAGE_STORAGE)) {
3337
3424
  mkdirSync(MESSAGE_STORAGE, { recursive: true });
3338
3425
  }
3339
- const directPath = join5(MESSAGE_STORAGE, sessionID);
3426
+ const directPath = join6(MESSAGE_STORAGE, sessionID);
3340
3427
  if (existsSync4(directPath)) {
3341
3428
  return directPath;
3342
3429
  }
3343
3430
  for (const dir of readdirSync(MESSAGE_STORAGE)) {
3344
- const sessionPath = join5(MESSAGE_STORAGE, dir, sessionID);
3431
+ const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3345
3432
  if (existsSync4(sessionPath)) {
3346
3433
  return sessionPath;
3347
3434
  }
@@ -3395,17 +3482,34 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3395
3482
  sessionID
3396
3483
  };
3397
3484
  try {
3398
- writeFileSync(join5(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3399
- const partDir = join5(PART_STORAGE, messageID);
3485
+ writeFileSync(join6(messageDir, `${messageID}.json`), JSON.stringify(messageMeta, null, 2));
3486
+ const partDir = join6(PART_STORAGE, messageID);
3400
3487
  if (!existsSync4(partDir)) {
3401
3488
  mkdirSync(partDir, { recursive: true });
3402
3489
  }
3403
- writeFileSync(join5(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3490
+ writeFileSync(join6(partDir, `${partID}.json`), JSON.stringify(textPart, null, 2));
3404
3491
  return true;
3405
3492
  } catch {
3406
3493
  return false;
3407
3494
  }
3408
3495
  }
3496
+ // src/hooks/non-interactive-env/detector.ts
3497
+ function isNonInteractive() {
3498
+ if (process.env.CI === "true" || process.env.CI === "1") {
3499
+ return true;
3500
+ }
3501
+ if (process.env.OPENCODE_RUN === "true" || process.env.OPENCODE_NON_INTERACTIVE === "true") {
3502
+ return true;
3503
+ }
3504
+ if (process.env.GITHUB_ACTIONS === "true") {
3505
+ return true;
3506
+ }
3507
+ if (process.stdout.isTTY !== true) {
3508
+ return true;
3509
+ }
3510
+ return false;
3511
+ }
3512
+
3409
3513
  // src/hooks/todo-continuation-enforcer.ts
3410
3514
  var HOOK_NAME = "todo-continuation-enforcer";
3411
3515
  var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
@@ -3418,11 +3522,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
3418
3522
  function getMessageDir(sessionID) {
3419
3523
  if (!existsSync5(MESSAGE_STORAGE))
3420
3524
  return null;
3421
- const directPath = join6(MESSAGE_STORAGE, sessionID);
3525
+ const directPath = join7(MESSAGE_STORAGE, sessionID);
3422
3526
  if (existsSync5(directPath))
3423
3527
  return directPath;
3424
3528
  for (const dir of readdirSync2(MESSAGE_STORAGE)) {
3425
- const sessionPath = join6(MESSAGE_STORAGE, dir, sessionID);
3529
+ const sessionPath = join7(MESSAGE_STORAGE, dir, sessionID);
3426
3530
  if (existsSync5(sessionPath))
3427
3531
  return sessionPath;
3428
3532
  }
@@ -3450,12 +3554,14 @@ function detectInterrupt(error) {
3450
3554
  }
3451
3555
  var COUNTDOWN_SECONDS = 2;
3452
3556
  var TOAST_DURATION_MS = 900;
3453
- function createTodoContinuationEnforcer(ctx) {
3557
+ function createTodoContinuationEnforcer(ctx, options = {}) {
3558
+ const { backgroundManager } = options;
3454
3559
  const remindedSessions = new Set;
3455
3560
  const interruptedSessions = new Set;
3456
3561
  const errorSessions = new Set;
3457
3562
  const recoveringSessions = new Set;
3458
3563
  const pendingCountdowns = new Map;
3564
+ const preemptivelyInjectedSessions = new Set;
3459
3565
  const markRecovering = (sessionID) => {
3460
3566
  recoveringSessions.add(sessionID);
3461
3567
  };
@@ -3623,10 +3729,59 @@ function createTodoContinuationEnforcer(ctx) {
3623
3729
  log(`[${HOOK_NAME}] Cancelled countdown on user message`, { sessionID });
3624
3730
  }
3625
3731
  remindedSessions.delete(sessionID);
3732
+ preemptivelyInjectedSessions.delete(sessionID);
3626
3733
  }
3627
3734
  if (sessionID && role === "assistant" && finish) {
3628
3735
  remindedSessions.delete(sessionID);
3629
- log(`[${HOOK_NAME}] Cleared reminded state on assistant finish`, { sessionID });
3736
+ preemptivelyInjectedSessions.delete(sessionID);
3737
+ log(`[${HOOK_NAME}] Cleared reminded/preemptive state on assistant finish`, { sessionID });
3738
+ const isTerminalFinish = finish && !["tool-calls", "unknown"].includes(finish);
3739
+ if (isTerminalFinish && isNonInteractive()) {
3740
+ log(`[${HOOK_NAME}] Terminal finish in non-interactive mode`, { sessionID, finish });
3741
+ const mainSessionID2 = getMainSessionID();
3742
+ if (mainSessionID2 && sessionID !== mainSessionID2) {
3743
+ log(`[${HOOK_NAME}] Skipped preemptive: not main session`, { sessionID, mainSessionID: mainSessionID2 });
3744
+ return;
3745
+ }
3746
+ if (preemptivelyInjectedSessions.has(sessionID)) {
3747
+ log(`[${HOOK_NAME}] Skipped preemptive: already injected`, { sessionID });
3748
+ return;
3749
+ }
3750
+ if (recoveringSessions.has(sessionID) || errorSessions.has(sessionID) || interruptedSessions.has(sessionID)) {
3751
+ log(`[${HOOK_NAME}] Skipped preemptive: session in error/recovery state`, { sessionID });
3752
+ return;
3753
+ }
3754
+ const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
3755
+ let hasIncompleteTodos = false;
3756
+ try {
3757
+ const response = await ctx.client.session.todo({ path: { id: sessionID } });
3758
+ const todos = response.data ?? response;
3759
+ hasIncompleteTodos = todos?.some((t) => t.status !== "completed" && t.status !== "cancelled") ?? false;
3760
+ } catch {
3761
+ log(`[${HOOK_NAME}] Failed to fetch todos for preemptive check`, { sessionID });
3762
+ }
3763
+ if (hasRunningBgTasks || hasIncompleteTodos) {
3764
+ log(`[${HOOK_NAME}] Preemptive injection needed`, { sessionID, hasRunningBgTasks, hasIncompleteTodos });
3765
+ preemptivelyInjectedSessions.add(sessionID);
3766
+ try {
3767
+ const messageDir = getMessageDir(sessionID);
3768
+ const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
3769
+ const prompt = hasRunningBgTasks ? "[SYSTEM] Background tasks are still running. Wait for their completion before proceeding." : CONTINUATION_PROMPT;
3770
+ await ctx.client.session.prompt({
3771
+ path: { id: sessionID },
3772
+ body: {
3773
+ agent: prevMessage?.agent,
3774
+ parts: [{ type: "text", text: prompt }]
3775
+ },
3776
+ query: { directory: ctx.directory }
3777
+ });
3778
+ log(`[${HOOK_NAME}] Preemptive injection successful`, { sessionID });
3779
+ } catch (err) {
3780
+ log(`[${HOOK_NAME}] Preemptive injection failed`, { sessionID, error: String(err) });
3781
+ preemptivelyInjectedSessions.delete(sessionID);
3782
+ }
3783
+ }
3784
+ }
3630
3785
  }
3631
3786
  }
3632
3787
  if (event.type === "session.deleted") {
@@ -3636,6 +3791,7 @@ function createTodoContinuationEnforcer(ctx) {
3636
3791
  interruptedSessions.delete(sessionInfo.id);
3637
3792
  errorSessions.delete(sessionInfo.id);
3638
3793
  recoveringSessions.delete(sessionInfo.id);
3794
+ preemptivelyInjectedSessions.delete(sessionInfo.id);
3639
3795
  const countdown = pendingCountdowns.get(sessionInfo.id);
3640
3796
  if (countdown) {
3641
3797
  clearInterval(countdown.intervalId);
@@ -3897,6 +4053,9 @@ function createSessionNotification(ctx, config = {}) {
3897
4053
  return;
3898
4054
  if (subagentSessions.has(sessionID))
3899
4055
  return;
4056
+ const mainSessionID2 = getMainSessionID();
4057
+ if (mainSessionID2 && sessionID !== mainSessionID2)
4058
+ return;
3900
4059
  if (notifiedSessions.has(sessionID))
3901
4060
  return;
3902
4061
  if (pendingTimers.has(sessionID))
@@ -3942,24 +4101,24 @@ function createSessionNotification(ctx, config = {}) {
3942
4101
  }
3943
4102
  // src/hooks/session-recovery/storage.ts
3944
4103
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
3945
- import { join as join8 } from "path";
4104
+ import { join as join9 } from "path";
3946
4105
 
3947
4106
  // src/hooks/session-recovery/constants.ts
3948
- import { join as join7 } from "path";
4107
+ import { join as join8 } from "path";
3949
4108
 
3950
4109
  // node_modules/xdg-basedir/index.js
3951
- import os3 from "os";
3952
- import path3 from "path";
3953
- var homeDirectory = os3.homedir();
4110
+ import os4 from "os";
4111
+ import path4 from "path";
4112
+ var homeDirectory = os4.homedir();
3954
4113
  var { env } = process;
3955
- var xdgData2 = env.XDG_DATA_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "share") : undefined);
3956
- var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path3.join(homeDirectory, ".config") : undefined);
3957
- var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path3.join(homeDirectory, ".local", "state") : undefined);
3958
- var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path3.join(homeDirectory, ".cache") : undefined);
4114
+ var xdgData = env.XDG_DATA_HOME || (homeDirectory ? path4.join(homeDirectory, ".local", "share") : undefined);
4115
+ var xdgConfig = env.XDG_CONFIG_HOME || (homeDirectory ? path4.join(homeDirectory, ".config") : undefined);
4116
+ var xdgState = env.XDG_STATE_HOME || (homeDirectory ? path4.join(homeDirectory, ".local", "state") : undefined);
4117
+ var xdgCache = env.XDG_CACHE_HOME || (homeDirectory ? path4.join(homeDirectory, ".cache") : undefined);
3959
4118
  var xdgRuntime = env.XDG_RUNTIME_DIR || undefined;
3960
4119
  var xdgDataDirectories = (env.XDG_DATA_DIRS || "/usr/local/share/:/usr/share/").split(":");
3961
- if (xdgData2) {
3962
- xdgDataDirectories.unshift(xdgData2);
4120
+ if (xdgData) {
4121
+ xdgDataDirectories.unshift(xdgData);
3963
4122
  }
3964
4123
  var xdgConfigDirectories = (env.XDG_CONFIG_DIRS || "/etc/xdg").split(":");
3965
4124
  if (xdgConfig) {
@@ -3967,9 +4126,9 @@ if (xdgConfig) {
3967
4126
  }
3968
4127
 
3969
4128
  // src/hooks/session-recovery/constants.ts
3970
- var OPENCODE_STORAGE2 = join7(xdgData2 ?? "", "opencode", "storage");
3971
- var MESSAGE_STORAGE2 = join7(OPENCODE_STORAGE2, "message");
3972
- var PART_STORAGE2 = join7(OPENCODE_STORAGE2, "part");
4129
+ var OPENCODE_STORAGE2 = join8(xdgData ?? "", "opencode", "storage");
4130
+ var MESSAGE_STORAGE2 = join8(OPENCODE_STORAGE2, "message");
4131
+ var PART_STORAGE2 = join8(OPENCODE_STORAGE2, "part");
3973
4132
  var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
3974
4133
  var META_TYPES = new Set(["step-start", "step-finish"]);
3975
4134
  var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
@@ -3983,12 +4142,12 @@ function generatePartId2() {
3983
4142
  function getMessageDir2(sessionID) {
3984
4143
  if (!existsSync6(MESSAGE_STORAGE2))
3985
4144
  return "";
3986
- const directPath = join8(MESSAGE_STORAGE2, sessionID);
4145
+ const directPath = join9(MESSAGE_STORAGE2, sessionID);
3987
4146
  if (existsSync6(directPath)) {
3988
4147
  return directPath;
3989
4148
  }
3990
4149
  for (const dir of readdirSync3(MESSAGE_STORAGE2)) {
3991
- const sessionPath = join8(MESSAGE_STORAGE2, dir, sessionID);
4150
+ const sessionPath = join9(MESSAGE_STORAGE2, dir, sessionID);
3992
4151
  if (existsSync6(sessionPath)) {
3993
4152
  return sessionPath;
3994
4153
  }
@@ -4004,7 +4163,7 @@ function readMessages(sessionID) {
4004
4163
  if (!file.endsWith(".json"))
4005
4164
  continue;
4006
4165
  try {
4007
- const content = readFileSync3(join8(messageDir, file), "utf-8");
4166
+ const content = readFileSync3(join9(messageDir, file), "utf-8");
4008
4167
  messages.push(JSON.parse(content));
4009
4168
  } catch {
4010
4169
  continue;
@@ -4019,7 +4178,7 @@ function readMessages(sessionID) {
4019
4178
  });
4020
4179
  }
4021
4180
  function readParts(messageID) {
4022
- const partDir = join8(PART_STORAGE2, messageID);
4181
+ const partDir = join9(PART_STORAGE2, messageID);
4023
4182
  if (!existsSync6(partDir))
4024
4183
  return [];
4025
4184
  const parts = [];
@@ -4027,7 +4186,7 @@ function readParts(messageID) {
4027
4186
  if (!file.endsWith(".json"))
4028
4187
  continue;
4029
4188
  try {
4030
- const content = readFileSync3(join8(partDir, file), "utf-8");
4189
+ const content = readFileSync3(join9(partDir, file), "utf-8");
4031
4190
  parts.push(JSON.parse(content));
4032
4191
  } catch {
4033
4192
  continue;
@@ -4057,7 +4216,7 @@ function messageHasContent(messageID) {
4057
4216
  return parts.some(hasContent);
4058
4217
  }
4059
4218
  function injectTextPart(sessionID, messageID, text) {
4060
- const partDir = join8(PART_STORAGE2, messageID);
4219
+ const partDir = join9(PART_STORAGE2, messageID);
4061
4220
  if (!existsSync6(partDir)) {
4062
4221
  mkdirSync2(partDir, { recursive: true });
4063
4222
  }
@@ -4071,7 +4230,7 @@ function injectTextPart(sessionID, messageID, text) {
4071
4230
  synthetic: true
4072
4231
  };
4073
4232
  try {
4074
- writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4233
+ writeFileSync2(join9(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4075
4234
  return true;
4076
4235
  } catch {
4077
4236
  return false;
@@ -4134,7 +4293,7 @@ function findMessagesWithOrphanThinking(sessionID) {
4134
4293
  return result;
4135
4294
  }
4136
4295
  function prependThinkingPart(sessionID, messageID) {
4137
- const partDir = join8(PART_STORAGE2, messageID);
4296
+ const partDir = join9(PART_STORAGE2, messageID);
4138
4297
  if (!existsSync6(partDir)) {
4139
4298
  mkdirSync2(partDir, { recursive: true });
4140
4299
  }
@@ -4148,14 +4307,14 @@ function prependThinkingPart(sessionID, messageID) {
4148
4307
  synthetic: true
4149
4308
  };
4150
4309
  try {
4151
- writeFileSync2(join8(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4310
+ writeFileSync2(join9(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
4152
4311
  return true;
4153
4312
  } catch {
4154
4313
  return false;
4155
4314
  }
4156
4315
  }
4157
4316
  function stripThinkingParts(messageID) {
4158
- const partDir = join8(PART_STORAGE2, messageID);
4317
+ const partDir = join9(PART_STORAGE2, messageID);
4159
4318
  if (!existsSync6(partDir))
4160
4319
  return false;
4161
4320
  let anyRemoved = false;
@@ -4163,7 +4322,7 @@ function stripThinkingParts(messageID) {
4163
4322
  if (!file.endsWith(".json"))
4164
4323
  continue;
4165
4324
  try {
4166
- const filePath = join8(partDir, file);
4325
+ const filePath = join9(partDir, file);
4167
4326
  const content = readFileSync3(filePath, "utf-8");
4168
4327
  const part = JSON.parse(content);
4169
4328
  if (THINKING_TYPES.has(part.type)) {
@@ -4177,7 +4336,7 @@ function stripThinkingParts(messageID) {
4177
4336
  return anyRemoved;
4178
4337
  }
4179
4338
  function replaceEmptyTextParts(messageID, replacementText) {
4180
- const partDir = join8(PART_STORAGE2, messageID);
4339
+ const partDir = join9(PART_STORAGE2, messageID);
4181
4340
  if (!existsSync6(partDir))
4182
4341
  return false;
4183
4342
  let anyReplaced = false;
@@ -4185,7 +4344,7 @@ function replaceEmptyTextParts(messageID, replacementText) {
4185
4344
  if (!file.endsWith(".json"))
4186
4345
  continue;
4187
4346
  try {
4188
- const filePath = join8(partDir, file);
4347
+ const filePath = join9(partDir, file);
4189
4348
  const content = readFileSync3(filePath, "utf-8");
4190
4349
  const part = JSON.parse(content);
4191
4350
  if (part.type === "text") {
@@ -4462,7 +4621,7 @@ function createSessionRecoveryHook(ctx, options) {
4462
4621
  // src/hooks/comment-checker/cli.ts
4463
4622
  var {spawn: spawn3 } = globalThis.Bun;
4464
4623
  import { createRequire as createRequire2 } from "module";
4465
- import { dirname, join as join10 } from "path";
4624
+ import { dirname, join as join11 } from "path";
4466
4625
  import { existsSync as existsSync8 } from "fs";
4467
4626
  import * as fs3 from "fs";
4468
4627
  import { tmpdir as tmpdir3 } from "os";
@@ -4470,11 +4629,11 @@ import { tmpdir as tmpdir3 } from "os";
4470
4629
  // src/hooks/comment-checker/downloader.ts
4471
4630
  var {spawn: spawn2 } = globalThis.Bun;
4472
4631
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, chmodSync, unlinkSync as unlinkSync2, appendFileSync as appendFileSync2 } from "fs";
4473
- import { join as join9 } from "path";
4632
+ import { join as join10 } from "path";
4474
4633
  import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
4475
4634
  import { createRequire } from "module";
4476
4635
  var DEBUG = process.env.COMMENT_CHECKER_DEBUG === "1";
4477
- var DEBUG_FILE = join9(tmpdir2(), "comment-checker-debug.log");
4636
+ var DEBUG_FILE = join10(tmpdir2(), "comment-checker-debug.log");
4478
4637
  function debugLog(...args) {
4479
4638
  if (DEBUG) {
4480
4639
  const msg = `[${new Date().toISOString()}] [comment-checker:downloader] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4492,14 +4651,14 @@ var PLATFORM_MAP = {
4492
4651
  };
4493
4652
  function getCacheDir() {
4494
4653
  const xdgCache2 = process.env.XDG_CACHE_HOME;
4495
- const base = xdgCache2 || join9(homedir4(), ".cache");
4496
- return join9(base, "oh-my-opencode", "bin");
4654
+ const base = xdgCache2 || join10(homedir4(), ".cache");
4655
+ return join10(base, "oh-my-opencode", "bin");
4497
4656
  }
4498
4657
  function getBinaryName() {
4499
4658
  return process.platform === "win32" ? "comment-checker.exe" : "comment-checker";
4500
4659
  }
4501
4660
  function getCachedBinaryPath() {
4502
- const binaryPath = join9(getCacheDir(), getBinaryName());
4661
+ const binaryPath = join10(getCacheDir(), getBinaryName());
4503
4662
  return existsSync7(binaryPath) ? binaryPath : null;
4504
4663
  }
4505
4664
  function getPackageVersion() {
@@ -4547,14 +4706,14 @@ async function downloadCommentChecker() {
4547
4706
  }
4548
4707
  const cacheDir = getCacheDir();
4549
4708
  const binaryName = getBinaryName();
4550
- const binaryPath = join9(cacheDir, binaryName);
4709
+ const binaryPath = join10(cacheDir, binaryName);
4551
4710
  if (existsSync7(binaryPath)) {
4552
4711
  debugLog("Binary already cached at:", binaryPath);
4553
4712
  return binaryPath;
4554
4713
  }
4555
4714
  const version = getPackageVersion();
4556
- const { os: os4, arch, ext } = platformInfo;
4557
- const assetName = `comment-checker_v${version}_${os4}_${arch}.${ext}`;
4715
+ const { os: os5, arch, ext } = platformInfo;
4716
+ const assetName = `comment-checker_v${version}_${os5}_${arch}.${ext}`;
4558
4717
  const downloadUrl = `https://github.com/${REPO}/releases/download/v${version}/${assetName}`;
4559
4718
  debugLog(`Downloading from: ${downloadUrl}`);
4560
4719
  console.log(`[oh-my-opencode] Downloading comment-checker binary...`);
@@ -4566,7 +4725,7 @@ async function downloadCommentChecker() {
4566
4725
  if (!response.ok) {
4567
4726
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4568
4727
  }
4569
- const archivePath = join9(cacheDir, assetName);
4728
+ const archivePath = join10(cacheDir, assetName);
4570
4729
  const arrayBuffer = await response.arrayBuffer();
4571
4730
  await Bun.write(archivePath, arrayBuffer);
4572
4731
  debugLog(`Downloaded archive to: ${archivePath}`);
@@ -4602,7 +4761,7 @@ async function ensureCommentCheckerBinary() {
4602
4761
 
4603
4762
  // src/hooks/comment-checker/cli.ts
4604
4763
  var DEBUG2 = process.env.COMMENT_CHECKER_DEBUG === "1";
4605
- var DEBUG_FILE2 = join10(tmpdir3(), "comment-checker-debug.log");
4764
+ var DEBUG_FILE2 = join11(tmpdir3(), "comment-checker-debug.log");
4606
4765
  function debugLog2(...args) {
4607
4766
  if (DEBUG2) {
4608
4767
  const msg = `[${new Date().toISOString()}] [comment-checker:cli] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4619,7 +4778,7 @@ function findCommentCheckerPathSync() {
4619
4778
  const require2 = createRequire2(import.meta.url);
4620
4779
  const cliPkgPath = require2.resolve("@code-yeongyu/comment-checker/package.json");
4621
4780
  const cliDir = dirname(cliPkgPath);
4622
- const binaryPath = join10(cliDir, "bin", binaryName);
4781
+ const binaryPath = join11(cliDir, "bin", binaryName);
4623
4782
  if (existsSync8(binaryPath)) {
4624
4783
  debugLog2("found binary in main package:", binaryPath);
4625
4784
  return binaryPath;
@@ -4666,8 +4825,8 @@ async function getCommentCheckerPath() {
4666
4825
  function startBackgroundInit() {
4667
4826
  if (!initPromise) {
4668
4827
  initPromise = getCommentCheckerPath();
4669
- initPromise.then((path4) => {
4670
- debugLog2("background init complete:", path4 || "no binary");
4828
+ initPromise.then((path5) => {
4829
+ debugLog2("background init complete:", path5 || "no binary");
4671
4830
  }).catch((err) => {
4672
4831
  debugLog2("background init error:", err);
4673
4832
  });
@@ -4716,9 +4875,9 @@ async function runCommentChecker(input, cliPath) {
4716
4875
  import * as fs4 from "fs";
4717
4876
  import { existsSync as existsSync9 } from "fs";
4718
4877
  import { tmpdir as tmpdir4 } from "os";
4719
- import { join as join11 } from "path";
4878
+ import { join as join12 } from "path";
4720
4879
  var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
4721
- var DEBUG_FILE3 = join11(tmpdir4(), "comment-checker-debug.log");
4880
+ var DEBUG_FILE3 = join12(tmpdir4(), "comment-checker-debug.log");
4722
4881
  function debugLog3(...args) {
4723
4882
  if (DEBUG3) {
4724
4883
  const msg = `[${new Date().toISOString()}] [comment-checker:hook] ${args.map((a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
@@ -4742,8 +4901,8 @@ function createCommentCheckerHooks() {
4742
4901
  debugLog3("createCommentCheckerHooks called");
4743
4902
  startBackgroundInit();
4744
4903
  cliPathPromise = getCommentCheckerPath();
4745
- cliPathPromise.then((path4) => {
4746
- debugLog3("CLI path resolved:", path4 || "disabled (no binary)");
4904
+ cliPathPromise.then((path5) => {
4905
+ debugLog3("CLI path resolved:", path5 || "disabled (no binary)");
4747
4906
  }).catch((err) => {
4748
4907
  debugLog3("CLI path resolution error:", err);
4749
4908
  });
@@ -4850,7 +5009,7 @@ var TRUNCATABLE_TOOLS = [
4850
5009
  ];
4851
5010
  function createToolOutputTruncatorHook(ctx, options) {
4852
5011
  const truncator = createDynamicTruncator(ctx);
4853
- const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? false;
5012
+ const truncateAll = options?.experimental?.truncate_all_tool_outputs ?? true;
4854
5013
  const toolExecuteAfter = async (input, output) => {
4855
5014
  if (!truncateAll && !TRUNCATABLE_TOOLS.includes(input.tool))
4856
5015
  return;
@@ -4867,7 +5026,7 @@ function createToolOutputTruncatorHook(ctx, options) {
4867
5026
  }
4868
5027
  // src/hooks/directory-agents-injector/index.ts
4869
5028
  import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
4870
- import { dirname as dirname2, join as join14, resolve as resolve2 } from "path";
5029
+ import { dirname as dirname2, join as join15, resolve as resolve2 } from "path";
4871
5030
 
4872
5031
  // src/hooks/directory-agents-injector/storage.ts
4873
5032
  import {
@@ -4877,17 +5036,17 @@ import {
4877
5036
  writeFileSync as writeFileSync3,
4878
5037
  unlinkSync as unlinkSync3
4879
5038
  } from "fs";
4880
- import { join as join13 } from "path";
5039
+ import { join as join14 } from "path";
4881
5040
 
4882
5041
  // src/hooks/directory-agents-injector/constants.ts
4883
- import { join as join12 } from "path";
4884
- var OPENCODE_STORAGE3 = join12(xdgData2 ?? "", "opencode", "storage");
4885
- var AGENTS_INJECTOR_STORAGE = join12(OPENCODE_STORAGE3, "directory-agents");
5042
+ import { join as join13 } from "path";
5043
+ var OPENCODE_STORAGE3 = join13(xdgData ?? "", "opencode", "storage");
5044
+ var AGENTS_INJECTOR_STORAGE = join13(OPENCODE_STORAGE3, "directory-agents");
4886
5045
  var AGENTS_FILENAME = "AGENTS.md";
4887
5046
 
4888
5047
  // src/hooks/directory-agents-injector/storage.ts
4889
5048
  function getStoragePath(sessionID) {
4890
- return join13(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
5049
+ return join14(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
4891
5050
  }
4892
5051
  function loadInjectedPaths(sessionID) {
4893
5052
  const filePath = getStoragePath(sessionID);
@@ -4929,18 +5088,18 @@ function createDirectoryAgentsInjectorHook(ctx) {
4929
5088
  }
4930
5089
  return sessionCaches.get(sessionID);
4931
5090
  }
4932
- function resolveFilePath2(path4) {
4933
- if (!path4)
5091
+ function resolveFilePath2(path5) {
5092
+ if (!path5)
4934
5093
  return null;
4935
- if (path4.startsWith("/"))
4936
- return path4;
4937
- return resolve2(ctx.directory, path4);
5094
+ if (path5.startsWith("/"))
5095
+ return path5;
5096
+ return resolve2(ctx.directory, path5);
4938
5097
  }
4939
5098
  function findAgentsMdUp(startDir) {
4940
5099
  const found = [];
4941
5100
  let current = startDir;
4942
5101
  while (true) {
4943
- const agentsPath = join14(current, AGENTS_FILENAME);
5102
+ const agentsPath = join15(current, AGENTS_FILENAME);
4944
5103
  if (existsSync11(agentsPath)) {
4945
5104
  found.push(agentsPath);
4946
5105
  }
@@ -5034,7 +5193,7 @@ ${content}`;
5034
5193
  }
5035
5194
  // src/hooks/directory-readme-injector/index.ts
5036
5195
  import { existsSync as existsSync13, readFileSync as readFileSync7 } from "fs";
5037
- import { dirname as dirname3, join as join17, resolve as resolve3 } from "path";
5196
+ import { dirname as dirname3, join as join18, resolve as resolve3 } from "path";
5038
5197
 
5039
5198
  // src/hooks/directory-readme-injector/storage.ts
5040
5199
  import {
@@ -5044,17 +5203,17 @@ import {
5044
5203
  writeFileSync as writeFileSync4,
5045
5204
  unlinkSync as unlinkSync4
5046
5205
  } from "fs";
5047
- import { join as join16 } from "path";
5206
+ import { join as join17 } from "path";
5048
5207
 
5049
5208
  // src/hooks/directory-readme-injector/constants.ts
5050
- import { join as join15 } from "path";
5051
- var OPENCODE_STORAGE4 = join15(xdgData2 ?? "", "opencode", "storage");
5052
- var README_INJECTOR_STORAGE = join15(OPENCODE_STORAGE4, "directory-readme");
5209
+ import { join as join16 } from "path";
5210
+ var OPENCODE_STORAGE4 = join16(xdgData ?? "", "opencode", "storage");
5211
+ var README_INJECTOR_STORAGE = join16(OPENCODE_STORAGE4, "directory-readme");
5053
5212
  var README_FILENAME = "README.md";
5054
5213
 
5055
5214
  // src/hooks/directory-readme-injector/storage.ts
5056
5215
  function getStoragePath2(sessionID) {
5057
- return join16(README_INJECTOR_STORAGE, `${sessionID}.json`);
5216
+ return join17(README_INJECTOR_STORAGE, `${sessionID}.json`);
5058
5217
  }
5059
5218
  function loadInjectedPaths2(sessionID) {
5060
5219
  const filePath = getStoragePath2(sessionID);
@@ -5096,18 +5255,18 @@ function createDirectoryReadmeInjectorHook(ctx) {
5096
5255
  }
5097
5256
  return sessionCaches.get(sessionID);
5098
5257
  }
5099
- function resolveFilePath2(path4) {
5100
- if (!path4)
5258
+ function resolveFilePath2(path5) {
5259
+ if (!path5)
5101
5260
  return null;
5102
- if (path4.startsWith("/"))
5103
- return path4;
5104
- return resolve3(ctx.directory, path4);
5261
+ if (path5.startsWith("/"))
5262
+ return path5;
5263
+ return resolve3(ctx.directory, path5);
5105
5264
  }
5106
5265
  function findReadmeMdUp(startDir) {
5107
5266
  const found = [];
5108
5267
  let current = startDir;
5109
5268
  while (true) {
5110
- const readmePath = join17(current, README_FILENAME);
5269
+ const readmePath = join18(current, README_FILENAME);
5111
5270
  if (existsSync13(readmePath)) {
5112
5271
  found.push(readmePath);
5113
5272
  }
@@ -5405,26 +5564,26 @@ var TRUNCATE_CONFIG = {
5405
5564
  // src/hooks/anthropic-auto-compact/storage.ts
5406
5565
  import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
5407
5566
  import { homedir as homedir5 } from "os";
5408
- import { join as join18 } from "path";
5409
- var OPENCODE_STORAGE5 = join18(xdgData2 ?? "", "opencode", "storage");
5567
+ import { join as join19 } from "path";
5568
+ var OPENCODE_STORAGE5 = join19(xdgData ?? "", "opencode", "storage");
5410
5569
  if (process.platform === "darwin" && !existsSync14(OPENCODE_STORAGE5)) {
5411
- const localShare = join18(homedir5(), ".local", "share", "opencode", "storage");
5570
+ const localShare = join19(homedir5(), ".local", "share", "opencode", "storage");
5412
5571
  if (existsSync14(localShare)) {
5413
5572
  OPENCODE_STORAGE5 = localShare;
5414
5573
  }
5415
5574
  }
5416
- var MESSAGE_STORAGE3 = join18(OPENCODE_STORAGE5, "message");
5417
- var PART_STORAGE3 = join18(OPENCODE_STORAGE5, "part");
5575
+ var MESSAGE_STORAGE3 = join19(OPENCODE_STORAGE5, "message");
5576
+ var PART_STORAGE3 = join19(OPENCODE_STORAGE5, "part");
5418
5577
  var TRUNCATION_MESSAGE = "[TOOL RESULT TRUNCATED - Context limit exceeded. Original output was too large and has been truncated to recover the session. Please re-run this tool if you need the full output.]";
5419
5578
  function getMessageDir3(sessionID) {
5420
5579
  if (!existsSync14(MESSAGE_STORAGE3))
5421
5580
  return "";
5422
- const directPath = join18(MESSAGE_STORAGE3, sessionID);
5581
+ const directPath = join19(MESSAGE_STORAGE3, sessionID);
5423
5582
  if (existsSync14(directPath)) {
5424
5583
  return directPath;
5425
5584
  }
5426
5585
  for (const dir of readdirSync4(MESSAGE_STORAGE3)) {
5427
- const sessionPath = join18(MESSAGE_STORAGE3, dir, sessionID);
5586
+ const sessionPath = join19(MESSAGE_STORAGE3, dir, sessionID);
5428
5587
  if (existsSync14(sessionPath)) {
5429
5588
  return sessionPath;
5430
5589
  }
@@ -5448,14 +5607,14 @@ function findToolResultsBySize(sessionID) {
5448
5607
  const messageIds = getMessageIds(sessionID);
5449
5608
  const results = [];
5450
5609
  for (const messageID of messageIds) {
5451
- const partDir = join18(PART_STORAGE3, messageID);
5610
+ const partDir = join19(PART_STORAGE3, messageID);
5452
5611
  if (!existsSync14(partDir))
5453
5612
  continue;
5454
5613
  for (const file of readdirSync4(partDir)) {
5455
5614
  if (!file.endsWith(".json"))
5456
5615
  continue;
5457
5616
  try {
5458
- const partPath = join18(partDir, file);
5617
+ const partPath = join19(partDir, file);
5459
5618
  const content = readFileSync8(partPath, "utf-8");
5460
5619
  const part = JSON.parse(content);
5461
5620
  if (part.type === "tool" && part.state?.output && !part.truncated) {
@@ -6063,7 +6222,7 @@ function createAnthropicAutoCompactHook(ctx, options) {
6063
6222
  }
6064
6223
  // src/hooks/preemptive-compaction/index.ts
6065
6224
  import { existsSync as existsSync15, readdirSync as readdirSync5 } from "fs";
6066
- import { join as join19 } from "path";
6225
+ import { join as join20 } from "path";
6067
6226
 
6068
6227
  // src/hooks/preemptive-compaction/constants.ts
6069
6228
  var DEFAULT_THRESHOLD = 0.85;
@@ -6079,11 +6238,11 @@ function isSupportedModel(modelID) {
6079
6238
  function getMessageDir4(sessionID) {
6080
6239
  if (!existsSync15(MESSAGE_STORAGE))
6081
6240
  return null;
6082
- const directPath = join19(MESSAGE_STORAGE, sessionID);
6241
+ const directPath = join20(MESSAGE_STORAGE, sessionID);
6083
6242
  if (existsSync15(directPath))
6084
6243
  return directPath;
6085
6244
  for (const dir of readdirSync5(MESSAGE_STORAGE)) {
6086
- const sessionPath = join19(MESSAGE_STORAGE, dir, sessionID);
6245
+ const sessionPath = join20(MESSAGE_STORAGE, dir, sessionID);
6087
6246
  if (existsSync15(sessionPath))
6088
6247
  return sessionPath;
6089
6248
  }
@@ -6569,7 +6728,7 @@ function createThinkModeHook() {
6569
6728
  }
6570
6729
  // src/hooks/claude-code-hooks/config.ts
6571
6730
  import { homedir as homedir6 } from "os";
6572
- import { join as join20 } from "path";
6731
+ import { join as join21 } from "path";
6573
6732
  import { existsSync as existsSync16 } from "fs";
6574
6733
  function normalizeHookMatcher(raw) {
6575
6734
  return {
@@ -6583,7 +6742,8 @@ function normalizeHooksConfig(raw) {
6583
6742
  "PreToolUse",
6584
6743
  "PostToolUse",
6585
6744
  "UserPromptSubmit",
6586
- "Stop"
6745
+ "Stop",
6746
+ "PreCompact"
6587
6747
  ];
6588
6748
  for (const eventType of eventTypes) {
6589
6749
  if (raw[eventType]) {
@@ -6595,9 +6755,9 @@ function normalizeHooksConfig(raw) {
6595
6755
  function getClaudeSettingsPaths(customPath) {
6596
6756
  const home = homedir6();
6597
6757
  const paths = [
6598
- join20(home, ".claude", "settings.json"),
6599
- join20(process.cwd(), ".claude", "settings.json"),
6600
- join20(process.cwd(), ".claude", "settings.local.json")
6758
+ join21(home, ".claude", "settings.json"),
6759
+ join21(process.cwd(), ".claude", "settings.json"),
6760
+ join21(process.cwd(), ".claude", "settings.local.json")
6601
6761
  ];
6602
6762
  if (customPath && existsSync16(customPath)) {
6603
6763
  paths.unshift(customPath);
@@ -6610,7 +6770,8 @@ function mergeHooksConfig(base, override) {
6610
6770
  "PreToolUse",
6611
6771
  "PostToolUse",
6612
6772
  "UserPromptSubmit",
6613
- "Stop"
6773
+ "Stop",
6774
+ "PreCompact"
6614
6775
  ];
6615
6776
  for (const eventType of eventTypes) {
6616
6777
  if (override[eventType]) {
@@ -6642,20 +6803,20 @@ async function loadClaudeHooksConfig(customSettingsPath) {
6642
6803
  // src/hooks/claude-code-hooks/config-loader.ts
6643
6804
  import { existsSync as existsSync17 } from "fs";
6644
6805
  import { homedir as homedir7 } from "os";
6645
- import { join as join21 } from "path";
6646
- var USER_CONFIG_PATH = join21(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
6806
+ import { join as join22 } from "path";
6807
+ var USER_CONFIG_PATH = join22(homedir7(), ".config", "opencode", "opencode-cc-plugin.json");
6647
6808
  function getProjectConfigPath() {
6648
- return join21(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6809
+ return join22(process.cwd(), ".opencode", "opencode-cc-plugin.json");
6649
6810
  }
6650
- async function loadConfigFromPath(path4) {
6651
- if (!existsSync17(path4)) {
6811
+ async function loadConfigFromPath(path5) {
6812
+ if (!existsSync17(path5)) {
6652
6813
  return null;
6653
6814
  }
6654
6815
  try {
6655
- const content = await Bun.file(path4).text();
6816
+ const content = await Bun.file(path5).text();
6656
6817
  return JSON.parse(content);
6657
6818
  } catch (error) {
6658
- log("Failed to load config", { path: path4, error });
6819
+ log("Failed to load config", { path: path5, error });
6659
6820
  return null;
6660
6821
  }
6661
6822
  }
@@ -6668,7 +6829,8 @@ function mergeDisabledHooks(base, override) {
6668
6829
  Stop: override.Stop ?? base.Stop,
6669
6830
  PreToolUse: override.PreToolUse ?? base.PreToolUse,
6670
6831
  PostToolUse: override.PostToolUse ?? base.PostToolUse,
6671
- UserPromptSubmit: override.UserPromptSubmit ?? base.UserPromptSubmit
6832
+ UserPromptSubmit: override.UserPromptSubmit ?? base.UserPromptSubmit,
6833
+ PreCompact: override.PreCompact ?? base.PreCompact
6672
6834
  };
6673
6835
  }
6674
6836
  async function loadPluginExtendedConfig() {
@@ -6827,13 +6989,13 @@ async function executePreToolUseHooks(ctx, config, extendedConfig) {
6827
6989
  }
6828
6990
 
6829
6991
  // src/hooks/claude-code-hooks/transcript.ts
6830
- import { join as join22 } from "path";
6992
+ import { join as join23 } from "path";
6831
6993
  import { mkdirSync as mkdirSync6, appendFileSync as appendFileSync5, existsSync as existsSync18, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5 } from "fs";
6832
6994
  import { homedir as homedir8, tmpdir as tmpdir5 } from "os";
6833
6995
  import { randomUUID } from "crypto";
6834
- var TRANSCRIPT_DIR = join22(homedir8(), ".claude", "transcripts");
6996
+ var TRANSCRIPT_DIR = join23(homedir8(), ".claude", "transcripts");
6835
6997
  function getTranscriptPath(sessionId) {
6836
- return join22(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6998
+ return join23(TRANSCRIPT_DIR, `${sessionId}.jsonl`);
6837
6999
  }
6838
7000
  function ensureTranscriptDir() {
6839
7001
  if (!existsSync18(TRANSCRIPT_DIR)) {
@@ -6842,10 +7004,10 @@ function ensureTranscriptDir() {
6842
7004
  }
6843
7005
  function appendTranscriptEntry(sessionId, entry) {
6844
7006
  ensureTranscriptDir();
6845
- const path4 = getTranscriptPath(sessionId);
7007
+ const path5 = getTranscriptPath(sessionId);
6846
7008
  const line = JSON.stringify(entry) + `
6847
7009
  `;
6848
- appendFileSync5(path4, line);
7010
+ appendFileSync5(path5, line);
6849
7011
  }
6850
7012
  function recordToolUse(sessionId, toolName, toolInput) {
6851
7013
  appendTranscriptEntry(sessionId, {
@@ -6923,7 +7085,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6923
7085
  }
6924
7086
  };
6925
7087
  entries.push(JSON.stringify(currentEntry));
6926
- const tempPath = join22(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
7088
+ const tempPath = join23(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6927
7089
  writeFileSync6(tempPath, entries.join(`
6928
7090
  `) + `
6929
7091
  `);
@@ -6943,7 +7105,7 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6943
7105
  ]
6944
7106
  }
6945
7107
  };
6946
- const tempPath = join22(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
7108
+ const tempPath = join23(tmpdir5(), `opencode-transcript-${sessionId}-${randomUUID()}.jsonl`);
6947
7109
  writeFileSync6(tempPath, JSON.stringify(currentEntry) + `
6948
7110
  `);
6949
7111
  return tempPath;
@@ -6952,11 +7114,11 @@ async function buildTranscriptFromSession(client, sessionId, directory, currentT
6952
7114
  }
6953
7115
  }
6954
7116
  }
6955
- function deleteTempTranscript(path4) {
6956
- if (!path4)
7117
+ function deleteTempTranscript(path5) {
7118
+ if (!path5)
6957
7119
  return;
6958
7120
  try {
6959
- unlinkSync5(path4);
7121
+ unlinkSync5(path5);
6960
7122
  } catch {}
6961
7123
  }
6962
7124
 
@@ -7155,11 +7317,11 @@ ${USER_PROMPT_SUBMIT_TAG_CLOSE}`);
7155
7317
  }
7156
7318
 
7157
7319
  // src/hooks/claude-code-hooks/todo.ts
7158
- import { join as join23 } from "path";
7320
+ import { join as join24 } from "path";
7159
7321
  import { homedir as homedir9 } from "os";
7160
- var TODO_DIR = join23(homedir9(), ".claude", "todos");
7322
+ var TODO_DIR = join24(homedir9(), ".claude", "todos");
7161
7323
  function getTodoPath(sessionId) {
7162
- return join23(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
7324
+ return join24(TODO_DIR, `${sessionId}-agent-${sessionId}.json`);
7163
7325
  }
7164
7326
 
7165
7327
  // src/hooks/claude-code-hooks/stop.ts
@@ -7224,6 +7386,74 @@ async function executeStopHooks(ctx, config, extendedConfig) {
7224
7386
  return { block: false };
7225
7387
  }
7226
7388
 
7389
+ // src/hooks/claude-code-hooks/pre-compact.ts
7390
+ async function executePreCompactHooks(ctx, config, extendedConfig) {
7391
+ if (!config) {
7392
+ return { context: [] };
7393
+ }
7394
+ const matchers = findMatchingHooks(config, "PreCompact", "*");
7395
+ if (matchers.length === 0) {
7396
+ return { context: [] };
7397
+ }
7398
+ const stdinData = {
7399
+ session_id: ctx.sessionId,
7400
+ cwd: ctx.cwd,
7401
+ hook_event_name: "PreCompact",
7402
+ hook_source: "opencode-plugin"
7403
+ };
7404
+ const startTime = Date.now();
7405
+ let firstHookName;
7406
+ const collectedContext = [];
7407
+ for (const matcher of matchers) {
7408
+ for (const hook of matcher.hooks) {
7409
+ if (hook.type !== "command")
7410
+ continue;
7411
+ if (isHookCommandDisabled("PreCompact", hook.command, extendedConfig ?? null)) {
7412
+ log("PreCompact hook command skipped (disabled by config)", { command: hook.command });
7413
+ continue;
7414
+ }
7415
+ const hookName = hook.command.split("/").pop() || hook.command;
7416
+ if (!firstHookName)
7417
+ firstHookName = hookName;
7418
+ const result = await executeHookCommand(hook.command, JSON.stringify(stdinData), ctx.cwd, { forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath });
7419
+ if (result.exitCode === 2) {
7420
+ log("PreCompact hook blocked", { hookName, stderr: result.stderr });
7421
+ continue;
7422
+ }
7423
+ if (result.stdout) {
7424
+ try {
7425
+ const output = JSON.parse(result.stdout);
7426
+ if (output.hookSpecificOutput?.additionalContext) {
7427
+ collectedContext.push(...output.hookSpecificOutput.additionalContext);
7428
+ } else if (output.context) {
7429
+ collectedContext.push(...output.context);
7430
+ }
7431
+ if (output.continue === false) {
7432
+ return {
7433
+ context: collectedContext,
7434
+ elapsedMs: Date.now() - startTime,
7435
+ hookName: firstHookName,
7436
+ continue: output.continue,
7437
+ stopReason: output.stopReason,
7438
+ suppressOutput: output.suppressOutput,
7439
+ systemMessage: output.systemMessage
7440
+ };
7441
+ }
7442
+ } catch {
7443
+ if (result.stdout.trim()) {
7444
+ collectedContext.push(result.stdout.trim());
7445
+ }
7446
+ }
7447
+ }
7448
+ }
7449
+ }
7450
+ return {
7451
+ context: collectedContext,
7452
+ elapsedMs: Date.now() - startTime,
7453
+ hookName: firstHookName
7454
+ };
7455
+ }
7456
+
7227
7457
  // src/hooks/claude-code-hooks/tool-input-cache.ts
7228
7458
  var cache = new Map;
7229
7459
  var CACHE_TTL = 60000;
@@ -7256,6 +7486,27 @@ var sessionErrorState = new Map;
7256
7486
  var sessionInterruptState = new Map;
7257
7487
  function createClaudeCodeHooksHook(ctx, config = {}) {
7258
7488
  return {
7489
+ "experimental.session.compacting": async (input, output) => {
7490
+ if (isHookDisabled(config, "PreCompact")) {
7491
+ return;
7492
+ }
7493
+ const claudeConfig = await loadClaudeHooksConfig();
7494
+ const extendedConfig = await loadPluginExtendedConfig();
7495
+ const preCompactCtx = {
7496
+ sessionId: input.sessionID,
7497
+ cwd: ctx.directory
7498
+ };
7499
+ const result = await executePreCompactHooks(preCompactCtx, claudeConfig, extendedConfig);
7500
+ if (result.context.length > 0) {
7501
+ log("PreCompact hooks injecting context", {
7502
+ sessionID: input.sessionID,
7503
+ contextCount: result.context.length,
7504
+ hookName: result.hookName,
7505
+ elapsedMs: result.elapsedMs
7506
+ });
7507
+ output.context.push(...result.context);
7508
+ }
7509
+ },
7259
7510
  "chat.message": async (input, output) => {
7260
7511
  const interruptState = sessionInterruptState.get(input.sessionID);
7261
7512
  if (interruptState?.interrupted) {
@@ -7505,12 +7756,12 @@ import {
7505
7756
  realpathSync,
7506
7757
  statSync as statSync2
7507
7758
  } from "fs";
7508
- import { dirname as dirname4, join as join25, relative } from "path";
7759
+ import { dirname as dirname4, join as join26, relative } from "path";
7509
7760
 
7510
7761
  // src/hooks/rules-injector/constants.ts
7511
- import { join as join24 } from "path";
7512
- var OPENCODE_STORAGE6 = join24(xdgData2 ?? "", "opencode", "storage");
7513
- var RULES_INJECTOR_STORAGE = join24(OPENCODE_STORAGE6, "rules-injector");
7762
+ import { join as join25 } from "path";
7763
+ var OPENCODE_STORAGE6 = join25(xdgData ?? "", "opencode", "storage");
7764
+ var RULES_INJECTOR_STORAGE = join25(OPENCODE_STORAGE6, "rules-injector");
7514
7765
  var PROJECT_MARKERS = [
7515
7766
  ".git",
7516
7767
  "pyproject.toml",
@@ -7537,7 +7788,7 @@ function findProjectRoot(startPath) {
7537
7788
  }
7538
7789
  while (true) {
7539
7790
  for (const marker of PROJECT_MARKERS) {
7540
- const markerPath = join25(current, marker);
7791
+ const markerPath = join26(current, marker);
7541
7792
  if (existsSync19(markerPath)) {
7542
7793
  return current;
7543
7794
  }
@@ -7555,7 +7806,7 @@ function findRuleFilesRecursive(dir, results) {
7555
7806
  try {
7556
7807
  const entries = readdirSync6(dir, { withFileTypes: true });
7557
7808
  for (const entry of entries) {
7558
- const fullPath = join25(dir, entry.name);
7809
+ const fullPath = join26(dir, entry.name);
7559
7810
  if (entry.isDirectory()) {
7560
7811
  findRuleFilesRecursive(fullPath, results);
7561
7812
  } else if (entry.isFile()) {
@@ -7581,7 +7832,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7581
7832
  let distance = 0;
7582
7833
  while (true) {
7583
7834
  for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
7584
- const ruleDir = join25(currentDir, parent, subdir);
7835
+ const ruleDir = join26(currentDir, parent, subdir);
7585
7836
  const files = [];
7586
7837
  findRuleFilesRecursive(ruleDir, files);
7587
7838
  for (const filePath of files) {
@@ -7605,7 +7856,7 @@ function findRuleFiles(projectRoot, homeDir, currentFile) {
7605
7856
  currentDir = parentDir;
7606
7857
  distance++;
7607
7858
  }
7608
- const userRuleDir = join25(homeDir, USER_RULE_DIR);
7859
+ const userRuleDir = join26(homeDir, USER_RULE_DIR);
7609
7860
  const userFiles = [];
7610
7861
  findRuleFilesRecursive(userRuleDir, userFiles);
7611
7862
  for (const filePath of userFiles) {
@@ -7800,9 +8051,9 @@ import {
7800
8051
  writeFileSync as writeFileSync7,
7801
8052
  unlinkSync as unlinkSync6
7802
8053
  } from "fs";
7803
- import { join as join26 } from "path";
8054
+ import { join as join27 } from "path";
7804
8055
  function getStoragePath3(sessionID) {
7805
- return join26(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
8056
+ return join27(RULES_INJECTOR_STORAGE, `${sessionID}.json`);
7806
8057
  }
7807
8058
  function loadInjectedRules(sessionID) {
7808
8059
  const filePath = getStoragePath3(sessionID);
@@ -7849,12 +8100,12 @@ function createRulesInjectorHook(ctx) {
7849
8100
  }
7850
8101
  return sessionCaches.get(sessionID);
7851
8102
  }
7852
- function resolveFilePath2(path4) {
7853
- if (!path4)
8103
+ function resolveFilePath2(path5) {
8104
+ if (!path5)
7854
8105
  return null;
7855
- if (path4.startsWith("/"))
7856
- return path4;
7857
- return resolve4(ctx.directory, path4);
8106
+ if (path5.startsWith("/"))
8107
+ return path5;
8108
+ return resolve4(ctx.directory, path5);
7858
8109
  }
7859
8110
  function processFilePathForInjection(filePath, sessionID, output) {
7860
8111
  const resolved = resolveFilePath2(filePath);
@@ -7973,33 +8224,33 @@ function createBackgroundNotificationHook(manager) {
7973
8224
  }
7974
8225
  // src/hooks/auto-update-checker/checker.ts
7975
8226
  import * as fs5 from "fs";
7976
- import * as path5 from "path";
8227
+ import * as path6 from "path";
7977
8228
  import { fileURLToPath } from "url";
7978
8229
 
7979
8230
  // src/hooks/auto-update-checker/constants.ts
7980
- import * as path4 from "path";
7981
- import * as os4 from "os";
8231
+ import * as path5 from "path";
8232
+ import * as os5 from "os";
7982
8233
  var PACKAGE_NAME = "oh-my-opencode";
7983
8234
  var NPM_REGISTRY_URL = `https://registry.npmjs.org/-/package/${PACKAGE_NAME}/dist-tags`;
7984
8235
  var NPM_FETCH_TIMEOUT = 5000;
7985
8236
  function getCacheDir2() {
7986
8237
  if (process.platform === "win32") {
7987
- return path4.join(process.env.LOCALAPPDATA ?? os4.homedir(), "opencode");
8238
+ return path5.join(process.env.LOCALAPPDATA ?? os5.homedir(), "opencode");
7988
8239
  }
7989
- return path4.join(os4.homedir(), ".cache", "opencode");
8240
+ return path5.join(os5.homedir(), ".cache", "opencode");
7990
8241
  }
7991
8242
  var CACHE_DIR = getCacheDir2();
7992
- var VERSION_FILE = path4.join(CACHE_DIR, "version");
7993
- var INSTALLED_PACKAGE_JSON = path4.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
8243
+ var VERSION_FILE = path5.join(CACHE_DIR, "version");
8244
+ var INSTALLED_PACKAGE_JSON = path5.join(CACHE_DIR, "node_modules", PACKAGE_NAME, "package.json");
7994
8245
  function getUserConfigDir2() {
7995
8246
  if (process.platform === "win32") {
7996
- return process.env.APPDATA ?? path4.join(os4.homedir(), "AppData", "Roaming");
8247
+ return process.env.APPDATA ?? path5.join(os5.homedir(), "AppData", "Roaming");
7997
8248
  }
7998
- return process.env.XDG_CONFIG_HOME ?? path4.join(os4.homedir(), ".config");
8249
+ return process.env.XDG_CONFIG_HOME ?? path5.join(os5.homedir(), ".config");
7999
8250
  }
8000
8251
  var USER_CONFIG_DIR = getUserConfigDir2();
8001
- var USER_OPENCODE_CONFIG = path4.join(USER_CONFIG_DIR, "opencode", "opencode.json");
8002
- var USER_OPENCODE_CONFIG_JSONC = path4.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
8252
+ var USER_OPENCODE_CONFIG = path5.join(USER_CONFIG_DIR, "opencode", "opencode.json");
8253
+ var USER_OPENCODE_CONFIG_JSONC = path5.join(USER_CONFIG_DIR, "opencode", "opencode.jsonc");
8003
8254
 
8004
8255
  // src/hooks/auto-update-checker/checker.ts
8005
8256
  function stripJsonComments(json) {
@@ -8007,8 +8258,8 @@ function stripJsonComments(json) {
8007
8258
  }
8008
8259
  function getConfigPaths(directory) {
8009
8260
  return [
8010
- path5.join(directory, ".opencode", "opencode.json"),
8011
- path5.join(directory, ".opencode", "opencode.jsonc"),
8261
+ path6.join(directory, ".opencode", "opencode.json"),
8262
+ path6.join(directory, ".opencode", "opencode.jsonc"),
8012
8263
  USER_OPENCODE_CONFIG,
8013
8264
  USER_OPENCODE_CONFIG_JSONC
8014
8265
  ];
@@ -8039,9 +8290,9 @@ function getLocalDevPath(directory) {
8039
8290
  function findPackageJsonUp(startPath) {
8040
8291
  try {
8041
8292
  const stat = fs5.statSync(startPath);
8042
- let dir = stat.isDirectory() ? startPath : path5.dirname(startPath);
8293
+ let dir = stat.isDirectory() ? startPath : path6.dirname(startPath);
8043
8294
  for (let i = 0;i < 10; i++) {
8044
- const pkgPath = path5.join(dir, "package.json");
8295
+ const pkgPath = path6.join(dir, "package.json");
8045
8296
  if (fs5.existsSync(pkgPath)) {
8046
8297
  try {
8047
8298
  const content = fs5.readFileSync(pkgPath, "utf-8");
@@ -8050,7 +8301,7 @@ function findPackageJsonUp(startPath) {
8050
8301
  return pkgPath;
8051
8302
  } catch {}
8052
8303
  }
8053
- const parent = path5.dirname(dir);
8304
+ const parent = path6.dirname(dir);
8054
8305
  if (parent === dir)
8055
8306
  break;
8056
8307
  dir = parent;
@@ -8107,7 +8358,7 @@ function getCachedVersion() {
8107
8358
  }
8108
8359
  } catch {}
8109
8360
  try {
8110
- const currentDir = path5.dirname(fileURLToPath(import.meta.url));
8361
+ const currentDir = path6.dirname(fileURLToPath(import.meta.url));
8111
8362
  const pkgPath = findPackageJsonUp(currentDir);
8112
8363
  if (pkgPath) {
8113
8364
  const content = fs5.readFileSync(pkgPath, "utf-8");
@@ -8183,12 +8434,12 @@ async function getLatestVersion() {
8183
8434
 
8184
8435
  // src/hooks/auto-update-checker/cache.ts
8185
8436
  import * as fs6 from "fs";
8186
- import * as path6 from "path";
8437
+ import * as path7 from "path";
8187
8438
  function stripTrailingCommas(json) {
8188
8439
  return json.replace(/,(\s*[}\]])/g, "$1");
8189
8440
  }
8190
8441
  function removeFromBunLock(packageName) {
8191
- const lockPath = path6.join(CACHE_DIR, "bun.lock");
8442
+ const lockPath = path7.join(CACHE_DIR, "bun.lock");
8192
8443
  if (!fs6.existsSync(lockPath))
8193
8444
  return false;
8194
8445
  try {
@@ -8214,8 +8465,8 @@ function removeFromBunLock(packageName) {
8214
8465
  }
8215
8466
  function invalidatePackage(packageName = PACKAGE_NAME) {
8216
8467
  try {
8217
- const pkgDir = path6.join(CACHE_DIR, "node_modules", packageName);
8218
- const pkgJsonPath = path6.join(CACHE_DIR, "package.json");
8468
+ const pkgDir = path7.join(CACHE_DIR, "node_modules", packageName);
8469
+ const pkgJsonPath = path7.join(CACHE_DIR, "package.json");
8219
8470
  let packageRemoved = false;
8220
8471
  let dependencyRemoved = false;
8221
8472
  let lockRemoved = false;
@@ -8409,12 +8660,12 @@ import {
8409
8660
  writeFileSync as writeFileSync10,
8410
8661
  unlinkSync as unlinkSync7
8411
8662
  } from "fs";
8412
- import { join as join31 } from "path";
8663
+ import { join as join32 } from "path";
8413
8664
 
8414
8665
  // src/hooks/agent-usage-reminder/constants.ts
8415
- import { join as join30 } from "path";
8416
- var OPENCODE_STORAGE7 = join30(xdgData2 ?? "", "opencode", "storage");
8417
- var AGENT_USAGE_REMINDER_STORAGE = join30(OPENCODE_STORAGE7, "agent-usage-reminder");
8666
+ import { join as join31 } from "path";
8667
+ var OPENCODE_STORAGE7 = join31(xdgData ?? "", "opencode", "storage");
8668
+ var AGENT_USAGE_REMINDER_STORAGE = join31(OPENCODE_STORAGE7, "agent-usage-reminder");
8418
8669
  var TARGET_TOOLS = new Set([
8419
8670
  "grep",
8420
8671
  "safe_grep",
@@ -8459,7 +8710,7 @@ ALWAYS prefer: Multiple parallel background_task calls > Direct tool calls
8459
8710
 
8460
8711
  // src/hooks/agent-usage-reminder/storage.ts
8461
8712
  function getStoragePath4(sessionID) {
8462
- return join31(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
8713
+ return join32(AGENT_USAGE_REMINDER_STORAGE, `${sessionID}.json`);
8463
8714
  }
8464
8715
  function loadAgentUsageState(sessionID) {
8465
8716
  const filePath = getStoragePath4(sessionID);
@@ -8781,12 +9032,12 @@ import {
8781
9032
  writeFileSync as writeFileSync11,
8782
9033
  unlinkSync as unlinkSync8
8783
9034
  } from "fs";
8784
- import { join as join33 } from "path";
9035
+ import { join as join34 } from "path";
8785
9036
 
8786
9037
  // src/hooks/interactive-bash-session/constants.ts
8787
- import { join as join32 } from "path";
8788
- var OPENCODE_STORAGE8 = join32(xdgData2 ?? "", "opencode", "storage");
8789
- var INTERACTIVE_BASH_SESSION_STORAGE = join32(OPENCODE_STORAGE8, "interactive-bash-session");
9038
+ import { join as join33 } from "path";
9039
+ var OPENCODE_STORAGE8 = join33(xdgData ?? "", "opencode", "storage");
9040
+ var INTERACTIVE_BASH_SESSION_STORAGE = join33(OPENCODE_STORAGE8, "interactive-bash-session");
8790
9041
  var OMO_SESSION_PREFIX = "omo-";
8791
9042
  function buildSessionReminderMessage(sessions) {
8792
9043
  if (sessions.length === 0)
@@ -8798,7 +9049,7 @@ function buildSessionReminderMessage(sessions) {
8798
9049
 
8799
9050
  // src/hooks/interactive-bash-session/storage.ts
8800
9051
  function getStoragePath5(sessionID) {
8801
- return join33(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
9052
+ return join34(INTERACTIVE_BASH_SESSION_STORAGE, `${sessionID}.json`);
8802
9053
  }
8803
9054
  function loadInteractiveBashSessionState(sessionID) {
8804
9055
  const filePath = getStoragePath5(sessionID);
@@ -10776,7 +11027,7 @@ async function createGoogleAntigravityAuthPlugin({
10776
11027
  // src/features/claude-code-command-loader/loader.ts
10777
11028
  import { existsSync as existsSync25, readdirSync as readdirSync7, readFileSync as readFileSync15 } from "fs";
10778
11029
  import { homedir as homedir12 } from "os";
10779
- import { join as join34, basename } from "path";
11030
+ import { join as join35, basename } from "path";
10780
11031
  function loadCommandsFromDir(commandsDir, scope) {
10781
11032
  if (!existsSync25(commandsDir)) {
10782
11033
  return [];
@@ -10786,7 +11037,7 @@ function loadCommandsFromDir(commandsDir, scope) {
10786
11037
  for (const entry of entries) {
10787
11038
  if (!isMarkdownFile(entry))
10788
11039
  continue;
10789
- const commandPath = join34(commandsDir, entry.name);
11040
+ const commandPath = join35(commandsDir, entry.name);
10790
11041
  const commandName = basename(entry.name, ".md");
10791
11042
  try {
10792
11043
  const content = readFileSync15(commandPath, "utf-8");
@@ -10829,29 +11080,29 @@ function commandsToRecord(commands) {
10829
11080
  return result;
10830
11081
  }
10831
11082
  function loadUserCommands() {
10832
- const userCommandsDir = join34(homedir12(), ".claude", "commands");
11083
+ const userCommandsDir = join35(homedir12(), ".claude", "commands");
10833
11084
  const commands = loadCommandsFromDir(userCommandsDir, "user");
10834
11085
  return commandsToRecord(commands);
10835
11086
  }
10836
11087
  function loadProjectCommands() {
10837
- const projectCommandsDir = join34(process.cwd(), ".claude", "commands");
11088
+ const projectCommandsDir = join35(process.cwd(), ".claude", "commands");
10838
11089
  const commands = loadCommandsFromDir(projectCommandsDir, "project");
10839
11090
  return commandsToRecord(commands);
10840
11091
  }
10841
11092
  function loadOpencodeGlobalCommands() {
10842
- const opencodeCommandsDir = join34(homedir12(), ".config", "opencode", "command");
11093
+ const opencodeCommandsDir = join35(homedir12(), ".config", "opencode", "command");
10843
11094
  const commands = loadCommandsFromDir(opencodeCommandsDir, "opencode");
10844
11095
  return commandsToRecord(commands);
10845
11096
  }
10846
11097
  function loadOpencodeProjectCommands() {
10847
- const opencodeProjectDir = join34(process.cwd(), ".opencode", "command");
11098
+ const opencodeProjectDir = join35(process.cwd(), ".opencode", "command");
10848
11099
  const commands = loadCommandsFromDir(opencodeProjectDir, "opencode-project");
10849
11100
  return commandsToRecord(commands);
10850
11101
  }
10851
11102
  // src/features/claude-code-skill-loader/loader.ts
10852
11103
  import { existsSync as existsSync26, readdirSync as readdirSync8, readFileSync as readFileSync16 } from "fs";
10853
11104
  import { homedir as homedir13 } from "os";
10854
- import { join as join35 } from "path";
11105
+ import { join as join36 } from "path";
10855
11106
  function loadSkillsFromDir(skillsDir, scope) {
10856
11107
  if (!existsSync26(skillsDir)) {
10857
11108
  return [];
@@ -10861,11 +11112,11 @@ function loadSkillsFromDir(skillsDir, scope) {
10861
11112
  for (const entry of entries) {
10862
11113
  if (entry.name.startsWith("."))
10863
11114
  continue;
10864
- const skillPath = join35(skillsDir, entry.name);
11115
+ const skillPath = join36(skillsDir, entry.name);
10865
11116
  if (!entry.isDirectory() && !entry.isSymbolicLink())
10866
11117
  continue;
10867
11118
  const resolvedPath = resolveSymlink(skillPath);
10868
- const skillMdPath = join35(resolvedPath, "SKILL.md");
11119
+ const skillMdPath = join36(resolvedPath, "SKILL.md");
10869
11120
  if (!existsSync26(skillMdPath))
10870
11121
  continue;
10871
11122
  try {
@@ -10903,7 +11154,7 @@ $ARGUMENTS
10903
11154
  return skills;
10904
11155
  }
10905
11156
  function loadUserSkillsAsCommands() {
10906
- const userSkillsDir = join35(homedir13(), ".claude", "skills");
11157
+ const userSkillsDir = join36(homedir13(), ".claude", "skills");
10907
11158
  const skills = loadSkillsFromDir(userSkillsDir, "user");
10908
11159
  return skills.reduce((acc, skill) => {
10909
11160
  acc[skill.name] = skill.definition;
@@ -10911,7 +11162,7 @@ function loadUserSkillsAsCommands() {
10911
11162
  }, {});
10912
11163
  }
10913
11164
  function loadProjectSkillsAsCommands() {
10914
- const projectSkillsDir = join35(process.cwd(), ".claude", "skills");
11165
+ const projectSkillsDir = join36(process.cwd(), ".claude", "skills");
10915
11166
  const skills = loadSkillsFromDir(projectSkillsDir, "project");
10916
11167
  return skills.reduce((acc, skill) => {
10917
11168
  acc[skill.name] = skill.definition;
@@ -10921,7 +11172,7 @@ function loadProjectSkillsAsCommands() {
10921
11172
  // src/features/claude-code-agent-loader/loader.ts
10922
11173
  import { existsSync as existsSync27, readdirSync as readdirSync9, readFileSync as readFileSync17 } from "fs";
10923
11174
  import { homedir as homedir14 } from "os";
10924
- import { join as join36, basename as basename2 } from "path";
11175
+ import { join as join37, basename as basename2 } from "path";
10925
11176
  function parseToolsConfig(toolsStr) {
10926
11177
  if (!toolsStr)
10927
11178
  return;
@@ -10943,7 +11194,7 @@ function loadAgentsFromDir(agentsDir, scope) {
10943
11194
  for (const entry of entries) {
10944
11195
  if (!isMarkdownFile(entry))
10945
11196
  continue;
10946
- const agentPath = join36(agentsDir, entry.name);
11197
+ const agentPath = join37(agentsDir, entry.name);
10947
11198
  const agentName = basename2(entry.name, ".md");
10948
11199
  try {
10949
11200
  const content = readFileSync17(agentPath, "utf-8");
@@ -10973,7 +11224,7 @@ function loadAgentsFromDir(agentsDir, scope) {
10973
11224
  return agents;
10974
11225
  }
10975
11226
  function loadUserAgents() {
10976
- const userAgentsDir = join36(homedir14(), ".claude", "agents");
11227
+ const userAgentsDir = join37(homedir14(), ".claude", "agents");
10977
11228
  const agents = loadAgentsFromDir(userAgentsDir, "user");
10978
11229
  const result = {};
10979
11230
  for (const agent of agents) {
@@ -10982,7 +11233,7 @@ function loadUserAgents() {
10982
11233
  return result;
10983
11234
  }
10984
11235
  function loadProjectAgents() {
10985
- const projectAgentsDir = join36(process.cwd(), ".claude", "agents");
11236
+ const projectAgentsDir = join37(process.cwd(), ".claude", "agents");
10986
11237
  const agents = loadAgentsFromDir(projectAgentsDir, "project");
10987
11238
  const result = {};
10988
11239
  for (const agent of agents) {
@@ -10993,7 +11244,7 @@ function loadProjectAgents() {
10993
11244
  // src/features/claude-code-mcp-loader/loader.ts
10994
11245
  import { existsSync as existsSync28 } from "fs";
10995
11246
  import { homedir as homedir15 } from "os";
10996
- import { join as join37 } from "path";
11247
+ import { join as join38 } from "path";
10997
11248
 
10998
11249
  // src/features/claude-code-mcp-loader/env-expander.ts
10999
11250
  function expandEnvVars(value) {
@@ -11062,9 +11313,9 @@ function getMcpConfigPaths() {
11062
11313
  const home = homedir15();
11063
11314
  const cwd = process.cwd();
11064
11315
  return [
11065
- { path: join37(home, ".claude", ".mcp.json"), scope: "user" },
11066
- { path: join37(cwd, ".mcp.json"), scope: "project" },
11067
- { path: join37(cwd, ".claude", ".mcp.json"), scope: "local" }
11316
+ { path: join38(home, ".claude", ".mcp.json"), scope: "user" },
11317
+ { path: join38(cwd, ".mcp.json"), scope: "project" },
11318
+ { path: join38(cwd, ".claude", ".mcp.json"), scope: "local" }
11068
11319
  ];
11069
11320
  }
11070
11321
  async function loadMcpConfigFile(filePath) {
@@ -11083,13 +11334,13 @@ async function loadMcpConfigs() {
11083
11334
  const servers = {};
11084
11335
  const loadedServers = [];
11085
11336
  const paths = getMcpConfigPaths();
11086
- for (const { path: path7, scope } of paths) {
11087
- const config = await loadMcpConfigFile(path7);
11337
+ for (const { path: path8, scope } of paths) {
11338
+ const config = await loadMcpConfigFile(path8);
11088
11339
  if (!config?.mcpServers)
11089
11340
  continue;
11090
11341
  for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
11091
11342
  if (serverConfig.disabled) {
11092
- log(`Skipping disabled MCP server "${name}"`, { path: path7 });
11343
+ log(`Skipping disabled MCP server "${name}"`, { path: path8 });
11093
11344
  continue;
11094
11345
  }
11095
11346
  try {
@@ -11100,7 +11351,7 @@ async function loadMcpConfigs() {
11100
11351
  loadedServers.splice(existingIndex, 1);
11101
11352
  }
11102
11353
  loadedServers.push({ name, scope, config: transformed });
11103
- log(`Loaded MCP server "${name}" from ${scope}`, { path: path7 });
11354
+ log(`Loaded MCP server "${name}" from ${scope}`, { path: path8 });
11104
11355
  } catch (error) {
11105
11356
  log(`Failed to transform MCP server "${name}"`, error);
11106
11357
  }
@@ -11404,13 +11655,13 @@ var EXT_TO_LANG = {
11404
11655
  };
11405
11656
  // src/tools/lsp/config.ts
11406
11657
  import { existsSync as existsSync29, readFileSync as readFileSync18 } from "fs";
11407
- import { join as join38 } from "path";
11658
+ import { join as join39 } from "path";
11408
11659
  import { homedir as homedir16 } from "os";
11409
- function loadJsonFile(path7) {
11410
- if (!existsSync29(path7))
11660
+ function loadJsonFile(path8) {
11661
+ if (!existsSync29(path8))
11411
11662
  return null;
11412
11663
  try {
11413
- return JSON.parse(readFileSync18(path7, "utf-8"));
11664
+ return JSON.parse(readFileSync18(path8, "utf-8"));
11414
11665
  } catch {
11415
11666
  return null;
11416
11667
  }
@@ -11418,9 +11669,9 @@ function loadJsonFile(path7) {
11418
11669
  function getConfigPaths2() {
11419
11670
  const cwd = process.cwd();
11420
11671
  return {
11421
- project: join38(cwd, ".opencode", "oh-my-opencode.json"),
11422
- user: join38(homedir16(), ".config", "opencode", "oh-my-opencode.json"),
11423
- opencode: join38(homedir16(), ".config", "opencode", "opencode.json")
11672
+ project: join39(cwd, ".opencode", "oh-my-opencode.json"),
11673
+ user: join39(homedir16(), ".config", "opencode", "oh-my-opencode.json"),
11674
+ opencode: join39(homedir16(), ".config", "opencode", "opencode.json")
11424
11675
  };
11425
11676
  }
11426
11677
  function loadAllConfigs() {
@@ -11516,18 +11767,18 @@ function isServerInstalled(command) {
11516
11767
  const pathSeparator = isWindows2 ? ";" : ":";
11517
11768
  const paths = pathEnv.split(pathSeparator);
11518
11769
  for (const p of paths) {
11519
- if (existsSync29(join38(p, cmd)) || existsSync29(join38(p, cmd + ext))) {
11770
+ if (existsSync29(join39(p, cmd)) || existsSync29(join39(p, cmd + ext))) {
11520
11771
  return true;
11521
11772
  }
11522
11773
  }
11523
11774
  const cwd = process.cwd();
11524
11775
  const additionalPaths = [
11525
- join38(cwd, "node_modules", ".bin", cmd),
11526
- join38(cwd, "node_modules", ".bin", cmd + ext),
11527
- join38(homedir16(), ".config", "opencode", "bin", cmd),
11528
- join38(homedir16(), ".config", "opencode", "bin", cmd + ext),
11529
- join38(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd),
11530
- join38(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
11776
+ join39(cwd, "node_modules", ".bin", cmd),
11777
+ join39(cwd, "node_modules", ".bin", cmd + ext),
11778
+ join39(homedir16(), ".config", "opencode", "bin", cmd),
11779
+ join39(homedir16(), ".config", "opencode", "bin", cmd + ext),
11780
+ join39(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd),
11781
+ join39(homedir16(), ".config", "opencode", "node_modules", ".bin", cmd + ext)
11531
11782
  ];
11532
11783
  for (const p of additionalPaths) {
11533
11784
  if (existsSync29(p)) {
@@ -13139,10 +13390,10 @@ function mergeDefs(...defs) {
13139
13390
  function cloneDef(schema) {
13140
13391
  return mergeDefs(schema._zod.def);
13141
13392
  }
13142
- function getElementAtPath(obj, path7) {
13143
- if (!path7)
13393
+ function getElementAtPath(obj, path8) {
13394
+ if (!path8)
13144
13395
  return obj;
13145
- return path7.reduce((acc, key) => acc?.[key], obj);
13396
+ return path8.reduce((acc, key) => acc?.[key], obj);
13146
13397
  }
13147
13398
  function promiseAllObject(promisesObj) {
13148
13399
  const keys = Object.keys(promisesObj);
@@ -13501,11 +13752,11 @@ function aborted(x, startIndex = 0) {
13501
13752
  }
13502
13753
  return false;
13503
13754
  }
13504
- function prefixIssues(path7, issues) {
13755
+ function prefixIssues(path8, issues) {
13505
13756
  return issues.map((iss) => {
13506
13757
  var _a;
13507
13758
  (_a = iss).path ?? (_a.path = []);
13508
- iss.path.unshift(path7);
13759
+ iss.path.unshift(path8);
13509
13760
  return iss;
13510
13761
  });
13511
13762
  }
@@ -13673,7 +13924,7 @@ function treeifyError(error, _mapper) {
13673
13924
  return issue2.message;
13674
13925
  };
13675
13926
  const result = { errors: [] };
13676
- const processError = (error2, path7 = []) => {
13927
+ const processError = (error2, path8 = []) => {
13677
13928
  var _a, _b;
13678
13929
  for (const issue2 of error2.issues) {
13679
13930
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -13683,7 +13934,7 @@ function treeifyError(error, _mapper) {
13683
13934
  } else if (issue2.code === "invalid_element") {
13684
13935
  processError({ issues: issue2.issues }, issue2.path);
13685
13936
  } else {
13686
- const fullpath = [...path7, ...issue2.path];
13937
+ const fullpath = [...path8, ...issue2.path];
13687
13938
  if (fullpath.length === 0) {
13688
13939
  result.errors.push(mapper(issue2));
13689
13940
  continue;
@@ -13715,8 +13966,8 @@ function treeifyError(error, _mapper) {
13715
13966
  }
13716
13967
  function toDotPath(_path) {
13717
13968
  const segs = [];
13718
- const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13719
- for (const seg of path7) {
13969
+ const path8 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
13970
+ for (const seg of path8) {
13720
13971
  if (typeof seg === "number")
13721
13972
  segs.push(`[${seg}]`);
13722
13973
  else if (typeof seg === "symbol")
@@ -25059,13 +25310,13 @@ var lsp_code_action_resolve = tool({
25059
25310
  });
25060
25311
  // src/tools/ast-grep/constants.ts
25061
25312
  import { createRequire as createRequire4 } from "module";
25062
- import { dirname as dirname6, join as join40 } from "path";
25313
+ import { dirname as dirname6, join as join41 } from "path";
25063
25314
  import { existsSync as existsSync32, statSync as statSync4 } from "fs";
25064
25315
 
25065
25316
  // src/tools/ast-grep/downloader.ts
25066
25317
  var {spawn: spawn5 } = globalThis.Bun;
25067
25318
  import { existsSync as existsSync31, mkdirSync as mkdirSync10, chmodSync as chmodSync2, unlinkSync as unlinkSync9 } from "fs";
25068
- import { join as join39 } from "path";
25319
+ import { join as join40 } from "path";
25069
25320
  import { homedir as homedir17 } from "os";
25070
25321
  import { createRequire as createRequire3 } from "module";
25071
25322
  var REPO2 = "ast-grep/ast-grep";
@@ -25091,18 +25342,18 @@ var PLATFORM_MAP2 = {
25091
25342
  function getCacheDir3() {
25092
25343
  if (process.platform === "win32") {
25093
25344
  const localAppData = process.env.LOCALAPPDATA || process.env.APPDATA;
25094
- const base2 = localAppData || join39(homedir17(), "AppData", "Local");
25095
- return join39(base2, "oh-my-opencode", "bin");
25345
+ const base2 = localAppData || join40(homedir17(), "AppData", "Local");
25346
+ return join40(base2, "oh-my-opencode", "bin");
25096
25347
  }
25097
25348
  const xdgCache2 = process.env.XDG_CACHE_HOME;
25098
- const base = xdgCache2 || join39(homedir17(), ".cache");
25099
- return join39(base, "oh-my-opencode", "bin");
25349
+ const base = xdgCache2 || join40(homedir17(), ".cache");
25350
+ return join40(base, "oh-my-opencode", "bin");
25100
25351
  }
25101
25352
  function getBinaryName3() {
25102
25353
  return process.platform === "win32" ? "sg.exe" : "sg";
25103
25354
  }
25104
25355
  function getCachedBinaryPath2() {
25105
- const binaryPath = join39(getCacheDir3(), getBinaryName3());
25356
+ const binaryPath = join40(getCacheDir3(), getBinaryName3());
25106
25357
  return existsSync31(binaryPath) ? binaryPath : null;
25107
25358
  }
25108
25359
  async function extractZip2(archivePath, destDir) {
@@ -25129,12 +25380,12 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
25129
25380
  }
25130
25381
  const cacheDir = getCacheDir3();
25131
25382
  const binaryName = getBinaryName3();
25132
- const binaryPath = join39(cacheDir, binaryName);
25383
+ const binaryPath = join40(cacheDir, binaryName);
25133
25384
  if (existsSync31(binaryPath)) {
25134
25385
  return binaryPath;
25135
25386
  }
25136
- const { arch, os: os5 } = platformInfo;
25137
- const assetName = `app-${arch}-${os5}.zip`;
25387
+ const { arch, os: os6 } = platformInfo;
25388
+ const assetName = `app-${arch}-${os6}.zip`;
25138
25389
  const downloadUrl = `https://github.com/${REPO2}/releases/download/${version2}/${assetName}`;
25139
25390
  console.log(`[oh-my-opencode] Downloading ast-grep binary...`);
25140
25391
  try {
@@ -25145,7 +25396,7 @@ async function downloadAstGrep(version2 = DEFAULT_VERSION) {
25145
25396
  if (!response2.ok) {
25146
25397
  throw new Error(`HTTP ${response2.status}: ${response2.statusText}`);
25147
25398
  }
25148
- const archivePath = join39(cacheDir, assetName);
25399
+ const archivePath = join40(cacheDir, assetName);
25149
25400
  const arrayBuffer = await response2.arrayBuffer();
25150
25401
  await Bun.write(archivePath, arrayBuffer);
25151
25402
  await extractZip2(archivePath, cacheDir);
@@ -25203,7 +25454,7 @@ function findSgCliPathSync() {
25203
25454
  const require2 = createRequire4(import.meta.url);
25204
25455
  const cliPkgPath = require2.resolve("@ast-grep/cli/package.json");
25205
25456
  const cliDir = dirname6(cliPkgPath);
25206
- const sgPath = join40(cliDir, binaryName);
25457
+ const sgPath = join41(cliDir, binaryName);
25207
25458
  if (existsSync32(sgPath) && isValidBinary(sgPath)) {
25208
25459
  return sgPath;
25209
25460
  }
@@ -25215,7 +25466,7 @@ function findSgCliPathSync() {
25215
25466
  const pkgPath = require2.resolve(`${platformPkg}/package.json`);
25216
25467
  const pkgDir = dirname6(pkgPath);
25217
25468
  const astGrepName = process.platform === "win32" ? "ast-grep.exe" : "ast-grep";
25218
- const binaryPath = join40(pkgDir, astGrepName);
25469
+ const binaryPath = join41(pkgDir, astGrepName);
25219
25470
  if (existsSync32(binaryPath) && isValidBinary(binaryPath)) {
25220
25471
  return binaryPath;
25221
25472
  }
@@ -25223,9 +25474,9 @@ function findSgCliPathSync() {
25223
25474
  }
25224
25475
  if (process.platform === "darwin") {
25225
25476
  const homebrewPaths = ["/opt/homebrew/bin/sg", "/usr/local/bin/sg"];
25226
- for (const path7 of homebrewPaths) {
25227
- if (existsSync32(path7) && isValidBinary(path7)) {
25228
- return path7;
25477
+ for (const path8 of homebrewPaths) {
25478
+ if (existsSync32(path8) && isValidBinary(path8)) {
25479
+ return path8;
25229
25480
  }
25230
25481
  }
25231
25482
  }
@@ -25243,8 +25494,8 @@ function getSgCliPath() {
25243
25494
  }
25244
25495
  return "sg";
25245
25496
  }
25246
- function setSgCliPath(path7) {
25247
- resolvedCliPath2 = path7;
25497
+ function setSgCliPath(path8) {
25498
+ resolvedCliPath2 = path8;
25248
25499
  }
25249
25500
  var SG_CLI_PATH = getSgCliPath();
25250
25501
  var CLI_LANGUAGES = [
@@ -25591,19 +25842,19 @@ var {spawn: spawn7 } = globalThis.Bun;
25591
25842
 
25592
25843
  // src/tools/grep/constants.ts
25593
25844
  import { existsSync as existsSync35 } from "fs";
25594
- import { join as join42, dirname as dirname7 } from "path";
25845
+ import { join as join43, dirname as dirname7 } from "path";
25595
25846
  import { spawnSync } from "child_process";
25596
25847
 
25597
25848
  // src/tools/grep/downloader.ts
25598
25849
  import { existsSync as existsSync34, mkdirSync as mkdirSync11, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync10 } from "fs";
25599
- import { join as join41 } from "path";
25850
+ import { join as join42 } from "path";
25600
25851
  function getInstallDir() {
25601
25852
  const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
25602
- return join41(homeDir, ".cache", "oh-my-opencode", "bin");
25853
+ return join42(homeDir, ".cache", "oh-my-opencode", "bin");
25603
25854
  }
25604
25855
  function getRgPath() {
25605
25856
  const isWindows2 = process.platform === "win32";
25606
- return join41(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
25857
+ return join42(getInstallDir(), isWindows2 ? "rg.exe" : "rg");
25607
25858
  }
25608
25859
  function getInstalledRipgrepPath() {
25609
25860
  const rgPath = getRgPath();
@@ -25630,10 +25881,10 @@ function getOpenCodeBundledRg() {
25630
25881
  const isWindows2 = process.platform === "win32";
25631
25882
  const rgName = isWindows2 ? "rg.exe" : "rg";
25632
25883
  const candidates = [
25633
- join42(execDir, rgName),
25634
- join42(execDir, "bin", rgName),
25635
- join42(execDir, "..", "bin", rgName),
25636
- join42(execDir, "..", "libexec", rgName)
25884
+ join43(execDir, rgName),
25885
+ join43(execDir, "bin", rgName),
25886
+ join43(execDir, "..", "bin", rgName),
25887
+ join43(execDir, "..", "libexec", rgName)
25637
25888
  ];
25638
25889
  for (const candidate of candidates) {
25639
25890
  if (existsSync35(candidate)) {
@@ -26041,7 +26292,7 @@ var glob = tool({
26041
26292
  // src/tools/slashcommand/tools.ts
26042
26293
  import { existsSync as existsSync36, readdirSync as readdirSync11, readFileSync as readFileSync21 } from "fs";
26043
26294
  import { homedir as homedir18 } from "os";
26044
- import { join as join43, basename as basename3, dirname as dirname8 } from "path";
26295
+ import { join as join44, basename as basename3, dirname as dirname8 } from "path";
26045
26296
  function discoverCommandsFromDir(commandsDir, scope) {
26046
26297
  if (!existsSync36(commandsDir)) {
26047
26298
  return [];
@@ -26051,7 +26302,7 @@ function discoverCommandsFromDir(commandsDir, scope) {
26051
26302
  for (const entry of entries) {
26052
26303
  if (!isMarkdownFile(entry))
26053
26304
  continue;
26054
- const commandPath = join43(commandsDir, entry.name);
26305
+ const commandPath = join44(commandsDir, entry.name);
26055
26306
  const commandName = basename3(entry.name, ".md");
26056
26307
  try {
26057
26308
  const content = readFileSync21(commandPath, "utf-8");
@@ -26079,10 +26330,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
26079
26330
  return commands;
26080
26331
  }
26081
26332
  function discoverCommandsSync() {
26082
- const userCommandsDir = join43(homedir18(), ".claude", "commands");
26083
- const projectCommandsDir = join43(process.cwd(), ".claude", "commands");
26084
- const opencodeGlobalDir = join43(homedir18(), ".config", "opencode", "command");
26085
- const opencodeProjectDir = join43(process.cwd(), ".opencode", "command");
26333
+ const userCommandsDir = join44(homedir18(), ".claude", "commands");
26334
+ const projectCommandsDir = join44(process.cwd(), ".claude", "commands");
26335
+ const opencodeGlobalDir = join44(homedir18(), ".config", "opencode", "command");
26336
+ const opencodeProjectDir = join44(process.cwd(), ".opencode", "command");
26086
26337
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
26087
26338
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
26088
26339
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -26205,6 +26456,484 @@ Provide a command name to execute.`;
26205
26456
  Try a different command name.`;
26206
26457
  }
26207
26458
  });
26459
+ // src/tools/session-manager/constants.ts
26460
+ import { join as join45 } from "path";
26461
+ import { homedir as homedir19 } from "os";
26462
+ var OPENCODE_STORAGE9 = getOpenCodeStorageDir();
26463
+ var MESSAGE_STORAGE4 = join45(OPENCODE_STORAGE9, "message");
26464
+ var PART_STORAGE4 = join45(OPENCODE_STORAGE9, "part");
26465
+ var TODO_DIR2 = join45(homedir19(), ".claude", "todos");
26466
+ var TRANSCRIPT_DIR2 = join45(homedir19(), ".claude", "transcripts");
26467
+ var SESSION_LIST_DESCRIPTION = `List all OpenCode sessions with optional filtering.
26468
+
26469
+ Returns a list of available session IDs with metadata including message count, date range, and agents used.
26470
+
26471
+ Arguments:
26472
+ - limit (optional): Maximum number of sessions to return
26473
+ - from_date (optional): Filter sessions from this date (ISO 8601 format)
26474
+ - to_date (optional): Filter sessions until this date (ISO 8601 format)
26475
+
26476
+ Example output:
26477
+ | Session ID | Messages | First | Last | Agents |
26478
+ |------------|----------|-------|------|--------|
26479
+ | ses_abc123 | 45 | 2025-12-20 | 2025-12-24 | build, oracle |
26480
+ | ses_def456 | 12 | 2025-12-19 | 2025-12-19 | build |`;
26481
+ var SESSION_READ_DESCRIPTION = `Read messages and history from an OpenCode session.
26482
+
26483
+ Returns a formatted view of session messages with role, timestamp, and content. Optionally includes todos and transcript data.
26484
+
26485
+ Arguments:
26486
+ - session_id (required): Session ID to read
26487
+ - include_todos (optional): Include todo list if available (default: false)
26488
+ - include_transcript (optional): Include transcript log if available (default: false)
26489
+ - limit (optional): Maximum number of messages to return (default: all)
26490
+
26491
+ Example output:
26492
+ Session: ses_abc123
26493
+ Messages: 45
26494
+ Date Range: 2025-12-20 to 2025-12-24
26495
+
26496
+ [Message 1] user (2025-12-20 10:30:00)
26497
+ Hello, can you help me with...
26498
+
26499
+ [Message 2] assistant (2025-12-20 10:30:15)
26500
+ Of course! Let me help you with...`;
26501
+ var SESSION_SEARCH_DESCRIPTION = `Search for content within OpenCode session messages.
26502
+
26503
+ Performs full-text search across session messages and returns matching excerpts with context.
26504
+
26505
+ Arguments:
26506
+ - query (required): Search query string
26507
+ - session_id (optional): Search within specific session only (default: all sessions)
26508
+ - case_sensitive (optional): Case-sensitive search (default: false)
26509
+ - limit (optional): Maximum number of results to return (default: 20)
26510
+
26511
+ Example output:
26512
+ Found 3 matches across 2 sessions:
26513
+
26514
+ [ses_abc123] Message msg_001 (user)
26515
+ ...implement the **session manager** tool...
26516
+
26517
+ [ses_abc123] Message msg_005 (assistant)
26518
+ ...I'll create a **session manager** with full search...
26519
+
26520
+ [ses_def456] Message msg_012 (user)
26521
+ ...use the **session manager** to find...`;
26522
+ var SESSION_INFO_DESCRIPTION = `Get metadata and statistics about an OpenCode session.
26523
+
26524
+ Returns detailed information about a session including message count, date range, agents used, and available data sources.
26525
+
26526
+ Arguments:
26527
+ - session_id (required): Session ID to inspect
26528
+
26529
+ Example output:
26530
+ Session ID: ses_abc123
26531
+ Messages: 45
26532
+ Date Range: 2025-12-20 10:30:00 to 2025-12-24 15:45:30
26533
+ Duration: 4 days, 5 hours
26534
+ Agents Used: build, oracle, librarian
26535
+ Has Todos: Yes (12 items, 8 completed)
26536
+ Has Transcript: Yes (234 entries)`;
26537
+
26538
+ // src/tools/session-manager/storage.ts
26539
+ import { existsSync as existsSync37, readdirSync as readdirSync12, readFileSync as readFileSync22 } from "fs";
26540
+ import { join as join46 } from "path";
26541
+ function getAllSessions() {
26542
+ if (!existsSync37(MESSAGE_STORAGE4))
26543
+ return [];
26544
+ const sessions = [];
26545
+ function scanDirectory(dir) {
26546
+ try {
26547
+ for (const entry of readdirSync12(dir, { withFileTypes: true })) {
26548
+ if (entry.isDirectory()) {
26549
+ const sessionPath = join46(dir, entry.name);
26550
+ const files = readdirSync12(sessionPath);
26551
+ if (files.some((f) => f.endsWith(".json"))) {
26552
+ sessions.push(entry.name);
26553
+ } else {
26554
+ scanDirectory(sessionPath);
26555
+ }
26556
+ }
26557
+ }
26558
+ } catch {
26559
+ return;
26560
+ }
26561
+ }
26562
+ scanDirectory(MESSAGE_STORAGE4);
26563
+ return [...new Set(sessions)];
26564
+ }
26565
+ function getMessageDir5(sessionID) {
26566
+ if (!existsSync37(MESSAGE_STORAGE4))
26567
+ return "";
26568
+ const directPath = join46(MESSAGE_STORAGE4, sessionID);
26569
+ if (existsSync37(directPath)) {
26570
+ return directPath;
26571
+ }
26572
+ for (const dir of readdirSync12(MESSAGE_STORAGE4)) {
26573
+ const sessionPath = join46(MESSAGE_STORAGE4, dir, sessionID);
26574
+ if (existsSync37(sessionPath)) {
26575
+ return sessionPath;
26576
+ }
26577
+ }
26578
+ return "";
26579
+ }
26580
+ function sessionExists(sessionID) {
26581
+ return getMessageDir5(sessionID) !== "";
26582
+ }
26583
+ function readSessionMessages(sessionID) {
26584
+ const messageDir = getMessageDir5(sessionID);
26585
+ if (!messageDir || !existsSync37(messageDir))
26586
+ return [];
26587
+ const messages = [];
26588
+ for (const file2 of readdirSync12(messageDir)) {
26589
+ if (!file2.endsWith(".json"))
26590
+ continue;
26591
+ try {
26592
+ const content = readFileSync22(join46(messageDir, file2), "utf-8");
26593
+ const meta = JSON.parse(content);
26594
+ const parts = readParts2(meta.id);
26595
+ messages.push({
26596
+ id: meta.id,
26597
+ role: meta.role,
26598
+ agent: meta.agent,
26599
+ time: meta.time,
26600
+ parts
26601
+ });
26602
+ } catch {
26603
+ continue;
26604
+ }
26605
+ }
26606
+ return messages.sort((a, b) => {
26607
+ const aTime = a.time?.created ?? 0;
26608
+ const bTime = b.time?.created ?? 0;
26609
+ if (aTime !== bTime)
26610
+ return aTime - bTime;
26611
+ return a.id.localeCompare(b.id);
26612
+ });
26613
+ }
26614
+ function readParts2(messageID) {
26615
+ const partDir = join46(PART_STORAGE4, messageID);
26616
+ if (!existsSync37(partDir))
26617
+ return [];
26618
+ const parts = [];
26619
+ for (const file2 of readdirSync12(partDir)) {
26620
+ if (!file2.endsWith(".json"))
26621
+ continue;
26622
+ try {
26623
+ const content = readFileSync22(join46(partDir, file2), "utf-8");
26624
+ parts.push(JSON.parse(content));
26625
+ } catch {
26626
+ continue;
26627
+ }
26628
+ }
26629
+ return parts.sort((a, b) => a.id.localeCompare(b.id));
26630
+ }
26631
+ function readSessionTodos(sessionID) {
26632
+ if (!existsSync37(TODO_DIR2))
26633
+ return [];
26634
+ const todoFiles = readdirSync12(TODO_DIR2).filter((f) => f.includes(sessionID) && f.endsWith(".json"));
26635
+ for (const file2 of todoFiles) {
26636
+ try {
26637
+ const content = readFileSync22(join46(TODO_DIR2, file2), "utf-8");
26638
+ const data = JSON.parse(content);
26639
+ if (Array.isArray(data)) {
26640
+ return data.map((item) => ({
26641
+ id: item.id || "",
26642
+ content: item.content || "",
26643
+ status: item.status || "pending",
26644
+ priority: item.priority
26645
+ }));
26646
+ }
26647
+ } catch {
26648
+ continue;
26649
+ }
26650
+ }
26651
+ return [];
26652
+ }
26653
+ function readSessionTranscript(sessionID) {
26654
+ if (!existsSync37(TRANSCRIPT_DIR2))
26655
+ return 0;
26656
+ const transcriptFile = join46(TRANSCRIPT_DIR2, `${sessionID}.jsonl`);
26657
+ if (!existsSync37(transcriptFile))
26658
+ return 0;
26659
+ try {
26660
+ const content = readFileSync22(transcriptFile, "utf-8");
26661
+ return content.trim().split(`
26662
+ `).filter(Boolean).length;
26663
+ } catch {
26664
+ return 0;
26665
+ }
26666
+ }
26667
+ function getSessionInfo(sessionID) {
26668
+ const messages = readSessionMessages(sessionID);
26669
+ if (messages.length === 0)
26670
+ return null;
26671
+ const agentsUsed = new Set;
26672
+ let firstMessage;
26673
+ let lastMessage;
26674
+ for (const msg of messages) {
26675
+ if (msg.agent)
26676
+ agentsUsed.add(msg.agent);
26677
+ if (msg.time?.created) {
26678
+ const date5 = new Date(msg.time.created);
26679
+ if (!firstMessage || date5 < firstMessage)
26680
+ firstMessage = date5;
26681
+ if (!lastMessage || date5 > lastMessage)
26682
+ lastMessage = date5;
26683
+ }
26684
+ }
26685
+ const todos = readSessionTodos(sessionID);
26686
+ const transcriptEntries = readSessionTranscript(sessionID);
26687
+ return {
26688
+ id: sessionID,
26689
+ message_count: messages.length,
26690
+ first_message: firstMessage,
26691
+ last_message: lastMessage,
26692
+ agents_used: Array.from(agentsUsed),
26693
+ has_todos: todos.length > 0,
26694
+ has_transcript: transcriptEntries > 0,
26695
+ todos,
26696
+ transcript_entries: transcriptEntries
26697
+ };
26698
+ }
26699
+
26700
+ // src/tools/session-manager/utils.ts
26701
+ function formatSessionList(sessionIDs) {
26702
+ if (sessionIDs.length === 0) {
26703
+ return "No sessions found.";
26704
+ }
26705
+ const infos = sessionIDs.map((id) => getSessionInfo(id)).filter((info) => info !== null);
26706
+ if (infos.length === 0) {
26707
+ return "No valid sessions found.";
26708
+ }
26709
+ const headers = ["Session ID", "Messages", "First", "Last", "Agents"];
26710
+ const rows = infos.map((info) => [
26711
+ info.id,
26712
+ info.message_count.toString(),
26713
+ info.first_message?.toISOString().split("T")[0] ?? "N/A",
26714
+ info.last_message?.toISOString().split("T")[0] ?? "N/A",
26715
+ info.agents_used.join(", ") || "none"
26716
+ ]);
26717
+ const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
26718
+ const formatRow = (cells) => {
26719
+ return "| " + cells.map((cell, i) => cell.padEnd(colWidths[i])).join(" | ").trim() + " |";
26720
+ };
26721
+ const separator = "|" + colWidths.map((w) => "-".repeat(w + 2)).join("|") + "|";
26722
+ return [formatRow(headers), separator, ...rows.map(formatRow)].join(`
26723
+ `);
26724
+ }
26725
+ function formatSessionMessages(messages, includeTodos, todos) {
26726
+ if (messages.length === 0) {
26727
+ return "No messages found in this session.";
26728
+ }
26729
+ const lines = [];
26730
+ for (const msg of messages) {
26731
+ const timestamp = msg.time?.created ? new Date(msg.time.created).toISOString() : "Unknown time";
26732
+ const agent = msg.agent ? ` (${msg.agent})` : "";
26733
+ lines.push(`
26734
+ [${msg.role}${agent}] ${timestamp}`);
26735
+ for (const part of msg.parts) {
26736
+ if (part.type === "text" && part.text) {
26737
+ lines.push(part.text.trim());
26738
+ } else if (part.type === "thinking" && part.thinking) {
26739
+ lines.push(`[thinking] ${part.thinking.substring(0, 200)}...`);
26740
+ } else if ((part.type === "tool_use" || part.type === "tool") && part.tool) {
26741
+ const input = part.input ? JSON.stringify(part.input).substring(0, 100) : "";
26742
+ lines.push(`[tool: ${part.tool}] ${input}`);
26743
+ } else if (part.type === "tool_result") {
26744
+ const output = part.output ? part.output.substring(0, 200) : "";
26745
+ lines.push(`[tool result] ${output}...`);
26746
+ }
26747
+ }
26748
+ }
26749
+ if (includeTodos && todos && todos.length > 0) {
26750
+ lines.push(`
26751
+
26752
+ === Todos ===`);
26753
+ for (const todo of todos) {
26754
+ const status = todo.status === "completed" ? "\u2713" : todo.status === "in_progress" ? "\u2192" : "\u25CB";
26755
+ lines.push(`${status} [${todo.status}] ${todo.content}`);
26756
+ }
26757
+ }
26758
+ return lines.join(`
26759
+ `);
26760
+ }
26761
+ function formatSessionInfo(info) {
26762
+ const lines = [
26763
+ `Session ID: ${info.id}`,
26764
+ `Messages: ${info.message_count}`,
26765
+ `Date Range: ${info.first_message?.toISOString() ?? "N/A"} to ${info.last_message?.toISOString() ?? "N/A"}`,
26766
+ `Agents Used: ${info.agents_used.join(", ") || "none"}`,
26767
+ `Has Todos: ${info.has_todos ? `Yes (${info.todos?.length ?? 0} items)` : "No"}`,
26768
+ `Has Transcript: ${info.has_transcript ? `Yes (${info.transcript_entries} entries)` : "No"}`
26769
+ ];
26770
+ if (info.first_message && info.last_message) {
26771
+ const duration3 = info.last_message.getTime() - info.first_message.getTime();
26772
+ const days = Math.floor(duration3 / (1000 * 60 * 60 * 24));
26773
+ const hours = Math.floor(duration3 % (1000 * 60 * 60 * 24) / (1000 * 60 * 60));
26774
+ if (days > 0 || hours > 0) {
26775
+ lines.push(`Duration: ${days} days, ${hours} hours`);
26776
+ }
26777
+ }
26778
+ return lines.join(`
26779
+ `);
26780
+ }
26781
+ function formatSearchResults(results) {
26782
+ if (results.length === 0) {
26783
+ return "No matches found.";
26784
+ }
26785
+ const lines = [`Found ${results.length} matches:
26786
+ `];
26787
+ for (const result of results) {
26788
+ const timestamp = result.timestamp ? new Date(result.timestamp).toISOString() : "";
26789
+ lines.push(`[${result.session_id}] ${result.message_id} (${result.role}) ${timestamp}`);
26790
+ lines.push(` ${result.excerpt}`);
26791
+ lines.push(` Matches: ${result.match_count}
26792
+ `);
26793
+ }
26794
+ return lines.join(`
26795
+ `);
26796
+ }
26797
+ function filterSessionsByDate(sessionIDs, fromDate, toDate) {
26798
+ if (!fromDate && !toDate)
26799
+ return sessionIDs;
26800
+ const from = fromDate ? new Date(fromDate) : null;
26801
+ const to = toDate ? new Date(toDate) : null;
26802
+ return sessionIDs.filter((id) => {
26803
+ const info = getSessionInfo(id);
26804
+ if (!info || !info.last_message)
26805
+ return false;
26806
+ if (from && info.last_message < from)
26807
+ return false;
26808
+ if (to && info.last_message > to)
26809
+ return false;
26810
+ return true;
26811
+ });
26812
+ }
26813
+ function searchInSession(sessionID, query, caseSensitive = false) {
26814
+ const messages = readSessionMessages(sessionID);
26815
+ const results = [];
26816
+ const searchQuery = caseSensitive ? query : query.toLowerCase();
26817
+ for (const msg of messages) {
26818
+ let matchCount = 0;
26819
+ let excerpts = [];
26820
+ for (const part of msg.parts) {
26821
+ if (part.type === "text" && part.text) {
26822
+ const text = caseSensitive ? part.text : part.text.toLowerCase();
26823
+ const matches = text.split(searchQuery).length - 1;
26824
+ if (matches > 0) {
26825
+ matchCount += matches;
26826
+ const index = text.indexOf(searchQuery);
26827
+ if (index !== -1) {
26828
+ const start = Math.max(0, index - 50);
26829
+ const end = Math.min(text.length, index + searchQuery.length + 50);
26830
+ let excerpt = part.text.substring(start, end);
26831
+ if (start > 0)
26832
+ excerpt = "..." + excerpt;
26833
+ if (end < text.length)
26834
+ excerpt = excerpt + "...";
26835
+ excerpts.push(excerpt);
26836
+ }
26837
+ }
26838
+ }
26839
+ }
26840
+ if (matchCount > 0) {
26841
+ results.push({
26842
+ session_id: sessionID,
26843
+ message_id: msg.id,
26844
+ role: msg.role,
26845
+ excerpt: excerpts[0] || "",
26846
+ match_count: matchCount,
26847
+ timestamp: msg.time?.created
26848
+ });
26849
+ }
26850
+ }
26851
+ return results;
26852
+ }
26853
+
26854
+ // src/tools/session-manager/tools.ts
26855
+ var session_list = tool({
26856
+ description: SESSION_LIST_DESCRIPTION,
26857
+ args: {
26858
+ limit: tool.schema.number().optional().describe("Maximum number of sessions to return"),
26859
+ from_date: tool.schema.string().optional().describe("Filter sessions from this date (ISO 8601 format)"),
26860
+ to_date: tool.schema.string().optional().describe("Filter sessions until this date (ISO 8601 format)")
26861
+ },
26862
+ execute: async (args, _context) => {
26863
+ try {
26864
+ let sessions = getAllSessions();
26865
+ if (args.from_date || args.to_date) {
26866
+ sessions = filterSessionsByDate(sessions, args.from_date, args.to_date);
26867
+ }
26868
+ if (args.limit && args.limit > 0) {
26869
+ sessions = sessions.slice(0, args.limit);
26870
+ }
26871
+ return formatSessionList(sessions);
26872
+ } catch (e) {
26873
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26874
+ }
26875
+ }
26876
+ });
26877
+ var session_read = tool({
26878
+ description: SESSION_READ_DESCRIPTION,
26879
+ args: {
26880
+ session_id: tool.schema.string().describe("Session ID to read"),
26881
+ include_todos: tool.schema.boolean().optional().describe("Include todo list if available (default: false)"),
26882
+ include_transcript: tool.schema.boolean().optional().describe("Include transcript log if available (default: false)"),
26883
+ limit: tool.schema.number().optional().describe("Maximum number of messages to return (default: all)")
26884
+ },
26885
+ execute: async (args, _context) => {
26886
+ try {
26887
+ if (!sessionExists(args.session_id)) {
26888
+ return `Session not found: ${args.session_id}`;
26889
+ }
26890
+ let messages = readSessionMessages(args.session_id);
26891
+ if (args.limit && args.limit > 0) {
26892
+ messages = messages.slice(0, args.limit);
26893
+ }
26894
+ const todos = args.include_todos ? readSessionTodos(args.session_id) : undefined;
26895
+ return formatSessionMessages(messages, args.include_todos, todos);
26896
+ } catch (e) {
26897
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26898
+ }
26899
+ }
26900
+ });
26901
+ var session_search = tool({
26902
+ description: SESSION_SEARCH_DESCRIPTION,
26903
+ args: {
26904
+ query: tool.schema.string().describe("Search query string"),
26905
+ session_id: tool.schema.string().optional().describe("Search within specific session only (default: all sessions)"),
26906
+ case_sensitive: tool.schema.boolean().optional().describe("Case-sensitive search (default: false)"),
26907
+ limit: tool.schema.number().optional().describe("Maximum number of results to return (default: 20)")
26908
+ },
26909
+ execute: async (args, _context) => {
26910
+ try {
26911
+ const sessions = args.session_id ? [args.session_id] : getAllSessions();
26912
+ const allResults = sessions.flatMap((sid) => searchInSession(sid, args.query, args.case_sensitive));
26913
+ const limited = args.limit && args.limit > 0 ? allResults.slice(0, args.limit) : allResults.slice(0, 20);
26914
+ return formatSearchResults(limited);
26915
+ } catch (e) {
26916
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26917
+ }
26918
+ }
26919
+ });
26920
+ var session_info = tool({
26921
+ description: SESSION_INFO_DESCRIPTION,
26922
+ args: {
26923
+ session_id: tool.schema.string().describe("Session ID to inspect")
26924
+ },
26925
+ execute: async (args, _context) => {
26926
+ try {
26927
+ const info = getSessionInfo(args.session_id);
26928
+ if (!info) {
26929
+ return `Session not found: ${args.session_id}`;
26930
+ }
26931
+ return formatSessionInfo(info);
26932
+ } catch (e) {
26933
+ return `Error: ${e instanceof Error ? e.message : String(e)}`;
26934
+ }
26935
+ }
26936
+ });
26208
26937
  // src/tools/interactive-bash/constants.ts
26209
26938
  var DEFAULT_TIMEOUT_MS4 = 60000;
26210
26939
  var BLOCKED_TMUX_SUBCOMMANDS = [
@@ -26238,12 +26967,12 @@ async function findTmuxPath() {
26238
26967
  return null;
26239
26968
  }
26240
26969
  const stdout = await new Response(proc.stdout).text();
26241
- const path7 = stdout.trim().split(`
26970
+ const path8 = stdout.trim().split(`
26242
26971
  `)[0];
26243
- if (!path7) {
26972
+ if (!path8) {
26244
26973
  return null;
26245
26974
  }
26246
- const verifyProc = spawn9([path7, "-V"], {
26975
+ const verifyProc = spawn9([path8, "-V"], {
26247
26976
  stdout: "pipe",
26248
26977
  stderr: "pipe"
26249
26978
  });
@@ -26251,7 +26980,7 @@ async function findTmuxPath() {
26251
26980
  if (verifyExitCode !== 0) {
26252
26981
  return null;
26253
26982
  }
26254
- return path7;
26983
+ return path8;
26255
26984
  } catch {
26256
26985
  return null;
26257
26986
  }
@@ -26264,9 +26993,9 @@ async function getTmuxPath() {
26264
26993
  return initPromise3;
26265
26994
  }
26266
26995
  initPromise3 = (async () => {
26267
- const path7 = await findTmuxPath();
26268
- tmuxPath = path7;
26269
- return path7;
26996
+ const path8 = await findTmuxPath();
26997
+ tmuxPath = path8;
26998
+ return path8;
26270
26999
  })();
26271
27000
  return initPromise3;
26272
27001
  }
@@ -26905,20 +27634,24 @@ var builtinTools = {
26905
27634
  ast_grep_replace,
26906
27635
  grep,
26907
27636
  glob,
26908
- slashcommand
27637
+ slashcommand,
27638
+ session_list,
27639
+ session_read,
27640
+ session_search,
27641
+ session_info
26909
27642
  };
26910
27643
  // src/features/background-agent/manager.ts
26911
- import { existsSync as existsSync37, readdirSync as readdirSync12 } from "fs";
26912
- import { join as join44 } from "path";
26913
- function getMessageDir5(sessionID) {
26914
- if (!existsSync37(MESSAGE_STORAGE))
27644
+ import { existsSync as existsSync38, readdirSync as readdirSync13 } from "fs";
27645
+ import { join as join47 } from "path";
27646
+ function getMessageDir6(sessionID) {
27647
+ if (!existsSync38(MESSAGE_STORAGE))
26915
27648
  return null;
26916
- const directPath = join44(MESSAGE_STORAGE, sessionID);
26917
- if (existsSync37(directPath))
27649
+ const directPath = join47(MESSAGE_STORAGE, sessionID);
27650
+ if (existsSync38(directPath))
26918
27651
  return directPath;
26919
- for (const dir of readdirSync12(MESSAGE_STORAGE)) {
26920
- const sessionPath = join44(MESSAGE_STORAGE, dir, sessionID);
26921
- if (existsSync37(sessionPath))
27652
+ for (const dir of readdirSync13(MESSAGE_STORAGE)) {
27653
+ const sessionPath = join47(MESSAGE_STORAGE, dir, sessionID);
27654
+ if (existsSync38(sessionPath))
26922
27655
  return sessionPath;
26923
27656
  }
26924
27657
  return null;
@@ -27154,7 +27887,7 @@ class BackgroundManager {
27154
27887
  log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
27155
27888
  setTimeout(async () => {
27156
27889
  try {
27157
- const messageDir = getMessageDir5(task.parentSessionID);
27890
+ const messageDir = getMessageDir6(task.parentSessionID);
27158
27891
  const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
27159
27892
  await this.client.session.prompt({
27160
27893
  path: { id: task.parentSessionID },
@@ -27322,6 +28055,7 @@ var OverridableAgentNameSchema = exports_external.enum([
27322
28055
  "build",
27323
28056
  "plan",
27324
28057
  "Sisyphus",
28058
+ "Builder-Sisyphus",
27325
28059
  "Planner-Sisyphus",
27326
28060
  "oracle",
27327
28061
  "librarian",
@@ -27358,6 +28092,7 @@ var AgentOverrideConfigSchema = exports_external.object({
27358
28092
  temperature: exports_external.number().min(0).max(2).optional(),
27359
28093
  top_p: exports_external.number().min(0).max(1).optional(),
27360
28094
  prompt: exports_external.string().optional(),
28095
+ prompt_append: exports_external.string().optional(),
27361
28096
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
27362
28097
  disable: exports_external.boolean().optional(),
27363
28098
  description: exports_external.string().optional(),
@@ -27369,6 +28104,7 @@ var AgentOverridesSchema = exports_external.object({
27369
28104
  build: AgentOverrideConfigSchema.optional(),
27370
28105
  plan: AgentOverrideConfigSchema.optional(),
27371
28106
  Sisyphus: AgentOverrideConfigSchema.optional(),
28107
+ "Builder-Sisyphus": AgentOverrideConfigSchema.optional(),
27372
28108
  "Planner-Sisyphus": AgentOverrideConfigSchema.optional(),
27373
28109
  oracle: AgentOverrideConfigSchema.optional(),
27374
28110
  librarian: AgentOverrideConfigSchema.optional(),
@@ -27385,14 +28121,18 @@ var ClaudeCodeConfigSchema = exports_external.object({
27385
28121
  hooks: exports_external.boolean().optional()
27386
28122
  });
27387
28123
  var SisyphusAgentConfigSchema = exports_external.object({
27388
- disabled: exports_external.boolean().optional()
28124
+ disabled: exports_external.boolean().optional(),
28125
+ builder_enabled: exports_external.boolean().optional(),
28126
+ planner_enabled: exports_external.boolean().optional(),
28127
+ replace_build: exports_external.boolean().optional(),
28128
+ replace_plan: exports_external.boolean().optional()
27389
28129
  });
27390
28130
  var ExperimentalConfigSchema = exports_external.object({
27391
28131
  aggressive_truncation: exports_external.boolean().optional(),
27392
28132
  auto_resume: exports_external.boolean().optional(),
27393
28133
  preemptive_compaction: exports_external.boolean().optional(),
27394
28134
  preemptive_compaction_threshold: exports_external.number().min(0.5).max(0.95).optional(),
27395
- truncate_all_tool_outputs: exports_external.boolean().optional()
28135
+ truncate_all_tool_outputs: exports_external.boolean().default(true)
27396
28136
  });
27397
28137
  var OhMyOpenCodeConfigSchema = exports_external.object({
27398
28138
  $schema: exports_external.string().optional(),
@@ -27477,9 +28217,53 @@ var PLAN_PERMISSION = {
27477
28217
  webfetch: "allow"
27478
28218
  };
27479
28219
 
28220
+ // src/agents/build-prompt.ts
28221
+ var BUILD_SYSTEM_PROMPT = `<system-reminder>
28222
+ # Build Mode - System Reminder
28223
+
28224
+ BUILD MODE ACTIVE - you are in EXECUTION phase. Your responsibility is to:
28225
+ - Implement features and make code changes
28226
+ - Execute commands and run tests
28227
+ - Fix bugs and refactor code
28228
+ - Deploy and build systems
28229
+ - Make all necessary file modifications
28230
+
28231
+ You have FULL permissions to edit files, run commands, and make system changes.
28232
+ This is the implementation phase - execute decisively and thoroughly.
28233
+
28234
+ ---
28235
+
28236
+ ## Responsibility
28237
+
28238
+ Your current responsibility is to implement, build, and execute. You should:
28239
+ - Write and modify code to accomplish the user's goals
28240
+ - Run tests and builds to verify your changes
28241
+ - Fix errors and issues that arise
28242
+ - Use all available tools to complete the task efficiently
28243
+ - Delegate to specialized agents when appropriate for better results
28244
+
28245
+ **NOTE:** You should ask the user for clarification when requirements are ambiguous,
28246
+ but once the path is clear, execute confidently. The goal is to deliver working,
28247
+ tested, production-ready solutions.
28248
+
28249
+ ---
28250
+
28251
+ ## Important
28252
+
28253
+ The user wants you to execute and implement. You SHOULD make edits, run necessary
28254
+ tools, and make changes to accomplish the task. Use your full capabilities to
28255
+ deliver excellent results.
28256
+ </system-reminder>
28257
+ `;
28258
+ var BUILD_PERMISSION = {
28259
+ edit: "ask",
28260
+ bash: "ask",
28261
+ webfetch: "allow"
28262
+ };
28263
+
27480
28264
  // src/index.ts
27481
28265
  import * as fs7 from "fs";
27482
- import * as path7 from "path";
28266
+ import * as path8 from "path";
27483
28267
  var AGENT_NAME_MAP = {
27484
28268
  omo: "Sisyphus",
27485
28269
  OmO: "Sisyphus",
@@ -27582,8 +28366,8 @@ function mergeConfigs(base, override) {
27582
28366
  };
27583
28367
  }
27584
28368
  function loadPluginConfig(directory) {
27585
- const userConfigPath = path7.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
27586
- const projectConfigPath = path7.join(directory, ".opencode", "oh-my-opencode.json");
28369
+ const userConfigPath = path8.join(getUserConfigDir(), "opencode", "oh-my-opencode.json");
28370
+ const projectConfigPath = path8.join(directory, ".opencode", "oh-my-opencode.json");
27587
28371
  let config3 = loadConfigFromPath2(userConfigPath) ?? {};
27588
28372
  const projectConfig = loadConfigFromPath2(projectConfigPath);
27589
28373
  if (projectConfig) {
@@ -27614,14 +28398,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
27614
28398
  }
27615
28399
  return;
27616
28400
  };
27617
- const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
27618
28401
  const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
27619
28402
  const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
27620
28403
  const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
27621
- if (sessionRecovery && todoContinuationEnforcer) {
27622
- sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
27623
- sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
27624
- }
27625
28404
  const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
27626
28405
  const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
27627
28406
  const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
@@ -27650,6 +28429,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
27650
28429
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
27651
28430
  const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
27652
28431
  const backgroundManager = new BackgroundManager(ctx);
28432
+ const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
28433
+ if (sessionRecovery && todoContinuationEnforcer) {
28434
+ sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
28435
+ sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
28436
+ }
27653
28437
  const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
27654
28438
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
27655
28439
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
@@ -27693,26 +28477,46 @@ var OhMyOpenCodePlugin = async (ctx) => {
27693
28477
  const userAgents = pluginConfig.claude_code?.agents ?? true ? loadUserAgents() : {};
27694
28478
  const projectAgents = pluginConfig.claude_code?.agents ?? true ? loadProjectAgents() : {};
27695
28479
  const isSisyphusEnabled = pluginConfig.sisyphus_agent?.disabled !== true;
28480
+ const builderEnabled = pluginConfig.sisyphus_agent?.builder_enabled ?? false;
28481
+ const plannerEnabled = pluginConfig.sisyphus_agent?.planner_enabled ?? true;
28482
+ const replaceBuild = pluginConfig.sisyphus_agent?.replace_build ?? true;
28483
+ const replacePlan = pluginConfig.sisyphus_agent?.replace_plan ?? true;
27696
28484
  if (isSisyphusEnabled && builtinAgents.Sisyphus) {
27697
- const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
27698
- const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
27699
- const plannerSisyphusBase = {
27700
- ...planConfigWithoutName,
27701
- prompt: PLAN_SYSTEM_PROMPT,
27702
- permission: PLAN_PERMISSION,
27703
- description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
27704
- color: config3.agent?.plan?.color ?? "#6495ED"
28485
+ const agentConfig = {
28486
+ Sisyphus: builtinAgents.Sisyphus
27705
28487
  };
27706
- const plannerSisyphusConfig = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
28488
+ if (builderEnabled) {
28489
+ const { name: _buildName, ...buildConfigWithoutName } = config3.agent?.build ?? {};
28490
+ const builderSisyphusOverride = pluginConfig.agents?.["Builder-Sisyphus"];
28491
+ const builderSisyphusBase = {
28492
+ ...buildConfigWithoutName,
28493
+ prompt: BUILD_SYSTEM_PROMPT,
28494
+ permission: BUILD_PERMISSION,
28495
+ description: `${config3.agent?.build?.description ?? "Build agent"} (OhMyOpenCode version)`,
28496
+ color: config3.agent?.build?.color ?? "#32CD32"
28497
+ };
28498
+ agentConfig["Builder-Sisyphus"] = builderSisyphusOverride ? { ...builderSisyphusBase, ...builderSisyphusOverride } : builderSisyphusBase;
28499
+ }
28500
+ if (plannerEnabled) {
28501
+ const { name: _planName, ...planConfigWithoutName } = config3.agent?.plan ?? {};
28502
+ const plannerSisyphusOverride = pluginConfig.agents?.["Planner-Sisyphus"];
28503
+ const plannerSisyphusBase = {
28504
+ ...planConfigWithoutName,
28505
+ prompt: PLAN_SYSTEM_PROMPT,
28506
+ permission: PLAN_PERMISSION,
28507
+ description: `${config3.agent?.plan?.description ?? "Plan agent"} (OhMyOpenCode version)`,
28508
+ color: config3.agent?.plan?.color ?? "#6495ED"
28509
+ };
28510
+ agentConfig["Planner-Sisyphus"] = plannerSisyphusOverride ? { ...plannerSisyphusBase, ...plannerSisyphusOverride } : plannerSisyphusBase;
28511
+ }
27707
28512
  config3.agent = {
27708
- Sisyphus: builtinAgents.Sisyphus,
27709
- "Planner-Sisyphus": plannerSisyphusConfig,
28513
+ ...agentConfig,
27710
28514
  ...Object.fromEntries(Object.entries(builtinAgents).filter(([k]) => k !== "Sisyphus")),
27711
28515
  ...userAgents,
27712
28516
  ...projectAgents,
27713
28517
  ...config3.agent,
27714
- build: { ...config3.agent?.build, mode: "subagent" },
27715
- plan: { ...config3.agent?.plan, mode: "subagent" }
28518
+ ...replaceBuild ? { build: { ...config3.agent?.build, mode: "subagent" } } : {},
28519
+ ...replacePlan ? { plan: { ...config3.agent?.plan, mode: "subagent" } } : {}
27716
28520
  };
27717
28521
  } else {
27718
28522
  config3.agent = {