This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
ヘッドレスブラウザで Web ページを操作するツール。同一ジョブ内ではブラウザコンテキスト(Cookie・ログイン状態)が永続化される。
|
||||
|
||||
ログインが必要なサイト(X / 管理画面など)を継続的にスクレイピングするなら、保存済みログインセッションを使うとよい。詳細は `ReadToolDoc({ name: "BrowserSessions" })`(または [browse-sessions.md](./browse-sessions.md))を参照。
|
||||
|
||||
## 2 つのモード
|
||||
|
||||
### 1. 基本モード — URL を開いてテキスト取得
|
||||
|
||||
@@ -15,12 +15,13 @@ cron の定期タスク(`scheduled_tasks`)とは別概念(予定は実行
|
||||
- `title`(必須): 予定のタイトル
|
||||
- `date`(必須): 開始日 `YYYY-MM-DD`(ローカル日付)
|
||||
- `end_date`(任意): 終了日 `YYYY-MM-DD`。複数日にまたがる予定のとき指定。省略すると単日。`date` より前は不可
|
||||
- `time`(任意): `HH:MM`。省略すると終日扱い
|
||||
- `time`(任意): 開始 `HH:MM`。省略すると終日扱い
|
||||
- `end_time`(任意): 終了 `HH:MM`。`time`(開始)があるときだけ有効。単日では開始時刻以降のみ(複数日は終了日側の時刻なので順序不問)
|
||||
- `description`(任意): 補足説明
|
||||
|
||||
例:
|
||||
```
|
||||
AddCalendarEvent({ title: "定例MTG", date: "2026-07-01", time: "10:00" })
|
||||
AddCalendarEvent({ title: "定例MTG", date: "2026-07-01", time: "10:00", end_time: "11:00" })
|
||||
AddCalendarEvent({ title: "提出締切", date: "2026-07-10" }) // 終日
|
||||
AddCalendarEvent({ title: "現地出張", date: "2026-07-10", end_date: "2026-07-12" }) // 複数日
|
||||
```
|
||||
@@ -40,6 +41,7 @@ AddCalendarEvent({ title: "現地出張", date: "2026-07-10", end_date: "2026-07
|
||||
|
||||
## gotcha
|
||||
|
||||
- `date` / `from` / `to` は `YYYY-MM-DD`、`time` は `HH:MM` 厳守。形式違反はエラー。
|
||||
- `date` / `from` / `to` は `YYYY-MM-DD`、`time` / `end_time` は `HH:MM` 厳守。形式違反はエラー。
|
||||
- `end_time` は `time` なしには指定できない(終日の予定に終了時刻は持てない)。
|
||||
- スペース未所属のタスクからは呼べない(`ctx.spaceId` 必須)。
|
||||
- 予定はスペースの可視性に従う。イベント単独の可視性は持たない。
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
# Delegate
|
||||
|
||||
サブエージェントを同期的にインライン実行して、重い処理を委譲し本体のコンテキストを節約する。サブエージェントの中間ターンは親に見えないため、複雑な作業の最終結果だけを取得できる。
|
||||
|
||||
## 基本
|
||||
|
||||
```js
|
||||
Delegate({
|
||||
description: "ファイル群を分析",
|
||||
prompt: "以下の 10 個のファイルを読み込み、各々の行数・言語・依存関係を分析して、JSON サマリーを output/file-analysis.json に書き込む。ファイルリスト: [リストを展開]"
|
||||
})
|
||||
```
|
||||
|
||||
呼び出すと、サブエージェントが自身の conversation context で独立に実行される。サブエージェントが完了(success / aborted)したら、最終結果の文字列だけを返す。**サブエージェントの中間会話ターンは親に入らない** —— つまり、長い調査や複雑な分析が親のコンテキスト使用量に影響しない。
|
||||
|
||||
## いつ使うか
|
||||
|
||||
### Delegate が向いているケース
|
||||
|
||||
- **単一の明確で集中した委譲タスク**(「複数ファイルから要約を抽出」「重い分析を実行」「外部情報の深い調査」)
|
||||
- **中間結果は不要で、最終成果物だけが欲しい**(サブエージェントの思考プロセスは必要ない)
|
||||
- **親のコンテキスト節約が最優先**(サブの中間ターンが消費メモリ = 親に影響しない)
|
||||
- **処理が比較的短時間で完了する見込み**
|
||||
|
||||
例:
|
||||
- 「100 個のファイルを読んで, CSV を生成」→ delegate で委譲, 完了後に result を引き継ぐ
|
||||
- 「特定キーワードで 30 件検索し, 記事要約→集約」→ 重い WebFetch も delegate 内で完結
|
||||
- 「複数 PDF を OCR → テキスト抽出 → 解析」→ 前処理を delegate, 後続は親が実行
|
||||
|
||||
### SpawnSubTask が向いているケース
|
||||
|
||||
- **複数の独立したテーマ**(A, B, C に分解)→ 並列実行が必須
|
||||
- **サブタスク間に依存がない**(A が完了してから B という制約がない)
|
||||
- **各サブの成果物がそれぞれ意味を持つ**(集約不要または集約が簡単)
|
||||
|
||||
### Delegate が向かないケース
|
||||
|
||||
- **対話が必要**(ASK ツールを呼ぶ)→ 子の ASK は親に bubbles up する
|
||||
- **ネスト深さが 2 を超える**(delegate の中から delegate, その中から delegate...)
|
||||
- **超長時間タスク**(非常に長くかかる処理)→ 親が完了を待ってブロックするため、焦点を絞ったタスクに限定すること
|
||||
|
||||
## パラメータ
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
|-----------|-----|------|------|
|
||||
| `description` | string | ✅ | 委譲タスクを表す 3〜6 語の短いラベル |
|
||||
| `prompt` | string | ✅ | サブエージェントへの自己完結した完全な指示 |
|
||||
|
||||
## prompt の書き方
|
||||
|
||||
**自己完結**で書く。サブエージェントは親の conversation history を見られないため:
|
||||
|
||||
- タスクの全背景を展開する(親の context は参照不可)
|
||||
- 前提情報・ファイルリストなどを明示的に記載
|
||||
- **期待する成果物を明確に**(ファイル名・形式・場所)
|
||||
- 成功の定義を簡潔に述べる
|
||||
|
||||
❌ 「さっきの調査の続きをやって」
|
||||
✅ 「以下のキーワード 3 つについて [展開], 各々のメリット・デメリット・ユースケースを調査し, output/comparison.md に Markdown 形式で整理する。セクション: 概要 / メリット / デメリット / 用途 / 参考資料"
|
||||
|
||||
❌ 「ファイルを分析して」
|
||||
✅ 「`input/` 配下の全 .ts ファイルを読み込み, 以下を計測して output/stats.json に JSON 形式で出力: { fileName, lineCount, exports[], imports[] }"
|
||||
|
||||
## 結果の使い方
|
||||
|
||||
Delegate の result は文字列。次の movement で Read / Bash 等で参照:
|
||||
|
||||
```js
|
||||
const delegateResult = "..."; // Delegate が返した result
|
||||
|
||||
// 結果ファイルを読み込む(委譲先が output/ に書いていれば)
|
||||
Read({ path: "output/analysis.json" })
|
||||
|
||||
// またはログを参照
|
||||
Bash({ command: "cat logs/activity.log | tail -20" })
|
||||
```
|
||||
|
||||
## 制限
|
||||
|
||||
- **直列実行**: Delegate × N 個を呼ぶとシリアルに実行される(並列不可)。高速化は SpawnSubTask で並列化
|
||||
- **ネスト深さ**: 約 2 レベル(delegate 内からさらに delegate を呼ぶのは許可だが, 3 段階目以上は危険)
|
||||
- **完了待ちブロック**: 親は子が完了するまでブロックされるため、1 回の delegate は焦点を絞ること
|
||||
- **ASK 必須情報**: サブエージェントが ASK を呼んだら, 親に `[delegate 要追加情報] ...` と bubble up される。親が回答を `transition({lessons: "..." })` で与える
|
||||
- **ワークスペース共有**: 同じ workspace で実行されるため, output/ は親から見える。input/ も共有
|
||||
|
||||
## 使用例
|
||||
|
||||
### 例 1: ファイル分析の要約化
|
||||
|
||||
```js
|
||||
Delegate({
|
||||
description: "ソースファイル群を分析",
|
||||
prompt: `
|
||||
以下の 5 つのファイルを読み込み, 各々の:
|
||||
- 行数
|
||||
- 主要な exported 関数・クラス名
|
||||
- 依存 import 数
|
||||
|
||||
を計測して, output/file-summary.json に以下形式で出力:
|
||||
|
||||
[
|
||||
{ file: "path/to/file.ts", lines: 123, exports: [...], imports: 5 },
|
||||
...
|
||||
]
|
||||
|
||||
ファイル:
|
||||
1. src/engine/agent-loop.ts
|
||||
2. src/engine/piece-runner.ts
|
||||
3. src/llm/openai-compat.ts
|
||||
4. src/worker.ts
|
||||
5. src/config-manager.ts
|
||||
|
||||
実行後, 必ず output/file-summary.json に JSON を書き込んで終了すること。
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### 例 2: 複数 URL 検索 → 要約集約
|
||||
|
||||
```js
|
||||
Delegate({
|
||||
description: "キーワード検索と要約集約",
|
||||
prompt: `
|
||||
以下の 3 つのキーワードで Web 検索し, 上位 5 件ずつ fetch して要約をまとめる:
|
||||
- キーワード A
|
||||
- キーワード B
|
||||
- キーワード C
|
||||
|
||||
各キーワード毎に:
|
||||
1. WebSearch で 5-10 件検索
|
||||
2. 各 URL を WebFetch で取得
|
||||
3. テキスト要約(最大 5 行)を抽出
|
||||
|
||||
結果を output/search-summary.md に Markdown で出力:
|
||||
|
||||
# Search Results
|
||||
|
||||
## Keyword A
|
||||
[上位 3 件の要約]
|
||||
|
||||
## Keyword B
|
||||
[上位 3 件の要約]
|
||||
|
||||
## Keyword C
|
||||
[上位 3 件の要約]
|
||||
|
||||
実行後, output/search-summary.md に結果を保存して終了。
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
## 詳細は ReadToolDoc
|
||||
|
||||
詳細な実装・エラーハンドリング・context 管理については `ReadToolDoc({ name: "Delegate" })` で確認可能。
|
||||
@@ -0,0 +1,38 @@
|
||||
# GetMyOrchestratorState
|
||||
|
||||
呼び出しユーザーの現在の Orchestrator 状態を **sanitized な Markdown スナップショット**で返すメタツール(常時利用可能、引数なし)。「自分の MCP は何が繋がっている?」「最近何を実行した?」のようなユーザー固有の質問に答える前に呼ぶ。
|
||||
|
||||
## 引数
|
||||
|
||||
なし。認証済みユーザー(`ctx.userId`)が前提。未認証ならエラーを返す。
|
||||
|
||||
## 出力に含まれる項目
|
||||
|
||||
| セクション | 内容 |
|
||||
|-----------|------|
|
||||
| ユーザー | id / 名前 / role |
|
||||
| 最近のタスク | 直近 5 件(task-id・ピース名・状態・作成日時・タイトル先頭 60 字) |
|
||||
| MCP サーバー | **このタスクで実際に利用可能なものだけ**(後述)。各サーバーの認証種別 / 個人・全体 / 連携状況 |
|
||||
| ユーザーフォルダ | AGENTS.md の有無とサイズ、`memory/` `scripts/` `browser-macros/` `templates/` `recordings/` の件数 |
|
||||
| カスタム Piece | 自分の fork(`data/users/{id}/pieces/`) |
|
||||
| 組み込み Piece | 名前のみ列挙 |
|
||||
|
||||
## 秘密情報は一切返さない
|
||||
|
||||
トークン・OAuth client secret・暗号化 blob などのセンシティブな値は**含まれない**。MCP は「連携済み / 未連携」の状態だけを返す(中身のトークンは出さない)。
|
||||
|
||||
## MCP のスコープに注意
|
||||
|
||||
報告される MCP サーバーは、**そのタスクの実効スペース(`ctx.spaceId`)で実際にエージェントへ公開されているものだけ**。別スペースに登録済みでも、このタスクから呼べないサーバーは「連携済み」と表示しない(旧仕様は owner スコープで列挙し、呼べないサーバーを連携済みと誤報していた)。
|
||||
|
||||
- 個人ワークスペース(`space_id IS NULL`)のタスク → `space_id IS NULL` のサーバー
|
||||
- 個別スペースのタスク → その `space_id` のサーバー
|
||||
|
||||
## 制約
|
||||
|
||||
- サーバー側で DB 依存が注入されていない場合(`setAppDocsDeps` 未呼び出し)はエラー。
|
||||
- 各クエリは best-effort で、失敗してもセクション単位で「取得失敗」を出して継続する。
|
||||
|
||||
## 関連
|
||||
|
||||
ユーザーフォルダの中身そのものは [ListUserAssets](./listuserassets.md) / [ReadUserMemory](./readusermemory.md) などで個別に参照する。
|
||||
@@ -0,0 +1,46 @@
|
||||
# ListAppDocs
|
||||
|
||||
MAESTRO のプロジェクト内ドキュメントを **カテゴリ別に一覧**するメタツール(常時利用可能、引数なし)。質問に答える前に、関連 doc を探すために使う。各エントリの symbolic name は [ReadAppDoc](./readappdoc.md) にそのまま渡せる。
|
||||
|
||||
## 引数
|
||||
|
||||
なし。
|
||||
|
||||
## 出力(3 カテゴリの Markdown)
|
||||
|
||||
| セクション | 内容 | 読み込み形式 |
|
||||
|-----------|------|------------|
|
||||
| Piece 一覧 | `pieces/*.yaml` の名前+ description 1 行 | `piece/<name>` |
|
||||
| ドキュメント | `docs/` 配下の `.md`(後述の除外を除く) | `docs/<path>` |
|
||||
| ツール参照 | `docs/tools/*.md` 全件 | `tool/<name>`(`ReadToolDoc` と同等) |
|
||||
|
||||
```
|
||||
ListAppDocs()
|
||||
→
|
||||
# Piece 一覧 (`piece/<name>` で読み込み)
|
||||
- `piece/chat` — 雑多な依頼に答える汎用ピース
|
||||
...
|
||||
# ドキュメント (`docs/<path>` で読み込み)
|
||||
- `docs/mcp` — MCP 連携の概要
|
||||
...
|
||||
# ツール参照 (`tool/<name>` で読み込み — ReadToolDoc と同等)
|
||||
- `tool/browseweb` — ヘッドレスブラウザで…
|
||||
```
|
||||
|
||||
## 一覧から除外されるもの
|
||||
|
||||
内部向け・履歴的な doc はノイズになるため `docs/` セクションから除外される:
|
||||
|
||||
- `docs/design/`
|
||||
- `docs/maintenance-checklist`
|
||||
|
||||
これらは `ReadAppDoc` でも基本的に読めない(block 対象)。
|
||||
|
||||
## 制約
|
||||
|
||||
- description は各ファイルの frontmatter を飛ばした最初の見出し/段落から最大 140 字を自動抽出。
|
||||
- 合計エントリが 150 件を超えると、末尾に「個別取得を促す注記」が付く。
|
||||
|
||||
## 関連
|
||||
|
||||
個別の doc を読むには [ReadAppDoc](./readappdoc.md)。
|
||||
@@ -0,0 +1,45 @@
|
||||
# MissionUpdate
|
||||
|
||||
タスクの **Mission Brief**(`goal` / `done` / `open` / `clarifications`)を更新するメタツール。`allowed_tools` に書かなくても常時利用可能(META_TOOL)。
|
||||
|
||||
Mission Brief は毎 movement のシステムプロンプト冒頭に常に描画され、会話が長くなった後やステップをまたいでも消えない「参照点」になる。ユーザーも Overview タブから直接編集できる。
|
||||
|
||||
## いつ使うか
|
||||
|
||||
- **新規タスクの最初のツール呼び出しで `goal` を必ず set する。** ユーザーが最初に依頼した本質的な要件を verbatim(言い換えず原文のまま)で固定する。後で会話が長くなっても、ここを見れば「本来何を頼まれたか」がぶれない。
|
||||
- 作業の節目で `done`(完了したマイルストーン)と `open`(残作業・ブロッカー)を更新する。重複作業の防止と、次の一手の見通しに使う。
|
||||
- ユーザーが途中で補足・制約を足してきたら `clarifications` に記録する(「これは壊さないで」「言語は英語で」など)。
|
||||
|
||||
## 引数
|
||||
|
||||
| 引数 | 説明 |
|
||||
|------|------|
|
||||
| `goal` | タスク全体のゴール。ユーザーの本質的な要件を原文で。Markdown 可 |
|
||||
| `done` | これまでに完了した主要マイルストーン。箇条書き推奨 |
|
||||
| `open` | 残っている作業・未解決のブロッカー。箇条書き推奨 |
|
||||
| `clarifications` | ユーザーから追加された補足・制約。Markdown 可 |
|
||||
|
||||
すべて任意だが、最低1つは指定する必要がある(全フィールド未指定はエラー)。
|
||||
|
||||
## 部分置換セマンティクス
|
||||
|
||||
**指定したフィールドだけ**が上書きされ、未指定のフィールドは現状のまま残る。`done` だけ渡せば `goal` は変わらない。
|
||||
|
||||
```js
|
||||
// 冒頭: goal を固定
|
||||
MissionUpdate({ goal: "売上 CSV を月次集計して棒グラフ付き PDF にする" })
|
||||
|
||||
// 進行中: 完了分と残りを更新(goal はそのまま)
|
||||
MissionUpdate({ done: "- CSV パース\n- 月次集計", open: "- PDF レンダリング" })
|
||||
```
|
||||
|
||||
## 制約・注意
|
||||
|
||||
- 各フィールドは **2000 文字**で打ち切られる(超過分は `…[truncated]` 付きで切られる)。要点を簡潔に。
|
||||
- 全フィールドを空文字で渡すと Mission Brief は**クリア**される。
|
||||
- **サブタスクなど `local_task` に紐付かない実行コンテキストでは使えない**。その場合は no-op としてエラーを返すので、サブタスク側では呼ばないこと。
|
||||
- 保存単位はタスク(per-LocalTask)。ジョブや movement 単位ではないので、ASK ラウンドや follow-up メッセージをまたいでも保持される。
|
||||
|
||||
## 関連
|
||||
|
||||
ステップ間で得た教訓は `transition` / `complete` の `lessons` フィールドで記録する(Mission Brief とは別系統)。
|
||||
@@ -0,0 +1,44 @@
|
||||
# ReadAppDoc
|
||||
|
||||
MAESTRO のプロジェクト内ドキュメント(`docs/` / `pieces/`)を **symbolic name** で読み込むメタツール(常時利用可能)。主に Help アシスタントが、概念や操作手順をユーザーに答える前のリファレンス参照に使う。
|
||||
|
||||
ワークスペース外の固定パスを読むため、通常の `Read` ツールでは到達できない。
|
||||
|
||||
## 引数
|
||||
|
||||
| 引数 | 必須 | 説明 |
|
||||
|------|------|------|
|
||||
| `name` | はい | 読みたい doc の symbolic name(下記形式) |
|
||||
|
||||
## name の形式
|
||||
|
||||
| 形式 | 解決先 | 例 |
|
||||
|------|--------|-----|
|
||||
| `docs/<path>` | `docs/<path>.md`(`.md` は省略可) | `docs/mcp`, `docs/architecture` |
|
||||
| `piece/<name>` | `pieces/<name>.yaml` | `piece/chat`, `piece/research` |
|
||||
| `tool/<name>` | `docs/tools/<name>.md`(`ReadToolDoc` と同等) | `tool/browseweb` |
|
||||
|
||||
利用可能な doc が分からないときは、先に `ListAppDocs()` で一覧を取得する。
|
||||
|
||||
```js
|
||||
ListAppDocs() // まず一覧を見る
|
||||
ReadAppDoc({ name: "docs/mcp" }) // 該当 doc を読む
|
||||
ReadAppDoc({ name: "tool/browse-sessions" })
|
||||
```
|
||||
|
||||
## 読めないもの(allow-list / block)
|
||||
|
||||
セキュリティのため、開けるのは `docs/` `pieces/` `docs/tools/` 配下に限られ、以下は拒否される:
|
||||
|
||||
- 内部向けトップレベル doc: `CLAUDE.md` / `AGENTS.md` / `README.md` / `architecture`
|
||||
- パストラバーサル(`..` を含む name)や allow-list 外の絶対パス
|
||||
|
||||
## 制約
|
||||
|
||||
- 1 doc あたり **32KB** で打ち切り(超過分は末尾に omitted バイト数を注記)。
|
||||
- 存在しない name や不正な形式はエラーを返す。エラー時は `ListAppDocs()` で正しい名前を確認すること。
|
||||
|
||||
## 関連
|
||||
|
||||
- 一覧取得は [ListAppDocs](./listappdocs.md)。
|
||||
- ツール単体の詳細は `ReadToolDoc({ name: "..." })`(`ReadAppDoc({ name: "tool/..." })` と同じ docs/tools を読む)。
|
||||
@@ -28,7 +28,7 @@ RequestTool を呼んでも、そのツールが**その場で使えるように
|
||||
|
||||
- **既に利用可能**: そのツールはこの movement で使える → 記録せず「そのまま呼んでください」と返る。
|
||||
- **`requested`**: カタログに存在するがこの movement では未許可 → 設定漏れ候補として記録。
|
||||
- **`unknown`**: そんなツールは存在しない(名前の誤り・能力ギャップ)→ 記録するが付与対象外。
|
||||
- **`unknown`**: そんなツールは存在しない(名前の誤り・能力ギャップ)→ **エラーを返す**(実在ツール名のみ要求可)。診断のため記録は残るが、承認待ちにはならない。エラーを受けたら実在するツールで進めること。
|
||||
|
||||
## 関連
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# TestWorkspaceApp
|
||||
|
||||
ワークスペース・アプリをヘッドレスブラウザで実起動し、操作・期待値検証・ファイルの変化確認を行う E2E テストツール。`workspace-app` ピースの verify ステップが自動的に呼び出す。
|
||||
|
||||
## 入力パラメータ
|
||||
|
||||
| パラメータ | 型 | 必須 | 説明 |
|
||||
|-----------|-----|------|------|
|
||||
| `space` | string | ✓ | テスト対象スペースの ID |
|
||||
| `app` | string | ✓ | `apps/` 配下のアプリフォルダ名(パスではなく名前のみ) |
|
||||
| `entry` | string | — | エントリー HTML のパス(スペースファイルルートからの相対)。省略時は `apps/{app}/index.html` |
|
||||
| `seed_files` | array | — | テスト前にワークスペースへ書き込むファイル群(下記参照) |
|
||||
| `steps` | array | — | ブラウザアクション列(`BrowseWebAction` と同じ形式、下記参照) |
|
||||
| `expect` | array | — | 検証する期待値リスト(下記参照) |
|
||||
| `timeout_ms` | number | — | ブラウザ操作全体のタイムアウト(ミリ秒、デフォルト 30000) |
|
||||
|
||||
### seed_files
|
||||
|
||||
テスト開始前にワークスペースへ書き込むファイルを指定する。アプリが読み取る入力データを事前配置するために使う。
|
||||
|
||||
```json
|
||||
"seed_files": [
|
||||
{ "path": "output/notes.md", "content": "# Hello\nworld" }
|
||||
]
|
||||
```
|
||||
|
||||
- `path` はワークスペースルートからの相対パス(`resolveAndGuard` でパストラバーサルを防ぐ)
|
||||
- `content` は UTF-8 文字列
|
||||
- 書き込んだファイルは `file_changes` に記録される(bytes はシード時の書き込みサイズ)
|
||||
|
||||
### steps(BrowseWebAction 形式)
|
||||
|
||||
ハーネスページ上で実行するブラウザ操作を配列で渡す。型は `BrowseWebAction` と共通。
|
||||
|
||||
| type | 追加フィールド | 説明 |
|
||||
|------|--------------|------|
|
||||
| `goto` | `url` | 指定 URL へ移動(ハーネス URL は自動で最初に追加されるため通常不要) |
|
||||
| `click` | `selector` または `ref` | 要素をクリック |
|
||||
| `fill` | `selector` または `ref`, `value` | フォーム入力 |
|
||||
| `screenshot` | `value`(ファイル名) | スクリーンショットを撮影 |
|
||||
| `getText` | — | ページのテキストを取得(期待値チェックに使われる) |
|
||||
| `dumpHtml` | — | ページの HTML を取得 |
|
||||
| `wait` | `ms` | 指定ミリ秒待機 |
|
||||
|
||||
ハーネスは起動時に自動的に `goto → wait(2000ms) → getText` を実行してからユーザー指定ステップを追加する。`goto` を先頭に追加する必要はない。
|
||||
|
||||
### expect(期待値リスト)
|
||||
|
||||
| kind | フィールド | 説明 |
|
||||
|------|-----------|------|
|
||||
| `text` | `target`(CSS セレクタ), `contains` | ページテキスト全体が `contains` を含む。`target` は情報的なセレクタ(精度向上には `getText` アクションを先に実行) |
|
||||
| `file` | `target`(ワークスペース相対パス), `contains` | 指定ファイルが存在し、その内容が `contains` を含む |
|
||||
|
||||
**重要**: `kind` は `"text"` または `"file"` のみ有効。他の値は常に失敗として記録される(サイレントパスなし)。`contains` が空文字列の場合も評価は行われる(空文字列はどの文字列にも含まれるため通過)。
|
||||
|
||||
```json
|
||||
"expect": [
|
||||
{ "kind": "text", "target": "[data-testid=\"status\"]", "contains": "保存OK" },
|
||||
{ "kind": "file", "target": "output/notes.md", "contains": "edited-by-e2e" }
|
||||
]
|
||||
```
|
||||
|
||||
## 戻り値
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"failures": [],
|
||||
"screenshots": ["app-test-myapp-final.png"],
|
||||
"console_errors": [],
|
||||
"bridge_calls": [],
|
||||
"file_changes": [
|
||||
{ "path": "output/notes.md", "bytes": 14 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| フィールド | 説明 |
|
||||
|-----------|------|
|
||||
| `ok` | `failures` が空なら `true` |
|
||||
| `failures` | 失敗した期待値の説明(配列) |
|
||||
| `screenshots` | 撮影したスクリーンショットのファイル名一覧 |
|
||||
| `console_errors` | ブラウザコンソールのエラーメッセージ |
|
||||
| `bridge_calls` | **V1 では常に `[]`**(ヘッドレス環境ではブリッジ呼び出しの観測不可) |
|
||||
| `file_changes` | seed_files で書き込んだファイルの一覧と byte 数 |
|
||||
|
||||
## テンプレートの `e2e.example.json` を使う
|
||||
|
||||
`docs/examples/workspace-apps/{note-editor,data-viewer,form-input,dashboard}/e2e.example.json` にひな型が入っている。TestWorkspaceApp に渡す JSON の例として使えるので、新しいアプリを作る際はここをコピーして改変する。
|
||||
|
||||
### note-editor の例
|
||||
|
||||
```json
|
||||
{
|
||||
"app": "note-editor",
|
||||
"seed_files": [
|
||||
{ "path": "output/note.md", "content": "seeded-content" }
|
||||
],
|
||||
"steps": [
|
||||
{ "type": "click", "selector": "[data-testid=\"load\"]" },
|
||||
{ "type": "getText", "selector": "[data-testid=\"status\"]" },
|
||||
{ "type": "fill", "selector": "[data-testid=\"body\"]", "value": "edited-by-e2e" },
|
||||
{ "type": "click", "selector": "[data-testid=\"save\"]" },
|
||||
{ "type": "getText", "selector": "[data-testid=\"status\"]" }
|
||||
],
|
||||
"expect": [
|
||||
{ "kind": "text", "target": "[data-testid=\"status\"]", "contains": "保存OK" },
|
||||
{ "kind": "file", "target": "output/note.md", "contains": "edited-by-e2e" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事項(ゴッチャ)
|
||||
|
||||
### (a) seed_files の書き込みは本物の `output/` に入る
|
||||
|
||||
seed_files で書き込んだファイルはワークスペースの実際のパスに書かれる。テスト用の一時領域ではなく、`output/note.md` を指定すれば本物の `output/note.md` が変更される。テスト後のクリーンアップはユーザー(またはエージェント)が行う必要がある。
|
||||
|
||||
### (b) 書き込み確認ダイアログはハーネス側で自動承認される
|
||||
|
||||
ブラウザ内の `writeFile` / `deleteFile` ブリッジ呼び出しは、`/app-harness` ルートではダイアログが自動承認される。本番の `/ui/app/...` 上では確認ダイアログが表示されるので挙動が異なる。
|
||||
|
||||
### (c) UI のビルドが必要
|
||||
|
||||
`/app-harness` ルートは Vite でビルドされた UI から配信される。`npm run build:ui` を実行していないと 404 になり、テストが失敗する。開発中は `cd ui && npm run dev` を起動してから dev モードで試すこともできる。
|
||||
|
||||
### (d) bridge_calls は V1 では常に空
|
||||
|
||||
ヘッドレスブラウザ環境ではブリッジ呼び出し(`readFile` / `writeFile` など)の観測が技術的に困難なため、`bridge_calls` フィールドは常に `[]` を返す。ブリッジの動作確認は `expect` の `kind: "file"` でファイルの変化を確認することで代替する。
|
||||
|
||||
## ワークフロー例
|
||||
|
||||
```
|
||||
# 1. アプリを作成
|
||||
Write({ path: "apps/note-editor/index.html", content: "..." })
|
||||
|
||||
# 2. E2E テストで動作確認
|
||||
TestWorkspaceApp({
|
||||
space: ctx.spaceId,
|
||||
app: "note-editor",
|
||||
seed_files: [{ path: "output/test-note.md", content: "# Test" }],
|
||||
steps: [
|
||||
{ type: "wait", ms: 1500 },
|
||||
{ type: "getText" },
|
||||
{ type: "screenshot", value: "after-load.png" }
|
||||
],
|
||||
expect: [
|
||||
{ kind: "text", target: "body", contains: "Test" }
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
テストが通れば `ok: true` が返り、failures は空になる。
|
||||
@@ -19,26 +19,84 @@ JS/CSS、`data:` 画像のみ)。ユーザーはワークスペースの「ア
|
||||
- **パスはワークスペース相対**: `output/...`・`apps/{name}/data/...` への書き込みは確認不要。
|
||||
それ以外への書き込み・削除はユーザー確認が出る。`..`・絶対パスは拒否される。
|
||||
|
||||
## ブリッジ API(要点)
|
||||
## ブリッジ API(このヘルパーをそのまま使うこと)
|
||||
|
||||
アプリは `window.parent.postMessage({ ...req, id }, '*')` で要求を送り、応答は `window` の
|
||||
`message` イベントで受け取る。応答エンベロープは **`{ id, ok: true, data }`** か
|
||||
**`{ id, ok: false, error }`**。`id` を突き合わせ、`ok` を見て `data` を取り出す。
|
||||
次の `call()` ヘルパーが正典実装。**改変せずそのままコピーして使う**(自己流で書くと
|
||||
エンベロープを取り違えて動かない)。
|
||||
|
||||
```html
|
||||
<script>
|
||||
let seq = 0;
|
||||
const pending = new Map();
|
||||
function call(req) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = ++seq;
|
||||
pending.set(id, { resolve, reject });
|
||||
window.parent.postMessage({ ...req, id }, '*');
|
||||
});
|
||||
}
|
||||
window.addEventListener('message', (e) => {
|
||||
const r = e.data;
|
||||
const p = pending.get(r && r.id);
|
||||
if (!p) return;
|
||||
pending.delete(r.id);
|
||||
r.ok ? p.resolve(r.data) : p.reject(new Error(r.error));
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
使い方(`call()` は成功時に応答の `data` を返す):
|
||||
|
||||
```js
|
||||
// 一意 id を付けて postMessage、message イベントで応答を id 突き合わせ。
|
||||
const { entries } = await call({ type: 'listFiles', dir: 'output' }); // 一覧
|
||||
const { content } = await call({ type: 'readFile', path: 'output/x.md' }); // 読み取り
|
||||
await call({ type: 'writeFile', path: 'output/note.md', content: '...' }); // 書き込み
|
||||
await call({ type: 'deleteFile', path: 'output/old.txt' }); // 削除(常に確認)
|
||||
```
|
||||
|
||||
完全なプロトコル・最小ヘルパー実装・応答形式は **`docs/workspace-apps-bridge.md`** を参照。
|
||||
確認なしで書ける場所は `output/` 配下と自分のアプリの `apps/{name}/data/` 配下のみ。
|
||||
それ以外への `writeFile`・すべての `deleteFile` はユーザー確認ダイアログが出る。
|
||||
`..`・絶対パスは拒否される。
|
||||
|
||||
## ひな型をコピーする
|
||||
|
||||
動くサンプルが `docs/examples/workspace-apps/file-note/` にある(`index.html` + `app.json`)。
|
||||
`output/` を一覧・閲覧し、メモを `output/note.md` に保存する自己完結アプリ。これを土台に、
|
||||
依頼に合わせて改変して `apps/{name}/index.html` に Write するのが最短。
|
||||
ワークスペース内の `readonly/app-templates/` に、複数のアーキタイプテンプレートが
|
||||
**seed 済み**(workspace-app ピース実行時に自動コピーされる)。Read できる実ファイルなので、
|
||||
`Glob({ pattern: "readonly/app-templates/*/index.html" })` で一覧を確認できる。
|
||||
|
||||
| フォルダ | 用途 |
|
||||
|---------|------|
|
||||
| `note-editor/` | `output/note.md` を読み書きするテキストエディタ |
|
||||
| `data-viewer/` | CSV・JSONL ファイルをテーブル表示するビューア |
|
||||
| `form-input/` | フォームで入力した内容を `output/` に書き出す |
|
||||
| `dashboard/` | 複数ファイルの集計結果をカードで表示するダッシュボード |
|
||||
|
||||
各フォルダに `index.html`・`app.json`・`e2e.example.json` が入っている。
|
||||
`e2e.example.json` は後述の E2E テストに直接渡せる入力例。
|
||||
いちばん近いものを `Read readonly/app-templates/{名前}/index.html` で読み、
|
||||
`apps/{name}/index.html` に Write するのが最短。テンプレには上記の正しいブリッジ実装が
|
||||
組み込み済みなので、ファイル I/O もそのまま動く。
|
||||
|
||||
`apps/{name}/app.json`(任意)で一覧の表示名・説明・エントリを指定できる(無ければフォルダ名)。
|
||||
|
||||
## E2E テスト(verify ステップ)
|
||||
|
||||
`workspace-app` ピースの verify ステップは、作成したアプリを **`TestWorkspaceApp` ツールで自動 E2E テスト** する。
|
||||
|
||||
- ヘッドレスブラウザでアプリを実際に起動
|
||||
- `seed_files` で指定した入力データをワークスペースに書き込んでからロード
|
||||
- `actions` に沿ってクリック・入力・スクリーンショット操作を実行
|
||||
- `expect` 条件(テキスト含有・ファイル存在・要素可視)を検証
|
||||
|
||||
テストに通過した場合のみ verify ステップを完了とする。失敗した場合はエラー内容と
|
||||
スクリーンショットが返るので、それをもとに HTML を修正して再実行する。
|
||||
|
||||
E2E の入力例は各テンプレートの `e2e.example.json` を参照。詳細な使い方は
|
||||
`ReadToolDoc({ name: "TestWorkspaceApp" })` で取得できる。
|
||||
|
||||
## 仕上げ
|
||||
|
||||
- 書き込んだら、ユーザーに「アプリ」タブの「開く」で起動できることを一言伝える。
|
||||
|
||||
Reference in New Issue
Block a user