oh-my-opencode 2.5.1 → 2.5.2

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.
@@ -4,6 +4,7 @@ export interface DisabledHooksConfig {
4
4
  PreToolUse?: string[];
5
5
  PostToolUse?: string[];
6
6
  UserPromptSubmit?: string[];
7
+ PreCompact?: string[];
7
8
  }
8
9
  export interface PluginExtendedConfig {
9
10
  disabledHooks?: DisabledHooksConfig;
@@ -1,6 +1,11 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
2
  import type { PluginConfig } from "./types";
3
3
  export declare function createClaudeCodeHooksHook(ctx: PluginInput, config?: PluginConfig): {
4
+ "experimental.session.compacting": (input: {
5
+ sessionID: string;
6
+ }, output: {
7
+ context: string[];
8
+ }) => Promise<void>;
4
9
  "chat.message": (input: {
5
10
  sessionID: string;
6
11
  agent?: string;
@@ -0,0 +1,16 @@
1
+ import type { ClaudeHooksConfig } from "./types";
2
+ import { type PluginExtendedConfig } from "./config-loader";
3
+ export interface PreCompactContext {
4
+ sessionId: string;
5
+ cwd: string;
6
+ }
7
+ export interface PreCompactResult {
8
+ context: string[];
9
+ elapsedMs?: number;
10
+ hookName?: string;
11
+ continue?: boolean;
12
+ stopReason?: string;
13
+ suppressOutput?: boolean;
14
+ systemMessage?: string;
15
+ }
16
+ export declare function executePreCompactHooks(ctx: PreCompactContext, config: ClaudeHooksConfig | null, extendedConfig?: PluginExtendedConfig | null): Promise<PreCompactResult>;
@@ -2,7 +2,7 @@
2
2
  * Claude Code Hooks Type Definitions
3
3
  * Maps Claude Code hook concepts to OpenCode plugin events
4
4
  */
5
- export type ClaudeHookEvent = "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop";
5
+ export type ClaudeHookEvent = "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop" | "PreCompact";
6
6
  export interface HookMatcher {
7
7
  matcher: string;
8
8
  hooks: HookCommand[];
@@ -16,6 +16,7 @@ export interface ClaudeHooksConfig {
16
16
  PostToolUse?: HookMatcher[];
17
17
  UserPromptSubmit?: HookMatcher[];
18
18
  Stop?: HookMatcher[];
19
+ PreCompact?: HookMatcher[];
19
20
  }
20
21
  export interface PreToolUseInput {
21
22
  session_id: string;
@@ -67,6 +68,12 @@ export interface StopInput {
67
68
  todo_path?: string;
68
69
  hook_source?: HookSource;
69
70
  }
71
+ export interface PreCompactInput {
72
+ session_id: string;
73
+ cwd: string;
74
+ hook_event_name: "PreCompact";
75
+ hook_source?: HookSource;
76
+ }
70
77
  export type PermissionDecision = "allow" | "deny" | "ask";
71
78
  /**
72
79
  * Common JSON fields for all hook outputs (Claude Code spec)
@@ -141,6 +148,15 @@ export interface StopOutput {
141
148
  permission_mode?: PermissionMode;
142
149
  inject_prompt?: string;
143
150
  }
151
+ export interface PreCompactOutput extends HookCommonOutput {
152
+ /** Additional context to inject into compaction prompt */
153
+ context?: string[];
154
+ hookSpecificOutput?: {
155
+ hookEventName: "PreCompact";
156
+ /** Additional context strings to inject */
157
+ additionalContext?: string[];
158
+ };
159
+ }
144
160
  export type ClaudeCodeContent = {
145
161
  type: "text";
146
162
  text: string;
@@ -0,0 +1 @@
1
+ export declare function isNonInteractive(): boolean;
@@ -1,5 +1,6 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
2
  export * from "./constants";
3
+ export * from "./detector";
3
4
  export * from "./types";
4
5
  export declare function createNonInteractiveEnvHook(_ctx: PluginInput): {
5
6
  "tool.execute.before": (input: {
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,8 @@
1
1
  import type { PluginInput } from "@opencode-ai/plugin";
2
+ import type { BackgroundManager } from "../features/background-agent";
3
+ export interface TodoContinuationEnforcerOptions {
4
+ backgroundManager?: BackgroundManager;
5
+ }
2
6
  export interface TodoContinuationEnforcer {
3
7
  handler: (input: {
4
8
  event: {
@@ -9,4 +13,4 @@ export interface TodoContinuationEnforcer {
9
13
  markRecovering: (sessionID: string) => void;
10
14
  markRecoveryComplete: (sessionID: string) => void;
11
15
  }
12
- export declare function createTodoContinuationEnforcer(ctx: PluginInput): TodoContinuationEnforcer;
16
+ export declare function createTodoContinuationEnforcer(ctx: PluginInput, options?: TodoContinuationEnforcerOptions): TodoContinuationEnforcer;
package/dist/index.js CHANGED
@@ -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]*)$/;
@@ -3213,11 +3284,11 @@ function addConfigLoadError(error) {
3213
3284
  var agentSources = {
3214
3285
  Sisyphus: createSisyphusAgent,
3215
3286
  oracle: createOracleAgent,
3216
- librarian: librarianAgent,
3217
- explore: exploreAgent,
3218
- "frontend-ui-ux-engineer": frontendUiUxEngineerAgent,
3219
- "document-writer": documentWriterAgent,
3220
- "multimodal-looker": multimodalLookerAgent
3287
+ librarian: createLibrarianAgent,
3288
+ explore: createExploreAgent,
3289
+ "frontend-ui-ux-engineer": createFrontendUiUxEngineerAgent,
3290
+ "document-writer": createDocumentWriterAgent,
3291
+ "multimodal-looker": createMultimodalLookerAgent
3221
3292
  };
3222
3293
  function isFactory(source) {
3223
3294
  return typeof source === "function";
@@ -3254,7 +3325,13 @@ Here is some useful information about the environment you are running in:
3254
3325
  </env>`;
3255
3326
  }
3256
3327
  function mergeAgentConfig(base, override) {
3257
- return deepMerge(base, override);
3328
+ const { prompt_append, ...rest } = override;
3329
+ const merged = deepMerge(base, rest);
3330
+ if (prompt_append && merged.prompt) {
3331
+ merged.prompt = merged.prompt + `
3332
+ ` + prompt_append;
3333
+ }
3334
+ return merged;
3258
3335
  }
3259
3336
  function createBuiltinAgents(disabledAgents = [], agentOverrides = {}, directory, systemDefaultModel) {
3260
3337
  const result = {};
@@ -3406,6 +3483,23 @@ function injectHookMessage(sessionID, hookContent, originalMessage) {
3406
3483
  return false;
3407
3484
  }
3408
3485
  }
3486
+ // src/hooks/non-interactive-env/detector.ts
3487
+ function isNonInteractive() {
3488
+ if (process.env.CI === "true" || process.env.CI === "1") {
3489
+ return true;
3490
+ }
3491
+ if (process.env.OPENCODE_RUN === "true" || process.env.OPENCODE_NON_INTERACTIVE === "true") {
3492
+ return true;
3493
+ }
3494
+ if (process.env.GITHUB_ACTIONS === "true") {
3495
+ return true;
3496
+ }
3497
+ if (process.stdout.isTTY !== true) {
3498
+ return true;
3499
+ }
3500
+ return false;
3501
+ }
3502
+
3409
3503
  // src/hooks/todo-continuation-enforcer.ts
3410
3504
  var HOOK_NAME = "todo-continuation-enforcer";
3411
3505
  var CONTINUATION_PROMPT = `[SYSTEM REMINDER - TODO CONTINUATION]
@@ -3450,12 +3544,14 @@ function detectInterrupt(error) {
3450
3544
  }
3451
3545
  var COUNTDOWN_SECONDS = 2;
3452
3546
  var TOAST_DURATION_MS = 900;
3453
- function createTodoContinuationEnforcer(ctx) {
3547
+ function createTodoContinuationEnforcer(ctx, options = {}) {
3548
+ const { backgroundManager } = options;
3454
3549
  const remindedSessions = new Set;
3455
3550
  const interruptedSessions = new Set;
3456
3551
  const errorSessions = new Set;
3457
3552
  const recoveringSessions = new Set;
3458
3553
  const pendingCountdowns = new Map;
3554
+ const preemptivelyInjectedSessions = new Set;
3459
3555
  const markRecovering = (sessionID) => {
3460
3556
  recoveringSessions.add(sessionID);
3461
3557
  };
@@ -3623,10 +3719,58 @@ function createTodoContinuationEnforcer(ctx) {
3623
3719
  log(`[${HOOK_NAME}] Cancelled countdown on user message`, { sessionID });
3624
3720
  }
3625
3721
  remindedSessions.delete(sessionID);
3722
+ preemptivelyInjectedSessions.delete(sessionID);
3626
3723
  }
3627
3724
  if (sessionID && role === "assistant" && finish) {
3628
3725
  remindedSessions.delete(sessionID);
3629
3726
  log(`[${HOOK_NAME}] Cleared reminded state on assistant finish`, { sessionID });
3727
+ const isTerminalFinish = finish && !["tool-calls", "unknown"].includes(finish);
3728
+ if (isTerminalFinish && isNonInteractive()) {
3729
+ log(`[${HOOK_NAME}] Terminal finish in non-interactive mode`, { sessionID, finish });
3730
+ const mainSessionID2 = getMainSessionID();
3731
+ if (mainSessionID2 && sessionID !== mainSessionID2) {
3732
+ log(`[${HOOK_NAME}] Skipped preemptive: not main session`, { sessionID, mainSessionID: mainSessionID2 });
3733
+ return;
3734
+ }
3735
+ if (preemptivelyInjectedSessions.has(sessionID)) {
3736
+ log(`[${HOOK_NAME}] Skipped preemptive: already injected`, { sessionID });
3737
+ return;
3738
+ }
3739
+ if (recoveringSessions.has(sessionID) || errorSessions.has(sessionID) || interruptedSessions.has(sessionID)) {
3740
+ log(`[${HOOK_NAME}] Skipped preemptive: session in error/recovery state`, { sessionID });
3741
+ return;
3742
+ }
3743
+ const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((t) => t.status === "running") : false;
3744
+ let hasIncompleteTodos = false;
3745
+ try {
3746
+ const response = await ctx.client.session.todo({ path: { id: sessionID } });
3747
+ const todos = response.data ?? response;
3748
+ hasIncompleteTodos = todos?.some((t) => t.status !== "completed" && t.status !== "cancelled") ?? false;
3749
+ } catch {
3750
+ log(`[${HOOK_NAME}] Failed to fetch todos for preemptive check`, { sessionID });
3751
+ }
3752
+ if (hasRunningBgTasks || hasIncompleteTodos) {
3753
+ log(`[${HOOK_NAME}] Preemptive injection needed`, { sessionID, hasRunningBgTasks, hasIncompleteTodos });
3754
+ preemptivelyInjectedSessions.add(sessionID);
3755
+ try {
3756
+ const messageDir = getMessageDir(sessionID);
3757
+ const prevMessage = messageDir ? findNearestMessageWithFields(messageDir) : null;
3758
+ const prompt = hasRunningBgTasks ? "[SYSTEM] Background tasks are still running. Wait for their completion before proceeding." : CONTINUATION_PROMPT;
3759
+ await ctx.client.session.prompt({
3760
+ path: { id: sessionID },
3761
+ body: {
3762
+ agent: prevMessage?.agent,
3763
+ parts: [{ type: "text", text: prompt }]
3764
+ },
3765
+ query: { directory: ctx.directory }
3766
+ });
3767
+ log(`[${HOOK_NAME}] Preemptive injection successful`, { sessionID });
3768
+ } catch (err) {
3769
+ log(`[${HOOK_NAME}] Preemptive injection failed`, { sessionID, error: String(err) });
3770
+ preemptivelyInjectedSessions.delete(sessionID);
3771
+ }
3772
+ }
3773
+ }
3630
3774
  }
3631
3775
  }
3632
3776
  if (event.type === "session.deleted") {
@@ -3636,6 +3780,7 @@ function createTodoContinuationEnforcer(ctx) {
3636
3780
  interruptedSessions.delete(sessionInfo.id);
3637
3781
  errorSessions.delete(sessionInfo.id);
3638
3782
  recoveringSessions.delete(sessionInfo.id);
3783
+ preemptivelyInjectedSessions.delete(sessionInfo.id);
3639
3784
  const countdown = pendingCountdowns.get(sessionInfo.id);
3640
3785
  if (countdown) {
3641
3786
  clearInterval(countdown.intervalId);
@@ -3897,6 +4042,9 @@ function createSessionNotification(ctx, config = {}) {
3897
4042
  return;
3898
4043
  if (subagentSessions.has(sessionID))
3899
4044
  return;
4045
+ const mainSessionID2 = getMainSessionID();
4046
+ if (mainSessionID2 && sessionID !== mainSessionID2)
4047
+ return;
3900
4048
  if (notifiedSessions.has(sessionID))
3901
4049
  return;
3902
4050
  if (pendingTimers.has(sessionID))
@@ -6583,7 +6731,8 @@ function normalizeHooksConfig(raw) {
6583
6731
  "PreToolUse",
6584
6732
  "PostToolUse",
6585
6733
  "UserPromptSubmit",
6586
- "Stop"
6734
+ "Stop",
6735
+ "PreCompact"
6587
6736
  ];
6588
6737
  for (const eventType of eventTypes) {
6589
6738
  if (raw[eventType]) {
@@ -6610,7 +6759,8 @@ function mergeHooksConfig(base, override) {
6610
6759
  "PreToolUse",
6611
6760
  "PostToolUse",
6612
6761
  "UserPromptSubmit",
6613
- "Stop"
6762
+ "Stop",
6763
+ "PreCompact"
6614
6764
  ];
6615
6765
  for (const eventType of eventTypes) {
6616
6766
  if (override[eventType]) {
@@ -6668,7 +6818,8 @@ function mergeDisabledHooks(base, override) {
6668
6818
  Stop: override.Stop ?? base.Stop,
6669
6819
  PreToolUse: override.PreToolUse ?? base.PreToolUse,
6670
6820
  PostToolUse: override.PostToolUse ?? base.PostToolUse,
6671
- UserPromptSubmit: override.UserPromptSubmit ?? base.UserPromptSubmit
6821
+ UserPromptSubmit: override.UserPromptSubmit ?? base.UserPromptSubmit,
6822
+ PreCompact: override.PreCompact ?? base.PreCompact
6672
6823
  };
6673
6824
  }
6674
6825
  async function loadPluginExtendedConfig() {
@@ -7224,6 +7375,74 @@ async function executeStopHooks(ctx, config, extendedConfig) {
7224
7375
  return { block: false };
7225
7376
  }
7226
7377
 
7378
+ // src/hooks/claude-code-hooks/pre-compact.ts
7379
+ async function executePreCompactHooks(ctx, config, extendedConfig) {
7380
+ if (!config) {
7381
+ return { context: [] };
7382
+ }
7383
+ const matchers = findMatchingHooks(config, "PreCompact", "*");
7384
+ if (matchers.length === 0) {
7385
+ return { context: [] };
7386
+ }
7387
+ const stdinData = {
7388
+ session_id: ctx.sessionId,
7389
+ cwd: ctx.cwd,
7390
+ hook_event_name: "PreCompact",
7391
+ hook_source: "opencode-plugin"
7392
+ };
7393
+ const startTime = Date.now();
7394
+ let firstHookName;
7395
+ const collectedContext = [];
7396
+ for (const matcher of matchers) {
7397
+ for (const hook of matcher.hooks) {
7398
+ if (hook.type !== "command")
7399
+ continue;
7400
+ if (isHookCommandDisabled("PreCompact", hook.command, extendedConfig ?? null)) {
7401
+ log("PreCompact hook command skipped (disabled by config)", { command: hook.command });
7402
+ continue;
7403
+ }
7404
+ const hookName = hook.command.split("/").pop() || hook.command;
7405
+ if (!firstHookName)
7406
+ firstHookName = hookName;
7407
+ const result = await executeHookCommand(hook.command, JSON.stringify(stdinData), ctx.cwd, { forceZsh: DEFAULT_CONFIG.forceZsh, zshPath: DEFAULT_CONFIG.zshPath });
7408
+ if (result.exitCode === 2) {
7409
+ log("PreCompact hook blocked", { hookName, stderr: result.stderr });
7410
+ continue;
7411
+ }
7412
+ if (result.stdout) {
7413
+ try {
7414
+ const output = JSON.parse(result.stdout);
7415
+ if (output.hookSpecificOutput?.additionalContext) {
7416
+ collectedContext.push(...output.hookSpecificOutput.additionalContext);
7417
+ } else if (output.context) {
7418
+ collectedContext.push(...output.context);
7419
+ }
7420
+ if (output.continue === false) {
7421
+ return {
7422
+ context: collectedContext,
7423
+ elapsedMs: Date.now() - startTime,
7424
+ hookName: firstHookName,
7425
+ continue: output.continue,
7426
+ stopReason: output.stopReason,
7427
+ suppressOutput: output.suppressOutput,
7428
+ systemMessage: output.systemMessage
7429
+ };
7430
+ }
7431
+ } catch {
7432
+ if (result.stdout.trim()) {
7433
+ collectedContext.push(result.stdout.trim());
7434
+ }
7435
+ }
7436
+ }
7437
+ }
7438
+ }
7439
+ return {
7440
+ context: collectedContext,
7441
+ elapsedMs: Date.now() - startTime,
7442
+ hookName: firstHookName
7443
+ };
7444
+ }
7445
+
7227
7446
  // src/hooks/claude-code-hooks/tool-input-cache.ts
7228
7447
  var cache = new Map;
7229
7448
  var CACHE_TTL = 60000;
@@ -7256,6 +7475,27 @@ var sessionErrorState = new Map;
7256
7475
  var sessionInterruptState = new Map;
7257
7476
  function createClaudeCodeHooksHook(ctx, config = {}) {
7258
7477
  return {
7478
+ "experimental.session.compacting": async (input, output) => {
7479
+ if (isHookDisabled(config, "PreCompact")) {
7480
+ return;
7481
+ }
7482
+ const claudeConfig = await loadClaudeHooksConfig();
7483
+ const extendedConfig = await loadPluginExtendedConfig();
7484
+ const preCompactCtx = {
7485
+ sessionId: input.sessionID,
7486
+ cwd: ctx.directory
7487
+ };
7488
+ const result = await executePreCompactHooks(preCompactCtx, claudeConfig, extendedConfig);
7489
+ if (result.context.length > 0) {
7490
+ log("PreCompact hooks injecting context", {
7491
+ sessionID: input.sessionID,
7492
+ contextCount: result.context.length,
7493
+ hookName: result.hookName,
7494
+ elapsedMs: result.elapsedMs
7495
+ });
7496
+ output.context.push(...result.context);
7497
+ }
7498
+ },
7259
7499
  "chat.message": async (input, output) => {
7260
7500
  const interruptState = sessionInterruptState.get(input.sessionID);
7261
7501
  if (interruptState?.interrupted) {
@@ -27358,6 +27598,7 @@ var AgentOverrideConfigSchema = exports_external.object({
27358
27598
  temperature: exports_external.number().min(0).max(2).optional(),
27359
27599
  top_p: exports_external.number().min(0).max(1).optional(),
27360
27600
  prompt: exports_external.string().optional(),
27601
+ prompt_append: exports_external.string().optional(),
27361
27602
  tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
27362
27603
  disable: exports_external.boolean().optional(),
27363
27604
  description: exports_external.string().optional(),
@@ -27614,14 +27855,9 @@ var OhMyOpenCodePlugin = async (ctx) => {
27614
27855
  }
27615
27856
  return;
27616
27857
  };
27617
- const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
27618
27858
  const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
27619
27859
  const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx, { experimental: pluginConfig.experimental }) : null;
27620
27860
  const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
27621
- if (sessionRecovery && todoContinuationEnforcer) {
27622
- sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
27623
- sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
27624
- }
27625
27861
  const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
27626
27862
  const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx, { experimental: pluginConfig.experimental }) : null;
27627
27863
  const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
@@ -27650,6 +27886,11 @@ var OhMyOpenCodePlugin = async (ctx) => {
27650
27886
  const interactiveBashSession = isHookEnabled("interactive-bash-session") ? createInteractiveBashSessionHook(ctx) : null;
27651
27887
  const emptyMessageSanitizer = isHookEnabled("empty-message-sanitizer") ? createEmptyMessageSanitizerHook() : null;
27652
27888
  const backgroundManager = new BackgroundManager(ctx);
27889
+ const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx, { backgroundManager }) : null;
27890
+ if (sessionRecovery && todoContinuationEnforcer) {
27891
+ sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
27892
+ sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
27893
+ }
27653
27894
  const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
27654
27895
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
27655
27896
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);