oh-my-opencode 1.1.7 → 1.1.9

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
@@ -602,3 +602,5 @@ OpenCode 를 사용하여 이 프로젝트의 99% 를 작성했습니다. 기능
602
602
  - [1.0.132](https://github.com/sst/opencode/releases/tag/v1.0.132) 혹은 이것보다 낮은 버전을 사용중이라면, OpenCode 의 버그로 인해 제대로 구성이 되지 않을 수 있습니다.
603
603
  - [이를 고치는 PR 이 1.0.132 배포 이후에 병합되었으므로](https://github.com/sst/opencode/pull/5040) 이 변경사항이 포함된 최신 버전을 사용해주세요.
604
604
  - TMI: PR 도 OhMyOpenCode 의 셋업의 Librarian, Explore, Oracle 을 활용하여 우연히 발견하고 해결되었습니다.
605
+
606
+ *멋진 히어로 이미지를 만들어주신 히어로 [@junhoyeo](https://github.com/junhoyeo) 께 감사드립니다*
package/README.md CHANGED
@@ -604,4 +604,4 @@ I have no affiliation with any project or model mentioned here. This is purely p
604
604
  - [The fix](https://github.com/sst/opencode/pull/5040) was merged after 1.0.132—use a newer version.
605
605
  - Fun fact: That PR was discovered and fixed thanks to OhMyOpenCode's Librarian, Explore, and Oracle setup.
606
606
 
607
- *Special thanks to @junhoyeo for this amazing hero image.*
607
+ *Special thanks to [@junhoyeo](https://github.com/junhoyeo) for this amazing hero image.*
@@ -33,6 +33,7 @@ export declare const HookNameSchema: z.ZodEnum<{
33
33
  "session-recovery": "session-recovery";
34
34
  "session-notification": "session-notification";
35
35
  "grep-output-truncator": "grep-output-truncator";
36
+ "tool-output-truncator": "tool-output-truncator";
36
37
  "directory-agents-injector": "directory-agents-injector";
37
38
  "directory-readme-injector": "directory-readme-injector";
38
39
  "empty-task-response-detector": "empty-task-response-detector";
@@ -40,7 +41,7 @@ export declare const HookNameSchema: z.ZodEnum<{
40
41
  "anthropic-auto-compact": "anthropic-auto-compact";
41
42
  "background-notification": "background-notification";
42
43
  "auto-update-checker": "auto-update-checker";
43
- "ultrawork-mode": "ultrawork-mode";
44
+ "keyword-detector": "keyword-detector";
44
45
  }>;
45
46
  export declare const AgentOverrideConfigSchema: z.ZodObject<{
46
47
  model: z.ZodOptional<z.ZodString>;
@@ -443,6 +444,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
443
444
  "session-recovery": "session-recovery";
444
445
  "session-notification": "session-notification";
445
446
  "grep-output-truncator": "grep-output-truncator";
447
+ "tool-output-truncator": "tool-output-truncator";
446
448
  "directory-agents-injector": "directory-agents-injector";
447
449
  "directory-readme-injector": "directory-readme-injector";
448
450
  "empty-task-response-detector": "empty-task-response-detector";
@@ -450,7 +452,7 @@ export declare const OhMyOpenCodeConfigSchema: z.ZodObject<{
450
452
  "anthropic-auto-compact": "anthropic-auto-compact";
451
453
  "background-notification": "background-notification";
452
454
  "auto-update-checker": "auto-update-checker";
453
- "ultrawork-mode": "ultrawork-mode";
455
+ "keyword-detector": "keyword-detector";
454
456
  }>>>;
455
457
  agents: z.ZodOptional<z.ZodObject<{
456
458
  build: z.ZodOptional<z.ZodOptional<z.ZodObject<{
@@ -4,6 +4,7 @@ export { createSessionNotification } from "./session-notification";
4
4
  export { createSessionRecoveryHook, type SessionRecoveryHook } from "./session-recovery";
5
5
  export { createCommentCheckerHooks } from "./comment-checker";
6
6
  export { createGrepOutputTruncatorHook } from "./grep-output-truncator";
7
+ export { createToolOutputTruncatorHook } from "./tool-output-truncator";
7
8
  export { createDirectoryAgentsInjectorHook } from "./directory-agents-injector";
8
9
  export { createDirectoryReadmeInjectorHook } from "./directory-readme-injector";
9
10
  export { createEmptyTaskResponseDetectorHook } from "./empty-task-response-detector";
@@ -13,5 +14,5 @@ export { createClaudeCodeHooksHook } from "./claude-code-hooks";
13
14
  export { createRulesInjectorHook } from "./rules-injector";
14
15
  export { createBackgroundNotificationHook } from "./background-notification";
15
16
  export { createAutoUpdateCheckerHook } from "./auto-update-checker";
16
- export { createUltraworkModeHook } from "./ultrawork-mode";
17
17
  export { createAgentUsageReminderHook } from "./agent-usage-reminder";
18
+ export { createKeywordDetectorHook } from "./keyword-detector";
@@ -0,0 +1,6 @@
1
+ export declare const CODE_BLOCK_PATTERN: RegExp;
2
+ export declare const INLINE_CODE_PATTERN: RegExp;
3
+ export declare const KEYWORD_DETECTORS: Array<{
4
+ pattern: RegExp;
5
+ message: string;
6
+ }>;
@@ -0,0 +1,6 @@
1
+ export declare function removeCodeBlocks(text: string): string;
2
+ export declare function detectKeywords(text: string): string[];
3
+ export declare function extractPromptText(parts: Array<{
4
+ type: string;
5
+ text?: string;
6
+ }>): string;
@@ -1,17 +1,7 @@
1
1
  export * from "./detector";
2
2
  export * from "./constants";
3
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 via history
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, inject ULTRAWORK_CONTEXT via injectHookMessage (history injection)
14
- */
4
+ export declare function createKeywordDetectorHook(): {
15
5
  "chat.message": (input: {
16
6
  sessionID: string;
17
7
  agent?: string;
@@ -28,9 +18,6 @@ export declare function createUltraworkModeHook(): {
28
18
  [key: string]: unknown;
29
19
  }>;
30
20
  }) => Promise<void>;
31
- /**
32
- * event hook - cleanup session state on deletion
33
- */
34
21
  event: ({ event, }: {
35
22
  event: {
36
23
  type: string;
@@ -0,0 +1,4 @@
1
+ export interface KeywordDetectorState {
2
+ detected: boolean;
3
+ injected: boolean;
4
+ }
@@ -0,0 +1,12 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ export declare function createToolOutputTruncatorHook(ctx: PluginInput): {
3
+ "tool.execute.after": (input: {
4
+ tool: string;
5
+ sessionID: string;
6
+ callID: string;
7
+ }, output: {
8
+ title: string;
9
+ output: string;
10
+ metadata: unknown;
11
+ }) => Promise<void>;
12
+ };
package/dist/index.js CHANGED
@@ -2657,7 +2657,13 @@ async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0,
2657
2657
  return resolved;
2658
2658
  }
2659
2659
  // src/shared/model-sanitizer.ts
2660
- function sanitizeModelField(_model) {
2660
+ function sanitizeModelField(model, source = "claude-code") {
2661
+ if (source === "claude-code") {
2662
+ return;
2663
+ }
2664
+ if (typeof model === "string" && model.trim().length > 0) {
2665
+ return model.trim();
2666
+ }
2661
2667
  return;
2662
2668
  }
2663
2669
  // src/shared/logger.ts
@@ -2798,6 +2804,111 @@ function resolveSymlink(filePath) {
2798
2804
  return filePath;
2799
2805
  }
2800
2806
  }
2807
+ // src/shared/dynamic-truncator.ts
2808
+ var ANTHROPIC_ACTUAL_LIMIT = 200000;
2809
+ var CHARS_PER_TOKEN_ESTIMATE = 4;
2810
+ var DEFAULT_TARGET_MAX_TOKENS = 50000;
2811
+ function estimateTokens(text) {
2812
+ return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
2813
+ }
2814
+ function truncateToTokenLimit(output, maxTokens, preserveHeaderLines = 3) {
2815
+ const currentTokens = estimateTokens(output);
2816
+ if (currentTokens <= maxTokens) {
2817
+ return { result: output, truncated: false };
2818
+ }
2819
+ const lines = output.split(`
2820
+ `);
2821
+ if (lines.length <= preserveHeaderLines) {
2822
+ const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE;
2823
+ return {
2824
+ result: output.slice(0, maxChars) + `
2825
+
2826
+ [Output truncated due to context window limit]`,
2827
+ truncated: true
2828
+ };
2829
+ }
2830
+ const headerLines = lines.slice(0, preserveHeaderLines);
2831
+ const contentLines = lines.slice(preserveHeaderLines);
2832
+ const headerText = headerLines.join(`
2833
+ `);
2834
+ const headerTokens = estimateTokens(headerText);
2835
+ const truncationMessageTokens = 50;
2836
+ const availableTokens = maxTokens - headerTokens - truncationMessageTokens;
2837
+ if (availableTokens <= 0) {
2838
+ return {
2839
+ result: headerText + `
2840
+
2841
+ [Content truncated due to context window limit]`,
2842
+ truncated: true,
2843
+ removedCount: contentLines.length
2844
+ };
2845
+ }
2846
+ const resultLines = [];
2847
+ let currentTokenCount = 0;
2848
+ for (const line of contentLines) {
2849
+ const lineTokens = estimateTokens(line + `
2850
+ `);
2851
+ if (currentTokenCount + lineTokens > availableTokens) {
2852
+ break;
2853
+ }
2854
+ resultLines.push(line);
2855
+ currentTokenCount += lineTokens;
2856
+ }
2857
+ const truncatedContent = [...headerLines, ...resultLines].join(`
2858
+ `);
2859
+ const removedCount = contentLines.length - resultLines.length;
2860
+ return {
2861
+ result: truncatedContent + `
2862
+
2863
+ [${removedCount} more lines truncated due to context window limit]`,
2864
+ truncated: true,
2865
+ removedCount
2866
+ };
2867
+ }
2868
+ async function getContextWindowUsage(ctx, sessionID) {
2869
+ try {
2870
+ const response = await ctx.client.session.messages({
2871
+ path: { id: sessionID }
2872
+ });
2873
+ const messages = response.data ?? response;
2874
+ const assistantMessages = messages.filter((m) => m.info.role === "assistant").map((m) => m.info);
2875
+ if (assistantMessages.length === 0)
2876
+ return null;
2877
+ const lastAssistant = assistantMessages[assistantMessages.length - 1];
2878
+ const lastTokens = lastAssistant.tokens;
2879
+ const usedTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0);
2880
+ const remainingTokens = ANTHROPIC_ACTUAL_LIMIT - usedTokens;
2881
+ return {
2882
+ usedTokens,
2883
+ remainingTokens,
2884
+ usagePercentage: usedTokens / ANTHROPIC_ACTUAL_LIMIT
2885
+ };
2886
+ } catch {
2887
+ return null;
2888
+ }
2889
+ }
2890
+ async function dynamicTruncate(ctx, sessionID, output, options = {}) {
2891
+ const { targetMaxTokens = DEFAULT_TARGET_MAX_TOKENS, preserveHeaderLines = 3 } = options;
2892
+ const usage = await getContextWindowUsage(ctx, sessionID);
2893
+ if (!usage) {
2894
+ return { result: output, truncated: false };
2895
+ }
2896
+ const maxOutputTokens = Math.min(usage.remainingTokens * 0.5, targetMaxTokens);
2897
+ if (maxOutputTokens <= 0) {
2898
+ return {
2899
+ result: "[Output suppressed - context window exhausted]",
2900
+ truncated: true
2901
+ };
2902
+ }
2903
+ return truncateToTokenLimit(output, maxOutputTokens, preserveHeaderLines);
2904
+ }
2905
+ function createDynamicTruncator(ctx) {
2906
+ return {
2907
+ truncate: (sessionID, output, options) => dynamicTruncate(ctx, sessionID, output, options),
2908
+ getUsage: (sessionID) => getContextWindowUsage(ctx, sessionID),
2909
+ truncateSync: (output, maxTokens, preserveHeaderLines) => truncateToTokenLimit(output, maxTokens, preserveHeaderLines)
2910
+ };
2911
+ }
2801
2912
  // src/agents/utils.ts
2802
2913
  var allBuiltinAgents = {
2803
2914
  oracle: oracleAgent,
@@ -3116,7 +3227,7 @@ function createTodoContinuationEnforcer(ctx) {
3116
3227
  }
3117
3228
  // src/hooks/context-window-monitor.ts
3118
3229
  var ANTHROPIC_DISPLAY_LIMIT = 1e6;
3119
- var ANTHROPIC_ACTUAL_LIMIT = 200000;
3230
+ var ANTHROPIC_ACTUAL_LIMIT2 = 200000;
3120
3231
  var CONTEXT_WARNING_THRESHOLD = 0.7;
3121
3232
  var CONTEXT_REMINDER = `[SYSTEM REMINDER - 1M Context Window]
3122
3233
 
@@ -3142,7 +3253,7 @@ function createContextWindowMonitorHook(ctx) {
3142
3253
  return;
3143
3254
  const lastTokens = lastAssistant.tokens;
3144
3255
  const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0);
3145
- const actualUsagePercentage = totalInputTokens / ANTHROPIC_ACTUAL_LIMIT;
3256
+ const actualUsagePercentage = totalInputTokens / ANTHROPIC_ACTUAL_LIMIT2;
3146
3257
  if (actualUsagePercentage < CONTEXT_WARNING_THRESHOLD)
3147
3258
  return;
3148
3259
  remindedSessions.add(sessionID);
@@ -4186,88 +4297,25 @@ ${result.message}`;
4186
4297
  debugLog3("CLI: no comments detected");
4187
4298
  }
4188
4299
  }
4189
- // src/hooks/grep-output-truncator.ts
4190
- var ANTHROPIC_ACTUAL_LIMIT2 = 200000;
4191
- var CHARS_PER_TOKEN_ESTIMATE = 4;
4192
- var TARGET_MAX_TOKENS = 50000;
4193
- function estimateTokens(text) {
4194
- return Math.ceil(text.length / CHARS_PER_TOKEN_ESTIMATE);
4195
- }
4196
- function truncateToTokenLimit(output, maxTokens) {
4197
- const currentTokens = estimateTokens(output);
4198
- if (currentTokens <= maxTokens) {
4199
- return { result: output, truncated: false };
4200
- }
4201
- const lines = output.split(`
4202
- `);
4203
- if (lines.length <= 3) {
4204
- const maxChars = maxTokens * CHARS_PER_TOKEN_ESTIMATE;
4205
- return {
4206
- result: output.slice(0, maxChars) + `
4207
-
4208
- [Output truncated due to context window limit]`,
4209
- truncated: true
4210
- };
4211
- }
4212
- const headerLines = lines.slice(0, 3);
4213
- const contentLines = lines.slice(3);
4214
- const headerText = headerLines.join(`
4215
- `);
4216
- const headerTokens = estimateTokens(headerText);
4217
- const availableTokens = maxTokens - headerTokens - 50;
4218
- if (availableTokens <= 0) {
4219
- return {
4220
- result: headerText + `
4221
-
4222
- [Content truncated due to context window limit]`,
4223
- truncated: true
4224
- };
4225
- }
4226
- let resultLines = [];
4227
- let currentTokenCount = 0;
4228
- for (const line of contentLines) {
4229
- const lineTokens = estimateTokens(line + `
4230
- `);
4231
- if (currentTokenCount + lineTokens > availableTokens) {
4232
- break;
4233
- }
4234
- resultLines.push(line);
4235
- currentTokenCount += lineTokens;
4236
- }
4237
- const truncatedContent = [...headerLines, ...resultLines].join(`
4238
- `);
4239
- const removedCount = contentLines.length - resultLines.length;
4240
- return {
4241
- result: truncatedContent + `
4242
-
4243
- [${removedCount} more lines truncated due to context window limit]`,
4244
- truncated: true
4245
- };
4246
- }
4247
- function createGrepOutputTruncatorHook(ctx) {
4248
- const GREP_TOOLS = ["safe_grep", "Grep"];
4300
+ // src/hooks/tool-output-truncator.ts
4301
+ var TRUNCATABLE_TOOLS = [
4302
+ "Grep",
4303
+ "safe_grep",
4304
+ "Glob",
4305
+ "safe_glob",
4306
+ "lsp_find_references",
4307
+ "lsp_document_symbols",
4308
+ "lsp_workspace_symbols",
4309
+ "lsp_diagnostics",
4310
+ "ast_grep_search"
4311
+ ];
4312
+ function createToolOutputTruncatorHook(ctx) {
4313
+ const truncator = createDynamicTruncator(ctx);
4249
4314
  const toolExecuteAfter = async (input, output) => {
4250
- if (!GREP_TOOLS.includes(input.tool))
4315
+ if (!TRUNCATABLE_TOOLS.includes(input.tool))
4251
4316
  return;
4252
- const { sessionID } = input;
4253
4317
  try {
4254
- const response = await ctx.client.session.messages({
4255
- path: { id: sessionID }
4256
- });
4257
- const messages = response.data ?? response;
4258
- const assistantMessages = messages.filter((m) => m.info.role === "assistant").map((m) => m.info);
4259
- if (assistantMessages.length === 0)
4260
- return;
4261
- const lastAssistant = assistantMessages[assistantMessages.length - 1];
4262
- const lastTokens = lastAssistant.tokens;
4263
- const totalInputTokens = (lastTokens?.input ?? 0) + (lastTokens?.cache?.read ?? 0);
4264
- const remainingTokens = ANTHROPIC_ACTUAL_LIMIT2 - totalInputTokens;
4265
- const maxOutputTokens = Math.min(remainingTokens * 0.5, TARGET_MAX_TOKENS);
4266
- if (maxOutputTokens <= 0) {
4267
- output.output = "[Output suppressed - context window exhausted]";
4268
- return;
4269
- }
4270
- const { result, truncated } = truncateToTokenLimit(output.output, maxOutputTokens);
4318
+ const { result, truncated } = await truncator.truncate(input.sessionID, output.output);
4271
4319
  if (truncated) {
4272
4320
  output.output = result;
4273
4321
  }
@@ -6826,97 +6874,6 @@ function createAutoUpdateCheckerHook(ctx) {
6826
6874
  }
6827
6875
  };
6828
6876
  }
6829
- // src/hooks/ultrawork-mode/constants.ts
6830
- var ULTRAWORK_PATTERNS = [/\bultrawork\b/i, /\bulw\b/i];
6831
- var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
6832
- var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
6833
- var ULTRAWORK_CONTEXT = `<ultrawork-mode>
6834
- [CODE RED] Maximum precision required. Ultrathink before acting.
6835
-
6836
- YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
6837
- TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
6838
-
6839
- ## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
6840
- - **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
6841
- - **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
6842
- - **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
6843
- - **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
6844
- - **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
6845
-
6846
- ## EXECUTION RULES
6847
- - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
6848
- - **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
6849
- - **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
6850
- - **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
6851
- - **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
6852
-
6853
- ## WORKFLOW
6854
- 1. Analyze the request and identify required capabilities
6855
- 2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
6856
- 3. Use planning agents to create detailed work breakdown
6857
- 4. Execute with continuous verification against original requirements
6858
-
6859
- </ultrawork-mode>
6860
-
6861
- ---
6862
-
6863
- `;
6864
-
6865
- // src/hooks/ultrawork-mode/detector.ts
6866
- function removeCodeBlocks2(text) {
6867
- return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
6868
- }
6869
- function detectUltraworkKeyword(text) {
6870
- const textWithoutCode = removeCodeBlocks2(text);
6871
- return ULTRAWORK_PATTERNS.some((pattern) => pattern.test(textWithoutCode));
6872
- }
6873
- function extractPromptText2(parts) {
6874
- return parts.filter((p) => p.type === "text").map((p) => p.text || "").join("");
6875
- }
6876
-
6877
- // src/hooks/ultrawork-mode/index.ts
6878
- var ultraworkModeState = new Map;
6879
- function createUltraworkModeHook() {
6880
- return {
6881
- "chat.message": async (input, output) => {
6882
- const state = {
6883
- detected: false,
6884
- injected: false
6885
- };
6886
- const promptText = extractPromptText2(output.parts);
6887
- if (!detectUltraworkKeyword(promptText)) {
6888
- ultraworkModeState.set(input.sessionID, state);
6889
- return;
6890
- }
6891
- state.detected = true;
6892
- log("Ultrawork keyword detected", { sessionID: input.sessionID });
6893
- const message = output.message;
6894
- const success = injectHookMessage(input.sessionID, ULTRAWORK_CONTEXT, {
6895
- agent: message.agent,
6896
- model: message.model,
6897
- path: message.path,
6898
- tools: message.tools
6899
- });
6900
- if (success) {
6901
- state.injected = true;
6902
- log("Ultrawork context injected via history", { sessionID: input.sessionID });
6903
- } else {
6904
- log("Ultrawork context injection failed", { sessionID: input.sessionID });
6905
- }
6906
- ultraworkModeState.set(input.sessionID, state);
6907
- },
6908
- event: async ({
6909
- event
6910
- }) => {
6911
- if (event.type === "session.deleted") {
6912
- const props = event.properties;
6913
- if (props?.info?.id) {
6914
- ultraworkModeState.delete(props.info.id);
6915
- }
6916
- }
6917
- }
6918
- };
6919
- }
6920
6877
  // src/hooks/agent-usage-reminder/storage.ts
6921
6878
  import {
6922
6879
  existsSync as existsSync19,
@@ -7067,6 +7024,123 @@ function createAgentUsageReminderHook(_ctx) {
7067
7024
  event: eventHandler
7068
7025
  };
7069
7026
  }
7027
+ // src/hooks/keyword-detector/constants.ts
7028
+ var CODE_BLOCK_PATTERN2 = /```[\s\S]*?```/g;
7029
+ var INLINE_CODE_PATTERN2 = /`[^`]+`/g;
7030
+ var KEYWORD_DETECTORS = [
7031
+ {
7032
+ pattern: /\b(ultrawork|ulw)\b/i,
7033
+ message: `<ultrawork-mode>
7034
+ [CODE RED] Maximum precision required. Ultrathink before acting.
7035
+
7036
+ YOU MUST LEVERAGE ALL AVAILABLE AGENTS TO THEIR FULLEST POTENTIAL.
7037
+ TELL THE USER WHAT AGENTS YOU WILL LEVERAGE NOW TO SATISFY USER'S REQUEST.
7038
+
7039
+ ## AGENT UTILIZATION PRINCIPLES (by capability, not by name)
7040
+ - **Codebase Exploration**: Spawn exploration agents using BACKGROUND TASKS for file patterns, internal implementations, project structure
7041
+ - **Documentation & References**: Use librarian-type agents via BACKGROUND TASKS for API references, examples, external library docs
7042
+ - **Planning & Strategy**: NEVER plan yourself - ALWAYS spawn a dedicated planning agent for work breakdown
7043
+ - **High-IQ Reasoning**: Leverage specialized agents for architecture decisions, code review, strategic planning
7044
+ - **Frontend/UI Tasks**: Delegate to UI-specialized agents for design and implementation
7045
+
7046
+ ## EXECUTION RULES
7047
+ - **TODO**: Track EVERY step. Mark complete IMMEDIATELY after each.
7048
+ - **PARALLEL**: Fire independent agent calls simultaneously via background_task - NEVER wait sequentially.
7049
+ - **BACKGROUND FIRST**: Use background_task for exploration/research agents (10+ concurrent if needed).
7050
+ - **VERIFY**: Re-read request after completion. Check ALL requirements met before reporting done.
7051
+ - **DELEGATE**: Don't do everything yourself - orchestrate specialized agents for their strengths.
7052
+
7053
+ ## WORKFLOW
7054
+ 1. Analyze the request and identify required capabilities
7055
+ 2. Spawn exploration/librarian agents via background_task in PARALLEL (10+ if needed)
7056
+ 3. Use planning agents to create detailed work breakdown
7057
+ 4. Execute with continuous verification against original requirements
7058
+
7059
+ </ultrawork-mode>
7060
+
7061
+ ---
7062
+
7063
+ `
7064
+ },
7065
+ {
7066
+ pattern: /\b(search|find|locate|lookup|look\s*up|explore|discover|scan|grep|query|browse|detect|trace|seek|track|pinpoint|hunt)\b|where\s+is|show\s+me|list\s+all|\uAC80\uC0C9|\uCC3E\uC544|\uD0D0\uC0C9|\uC870\uD68C|\uC2A4\uCE94|\uC11C\uCE58|\uB4A4\uC838|\uCC3E\uAE30|\uC5B4\uB514|\uCD94\uC801|\uD0D0\uC9C0|\uCC3E\uC544\uBD10|\uCC3E\uC544\uB0B4|\uBCF4\uC5EC\uC918|\uBAA9\uB85D|\u691C\u7D22|\u63A2\u3057\u3066|\u898B\u3064\u3051\u3066|\u30B5\u30FC\u30C1|\u63A2\u7D22|\u30B9\u30AD\u30E3\u30F3|\u3069\u3053|\u767A\u898B|\u635C\u7D22|\u898B\u3064\u3051\u51FA\u3059|\u4E00\u89A7|\u641C\u7D22|\u67E5\u627E|\u5BFB\u627E|\u67E5\u8BE2|\u68C0\u7D22|\u5B9A\u4F4D|\u626B\u63CF|\u53D1\u73B0|\u5728\u54EA\u91CC|\u627E\u51FA\u6765|\u5217\u51FA|t\u00ECm ki\u1EBFm|tra c\u1EE9u|\u0111\u1ECBnh v\u1ECB|qu\u00E9t|ph\u00E1t hi\u1EC7n|truy t\u00ECm|t\u00ECm ra|\u1EDF \u0111\u00E2u|li\u1EC7t k\u00EA/i,
7067
+ message: `[search-mode]
7068
+ MAXIMIZE SEARCH EFFORT. Launch multiple background agents IN PARALLEL:
7069
+ - explore agents (codebase patterns, file structures, ast-grep)
7070
+ - librarian agents (remote repos, official docs, GitHub examples)
7071
+ Plus direct tools: Grep, ripgrep (rg), ast-grep (sg)
7072
+ NEVER stop at first result - be exhaustive.`
7073
+ },
7074
+ {
7075
+ pattern: /\b(analyze|analyse|investigate|examine|research|study|deep[\s-]?dive|inspect|audit|evaluate|assess|review|diagnose|scrutinize|dissect|debug|comprehend|interpret|breakdown|understand)\b|why\s+is|how\s+does|how\s+to|\uBD84\uC11D|\uC870\uC0AC|\uD30C\uC545|\uC5F0\uAD6C|\uAC80\uD1A0|\uC9C4\uB2E8|\uC774\uD574|\uC124\uBA85|\uC6D0\uC778|\uC774\uC720|\uB72F\uC5B4\uBD10|\uB530\uC838\uBD10|\uD3C9\uAC00|\uD574\uC11D|\uB514\uBC84\uAE45|\uB514\uBC84\uADF8|\uC5B4\uB5BB\uAC8C|\uC65C|\uC0B4\uD3B4|\u5206\u6790|\u8ABF\u67FB|\u89E3\u6790|\u691C\u8A0E|\u7814\u7A76|\u8A3A\u65AD|\u7406\u89E3|\u8AAC\u660E|\u691C\u8A3C|\u7CBE\u67FB|\u7A76\u660E|\u30C7\u30D0\u30C3\u30B0|\u306A\u305C|\u3069\u3046|\u4ED5\u7D44\u307F|\u8C03\u67E5|\u68C0\u67E5|\u5256\u6790|\u6DF1\u5165|\u8BCA\u65AD|\u89E3\u91CA|\u8C03\u8BD5|\u4E3A\u4EC0\u4E48|\u539F\u7406|\u641E\u6E05\u695A|\u5F04\u660E\u767D|ph\u00E2n t\u00EDch|\u0111i\u1EC1u tra|nghi\u00EAn c\u1EE9u|ki\u1EC3m tra|xem x\u00E9t|ch\u1EA9n \u0111o\u00E1n|gi\u1EA3i th\u00EDch|t\u00ECm hi\u1EC3u|g\u1EE1 l\u1ED7i|t\u1EA1i sao/i,
7076
+ message: `[analyze-mode]
7077
+ DEEP ANALYSIS MODE. Execute in phases:
7078
+
7079
+ PHASE 1 - GATHER CONTEXT (10+ agents parallel):
7080
+ - 3+ explore agents (codebase structure, patterns, implementations)
7081
+ - 3+ librarian agents (official docs, best practices, examples)
7082
+ - 2+ general agents (different analytical perspectives)
7083
+
7084
+ PHASE 2 - EXPERT CONSULTATION (after Phase 1):
7085
+ - 3+ oracle agents in parallel with gathered context
7086
+ - Each oracle: different angle (architecture, performance, edge cases)
7087
+
7088
+ SYNTHESIZE: Cross-reference findings, identify consensus & contradictions.`
7089
+ }
7090
+ ];
7091
+
7092
+ // src/hooks/keyword-detector/detector.ts
7093
+ function removeCodeBlocks2(text) {
7094
+ return text.replace(CODE_BLOCK_PATTERN2, "").replace(INLINE_CODE_PATTERN2, "");
7095
+ }
7096
+ function detectKeywords(text) {
7097
+ const textWithoutCode = removeCodeBlocks2(text);
7098
+ return KEYWORD_DETECTORS.filter(({ pattern }) => pattern.test(textWithoutCode)).map(({ message }) => message);
7099
+ }
7100
+ function extractPromptText2(parts) {
7101
+ return parts.filter((p) => p.type === "text").map((p) => p.text || "").join(" ");
7102
+ }
7103
+
7104
+ // src/hooks/keyword-detector/index.ts
7105
+ var injectedSessions = new Set;
7106
+ function createKeywordDetectorHook() {
7107
+ return {
7108
+ "chat.message": async (input, output) => {
7109
+ if (injectedSessions.has(input.sessionID)) {
7110
+ return;
7111
+ }
7112
+ const promptText = extractPromptText2(output.parts);
7113
+ const messages = detectKeywords(promptText);
7114
+ if (messages.length === 0) {
7115
+ return;
7116
+ }
7117
+ log(`Keywords detected: ${messages.length}`, { sessionID: input.sessionID });
7118
+ const message = output.message;
7119
+ const context = messages.join(`
7120
+ `);
7121
+ const success = injectHookMessage(input.sessionID, context, {
7122
+ agent: message.agent,
7123
+ model: message.model,
7124
+ path: message.path,
7125
+ tools: message.tools
7126
+ });
7127
+ if (success) {
7128
+ injectedSessions.add(input.sessionID);
7129
+ log("Keyword context injected", { sessionID: input.sessionID });
7130
+ }
7131
+ },
7132
+ event: async ({
7133
+ event
7134
+ }) => {
7135
+ if (event.type === "session.deleted") {
7136
+ const props = event.properties;
7137
+ if (props?.info?.id) {
7138
+ injectedSessions.delete(props.info.id);
7139
+ }
7140
+ }
7141
+ }
7142
+ };
7143
+ }
7070
7144
  // src/auth/antigravity/constants.ts
7071
7145
  var ANTIGRAVITY_CLIENT_ID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
7072
7146
  var ANTIGRAVITY_CLIENT_SECRET = "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";
@@ -8656,12 +8730,13 @@ ${body.trim()}
8656
8730
  $ARGUMENTS
8657
8731
  </user-request>`;
8658
8732
  const formattedDescription = `(${scope}) ${data.description || ""}`;
8733
+ const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
8659
8734
  const definition = {
8660
8735
  name: commandName,
8661
8736
  description: formattedDescription,
8662
8737
  template: wrappedTemplate,
8663
8738
  agent: data.agent,
8664
- model: sanitizeModelField(data.model),
8739
+ model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
8665
8740
  subtask: data.subtask,
8666
8741
  argumentHint: data["argument-hint"]
8667
8742
  };
@@ -23347,9 +23422,27 @@ var ast_grep_replace = tool({
23347
23422
  var {spawn: spawn7 } = globalThis.Bun;
23348
23423
 
23349
23424
  // src/tools/grep/constants.ts
23350
- import { existsSync as existsSync29 } from "fs";
23351
- import { join as join33, dirname as dirname6 } from "path";
23425
+ import { existsSync as existsSync30 } from "fs";
23426
+ import { join as join34, dirname as dirname6 } from "path";
23352
23427
  import { spawnSync } from "child_process";
23428
+
23429
+ // src/tools/grep/downloader.ts
23430
+ import { existsSync as existsSync29, mkdirSync as mkdirSync10, chmodSync as chmodSync3, unlinkSync as unlinkSync10, readdirSync as readdirSync7 } from "fs";
23431
+ import { join as join33 } from "path";
23432
+ function getInstallDir() {
23433
+ const homeDir = process.env.HOME || process.env.USERPROFILE || ".";
23434
+ return join33(homeDir, ".cache", "oh-my-opencode", "bin");
23435
+ }
23436
+ function getRgPath() {
23437
+ const isWindows = process.platform === "win32";
23438
+ return join33(getInstallDir(), isWindows ? "rg.exe" : "rg");
23439
+ }
23440
+ function getInstalledRipgrepPath() {
23441
+ const rgPath = getRgPath();
23442
+ return existsSync29(rgPath) ? rgPath : null;
23443
+ }
23444
+
23445
+ // src/tools/grep/constants.ts
23353
23446
  var cachedCli = null;
23354
23447
  function findExecutable(name) {
23355
23448
  const isWindows = process.platform === "win32";
@@ -23369,13 +23462,13 @@ function getOpenCodeBundledRg() {
23369
23462
  const isWindows = process.platform === "win32";
23370
23463
  const rgName = isWindows ? "rg.exe" : "rg";
23371
23464
  const candidates = [
23372
- join33(execDir, rgName),
23373
- join33(execDir, "bin", rgName),
23374
- join33(execDir, "..", "bin", rgName),
23375
- join33(execDir, "..", "libexec", rgName)
23465
+ join34(execDir, rgName),
23466
+ join34(execDir, "bin", rgName),
23467
+ join34(execDir, "..", "bin", rgName),
23468
+ join34(execDir, "..", "libexec", rgName)
23376
23469
  ];
23377
23470
  for (const candidate of candidates) {
23378
- if (existsSync29(candidate)) {
23471
+ if (existsSync30(candidate)) {
23379
23472
  return candidate;
23380
23473
  }
23381
23474
  }
@@ -23394,6 +23487,11 @@ function resolveGrepCli() {
23394
23487
  cachedCli = { path: systemRg, backend: "rg" };
23395
23488
  return cachedCli;
23396
23489
  }
23490
+ const installedRg = getInstalledRipgrepPath();
23491
+ if (installedRg) {
23492
+ cachedCli = { path: installedRg, backend: "rg" };
23493
+ return cachedCli;
23494
+ }
23397
23495
  const grep = findExecutable("grep");
23398
23496
  if (grep) {
23399
23497
  cachedCli = { path: grep, backend: "grep" };
@@ -23773,28 +23871,29 @@ var glob = tool({
23773
23871
  }
23774
23872
  });
23775
23873
  // src/tools/slashcommand/tools.ts
23776
- import { existsSync as existsSync30, readdirSync as readdirSync7, readFileSync as readFileSync18 } from "fs";
23874
+ import { existsSync as existsSync31, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "fs";
23777
23875
  import { homedir as homedir15 } from "os";
23778
- import { join as join34, basename as basename3, dirname as dirname7 } from "path";
23876
+ import { join as join35, basename as basename3, dirname as dirname7 } from "path";
23779
23877
  function discoverCommandsFromDir(commandsDir, scope) {
23780
- if (!existsSync30(commandsDir)) {
23878
+ if (!existsSync31(commandsDir)) {
23781
23879
  return [];
23782
23880
  }
23783
- const entries = readdirSync7(commandsDir, { withFileTypes: true });
23881
+ const entries = readdirSync8(commandsDir, { withFileTypes: true });
23784
23882
  const commands = [];
23785
23883
  for (const entry of entries) {
23786
23884
  if (!isMarkdownFile(entry))
23787
23885
  continue;
23788
- const commandPath = join34(commandsDir, entry.name);
23886
+ const commandPath = join35(commandsDir, entry.name);
23789
23887
  const commandName = basename3(entry.name, ".md");
23790
23888
  try {
23791
23889
  const content = readFileSync18(commandPath, "utf-8");
23792
23890
  const { data, body } = parseFrontmatter(content);
23891
+ const isOpencodeSource = scope === "opencode" || scope === "opencode-project";
23793
23892
  const metadata = {
23794
23893
  name: commandName,
23795
23894
  description: data.description || "",
23796
23895
  argumentHint: data["argument-hint"],
23797
- model: sanitizeModelField(data.model),
23896
+ model: sanitizeModelField(data.model, isOpencodeSource ? "opencode" : "claude-code"),
23798
23897
  agent: data.agent,
23799
23898
  subtask: Boolean(data.subtask)
23800
23899
  };
@@ -23812,10 +23911,10 @@ function discoverCommandsFromDir(commandsDir, scope) {
23812
23911
  return commands;
23813
23912
  }
23814
23913
  function discoverCommandsSync() {
23815
- const userCommandsDir = join34(homedir15(), ".claude", "commands");
23816
- const projectCommandsDir = join34(process.cwd(), ".claude", "commands");
23817
- const opencodeGlobalDir = join34(homedir15(), ".config", "opencode", "command");
23818
- const opencodeProjectDir = join34(process.cwd(), ".opencode", "command");
23914
+ const userCommandsDir = join35(homedir15(), ".claude", "commands");
23915
+ const projectCommandsDir = join35(process.cwd(), ".claude", "commands");
23916
+ const opencodeGlobalDir = join35(homedir15(), ".config", "opencode", "command");
23917
+ const opencodeProjectDir = join35(process.cwd(), ".opencode", "command");
23819
23918
  const userCommands = discoverCommandsFromDir(userCommandsDir, "user");
23820
23919
  const opencodeGlobalCommands = discoverCommandsFromDir(opencodeGlobalDir, "opencode");
23821
23920
  const projectCommands = discoverCommandsFromDir(projectCommandsDir, "project");
@@ -23947,9 +24046,9 @@ var SkillFrontmatterSchema = exports_external.object({
23947
24046
  metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
23948
24047
  });
23949
24048
  // src/tools/skill/tools.ts
23950
- import { existsSync as existsSync31, readdirSync as readdirSync8, readFileSync as readFileSync19 } from "fs";
24049
+ import { existsSync as existsSync32, readdirSync as readdirSync9, readFileSync as readFileSync19 } from "fs";
23951
24050
  import { homedir as homedir16 } from "os";
23952
- import { join as join35, basename as basename4 } from "path";
24051
+ import { join as join36, basename as basename4 } from "path";
23953
24052
  function parseSkillFrontmatter(data) {
23954
24053
  return {
23955
24054
  name: typeof data.name === "string" ? data.name : "",
@@ -23960,19 +24059,19 @@ function parseSkillFrontmatter(data) {
23960
24059
  };
23961
24060
  }
23962
24061
  function discoverSkillsFromDir(skillsDir, scope) {
23963
- if (!existsSync31(skillsDir)) {
24062
+ if (!existsSync32(skillsDir)) {
23964
24063
  return [];
23965
24064
  }
23966
- const entries = readdirSync8(skillsDir, { withFileTypes: true });
24065
+ const entries = readdirSync9(skillsDir, { withFileTypes: true });
23967
24066
  const skills = [];
23968
24067
  for (const entry of entries) {
23969
24068
  if (entry.name.startsWith("."))
23970
24069
  continue;
23971
- const skillPath = join35(skillsDir, entry.name);
24070
+ const skillPath = join36(skillsDir, entry.name);
23972
24071
  if (entry.isDirectory() || entry.isSymbolicLink()) {
23973
24072
  const resolvedPath = resolveSymlink(skillPath);
23974
- const skillMdPath = join35(resolvedPath, "SKILL.md");
23975
- if (!existsSync31(skillMdPath))
24073
+ const skillMdPath = join36(resolvedPath, "SKILL.md");
24074
+ if (!existsSync32(skillMdPath))
23976
24075
  continue;
23977
24076
  try {
23978
24077
  const content = readFileSync19(skillMdPath, "utf-8");
@@ -23990,8 +24089,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
23990
24089
  return skills;
23991
24090
  }
23992
24091
  function discoverSkillsSync() {
23993
- const userSkillsDir = join35(homedir16(), ".claude", "skills");
23994
- const projectSkillsDir = join35(process.cwd(), ".claude", "skills");
24092
+ const userSkillsDir = join36(homedir16(), ".claude", "skills");
24093
+ const projectSkillsDir = join36(process.cwd(), ".claude", "skills");
23995
24094
  const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
23996
24095
  const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
23997
24096
  return [...projectSkills, ...userSkills];
@@ -24001,8 +24100,8 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
24001
24100
  `);
24002
24101
  async function parseSkillMd(skillPath) {
24003
24102
  const resolvedPath = resolveSymlink(skillPath);
24004
- const skillMdPath = join35(resolvedPath, "SKILL.md");
24005
- if (!existsSync31(skillMdPath)) {
24103
+ const skillMdPath = join36(resolvedPath, "SKILL.md");
24104
+ if (!existsSync32(skillMdPath)) {
24006
24105
  return null;
24007
24106
  }
24008
24107
  try {
@@ -24017,12 +24116,12 @@ async function parseSkillMd(skillPath) {
24017
24116
  allowedTools: frontmatter2["allowed-tools"],
24018
24117
  metadata: frontmatter2.metadata
24019
24118
  };
24020
- const referencesDir = join35(resolvedPath, "references");
24021
- const scriptsDir = join35(resolvedPath, "scripts");
24022
- const assetsDir = join35(resolvedPath, "assets");
24023
- const references = existsSync31(referencesDir) ? readdirSync8(referencesDir).filter((f) => !f.startsWith(".")) : [];
24024
- const scripts = existsSync31(scriptsDir) ? readdirSync8(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
24025
- const assets = existsSync31(assetsDir) ? readdirSync8(assetsDir).filter((f) => !f.startsWith(".")) : [];
24119
+ const referencesDir = join36(resolvedPath, "references");
24120
+ const scriptsDir = join36(resolvedPath, "scripts");
24121
+ const assetsDir = join36(resolvedPath, "assets");
24122
+ const references = existsSync32(referencesDir) ? readdirSync9(referencesDir).filter((f) => !f.startsWith(".")) : [];
24123
+ const scripts = existsSync32(scriptsDir) ? readdirSync9(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
24124
+ const assets = existsSync32(assetsDir) ? readdirSync9(assetsDir).filter((f) => !f.startsWith(".")) : [];
24026
24125
  return {
24027
24126
  name: metadata.name,
24028
24127
  path: resolvedPath,
@@ -24038,15 +24137,15 @@ async function parseSkillMd(skillPath) {
24038
24137
  }
24039
24138
  }
24040
24139
  async function discoverSkillsFromDirAsync(skillsDir) {
24041
- if (!existsSync31(skillsDir)) {
24140
+ if (!existsSync32(skillsDir)) {
24042
24141
  return [];
24043
24142
  }
24044
- const entries = readdirSync8(skillsDir, { withFileTypes: true });
24143
+ const entries = readdirSync9(skillsDir, { withFileTypes: true });
24045
24144
  const skills = [];
24046
24145
  for (const entry of entries) {
24047
24146
  if (entry.name.startsWith("."))
24048
24147
  continue;
24049
- const skillPath = join35(skillsDir, entry.name);
24148
+ const skillPath = join36(skillsDir, entry.name);
24050
24149
  if (entry.isDirectory() || entry.isSymbolicLink()) {
24051
24150
  const skillInfo = await parseSkillMd(skillPath);
24052
24151
  if (skillInfo) {
@@ -24057,8 +24156,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
24057
24156
  return skills;
24058
24157
  }
24059
24158
  async function discoverSkills() {
24060
- const userSkillsDir = join35(homedir16(), ".claude", "skills");
24061
- const projectSkillsDir = join35(process.cwd(), ".claude", "skills");
24159
+ const userSkillsDir = join36(homedir16(), ".claude", "skills");
24160
+ const projectSkillsDir = join36(process.cwd(), ".claude", "skills");
24062
24161
  const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
24063
24162
  const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
24064
24163
  return [...projectSkills, ...userSkills];
@@ -24087,7 +24186,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
24087
24186
  const referencesLoaded = [];
24088
24187
  if (includeRefs && skill.references.length > 0) {
24089
24188
  for (const ref of skill.references) {
24090
- const refPath = join35(skill.path, "references", ref);
24189
+ const refPath = join36(skill.path, "references", ref);
24091
24190
  try {
24092
24191
  let content = readFileSync19(refPath, "utf-8");
24093
24192
  content = await resolveCommandsInText(content);
@@ -24392,15 +24491,15 @@ function createBackgroundOutput(manager, client2) {
24392
24491
  }
24393
24492
  const shouldBlock = args.block === true;
24394
24493
  const timeoutMs = Math.min(args.timeout ?? 60000, 600000);
24395
- if (!shouldBlock) {
24396
- return formatTaskStatus(task);
24397
- }
24398
24494
  if (task.status === "completed") {
24399
24495
  return await formatTaskResult(task, client2);
24400
24496
  }
24401
24497
  if (task.status === "error" || task.status === "cancelled") {
24402
24498
  return formatTaskStatus(task);
24403
24499
  }
24500
+ if (!shouldBlock) {
24501
+ return formatTaskStatus(task);
24502
+ }
24404
24503
  const startTime = Date.now();
24405
24504
  while (Date.now() - startTime < timeoutMs) {
24406
24505
  await delay(1000);
@@ -24531,9 +24630,10 @@ Description: ${task.description}
24531
24630
  Agent: ${task.agent} (subagent)
24532
24631
  Status: ${task.status}
24533
24632
 
24534
- Use \`background_output\` tool with task_id="${task.id}" to check progress or retrieve results.
24535
- - block=false: Check status without waiting
24536
- - block=true (default): Wait for completion and get result`;
24633
+ The system will notify you when the task completes.
24634
+ Use \`background_output\` tool with task_id="${task.id}" to check progress:
24635
+ - block=false (default): Check status immediately - returns full status info
24636
+ - block=true: Wait for completion (rarely needed since system notifies)`;
24537
24637
  } catch (error45) {
24538
24638
  const message = error45 instanceof Error ? error45.message : String(error45);
24539
24639
  return `Failed to launch background agent task: ${message}`;
@@ -24575,7 +24675,8 @@ async function executeSync(args, toolContext, ctx) {
24575
24675
  agent: args.subagent_type,
24576
24676
  tools: {
24577
24677
  task: false,
24578
- call_omo_agent: false
24678
+ call_omo_agent: false,
24679
+ background_task: false
24579
24680
  },
24580
24681
  parts: [{ type: "text", text: args.prompt }]
24581
24682
  }
@@ -24795,10 +24896,9 @@ class BackgroundManager {
24795
24896
  body: {
24796
24897
  agent: input.agent,
24797
24898
  tools: {
24798
- background_task: false,
24799
- background_output: false,
24800
- background_cancel: false,
24801
- call_omo_agent: false
24899
+ task: false,
24900
+ call_omo_agent: false,
24901
+ background_task: false
24802
24902
  },
24803
24903
  parts: [{ type: "text", text: input.prompt }]
24804
24904
  }
@@ -25117,6 +25217,7 @@ var HookNameSchema = exports_external.enum([
25117
25217
  "session-notification",
25118
25218
  "comment-checker",
25119
25219
  "grep-output-truncator",
25220
+ "tool-output-truncator",
25120
25221
  "directory-agents-injector",
25121
25222
  "directory-readme-injector",
25122
25223
  "empty-task-response-detector",
@@ -25125,7 +25226,7 @@ var HookNameSchema = exports_external.enum([
25125
25226
  "rules-injector",
25126
25227
  "background-notification",
25127
25228
  "auto-update-checker",
25128
- "ultrawork-mode",
25229
+ "keyword-detector",
25129
25230
  "agent-usage-reminder"
25130
25231
  ]);
25131
25232
  var AgentOverrideConfigSchema = exports_external.object({
@@ -25249,7 +25350,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25249
25350
  sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
25250
25351
  }
25251
25352
  const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
25252
- const grepOutputTruncator = isHookEnabled("grep-output-truncator") ? createGrepOutputTruncatorHook(ctx) : null;
25353
+ const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx) : null;
25253
25354
  const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
25254
25355
  const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
25255
25356
  const emptyTaskResponseDetector = isHookEnabled("empty-task-response-detector") ? createEmptyTaskResponseDetectorHook(ctx) : null;
@@ -25260,7 +25361,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25260
25361
  const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx) : null;
25261
25362
  const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
25262
25363
  const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx) : null;
25263
- const ultraworkMode = isHookEnabled("ultrawork-mode") ? createUltraworkModeHook() : null;
25364
+ const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook() : null;
25264
25365
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
25265
25366
  updateTerminalTitle({ sessionId: "main" });
25266
25367
  const backgroundManager = new BackgroundManager(ctx);
@@ -25279,7 +25380,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25279
25380
  },
25280
25381
  "chat.message": async (input, output) => {
25281
25382
  await claudeCodeHooks["chat.message"]?.(input, output);
25282
- await ultraworkMode?.["chat.message"]?.(input, output);
25383
+ await keywordDetector?.["chat.message"]?.(input, output);
25283
25384
  },
25284
25385
  config: async (config3) => {
25285
25386
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents);
@@ -25359,7 +25460,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25359
25460
  await rulesInjector?.event(input);
25360
25461
  await thinkMode?.event(input);
25361
25462
  await anthropicAutoCompact?.event(input);
25362
- await ultraworkMode?.event(input);
25463
+ await keywordDetector?.event(input);
25363
25464
  await agentUsageReminder?.event(input);
25364
25465
  const { event } = input;
25365
25466
  const props = event.properties;
@@ -25454,7 +25555,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25454
25555
  },
25455
25556
  "tool.execute.after": async (input, output) => {
25456
25557
  await claudeCodeHooks["tool.execute.after"](input, output);
25457
- await grepOutputTruncator?.["tool.execute.after"](input, output);
25558
+ await toolOutputTruncator?.["tool.execute.after"](input, output);
25458
25559
  await contextWindowMonitor?.["tool.execute.after"](input, output);
25459
25560
  await commentChecker?.["tool.execute.after"](input, output);
25460
25561
  await directoryAgentsInjector?.["tool.execute.after"](input, output);
@@ -0,0 +1,27 @@
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ export interface TruncationResult {
3
+ result: string;
4
+ truncated: boolean;
5
+ removedCount?: number;
6
+ }
7
+ export interface TruncationOptions {
8
+ targetMaxTokens?: number;
9
+ preserveHeaderLines?: number;
10
+ contextWindowLimit?: number;
11
+ }
12
+ export declare function truncateToTokenLimit(output: string, maxTokens: number, preserveHeaderLines?: number): TruncationResult;
13
+ export declare function getContextWindowUsage(ctx: PluginInput, sessionID: string): Promise<{
14
+ usedTokens: number;
15
+ remainingTokens: number;
16
+ usagePercentage: number;
17
+ } | null>;
18
+ export declare function dynamicTruncate(ctx: PluginInput, sessionID: string, output: string, options?: TruncationOptions): Promise<TruncationResult>;
19
+ export declare function createDynamicTruncator(ctx: PluginInput): {
20
+ truncate: (sessionID: string, output: string, options?: TruncationOptions) => Promise<TruncationResult>;
21
+ getUsage: (sessionID: string) => Promise<{
22
+ usedTokens: number;
23
+ remainingTokens: number;
24
+ usagePercentage: number;
25
+ } | null>;
26
+ truncateSync: (output: string, maxTokens: number, preserveHeaderLines?: number) => TruncationResult;
27
+ };
@@ -9,3 +9,4 @@ export * from "./pattern-matcher";
9
9
  export * from "./hook-disabled";
10
10
  export * from "./deep-merge";
11
11
  export * from "./file-utils";
12
+ export * from "./dynamic-truncator";
@@ -1,11 +1,3 @@
1
- /**
2
- * Sanitizes model field from frontmatter.
3
- * Always returns undefined to let SDK use default model.
4
- *
5
- * Claude Code and OpenCode use different model ID formats,
6
- * so we ignore the model field and let OpenCode use its configured default.
7
- *
8
- * @param _model - Raw model value from frontmatter (ignored)
9
- * @returns Always undefined to inherit default model
10
- */
11
- export declare function sanitizeModelField(_model: unknown): undefined;
1
+ type CommandSource = "claude-code" | "opencode";
2
+ export declare function sanitizeModelField(model: unknown, source?: CommandSource): string | undefined;
3
+ export {};
@@ -4,6 +4,7 @@ interface ResolvedCli {
4
4
  backend: GrepBackend;
5
5
  }
6
6
  export declare function resolveGrepCli(): ResolvedCli;
7
+ export declare function resolveGrepCliWithAutoInstall(): Promise<ResolvedCli>;
7
8
  export declare const DEFAULT_MAX_DEPTH = 20;
8
9
  export declare const DEFAULT_MAX_FILESIZE = "10M";
9
10
  export declare const DEFAULT_MAX_COUNT = 500;
@@ -0,0 +1,3 @@
1
+ export declare function findFileRecursive(dir: string, filename: string): string | null;
2
+ export declare function downloadAndInstallRipgrep(): Promise<string>;
3
+ export declare function getInstalledRipgrepPath(): string | null;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
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",
@@ -49,8 +49,8 @@
49
49
  "@ast-grep/cli": "^0.40.0",
50
50
  "@ast-grep/napi": "^0.40.0",
51
51
  "@code-yeongyu/comment-checker": "^0.5.0",
52
- "@opencode-ai/plugin": "^1.0.150",
53
52
  "@openauthjs/openauth": "^0.4.3",
53
+ "@opencode-ai/plugin": "^1.0.150",
54
54
  "hono": "^4.10.4",
55
55
  "picomatch": "^4.0.2",
56
56
  "xdg-basedir": "^5.1.0",
@@ -1,15 +0,0 @@
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";
@@ -1,16 +0,0 @@
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;
@@ -1,20 +0,0 @@
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
- }