Compare commits

..

12 Commits

Author SHA1 Message Date
github-actions[bot]
c4ce119e61 release: v2.2.0 2025-12-17 10:26:26 +00:00
YeonGyu-Kim
17b4304a5f Expand Todo Management section with detailed guidelines
🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 19:11:06 +09:00
YeonGyu-Kim
c6595bee3e Add OmO agent model fallback chain to inherit OpenCode system default (#79)
- Add systemDefaultModel parameter to createBuiltinAgents() function
- Implement model fallback priority chain for OmO agent:
  1. oh-my-opencode.json agents.OmO.model (explicit override)
  2. OpenCode system config.model (system default)
  3. Hardcoded default in omoAgent (fallback)
- Pass config.model from OpenCode settings to createBuiltinAgents()

This fixes issue #79 where users couldn't change agent models via OpenCode config.

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 18:27:24 +09:00
YeonGyu-Kim
e144dd54a7 Merge branch 'remove-session-rename-hook' 2025-12-17 09:47:16 +09:00
YeonGyu-Kim
8cdbd1cbc0 refactor: remove terminal title update feature
OpenCode now supports terminal title updates natively (since v1.0.150,
commit 8346550), making this plugin feature redundant. Remove the
entire terminal title feature and clean up associated dead code.

Ref: https://github.com/sst/opencode/commit/8346550

Removed:
- src/features/terminal/ (title.ts, index.ts)
- src/features/claude-code-session-state/detector.ts (dead code)
- src/features/claude-code-session-state/types.ts (dead code)
- Session title tracking (setCurrentSession, getCurrentSessionTitle)
- Terminal title update calls from event handlers

Retained:
- subagentSessions (used by background-agent, session-notification)
- mainSessionID tracking (used by session recovery)

🤖 Generated with [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 16:16:47 +09:00
YeonGyu-Kim
276b1ba865 Merge branch 'fix-omo-plan-agent-permissions' 2025-12-17 09:46:56 +09:00
YeonGyu-Kim
1de27e41e0 Merge branch 'allow-external-read-webfetch-hooks' 2025-12-17 09:46:36 +09:00
YeonGyu-Kim
98ffe3f853 feat: auto-allow webfetch and external_directory permissions
Inject permission config to automatically allow webfetch and
external_directory (external read) tools without user confirmation.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 16:24:31 +09:00
YeonGyu-Kim
0261652fa3 Add concrete oh-my-opencode.json configuration examples to LLM installation guide
- When user lacks Claude Pro/Max: Shows opencode/big-pickle fallback for OmO and librarian
- When user lacks ChatGPT: Shows anthropic/claude-opus-4-5 fallback for oracle
- When Gemini not integrated: Shows anthropic/claude-opus-4-5 fallback for frontend-ui-ux-engineer, document-writer, multimodal-looker

Updates all three README files (English, Korean, Japanese) with improved Step 0 setup guidance.

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 09:46:16 +09:00
YeonGyu-Kim
9cef9d1142 Add opencode-antigravity-auth plugin guide and oh-my-opencode.json model override documentation
- Added opencode-antigravity-auth plugin setup guide to Installation section
- Added oh-my-opencode.json agent model override configuration with google_auth: false
- Added available Antigravity model names reference list
- Updated Configuration > Google Auth section with plugin recommendation
- Documented multi-account load balancing feature
- Applied documentation updates to EN, KO, JA READMEs

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 09:45:23 +09:00
YeonGyu-Kim
67bcd4def4 fix(auth): resolve Google Antigravity OAuth 404 error by using fallback project ID
When project ID fetching failed, an empty string was returned causing 404 errors on API requests. Now uses ANTIGRAVITY_DEFAULT_PROJECT_ID as fallback:

- isFreeTier(): Returns true when tierId is undefined (free tier by default)
- Import ANTIGRAVITY_DEFAULT_PROJECT_ID constant
- Replace empty project ID returns with fallback in all code paths:
  - When loadCodeAssist returns null
  - When PAID tier is detected
  - When non-FREE tier without project
  - When onboard/managed project ID fetch fails

Matches behavior of NoeFabris/opencode-antigravity-auth implementation.

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 09:34:32 +09:00
YeonGyu-Kim
1ecf35ff60 fix(agents): restrict OmO-Plan to read-only tools, inherit from default plan agent (#72)
Remove OmO agent permission spread from omoPlanBase to ensure OmO-Plan:
- Uses read-only tools only (read, glob, grep, etc)
- Focuses on planning and analysis
- Can ask follow-up questions for clarification
- Does not execute code changes

🤖 Generated with assistance of OhMyOpenCode (https://github.com/code-yeongyu/oh-my-opencode)
2025-12-17 00:43:59 +09:00
14 changed files with 335 additions and 253 deletions

View File

@@ -150,15 +150,42 @@ Windows から Linux に初めて乗り換えた時のこと、自分の思い
### ステップ 0: セットアップについてユーザーに尋ねる
OpenCode は様々なモデルをサポートしており、当プラグインは様々な最先端 LLM の使用を提案しています。そのため、ユーザーに以下の質問をする必要があります:
1. Claude Pro/Max のサブスクリプションをお持ちですか?お持ちでない場合、メインオーケストレーターモデルとして 'opencode/big-pickle' (glm 4.6 として知られる) を使用することになります (非推奨)。
-> ユーザーが「はい」と答えた場合、ToDo 追加: "ユーザーの Claude Pro/Max 認証設定"
-> ユーザーが「いいえ」と答えた場合、ToDo 追加: "oh-my-opencode.json の OmO エージェントモデルを opencode/big-pickle にオーバーライド"
2. ChatGPT のサブスクリプションをお持ちですかお持ちでない場合、oracle エージェントに 32k thinking オプション付きの 'claude-opus-4-5' を使用することになります。
-> ユーザーが「はい」と答えた場合、ToDo 追加: "ユーザーの ChatGPT 認証設定"
-> ユーザーが「いいえ」と答えた場合、ToDo 追加: "oh-my-opencode.json の oracle エージェントモデルを 32k thinking 付きの claude-opus-4-5 にオーバーライド"
3. Gemini モデルを統合しますか?統合しない場合、美しいデザインを享受できません。
-> ユーザーが「はい」と答えた場合、ToDo 追加: "Google OAuth 設定"
-> ユーザーが「いいえ」と答えた場合、ToDo 追加: "oh-my-opencode.json の frontend-ui-ux-engineer エージェントモデルを 32k thinking 付きの claude-opus-4-5 にオーバーライド"
1. **Claude Pro/Max のサブスクリプションをお持ちですか?** お持ちでない場合、メインオーケストレーターモデルとして 'opencode/big-pickle' (GLM 4.6 として知られる) を使用することになります (非推奨)。
- ユーザーが**「はい」**と答えた場合、ToDo 追加: "ユーザーの Claude Pro/Max 認証設定"
- ユーザーが**「いいえ」**と答えた場合、`~/.config/opencode/oh-my-opencode.json` に以下を保存:
```json
{
"agents": {
"OmO": { "model": "opencode/big-pickle" },
"librarian": { "model": "opencode/big-pickle" }
}
}
```
2. **ChatGPT のサブスクリプションをお持ちですか?** お持ちでない場合、oracle エージェントに 'anthropic/claude-opus-4-5' を使用することになります。
- ユーザーが**「はい」**と答えた場合、ToDo 追加: "ユーザーの ChatGPT 認証設定"
- ユーザーが**「いいえ」**と答えた場合、`oh-my-opencode.json` に以下をマージ:
```json
{
"agents": {
"oracle": { "model": "anthropic/claude-opus-4-5" }
}
}
```
3. **Gemini モデルを統合しますか?** 統合しない場合、美しいデザインを享受できません。
- ユーザーが**「はい」**と答えた場合、ToDo 追加: "Google OAuth 設定"
- ユーザーが**「いいえ」**と答えた場合、`oh-my-opencode.json` に以下をマージ:
```json
{
"agents": {
"frontend-ui-ux-engineer": { "model": "anthropic/claude-opus-4-5" },
"document-writer": { "model": "anthropic/claude-opus-4-5" },
"multimodal-looker": { "model": "anthropic/claude-opus-4-5" }
}
}
```
### ステップ 1: OpenCode インストール確認
@@ -223,25 +250,52 @@ opencode auth login
#### 4.2 Google Gemini (Antigravity OAuth)
まず、`~/.config/opencode/oh-my-opencode.json` で Google auth を有効にします:
まず、opencode-antigravity-auth プラグインを追加します:
```json
{
"google_auth": true
"plugin": [
"oh-my-opencode",
"opencode-antigravity-auth@1.1.2"
]
}
```
#### 4.2.1 モデル設定
`opencode.json` に完全なモデル設定も構成する必要があります。
[opencode-antigravity-auth ドキュメント](https://github.com/NoeFabris/opencode-antigravity-auth)を読み、README から provider/models 設定をコピーして、ユーザーの既存のセットアップを壊さないよう慎重にマージしてください。
#### 4.2.2 oh-my-opencode エージェントモデルのオーバーライド
`opencode-antigravity-auth` プラグインは内蔵 Google auth と異なるモデル名を使用します。`oh-my-opencode.json`(または `.opencode/oh-my-opencode.json`)でエージェントモデルをオーバーライドし、内蔵 `google_auth` を無効化してください:
```json
{
"google_auth": false,
"agents": {
"frontend-ui-ux-engineer": { "model": "google/gemini-3-pro-high" },
"document-writer": { "model": "google/gemini-3-pro-high" },
"multimodal-looker": { "model": "google/gemini-2.5-flash" }
}
}
```
**利用可能なモデル名**: `google/gemini-3-pro-high`, `google/gemini-3-pro-medium`, `google/gemini-3-pro-low`, `google/gemini-2.5-flash`, `google/gemini-2.5-flash-lite`, `google/claude-sonnet-4-5`, `google/claude-sonnet-4-5-thinking`, `google/claude-opus-4-5-thinking`, `google/gpt-oss-120b-medium`
その後、認証を行います:
```bash
opencode auth login
# Provider: Google を選択
# Login method: OAuth with Google (Antigravity) を選択
# ユーザーにブラウザでの OAuth フロー完了を案内
# 完了まで待機
# ブラウザでサインイン完了(自動検出)
# オプション:マルチアカウントロードバランシング用に追加の Google アカウントを登録
# 成功を確認し、ユーザーに報告
```
**マルチアカウントロードバランシング**: プラグインは最大10個の Google アカウントをサポートします。1つのアカウントがレートリミットに達すると、自動的に次のアカウントに切り替わります。
#### 4.3 OpenAI (ChatGPT Plus/Pro)
まず、opencode-openai-codex-auth プラグインを追加します:
@@ -559,7 +613,22 @@ Oh My OpenCode は以下の場所からフックを読み込んで実行しま
### Google Auth
Google Gemini モデルのための内蔵 Antigravity OAuth を有効化します:
**推奨**: 外部の [`opencode-antigravity-auth`](https://github.com/NoeFabris/opencode-antigravity-auth) プラグインを使用してください。マルチアカウントロードバランシング、より多くのモデルAntigravity 経由の Claude を含む)、活発なメンテナンスを提供します。[インストール > Google Gemini](#42-google-gemini-antigravity-oauth) を参照。
`opencode-antigravity-auth` 使用時は内蔵 auth を無効化し、`oh-my-opencode.json` でエージェントモデルをオーバーライドしてください:
```json
{
"google_auth": false,
"agents": {
"frontend-ui-ux-engineer": { "model": "google/gemini-3-pro-high" },
"document-writer": { "model": "google/gemini-3-pro-high" },
"multimodal-looker": { "model": "google/gemini-2.5-flash" }
}
}
```
**代替案**: 内蔵 Antigravity OAuth を有効化単一アカウント、Gemini モデルのみ):
```json
{
@@ -567,8 +636,6 @@ Google Gemini モデルのための内蔵 Antigravity OAuth を有効化しま
}
```
有効化すると、`opencode auth login` 実行時に Google プロバイダーで "OAuth with Google (Antigravity)" ログインオプションが表示されます。
### Agents
内蔵エージェント設定をオーバーライドできます:

View File

@@ -147,15 +147,42 @@ OpenCode 가 낭만이 사라진것같은 오늘날의 시대에, 당신에게
### 0단계: 사용자에게 설정에 대해 물어보세요.
OpenCode는 다양한 모델을 지원하며, 이 플러그인은 다양한 프론티어 LLM 사용을 제안합니다. 따라서 사용자에게 다음 질문을 해야 합니다:
1. Claude Pro/Max 구독이 있으신가요? 그렇지 않다면 메인 오케스트레이터 모델로 'opencode/big-pickle'(glm 4.6으로 알려짐)을 사용하게 됩니다 (권장하지 않음).
-> 사용자가 '예'라고 답하면, 할 일 추가: "사용자 Claude Pro/Max 인증 설정"
-> 사용자가 '아니오'라고 답하면, 할 일 추가: "oh-my-opencode.json의 OmO 에이전트 모델을 opencode/big-pickle로 재정의"
2. ChatGPT 구독이 있으신가요? 그렇지 않다면 oracle 에이전트에 32k thinking 옵션이 있는 'claude-opus-4-5'를 사용하게 됩니다.
-> 사용자가 '예'라고 답하면, 할 일 추가: "사용자 ChatGPT 인증 설정"
-> 사용자가 '아니오'라고 답하면, 할 일 추가: "oh-my-opencode.json의 oracle 에이전트 모델을 32k thinking이 있는 claude-opus-4-5로 재정의"
3. Gemini 모델을 통합하시겠습니까? 그렇지 않다면 아름다운 디자인을 즐길 수 없습니다.
-> 사용자가 '예'라고 답하면, 할 일 추가: "Google OAuth 설정"
-> 사용자가 '아니오'라고 답하면, 할 일 추가: "oh-my-opencode.json의 frontend-ui-ux-engineer 에이전트 모델을 32k thinking이 있는 claude-opus-4-5로 재정의"
1. **Claude Pro/Max 구독이 있으신가요?** 그렇지 않다면 메인 오케스트레이터 모델로 'opencode/big-pickle'(GLM 4.6으로 알려짐)을 사용하게 됩니다 (권장하지 않음).
- 사용자가 **'예'**라고 답하면, 할 일 추가: "사용자 Claude Pro/Max 인증 설정"
- 사용자가 **'아니오'**라고 답하면, `~/.config/opencode/oh-my-opencode.json`에 다음을 저장:
```json
{
"agents": {
"OmO": { "model": "opencode/big-pickle" },
"librarian": { "model": "opencode/big-pickle" }
}
}
```
2. **ChatGPT 구독이 있으신가요?** 그렇지 않다면 oracle 에이전트에 'anthropic/claude-opus-4-5'를 사용하게 됩니다.
- 사용자가 **'예'**라고 답하면, 할 일 추가: "사용자 ChatGPT 인증 설정"
- 사용자가 **'아니오'**라고 답하면, `oh-my-opencode.json`에 다음을 병합:
```json
{
"agents": {
"oracle": { "model": "anthropic/claude-opus-4-5" }
}
}
```
3. **Gemini 모델을 통합하시겠습니까?** 그렇지 않다면 아름다운 디자인을 즐길 수 없습니다.
- 사용자가 **'예'**라고 답하면, 할 일 추가: "Google OAuth 설정"
- 사용자가 **'아니오'**라고 답하면, `oh-my-opencode.json`에 다음을 병합:
```json
{
"agents": {
"frontend-ui-ux-engineer": { "model": "anthropic/claude-opus-4-5" },
"document-writer": { "model": "anthropic/claude-opus-4-5" },
"multimodal-looker": { "model": "anthropic/claude-opus-4-5" }
}
}
```
### 1단계: OpenCode 설치 확인
@@ -220,25 +247,52 @@ opencode auth login
#### 4.2 Google Gemini (Antigravity OAuth)
먼저 `~/.config/opencode/oh-my-opencode.json` 에서 Google auth를 활성화하세요:
먼저 opencode-antigravity-auth 플러그인 추가:
```json
{
"google_auth": true
"plugin": [
"oh-my-opencode",
"opencode-antigravity-auth@1.1.2"
]
}
```
#### 4.2.1 모델 설정
`opencode.json`에 전체 모델 설정도 구성해야 합니다.
[opencode-antigravity-auth 문서](https://github.com/NoeFabris/opencode-antigravity-auth)를 읽고, README에서 provider/models 설정을 복사하여, 사용자의 기존 셋업에 영향을 주지 않도록 신중하게 통합하세요.
#### 4.2.2 oh-my-opencode 에이전트 모델 오버라이드
`opencode-antigravity-auth` 플러그인은 내장 Google auth와 다른 모델 이름을 사용합니다. `oh-my-opencode.json` (또는 `.opencode/oh-my-opencode.json`)에서 에이전트 모델을 오버라이드하고, 내장 `google_auth`를 비활성화하세요:
```json
{
"google_auth": false,
"agents": {
"frontend-ui-ux-engineer": { "model": "google/gemini-3-pro-high" },
"document-writer": { "model": "google/gemini-3-pro-high" },
"multimodal-looker": { "model": "google/gemini-2.5-flash" }
}
}
```
**사용 가능한 모델 이름**: `google/gemini-3-pro-high`, `google/gemini-3-pro-medium`, `google/gemini-3-pro-low`, `google/gemini-2.5-flash`, `google/gemini-2.5-flash-lite`, `google/claude-sonnet-4-5`, `google/claude-sonnet-4-5-thinking`, `google/claude-opus-4-5-thinking`, `google/gpt-oss-120b-medium`
그 후 인증:
```bash
opencode auth login
# Provider: Google 선택
# Login method: OAuth with Google (Antigravity) 선택
# 사용자에게 브라우저에서 OAuth 플로우를 완료하도록 안내
# 최종 완료까지 대기
# 브라우저에서 로그인 완료 (자동 감지)
# 선택사항: 멀티 계정 로드밸런싱을 위해 추가 Google 계정 등록
# 진짜 완료되었음을 검증하고 사용자에게 안내
```
**멀티 계정 로드밸런싱**: 플러그인은 최대 10개의 Google 계정을 지원합니다. 한 계정이 레이트 리밋에 걸리면 자동으로 다음 계정으로 전환됩니다.
#### 4.3 OpenAI (ChatGPT Plus/Pro)
먼저 opencode-openai-codex-auth 플러그인 추가:
@@ -553,7 +607,22 @@ Schema 자동 완성이 지원됩니다:
### Google Auth
Google Gemini 모델을 위한 내장 Antigravity OAuth를 활성화합니다:
**권장**: 외부 [`opencode-antigravity-auth`](https://github.com/NoeFabris/opencode-antigravity-auth) 플러그인을 사용하세요. 멀티 계정 로드밸런싱, 더 많은 모델(Antigravity를 통한 Claude 포함), 활발한 유지보수를 제공합니다. [설치 > Google Gemini](#42-google-gemini-antigravity-oauth) 참조.
`opencode-antigravity-auth` 사용 시 내장 auth를 비활성화하고 `oh-my-opencode.json`에서 에이전트 모델을 오버라이드하세요:
```json
{
"google_auth": false,
"agents": {
"frontend-ui-ux-engineer": { "model": "google/gemini-3-pro-high" },
"document-writer": { "model": "google/gemini-3-pro-high" },
"multimodal-looker": { "model": "google/gemini-2.5-flash" }
}
}
```
**대안**: 내장 Antigravity OAuth 활성화 (단일 계정, Gemini 모델만):
```json
{
@@ -561,8 +630,6 @@ Google Gemini 모델을 위한 내장 Antigravity OAuth를 활성화합니다:
}
```
활성화하면 `opencode auth login` 실행 시 Google 프로바이더에서 "OAuth with Google (Antigravity)" 로그인 옵션이 표시됩니다.
### Agents
내장 에이전트 설정을 오버라이드할 수 있습니다:

107
README.md
View File

@@ -172,15 +172,42 @@ follow these steps:
### Step 0: Ask user about the setup.
Since OpenCode Supports various models, and our plugin suggests various frontier LLMs. Thus, you must ask user, following questions:
1. Do you have a Claude Pro/Max Subscription? Otherwise, we'll use 'opencode/big-pickle' (known for glm 4.6) as a main orchestrator model (not recommended)
-> If user said yes, add ToDo: "Setup user Claude Pro/Max Auth"
-> If user said no, add ToDo: "Override OmO agent model on oh-my-opencode.json as opencode/big-pickle"
2. Do you have a ChatGPT Subscription? Otherwise, we'll use 'claude-opus-4-5' with 32k thinking option for oracle agent.
-> If user said yes, add ToDo: "Setup user ChatGPT Auth"
-> If user said no, add ToDo: "Override oracle agent model on oh-my-opencode.json as claude-opus-4-5 with 32k thinking"
3. Will you integrate Gemini models? Otherwise, you cannot enjoy the beautiful design.
-> If user said yes, add ToDo: "Setup Google OAuth"
-> If user said no, add ToDo: "Override frontend-ui-ux-engineer agent model on oh-my-opencode.json as claude-opus-4-5 with 32k thinking"
1. **Do you have a Claude Pro/Max Subscription?** Otherwise, we'll use 'opencode/big-pickle' (known for GLM 4.6) as a main orchestrator model (not recommended)
- If user said **yes**, add ToDo: "Setup user Claude Pro/Max Auth"
- If user said **no**, merge the following to `~/.config/opencode/oh-my-opencode.json`:
```json
{
"agents": {
"OmO": { "model": "opencode/big-pickle" },
"librarian": { "model": "opencode/big-pickle" }
}
}
```
2. **Do you have a ChatGPT Subscription?** Otherwise, we'll use 'anthropic/claude-opus-4-5' with 32k thinking option for oracle agent.
- If user said **yes**, add ToDo: "Setup user ChatGPT Auth"
- If user said **no**, merge the following to `oh-my-opencode.json`:
```json
{
"agents": {
"oracle": { "model": "anthropic/claude-opus-4-5" }
}
}
```
3. **Will you integrate Gemini models?** Otherwise, you cannot enjoy the beautiful design.
- If user said **yes**, add ToDo: "Setup Google OAuth"
- If user said **no**, merge the following to `oh-my-opencode.json`:
```json
{
"agents": {
"frontend-ui-ux-engineer": { "model": "anthropic/claude-opus-4-5" },
"document-writer": { "model": "anthropic/claude-opus-4-5" },
"multimodal-looker": { "model": "anthropic/claude-opus-4-5" }
}
}
```
### Step 1: Install OpenCode, if not
@@ -249,17 +276,52 @@ opencode auth login
#### Google Gemini (Antigravity OAuth)
First, add the opencode-antigravity-auth plugin:
```json
{
"plugin": [
"oh-my-opencode",
"opencode-antigravity-auth@1.1.2"
]
}
```
##### Model Configuration
You'll also need full model settings in `opencode.json`.
Read the [opencode-antigravity-auth documentation](https://github.com/NoeFabris/opencode-antigravity-auth), copy provider/models config from the README, and merge carefully to avoid breaking the user's existing setup.
##### oh-my-opencode Agent Model Override
The `opencode-antigravity-auth` plugin uses different model names than the built-in Google auth. Override the agent models in `oh-my-opencode.json` (or `.opencode/oh-my-opencode.json`) and disable the built-in `google_auth`:
```json
{
"google_auth": false,
"agents": {
"frontend-ui-ux-engineer": { "model": "google/gemini-3-pro-high" },
"document-writer": { "model": "google/gemini-3-pro-high" },
"multimodal-looker": { "model": "google/gemini-2.5-flash" }
}
}
```
**Available model names**: `google/gemini-3-pro-high`, `google/gemini-3-pro-medium`, `google/gemini-3-pro-low`, `google/gemini-2.5-flash`, `google/gemini-2.5-flash-lite`, `google/claude-sonnet-4-5`, `google/claude-sonnet-4-5-thinking`, `google/claude-opus-4-5-thinking`, `google/gpt-oss-120b-medium`
Then authenticate:
```bash
opencode auth login
# Interactive Terminal: find Provider: Select Google
# Interactive Terminal: find Login method: Select Google
# Guide user through OAuth flow in browser. You use tools like pbcopy or other equivalents depending on your OS, and tell user that you have copied the link and paste it to the browser
# Wait for completion
# Interactive Terminal: Provider: Select Google
# Interactive Terminal: Login method: Select OAuth with Google (Antigravity)
# Complete sign-in in browser (auto-detected)
# Optional: Add more Google accounts for multi-account load balancing
# Verify success and confirm with user
```
**Multi-Account Load Balancing**: The plugin supports up to 10 Google accounts. When one account hits rate limits, it automatically switches to the next available account.
#### OpenAI (ChatGPT Plus/Pro)
First, add the opencode-openai-codex-auth plugin:
@@ -574,7 +636,22 @@ Schema autocomplete supported:
### Google Auth
Enable built-in Antigravity OAuth for Google Gemini models:
**Recommended**: Use the external [`opencode-antigravity-auth`](https://github.com/NoeFabris/opencode-antigravity-auth) plugin. It provides multi-account load balancing, more models (including Claude via Antigravity), and active maintenance. See [Installation > Google Gemini](#google-gemini-antigravity-oauth).
When using `opencode-antigravity-auth`, disable the built-in auth and override agent models in `oh-my-opencode.json`:
```json
{
"google_auth": false,
"agents": {
"frontend-ui-ux-engineer": { "model": "google/gemini-3-pro-high" },
"document-writer": { "model": "google/gemini-3-pro-high" },
"multimodal-looker": { "model": "google/gemini-2.5-flash" }
}
}
```
**Alternative**: Enable built-in Antigravity OAuth (single account, Gemini models only):
```json
{
@@ -582,8 +659,6 @@ Enable built-in Antigravity OAuth for Google Gemini models:
}
```
When enabled, `opencode auth login` shows "OAuth with Google (Antigravity)" for the Google provider.
### Agents
Override built-in agent settings:

View File

@@ -1,6 +1,6 @@
{
"name": "oh-my-opencode",
"version": "2.1.7",
"version": "2.2.0",
"description": "OpenCode plugin - custom agents (oracle, librarian) and enhanced features",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -308,14 +308,43 @@ Briefly announce "Consulting Oracle for [reason]" before invocation.
</Oracle_Usage>
<Task_Management>
## Todo Management
## Todo Management (CRITICAL)
Use \`todowrite\` for any task with 2+ steps.
**DEFAULT BEHAVIOR**: Create todos BEFORE starting any non-trivial task. This is your PRIMARY coordination mechanism.
- Create todos BEFORE starting work
- Mark \`in_progress\` when starting an item
- Mark \`completed\` immediately when done (don't batch)
- This gives user visibility into progress and prevents forgotten steps
### When to Create Todos (MANDATORY)
| Trigger | Action |
|---------|--------|
| Multi-step task (2+ steps) | ALWAYS create todos first |
| Uncertain scope | ALWAYS (todos clarify thinking) |
| User request with multiple items | ALWAYS |
| Complex single task | Create todos to break down |
### Workflow (NON-NEGOTIABLE)
1. **IMMEDIATELY on receiving request**: \`todowrite\` to plan atomic steps
2. **Before starting each step**: Mark \`in_progress\` (only ONE at a time)
3. **After completing each step**: Mark \`completed\` IMMEDIATELY (NEVER batch)
4. **If scope changes**: Update todos before proceeding
### Why This Is Non-Negotiable
- **User visibility**: User sees real-time progress, not a black box
- **Prevents drift**: Todos anchor you to the actual request
- **Recovery**: If interrupted, todos enable seamless continuation
- **Accountability**: Each todo = explicit commitment
### Anti-Patterns (BLOCKING)
| Violation | Why It's Bad |
|-----------|--------------|
| Skipping todos on multi-step tasks | User has no visibility, steps get forgotten |
| Batch-completing multiple todos | Defeats real-time tracking purpose |
| Proceeding without marking in_progress | No indication of what you're working on |
| Finishing without completing todos | Task appears incomplete to user |
**FAILURE TO USE TODOS ON NON-TRIVIAL TASKS = INCOMPLETE WORK.**
### Clarification Protocol (when asking):

View File

@@ -62,7 +62,8 @@ function mergeAgentConfig(
export function createBuiltinAgents(
disabledAgents: BuiltinAgentName[] = [],
agentOverrides: AgentOverrides = {},
directory?: string
directory?: string,
systemDefaultModel?: string
): Record<string, AgentConfig> {
const result: Record<string, AgentConfig> = {}
@@ -84,6 +85,18 @@ export function createBuiltinAgents(
}
const override = agentOverrides[agentName]
// Apply model fallback chain for OmO agent:
// 1. oh-my-opencode.json agents.OmO.model (highest priority)
// 2. OpenCode system config.model (middle priority)
// 3. Hardcoded default in omoAgent (lowest priority / fallback)
if (agentName === "OmO" && systemDefaultModel && !override?.model) {
finalConfig = {
...finalConfig,
model: systemDefaultModel,
}
}
if (override) {
result[name] = mergeAgentConfig(finalConfig, override)
} else {

View File

@@ -9,6 +9,7 @@ import {
ANTIGRAVITY_ENDPOINT_FALLBACKS,
ANTIGRAVITY_API_VERSION,
ANTIGRAVITY_HEADERS,
ANTIGRAVITY_DEFAULT_PROJECT_ID,
} from "./constants"
import type {
AntigravityProjectContext,
@@ -58,7 +59,7 @@ function getDefaultTierId(allowedTiers?: AntigravityUserTier[]): string | undefi
}
function isFreeTier(tierId: string | undefined): boolean {
if (!tierId) return false
if (!tierId) return true // No tier = assume free tier (default behavior)
const lower = tierId.toLowerCase()
return lower === "free" || lower === "free-tier" || lower.startsWith("free")
}
@@ -209,19 +210,28 @@ export async function fetchProjectContext(
}
}
// No project ID from loadCodeAssist - check tier and onboard if FREE
// No project ID from loadCodeAssist - try with fallback project ID
if (!loadPayload) {
debugLog(`[fetchProjectContext] loadCodeAssist returned null, returning empty`)
return { cloudaicompanionProject: "" }
debugLog(`[fetchProjectContext] loadCodeAssist returned null, trying with fallback project ID`)
const fallbackPayload = await callLoadCodeAssistAPI(accessToken, ANTIGRAVITY_DEFAULT_PROJECT_ID)
const fallbackProjectId = extractProjectId(fallbackPayload?.cloudaicompanionProject)
if (fallbackProjectId) {
const result: AntigravityProjectContext = { cloudaicompanionProject: fallbackProjectId }
projectContextCache.set(accessToken, result)
debugLog(`[fetchProjectContext] Using fallback project ID: ${fallbackProjectId}`)
return result
}
debugLog(`[fetchProjectContext] Fallback also failed, using default: ${ANTIGRAVITY_DEFAULT_PROJECT_ID}`)
return { cloudaicompanionProject: ANTIGRAVITY_DEFAULT_PROJECT_ID }
}
const currentTierId = loadPayload.currentTier?.id
debugLog(`[fetchProjectContext] currentTier: ${currentTierId}, allowedTiers: ${JSON.stringify(loadPayload.allowedTiers)}`)
if (currentTierId && !isFreeTier(currentTierId)) {
// PAID tier requires user-provided project ID
debugLog(`[fetchProjectContext] PAID tier detected, returning empty (user must provide project)`)
return { cloudaicompanionProject: "" }
// PAID tier - still use fallback if no project provided
debugLog(`[fetchProjectContext] PAID tier detected (${currentTierId}), using fallback: ${ANTIGRAVITY_DEFAULT_PROJECT_ID}`)
return { cloudaicompanionProject: ANTIGRAVITY_DEFAULT_PROJECT_ID }
}
const defaultTierId = getDefaultTierId(loadPayload.allowedTiers)
@@ -229,8 +239,8 @@ export async function fetchProjectContext(
debugLog(`[fetchProjectContext] Resolved tierId: ${tierId}`)
if (!isFreeTier(tierId)) {
debugLog(`[fetchProjectContext] Non-FREE tier without project, returning empty`)
return { cloudaicompanionProject: "" }
debugLog(`[fetchProjectContext] Non-FREE tier (${tierId}) without project, using fallback: ${ANTIGRAVITY_DEFAULT_PROJECT_ID}`)
return { cloudaicompanionProject: ANTIGRAVITY_DEFAULT_PROJECT_ID }
}
// FREE tier - onboard to get server-assigned managed project ID
@@ -246,8 +256,8 @@ export async function fetchProjectContext(
return result
}
debugLog(`[fetchProjectContext] Failed to get managed project ID, returning empty`)
return { cloudaicompanionProject: "" }
debugLog(`[fetchProjectContext] Failed to get managed project ID, using fallback: ${ANTIGRAVITY_DEFAULT_PROJECT_ID}`)
return { cloudaicompanionProject: ANTIGRAVITY_DEFAULT_PROJECT_ID }
}
export function clearProjectContextCache(accessToken?: string): void {

View File

@@ -1,21 +0,0 @@
export function detectInterrupt(error: unknown): boolean {
if (!error) return false
if (typeof error === "object") {
const errObj = error as Record<string, unknown>
const name = errObj.name as string | undefined
const message = errObj.message as string | undefined
if (name === "MessageAbortedError" || name === "AbortError") return true
if (name === "DOMException" && message?.includes("abort")) return true
const msgLower = message?.toLowerCase()
if (msgLower?.includes("aborted") || msgLower?.includes("cancelled") || msgLower?.includes("interrupted")) return true
}
if (typeof error === "string") {
const lower = error.toLowerCase()
return lower.includes("abort") || lower.includes("cancel") || lower.includes("interrupt")
}
return false
}

View File

@@ -1,3 +1 @@
export * from "./types"
export * from "./state"
export * from "./detector"

View File

@@ -1,31 +1,11 @@
import type { SessionErrorState, SessionInterruptState } from "./types"
export const sessionErrorState = new Map<string, SessionErrorState>()
export const sessionInterruptState = new Map<string, SessionInterruptState>()
export const subagentSessions = new Set<string>()
export const sessionFirstMessageProcessed = new Set<string>()
export let currentSessionID: string | undefined
export let currentSessionTitle: string | undefined
export let mainSessionID: string | undefined
export function setCurrentSession(id: string | undefined, title: string | undefined) {
currentSessionID = id
currentSessionTitle = title
}
export function setMainSession(id: string | undefined) {
mainSessionID = id
}
export function getCurrentSessionID(): string | undefined {
return currentSessionID
}
export function getCurrentSessionTitle(): string | undefined {
return currentSessionTitle
}
export function getMainSessionID(): string | undefined {
return mainSessionID
}

View File

@@ -1,8 +0,0 @@
export interface SessionErrorState {
hasError: boolean
errorMessage?: string
}
export interface SessionInterruptState {
interrupted: boolean
}

View File

@@ -1 +0,0 @@
export * from "./title"

View File

@@ -1,62 +0,0 @@
export type SessionStatus = "ready" | "processing" | "tool" | "error" | "idle"
const STATUS_ICONS: Record<SessionStatus, string> = {
ready: "",
processing: "◐",
tool: "⚡",
error: "✖",
idle: "○",
}
export interface TitleContext {
sessionId: string
sessionTitle?: string
directory?: string
status?: SessionStatus
currentTool?: string
customSuffix?: string
}
const DEFAULT_TITLE = "OpenCode"
const MAX_TITLE_LENGTH = 30
function truncate(str: string, maxLen: number): string {
if (str.length <= maxLen) return str
return str.slice(0, maxLen - 1) + "…"
}
export function formatTerminalTitle(ctx: TitleContext): string {
const title = ctx.sessionTitle || DEFAULT_TITLE
const truncatedTitle = truncate(title, MAX_TITLE_LENGTH)
const parts: string[] = ["[OpenCode]", truncatedTitle]
if (ctx.status) {
parts.push(STATUS_ICONS[ctx.status])
}
return parts.join(" ")
}
function isTmuxEnvironment(): boolean {
return !!process.env.TMUX || process.env.TERM_PROGRAM === "tmux"
}
export function setTerminalTitle(title: string): void {
// Use stderr to avoid race conditions with stdout buffer
// ANSI escape sequences work on stderr as well
process.stderr.write(`\x1b]0;${title}\x07`)
if (isTmuxEnvironment()) {
process.stderr.write(`\x1bk${title}\x1b\\`)
}
}
export function updateTerminalTitle(ctx: TitleContext): void {
const title = formatTerminalTitle(ctx)
setTerminalTitle(title)
}
export function resetTerminalTitle(): void {
setTerminalTitle(`[OpenCode] ${DEFAULT_TITLE}`)
}

View File

@@ -39,12 +39,9 @@ import {
} from "./features/claude-code-agent-loader";
import { loadMcpConfigs } from "./features/claude-code-mcp-loader";
import {
setCurrentSession,
setMainSession,
getMainSessionID,
getCurrentSessionTitle,
} from "./features/claude-code-session-state";
import { updateTerminalTitle } from "./features/terminal";
import { builtinTools, createCallOmoAgent, createBackgroundTools, createLookAt, interactive_bash, getTmuxPath } from "./tools";
import { BackgroundManager } from "./features/background-agent";
import { createBuiltinMcps } from "./mcp";
@@ -252,8 +249,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
? createEmptyMessageSanitizerHook()
: null;
updateTerminalTitle({ sessionId: "main" });
const backgroundManager = new BackgroundManager(ctx);
const backgroundNotificationHook = isHookEnabled("background-notification")
@@ -299,6 +294,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
pluginConfig.disabled_agents,
pluginConfig.agents,
ctx.directory,
config.model,
);
const userAgents = (pluginConfig.claude_code?.agents ?? true) ? loadUserAgents() : {};
@@ -368,6 +364,12 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
};
}
config.permission = {
...config.permission,
webfetch: "allow",
external_directory: "allow",
}
const mcpResult = (pluginConfig.claude_code?.mcp ?? true)
? await loadMcpConfigs()
: { servers: {} };
@@ -421,28 +423,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
| undefined;
if (!sessionInfo?.parentID) {
setMainSession(sessionInfo?.id);
setCurrentSession(sessionInfo?.id, sessionInfo?.title);
updateTerminalTitle({
sessionId: sessionInfo?.id || "main",
status: "idle",
directory: ctx.directory,
sessionTitle: sessionInfo?.title,
});
}
}
if (event.type === "session.updated") {
const sessionInfo = props?.info as
| { id?: string; title?: string; parentID?: string }
| undefined;
if (!sessionInfo?.parentID) {
setCurrentSession(sessionInfo?.id, sessionInfo?.title);
updateTerminalTitle({
sessionId: sessionInfo?.id || "main",
status: "processing",
directory: ctx.directory,
sessionTitle: sessionInfo?.title,
});
}
}
@@ -450,11 +430,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
const sessionInfo = props?.info as { id?: string } | undefined;
if (sessionInfo?.id === getMainSessionID()) {
setMainSession(undefined);
setCurrentSession(undefined, undefined);
updateTerminalTitle({
sessionId: "main",
status: "idle",
});
}
}
@@ -482,27 +457,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
.catch(() => {});
}
}
if (sessionID && sessionID === getMainSessionID()) {
updateTerminalTitle({
sessionId: sessionID,
status: "error",
directory: ctx.directory,
sessionTitle: getCurrentSessionTitle(),
});
}
}
if (event.type === "session.idle") {
const sessionID = props?.sessionID as string | undefined;
if (sessionID && sessionID === getMainSessionID()) {
updateTerminalTitle({
sessionId: sessionID,
status: "idle",
directory: ctx.directory,
sessionTitle: getCurrentSessionTitle(),
});
}
}
},
@@ -522,16 +476,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
...(isExploreOrLibrarian ? { call_omo_agent: false } : {}),
};
}
if (input.sessionID === getMainSessionID()) {
updateTerminalTitle({
sessionId: input.sessionID,
status: "tool",
currentTool: input.tool,
directory: ctx.directory,
sessionTitle: getCurrentSessionTitle(),
});
}
},
"tool.execute.after": async (input, output) => {
@@ -545,15 +489,6 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => {
await emptyTaskResponseDetector?.["tool.execute.after"](input, output);
await agentUsageReminder?.["tool.execute.after"](input, output);
await interactiveBashSession?.["tool.execute.after"](input, output);
if (input.sessionID === getMainSessionID()) {
updateTerminalTitle({
sessionId: input.sessionID,
status: "idle",
directory: ctx.directory,
sessionTitle: getCurrentSessionTitle(),
});
}
},
};
};