sync: update from private repo (2336949a)
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
oss-sync
2026-09-17 11:29:31 +00:00
parent 28f74c4e37
commit 57702b4f34
2139 changed files with 321270 additions and 17494 deletions
+175 -8
View File
@@ -5,12 +5,158 @@
---
## 0-A. ツールモジュールの自己申告(TOOL_MODULE)— まずここを読む
**2026-08-15(#1126)から、モジュールは自分の属性を自分で申告できる。**新しくツールを足すときは、離れた 4 ファイルを追いかける前に、まずモジュールの中に申告を 1 つ書く。
### 書き方
自分のモジュール(`src/engine/tools/<name>.ts`)で、`TOOL_DEFS` の隣に置く。
```ts
import type { ToolModuleManifest } from './module-manifest.js';
export const TOOL_MODULE: ToolModuleManifest = {
id: 'youtube', // ファイル名(拡張子なし)と揃える
category: 'youtube', // 分類。MODULE_SPECS と同じ語彙
rawSaveTools: ['GetYouTubeTranscript', 'SearchYouTube'],
};
```
### 書ける項目
| 項目 | 意味 | 以前の書き場所 |
|---|---|---|
| `id` | モジュールの識別子。診断とテストの照合に使う | (なし) |
| `category` | 分類 | `tool-categories.ts` の `MODULE_SPECS` |
| `metaTools` | ワークスペース設定に関係なく常時載せる | `tool-categories.ts` と `index.ts` の 2 か所 |
| `rawSaveTools` | 結果を `logs/raw/` に生のまま残す | `raw-save.ts` の `RAW_SAVE_TOOLS` |
| `rawLogOnlyTools` | 保存先パスだけ記録する | `raw-save.ts` の `RAW_LOG_ONLY_TOOLS` |
| `sensitiveTools` | 分類が既定 ON でも個別に既定 OFF | `tool-categories.ts` の `SENSITIVE_TOOLS` |
**書かない項目もある。**分類そのものを慎重扱いにする指定(`SENSITIVE_CATEGORIES`)は分類の属性なので申告には載せない。ワークフローの同梱は次の段の話で、まだ入れていない。
### 守ること
- **申告に書いた名前は、そのモジュールの `TOOL_DEFS` に必ずあること。**無い名前は収集時に落とされ、`phantoms` に記録される(採用されない)
- **同じ属性を中央の一覧と申告の両方に書かない。**両方にあると、申告を消しても実効値が変わらず、申告が飾りになる。移すときは中央の一覧から必ず消す
- **読む側は必ず解決関数を通すこと。**中央の一覧を直接読むと、申告した分が落ちる
| 解決関数(`src/engine/tools/module-manifest.ts`) | 返すもの |
|---|---|
| `resolveMetaToolNames()` | 常時利用の実効集合 |
| `resolveRawSaveToolNames()` | 生データ保存の実効集合 |
| `resolveRawLogOnlyToolNames()` | パスだけ記録の実効集合 |
| `resolveSensitiveToolNames()` | 既定 OFF の実効集合 |
| `resolveDeclaredCategories()` | 申告された分類(指定 → 分類) |
どれも「中央の一覧 ∪ 全モジュールの申告」を返す。**申告を持たないモジュールは、いままでどおり中央の一覧だけで決まる。**一度に全部を移す必要はない。
### 中央の一覧は残っている
`tool-categories.ts` の `META_TOOLS` / `SENSITIVE_TOOLS`、`raw-save.ts` の `RAW_SAVE_TOOLS` / `RAW_LOG_ONLY_TOOLS` は消していない。**申告を持たないモジュールの受け皿**として残す。移すときだけ、そのモジュールの分を中央から申告へ動かす。
### 確認方法
```bash
# 申告の収集・実効値・配線をまとめて見る
npx vitest run src/engine/tools/module-manifest.test.ts
```
この試験には**登録済みモジュールが 1 本残らず読み込めることを見る検査**が入っている。`index.ts` の `tryLoadModule` は読み込み失敗を握り潰すので、モジュールが壊れてもツールが黙って消えるだけで試験は緑のままになる。その穴を塞ぐための検査なので、**中に try/catch を足さないこと。**
### 既知の食い違い(未修正・意図的)
`workspace-task-search.ts` と `package-request.ts` は `index.ts` が実行時に読み込んでいるのに `MODULE_SPECS` に載っていない。載っているツールが全部 META なので実行時には届いてしまい、抜けに気づけない形になっている。**`/api/tools` のツール一覧には出ていない。**`MODULE_SPECS` に足すと設定画面の見た目が変わるため、この段では足さず、`module-manifest.ts` の `EXTRA_MODULE_SPECIFIERS` に置いて読み込み検査の対象にだけ入れてある。
---
## 0-B. 群(パック)の台帳 — 環境全体で群ごと止める
台帳: **`src/engine/tools/tool-pack-ledger.ts`**(データだけ。import を持たない)
関所: **`src/engine/tools/tool-pack-gate.ts`**(設定を読んで判定を凍らせる)
`config.yaml` の `tool_packs.disabled` に群の名前を書くと、その群のモジュールを**読み込む前に断る**。ツール定義・分類・申告の収集・`/api/tools` の一覧が同時に消える。
### 台帳に足すときの条件(両方満たすこと)
1. **外の相手をその群だけが持っている。**外部 CLI・外部サーバー・外部 API・外部ライブラリのいずれかが、その群の中からしか呼ばれていない
2. **代わりを置ける。**同じ用途を別の事業者・別の MCP サーバーで満たせる
**分類が同じという理由だけで束ねないこと。**文字起こし(`speech`)と読み上げ(`speak`)は別の群にしてある——接続先のサーバーが別だから。
条件を満たさないものを足すと「外したのに縁が切れていない」群ができて、段 F(実体と依存を捨てる)で必ず詰まる。載せなかったものとその理由は台帳のコメントにある(`mermaid` / `calendar` / `data` / `review` / `app-test`)。
### 守ること
- **設定の変更通知(`ConfigManager` の `config-changed`)に関所を繋がないこと。**走行中にツール一覧が変わると継続ジョブのプロンプトキャッシュが全損する。反映は再起動時のみ
- **モジュール指定を呼び出し側で `replace` しないこと。**`normalizeToolModuleSpecifier()` を通す。`MODULE_SPECS` は `../engine/tools/x.js`、`index.ts` は `./x.js` の 2 通りがあり、片方だけ素通りするのが一番起きやすい壊れ方
- **台帳の群のツールを `META_TOOLS` / `SENSITIVE_TOOLS` に書かないこと。**中央の一覧は群と無関係に効くので、書くと止めた群の名前が許可一覧に残る。`tool-pack-gate.test.ts` の歯止めが落ちる
- 台帳に**載っていない**群はいままでどおり全部使える。これが既定
### 関所を通す先(6 か所。1 つでも抜けると片側だけ素通りする)
| 場所 | 何が消えるか |
|---|---|
| `tools/index.ts` の `tryLoadModule` | エージェントに渡るツール定義と、実行の口 |
| `module-manifest.ts` の `listRegisteredToolModuleSpecifiers` | 自己申告の収集・分類の地図・生データ保存の対象 |
| `tool-categories.ts` の `listToolCategories` | 設定画面に並ぶ分類のトグル |
| `bridge/tools-api.ts` の `loadBuiltinEntries` | `/api/tools` のツール一覧 |
| `workspace-tool-policy.ts` の個別 sensitive ループ | ワークスペース側から入れ直す道(層の向き) |
| `worker.ts` の `ensureKeepaGraphs` 呼び出し | 成果物に差し込まれる価格グラフ(外部の画像 URL) |
最後の 1 行が要る理由は、**群の中の関数を群の外から静的に呼んでいる**から。`import` で直に持ってくる経路はモジュール読み込みの関所を通らないので、呼ぶ側が自分で `isToolPackDisabled()` を見るしかない。ツール定義が消えても差し込みが残ると、成果物を開いた利用者の画面が毎回その外部の相手を叩く——「群を外せば縁が切れる」が嘘になる。**群の中の関数を群の外から import したら、必ずこの形で関所を通すこと。**
### 逆に、関所を通してはいけない 2 か所(保存の検査)
保存済みの設定に、いま止めた群の名前が残っていることがある。それを綴り間違いと同じ扱いで弾くと、**その名前を送ってくるクライアントの保存が全部落ちる。**
送ってくるのは次の 2 つ。画面から普通に開き直した場合は、分類の一覧に無いので送られない。
- 群を止める前に開かれたままのタブ
- API を直接叩くクライアント
自動プロンプトの規則だけは事情が違い、**画面から編集しても必ず送られる**(ツール名は規則そのものの中身で、ツール一覧から組み直されないため)。落ちるのは、その群のツールを名指ししている規則の保存だけ。規則は 1 本ずつ送るので、他の規則は保存できる。
| 場所 | 使うもの |
|---|---|
| `space-tool-policy-api.ts` の保存検査 | `listToolCategories({ includeEnvDisabled: true })` |
| `space-auto-prompter-api.ts` の `knownToolNames` | `getToolCategoryMap({ includeEnvDisabled: true })` |
**「知っている名前か」を判定する側だけ**が `includeEnvDisabled` を使う。実際にどのツールが使えるかを決める側で使うと、層の向きが崩れる。ツール名やカテゴリ名を allowlist で検査する場所を新しく足すときは、ここに並べること。
### 画面に出ていない名前を、保存で消さないこと
`space-tool-policy-api.ts` の保存(PUT)は、**保存済みの名前のうちいま画面に出ていないものを、送られてこなくても残す**(`hiddenStoredNames`)。
画面が送る名前は GET が返した分類一覧から組み立てられる。群を止めるとその分類は一覧から消えるので、以後どの保存でも送られない。送られないことを「入れ直した」と読むと、そのワークスペースが**明示的に切っていた分類**が黙って消え、管理者があとで群を戻したときに既定 ON で復活する。切ったという意思のほうが失われるので、見えない名前は保存済みの値を残す。
残す対象は「既知(環境で止めた分を含む)だが、いまの画面には出ていない」名前だけ。綴り間違いの残骸はこれまでどおり掃除される。ツール名やカテゴリ名を丸ごと置き換える保存 API を新しく足すときは、同じ形にすること。
### 判定の記録
「いつ・なぜ・誰の設定で」は `getToolPackDecisions()` に全部載る。起動時に `[tool-packs] resolved` の 1 行がログに出る。群ごとの記録を足したくなったらここを読む——**新しい記録の仕組みを作らないこと。**
### 確認方法
```bash
npx vitest run src/engine/tools/tool-pack-gate.test.ts \
src/engine/tools/tool-pack-gate.wiring.test.ts \
src/bridge/space-api.tool-policy.pack-gate.test.ts
```
台帳に群を足したら、`config.yaml.example` の一覧・`ui/src/content/help/19-admin.md` の表・更新履歴も合わせて直す。あわせて `pieces/*.yaml` をその群のツール名で grep すること。既定のワークフローが「必ず〜を使う」の形でそのツールを名指ししていたら、群を止めた環境ではその指示が存在しないツールを指す。名指ししているワークフロー名はヘルプの注意書きに出す。
---
## 0. ツールのカテゴリ区分(workspace tool policy)
新しいツールを追加した場合、または既存ツールのカテゴリを変更した場合は、**`src/engine/tools/tool-categories.ts`** の `MODULE_SPECS` に登録し、安全(標準)かセンシティブかの区分を決める。workspace tool policy はこの定義を参照して、ワークスペースごとにツールの利用可否を解決する。
> **先に 0-A を読むこと。**新しいモジュールは、分類も既定 OFF も自分の `TOOL_MODULE` 申告に書ける。以下は申告を持たない既存モジュールの経路。
- `SENSITIVE_CATEGORIES`(現在: `ssh` / `browser`)に含まれるカテゴリのツールはデフォルト無効
- `SENSITIVE_TOOLS`(現在: `Bash`)に列挙されたツールは、カテゴリが `core` であっても個別にセンシティブ扱い
- `SENSITIVE_TOOLS`(現在: `Bash` / `SpawnSubTask`)に列挙されたツールは、カテゴリが `core` であっても個別にセンシティブ扱い。`SpinOffTask` は `task-spawn.ts` の申告側にある
- 新カテゴリは安全側(デフォルト有効)が既定。意図的にセンシティブにする場合のみ `SENSITIVE_CATEGORIES` に追加する
**対象ファイル:**
@@ -78,7 +224,7 @@ CLAUDE.md のモジュールテーブルに新ツール名が含まれている
**対象ファイル:**
- `src/engine/tools/tool-categories.ts` — カテゴリ定義・`SENSITIVE_TOOLS` から旧名を削除/リネーム
- `src/engine/tools/raw-save.ts` — `RAW_SAVE_TOOLS` に旧名が残っていないか
- `ui/src/components/settings/ToolsForm.tsx` — ヘルプテキスト等にツール名の言及がないか
- `ui/src/components/settings/ToolsWebForm.tsx` / `ToolsMediaForm.tsx` / `ToolsExternalForm.tsx` — ヘルプテキスト等にツール名の言及がないか
- `CLAUDE.md` — ツールモジュール構成テーブル
**なぜ必要か:**
@@ -160,13 +306,23 @@ grep -A 20 'LEGACY_SECTION_REDIRECT' ui/src/components/settings/SettingsSidebar.
---
## 4-D. タスク spawn に新しいフィールドを追加した場合(二重実装ミラー)
タスク/ジョブの spawn 経路は **2 系統の並行実装** になっている(統合は別課題):
- 通常作成: `src/bridge/local-tasks-crud-api.ts`(POST /api/local/tasks → createLocalTask + createJob)
- スケジュール実行: `src/scheduler.ts` の `executeAgentScheduledTask()`(createLocalTask + createJob を独自に呼ぶ。Router Phase 2 は通らない・metadata block はハードコード)
タスクやジョブに新しいフィールド(例: issue #974 の `llmWorkerId`/`requiredWorkerId`)を追加したら、**両方の経路に配線されているか必ず確認する**。片方だけだと「UI から作ると効くがスケジュール実行だと落ちる」型の欠落になる(#974 がまさにこれ)。
## 5. SSRF 保護に関わる変更をした場合
**対象ファイル:**
- `src/engine/tools/shared/ssrf.ts` — SSRF チェックロジック本体
- `src/engine/tools/browser.ts` — BrowseWeb(`setupRouteInterception` + `ssrfCheck`)
- `src/engine/tools/web.ts` — WebFetch / DownloadFile
- `ui/src/components/settings/ToolsForm.tsx` — SSRF Allowed Hosts のヘルプテキスト
- `src/engine/tools/shared/ssrf.ts` — SSRF チェックロジック本体 + ブラウザへの装着(`installContextSsrfGuard`)
- `src/engine/tools/browser.ts` — BrowseWeb(`ensureContextHooks` / `openIsolatedGuardedPage` + `ssrfCheck`)
- `src/engine/tools/web.ts` — WebFetch / DownloadFile / 検索・スクショが借りる共有ブラウザ
- `src/engine/tools/x.ts` — X の card 画像取得(同じ共有ブラウザを借りる)
- `ui/src/components/settings/ToolsWebForm.tsx` — SSRF Allowed Hosts のヘルプテキスト
**なぜ必要か:**
`webfetchAllowedHosts` は WebFetch と BrowseWeb の両方で使われている。片方だけ修正すると保護に穴が開くか、意図しないブロックが発生する。
@@ -608,6 +764,17 @@ bwrap のマウント構成を変えた場合、セキュリティ境界が変
- **冪等な状態一致スキップ**: reconciler は `payload.status.state` が算出後の state と一致する場合は再書き込みしない(`waiting_human → input-required` の churn 防止)。`saveA2aTask` は task id の upsert なので executor と reconciler が競合しても二重書きは安全。
- **委任カラムの保持**: reconciler が `saveA2aTask` を呼ぶ際は `payload.metadata` の `a2aDelegationId` / `a2aGrantId` / `a2aActingUserId` を `a2a_tasks` の `delegation_id` / `grant_id` / `acting_user_id` 列へ写す(`task-store.ts` の save 規約と同一)。列を増やすときは executor の初期 Task metadata・`SqliteA2aTaskStore.save`・`reconciler.persistTask` の 3 箇所を揃えること。
### A2A 成果物ダウンロード(`artifact-download-api.ts` / `artifact-parts.ts`)を変更するとき
- **artifactId は決定的**: `deriveArtifactId(a2aTaskId, fileName)` から作る。ランダム UUID に戻すと、executor が流した ID と reconciler が組み直した ID が食い違い、`tasks/get` のたびに成果物の識別子が変わる(受け入れ条件「ID の安定性」が崩れる)。
- **URI は識別子だけ**: `{baseUrl}/a2a/artifacts/{taskId}/{artifactId}`。ワークスペースの実パス・worktree 名・`output/` 相対パスのいずれも URI へ入れないこと。
- **baseUrl は 1 か所から導出**: `resolveA2aBaseUrl`(`a2a-subsystem.ts`)を executor(`request-handler.ts` 経由)と reconciler の両方へ渡す。片方だけ変えると経路によって URI が変わる。
- **認可は書き写さない**: 可視性は `getLocalTask(id, { viewer })`(=`buildVisibilityWhere`)にそのまま乗せ、委任のスペース範囲は `principal.delegation.grantedSpaceIds` との AND 交差で重ねる。可視性のルール(持ち主・組織・公開・スペースメンバー)をこのルータへ再実装しないこと。
- **タスクと委任の突き合わせは grant_id ではなく当事者で**: `resolveTaskDelegationParty` が返す `acting_user_id` × 委任行の `client_id` で一致を見る。`grant_id` の一致に戻すと、アクセストークン(寿命 10 分・更新トークン無し)を取り直すための再承認が新しい grant を作り、それ以前に配ったリンクが一斉に 404 になる(PR #1111 レビュー P2-1)。引いた委任行の生死は見ないこと——提示されたトークン側の生死・期限は `authenticateA2aRequest` が引き直している。
- **`deriveArtifactId` の区切りは生バイトで書かない**: NUL はエスケープ表記(`\u0000`)で書く。生の 1 バイトをソースへ置くと git がファイル全体を二値と判定し、以後この中核モジュールの差分がどこでも読めなくなる。
- **どのファイルを返すかは記録済みメタ情報から引く**: クライアントが送れるのは taskId と artifactId だけ。パスを受け取るクエリ引数を足さないこと。パス解決は必ず `ensurePathWithin(workspace/output, name)` を通す(字句チェックと realpath チェックの両方がここに入っている)。
- **チャットコネクタ経路には baseUrl を渡さない**: `chat-connector-service.ts` は SDK の TaskStore を通さず `a2a_tasks` 行が残らないため、URI を載せると開けないリンクになる。
**確認方法:**
```bash
# A2A 関連テスト一括実行
@@ -638,8 +805,8 @@ grep -n "a2a_delegations\|a2a_skills" src/db/repository.ts
**対象ファイル(RequestPackage を例に):**
- `src/engine/tools/package-request.ts` — ツール本体(`TOOL_DEFS` + `executeTool`)。バリデーションは `engine/python-packages.ts` の `parseAndValidateSpec` / `assertNotShadowing` を再利用
- `src/engine/tools/core.ts` — `ToolContext` に park フラグ(`pendingPackageApproval`)+ recorder(`recordPackageRequest`)+ 補助状態(`installedPackages` / `declinedPackages`)
- `src/engine/tools/index.ts` — module loader / `Object.assign(allDefs, ...)` / **`META_TOOLS` 配列(2 箇所)** / dispatch 分岐
- `src/engine/tools/tool-categories.ts` — `META_TOOLS` Set にツール名を追加
- `src/engine/tools/index.ts` — module loader / `Object.assign(allDefs, ...)` / dispatch 分岐(**META の直書き配列は撤去済み。#1126**)
- 常時利用の登録先は 1 か所になった。モジュールの `TOOL_MODULE.metaTools` に書く(0-A 参照)。申告を持たない既存モジュールの分だけ `tool-categories.ts` の `META_TOOLS` Set に残っている
- `src/engine/agent-loop/pending-states.ts` — `buildPackageApprovalWaitResult`(park → `next:'WAITING_HUMAN_PACKAGE_REQUEST'`)
- `src/engine/agent-loop.ts` — import + `buildToolApprovalWaitResult` の隣で呼ぶ
- `src/engine/piece-runner.ts` — `WAITING_HUMAN_PACKAGE_REQUEST` sentinel 分岐(waiting_human park)+ **2 つの PieceRunOptions interface** に recorder/補助状態を追加 + ctx 構築で piece/movement を注入