commit
652630eeca
@ -4,12 +4,13 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [0.13.0-alpha.1] - 2026-02-13
|
||||
## [0.13.0] - 2026-02-13
|
||||
|
||||
### Added
|
||||
|
||||
- **Team Leader ムーブメント**: ムーブメント内でチームリーダーエージェントがタスクを動的にサブタスク(Part)へ分解し、複数のパートエージェントを並列実行する新しいムーブメントタイプ — `team_leader` 設定(persona, maxParts, timeoutMs, partPersona, partEdit, partPermissionMode)をサポート (#244)
|
||||
- **構造化出力(Structured Output)**: エージェント呼び出しに JSON Schema ベースの構造化出力を導入 — タスク分解(decomposition)、ルール評価(evaluation)、ステータス判定(judgment)の3つのスキーマを `builtins/schemas/` に追加。Claude / Codex 両プロバイダーで対応 (#257)
|
||||
- **`provider_options` ピースレベル設定**: ピース全体(`piece_config.provider_options`)および個別ムーブメントにプロバイダー固有オプション(`codex.network_access`、`opencode.network_access`)を設定可能に — 全ビルトインピースに Codex/OpenCode のネットワークアクセスを有効化
|
||||
- **`backend` ビルトインピース**: バックエンド開発特化のピースを新規追加 — バックエンド、セキュリティ、QA の並列専門家レビュー対応
|
||||
- **`backend-cqrs` ビルトインピース**: CQRS+ES 特化のバックエンド開発ピースを新規追加 — CQRS+ES、セキュリティ、QA の並列専門家レビュー対応
|
||||
- **AbortSignal によるパートタイムアウト**: Team Leader のパート実行にタイムアウト制御と親シグナル連動の AbortSignal を追加
|
||||
@ -21,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- **Phase 3 判定ロジックの刷新**: `JudgmentDetector` / `FallbackStrategy` を廃止し、構造化出力ベースの `status-judgment-phase.ts` に統合。判定の安定性と保守性を向上 (#257)
|
||||
- **Report フェーズのリトライ改善**: Report Phase(Phase 2)が失敗した場合、新規セッションで自動リトライするよう改善 (#245)
|
||||
- **Ctrl+C シャットダウンの統一**: `sigintHandler.ts` を廃止し、`ShutdownManager` に統合 — グレースフルシャットダウン → タイムアウト → 強制終了の3段階制御を全プロバイダーで共通化 (#237)
|
||||
- **スコープ外削除の防止ガードレール**: coder ペルソナにタスク指示書の範囲外の削除・構造変更を禁止するルールを追加。planner ペルソナにスコープ規律と参照資料の優先順位を追加
|
||||
- フロントエンドナレッジにデザイントークンとテーマスコープのガイダンスを追加
|
||||
- アーキテクチャナレッジの改善(en/ja 両対応)
|
||||
|
||||
@ -39,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- AbortSignal のユニットテスト追加(abort-signal, claude-executor-abort-signal, claude-provider-abort-signal)
|
||||
- Report Phase リトライのユニットテスト追加(report-phase-retry)
|
||||
- パブリック API エクスポートのユニットテスト追加(public-api-exports)
|
||||
- provider_options 関連のテスト追加(provider-options-piece-parser, models, opencode-types)
|
||||
- E2E テストの大幅拡充: cycle-detection, model-override, multi-step-sequential, pipeline-local-repo, report-file-output, run-sigint-graceful, session-log, structured-output, task-status-persistence
|
||||
- E2E テストヘルパーのリファクタリング(共通 setup 関数の抽出)
|
||||
- `judgment/` ディレクトリ(JudgmentDetector, FallbackStrategy)を削除
|
||||
|
||||
12
README.md
12
README.md
@ -803,10 +803,22 @@ Special `next` values: `COMPLETE` (success), `ABORT` (failure)
|
||||
| `provider` | - | Override provider for this movement (`claude`, `codex`, or `opencode`) |
|
||||
| `model` | - | Override model for this movement |
|
||||
| `permission_mode` | - | Permission mode: `readonly`, `edit`, `full` (provider-independent) |
|
||||
| `provider_options` | - | Provider-specific options (e.g. `codex.network_access`, `opencode.network_access`) |
|
||||
| `output_contracts` | - | Output contract definitions for report files |
|
||||
| `quality_gates` | - | AI directives for movement completion requirements |
|
||||
| `mcp_servers` | - | MCP (Model Context Protocol) server configuration (stdio/SSE/HTTP) |
|
||||
|
||||
Piece-level defaults can be set with `piece_config.provider_options`, and movement-level `provider_options` overrides them.
|
||||
|
||||
```yaml
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
```
|
||||
|
||||
## API Usage Example
|
||||
|
||||
```typescript
|
||||
|
||||
@ -35,3 +35,4 @@ You are the implementer. Focus on implementation, not design decisions.
|
||||
- Adding backward compatibility or legacy support without being asked → Absolutely prohibited
|
||||
- Leaving replaced code/exports after refactoring → Prohibited (remove unless explicitly told to keep)
|
||||
- Layering workarounds that bypass safety mechanisms on top of a root cause fix → Prohibited
|
||||
- Deleting existing features or structural changes not in the task order as a "side effect" → Prohibited (report even if included in the plan, when there's no basis in the task order for large-scale deletions)
|
||||
|
||||
@ -50,6 +50,23 @@ Judge from a big-picture perspective to avoid "missing the forest for the trees.
|
||||
| Non-functional Requirements | Are performance, security, etc. met? |
|
||||
| Scope | Is there scope creep beyond requirements? |
|
||||
|
||||
### Scope Creep Detection (Deletions are Critical)
|
||||
|
||||
File **deletions** and removal of existing features are the most dangerous form of scope creep.
|
||||
Additions can be reverted, but restoring deleted flows is difficult.
|
||||
|
||||
**Required steps:**
|
||||
1. List all deleted files (D) and deleted classes/methods/endpoints from the diff
|
||||
2. Cross-reference each deletion against the task order to find its justification
|
||||
3. REJECT any deletion that has no basis in the task order
|
||||
|
||||
**Typical scope creep patterns:**
|
||||
- A "change statuses" task includes wholesale deletion of Sagas or endpoints
|
||||
- A "UI fix" task includes structural changes to backend domain models
|
||||
- A "display change" task rewrites business logic flows
|
||||
|
||||
Even if reviewers approved a deletion as "sound design," REJECT it if it's outside the task order scope.
|
||||
|
||||
### 3. Risk Assessment
|
||||
|
||||
**Risk Matrix:**
|
||||
|
||||
@ -86,11 +86,22 @@ Based on investigation and design, determine the implementation direction:
|
||||
- Points to be careful about
|
||||
- Spec constraints
|
||||
|
||||
## Scope Discipline
|
||||
|
||||
Only plan work that is explicitly stated in the task order. Do not include implicit "improvements."
|
||||
|
||||
**Deletion criteria:**
|
||||
- **Code made newly unused by this task's changes** → OK to plan deletion (e.g., renamed old variable)
|
||||
- **Existing features, flows, endpoints, Sagas, events** → Do NOT delete unless explicitly instructed in the task order
|
||||
|
||||
"Change statuses to 5 values" means "rewrite enum values," NOT "delete flows that seem unnecessary."
|
||||
Do not over-interpret the task order. Plan only what is written.
|
||||
|
||||
## Design Principles
|
||||
|
||||
**Backward Compatibility:**
|
||||
- Do not include backward compatibility code unless explicitly instructed
|
||||
- Plan to delete things that are unused
|
||||
- Delete code that was made newly unused by this task's changes
|
||||
|
||||
**Don't Generate Unnecessary Code:**
|
||||
- Don't plan "just in case" code, future fields, or unused methods
|
||||
|
||||
@ -100,6 +100,21 @@ Check:
|
||||
|
||||
**REJECT if spec violations are found.** Don't assume "probably correct"—actually read and cross-reference the specs.
|
||||
|
||||
### Scope Creep Detection (Deletions are Critical)
|
||||
|
||||
File **deletions** and removal of existing features are the most dangerous form of scope creep.
|
||||
Additions can be reverted, but restoring deleted flows is difficult.
|
||||
|
||||
**Required steps:**
|
||||
1. List all deleted files (D) and deleted classes/methods/endpoints from the diff
|
||||
2. Cross-reference each deletion against the task order to find its justification
|
||||
3. REJECT any deletion that has no basis in the task order
|
||||
|
||||
**Typical scope creep patterns:**
|
||||
- A "change statuses" task includes wholesale deletion of Sagas or endpoints
|
||||
- A "UI fix" task includes structural changes to backend domain models
|
||||
- A "display change" task rewrites business logic flows
|
||||
|
||||
### 8. Piece Overall Review
|
||||
|
||||
**Check all reports in the report directory and verify overall piece consistency.**
|
||||
@ -115,7 +130,7 @@ Check:
|
||||
| Plan-implementation gap | REJECT - Request plan revision or implementation fix |
|
||||
| Unaddressed review feedback | REJECT - Point out specific unaddressed items |
|
||||
| Deviation from original purpose | REJECT - Request return to objective |
|
||||
| Scope creep | Record only - Address in next task |
|
||||
| Scope creep | REJECT - Deletions outside task order must be reverted |
|
||||
|
||||
### 9. Improvement Suggestion Check
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: backend-cqrs
|
||||
description: CQRS+ES, Security, QA Expert Review
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: backend
|
||||
description: Backend, Security, QA Expert Review
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: coding
|
||||
description: Lightweight development piece with planning and parallel reviews (plan -> implement -> parallel review -> complete)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: compound-eye
|
||||
description: Multi-model review - send the same instruction to Claude and Codex simultaneously, synthesize both responses
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: evaluate
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: default
|
||||
description: Standard development piece with planning and specialized reviews
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
loop_monitors:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-test
|
||||
description: E2E test focused piece (E2E analysis → E2E implementation → review → fix)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: plan_test
|
||||
loop_monitors:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: expert-cqrs
|
||||
description: CQRS+ES, Frontend, Security, QA Expert Review
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: expert
|
||||
description: Architecture, Frontend, Security, QA Expert Review
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: frontend
|
||||
description: Frontend, Security, QA Expert Review
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: magi
|
||||
description: MAGI Deliberation System - Analyze from 3 perspectives and decide by majority
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 5
|
||||
initial_movement: melchior
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: minimal
|
||||
description: Minimal development piece (implement -> parallel review -> fix if needed -> complete)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: implement
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: passthrough
|
||||
description: Single-agent thin wrapper. Pass task directly to coder as-is.
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: execute
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: research
|
||||
description: Research piece - autonomously executes research without asking questions
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: review-fix-minimal
|
||||
description: Review and fix piece for existing code (starts with review, no implementation)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: reviewers
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: review-only
|
||||
description: Review-only piece - reviews code without making edits
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: structural-reform
|
||||
description: Full project review and structural reform - iterative codebase restructuring with staged file splits
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 50
|
||||
initial_movement: review
|
||||
loop_monitors:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: unit-test
|
||||
description: Unit test focused piece (test analysis → test implementation → review → fix)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: plan_test
|
||||
loop_monitors:
|
||||
|
||||
@ -11,8 +11,14 @@
|
||||
小規模タスクの場合は設計セクションを省略してください。
|
||||
|
||||
**やること:**
|
||||
1. タスクの要件を理解する
|
||||
2. コードを調査して不明点を解決する
|
||||
3. 影響範囲を特定する
|
||||
4. ファイル構成・設計パターンを決定する(必要な場合)
|
||||
5. 実装アプローチを決める
|
||||
1. **参照資料の読み込み(必須・最初に実行)**
|
||||
- タスク指示書の「参照資料」セクションに記載されたファイル・ディレクトリを **Read/Glob で実際に開いて内容を確認する**
|
||||
- ディレクトリが指定されている場合は中身を列挙し、該当ファイルを特定してから読む
|
||||
- 参照資料が存在しない・見つからない場合はその旨を報告し、推測で代用しない
|
||||
- **指示書に明記されていない別ファイルを「参照資料の代わり」として使うことは禁止**
|
||||
2. タスクの要件を理解する
|
||||
- 参照資料の内容と現在の実装を突き合わせて差分を特定する
|
||||
3. コードを調査して不明点を解決する
|
||||
4. 影響範囲を特定する
|
||||
5. ファイル構成・設計パターンを決定する(必要な場合)
|
||||
6. 実装アプローチを決める
|
||||
|
||||
@ -35,3 +35,4 @@
|
||||
- 後方互換・Legacy 対応を勝手に追加する → 絶対禁止
|
||||
- リファクタリングで置き換えたコード・エクスポートを残す → 禁止(明示的に残すよう指示されない限り削除する)
|
||||
- 根本原因を修正した上で安全機構を迂回するワークアラウンドを重ねる → 禁止
|
||||
- タスク指示書にない既存機能の削除・構造変更を「ついでに」行う → 禁止(計画に含まれていても、指示書に根拠がない大規模削除は報告する)
|
||||
|
||||
@ -43,6 +43,23 @@
|
||||
| 非機能要件 | パフォーマンス、セキュリティ等は満たされているか |
|
||||
| スコープ | 要求以上のことをしていないか(スコープクリープ) |
|
||||
|
||||
### スコープクリープの検出(削除は最重要チェック)
|
||||
|
||||
ファイルの**削除**と既存機能の**除去**はスコープクリープの最も危険な形態。
|
||||
追加は元に戻せるが、削除されたフローの復元は困難。
|
||||
|
||||
**必須手順:**
|
||||
1. 変更差分から削除されたファイル(D)と削除されたクラス・メソッド・エンドポイントを列挙する
|
||||
2. 各削除がタスク指示書のどの項目に対応するかを照合する
|
||||
3. タスク指示書に根拠がない削除は REJECT する
|
||||
|
||||
**典型的なスコープクリープ:**
|
||||
- 「ステータス変更」タスクで Saga やエンドポイントが丸ごと削除されている
|
||||
- 「UI修正」タスクでバックエンドのドメインモデルが構造変更されている
|
||||
- 「表示変更」タスクでビジネスロジックのフローが書き換えられている
|
||||
|
||||
レビュアーが「設計判断として妥当」と承認していても、タスク指示書のスコープ外であれば REJECT する。
|
||||
|
||||
### リスク評価
|
||||
|
||||
| 影響度\発生確率 | 低 | 中 | 高 |
|
||||
|
||||
@ -25,6 +25,17 @@
|
||||
|
||||
## ドメイン知識
|
||||
|
||||
### 情報の優先順位
|
||||
|
||||
タスク指示書に「参照資料」が指定されている場合、**そのファイルが唯一のソース・オブ・トゥルース**である。
|
||||
類似の情報を含む別ファイルが存在しても、指示書が指定したファイルを優先する。
|
||||
|
||||
| 優先度 | ソース |
|
||||
|--------|--------|
|
||||
| **最優先** | タスク指示書の「参照資料」で指定されたファイル |
|
||||
| 次点 | 実際のソースコード(現在の実装) |
|
||||
| 参考 | その他のドキュメント |
|
||||
|
||||
### 情報の裏取り(ファクトチェック)
|
||||
|
||||
分析で使用する情報は必ずソース・オブ・トゥルースで裏取りする。
|
||||
@ -35,6 +46,7 @@
|
||||
| 設定値・名前 | 実際の設定ファイル・定義ファイル |
|
||||
| API・コマンド | 実際の実装コード |
|
||||
| データ構造・型 | 型定義ファイル・スキーマ |
|
||||
| デザイン仕様 | タスク指示書で指定された参照ファイル |
|
||||
|
||||
### 構造設計
|
||||
|
||||
@ -52,8 +64,19 @@
|
||||
- 循環依存を作らない
|
||||
- 責務の分離(読み取りと書き込み、ビジネスロジックと IO)
|
||||
|
||||
### スコープ規律
|
||||
|
||||
タスク指示書に明記された作業のみを計画する。暗黙の「改善」を勝手に含めない。
|
||||
|
||||
**削除の判断基準:**
|
||||
- **今回の変更で新たに未使用になったコード** → 削除を計画してよい(例: リネームした旧変数)
|
||||
- **既存の機能・フロー・エンドポイント・Saga・イベント** → タスク指示書で明示的に指示されない限り削除しない
|
||||
|
||||
「ステータスを5つに変更する」は「enum値を書き換える」であり、「不要になったフローを丸ごと削除する」ではない。
|
||||
タスク指示書の文言を拡大解釈しない。書かれていることだけを計画する。
|
||||
|
||||
### 計画の原則
|
||||
|
||||
- 後方互換コードは計画に含めない(明示的な指示がない限り不要)
|
||||
- 使われていないものは削除する計画を立てる
|
||||
- 今回の変更で新たに未使用になったコードは削除する計画を立てる
|
||||
- TODO コメントで済ませる計画は立てない。今やるか、やらないか
|
||||
|
||||
@ -102,6 +102,21 @@
|
||||
|
||||
「機能的に無害」は免罪符ではない。修正コストがほぼゼロの指摘を「非ブロッキング」「次回タスク」に分類することは妥協である。レビュアーが発見し、数分以内に修正できる問題は今回のタスクで修正させる。
|
||||
|
||||
### スコープクリープの検出(削除は最重要チェック)
|
||||
|
||||
ファイルの**削除**と既存機能の**除去**はスコープクリープの最も危険な形態。
|
||||
追加は元に戻せるが、削除されたフローの復元は困難。
|
||||
|
||||
**必須手順:**
|
||||
1. 変更差分から削除されたファイル(D)と削除されたクラス・メソッド・エンドポイントを列挙する
|
||||
2. 各削除がタスク指示書のどの項目に対応するかを照合する
|
||||
3. タスク指示書に根拠がない削除は REJECT する
|
||||
|
||||
**典型的なスコープクリープ:**
|
||||
- 「ステータス変更」タスクで Saga やエンドポイントが丸ごと削除されている
|
||||
- 「UI修正」タスクでバックエンドのドメインモデルが構造変更されている
|
||||
- 「表示変更」タスクでビジネスロジックのフローが書き換えられている
|
||||
|
||||
### ピース全体の見直し
|
||||
|
||||
レポートディレクトリ内の全レポートを確認し、ピース全体の整合性をチェックする。
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: backend-cqrs
|
||||
description: CQRS+ES・セキュリティ・QA専門家レビュー
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: backend
|
||||
description: バックエンド・セキュリティ・QA専門家レビュー
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: coding
|
||||
description: Lightweight development piece with planning and parallel reviews (plan -> implement -> parallel review -> complete)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: compound-eye
|
||||
description: 複眼レビュー - 同じ指示を Claude と Codex に同時に投げ、両者の回答を統合する
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: evaluate
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: default
|
||||
description: Standard development piece with planning and specialized reviews
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
loop_monitors:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-test
|
||||
description: E2Eテスト追加に特化したピース(E2E分析→E2E実装→レビュー→修正)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: plan_test
|
||||
loop_monitors:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: expert-cqrs
|
||||
description: CQRS+ES・フロントエンド・セキュリティ・QA専門家レビュー
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: expert
|
||||
description: アーキテクチャ・フロントエンド・セキュリティ・QA専門家レビュー
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: frontend
|
||||
description: フロントエンド・セキュリティ・QA専門家レビュー
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 30
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: magi
|
||||
description: MAGI合議システム - 3つの観点から分析し多数決で判定
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 5
|
||||
initial_movement: melchior
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: minimal
|
||||
description: Minimal development piece (implement -> parallel review -> fix if needed -> complete)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: implement
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: passthrough
|
||||
description: Single-agent thin wrapper. Pass task directly to coder as-is.
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: execute
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: research
|
||||
description: 調査ピース - 質問せずに自律的に調査を実行
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: review-fix-minimal
|
||||
description: 既存コードのレビューと修正ピース(レビュー開始、実装なし)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: reviewers
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: review-only
|
||||
description: レビュー専用ピース - コードをレビューするだけで編集は行わない
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 10
|
||||
initial_movement: plan
|
||||
movements:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: structural-reform
|
||||
description: プロジェクト全体レビューと構造改革 - 段階的なファイル分割による反復的コードベース再構築
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 50
|
||||
initial_movement: review
|
||||
loop_monitors:
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: unit-test
|
||||
description: 単体テスト追加に特化したピース(テスト分析→テスト実装→レビュー→修正)
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: plan_test
|
||||
loop_monitors:
|
||||
|
||||
@ -803,10 +803,22 @@ rules:
|
||||
| `provider` | - | このムーブメントのプロバイダーを上書き(`claude`、`codex`、または`opencode`) |
|
||||
| `model` | - | このムーブメントのモデルを上書き |
|
||||
| `permission_mode` | - | パーミッションモード: `readonly`、`edit`、`full`(プロバイダー非依存) |
|
||||
| `provider_options` | - | プロバイダー固有オプション(例: `codex.network_access`、`opencode.network_access`) |
|
||||
| `output_contracts` | - | レポートファイルの出力契約定義 |
|
||||
| `quality_gates` | - | ムーブメント完了要件のAIディレクティブ |
|
||||
| `mcp_servers` | - | MCP(Model Context Protocol)サーバー設定(stdio/SSE/HTTP) |
|
||||
|
||||
ピース全体のデフォルトは `piece_config.provider_options` で設定でき、ムーブメント側 `provider_options` で上書きできます。
|
||||
|
||||
```yaml
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
```
|
||||
|
||||
## API使用例
|
||||
|
||||
```typescript
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-cycle-detect
|
||||
description: Piece with loop_monitors for cycle detection E2E testing
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 20
|
||||
initial_movement: review
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-mock-max-iter
|
||||
description: Piece with max_movements=2 that loops between two steps
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 2
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-mock-no-match
|
||||
description: Piece with a strict rule condition that will not match mock output
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 3
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-mock-single
|
||||
description: Minimal mock-only piece for CLI E2E
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 3
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-mock-slow-multi-step
|
||||
description: Multi-step mock piece to keep tasks in-flight long enough for SIGINT E2E
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 20
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-mock-two-step
|
||||
description: Two-step sequential piece for E2E testing
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 5
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-multi-step-parallel
|
||||
description: Multi-step piece with parallel sub-movements for E2E testing
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 10
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-report-judge
|
||||
description: E2E piece that exercises report + judge phases
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 3
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-simple
|
||||
description: Minimal E2E test piece
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 5
|
||||
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
name: e2e-structured-output
|
||||
description: E2E piece to verify structured output rule matching
|
||||
piece_config:
|
||||
provider_options:
|
||||
codex:
|
||||
network_access: true
|
||||
opencode:
|
||||
network_access: true
|
||||
|
||||
|
||||
max_movements: 5
|
||||
|
||||
|
||||
@ -123,6 +123,7 @@ export function createIsolatedEnv(): IsolatedEnv {
|
||||
TAKT_CONFIG_DIR: taktDir,
|
||||
GIT_CONFIG_GLOBAL: gitConfigPath,
|
||||
TAKT_NO_TTY: '1',
|
||||
TAKT_NOTIFY_WEBHOOK: undefined,
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "takt",
|
||||
"version": "0.13.0-alpha.1",
|
||||
"version": "0.13.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "takt",
|
||||
"version": "0.13.0-alpha.1",
|
||||
"version": "0.13.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "takt",
|
||||
"version": "0.13.0-alpha.1",
|
||||
"version": "0.13.0",
|
||||
"description": "TAKT: TAKT Agent Koordination Topology - AI Agent Piece Orchestration",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@ -14,11 +14,13 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
// ===== Codex SDK mock =====
|
||||
|
||||
let mockEvents: Array<Record<string, unknown>> = [];
|
||||
let lastThreadOptions: Record<string, unknown> | undefined;
|
||||
|
||||
vi.mock('@openai/codex-sdk', () => {
|
||||
return {
|
||||
Codex: class MockCodex {
|
||||
async startThread() {
|
||||
async startThread(options?: Record<string, unknown>) {
|
||||
lastThreadOptions = options;
|
||||
return {
|
||||
id: 'thread-mock',
|
||||
runStreamed: async () => ({
|
||||
@ -44,6 +46,7 @@ describe('CodexClient — structuredOutput 抽出', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockEvents = [];
|
||||
lastThreadOptions = undefined;
|
||||
});
|
||||
|
||||
it('outputSchema 指定時に agent_message の JSON テキストを structuredOutput として返す', async () => {
|
||||
@ -149,4 +152,21 @@ describe('CodexClient — structuredOutput 抽出', () => {
|
||||
|
||||
expect(result.structuredOutput).toEqual({ step: 1 });
|
||||
});
|
||||
|
||||
it('provider_options.codex.network_access が ThreadOptions に反映される', async () => {
|
||||
mockEvents = [
|
||||
{ type: 'thread.started', thread_id: 'thread-1' },
|
||||
{ type: 'turn.completed', usage: { input_tokens: 0, cached_input_tokens: 0, output_tokens: 0 } },
|
||||
];
|
||||
|
||||
const client = new CodexClient();
|
||||
await client.call('coder', 'prompt', {
|
||||
cwd: '/tmp',
|
||||
networkAccess: true,
|
||||
});
|
||||
|
||||
expect(lastThreadOptions).toMatchObject({
|
||||
networkAccessEnabled: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -105,6 +105,54 @@ describe('PieceConfigRawSchema', () => {
|
||||
expect(result.movements![0]?.permission_mode).toBe('edit');
|
||||
});
|
||||
|
||||
it('should parse movement with provider_options', () => {
|
||||
const config = {
|
||||
name: 'test-piece',
|
||||
movements: [
|
||||
{
|
||||
name: 'implement',
|
||||
provider: 'codex',
|
||||
provider_options: {
|
||||
codex: { network_access: true },
|
||||
opencode: { network_access: false },
|
||||
},
|
||||
instruction: '{task}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = PieceConfigRawSchema.parse(config);
|
||||
expect(result.movements![0]?.provider_options).toEqual({
|
||||
codex: { network_access: true },
|
||||
opencode: { network_access: false },
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse piece-level piece_config.provider_options', () => {
|
||||
const config = {
|
||||
name: 'test-piece',
|
||||
piece_config: {
|
||||
provider_options: {
|
||||
codex: { network_access: true },
|
||||
},
|
||||
},
|
||||
movements: [
|
||||
{
|
||||
name: 'implement',
|
||||
provider: 'codex',
|
||||
instruction: '{task}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = PieceConfigRawSchema.parse(config);
|
||||
expect(result.piece_config).toEqual({
|
||||
provider_options: {
|
||||
codex: { network_access: true },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should allow omitting permission_mode', () => {
|
||||
const config = {
|
||||
name: 'test-piece',
|
||||
|
||||
@ -84,4 +84,16 @@ describe('OpenCode permissions', () => {
|
||||
action: 'deny',
|
||||
});
|
||||
});
|
||||
|
||||
it('should force allow web tools when networkAccess=true', () => {
|
||||
const ruleset = buildOpenCodePermissionRuleset('readonly', true);
|
||||
expect(ruleset.find((rule) => rule.permission === 'webfetch')?.action).toBe('allow');
|
||||
expect(ruleset.find((rule) => rule.permission === 'websearch')?.action).toBe('allow');
|
||||
});
|
||||
|
||||
it('should force deny web tools when networkAccess=false', () => {
|
||||
const ruleset = buildOpenCodePermissionRuleset('full', false);
|
||||
expect(ruleset.find((rule) => rule.permission === 'webfetch')?.action).toBe('deny');
|
||||
expect(ruleset.find((rule) => rule.permission === 'websearch')?.action).toBe('deny');
|
||||
});
|
||||
});
|
||||
|
||||
46
src/__tests__/provider-options-piece-parser.test.ts
Normal file
46
src/__tests__/provider-options-piece-parser.test.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { normalizePieceConfig } from '../infra/config/loaders/pieceParser.js';
|
||||
|
||||
describe('normalizePieceConfig provider_options', () => {
|
||||
it('piece-level global を movement に継承し、movement 側で上書きできる', () => {
|
||||
const raw = {
|
||||
name: 'provider-options',
|
||||
piece_config: {
|
||||
provider_options: {
|
||||
codex: { network_access: true },
|
||||
opencode: { network_access: false },
|
||||
},
|
||||
},
|
||||
movements: [
|
||||
{
|
||||
name: 'codex-default',
|
||||
provider: 'codex',
|
||||
instruction: '{task}',
|
||||
},
|
||||
{
|
||||
name: 'codex-override',
|
||||
provider: 'codex',
|
||||
provider_options: {
|
||||
codex: { network_access: false },
|
||||
},
|
||||
instruction: '{task}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const config = normalizePieceConfig(raw, process.cwd());
|
||||
|
||||
expect(config.providerOptions).toEqual({
|
||||
codex: { networkAccess: true },
|
||||
opencode: { networkAccess: false },
|
||||
});
|
||||
expect(config.movements[0]?.providerOptions).toEqual({
|
||||
codex: { networkAccess: true },
|
||||
opencode: { networkAccess: false },
|
||||
});
|
||||
expect(config.movements[1]?.providerOptions).toEqual({
|
||||
codex: { networkAccess: false },
|
||||
opencode: { networkAccess: false },
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -107,6 +107,7 @@ export class AgentRunner {
|
||||
maxTurns: options.maxTurns,
|
||||
model: AgentRunner.resolveModel(resolvedProvider, options, agentConfig),
|
||||
permissionMode: options.permissionMode,
|
||||
providerOptions: options.providerOptions,
|
||||
onStream: options.onStream,
|
||||
onPermissionRequest: options.onPermissionRequest,
|
||||
onAskUserQuestion: options.onAskUserQuestion,
|
||||
|
||||
@ -24,6 +24,11 @@ export interface RunAgentOptions {
|
||||
maxTurns?: number;
|
||||
/** Permission mode for tool execution (from piece step) */
|
||||
permissionMode?: PermissionMode;
|
||||
/** Provider-specific movement options */
|
||||
providerOptions?: {
|
||||
codex?: { networkAccess?: boolean };
|
||||
opencode?: { networkAccess?: boolean };
|
||||
};
|
||||
onStream?: StreamCallback;
|
||||
onPermissionRequest?: PermissionHandler;
|
||||
onAskUserQuestion?: AskUserQuestionHandler;
|
||||
|
||||
@ -80,6 +80,24 @@ export interface McpHttpServerConfig {
|
||||
/** MCP server configuration (union of all YAML-configurable transports) */
|
||||
export type McpServerConfig = McpStdioServerConfig | McpSseServerConfig | McpHttpServerConfig;
|
||||
|
||||
/** Codex provider-specific options */
|
||||
export interface CodexProviderOptions {
|
||||
/** Enable network access for Codex workspace-write sandbox */
|
||||
networkAccess?: boolean;
|
||||
}
|
||||
|
||||
/** OpenCode provider-specific options */
|
||||
export interface OpenCodeProviderOptions {
|
||||
/** Enable/disable network tools (webfetch/websearch) */
|
||||
networkAccess?: boolean;
|
||||
}
|
||||
|
||||
/** Provider-specific movement options */
|
||||
export interface MovementProviderOptions {
|
||||
codex?: CodexProviderOptions;
|
||||
opencode?: OpenCodeProviderOptions;
|
||||
}
|
||||
|
||||
/** Single movement in a piece */
|
||||
export interface PieceMovement {
|
||||
name: string;
|
||||
@ -103,6 +121,8 @@ export interface PieceMovement {
|
||||
model?: string;
|
||||
/** Permission mode for tool execution in this movement */
|
||||
permissionMode?: PermissionMode;
|
||||
/** Provider-specific movement options */
|
||||
providerOptions?: MovementProviderOptions;
|
||||
/** Whether this movement is allowed to edit project files (true=allowed, false=prohibited, undefined=no prompt) */
|
||||
edit?: boolean;
|
||||
instructionTemplate: string;
|
||||
@ -201,6 +221,8 @@ export interface LoopMonitorConfig {
|
||||
export interface PieceConfig {
|
||||
name: string;
|
||||
description?: string;
|
||||
/** Piece-level default provider options (used as movement defaults) */
|
||||
providerOptions?: MovementProviderOptions;
|
||||
/** Persona definitions — map of name to file path or inline content (raw, not content-resolved) */
|
||||
personas?: Record<string, string>;
|
||||
/** Resolved policy definitions — map of name to file content (resolved at parse time) */
|
||||
|
||||
@ -59,6 +59,20 @@ export const StatusSchema = z.enum([
|
||||
|
||||
/** Permission mode schema for tool execution */
|
||||
export const PermissionModeSchema = z.enum(['readonly', 'edit', 'full']);
|
||||
/** Provider-specific movement options schema */
|
||||
export const MovementProviderOptionsSchema = z.object({
|
||||
codex: z.object({
|
||||
network_access: z.boolean().optional(),
|
||||
}).optional(),
|
||||
opencode: z.object({
|
||||
network_access: z.boolean().optional(),
|
||||
}).optional(),
|
||||
}).optional();
|
||||
|
||||
/** Piece-level provider options schema */
|
||||
export const PieceProviderOptionsSchema = z.object({
|
||||
provider_options: MovementProviderOptionsSchema,
|
||||
}).optional();
|
||||
|
||||
/**
|
||||
* Output contract item schema (new structured format).
|
||||
@ -204,6 +218,7 @@ export const ParallelSubMovementRawSchema = z.object({
|
||||
provider: z.enum(['claude', 'codex', 'opencode', 'mock']).optional(),
|
||||
model: z.string().optional(),
|
||||
permission_mode: PermissionModeSchema.optional(),
|
||||
provider_options: MovementProviderOptionsSchema,
|
||||
edit: z.boolean().optional(),
|
||||
instruction: z.string().optional(),
|
||||
instruction_template: z.string().optional(),
|
||||
@ -235,6 +250,8 @@ export const PieceMovementRawSchema = z.object({
|
||||
model: z.string().optional(),
|
||||
/** Permission mode for tool execution in this movement */
|
||||
permission_mode: PermissionModeSchema.optional(),
|
||||
/** Provider-specific movement options */
|
||||
provider_options: MovementProviderOptionsSchema,
|
||||
/** Whether this movement is allowed to edit project files */
|
||||
edit: z.boolean().optional(),
|
||||
instruction: z.string().optional(),
|
||||
@ -295,6 +312,7 @@ export const InteractiveModeSchema = z.enum(INTERACTIVE_MODES);
|
||||
export const PieceConfigRawSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
description: z.string().optional(),
|
||||
piece_config: PieceProviderOptionsSchema,
|
||||
/** Piece-level persona definitions — map of name to .md file path or inline content */
|
||||
personas: z.record(z.string(), z.string()).optional(),
|
||||
/** Piece-level policy definitions — map of name to .md file path or inline content */
|
||||
|
||||
@ -38,6 +38,7 @@ export class OptionsBuilder {
|
||||
provider: resolved.provider,
|
||||
model: resolved.model,
|
||||
permissionMode: step.permissionMode,
|
||||
providerOptions: step.providerOptions,
|
||||
language: this.getLanguage(),
|
||||
onStream: this.engineOptions.onStream,
|
||||
onPermissionRequest: this.engineOptions.onPermissionRequest,
|
||||
|
||||
@ -95,6 +95,7 @@ export class CodexClient {
|
||||
...(options.model ? { model: options.model } : {}),
|
||||
workingDirectory: options.cwd,
|
||||
sandboxMode,
|
||||
...(options.networkAccess === undefined ? {} : { networkAccessEnabled: options.networkAccess }),
|
||||
};
|
||||
let threadId = options.sessionId;
|
||||
|
||||
|
||||
@ -27,6 +27,8 @@ export interface CodexCallOptions {
|
||||
systemPrompt?: string;
|
||||
/** Permission mode for sandbox configuration */
|
||||
permissionMode?: PermissionMode;
|
||||
/** Enable network access for workspace-write sandbox */
|
||||
networkAccess?: boolean;
|
||||
/** Enable streaming mode with callback (best-effort) */
|
||||
onStream?: StreamCallback;
|
||||
/** OpenAI API key (bypasses CLI auth) */
|
||||
|
||||
@ -24,6 +24,36 @@ import {
|
||||
|
||||
type RawStep = z.output<typeof PieceMovementRawSchema>;
|
||||
|
||||
function normalizeProviderOptions(
|
||||
raw: RawStep['provider_options'],
|
||||
): PieceMovement['providerOptions'] {
|
||||
if (!raw) return undefined;
|
||||
|
||||
const codex = raw.codex?.network_access === undefined
|
||||
? undefined
|
||||
: { networkAccess: raw.codex.network_access };
|
||||
const opencode = raw.opencode?.network_access === undefined
|
||||
? undefined
|
||||
: { networkAccess: raw.opencode.network_access };
|
||||
|
||||
if (!codex && !opencode) return undefined;
|
||||
return { ...(codex ? { codex } : {}), ...(opencode ? { opencode } : {}) };
|
||||
}
|
||||
|
||||
function mergeProviderOptions(
|
||||
base: PieceMovement['providerOptions'],
|
||||
override: PieceMovement['providerOptions'],
|
||||
): PieceMovement['providerOptions'] {
|
||||
const codexNetworkAccess = override?.codex?.networkAccess ?? base?.codex?.networkAccess;
|
||||
const opencodeNetworkAccess = override?.opencode?.networkAccess ?? base?.opencode?.networkAccess;
|
||||
|
||||
const codex = codexNetworkAccess === undefined ? undefined : { networkAccess: codexNetworkAccess };
|
||||
const opencode = opencodeNetworkAccess === undefined ? undefined : { networkAccess: opencodeNetworkAccess };
|
||||
|
||||
if (!codex && !opencode) return undefined;
|
||||
return { ...(codex ? { codex } : {}), ...(opencode ? { opencode } : {}) };
|
||||
}
|
||||
|
||||
/** Check if a raw output contract item is the object form (has 'name' property). */
|
||||
function isOutputContractItem(raw: unknown): raw is { name: string; order?: string; format?: string } {
|
||||
return typeof raw === 'object' && raw !== null && !Array.isArray(raw) && 'name' in raw;
|
||||
@ -209,6 +239,7 @@ function normalizeStepFromRaw(
|
||||
step: RawStep,
|
||||
pieceDir: string,
|
||||
sections: PieceSections,
|
||||
inheritedProviderOptions?: PieceMovement['providerOptions'],
|
||||
context?: FacetResolutionContext,
|
||||
): PieceMovement {
|
||||
const rules: PieceRule[] | undefined = step.rules?.map(normalizeRule);
|
||||
@ -241,6 +272,7 @@ function normalizeStepFromRaw(
|
||||
provider: step.provider,
|
||||
model: step.model,
|
||||
permissionMode: step.permission_mode,
|
||||
providerOptions: mergeProviderOptions(inheritedProviderOptions, normalizeProviderOptions(step.provider_options)),
|
||||
edit: step.edit,
|
||||
instructionTemplate: (step.instruction_template
|
||||
? resolveRefToContent(step.instruction_template, sections.resolvedInstructions, pieceDir, 'instructions', context)
|
||||
@ -254,7 +286,9 @@ function normalizeStepFromRaw(
|
||||
};
|
||||
|
||||
if (step.parallel && step.parallel.length > 0) {
|
||||
result.parallel = step.parallel.map((sub: RawStep) => normalizeStepFromRaw(sub, pieceDir, sections, context));
|
||||
result.parallel = step.parallel.map((sub: RawStep) =>
|
||||
normalizeStepFromRaw(sub, pieceDir, sections, inheritedProviderOptions, context),
|
||||
);
|
||||
}
|
||||
|
||||
const arpeggioConfig = normalizeArpeggio(step.arpeggio, pieceDir);
|
||||
@ -327,8 +361,10 @@ export function normalizePieceConfig(
|
||||
resolvedReportFormats,
|
||||
};
|
||||
|
||||
const pieceProviderOptions = normalizeProviderOptions(parsed.piece_config?.provider_options as RawStep['provider_options']);
|
||||
|
||||
const movements: PieceMovement[] = parsed.movements.map((step) =>
|
||||
normalizeStepFromRaw(step, pieceDir, sections, context),
|
||||
normalizeStepFromRaw(step, pieceDir, sections, pieceProviderOptions, context),
|
||||
);
|
||||
|
||||
// Schema guarantees movements.min(1)
|
||||
@ -337,6 +373,7 @@ export function normalizePieceConfig(
|
||||
return {
|
||||
name: parsed.name,
|
||||
description: parsed.description,
|
||||
providerOptions: pieceProviderOptions,
|
||||
personas: parsed.personas,
|
||||
policies: resolvedPolicies,
|
||||
knowledge: resolvedKnowledge,
|
||||
|
||||
@ -311,7 +311,7 @@ export class OpenCodeClient {
|
||||
const parsedModel = parseProviderModel(options.model, 'OpenCode model');
|
||||
const fullModel = `${parsedModel.providerID}/${parsedModel.modelID}`;
|
||||
const port = await getFreePort();
|
||||
const permission = buildOpenCodePermissionConfig(options.permissionMode);
|
||||
const permission = buildOpenCodePermissionConfig(options.permissionMode, options.networkAccess);
|
||||
const config = {
|
||||
model: fullModel,
|
||||
small_model: fullModel,
|
||||
@ -332,7 +332,7 @@ export class OpenCodeClient {
|
||||
? { data: { id: options.sessionId } }
|
||||
: await client.session.create({
|
||||
directory: options.cwd,
|
||||
permission: buildOpenCodePermissionRuleset(options.permissionMode),
|
||||
permission: buildOpenCodePermissionRuleset(options.permissionMode, options.networkAccess),
|
||||
});
|
||||
|
||||
const sessionId = sessionResult.data?.id;
|
||||
|
||||
@ -100,14 +100,38 @@ function buildPermissionMap(mode?: PermissionMode): OpenCodePermissionMap {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildOpenCodePermissionConfig(mode?: PermissionMode): OpenCodePermissionAction | Record<string, OpenCodePermissionAction> {
|
||||
if (mode === 'readonly') return 'deny';
|
||||
if (mode === 'full') return 'allow';
|
||||
return buildPermissionMap(mode);
|
||||
function applyNetworkAccessOverride(
|
||||
map: OpenCodePermissionMap,
|
||||
networkAccess?: boolean,
|
||||
): OpenCodePermissionMap {
|
||||
if (networkAccess === undefined) {
|
||||
return map;
|
||||
}
|
||||
|
||||
export function buildOpenCodePermissionRuleset(mode?: PermissionMode): Array<{ permission: string; pattern: string; action: OpenCodePermissionAction }> {
|
||||
const permissionMap = buildPermissionMap(mode);
|
||||
const action: OpenCodePermissionAction = networkAccess ? 'allow' : 'deny';
|
||||
return {
|
||||
...map,
|
||||
webfetch: action,
|
||||
websearch: action,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildOpenCodePermissionConfig(
|
||||
mode?: PermissionMode,
|
||||
networkAccess?: boolean,
|
||||
): OpenCodePermissionAction | Record<string, OpenCodePermissionAction> {
|
||||
if (networkAccess === undefined) {
|
||||
if (mode === 'readonly') return 'deny';
|
||||
if (mode === 'full') return 'allow';
|
||||
}
|
||||
return applyNetworkAccessOverride(buildPermissionMap(mode), networkAccess);
|
||||
}
|
||||
|
||||
export function buildOpenCodePermissionRuleset(
|
||||
mode?: PermissionMode,
|
||||
networkAccess?: boolean,
|
||||
): Array<{ permission: string; pattern: string; action: OpenCodePermissionAction }> {
|
||||
const permissionMap = applyNetworkAccessOverride(buildPermissionMap(mode), networkAccess);
|
||||
return OPEN_CODE_PERMISSION_KEYS.map((permission) => ({
|
||||
permission,
|
||||
pattern: '**',
|
||||
@ -165,6 +189,8 @@ export interface OpenCodeCallOptions {
|
||||
allowedTools?: string[];
|
||||
/** Permission mode for automatic permission handling */
|
||||
permissionMode?: PermissionMode;
|
||||
/** Override network access (webfetch/websearch) */
|
||||
networkAccess?: boolean;
|
||||
/** Enable streaming mode with callback (best-effort) */
|
||||
onStream?: StreamCallback;
|
||||
onAskUserQuestion?: AskUserQuestionHandler;
|
||||
|
||||
@ -31,6 +31,7 @@ function toCodexOptions(options: ProviderCallOptions): CodexCallOptions {
|
||||
sessionId: options.sessionId,
|
||||
model: options.model,
|
||||
permissionMode: options.permissionMode,
|
||||
networkAccess: options.providerOptions?.codex?.networkAccess,
|
||||
onStream: options.onStream,
|
||||
openaiApiKey: options.openaiApiKey ?? resolveOpenaiApiKey(),
|
||||
outputSchema: options.outputSchema,
|
||||
|
||||
@ -19,6 +19,7 @@ function toOpenCodeOptions(options: ProviderCallOptions): OpenCodeCallOptions {
|
||||
model: options.model,
|
||||
allowedTools: options.allowedTools,
|
||||
permissionMode: options.permissionMode,
|
||||
networkAccess: options.providerOptions?.opencode?.networkAccess,
|
||||
onStream: options.onStream,
|
||||
onAskUserQuestion: options.onAskUserQuestion,
|
||||
opencodeApiKey: options.opencodeApiKey ?? resolveOpencodeApiKey(),
|
||||
|
||||
@ -30,6 +30,11 @@ export interface ProviderCallOptions {
|
||||
maxTurns?: number;
|
||||
/** Permission mode for tool execution (from piece step) */
|
||||
permissionMode?: PermissionMode;
|
||||
/** Provider-specific movement options */
|
||||
providerOptions?: {
|
||||
codex?: { networkAccess?: boolean };
|
||||
opencode?: { networkAccess?: boolean };
|
||||
};
|
||||
onStream?: StreamCallback;
|
||||
onPermissionRequest?: PermissionHandler;
|
||||
onAskUserQuestion?: AskUserQuestionHandler;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user