oh-my-opencode 1.1.6 → 1.1.8

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 } 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,11 +23871,11 @@ 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 readdirSync7, 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
23881
  const entries = readdirSync7(commandsDir, { withFileTypes: true });
@@ -23785,16 +23883,17 @@ function discoverCommandsFromDir(commandsDir, scope) {
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 readdirSync8, 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,7 +24059,7 @@ 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
24065
  const entries = readdirSync8(skillsDir, { withFileTypes: true });
@@ -23968,11 +24067,11 @@ function discoverSkillsFromDir(skillsDir, scope) {
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) ? readdirSync8(referencesDir).filter((f) => !f.startsWith(".")) : [];
24123
+ const scripts = existsSync32(scriptsDir) ? readdirSync8(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
24124
+ const assets = existsSync32(assetsDir) ? readdirSync8(assetsDir).filter((f) => !f.startsWith(".")) : [];
24026
24125
  return {
24027
24126
  name: metadata.name,
24028
24127
  path: resolvedPath,
@@ -24038,7 +24137,7 @@ 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
24143
  const entries = readdirSync8(skillsDir, { withFileTypes: true });
@@ -24046,7 +24145,7 @@ async function discoverSkillsFromDirAsync(skillsDir) {
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);
@@ -24239,14 +24338,17 @@ function createBackgroundTask(manager) {
24239
24338
  args: {
24240
24339
  description: tool.schema.string().describe("Short task description (shown in status)"),
24241
24340
  prompt: tool.schema.string().describe("Full detailed prompt for the agent"),
24242
- agent: tool.schema.string().describe("Agent type to use (any agent allowed)")
24341
+ agent: tool.schema.string().describe("Agent type to use (any registered agent)")
24243
24342
  },
24244
24343
  async execute(args, toolContext) {
24344
+ if (!args.agent || args.agent.trim() === "") {
24345
+ return `\u274C Agent parameter is required. Please specify which agent to use (e.g., "explore", "librarian", "build", etc.)`;
24346
+ }
24245
24347
  try {
24246
24348
  const task = await manager.launch({
24247
24349
  description: args.description,
24248
24350
  prompt: args.prompt,
24249
- agent: args.agent,
24351
+ agent: args.agent.trim(),
24250
24352
  parentSessionID: toolContext.sessionID,
24251
24353
  parentMessageID: toolContext.messageID
24252
24354
  });
@@ -24389,15 +24491,15 @@ function createBackgroundOutput(manager, client2) {
24389
24491
  }
24390
24492
  const shouldBlock = args.block === true;
24391
24493
  const timeoutMs = Math.min(args.timeout ?? 60000, 600000);
24392
- if (!shouldBlock) {
24393
- return formatTaskStatus(task);
24394
- }
24395
24494
  if (task.status === "completed") {
24396
24495
  return await formatTaskResult(task, client2);
24397
24496
  }
24398
24497
  if (task.status === "error" || task.status === "cancelled") {
24399
24498
  return formatTaskStatus(task);
24400
24499
  }
24500
+ if (!shouldBlock) {
24501
+ return formatTaskStatus(task);
24502
+ }
24401
24503
  const startTime = Date.now();
24402
24504
  while (Date.now() - startTime < timeoutMs) {
24403
24505
  await delay(1000);
@@ -24528,9 +24630,10 @@ Description: ${task.description}
24528
24630
  Agent: ${task.agent} (subagent)
24529
24631
  Status: ${task.status}
24530
24632
 
24531
- Use \`background_output\` tool with task_id="${task.id}" to check progress or retrieve results.
24532
- - block=false: Check status without waiting
24533
- - 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)`;
24534
24637
  } catch (error45) {
24535
24638
  const message = error45 instanceof Error ? error45.message : String(error45);
24536
24639
  return `Failed to launch background agent task: ${message}`;
@@ -24565,17 +24668,37 @@ async function executeSync(args, toolContext, ctx) {
24565
24668
  }
24566
24669
  log(`[call_omo_agent] Sending prompt to session ${sessionID}`);
24567
24670
  log(`[call_omo_agent] Prompt text:`, args.prompt.substring(0, 100));
24568
- await ctx.client.session.prompt({
24569
- path: { id: sessionID },
24570
- body: {
24571
- agent: args.subagent_type,
24572
- tools: {
24573
- task: false,
24574
- call_omo_agent: false
24575
- },
24576
- parts: [{ type: "text", text: args.prompt }]
24671
+ try {
24672
+ await ctx.client.session.prompt({
24673
+ path: { id: sessionID },
24674
+ body: {
24675
+ agent: args.subagent_type,
24676
+ tools: {
24677
+ task: false,
24678
+ call_omo_agent: false,
24679
+ background_task: false,
24680
+ background_output: false,
24681
+ background_cancel: false
24682
+ },
24683
+ parts: [{ type: "text", text: args.prompt }]
24684
+ }
24685
+ });
24686
+ } catch (error45) {
24687
+ const errorMessage = error45 instanceof Error ? error45.message : String(error45);
24688
+ log(`[call_omo_agent] Prompt error:`, errorMessage);
24689
+ if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
24690
+ return `Error: Agent "${args.subagent_type}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.
24691
+
24692
+ <task_metadata>
24693
+ session_id: ${sessionID}
24694
+ </task_metadata>`;
24577
24695
  }
24578
- });
24696
+ return `Error: Failed to send prompt: ${errorMessage}
24697
+
24698
+ <task_metadata>
24699
+ session_id: ${sessionID}
24700
+ </task_metadata>`;
24701
+ }
24579
24702
  log(`[call_omo_agent] Prompt sent, fetching messages...`);
24580
24703
  const messagesResult = await ctx.client.session.messages({
24581
24704
  path: { id: sessionID }
@@ -24739,6 +24862,9 @@ class BackgroundManager {
24739
24862
  this.directory = ctx.directory;
24740
24863
  }
24741
24864
  async launch(input) {
24865
+ if (!input.agent || input.agent.trim() === "") {
24866
+ throw new Error("Agent parameter is required");
24867
+ }
24742
24868
  const createResult = await this.client.session.create({
24743
24869
  body: {
24744
24870
  parentID: input.parentSessionID,
@@ -24766,16 +24892,14 @@ class BackgroundManager {
24766
24892
  };
24767
24893
  this.tasks.set(task.id, task);
24768
24894
  this.startPolling();
24769
- log("[background-agent] Launching task:", { taskId: task.id, sessionID });
24895
+ log("[background-agent] Launching task:", { taskId: task.id, sessionID, agent: input.agent });
24770
24896
  this.client.session.promptAsync({
24771
24897
  path: { id: sessionID },
24772
24898
  body: {
24773
24899
  agent: input.agent,
24774
24900
  tools: {
24775
- background_task: false,
24776
- background_output: false,
24777
- background_cancel: false,
24778
- call_omo_agent: false
24901
+ task: false,
24902
+ background_task: false
24779
24903
  },
24780
24904
  parts: [{ type: "text", text: input.prompt }]
24781
24905
  }
@@ -24784,8 +24908,15 @@ class BackgroundManager {
24784
24908
  const existingTask = this.findBySession(sessionID);
24785
24909
  if (existingTask) {
24786
24910
  existingTask.status = "error";
24787
- existingTask.error = String(error45);
24911
+ const errorMessage = error45 instanceof Error ? error45.message : String(error45);
24912
+ if (errorMessage.includes("agent.name") || errorMessage.includes("undefined")) {
24913
+ existingTask.error = `Agent "${input.agent}" not found. Make sure the agent is registered in your opencode.json or provided by a plugin.`;
24914
+ } else {
24915
+ existingTask.error = errorMessage;
24916
+ }
24788
24917
  existingTask.completedAt = new Date;
24918
+ this.markForNotification(existingTask);
24919
+ this.notifyParentSession(existingTask);
24789
24920
  }
24790
24921
  });
24791
24922
  return task;
@@ -25087,6 +25218,7 @@ var HookNameSchema = exports_external.enum([
25087
25218
  "session-notification",
25088
25219
  "comment-checker",
25089
25220
  "grep-output-truncator",
25221
+ "tool-output-truncator",
25090
25222
  "directory-agents-injector",
25091
25223
  "directory-readme-injector",
25092
25224
  "empty-task-response-detector",
@@ -25095,7 +25227,7 @@ var HookNameSchema = exports_external.enum([
25095
25227
  "rules-injector",
25096
25228
  "background-notification",
25097
25229
  "auto-update-checker",
25098
- "ultrawork-mode",
25230
+ "keyword-detector",
25099
25231
  "agent-usage-reminder"
25100
25232
  ]);
25101
25233
  var AgentOverrideConfigSchema = exports_external.object({
@@ -25219,7 +25351,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25219
25351
  sessionRecovery.setOnRecoveryCompleteCallback(todoContinuationEnforcer.markRecoveryComplete);
25220
25352
  }
25221
25353
  const commentChecker = isHookEnabled("comment-checker") ? createCommentCheckerHooks() : null;
25222
- const grepOutputTruncator = isHookEnabled("grep-output-truncator") ? createGrepOutputTruncatorHook(ctx) : null;
25354
+ const toolOutputTruncator = isHookEnabled("tool-output-truncator") ? createToolOutputTruncatorHook(ctx) : null;
25223
25355
  const directoryAgentsInjector = isHookEnabled("directory-agents-injector") ? createDirectoryAgentsInjectorHook(ctx) : null;
25224
25356
  const directoryReadmeInjector = isHookEnabled("directory-readme-injector") ? createDirectoryReadmeInjectorHook(ctx) : null;
25225
25357
  const emptyTaskResponseDetector = isHookEnabled("empty-task-response-detector") ? createEmptyTaskResponseDetectorHook(ctx) : null;
@@ -25230,7 +25362,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25230
25362
  const anthropicAutoCompact = isHookEnabled("anthropic-auto-compact") ? createAnthropicAutoCompactHook(ctx) : null;
25231
25363
  const rulesInjector = isHookEnabled("rules-injector") ? createRulesInjectorHook(ctx) : null;
25232
25364
  const autoUpdateChecker = isHookEnabled("auto-update-checker") ? createAutoUpdateCheckerHook(ctx) : null;
25233
- const ultraworkMode = isHookEnabled("ultrawork-mode") ? createUltraworkModeHook() : null;
25365
+ const keywordDetector = isHookEnabled("keyword-detector") ? createKeywordDetectorHook() : null;
25234
25366
  const agentUsageReminder = isHookEnabled("agent-usage-reminder") ? createAgentUsageReminderHook(ctx) : null;
25235
25367
  updateTerminalTitle({ sessionId: "main" });
25236
25368
  const backgroundManager = new BackgroundManager(ctx);
@@ -25249,7 +25381,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25249
25381
  },
25250
25382
  "chat.message": async (input, output) => {
25251
25383
  await claudeCodeHooks["chat.message"]?.(input, output);
25252
- await ultraworkMode?.["chat.message"]?.(input, output);
25384
+ await keywordDetector?.["chat.message"]?.(input, output);
25253
25385
  },
25254
25386
  config: async (config3) => {
25255
25387
  const builtinAgents = createBuiltinAgents(pluginConfig.disabled_agents, pluginConfig.agents);
@@ -25329,7 +25461,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25329
25461
  await rulesInjector?.event(input);
25330
25462
  await thinkMode?.event(input);
25331
25463
  await anthropicAutoCompact?.event(input);
25332
- await ultraworkMode?.event(input);
25464
+ await keywordDetector?.event(input);
25333
25465
  await agentUsageReminder?.event(input);
25334
25466
  const { event } = input;
25335
25467
  const props = event.properties;
@@ -25424,7 +25556,7 @@ var OhMyOpenCodePlugin = async (ctx) => {
25424
25556
  },
25425
25557
  "tool.execute.after": async (input, output) => {
25426
25558
  await claudeCodeHooks["tool.execute.after"](input, output);
25427
- await grepOutputTruncator?.["tool.execute.after"](input, output);
25559
+ await toolOutputTruncator?.["tool.execute.after"](input, output);
25428
25560
  await contextWindowMonitor?.["tool.execute.after"](input, output);
25429
25561
  await commentChecker?.["tool.execute.after"](input, output);
25430
25562
  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,2 @@
1
+ export declare function downloadAndInstallRipgrep(): Promise<string>;
2
+ export declare function getInstalledRipgrepPath(): string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
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,9 @@
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
+ "glob": "^13.0.0",
54
55
  "hono": "^4.10.4",
55
56
  "picomatch": "^4.0.2",
56
57
  "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
- }