oh-my-opencode 1.0.0 → 1.0.1

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
@@ -332,6 +332,7 @@ OpenCode 는 아주 확장가능하고 아주 커스터마이저블합니다.
332
332
  - Use camelCase for function names
333
333
  ```
334
334
  - **Think Mode**: 확장된 사고(Extended Thinking)가 필요한 상황을 자동으로 감지하고 모드를 전환합니다. 사용자가 깊은 사고를 요청하는 표현(예: "think deeply", "ultrathink")을 감지하면, 추론 능력을 극대화하도록 모델 설정을 동적으로 조정합니다.
335
+ - **Ultrawork Mode**: 사용자가 "ultrawork" 또는 "ulw" 키워드를 입력하면 자동으로 에이전트 오케스트레이션 가이드를 주입합니다. 메인 에이전트가 모든 가용한 전문 에이전트(탐색, 사서, 계획, UI)를 백그라운드 작업을 통해 병렬로 최대한 활용하도록 강제하며, 엄격한 TODO 추적 및 검증 프로토콜을 따르게 합니다.
335
336
  - **Anthropic Auto Compact**: Anthropic 모델 사용 시 컨텍스트 한계에 도달하면 대화 기록을 자동으로 압축하여 효율적으로 관리합니다.
336
337
  - **Empty Task Response Detector**: 서브 에이전트가 수행한 작업이 비어있거나 무의미한 응답을 반환하는 경우를 감지하여, 오류 없이 우아하게 처리합니다.
337
338
  - **Grep Output Truncator**: Grep 검색 결과가 너무 길어 컨텍스트를 장악해버리는 것을 방지하기 위해, 과도한 출력을 자동으로 자릅니다.
@@ -344,7 +345,7 @@ OpenCode 는 아주 확장가능하고 아주 커스터마이저블합니다.
344
345
  }
345
346
  ```
346
347
 
347
- 사용 가능한 훅: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-auto-compact`, `rules-injector`, `background-notification`, `auto-update-checker`
348
+ 사용 가능한 훅: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `ultrawork-mode`, `anthropic-auto-compact`, `rules-injector`, `background-notification`, `auto-update-checker`
348
349
 
349
350
  > **참고**: `disabled_hooks`는 Oh My OpenCode의 내장 훅을 제어합니다. Claude Code의 `settings.json` 훅을 비활성화하려면 `claude_code.hooks: false`를 대신 사용하세요 ([호환성 토글](#호환성-토글) 참고).
350
351
 
package/README.md CHANGED
@@ -330,6 +330,7 @@ Example workflow:
330
330
  - Use camelCase for function names
331
331
  ```
332
332
  - **Think Mode**: Automatic extended thinking detection and mode switching. Detects when user requests deep thinking (e.g., "think deeply", "ultrathink") and dynamically adjusts model settings for enhanced reasoning.
333
+ - **Ultrawork Mode**: When user triggers "ultrawork" or "ulw" keywords, automatically injects agent orchestration guidance. Forces the main agent to leverage all available specialized agents (exploration, librarian, planning, UI) via background tasks in parallel, with strict TODO tracking and verification protocols.
333
334
  - **Anthropic Auto Compact**: Automatically compacts conversation history when approaching context limits for Anthropic models.
334
335
  - **Empty Task Response Detector**: Detects when subagent tasks return empty or meaningless responses and handles gracefully.
335
336
  - **Grep Output Truncator**: Prevents grep output from overwhelming the context by truncating excessively long results.
@@ -342,7 +343,7 @@ You can disable specific built-in hooks using `disabled_hooks` in `~/.config/ope
342
343
  }
343
344
  ```
344
345
 
345
- Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `anthropic-auto-compact`, `rules-injector`, `background-notification`, `auto-update-checker`
346
+ Available hooks: `todo-continuation-enforcer`, `context-window-monitor`, `session-recovery`, `session-notification`, `comment-checker`, `grep-output-truncator`, `directory-agents-injector`, `directory-readme-injector`, `empty-task-response-detector`, `think-mode`, `ultrawork-mode`, `anthropic-auto-compact`, `rules-injector`, `background-notification`, `auto-update-checker`
346
347
 
