oh-my-opencode 0.4.4 → 1.0.0

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/README.ko.md CHANGED
@@ -110,7 +110,7 @@ cat ~/.config/opencode/opencode.json # plugin 배열에 "oh-my-opencode"가 있
110
110
 
111
111
  ### 4단계: 인증정보 설정
112
112
 
113
- plugin 에 oh my opencode 를 추가했다면, tmux 를 사용하여 사용자의 인증 설정을 도와주세요.
113
+ plugin 에 oh my opencode 를 추가했다면, tmux 를 사용하여 사용자의 인증 설정을 도와주세요. **기본적으로 Google Auth (4.2단계) 설정을 권장합니다.**
114
114
 
115
115
  #### 4.1 Anthropic (Claude)
116
116
 
@@ -123,7 +123,9 @@ opencode auth login
123
123
 
124
124
  #### 4.2 Google Gemini (내장 Antigravity OAuth)
125
125
 
126
- 먼저 `~/.config/opencode/oh-my-opencode.json` (또는 프로젝트 레벨의 `.opencode/oh-my-opencode.json`)에서 Google auth를 활성화:
126
+ **권장되는 기본 설정입니다.**
127
+
128
+ 먼저 주도적으로 `~/.config/opencode/oh-my-opencode.json` (또는 프로젝트 레벨의 `.opencode/oh-my-opencode.json`)에서 Google auth를 활성화하세요:
127
129
 
