opencode-ultra 0.4.0 → 0.4.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.md +152 -264
- package/dist/hooks/prompt-renderer.d.ts +3 -2
- package/dist/hooks/session-compaction.d.ts +1 -1
- package/dist/index.js +67 -7
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/ttl-map.d.ts +19 -0
- package/dist/shared/types.d.ts +93 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,30 +1,52 @@
|
|
|
1
1
|
# opencode-ultra
|
|
2
2
|
|
|
3
|
-
[oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
##
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
|
11
|
-
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
20
|
-
|
|
3
|
+
[oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) をベースに、micode / opencode-skillful の良い部分を取り込んだ OpenCode 1.2.x プラグイン。
|
|
4
|
+
マルチエージェントオーケストレーション・キーワード駆動モード切替・ルール注入・セッション継続・AST検索を軽量な単一プラグインで実現する。
|
|
5
|
+
|
|
6
|
+
## 機能一覧
|
|
7
|
+
|
|
8
|
+
### ツール (7)
|
|
9
|
+
|
|
10
|
+
| ツール | 説明 |
|
|
11
|
+
|--------|------|
|
|
12
|
+
| `spawn_agent` | 並列エージェント実行 (ConcurrencyPool 制御付き) |
|
|
13
|
+
| `ralph_loop` | 自律ループ実行 (`<promise>DONE</promise>` で完了検知) |
|
|
14
|
+
| `cancel_ralph` | 実行中の Ralph Loop をキャンセル |
|
|
15
|
+
| `batch_read` | 複数ファイル並列読み込み (最大20) |
|
|
16
|
+
| `ledger_save` | Continuity Ledger の保存 (.opencode/ledgers/) |
|
|
17
|
+
| `ledger_load` | Continuity Ledger の読み込み (名前指定 or 最新) |
|
|
18
|
+
| `ast_search` | AST-aware コード検索 (ast-grep/sg バイナリ使用、未インストール時は自動スキップ) |
|
|
19
|
+
|
|
20
|
+
### フック (9)
|
|
21
|
+
|
|
22
|
+
| フック | hook ポイント | 説明 |
|
|
23
|
+
|--------|-------------|------|
|
|
24
|
+
| `keyword-detector` | chat.message | ultrawork/search/analyze/think キーワード検知 |
|
|
25
|
+
| `rules-injector` | system.transform | .opencode/rules.md を system prompt に注入 |
|
|
26
|
+
| `context-injector` | system.transform | ARCHITECTURE.md / CODE_STYLE.md を自動注入 |
|
|
27
|
+
| `fragment-injector` | system.transform | エージェント毎のカスタムプロンプト断片注入 |
|
|
28
|
+
| `prompt-renderer` | system.transform | モデル別 system prompt フォーマット最適化 (markdown/xml/json) |
|
|
29
|
+
| `comment-checker` | tool.execute.after | Write/Edit 後の AI スロップコメント検知 |
|
|
30
|
+
| `token-truncation` | tool.execute.after | 30000文字超のツール出力を先頭40%+末尾40%に圧縮 |
|
|
31
|
+
| `todo-enforcer` | event (session.idle) | 未完了 TODO を検知して強制継続 |
|
|
32
|
+
| `session-compaction` | experimental.session.compacting | セッション圧縮時に structured summary 生成 |
|
|
33
|
+
|
|
34
|
+
### その他
|
|
35
|
+
|
|
36
|
+
| 機能 | 説明 |
|
|
37
|
+
|------|------|
|
|
38
|
+
| **Built-in Agent Demotion** | OpenCode 標準エージェント (build/plan/triage/docs) を subagent に降格 |
|
|
39
|
+
| **MCP 自動登録** | Context7 を自動登録 (API キー任意) |
|
|
40
|
+
| **Plan-First Protocol** | spawn_agent 前に Phase 形式のプランを提示、ユーザー承認を待つ |
|
|
41
|
+
| **Categories** | spawn_agent の category パラメータでモデル/バリアントを一括切替 |
|
|
42
|
+
| **Concurrency Pool** | Semaphore ベースの global/provider/model 三層並列制御 |
|
|
21
43
|
|
|
22
44
|
## アーキテクチャ
|
|
23
45
|
|
|
24
46
|
```
|
|
25
47
|
User
|
|
26
48
|
│
|
|
27
|
-
│ "ultrawork" / "ulw" キーワード
|
|
49
|
+
│ "ultrawork" / "ulw" / "think hard" キーワード
|
|
28
50
|
│
|
|
29
51
|
▼
|
|
30
52
|
┌──────────────────────────────────────────────┐
|
|
@@ -32,27 +54,37 @@ User
|
|
|
32
54
|
│ │
|
|
33
55
|
│ chat.message hook │
|
|
34
56
|
│ ├─ keyword-detector │
|
|
35
|
-
│ │ └─ ultrawork / search / analyze
|
|
57
|
+
│ │ └─ ultrawork / search / analyze / think │
|
|
36
58
|
│ └─ ultrawork → variant: "max" │
|
|
37
59
|
│ │
|
|
38
60
|
│ system.transform hook │
|
|
39
61
|
│ ├─ keyword message injection │
|
|
40
|
-
│
|
|
62
|
+
│ ├─ rules-injector (.opencode/rules.md) │
|
|
63
|
+
│ ├─ context-injector (ARCHITECTURE/CODE_STYLE)│
|
|
64
|
+
│ ├─ fragment-injector (per-agent fragments) │
|
|
65
|
+
│ └─ prompt-renderer (markdown/xml/json) │
|
|
41
66
|
│ │
|
|
42
67
|
│ tool.execute.after hook │
|
|
43
|
-
│
|
|
68
|
+
│ ├─ comment-checker (Write/Edit 後に検査) │
|
|
69
|
+
│ └─ token-truncation (30000文字超を圧縮) │
|
|
70
|
+
│ │
|
|
71
|
+
│ session.compacting hook │
|
|
72
|
+
│ └─ session-compaction (structured summary) │
|
|
44
73
|
│ │
|
|
45
74
|
│ event hook │
|
|
46
75
|
│ ├─ session cleanup │
|
|
47
76
|
│ └─ todo-enforcer (session.idle 時に検査) │
|
|
48
77
|
│ │
|
|
49
78
|
│ config hook │
|
|
50
|
-
│
|
|
79
|
+
│ ├─ register agents to OpenCode │
|
|
80
|
+
│ └─ demote built-in agents to subagent │
|
|
51
81
|
│ │
|
|
52
82
|
│ tools │
|
|
53
83
|
│ ├─ spawn_agent (並列実行 + 並列制御) │
|
|
54
|
-
│ ├─ ralph_loop
|
|
55
|
-
│
|
|
84
|
+
│ ├─ ralph_loop / cancel_ralph │
|
|
85
|
+
│ ├─ batch_read (複数ファイル並列読み込み) │
|
|
86
|
+
│ ├─ ledger_save / ledger_load (文脈継続) │
|
|
87
|
+
│ └─ ast_search (構文木検索, optional) │
|
|
56
88
|
│ │
|
|
57
89
|
│ MCP registration │
|
|
58
90
|
│ └─ context7 (自動登録) │
|
|
@@ -72,13 +104,13 @@ User
|
|
|
72
104
|
|
|
73
105
|
## エージェント構成
|
|
74
106
|
|
|
75
|
-
Sisyphus (オーケストレーター)
|
|
107
|
+
Sisyphus (オーケストレーター) が直接コードを読み、実装はサブエージェントに委任する。
|
|
76
108
|
|
|
77
109
|
### ビルトインデフォルト
|
|
78
110
|
|
|
79
111
|
| Agent | 役割 | デフォルトモデル | モード |
|
|
80
112
|
|-------|------|-----------------|--------|
|
|
81
|
-
| **sisyphus** | オーケストレーター —
|
|
113
|
+
| **sisyphus** | オーケストレーター — 読み込み+分析+計画+委任 | openai-codex/gpt-5.3-codex | primary |
|
|
82
114
|
| **oracle** | 設計・デバッグ・アーキテクチャ判断 | openai/gpt-5.2 | subagent |
|
|
83
115
|
| **explore** | 高速コードベース偵察 | anthropic/claude-haiku-4-5 | subagent |
|
|
84
116
|
| **librarian** | ドキュメント・ベストプラクティス調査 | anthropic/claude-sonnet-4-5 | subagent |
|
|
@@ -88,314 +120,175 @@ Sisyphus (オーケストレーター) が直接ツールを使わず、全て
|
|
|
88
120
|
| **atlas** | タスク管理・進捗追跡 | anthropic/claude-sonnet-4-5 | subagent |
|
|
89
121
|
| **multimodal-looker** | 画像・スクリーンショット解析 | anthropic/claude-sonnet-4-5 | subagent |
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
### Sisyphus の制約
|
|
123
|
+
全てのモデルは `oh-my-opencode.json` でオーバーライド可能。
|
|
94
124
|
|
|
95
|
-
Sisyphus
|
|
125
|
+
### Sisyphus のツール権限
|
|
96
126
|
|
|
97
127
|
```
|
|
98
|
-
Grep:
|
|
99
|
-
Write: false, Edit: false, Bash: false
|
|
128
|
+
Grep: true, Glob: true, Read: true ← 直接読み込み可能
|
|
129
|
+
Write: false, Edit: false, Bash: false ← サブエージェントに委任
|
|
100
130
|
```
|
|
101
131
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
## Plan-First Protocol
|
|
132
|
+
## Continuity Ledger
|
|
105
133
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
## Execution Plan
|
|
110
|
-
|
|
111
|
-
### Phase 1: 偵察 (parallel)
|
|
112
|
-
- [ ] API ルート探索 → explore (gpt-5.3-codex-spark)
|
|
113
|
-
- [ ] フレームワーク仕様調査 → librarian (glm-4.7)
|
|
114
|
-
|
|
115
|
-
### Phase 2: 実装 (after Phase 1)
|
|
116
|
-
- [ ] エンドポイント実装 → hephaestus (gpt-5.2)
|
|
117
|
-
|
|
118
|
-
### Phase 3: Review
|
|
119
|
-
- [ ] コードレビュー → momus (gpt-5.3-codex)
|
|
120
|
-
|
|
121
|
-
Estimated agents: 4 | Phases: 3
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
- 独立タスクは同一 Phase にグループ化 (並列実行)
|
|
125
|
-
- 依存関係があるタスクは別 Phase
|
|
126
|
-
- エージェント名 + 実際のモデル名を表示
|
|
127
|
-
- **「このプランで進めますか?」で承認待ち — OK するまで実行しない**
|
|
128
|
-
|
|
129
|
-
## spawn_agent ツール
|
|
130
|
-
|
|
131
|
-
Sisyphus が使う並列エージェント実行ツール。
|
|
134
|
+
セッション間の文脈を引き継ぐマークダウンドキュメント。`.opencode/ledgers/` に保存される。
|
|
132
135
|
|
|
133
136
|
```typescript
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{ agent: "librarian", prompt: "Research Express middleware best practices", description: "Middleware docs" },
|
|
138
|
-
{ agent: "hephaestus", prompt: "Implement the feature", description: "Implementation", category: "deep" }
|
|
139
|
-
]
|
|
140
|
-
})
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- 複数エージェントは **Promise.all で並列実行**(ConcurrencyPool による同時実行制御付き)
|
|
144
|
-
- 各エージェントは**エフェメラルセッション**で起動 — 完了後に自動削除
|
|
145
|
-
- 内部セッションには `internalSessions` フラグを付与し、**フックの再帰を防止**
|
|
146
|
-
- 進捗は `toolCtx.metadata({ title })` でステータスバーに表示
|
|
147
|
-
- Toast 通知で個別エージェントの完了を通知
|
|
148
|
-
- **`category` パラメータ**でモデル/バリアントをカテゴリから自動解決
|
|
137
|
+
// 保存
|
|
138
|
+
ledger_save({ name: "auth-refactor", content: "## Context\n..." })
|
|
139
|
+
// → Saved to .opencode/ledgers/auth-refactor.md
|
|
149
140
|
|
|
150
|
-
|
|
141
|
+
// 読み込み (名前指定)
|
|
142
|
+
ledger_load({ name: "auth-refactor" })
|
|
151
143
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
```
|
|
155
|
-
spawn_agent に 5 エージェント渡す
|
|
156
|
-
→ defaultConcurrency: 3 なら同時 3 個まで
|
|
157
|
-
→ 残りは空きを待って順次実行
|
|
144
|
+
// 読み込み (最新)
|
|
145
|
+
ledger_load({})
|
|
158
146
|
```
|
|
159
147
|
|
|
160
|
-
|
|
161
|
-
|---------------|------|------|
|
|
162
|
-
| `defaultConcurrency` | 全体の同時実行上限 | 全エージェント合計で N 個まで同時実行 |
|
|
163
|
-
| `providerConcurrency` | プロバイダー別上限 (例: openai: 8) | 同一プロバイダーのエージェントを N 個に制限 |
|
|
164
|
-
| `modelConcurrency` | モデル別上限 (例: gpt-5.2: 2) | 同一モデルのエージェントを N 個に制限 |
|
|
148
|
+
## Fragment Injection
|
|
165
149
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
`background_task` 未設定の場合は制限なし(従来通り全エージェント同時発火)。
|
|
169
|
-
|
|
170
|
-
## Categories
|
|
171
|
-
|
|
172
|
-
spawn_agent の `category` パラメータでモデル/バリアントを一括切替できる。
|
|
173
|
-
|
|
174
|
-
### ビルトインカテゴリ
|
|
175
|
-
|
|
176
|
-
| カテゴリ | 用途 | デフォルトモデル | バリアント |
|
|
177
|
-
|---------|------|-----------------|-----------|
|
|
178
|
-
| `visual-engineering` | フロントエンド/UI実装 | google/gemini-3-pro | — |
|
|
179
|
-
| `ultrabrain` | 最高精度タスク | openai/gpt-5.3-codex | xhigh |
|
|
180
|
-
| `deep` | 深い分析・複雑ロジック | openai/gpt-5.3-codex | medium |
|
|
181
|
-
| `quick` | 高速・低コストタスク | anthropic/claude-haiku-4-5 | — |
|
|
182
|
-
| `writing` | ドキュメント・文章 | zai-coding-plan/glm-4.7 | — |
|
|
183
|
-
|
|
184
|
-
設定ファイルでカスタムカテゴリの追加やビルトインの上書きが可能:
|
|
150
|
+
エージェント毎にカスタムプロンプト断片を注入する。
|
|
185
151
|
|
|
186
152
|
```jsonc
|
|
187
153
|
{
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
154
|
+
"fragments": {
|
|
155
|
+
"hephaestus": [".opencode/fragments/strict-coding.md"],
|
|
156
|
+
"sisyphus": [".opencode/fragments/planning-rules.md"]
|
|
191
157
|
}
|
|
192
158
|
}
|
|
193
159
|
```
|
|
194
160
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
自律ループ実行ツール。エージェントが `<promise>DONE</promise>` を出力するか、最大イテレーション数に達するまで繰り返し実行する。
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
ralph_loop({
|
|
201
|
-
prompt: "Implement all unit tests for the auth module",
|
|
202
|
-
agent: "hephaestus", // default
|
|
203
|
-
maxIterations: 10 // default
|
|
204
|
-
})
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
- 各イテレーションで前回の進捗をレビューし、続きから再開するよう指示
|
|
208
|
-
- ステータスバーに `Ralph Loop [3/10] — hephaestus` のように進捗表示
|
|
209
|
-
- `cancel_ralph` ツールで実行中のループを全てキャンセル可能
|
|
210
|
-
|
|
211
|
-
## Todo Enforcer
|
|
212
|
-
|
|
213
|
-
セッションが idle になったとき、最後の assistant メッセージに未完了の TODO(`- [ ]`)がある場合、強制継続プロンプトを送信する。
|
|
214
|
-
|
|
215
|
-
- **最大 5 回**(デフォルト)の強制で無限ループを防止
|
|
216
|
-
- spawn_agent のサブセッションには適用されない
|
|
217
|
-
- `disabled_hooks: ["todo-enforcer"]` で無効化可能
|
|
218
|
-
- 設定で最大回数を変更可能: `"todo_enforcer": { "maxEnforcements": 3 }`
|
|
219
|
-
|
|
220
|
-
## Comment Checker
|
|
221
|
-
|
|
222
|
-
Write / Edit ツール実行後に、書き込まれたファイルのコメント品質を検査する。
|
|
161
|
+
指定ファイルの内容が該当エージェントの system prompt に追加される。
|
|
223
162
|
|
|
224
|
-
|
|
163
|
+
## Per-model Prompt Renderer
|
|
225
164
|
|
|
226
|
-
|
|
227
|
-
- 80 文字超の行コメント (`// This function does...`)
|
|
228
|
-
- 説明的すぎる冒頭 (`// This`, `// The`, `// We`, `// Here`, `// Note:`)
|
|
229
|
-
- AI プレースホルダー (`// TODO: implement`)
|
|
230
|
-
- lint 回避コメント (`// eslint-disable`)
|
|
231
|
-
- 300 文字超の巨大 JSDoc
|
|
232
|
-
|
|
233
|
-
閾値超えの場合、ツール出力に `[Comment Checker]` 警告を追加する。
|
|
165
|
+
モデルに応じて system prompt のフォーマットを最適化する。
|
|
234
166
|
|
|
235
167
|
```jsonc
|
|
236
168
|
{
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
"
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
## MCP 自動登録
|
|
245
|
-
|
|
246
|
-
プラグイン初期化時に以下の MCP サーバーを自動登録する:
|
|
247
|
-
|
|
248
|
-
| MCP | パッケージ | API キー |
|
|
249
|
-
|-----|-----------|----------|
|
|
250
|
-
| **Context7** | `@upstash/context7-mcp` | オプション(あるとレートリミット緩和) |
|
|
251
|
-
|
|
252
|
-
API キーの渡し方:
|
|
253
|
-
|
|
254
|
-
```jsonc
|
|
255
|
-
{
|
|
256
|
-
"mcp_api_keys": {
|
|
257
|
-
"context7": "ctx7sk-..."
|
|
169
|
+
"prompt_renderer": {
|
|
170
|
+
"default": "markdown",
|
|
171
|
+
"model_overrides": {
|
|
172
|
+
"anthropic/claude-sonnet-4-5": "xml",
|
|
173
|
+
"openai/gpt-5.2": "json"
|
|
174
|
+
}
|
|
258
175
|
}
|
|
259
176
|
}
|
|
260
177
|
```
|
|
261
178
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
179
|
+
| フォーマット | 出力例 |
|
|
180
|
+
|-------------|--------|
|
|
181
|
+
| `markdown` | そのまま (デフォルト) |
|
|
182
|
+
| `xml` | `<section name="Rules">...</section>` |
|
|
183
|
+
| `json` | `{"section":"Rules","content":"..."}` |
|
|
265
184
|
|
|
266
185
|
## キーワード検知
|
|
267
186
|
|
|
268
|
-
ユーザーのプロンプトからキーワードを検知し、system prompt に動作モード指示を注入する。
|
|
269
|
-
コードブロック (`` ``` `` / `` ` ``) 内のキーワードは無視する。
|
|
270
|
-
|
|
271
187
|
| キーワード | モード | 効果 |
|
|
272
188
|
|-----------|--------|------|
|
|
273
|
-
| `ultrawork` / `ulw` | ultrawork | variant
|
|
274
|
-
| `search` / `find` / `探して`
|
|
275
|
-
| `analyze` / `調査` /
|
|
189
|
+
| `ultrawork` / `ulw` | ultrawork | variant "max"、全エージェント活用 |
|
|
190
|
+
| `search` / `find` / `探して` 等 | search | 網羅的検索 |
|
|
191
|
+
| `analyze` / `調査` / `debug` 等 | analyze | コンテキスト収集 |
|
|
192
|
+
| `think hard` / `じっくり` / `熟考` 等 | think | Extended thinking 有効化 |
|
|
276
193
|
|
|
277
194
|
日本語・中国語キーワードにも対応。
|
|
278
195
|
|
|
279
|
-
## ルール注入
|
|
280
|
-
|
|
281
|
-
`<project>/.opencode/rules.md` が存在する場合、その内容を system prompt に `## Project Rules` として自動注入する。ファイルの mtime でキャッシュし、変更時のみ再読み込み。
|
|
282
|
-
|
|
283
196
|
## 設定
|
|
284
197
|
|
|
285
|
-
2
|
|
198
|
+
2段階の設定マージ:
|
|
286
199
|
|
|
287
200
|
1. **ユーザー設定**: `~/.config/opencode/oh-my-opencode.json[c]`
|
|
288
201
|
2. **プロジェクト設定**: `<project>/.opencode/oh-my-opencode.json[c]`
|
|
289
202
|
|
|
290
|
-
プロジェクト設定がユーザー設定をオーバーライドする。
|
|
291
|
-
|
|
292
203
|
### 設定スキーマ
|
|
293
204
|
|
|
294
205
|
```jsonc
|
|
295
206
|
{
|
|
296
|
-
//
|
|
207
|
+
// エージェントオーバーライド
|
|
297
208
|
"agents": {
|
|
298
209
|
"sisyphus": { "model": "openai/gpt-5.2" },
|
|
299
|
-
"oracle": { "model": "zai-coding-plan/glm-5" }
|
|
300
|
-
"explore": { "model": "openai/gpt-5.3-codex-spark" }
|
|
210
|
+
"oracle": { "model": "zai-coding-plan/glm-5" }
|
|
301
211
|
},
|
|
302
212
|
|
|
303
|
-
// カテゴリ
|
|
213
|
+
// カテゴリ
|
|
304
214
|
"categories": {
|
|
305
215
|
"quick": { "model": "openai/gpt-4o-mini" }
|
|
306
216
|
},
|
|
307
217
|
|
|
218
|
+
// Fragment Injection
|
|
219
|
+
"fragments": {
|
|
220
|
+
"hephaestus": [".opencode/fragments/strict.md"]
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// Per-model Renderer
|
|
224
|
+
"prompt_renderer": {
|
|
225
|
+
"default": "markdown",
|
|
226
|
+
"model_overrides": {}
|
|
227
|
+
},
|
|
228
|
+
|
|
308
229
|
// 無効化
|
|
309
230
|
"disabled_agents": [],
|
|
310
|
-
"disabled_hooks": [
|
|
311
|
-
|
|
312
|
-
|
|
231
|
+
"disabled_hooks": [], // keyword-detector, rules-injector, context-injector,
|
|
232
|
+
// fragment-injector, prompt-renderer, comment-checker,
|
|
233
|
+
// token-truncation, todo-enforcer, session-compaction
|
|
234
|
+
"disabled_tools": [], // spawn_agent, ralph_loop, cancel_ralph,
|
|
235
|
+
// batch_read, ledger_save, ledger_load, ast_search
|
|
236
|
+
"disabled_mcps": [], // context7
|
|
237
|
+
|
|
238
|
+
// Built-in Agent Demotion (default: true)
|
|
239
|
+
"demote_builtin": true,
|
|
313
240
|
|
|
314
241
|
// 並列制御
|
|
315
242
|
"background_task": {
|
|
316
243
|
"defaultConcurrency": 3,
|
|
317
|
-
"providerConcurrency": {
|
|
318
|
-
|
|
319
|
-
"zai-coding-plan": 10
|
|
320
|
-
},
|
|
321
|
-
"modelConcurrency": {
|
|
322
|
-
"openai/gpt-5.2": 2
|
|
323
|
-
}
|
|
244
|
+
"providerConcurrency": { "openai": 8 },
|
|
245
|
+
"modelConcurrency": { "openai/gpt-5.2": 2 }
|
|
324
246
|
},
|
|
325
247
|
|
|
326
|
-
//
|
|
327
|
-
"
|
|
328
|
-
"maxRatio": 0.3,
|
|
329
|
-
"slopThreshold": 3
|
|
330
|
-
},
|
|
248
|
+
// Token Truncation
|
|
249
|
+
"token_truncation": { "maxChars": 30000 },
|
|
331
250
|
|
|
332
|
-
//
|
|
333
|
-
"
|
|
334
|
-
|
|
335
|
-
|
|
251
|
+
// Comment Checker
|
|
252
|
+
"comment_checker": { "maxRatio": 0.3, "slopThreshold": 3 },
|
|
253
|
+
|
|
254
|
+
// Todo Enforcer
|
|
255
|
+
"todo_enforcer": { "maxEnforcements": 5 },
|
|
336
256
|
|
|
337
257
|
// MCP API キー
|
|
338
|
-
"mcp_api_keys": {
|
|
339
|
-
"context7": "ctx7sk-..."
|
|
340
|
-
}
|
|
258
|
+
"mcp_api_keys": { "context7": "ctx7sk-..." }
|
|
341
259
|
}
|
|
342
260
|
```
|
|
343
261
|
|
|
344
|
-
### エージェントオーバーライドで使えるフィールド
|
|
345
|
-
|
|
346
|
-
| フィールド | 型 | 説明 |
|
|
347
|
-
|-----------|---|------|
|
|
348
|
-
| `model` | string | 使用モデル (provider/model 形式) |
|
|
349
|
-
| `description` | string | エージェントの説明 |
|
|
350
|
-
| `prompt` | string | システムプロンプト全体を差し替え |
|
|
351
|
-
| `prompt_append` | string | プロンプト末尾に追記 |
|
|
352
|
-
| `disable` | boolean | 個別に無効化 |
|
|
353
|
-
| `mode` | `"subagent"` / `"primary"` / `"all"` | 実行モード |
|
|
354
|
-
| `maxTokens` | number | 最大出力トークン |
|
|
355
|
-
| `thinking` | `{ type, budgetTokens }` | 思考トークン設定 |
|
|
356
|
-
| `reasoningEffort` | `"low"` / `"medium"` / `"high"` / `"xhigh"` | 推論レベル |
|
|
357
|
-
| `temperature` | number (0-2) | サンプリング温度 |
|
|
358
|
-
| `top_p` | number (0-1) | Top-p |
|
|
359
|
-
| `category` | string | デフォルトカテゴリ |
|
|
360
|
-
| `variant` | string | モデルバリアント |
|
|
361
|
-
|
|
362
|
-
## フック再帰防止
|
|
363
|
-
|
|
364
|
-
spawn_agent / ralph_loop で生成されたエフェメラルセッションは `internalSessions` Set で追跡される。`chat.message`、`system.transform`、`tool.execute.after`、`event` の各フックは内部セッションに対してスキップされ、キーワード検知・ルール注入・Comment Checker・Todo Enforcer がサブエージェントに二重適用されることを防ぐ。
|
|
365
|
-
|
|
366
262
|
## プロジェクト構成
|
|
367
263
|
|
|
368
264
|
```
|
|
369
265
|
opencode-ultra/
|
|
370
266
|
├── src/
|
|
371
|
-
│ ├── index.ts
|
|
372
|
-
│ ├── config.ts
|
|
267
|
+
│ ├── index.ts # プラグインエントリポイント
|
|
268
|
+
│ ├── config.ts # 設定ロード・マージ・バリデーション
|
|
373
269
|
│ ├── agents/
|
|
374
|
-
│ │ ├── types.ts
|
|
375
|
-
│ │ └── index.ts
|
|
270
|
+
│ │ ├── types.ts # AgentDef 型定義
|
|
271
|
+
│ │ └── index.ts # ビルトインエージェント + 動的プロンプト生成
|
|
376
272
|
│ ├── hooks/
|
|
377
|
-
│ │ ├── keyword-detector.ts
|
|
378
|
-
│ │ ├── rules-injector.ts
|
|
379
|
-
│ │ ├──
|
|
380
|
-
│ │
|
|
273
|
+
│ │ ├── keyword-detector.ts # ultrawork/search/analyze/think 検知
|
|
274
|
+
│ │ ├── rules-injector.ts # rules.md + ARCHITECTURE.md + CODE_STYLE.md 注入
|
|
275
|
+
│ │ ├── fragment-injector.ts # エージェント毎の断片注入
|
|
276
|
+
│ │ ├── prompt-renderer.ts # モデル別フォーマット変換
|
|
277
|
+
│ │ ├── comment-checker.ts # AI スロップコメント検知
|
|
278
|
+
│ │ ├── token-truncation.ts # ツール出力の賢い圧縮
|
|
279
|
+
│ │ ├── todo-enforcer.ts # 未完了 TODO 強制継続
|
|
280
|
+
│ │ └── session-compaction.ts # セッション圧縮サマリ生成
|
|
381
281
|
│ ├── tools/
|
|
382
|
-
│ │ ├── spawn-agent.ts
|
|
383
|
-
│ │
|
|
384
|
-
│ ├──
|
|
385
|
-
│ │ ├──
|
|
386
|
-
│ │
|
|
387
|
-
│
|
|
388
|
-
│ ├── categories/
|
|
389
|
-
│
|
|
390
|
-
│
|
|
391
|
-
|
|
392
|
-
│ │ └── index.ts # MCP 登録ロジック
|
|
393
|
-
│ └── shared/
|
|
394
|
-
│ ├── paths.ts # XDG パス解決
|
|
395
|
-
│ ├── jsonc.ts # JSONC パーサー
|
|
396
|
-
│ ├── log.ts # [opencode-ultra] プレフィックスログ
|
|
397
|
-
│ └── index.ts # バレルエクスポート
|
|
398
|
-
├── __test__/ # Bun テスト (70 tests)
|
|
282
|
+
│ │ ├── spawn-agent.ts # 並列エージェント実行
|
|
283
|
+
│ │ ├── ralph-loop.ts # 自律ループ実行
|
|
284
|
+
│ │ ├── batch-read.ts # 複数ファイル並列読み込み
|
|
285
|
+
│ │ ├── continuity-ledger.ts # セッション間文脈継続
|
|
286
|
+
│ │ └── ast-search.ts # AST-aware コード検索
|
|
287
|
+
│ ├── concurrency/ # Semaphore + ConcurrencyPool
|
|
288
|
+
│ ├── categories/ # ビルトインカテゴリ
|
|
289
|
+
│ ├── mcp/ # MCP サーバー登録
|
|
290
|
+
│ └── shared/ # ユーティリティ
|
|
291
|
+
├── __test__/ # Bun テスト (92 tests, 20 files)
|
|
399
292
|
├── package.json
|
|
400
293
|
├── tsconfig.json
|
|
401
294
|
└── .gitignore
|
|
@@ -404,13 +297,8 @@ opencode-ultra/
|
|
|
404
297
|
## ビルド・テスト
|
|
405
298
|
|
|
406
299
|
```bash
|
|
407
|
-
# 依存インストール
|
|
408
300
|
bun install
|
|
409
|
-
|
|
410
|
-
# ビルド
|
|
411
|
-
bun build src/index.ts --outdir dist --target bun --format esm
|
|
412
|
-
|
|
413
|
-
# テスト
|
|
301
|
+
bun run build
|
|
414
302
|
bun test
|
|
415
303
|
```
|
|
416
304
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import type { ModelRef } from "../shared/types";
|
|
1
2
|
export type PromptFormat = "markdown" | "xml" | "json";
|
|
2
3
|
export type PromptRendererConfig = {
|
|
3
4
|
default: PromptFormat;
|
|
4
5
|
model_overrides: Record<string, PromptFormat>;
|
|
5
6
|
};
|
|
6
|
-
export declare function resolvePromptFormat(inputModel:
|
|
7
|
+
export declare function resolvePromptFormat(inputModel: ModelRef | undefined, config?: Partial<PromptRendererConfig>): PromptFormat;
|
|
7
8
|
export declare function formatSystemSection(format: PromptFormat, content: string): string;
|
|
8
9
|
export declare function createPromptRendererHook(internalSessions: Set<string>, config?: Partial<PromptRendererConfig>): (input: {
|
|
9
10
|
sessionID?: string;
|
|
10
|
-
model?:
|
|
11
|
+
model?: ModelRef;
|
|
11
12
|
}, output: {
|
|
12
13
|
system: string[];
|
|
13
14
|
}) => void;
|
|
@@ -11,7 +11,7 @@ type MessageLike = {
|
|
|
11
11
|
};
|
|
12
12
|
export declare function buildSessionSummary(messages: MessageLike[]): string;
|
|
13
13
|
export declare function createSessionCompactionHook(ctx: {
|
|
14
|
-
client:
|
|
14
|
+
client: Pick<import("../shared/types").ExtendedClient, "session">;
|
|
15
15
|
}, internalSessions: Set<string>): (input: {
|
|
16
16
|
sessionID: string;
|
|
17
17
|
}, output: {
|
package/dist/index.js
CHANGED
|
@@ -14369,6 +14369,49 @@ function log(message, data) {
|
|
|
14369
14369
|
console.error(`${PREFIX} ${message}`);
|
|
14370
14370
|
}
|
|
14371
14371
|
}
|
|
14372
|
+
// src/shared/ttl-map.ts
|
|
14373
|
+
class TtlMap {
|
|
14374
|
+
map = new Map;
|
|
14375
|
+
maxSize;
|
|
14376
|
+
ttlMs;
|
|
14377
|
+
constructor(opts = {}) {
|
|
14378
|
+
this.maxSize = opts.maxSize ?? 1000;
|
|
14379
|
+
this.ttlMs = opts.ttlMs ?? 0;
|
|
14380
|
+
}
|
|
14381
|
+
get(key) {
|
|
14382
|
+
const entry = this.map.get(key);
|
|
14383
|
+
if (!entry)
|
|
14384
|
+
return;
|
|
14385
|
+
if (this.ttlMs > 0 && Date.now() - entry.createdAt > this.ttlMs) {
|
|
14386
|
+
this.map.delete(key);
|
|
14387
|
+
return;
|
|
14388
|
+
}
|
|
14389
|
+
return entry.value;
|
|
14390
|
+
}
|
|
14391
|
+
set(key, value) {
|
|
14392
|
+
if (this.map.has(key)) {
|
|
14393
|
+
this.map.delete(key);
|
|
14394
|
+
}
|
|
14395
|
+
if (this.map.size >= this.maxSize) {
|
|
14396
|
+
const oldest = this.map.keys().next().value;
|
|
14397
|
+
if (oldest !== undefined)
|
|
14398
|
+
this.map.delete(oldest);
|
|
14399
|
+
}
|
|
14400
|
+
this.map.set(key, { value, createdAt: Date.now() });
|
|
14401
|
+
}
|
|
14402
|
+
has(key) {
|
|
14403
|
+
return this.get(key) !== undefined;
|
|
14404
|
+
}
|
|
14405
|
+
delete(key) {
|
|
14406
|
+
return this.map.delete(key);
|
|
14407
|
+
}
|
|
14408
|
+
get size() {
|
|
14409
|
+
return this.map.size;
|
|
14410
|
+
}
|
|
14411
|
+
clear() {
|
|
14412
|
+
this.map.clear();
|
|
14413
|
+
}
|
|
14414
|
+
}
|
|
14372
14415
|
// src/config.ts
|
|
14373
14416
|
var AgentOverrideSchema = exports_external.object({
|
|
14374
14417
|
model: exports_external.string().optional(),
|
|
@@ -14824,7 +14867,7 @@ var THINK_MESSAGE = `Extended thinking enabled. Take your time to reason thoroug
|
|
|
14824
14867
|
// src/hooks/rules-injector.ts
|
|
14825
14868
|
import * as fs2 from "fs";
|
|
14826
14869
|
import * as path2 from "path";
|
|
14827
|
-
var cache = new
|
|
14870
|
+
var cache = new TtlMap({ maxSize: 50, ttlMs: 10 * 60 * 1000 });
|
|
14828
14871
|
function loadCachedFile(filePath) {
|
|
14829
14872
|
try {
|
|
14830
14873
|
if (!fs2.existsSync(filePath))
|
|
@@ -14882,7 +14925,7 @@ function loadCodeStyle(projectDir) {
|
|
|
14882
14925
|
// src/hooks/fragment-injector.ts
|
|
14883
14926
|
import * as fs3 from "fs";
|
|
14884
14927
|
import * as path3 from "path";
|
|
14885
|
-
var cache2 = new
|
|
14928
|
+
var cache2 = new TtlMap({ maxSize: 100, ttlMs: 10 * 60 * 1000 });
|
|
14886
14929
|
async function readCached(absPath) {
|
|
14887
14930
|
try {
|
|
14888
14931
|
const st = await fs3.promises.stat(absPath);
|
|
@@ -27322,7 +27365,8 @@ function resolveCategory(categoryName, configCategories) {
|
|
|
27322
27365
|
|
|
27323
27366
|
// src/tools/spawn-agent.ts
|
|
27324
27367
|
function showToast(ctx, title, message, variant = "info") {
|
|
27325
|
-
ctx.client
|
|
27368
|
+
const client = ctx.client;
|
|
27369
|
+
client.tui?.showToast?.({
|
|
27326
27370
|
body: { title, message, variant, duration: 2000 }
|
|
27327
27371
|
})?.catch?.(() => {});
|
|
27328
27372
|
}
|
|
@@ -27427,6 +27471,18 @@ spawn_agent({
|
|
|
27427
27471
|
if (!agents || agents.length === 0) {
|
|
27428
27472
|
return "No agents specified.";
|
|
27429
27473
|
}
|
|
27474
|
+
for (let i = 0;i < agents.length; i++) {
|
|
27475
|
+
const t = agents[i];
|
|
27476
|
+
if (!t.agent || typeof t.agent !== "string" || t.agent.trim().length === 0) {
|
|
27477
|
+
return `Error: agents[${i}].agent is required (non-empty string)`;
|
|
27478
|
+
}
|
|
27479
|
+
if (!t.prompt || typeof t.prompt !== "string" || t.prompt.trim().length === 0) {
|
|
27480
|
+
return `Error: agents[${i}].prompt is required (non-empty string)`;
|
|
27481
|
+
}
|
|
27482
|
+
if (!t.description || typeof t.description !== "string") {
|
|
27483
|
+
return `Error: agents[${i}].description is required`;
|
|
27484
|
+
}
|
|
27485
|
+
}
|
|
27430
27486
|
const agentNames = agents.map((a) => a.agent);
|
|
27431
27487
|
log("spawn_agent", { count: agents.length, agents: agentNames });
|
|
27432
27488
|
showToast(ctx, "spawn_agent", `${agents.length} agents: ${agentNames.join(", ")}`);
|
|
@@ -28190,6 +28246,8 @@ class Semaphore {
|
|
|
28190
28246
|
});
|
|
28191
28247
|
}
|
|
28192
28248
|
release() {
|
|
28249
|
+
if (this.active <= 0)
|
|
28250
|
+
return;
|
|
28193
28251
|
this.active--;
|
|
28194
28252
|
const next = this.queue.shift();
|
|
28195
28253
|
if (next)
|
|
@@ -28279,7 +28337,8 @@ async function registerMcps(ctx, disabled, apiKeys) {
|
|
|
28279
28337
|
if (config3.env) {
|
|
28280
28338
|
body.env = config3.env;
|
|
28281
28339
|
}
|
|
28282
|
-
|
|
28340
|
+
const client = ctx.client;
|
|
28341
|
+
await client.mcp?.add?.({ body });
|
|
28283
28342
|
log(`MCP ${name} registered${apiKey ? " (with API key)" : ""}`);
|
|
28284
28343
|
} catch (err) {
|
|
28285
28344
|
log(`MCP ${name} registration failed: ${err}`);
|
|
@@ -28313,10 +28372,10 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
28313
28372
|
const todoEnforcer = createTodoEnforcer(ctx, internalSessions, pluginConfig.todo_enforcer?.maxEnforcements);
|
|
28314
28373
|
const commentCheckerHook = createCommentCheckerHook(internalSessions, pluginConfig.comment_checker?.maxRatio, pluginConfig.comment_checker?.slopThreshold);
|
|
28315
28374
|
const tokenTruncationHook = createTokenTruncationHook(internalSessions, pluginConfig.token_truncation?.maxChars);
|
|
28316
|
-
const sessionCompactionHook = createSessionCompactionHook(ctx, internalSessions);
|
|
28375
|
+
const sessionCompactionHook = createSessionCompactionHook({ client: ctx.client }, internalSessions);
|
|
28317
28376
|
const fragmentInjector = createFragmentInjector(ctx, internalSessions, pluginConfig.fragments);
|
|
28318
28377
|
const promptRendererHook = createPromptRendererHook(internalSessions, pluginConfig.prompt_renderer);
|
|
28319
|
-
const pendingKeywords = new
|
|
28378
|
+
const pendingKeywords = new TtlMap({ maxSize: 200, ttlMs: 5 * 60 * 1000 });
|
|
28320
28379
|
log("Config loaded", {
|
|
28321
28380
|
agentCount: Object.keys(agents).length,
|
|
28322
28381
|
disabledHooks: [...disabledHooks],
|
|
@@ -28397,7 +28456,8 @@ var OpenCodeUltra = async (ctx) => {
|
|
|
28397
28456
|
if (output.message.variant === undefined) {
|
|
28398
28457
|
output.message.variant = "max";
|
|
28399
28458
|
}
|
|
28400
|
-
ctx.client
|
|
28459
|
+
const client = ctx.client;
|
|
28460
|
+
client.tui?.showToast?.({
|
|
28401
28461
|
body: {
|
|
28402
28462
|
title: "ULTRAWORK MODE",
|
|
28403
28463
|
message: "Maximum precision engaged. All agents at your disposal.",
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple Map with max size eviction (LRU-like: evicts oldest entry when full).
|
|
3
|
+
* Optionally supports TTL-based expiry.
|
|
4
|
+
*/
|
|
5
|
+
export declare class TtlMap<K, V> {
|
|
6
|
+
private map;
|
|
7
|
+
private maxSize;
|
|
8
|
+
private ttlMs;
|
|
9
|
+
constructor(opts?: {
|
|
10
|
+
maxSize?: number;
|
|
11
|
+
ttlMs?: number;
|
|
12
|
+
});
|
|
13
|
+
get(key: K): V | undefined;
|
|
14
|
+
set(key: K, value: V): void;
|
|
15
|
+
has(key: K): boolean;
|
|
16
|
+
delete(key: K): boolean;
|
|
17
|
+
get size(): number;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extended type definitions for OpenCode client APIs that are not (yet) in @opencode-ai/sdk types.
|
|
3
|
+
* These describe the actual runtime shape observed in OpenCode 1.2.x.
|
|
4
|
+
*/
|
|
5
|
+
/** Model identifier as passed through hook input */
|
|
6
|
+
export interface ModelRef {
|
|
7
|
+
providerID?: string;
|
|
8
|
+
modelID?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Toast notification (TUI-only, non-standard) */
|
|
11
|
+
export interface ToastOptions {
|
|
12
|
+
body: {
|
|
13
|
+
title: string;
|
|
14
|
+
message: string;
|
|
15
|
+
variant?: "info" | "success" | "warning" | "error";
|
|
16
|
+
duration?: number;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Extended OpenCode client with optional TUI and MCP methods */
|
|
20
|
+
export interface ExtendedClient {
|
|
21
|
+
session: {
|
|
22
|
+
create: (opts: {
|
|
23
|
+
body: Record<string, unknown>;
|
|
24
|
+
query?: {
|
|
25
|
+
directory?: string;
|
|
26
|
+
};
|
|
27
|
+
}) => Promise<{
|
|
28
|
+
data?: {
|
|
29
|
+
id?: string;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
prompt: (opts: {
|
|
33
|
+
path: {
|
|
34
|
+
id: string;
|
|
35
|
+
};
|
|
36
|
+
body: {
|
|
37
|
+
parts: Array<{
|
|
38
|
+
type: string;
|
|
39
|
+
text: string;
|
|
40
|
+
}>;
|
|
41
|
+
agent?: string;
|
|
42
|
+
};
|
|
43
|
+
query?: {
|
|
44
|
+
directory?: string;
|
|
45
|
+
};
|
|
46
|
+
}) => Promise<unknown>;
|
|
47
|
+
messages: (opts: {
|
|
48
|
+
path: {
|
|
49
|
+
id: string;
|
|
50
|
+
};
|
|
51
|
+
query?: {
|
|
52
|
+
directory?: string;
|
|
53
|
+
};
|
|
54
|
+
}) => Promise<{
|
|
55
|
+
data?: Array<{
|
|
56
|
+
info?: {
|
|
57
|
+
role?: string;
|
|
58
|
+
};
|
|
59
|
+
parts?: Array<{
|
|
60
|
+
type: string;
|
|
61
|
+
text?: string;
|
|
62
|
+
}>;
|
|
63
|
+
}>;
|
|
64
|
+
}>;
|
|
65
|
+
delete: (opts: {
|
|
66
|
+
path: {
|
|
67
|
+
id: string;
|
|
68
|
+
};
|
|
69
|
+
query?: {
|
|
70
|
+
directory?: string;
|
|
71
|
+
};
|
|
72
|
+
}) => Promise<unknown>;
|
|
73
|
+
listMessages?: (opts: {
|
|
74
|
+
id: string;
|
|
75
|
+
}) => Promise<{
|
|
76
|
+
messages?: unknown[];
|
|
77
|
+
} | unknown[]>;
|
|
78
|
+
};
|
|
79
|
+
tui?: {
|
|
80
|
+
showToast?: (opts: ToastOptions) => Promise<void>;
|
|
81
|
+
};
|
|
82
|
+
mcp?: {
|
|
83
|
+
add?: (opts: {
|
|
84
|
+
body: Record<string, unknown>;
|
|
85
|
+
}) => Promise<void>;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** System transform hook input */
|
|
89
|
+
export interface SystemTransformInput {
|
|
90
|
+
sessionID?: string;
|
|
91
|
+
model?: ModelRef;
|
|
92
|
+
agent?: string;
|
|
93
|
+
}
|
package/package.json
CHANGED