347
348
  > **Note**: `disabled_hooks` controls Oh My OpenCode's built-in hooks. To disable Claude Code's `settings.json` hooks, use `claude_code.hooks: false` instead (see [Compatibility Toggles](#compatibility-toggles)).
348
349
 
@@ -1,6 +1,7 @@
1
1
  export interface ThinkModeState {
2
2
  requested: boolean;
3
3
  modelSwitched: boolean;
4
+ thinkingConfigInjected: boolean;
4
5
  providerID?: string;
5
6
  modelID?: string;
6
7
  }
@@ -4,13 +4,13 @@ export * from "./types";
4
4
  export declare function clearUltraworkModeState(sessionID: string): void;
5
5
  export declare function createUltraworkModeHook(): {
6
6
  /**
7
- * chat.message hook - detect ultrawork/ulw keywords, inject context
7
+ * chat.message hook - detect ultrawork/ulw keywords, inject context via history
8
8
  *
9
9
  * Execution timing: AFTER claudeCodeHooks["chat.message"]
10
10
  * Behavior:
11
11
  * 1. Extract text from user prompt
12
12
  * 2. Detect ultrawork/ulw keywords (excluding code blocks)
13
- * 3. If detected, prepend ULTRAWORK_CONTEXT to first text part
13
+ * 3. If detected, inject ULTRAWORK_CONTEXT via injectHookMessage (history injection)
14
14
  */
15
15
  "chat.message": (input: {
16
16
  sessionID: string;
package/dist/index.js CHANGED
@@ -4817,6 +4817,46 @@ var ALREADY_HIGH = new Set([
4817
4817
  "gpt-5.2-chat-latest-high",
4818
4818
  "gpt-5.2-pro-high"
4819
4819
  ]);
4820
+ var THINKING_CONFIGS = {
4821
+ anthropic: {
4822
+ thinking: {
4823
+ type: "enabled",
4824
+ budgetTokens: 64000
4825
+ },
4826
+ maxTokens: 128000
4827
+ },
4828
+ "amazon-bedrock": {
4829
+ reasoningConfig: {
4830
+ type: "enabled",
4831
+ budgetTokens: 32000
4832
+ },
4833
+ maxTokens: 64000
4834
+ },
4835
+ google: {
4836
+ providerOptions: {
4837
+ google: {
4838
+ thinkingConfig: {
4839
+ thinkingLevel: "HIGH"
4840
+ }
4841
+ }
4842
+ }
4843
+ },
4844
+ "google-vertex": {
4845
+ providerOptions: {
4846
+ "google-vertex": {
4847
+ thinkingConfig: {
4848
+ thinkingLevel: "HIGH"
4849
+ }
4850
+ }
4851
+ }
4852
+ }
4853
+ };
4854
+ var THINKING_CAPABLE_MODELS = {
4855
+ anthropic: ["claude-sonnet-4", "claude-opus-4", "claude-3"],
4856
+ "amazon-bedrock": ["claude", "anthropic"],
4857
+ google: ["gemini-2", "gemini-3"],
4858
+ "google-vertex": ["gemini-2", "gemini-3"]
4859
+ };
4820
4860
  function getHighVariant(modelID) {
4821
4861
  if (ALREADY_HIGH.has(modelID)) {
4822
4862
  return null;
@@ -4826,6 +4866,19 @@ function getHighVariant(modelID) {
4826
4866
  function isAlreadyHighVariant(modelID) {
4827
4867
  return ALREADY_HIGH.has(modelID) || modelID.endsWith("-high");
4828
4868
  }
4869
+ function getThinkingConfig(providerID, modelID) {
4870
+ if (isAlreadyHighVariant(modelID)) {
4871
+ return null;
4872
+ }
4873
+ const config = THINKING_CONFIGS[providerID];
4874
+ const capablePatterns = THINKING_CAPABLE_MODELS[providerID];
4875
+ if (!config || !capablePatterns) {
4876
+ return null;
4877
+ }
4878
+ const modelLower = modelID.toLowerCase();
4879
+ const isCapable = capablePatterns.some((pattern) => modelLower.includes(pattern.toLowerCase()));
4880
+ return isCapable ? config : null;
4881
+ }
4829
4882
 
4830
4883
  // src/hooks/think-mode/index.ts
4831
4884
  var thinkModeState = new Map;
@@ -4835,7 +4888,8 @@ function createThinkModeHook() {
4835
4888
  const promptText = extractPromptText(output.parts);
4836
4889
  const state = {
4837
4890
  requested: false,
4838
- modelSwitched: false
4891
+ modelSwitched: false,
4892
+ thinkingConfigInjected: false
4839
4893
  };
4840
4894
  if (!detectThinkKeyword(promptText)) {
4841
4895
  thinkModeState.set(sessionID, state);
@@ -4854,15 +4908,28 @@ function createThinkModeHook() {
4854
4908
  return;
4855
4909
  }
4856
4910
  const highVariant = getHighVariant(currentModel.modelID);
4857
- if (!highVariant) {
4858
- thinkModeState.set(sessionID, state);
4859
- return;
4911
+ const thinkingConfig = getThinkingConfig(currentModel.providerID, currentModel.modelID);
4912
+ if (highVariant) {
4913
+ output.message.model = {
4914
+ providerID: currentModel.providerID,
4915
+ modelID: highVariant
4916
+ };
4917
+ state.modelSwitched = true;
4918
+ log("Think mode: model switched to high variant", {
4919
+ sessionID,
4920
+ from: currentModel.modelID,
4921
+ to: highVariant
4922
+ });
4923
+ }
4924
+ if (thinkingConfig) {
4925
+ Object.assign(output.message, thinkingConfig);
4926
+ state.thinkingConfigInjected = true;
4927
+ log("Think mode: thinking config injected", {
4928
+ sessionID,
4929
+ provider: currentModel.providerID,
4930
+ config: thinkingConfig
4931
+ });
4860
4932
  }
4861
- output.message.model = {
4862
- providerID: currentModel.providerID,
4863
- modelID: highVariant
4864
- };
4865
- state.modelSwitched = true;
4866
4933
  thinkModeState.set(sessionID, state);
4867
4934
  },
4868
4935
  event: async ({ event }) => {
@@ -6586,12 +6653,18 @@ function createUltraworkModeHook() {
6586
6653
  }
6587
6654
  state.detected = true;
6588
6655
  log("Ultrawork keyword detected", { sessionID: input.sessionID });
6589
- const parts = output.parts;
6590
- const idx = parts.findIndex((p) => p.type === "text" && p.text);
6591
- if (idx >= 0) {
6592
- parts[idx].text = `${ULTRAWORK_CONTEXT}${parts[idx].text ?? ""}`;
6656
+ const message = output.message;
6657
+ const success = injectHookMessage(input.sessionID, ULTRAWORK_CONTEXT, {
6658
+ agent: message.agent,
6659
+ model: message.model,
6660
+ path: message.path,
6661
+ tools: message.tools
6662
+ });
6663
+ if (success) {
6593
6664
  state.injected = true;
6594
- log("Ultrawork context injected", { sessionID: input.sessionID });
6665
+ log("Ultrawork context injected via history", { sessionID: input.sessionID });
6666
+ } else {
6667
+ log("Ultrawork context injection failed", { sessionID: input.sessionID });
6595
6668
  }
6596
6669
  ultraworkModeState.set(input.sessionID, state);
6597
6670
  },
@@ -8190,7 +8263,7 @@ function loadOpencodeProjectCommands() {
8190
8263
  return commandsToRecord(commands);
8191
8264
  }
8192
8265
  // src/features/claude-code-skill-loader/loader.ts
8193
- import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync3, readlinkSync } from "fs";
8266
+ import { existsSync as existsSync20, readdirSync as readdirSync5, readFileSync as readFileSync12, lstatSync, readlinkSync } from "fs";
8194
8267
  import { homedir as homedir10 } from "os";
8195
8268
  import { join as join25, resolve as resolve4 } from "path";
8196
8269
  function loadSkillsFromDir(skillsDir, scope) {
@@ -8206,8 +8279,12 @@ function loadSkillsFromDir(skillsDir, scope) {
8206
8279
  if (!entry.isDirectory() && !entry.isSymbolicLink())
8207
8280
  continue;
8208
8281
  let resolvedPath = skillPath;
8209
- if (statSync3(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
8210
- resolvedPath = resolve4(skillPath, "..", readlinkSync(skillPath));
8282
+ try {
8283
+ if (lstatSync(skillPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
8284
+ resolvedPath = resolve4(skillPath, "..", readlinkSync(skillPath));
8285
+ }
8286
+ } catch {
8287
+ continue;
8211
8288
  }
8212
8289
  const skillMdPath = join25(resolvedPath, "SKILL.md");
8213
8290
  if (!existsSync20(skillMdPath))
@@ -22308,7 +22385,7 @@ var lsp_code_action_resolve = tool({
22308
22385
  // src/tools/ast-grep/constants.ts
22309
22386
  import { createRequire as createRequire4 } from "module";
22310
22387
  import { dirname as dirname5, join as join30 } from "path";
22311
- import { existsSync as existsSync26, statSync as statSync4 } from "fs";
22388
+ import { existsSync as existsSync26, statSync as statSync3 } from "fs";
22312
22389
 
22313
22390
  // src/tools/ast-grep/downloader.ts
22314
22391
  var {spawn: spawn5 } = globalThis.Bun;
@@ -22422,7 +22499,7 @@ async function ensureAstGrepBinary() {
22422
22499
  // src/tools/ast-grep/constants.ts
22423
22500
  function isValidBinary(filePath) {
22424
22501
  try {
22425
- return statSync4(filePath).size > 1e4;
22502
+ return statSync3(filePath).size > 1e4;
22426
22503
  } catch {
22427
22504
  return false;
22428
22505
  }
@@ -23442,7 +23519,7 @@ var SkillFrontmatterSchema = exports_external.object({
23442
23519
  metadata: exports_external.record(exports_external.string(), exports_external.string()).optional()
23443
23520
  });
23444
23521
  // src/tools/skill/tools.ts
23445
- import { existsSync as existsSync30, readdirSync as readdirSync8, statSync as statSync5, readlinkSync as readlinkSync2, readFileSync as readFileSync18 } from "fs";
23522
+ import { existsSync as existsSync30, readdirSync as readdirSync8, lstatSync as lstatSync2, readlinkSync as readlinkSync2, readFileSync as readFileSync18 } from "fs";
23446
23523
  import { homedir as homedir16 } from "os";
23447
23524
  import { join as join33, resolve as resolve7, basename as basename4 } from "path";
23448
23525
  function parseSkillFrontmatter(data) {
@@ -23467,7 +23544,7 @@ function discoverSkillsFromDir(skillsDir, scope) {
23467
23544
  if (entry.isDirectory() || entry.isSymbolicLink()) {
23468
23545
  let resolvedPath = skillPath;
23469
23546
  try {
23470
- const stats = statSync5(skillPath, { throwIfNoEntry: false });
23547
+ const stats = lstatSync2(skillPath, { throwIfNoEntry: false });
23471
23548
  if (stats?.isSymbolicLink()) {
23472
23549
  resolvedPath = resolve7(skillPath, "..", readlinkSync2(skillPath));
23473
23550
  }
@@ -23504,7 +23581,7 @@ var skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.descr
23504
23581
  `);
23505
23582
  function resolveSymlink(skillPath) {
23506
23583
  try {
23507
- const stats = statSync5(skillPath, { throwIfNoEntry: false });
23584
+ const stats = lstatSync2(skillPath, { throwIfNoEntry: false });
23508
23585
  if (stats?.isSymbolicLink()) {
23509
23586
  return resolve7(skillPath, "..", readlinkSync2(skillPath));
23510
23587
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
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",