128
130
  ```json
129
131
  {
@@ -170,9 +172,9 @@ opencode auth login
170
172
 
171
173
  ### ⚠️ 주의
172
174
 
173
- **사용자가 명시적으로 요청하지 않는 한, `oh-my-opencode.json` 설정 파일을 생성하거나 수정하지 마세요.**
175
+ **사용자가 명시적으로 요청하지 않는 한, 모델 설정을 변경하거나 기능(에이전트, 훅, MCP)을 비활성화하지 마세요.**
174
176
 
175
- 플러그인은 설정 파일 없이도 기본값으로 완벽하게 동작합니다. 설정 파일은 사용자가 커스터마이징을 원할 때만 필요합니다.
177
+ 플러그인은 기본값으로 완벽하게 동작합니다. 권장 사항인 `google_auth: true` 설정을 제외하고는, 명시적인 요청 없이 다른 설정을 변경하거나 기능을 끄지 마세요.
176
178
 
177
179
  </details>
178
180
 
@@ -334,6 +336,18 @@ OpenCode 는 아주 확장가능하고 아주 커스터마이저블합니다.
334
336
  - **Empty Task Response Detector**: 서브 에이전트가 수행한 작업이 비어있거나 무의미한 응답을 반환하는 경우를 감지하여, 오류 없이 우아하게 처리합니다.
335
337
  - **Grep Output Truncator**: Grep 검색 결과가 너무 길어 컨텍스트를 장악해버리는 것을 방지하기 위해, 과도한 출력을 자동으로 자릅니다.
336
338
 
339
+ 필요 없는 훅이 있다면, `~/.config/opencode/oh-my-opencode.json` 혹은 `.opencode/oh-my-opencode.json`의 `disabled_hooks`를 사용하여 비활성화할 수 있습니다:
340
+
341
+ ```json
342
+ {
343
+ "disabled_hooks": ["session-notification", "comment-checker"]
344
+ }
345
+ ```
346
+
347
+ 사용 가능한 훅: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-auto-compact`, `rules-injector`, `background-notification`, `auto-update-checker`
348
+
349
+ > **참고**: `disabled_hooks`는 Oh My OpenCode의 내장 훅을 제어합니다. Claude Code의 `settings.json` 훅을 비활성화하려면 `claude_code.hooks: false`를 대신 사용하세요 ([호환성 토글](#호환성-토글) 참고).
350
+
337
351
  ### Claude Code 호환성
338
352
 
339
353
  Oh My OpenCode는 Claude Code 설정과 완벽하게 호환됩니다. Claude Code를 사용하셨다면, 기존 설정을 그대로 사용할 수 있습니다.
package/README.md CHANGED
@@ -109,7 +109,7 @@ cat ~/.config/opencode/opencode.json # Should contain "oh-my-opencode" in plugi
109
109
 
110
110
  ### Step 4: Configure Authentication
111
111
 
112
- Once `oh-my-opencode` is added to the plugin list, use `tmux` to assist the user with authentication setup.
112
+ Once `oh-my-opencode` is added to the plugin list, use `tmux` to assist the user with authentication setup. **It is RECOMMENDED to set up Google Auth (Step 4.2) by default.**
113
113
 
114
114
  #### 4.1 Anthropic (Claude)
115
115
 
@@ -122,7 +122,9 @@ opencode auth login
122
122
 
123
123
  #### 4.2 Google Gemini (Built-in Antigravity OAuth)
124
124
 
125
- First, enable Google auth in `~/.config/opencode/oh-my-opencode.json` (or `.opencode/oh-my-opencode.json` for project-level):
125
+ **This is the RECOMMENDED default configuration.**
126
+
127
+ First, proactively enable Google auth in `~/.config/opencode/oh-my-opencode.json` (or `.opencode/oh-my-opencode.json` for project-level):
126
128
 
127
129
  ```json
128
130
  {
@@ -169,9 +171,9 @@ For users who prefer proxy-based authentication, [VibeProxy](https://github.com/
169
171
 
170
172
  ### ⚠️ Warning
171
173
 
172
- **Do NOT create or modify the `oh-my-opencode.json` configuration file unless the user explicitly requests it.**
174
+ **Do NOT modify model settings or disable features (agents, hooks, MCPs) unless the user explicitly requests it.**
173
175
 
174
- The plugin works perfectly with default settings without any configuration file. The configuration file is only needed when the user wants customization.
176
+ The plugin works perfectly with default settings. While you SHOULD enable `google_auth: true` as recommended, do not alter other configurations or disable features without a specific request.
175
177
 
176
178
  </details>
177
179
 
@@ -332,6 +334,18 @@ Example workflow:
332
334
  - **Empty Task Response Detector**: Detects when subagent tasks return empty or meaningless responses and handles gracefully.
333
335
  - **Grep Output Truncator**: Prevents grep output from overwhelming the context by truncating excessively long results.
334
336
 
337
+ You can disable specific built-in hooks using `disabled_hooks` in `~/.config/opencode/oh-my-opencode.json` or `.opencode/oh-my-opencode.json`:
338
+
339
+ ```json
340
+ {
341
+ "disabled_hooks": ["session-notification", "comment-checker"]
342
+ }
343
+ ```
344
+
345
+ Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-auto-compact`, `rules-injector`, `background-notification`, `auto-update-checker`
346
+
347
+ > **Note**: `disabled_hooks` controls Oh My OpenCode's built-in hooks. To disable Claude Code's `settings.json` hooks, use `claude_code.hooks: false` instead (see [Compatibility Toggles](#compatibility-toggles)).
348
+
335
349
  ### Claude Code Compatibility
336
350
 
337
351
  Oh My OpenCode provides seamless Claude Code configuration compatibility. If you've been using Claude Code, your existing setup works out of the box.
@@ -1,2 +1,2 @@
1
- export { OhMyOpenCodeConfigSchema, AgentOverrideConfigSchema, AgentOverridesSchema, McpNameSchema, AgentNameSchema, } from "./schema";
2
- export type { OhMyOpenCodeConfig, AgentOverrideConfig, AgentOverrides, McpName, AgentName, } from "./schema";
1
+ export { OhMyOpenCodeConfigSchema, AgentOverrideConfigSchema, AgentOverridesSchema, McpNameSchema, AgentNameSchema, HookNameSchema, } from "./schema";
2
+ export type { OhMyOpenCodeConfig, AgentOverrideConfig, AgentOverrides, McpName, AgentName, HookName, } from "./schema";
@@ -6,6 +6,23 @@ export declare const AgentNameSchema: z.ZodEnum<{
6
6
  "frontend-ui-ux-engineer": "frontend-ui-ux-engineer";
7
7
  "document-writer": "document-writer";
8
8
  }>;
9
+ export declare const HookNameSchema: z.ZodEnum<{
10
+ "comment-checker": "comment-checker";
11
+ "rules-injector": "rules-injector";
12
+ "todo-continuation-enforcer": "todo-continuation-enforcer";
13
+ "context-window-monitor": "context-window-monitor";
14
+ "session-recovery": "session-recovery";
15
+ "session-notification": "session-notification";
16
+ "grep-output-truncator": "grep-output-truncator";
17
+ "directory-agents-injector": "directory-agents-injector";
18
+ "directory-readme-injector": "directory-readme-injector";
19
+ "empty-task-response-detector": "empty-task-response-detector";
20
+ "think-mode": "think-mode";
21
+ "anthropic-auto-compact": "anthropic-auto-compact";
22
+ "background-notification": "background-notification";
23
+ "auto-update-checker": "auto-update-checker";
24
+ "ultrawork-mode": "ultrawork-mode";
25
+ }>;
9
26
  export declare const AgentOverrideConfigSchema: z.ZodObject<{
10
27
  model: z.ZodOptional<z.ZodString>;
11
28
  temperature: z.ZodOptional<z.ZodNumber>;
@@ -304,6 +321,23 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
304
321
  "frontend-ui-ux-engineer": "frontend-ui-ux-engineer";
305
322
  "document-writer": "document-writer";
306
323
  }>>>;
324
+ disabled_hooks: z.ZodOptional<z.ZodArray<z.ZodEnum<{
325
+ "comment-checker": "comment-checker";
326
+ "rules-injector": "rules-injector";
327
+ "todo-continuation-enforcer": "todo-continuation-enforcer";
328
+ "context-window-monitor": "context-window-monitor";
329
+ "session-recovery": "session-recovery";
330
+ "session-notification": "session-notification";
331
+ "grep-output-truncator": "grep-output-truncator";
332
+ "directory-agents-injector": "directory-agents-injector";
333
+ "directory-readme-injector": "directory-readme-injector";
334
+ "empty-task-response-detector": "empty-task-response-detector";
335
+ "think-mode": "think-mode";
336
+ "anthropic-auto-compact": "anthropic-auto-compact";
337
+ "background-notification": "background-notification";
338
+ "auto-update-checker": "auto-update-checker";
339
+ "ultrawork-mode": "ultrawork-mode";
340
+ }>>>;
307
341
  agents: z.ZodOptional<z.ZodObject<{
308
342
  oracle: z.ZodOptional<z.ZodOptional<z.ZodObject<{
309
343
  model: z.ZodOptional<z.ZodString>;
@@ -549,4 +583,5 @@ export type OhMyOpenCodeConfig = z.infer<typeof OhMyOpenCodeConfigSchema>;
549
583
  export type AgentOverrideConfig = z.infer<typeof AgentOverrideConfigSchema>;
550
584
  export type AgentOverrides = z.infer<typeof AgentOverridesSchema>;
551
585
  export type AgentName = z.infer<typeof AgentNameSchema>;
586
+ export type HookName = z.infer<typeof HookNameSchema>;
552
587
  export { McpNameSchema, type McpName } from "../mcp/types";
@@ -3,6 +3,8 @@ export interface TaskProgress {
3
3
  toolCalls: number;
4
4
  lastTool?: string;
5
5
  lastUpdate: Date;
6
+ lastMessage?: string;
7
+ lastMessageAt?: Date;
6
8
  }
7
9
  export interface BackgroundTask {
8
10
  id: string;
@@ -10,6 +12,7 @@ export interface BackgroundTask {
10
12
  parentSessionID: string;
11
13
  parentMessageID: string;
12
14
  description: string;
15
+ prompt: string;
13
16
  agent: string;
14
17
  status: BackgroundTaskStatus;
15
18
  startedAt: Date;
@@ -13,3 +13,4 @@ export { createClaudeCodeHooksHook } from "./claude-code-hooks";
13
13
  export { createRulesInjectorHook } from "./rules-injector";
14
14
  export { createBackgroundNotificationHook } from "./background-notification";
15
15
  export { createAutoUpdateCheckerHook } from "./auto-update-checker";
16
+ export { createUltraworkModeHook } from "./ultrawork-mode";
@@ -8,6 +8,8 @@ interface SessionNotificationConfig {
8
8
  idleConfirmationDelay?: number;
9
9
  /** Skip notification if there are incomplete todos (default: true) */
10
10
  skipIfIncompleteTodos?: boolean;
11
+ /** Maximum number of sessions to track before cleanup (default: 100) */
12
+ maxTrackedSessions?: number;
11
13
  }
12
14
  export declare function createSessionNotification(ctx: PluginInput, config?: SessionNotificationConfig): ({ event }: {
13
15
  event: {
@@ -0,0 +1,15 @@
1
+ /** Keyword patterns - "ultrawork", "ulw" (case-insensitive, word boundary) */
2
+ export declare const ULTRAWORK_PATTERNS: RegExp[];
3
+ /** Code block pattern to exclude from keyword detection */
4
+ export declare const CODE_BLOCK_PATTERN: RegExp;
5
+ /** Inline code pattern to exclude */
6
+ export declare const INLINE_CODE_PATTERN: RegExp;
7
+ /**
8
+ * ULTRAWORK_CONTEXT - Agent-Agnostic Guidance
9
+ *
10
+ * Key principles:
11
+ * - NO specific agent names (oracle, librarian, etc.)
12
+ * - Only provide guidance based on agent role/capability
13
+ * - Emphasize parallel execution, TODO tracking, delegation
14
+ */
15
+ export declare const ULTRAWORK_CONTEXT = "<ultrawork-mode>\n[CODE RED] Maximum precision required. Ultrathink before acting.\n\nYOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.\nTELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.\n\n## AGENT UTILIZATION PRINCIPLES (by capability, not by name)\n- **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure\n- **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs\n- **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown\n- **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning\n- **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation\n\n## EXECUTION RULES\n- **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.\n- **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.\n- **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).\n- **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.\n- **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.\n\n## WORKFLOW\n1. Analyze the request and identify required capabilities\n2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)\n3. Use planning agents to create detailed work breakdown\n4. Execute with continuous verification against original requirements\n\n</ultrawork-mode>\n\n---\n\n";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Remove code blocks and inline code from text.
3
+ * Prevents false positives when keywords appear in code.
4
+ */
5
+ export declare function removeCodeBlocks(text: string): string;
6
+ /**
7
+ * Detect ultrawork keywords in text (excluding code blocks).
8
+ */
9
+ export declare function detectUltraworkKeyword(text: string): boolean;
10
+ /**
11
+ * Extract text content from message parts.
12
+ */
13
+ export declare function extractPromptText(parts: Array<{
14
+ type: string;
15
+ text?: string;
16
+ }>): string;
@@ -0,0 +1,40 @@
1
+ export * from "./detector";
2
+ export * from "./constants";
3
+ export * from "./types";
4
+ export declare function clearUltraworkModeState(sessionID: string): void;
5
+ export declare function createUltraworkModeHook(): {
6
+ /**
7
+ * chat.message hook - detect ultrawork/ulw keywords, inject context
8
+ *
9
+ * Execution timing: AFTER claudeCodeHooks["chat.message"]
10
+ * Behavior:
11
+ * 1. Extract text from user prompt
12
+ * 2. Detect ultrawork/ulw keywords (excluding code blocks)
13
+ * 3. If detected, prepend ULTRAWORK_CONTEXT to first text part
14
+ */
15
+ "chat.message": (input: {
16
+ sessionID: string;
17
+ agent?: string;
18
+ model?: {
19
+ providerID: string;
20
+ modelID: string;
21
+ };
22
+ messageID?: string;
23
+ }, output: {
24
+ message: Record<string, unknown>;
25
+ parts: Array<{
26
+ type: string;
27
+ text?: string;
28
+ [key: string]: unknown;
29
+ }>;
30
+ }) => Promise<void>;
31
+ /**
32
+ * event hook - cleanup session state on deletion
33
+ */
34
+ event: ({ event, }: {
35
+ event: {
36
+ type: string;
37
+ properties?: unknown;
38
+ };
39
+ }) => Promise<void>;
40
+ };
@@ -0,0 +1,20 @@
1
+ export interface UltraworkModeState {
2
+ /** Whether ultrawork keyword was detected */
3
+ detected: boolean;
4
+ /** Whether context was injected */
5
+ injected: boolean;
6
+ }
7
+ export interface ModelRef {
8
+ providerID: string;
9
+ modelID: string;
10
+ }
11
+ export interface MessageWithModel {
12
+ model?: ModelRef;
13
+ }
14
+ export interface UltraworkModeInput {
15
+ parts: Array<{
16
+ type: string;
17
+ text?: string;
18
+ }>;
19
+ message: MessageWithModel;
20
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import type { Plugin } from "@opencode-ai/plugin";
2
2
  declare const OhMyOpenCodePlugin: Plugin;
3
3
  export default OhMyOpenCodePlugin;
4
- export type { OhMyOpenCodeConfig, AgentName, AgentOverrideConfig, AgentOverrides, McpName, } from "./config";
4
+ export type { OhMyOpenCodeConfig, AgentName, AgentOverrideConfig, AgentOverrides, McpName, HookName, } from "./config";
package/dist/index.js CHANGED
@@ -184,8 +184,8 @@ var require_utils = __commonJS((exports) => {
184
184
  exports.toPosixSlashes = (str) => str.replace(REGEX_BACKSLASH, "/");
185
185
  exports.isWindows = () => {
186
186
  if (typeof navigator !== "undefined" && navigator.platform) {
187
- const platform = navigator.platform.toLowerCase();
188
- return platform === "win32" || platform === "windows";
187
+ const platform2 = navigator.platform.toLowerCase();
188
+ return platform2 === "win32" || platform2 === "windows";
189
189
  }
190
190
  if (typeof process !== "undefined" && process.platform) {
191
191
  return process.platform === "win32";
@@ -2934,6 +2934,198 @@ ${CONTEXT_REMINDER}
2934
2934
  event: eventHandler
2935
2935
  };
2936
2936
  }
2937
+ // src/hooks/session-notification.ts
2938
+ import { platform } from "os";
2939
+ function detectPlatform() {
2940
+ const p = platform();
2941
+ if (p === "darwin" || p === "linux" || p === "win32")
2942
+ return p;
2943
+ return "unsupported";
2944
+ }
2945
+ function getDefaultSoundPath(p) {
2946
+ switch (p) {
2947
+ case "darwin":
2948
+ return "/System/Library/Sounds/Glass.aiff";
2949
+ case "linux":
2950
+ return "/usr/share/sounds/freedesktop/stereo/complete.oga";
2951
+ case "win32":
2952
+ return "C:\\Windows\\Media\\notify.wav";
2953
+ default:
2954
+ return "";
2955
+ }
2956
+ }
2957
+ async function sendNotification(ctx, p, title, message) {
2958
+ const escapedTitle = title.replace(/"/g, "\\\"").replace(/'/g, "\\'");
2959
+ const escapedMessage = message.replace(/"/g, "\\\"").replace(/'/g, "\\'");
2960
+ switch (p) {
2961
+ case "darwin":
2962
+ await ctx.$`osascript -e ${'display notification "' + escapedMessage + '" with title "' + escapedTitle + '"'}`;
2963
+ break;
2964
+ case "linux":
2965
+ await ctx.$`notify-send ${escapedTitle} ${escapedMessage}`;
2966
+ break;
2967
+ case "win32":
2968
+ await ctx.$`powershell -Command ${"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('" + escapedMessage + "', '" + escapedTitle + "')"}`;
2969
+ break;
2970
+ }
2971
+ }
2972
+ async function playSound(ctx, p, soundPath) {
2973
+ switch (p) {
2974
+ case "darwin":
2975
+ ctx.$`afplay ${soundPath}`.catch(() => {});
2976
+ break;
2977
+ case "linux":
2978
+ ctx.$`paplay ${soundPath}`.catch(() => {
2979
+ ctx.$`aplay ${soundPath}`.catch(() => {});
2980
+ });
2981
+ break;
2982
+ case "win32":
2983
+ ctx.$`powershell -Command ${"(New-Object Media.SoundPlayer '" + soundPath + "').PlaySync()"}`.catch(() => {});
2984
+ break;
2985
+ }
2986
+ }
2987
+ async function hasIncompleteTodos(ctx, sessionID) {
2988
+ try {
2989
+ const response = await ctx.client.session.todo({ path: { id: sessionID } });
2990
+ const todos = response.data ?? response;
2991
+ if (!todos || todos.length === 0)
2992
+ return false;
2993
+ return todos.some((t) => t.status !== "completed" && t.status !== "cancelled");
2994
+ } catch {
2995
+ return false;
2996
+ }
2997
+ }
2998
+ function createSessionNotification(ctx, config = {}) {
2999
+ const currentPlatform = detectPlatform();
3000
+ const defaultSoundPath = getDefaultSoundPath(currentPlatform);
3001
+ const mergedConfig = {
3002
+ title: "OpenCode",
3003
+ message: "Agent is ready for input",
3004
+ playSound: false,
3005
+ soundPath: defaultSoundPath,
3006
+ idleConfirmationDelay: 1500,
3007
+ skipIfIncompleteTodos: true,
3008
+ maxTrackedSessions: 100,
3009
+ ...config
3010
+ };
3011
+ const notifiedSessions = new Set;
3012
+ const pendingTimers = new Map;
3013
+ const sessionActivitySinceIdle = new Set;
3014
+ const notificationVersions = new Map;
3015
+ function cleanupOldSessions() {
3016
+ const maxSessions = mergedConfig.maxTrackedSessions;
3017
+ if (notifiedSessions.size > maxSessions) {
3018
+ const sessionsToRemove = Array.from(notifiedSessions).slice(0, notifiedSessions.size - maxSessions);
3019
+ sessionsToRemove.forEach((id) => notifiedSessions.delete(id));
3020
+ }
3021
+ if (sessionActivitySinceIdle.size > maxSessions) {
3022
+ const sessionsToRemove = Array.from(sessionActivitySinceIdle).slice(0, sessionActivitySinceIdle.size - maxSessions);
3023
+ sessionsToRemove.forEach((id) => sessionActivitySinceIdle.delete(id));
3024
+ }
3025
+ if (notificationVersions.size > maxSessions) {
3026
+ const sessionsToRemove = Array.from(notificationVersions.keys()).slice(0, notificationVersions.size - maxSessions);
3027
+ sessionsToRemove.forEach((id) => notificationVersions.delete(id));
3028
+ }
3029
+ }
3030
+ function cancelPendingNotification(sessionID) {
3031
+ const timer = pendingTimers.get(sessionID);
3032
+ if (timer) {
3033
+ clearTimeout(timer);
3034
+ pendingTimers.delete(sessionID);
3035
+ }
3036
+ sessionActivitySinceIdle.add(sessionID);
3037
+ notificationVersions.set(sessionID, (notificationVersions.get(sessionID) ?? 0) + 1);
3038
+ }
3039
+ function markSessionActivity(sessionID) {
3040
+ cancelPendingNotification(sessionID);
3041
+ notifiedSessions.delete(sessionID);
3042
+ }
3043
+ async function executeNotification(sessionID, version) {
3044
+ pendingTimers.delete(sessionID);
3045
+ if (notificationVersions.get(sessionID) !== version) {
3046
+ return;
3047
+ }
3048
+ if (sessionActivitySinceIdle.has(sessionID)) {
3049
+ sessionActivitySinceIdle.delete(sessionID);
3050
+ return;
3051
+ }
3052
+ if (notifiedSessions.has(sessionID))
3053
+ return;
3054
+ if (mergedConfig.skipIfIncompleteTodos) {
3055
+ const hasPendingWork = await hasIncompleteTodos(ctx, sessionID);
3056
+ if (notificationVersions.get(sessionID) !== version) {
3057
+ return;
3058
+ }
3059
+ if (hasPendingWork)
3060
+ return;
3061
+ }
3062
+ if (notificationVersions.get(sessionID) !== version) {
3063
+ return;
3064
+ }
3065
+ notifiedSessions.add(sessionID);
3066
+ try {
3067
+ await sendNotification(ctx, currentPlatform, mergedConfig.title, mergedConfig.message);
3068
+ if (mergedConfig.playSound && mergedConfig.soundPath) {
3069
+ await playSound(ctx, currentPlatform, mergedConfig.soundPath);
3070
+ }
3071
+ } catch {}
3072
+ }
3073
+ return async ({ event }) => {
3074
+ if (currentPlatform === "unsupported")
3075
+ return;
3076
+ const props = event.properties;
3077
+ if (event.type === "session.updated" || event.type === "session.created") {
3078
+ const info = props?.info;
3079
+ const sessionID = info?.id;
3080
+ if (sessionID) {
3081
+ markSessionActivity(sessionID);
3082
+ }
3083
+ return;
3084
+ }
3085
+ if (event.type === "session.idle") {
3086
+ const sessionID = props?.sessionID;
3087
+ if (!sessionID)
3088
+ return;
3089
+ if (notifiedSessions.has(sessionID))
3090
+ return;
3091
+ if (pendingTimers.has(sessionID))
3092
+ return;
3093
+ sessionActivitySinceIdle.delete(sessionID);
3094
+ const currentVersion = (notificationVersions.get(sessionID) ?? 0) + 1;
3095
+ notificationVersions.set(sessionID, currentVersion);
3096
+ const timer = setTimeout(() => {
3097
+ executeNotification(sessionID, currentVersion);
3098
+ }, mergedConfig.idleConfirmationDelay);
3099
+ pendingTimers.set(sessionID, timer);
3100
+ cleanupOldSessions();
3101
+ return;
3102
+ }
3103
+ if (event.type === "message.updated" || event.type === "message.created") {
3104
+ const info = props?.info;
3105
+ const sessionID = info?.sessionID;
3106
+ if (sessionID) {
3107
+ markSessionActivity(sessionID);
3108
+ }
3109
+ return;
3110
+ }
3111
+ if (event.type === "tool.execute.before" || event.type === "tool.execute.after") {
3112
+ const sessionID = props?.sessionID;
3113
+ if (sessionID) {
3114
+ markSessionActivity(sessionID);
3115
+ }
3116
+ return;
3117
+ }
3118
+ if (event.type === "session.deleted") {
3119
+ const sessionInfo = props?.info;
3120
+ if (sessionInfo?.id) {
3121
+ cancelPendingNotification(sessionInfo.id);
3122
+ notifiedSessions.delete(sessionInfo.id);
3123
+ sessionActivitySinceIdle.delete(sessionInfo.id);
3124
+ notificationVersions.delete(sessionInfo.id);
3125
+ }
3126
+ }
3127
+ };
3128
+ }
2937
3129
  // src/hooks/session-recovery/storage.ts
2938
3130
  import { existsSync as existsSync3, mkdirSync, readdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "fs";
2939
3131
  import { join as join4 } from "path";
@@ -6330,6 +6522,91 @@ function createAutoUpdateCheckerHook(ctx) {
6330
6522
  }
6331
6523
  };
6332
6524
  }
6525
+ // src/hooks/ultrawork-mode/constants.ts
6526
+ var ULTRAWORK_PATTERNS = [/\bultrawork\b/i, /\bulw\b/i];
6527
+ var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
6528
+ var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
6529
+ var ULTRAWORK_CONTEXT = `<ultrawork-mode>
6530
+ [CODE RED] Maximum precision required. Ultrathink before acting.
6531
+
6532
+ YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
6533
+ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
6534
+
6535
+ ## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
6536
+ - **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
6537
+ - **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
6538
+ - **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
6539
+ - **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
6540
+ - **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
6541
+
6542
+ ## EXECUTION RULES
6543
+ - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
6544
+ - **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
6545
+ - **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
6546
+ - **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
6547
+ - **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
6548
+
6549
+ ## WORKFLOW
6550
+ 1. Analyze the request and identify required capabilities
6551
+ 2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
6552
+ 3. Use planning agents to create detailed work breakdown
6553
+ 4. Execute with continuous verification against original requirements
6554
+
6555
+ </ultrawork-mode>
6556
+
6557
+ ---
6558
+
6559
+ `;
6560
+
6561
+ // src/hooks/ultrawork-mode/detector.ts
6562
+ function removeCodeBlocks2(text) {
6563
+ return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
6564
+ }
6565
+ function detectUltraworkKeyword(text) {
6566
+ const textWithoutCode = removeCodeBlocks2(text);
6567
+ return ULTRAWORK_PATTERNS.some((pattern) => pattern.test(textWithoutCode));
6568
+ }
6569
+ function extractPromptText2(parts) {
6570
+ return parts.filter((p) => p.type === "text").map((p) => p.text || "").join("");
6571
+ }
6572
+
6573
+ // src/hooks/ultrawork-mode/index.ts
6574
+ var ultraworkModeState = new Map;
6575
+ function createUltraworkModeHook() {
6576
+ return {
6577
+ "chat.message": async (input, output) => {
6578
+ const state = {
6579
+ detected: false,
6580
+ injected: false
6581
+ };
6582
+ const promptText = extractPromptText2(output.parts);
6583
+ if (!detectUltraworkKeyword(promptText)) {
6584
+ ultraworkModeState.set(input.sessionID, state);
6585
+ return;
6586
+ }
6587
+ state.detected = true;
6588
+ log("Ultrawork keyword detected", { sessionID: input.sessionID });
6589
+ const parts = output.parts;
6590
+ const idx = parts.findIndex((p) => p.type === "text" && p.text);
6591
+ if (idx >= 0) {
6592
+ parts[idx].text = `${ULTRAWORK_CONTEXT}${parts[idx].text ?? ""}`;
6593
+ state.injected = true;
6594
+ log("Ultrawork context injected", { sessionID: input.sessionID });
6595
+ }
6596
+ ultraworkModeState.set(input.sessionID, state);
6597
+ },
6598
+ event: async ({
6599
+ event
6600
+ }) => {
6601
+ if (event.type === "session.deleted") {
6602
+ const props = event.properties;
6603
+ if (props?.info?.id) {
6604
+ ultraworkModeState.delete(props.info.id);
6605
+ }
6606
+ }
6607
+ }
6608
+ };
6609
+ }
6333
6610
  // src/auth/antigravity/constants.ts
6334
6611
  var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
6335
6612
  var ANTIGRAVITY_CLIENT_SECRET = "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";
@@ -7434,6 +7711,18 @@ function isRetryableError(status) {
7434
7711
  return true;
7435
7712
  return false;
7436
7713
  }
7714
+ var GCP_PERMISSION_ERROR_PATTERNS = [
7715
+ "PERMISSION_DENIED",
7716
+ "does not have permission",
7717
+ "Cloud AI Companion API has not been used",
7718
+ "has not been enabled"
7719
+ ];
7720
+ function isGcpPermissionError(text) {
7721
+ return GCP_PERMISSION_ERROR_PATTERNS.some((pattern) => text.includes(pattern));
7722
+ }
7723
+ function calculateRetryDelay2(attempt) {
7724
+ return Math.min(200 * Math.pow(2, attempt), 2000);
7725
+ }
7437
7726
  async function isRetryableResponse(response) {
7438
7727
  if (isRetryableError(response.status))
7439
7728
  return true;
@@ -7496,18 +7785,36 @@ async function attemptFetch(options) {
7496
7785
  thoughtSignature
7497
7786
  });
7498
7787
  debugLog6(`[REQ] streaming=${transformed.streaming}, url=${transformed.url}`);
7499
- const response = await fetch(transformed.url, {
7500
- method: init.method || "POST",
7501
- headers: transformed.headers,
7502
- body: JSON.stringify(transformed.body),
7503
- signal: init.signal
7504
- });
7505
- debugLog6(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
7506
- if (!response.ok && await isRetryableResponse(response)) {
7507
- debugLog6(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
7508
- return null;
7788
+ const maxPermissionRetries = 10;
7789
+ for (let attempt = 0;attempt <= maxPermissionRetries; attempt++) {
7790
+ const response = await fetch(transformed.url, {
7791
+ method: init.method || "POST",
7792
+ headers: transformed.headers,
7793
+ body: JSON.stringify(transformed.body),
7794
+ signal: init.signal
7795
+ });
7796
+ debugLog6(`[RESP] status=${response.status} content-type=${response.headers.get("content-type") ?? ""} url=${response.url}`);
7797
+ if (response.status === 403) {
7798
+ try {
7799
+ const text = await response.clone().text();
7800
+ if (isGcpPermissionError(text)) {
7801
+ if (attempt < maxPermissionRetries) {
7802
+ const delay = calculateRetryDelay2(attempt);
7803
+ debugLog6(`[RETRY] GCP permission error, retry ${attempt + 1}/${maxPermissionRetries} after ${delay}ms`);
7804
+ await new Promise((resolve4) => setTimeout(resolve4, delay));
7805
+ continue;
7806
+ }
7807
+ debugLog6(`[RETRY] GCP permission error, max retries exceeded`);
7808
+ }
7809
+ } catch {}
7810
+ }
7811
+ if (!response.ok && await isRetryableResponse(response)) {
7812
+ debugLog6(`Endpoint failed: ${endpoint} (status: ${response.status}), trying next`);
7813
+ return null;
7814
+ }
7815
+ return response;
7509
7816
  }
7510
- return response;
7817
+ return null;
7511
7818
  } catch (error) {
7512
7819
  debugLog6(`Endpoint failed: ${endpoint} (${error instanceof Error ? error.message : "Unknown error"}), trying next`);
7513
7820
  return null;
@@ -19435,10 +19742,10 @@ function _property(property, schema, params) {
19435
19742
  ...normalizeParams(params)
19436
19743
  });
19437
19744
  }
19438
- function _mime(types9, params) {
19745
+ function _mime(types10, params) {
19439
19746
  return new $ZodCheckMimeType({
19440
19747
  check: "mime_type",
19441
- mime: types9,
19748
+ mime: types10,
19442
19749
  ...normalizeParams(params)
19443
19750
  });
19444
19751
  }
@@ -21348,7 +21655,7 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
21348
21655
  ZodType.init(inst, def);
21349
21656
  inst.min = (size, params) => inst.check(_minSize(size, params));
21350
21657
  inst.max = (size, params) => inst.check(_maxSize(size, params));
21351
- inst.mime = (types9, params) => inst.check(_mime(Array.isArray(types9) ? types9 : [types9], params));
21658
+ inst.mime = (types10, params) => inst.check(_mime(Array.isArray(types10) ? types10 : [types10], params));
21352
21659
  });
21353
21660
  function file(params) {
21354
21661
  return _file(ZodFile, params);
@@ -22121,7 +22428,7 @@ function isValidBinary(filePath) {
22121
22428
  }
22122
22429
  }
22123
22430
  function getPlatformPackageName() {
22124
- const platform = process.platform;
22431
+ const platform2 = process.platform;
22125
22432
  const arch = process.arch;
22126
22433
  const platformMap = {
22127
22434
  "darwin-arm64": "@ast-grep/cli-darwin-arm64",
@@ -22132,7 +22439,7 @@ function getPlatformPackageName() {
22132
22439
  "win32-arm64": "@ast-grep/cli-win32-arm64-msvc",
22133
22440
  "win32-ia32": "@ast-grep/cli-win32-ia32-msvc"
22134
22441
  };
22135
- return platformMap[`${platform}-${arch}`] ?? null;
22442
+ return platformMap[`${platform2}-${arch}`] ?? null;
22136
22443
  }
22137
22444
  function findSgCliPathSync() {
22138
22445
  const binaryName = process.platform === "win32" ? "sg.exe" : "sg";
@@ -23479,20 +23786,48 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
23479
23786
  function delay(ms) {
23480
23787
  return new Promise((resolve8) => setTimeout(resolve8, ms));
23481
23788
  }
23789
+ function truncateText(text, maxLength) {
23790
+ if (text.length <= maxLength)
23791
+ return text;
23792
+ return text.slice(0, maxLength) + "...";
23793
+ }
23482
23794
  function formatTaskStatus(task) {
23483
23795
  const duration3 = formatDuration(task.startedAt, task.completedAt);
23484
- const progress = task.progress ? `
23796
+ const promptPreview = truncateText(task.prompt, 500);
23797
+ let progressSection = "";
23798
+ if (task.progress) {
23799
+ progressSection = `
23485
23800
  Tool calls: ${task.progress.toolCalls}
23486
- Last tool: ${task.progress.lastTool ?? "N/A"}` : "";
23487
- return `Task Status
23801
+ Last tool: ${task.progress.lastTool ?? "N/A"}`;
23802
+ }
23803
+ let lastMessageSection = "";
23804
+ if (task.progress?.lastMessage) {
23805
+ const truncated = truncateText(task.progress.lastMessage, 500);
23806
+ const messageTime = task.progress.lastMessageAt ? task.progress.lastMessageAt.toISOString() : "N/A";
23807
+ lastMessageSection = `
23488
23808
 
23489
- Task ID: ${task.id}
23490
- Description: ${task.description}
23491
- Agent: ${task.agent}
23492
- Status: ${task.status}
23493
- Duration: ${duration3}${progress}
23809
+ ## Last Message (${messageTime})
23494
23810
 
23495
- Session ID: ${task.sessionID}`;
23811
+ \`\`\`
23812
+ ${truncated}
23813
+ \`\`\``;
23814
+ }
23815
+ return `# Task Status
23816
+
23817
+ | Field | Value |
23818
+ |-------|-------|
23819
+ | Task ID | \`${task.id}\` |
23820
+ | Description | ${task.description} |
23821
+ | Agent | ${task.agent} |
23822
+ | Status | **${task.status}** |
23823
+ | Duration | ${duration3} |
23824
+ | Session ID | \`${task.sessionID}\` |${progressSection}
23825
+
23826
+ ## Original Prompt
23827
+
23828
+ \`\`\`
23829
+ ${promptPreview}
23830
+ \`\`\`${lastMessageSection}`;
23496
23831
  }
23497
23832
  async function formatTaskResult(task, client2) {
23498
23833
  const messagesResult = await client2.session.messages({
@@ -23834,6 +24169,7 @@ class BackgroundManager {
23834
24169
  parentSessionID: input.parentSessionID,
23835
24170
  parentMessageID: input.parentMessageID,
23836
24171
  description: input.description,
24172
+ prompt: input.prompt,
23837
24173
  agent: input.agent,
23838
24174
  status: "running",
23839
24175
  startedAt: new Date,
@@ -23991,23 +24327,18 @@ class BackgroundManager {
23991
24327
  }).catch(() => {});
23992
24328
  }
23993
24329
  const message = `[BACKGROUND TASK COMPLETED] Task "${task.description}" finished in ${duration3}. Use background_output with task_id="${task.id}" to get results.`;
23994
- const mainSessionID2 = getMainSessionID();
23995
- if (!mainSessionID2) {
23996
- log("[background-agent] No main session ID available, relying on pending queue");
23997
- return;
23998
- }
23999
- log("[background-agent] Sending notification to main session:", mainSessionID2);
24330
+ log("[background-agent] Sending notification to parent session:", { parentSessionID: task.parentSessionID });
24000
24331
  setTimeout(async () => {
24001
24332
  try {
24002
24333
  await this.client.session.prompt({
24003
- path: { id: mainSessionID2 },
24334
+ path: { id: task.parentSessionID },
24004
24335
  body: {
24005
24336
  parts: [{ type: "text", text: message }]
24006
24337
  },
24007
24338
  query: { directory: this.directory }
24008
24339
  });
24009
24340
  this.clearNotificationsForTask(task.id);
24010
- log("[background-agent] Successfully sent prompt to main session");
24341
+ log("[background-agent] Successfully sent prompt to parent session:", { parentSessionID: task.parentSessionID });
24011
24342
  } catch (error45) {
24012
24343
  log("[background-agent] prompt failed:", String(error45));
24013
24344
  }
@@ -24060,6 +24391,7 @@ class BackgroundManager {
24060
24391
  const assistantMsgs = messages.filter((m) => m.info?.role === "assistant");
24061
24392
  let toolCalls = 0;
24062
24393
  let lastTool;
24394
+ let lastMessage;
24063
24395
  for (const msg of assistantMsgs) {
24064
24396
  const parts = msg.parts ?? [];
24065
24397
  for (const part of parts) {
@@ -24067,6 +24399,9 @@ class BackgroundManager {
24067
24399
  toolCalls++;
24068
24400
  lastTool = part.tool || part.name || "unknown";
24069
24401
  }
24402
+ if (part.type === "text" && part.text) {
24403
+ lastMessage = part.text;
24404
+ }
24070
24405
  }
24071
24406
  }
24072
24407
  if (!task.progress) {
@@ -24075,6 +24410,10 @@ class BackgroundManager {
24075
24410
  task.progress.toolCalls = toolCalls;
24076
24411
  task.progress.lastTool = lastTool;
24077
24412
  task.progress.lastUpdate = new Date;
24413
+ if (lastMessage) {
24414
+ task.progress.lastMessage = lastMessage;
24415
+ task.progress.lastMessageAt = new Date;
24416
+ }
24078
24417
  }
24079
24418
  } catch (error45) {
24080
24419
  log("[background-agent] Poll error for task:", { taskId: task.id, error: error45 });
@@ -24137,6 +24476,23 @@ var AgentNameSchema = exports_external.enum([
24137
24476
  "frontend-ui-ux-engineer",
24138
24477
  "document-writer"
24139
24478
  ]);
24479
+ var HookNameSchema = exports_external.enum([
24480
+ "todo-continuation-enforcer",
24481
+ "context-window-monitor",
24482
+ "session-recovery",
24483
+ "session-notification",
24484
+ "comment-checker",
24485
+ "grep-output-truncator",
24486
+ "directory-agents-injector",
24487
+ "directory-readme-injector",
24488
+ "empty-task-response-detector",
24489
+ "think-mode",
24490
+ "anthropic-auto-compact",
24491
+ "rules-injector",
24492
+ "background-notification",
24493
+ "auto-update-checker",
24494
+ "ultrawork-mode"
24495
+ ]);
24140
24496
  var AgentOverrideConfigSchema = exports_external.object({
24141
24497
  model: exports_external.string().optional(),
24142
24498
  temperature: exports_external.number().min(0).max(2).optional(),
@@ -24167,6 +24523,7 @@ var OhMyOpenCodeConfigSchema = exports_external.object({
24167
24523
  $schema: exports_external.string().optional(),
24168
24524
  disabled_mcps: exports_external.array(McpNameSchema).optional(),
24169
24525
  disabled_agents: exports_external.array(AgentNameSchema).optional(),
24526
+ disabled_hooks: exports_external.array(HookNameSchema).optional(),
24170
24527
  agents: AgentOverridesSchema.optional(),
24171
24528
  claude_code: ClaudeCodeConfigSchema.optional(),
24172
24529
  google_auth: exports_external.boolean().optional()
@@ -24216,6 +24573,12 @@ function mergeConfigs(base, override) {
24216
24573
  ...override.disabled_mcps ?? []
24217
24574
  ])
24218
24575
  ],
24576
+ disabled_hooks: [
24577
+ ...new Set([
24578
+ ...base.disabled_hooks ?? [],
24579
+ ...override.disabled_hooks ?? []
24580
+ ])
24581
+ ],
24219
24582
  claude_code: deepMerge(base.claude_code, override.claude_code)
24220
24583
  };
24221
24584
  }
@@ -24231,32 +24594,39 @@ function loadPluginConfig(directory) {
24231
24594
  agents: config3.agents,
24232
24595
  disabled_agents: config3.disabled_agents,
24233
24596
  disabled_mcps: config3.disabled_mcps,
24597
+ disabled_hooks: config3.disabled_hooks,
24234
24598
  claude_code: config3.claude_code
24235
24599
  });
24236
24600
  return config3;
24237
24601
  }
24238
24602
  var OhMyOpenCodePlugin = async (ctx) => {
24239
24603
  const pluginConfig = loadPluginConfig(ctx.directory);
24240
- const todoContinuationEnforcer = createTodoContinuationEnforcer(ctx);
24241
- const contextWindowMonitor = createContextWindowMonitorHook(ctx);
24242
- const sessionRecovery = createSessionRecoveryHook(ctx);
24243
- sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
24244
- sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
24245
- const commentChecker = createCommentCheckerHooks();
24246
- const grepOutputTruncator = createGrepOutputTruncatorHook(ctx);
24247
- const directoryAgentsInjector = createDirectoryAgentsInjectorHook(ctx);
24248
- const directoryReadmeInjector = createDirectoryReadmeInjectorHook(ctx);
24249
- const emptyTaskResponseDetector = createEmptyTaskResponseDetectorHook(ctx);
24250
- const thinkMode = createThinkModeHook();
24604
+ const disabledHooks = new Set(pluginConfig.disabled_hooks ?? []);
24605
+ const isHookEnabled = (hookName) => !disabledHooks.has(hookName);
24606
+ const todoContinuationEnforcer = isHookEnabled("todo-continuation-enforcer") ? createTodoContinuationEnforcer(ctx) : null;
24607
+ const contextWindowMonitor = isHookEnabled("context-window-monitor") ? createContextWindowMonitorHook(ctx) : null;
24608
+ const sessionRecovery = isHookEnabled("session-recovery") ? createSessionRecoveryHook(ctx) : null;
24609
+ const sessionNotification = isHookEnabled("session-notification") ? createSessionNotification(ctx) : null;
24610
+ if (sessionRecovery && todoContinuationEnforcer) {
24611
+ sessionRecovery.setOnAbortCallback(todoContinuationEnforcer.markRecovering);
24612
+ sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
24613
+ }
24614
+ const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
24615
+ const grepOutputTruncator = isHookEnabled("grep-output-truncator") ? createGrepOutputTruncatorHook(ctx) : null;
24616
+ const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
24617
+ const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
24618
+ const emptyTaskResponseDetector = isHookEnabled("empty-task-response-detector") ? createEmptyTaskResponseDetectorHook(ctx) : null;
24619
+ const thinkMode = isHookEnabled("think-mode") ? createThinkModeHook() : null;
24251
24620
  const claudeCodeHooks = createClaudeCodeHooksHook(ctx, {
24252
24621
  disabledHooks: pluginConfig.claude_code?.hooks ?? true ? undefined : true
24253
24622
  });
24254
- const anthropicAutoCompact = createAnthropicAutoCompactHook(ctx);
24255
- const rulesInjector = createRulesInjectorHook(ctx);
24256
- const autoUpdateChecker = createAutoUpdateCheckerHook(ctx);
24623
+ const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx) : null;
24624
+ const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
24625
+ const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx) : null;
24626
+ const ultraworkMode = isHookEnabled("ultrawork-mode") ? createUltraworkModeHook() : null;
24257
24627
  updateTerminalTitle({ sessionId: "main" });
24258
24628
  const backgroundManager = new BackgroundManager(ctx);
24259
- const backgroundNotificationHook = createBackgroundNotificationHook(backgroundManager);
24629
+ const backgroundNotificationHook = isHookEnabled("background-notification") ? createBackgroundNotificationHook(backgroundManager) : null;
24260
24630
  const backgroundTools = createBackgroundTools(backgroundManager, ctx.client);
24261
24631
  const callOmoAgent = createCallOmoAgent(ctx, backgroundManager);
24262
24632
  const googleAuthHooks = pluginConfig.google_auth ? await createGoogleAntigravityAuthPlugin(ctx) : null;
@@ -24269,6 +24639,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
24269
24639
  },
24270
24640
  "chat.message": async (input, output) => {
24271
24641
  await claudeCodeHooks["chat.message"]?.(input, output);
24642
+ await ultraworkMode?.["chat.message"]?.(input, output);
24272
24643
  },
24273
24644
  config: async (config3) => {
24274
24645
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents);
@@ -24319,16 +24690,18 @@ var OhMyOpenCodePlugin = async (ctx) => {
24319
24690
  };
24320
24691
  },
24321
24692
  event: async (input) => {
24322
- await autoUpdateChecker.event(input);
24693
+ await autoUpdateChecker?.event(input);
24323
24694
  await claudeCodeHooks.event(input);
24324
- await backgroundNotificationHook.event(input);
24325
- await todoContinuationEnforcer.handler(input);
24326
- await contextWindowMonitor.event(input);
24327
- await directoryAgentsInjector.event(input);
24328
- await directoryReadmeInjector.event(input);
24329
- await rulesInjector.event(input);
24330
- await thinkMode.event(input);
24331
- await anthropicAutoCompact.event(input);
24695
+ await backgroundNotificationHook?.event(input);
24696
+ await sessionNotification?.(input);
24697
+ await todoContinuationEnforcer?.handler(input);
24698
+ await contextWindowMonitor?.event(input);
24699
+ await directoryAgentsInjector?.event(input);
24700
+ await directoryReadmeInjector?.event(input);
24701
+ await rulesInjector?.event(input);
24702
+ await thinkMode?.event(input);
24703
+ await anthropicAutoCompact?.event(input);
24704
+ await ultraworkMode?.event(input);
24332
24705
  const { event } = input;
24333
24706
  const props = event.properties;
24334
24707
  if (event.type === "session.created") {
@@ -24370,7 +24743,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
24370
24743
  if (event.type === "session.error") {
24371
24744
  const sessionID = props?.sessionID;
24372
24745
  const error45 = props?.error;
24373
- if (sessionRecovery.isRecoverableError(error45)) {
24746
+ if (sessionRecovery?.isRecoverableError(error45)) {
24374
24747
  const messageInfo = {
24375
24748
  id: props?.messageID,
24376
24749
  role: "assistant",
@@ -24409,7 +24782,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
24409
24782
  },
24410
24783
  "tool.execute.before": async (input, output) => {
24411
24784
  await claudeCodeHooks["tool.execute.before"](input, output);
24412
- await commentChecker["tool.execute.before"](input, output);
24785
+ await commentChecker?.["tool.execute.before"](input, output);
24413
24786
  if (input.sessionID === getMainSessionID()) {
24414
24787
  updateTerminalTitle({
24415
24788
  sessionId: input.sessionID,
@@ -24422,13 +24795,13 @@ var OhMyOpenCodePlugin = async (ctx) => {
24422
24795
  },
24423
24796
  "tool.execute.after": async (input, output) => {
24424
24797
  await claudeCodeHooks["tool.execute.after"](input, output);
24425
- await grepOutputTruncator["tool.execute.after"](input, output);
24426
- await contextWindowMonitor["tool.execute.after"](input, output);
24427
- await commentChecker["tool.execute.after"](input, output);
24428
- await directoryAgentsInjector["tool.execute.after"](input, output);
24429
- await directoryReadmeInjector["tool.execute.after"](input, output);
24430
- await rulesInjector["tool.execute.after"](input, output);
24431
- await emptyTaskResponseDetector["tool.execute.after"](input, output);
24798
+ await grepOutputTruncator?.["tool.execute.after"](input, output);
24799
+ await contextWindowMonitor?.["tool.execute.after"](input, output);
24800
+ await commentChecker?.["tool.execute.after"](input, output);
24801
+ await directoryAgentsInjector?.["tool.execute.after"](input, output);
24802
+ await directoryReadmeInjector?.["tool.execute.after"](input, output);
24803
+ await rulesInjector?.["tool.execute.after"](input, output);
24804
+ await emptyTaskResponseDetector?.["tool.execute.after"](input, output);
24432
24805
  if (input.sessionID === getMainSessionID()) {
24433
24806
  updateTerminalTitle({
24434
24807
  sessionId: input.sessionID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "0.4.4",
3
+ "version": "1.0.0",
4
4
  "description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",