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.
- package/LICENSE.md +82 -0
- package/README.ja.md +29 -25
- package/README.ko.md +29 -25
- package/README.md +29 -25
- package/README.zh-cn.md +29 -25
- package/dist/agents/document-writer.d.ts +1 -0
- package/dist/agents/explore.d.ts +1 -0
- package/dist/agents/frontend-ui-ux-engineer.d.ts +1 -0
- package/dist/agents/librarian.d.ts +1 -0
- package/dist/agents/multimodal-looker.d.ts +1 -0
- package/dist/agents/types.d.ts +3 -1
- package/dist/cli/config-manager.d.ts +0 -1
- package/dist/cli/index.js +2 -3
- package/dist/config/schema.d.ts +21 -0
- package/dist/hooks/claude-code-hooks/config-loader.d.ts +1 -0
- package/dist/hooks/claude-code-hooks/index.d.ts +5 -0
- package/dist/hooks/claude-code-hooks/pre-compact.d.ts +16 -0
- package/dist/hooks/claude-code-hooks/types.d.ts +17 -1
- package/dist/hooks/non-interactive-env/detector.d.ts +1 -0
- package/dist/hooks/non-interactive-env/index.d.ts +1 -0
- package/dist/hooks/session-notification.test.d.ts +1 -0
- package/dist/hooks/todo-continuation-enforcer.d.ts +5 -1
- package/dist/index.js +295 -54
- package/package.json +2 -2
- package/LICENSE +0 -21
|
@@ -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;
|
|
@@ -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
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
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
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
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
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
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
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
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:
|
|
3217
|
-
explore:
|
|
3218
|
-
"frontend-ui-ux-engineer":
|
|
3219
|
-
"document-writer":
|
|
3220
|
-
"multimodal-looker":
|
|
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
|
-
|
|
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);
|