oh-my-opencode 0.4.3 → 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 +18 -4
- package/README.md +18 -4
- package/dist/config/index.d.ts +2 -2
- package/dist/config/schema.d.ts +35 -0
- package/dist/features/background-agent/types.d.ts +3 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/session-notification.d.ts +2 -0
- package/dist/hooks/ultrawork-mode/constants.d.ts +15 -0
- package/dist/hooks/ultrawork-mode/detector.d.ts +16 -0
- package/dist/hooks/ultrawork-mode/index.d.ts +40 -0
- package/dist/hooks/ultrawork-mode/types.d.ts +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +441 -68
- package/package.json +1 -1
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
|
-
|
|
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
|
-
**사용자가 명시적으로 요청하지 않는 한,
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
package/dist/config/index.d.ts
CHANGED
|
@@ -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";
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -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;
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -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
|
|
188
|
-
return
|
|
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
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
debugLog6(`
|
|
7508
|
-
|
|
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
|
|
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(
|
|
19745
|
+
function _mime(types10, params) {
|
|
19439
19746
|
return new $ZodCheckMimeType({
|
|
19440
19747
|
check: "mime_type",
|
|
19441
|
-
mime:
|
|
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 = (
|
|
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
|
|
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[`${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
23490
|
-
Description: ${task.description}
|
|
23491
|
-
Agent: ${task.agent}
|
|
23492
|
-
Status: ${task.status}
|
|
23493
|
-
Duration: ${duration3}${progress}
|
|
23809
|
+
## Last Message (${messageTime})
|
|
23494
23810
|
|
|
23495
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
|
24241
|
-
const
|
|
24242
|
-
const
|
|
24243
|
-
|
|
24244
|
-
sessionRecovery
|
|
24245
|
-
const
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
const
|
|
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
|
|
24693
|
+
await autoUpdateChecker?.event(input);
|
|
24323
24694
|
await claudeCodeHooks.event(input);
|
|
24324
|
-
await backgroundNotificationHook
|
|
24325
|
-
await
|
|
24326
|
-
await
|
|
24327
|
-
await
|
|
24328
|
-
await
|
|
24329
|
-
await
|
|
24330
|
-
await
|
|
24331
|
-
await
|
|
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
|
|
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,
|