@@ -15,7 +15,7 @@ docker compose up -d # 初回はイメージをビルドしてから起動
|
||||
|
||||
`.env` や `config.yaml` は用意しなくても起動します。未設定のまま起動すると UI にセットアップウィザードが開き、そこから LLM の接続先を設定できます。先に指定しておきたい場合は `cp .env.example .env` して `OLLAMA_BASE_URL` / `OLLAMA_MODEL` を設定します([LLM エンドポイント](#llm-エンドポイント)参照)。
|
||||
|
||||
Compose は UI を `127.0.0.1:9876` だけに公開するので、初期状態では LAN から到達できません。外部公開は[ローカル以外へ出す](#ローカル以外へ出す)を参照。
|
||||
Compose は `9876` を**全インターフェース**に公開するので、Docker ホストは初期状態から LAN で到達できます。裏を返すと初期状態は**認証なしで露出**しており、ポートに届く相手はエージェントの Bash ツール経由でホスト上のコードを実行できます。共有ネットワークに置いたままにする前に[ローカル以外へ出す](#ローカル以外へ出す)を必ず読んでください。
|
||||
|
||||
### Windows(WSL2)
|
||||
|
||||
@@ -94,12 +94,13 @@ bubblewrap は非特権ユーザー名前空間を必要とします。多くの
|
||||
|
||||
## ローカル以外へ出す
|
||||
|
||||
既定のバインドは意図的にローカル限定です。ネットワークへ公開する前に:
|
||||
既定で Compose は `9876` を全インターフェースに公開するため、インスタンスは初期状態から LAN で到達でき、**かつ認証なし**です。共有ネットワーク・信頼できないネットワークで常用する前に必ず堅牢化してください:
|
||||
|
||||
1. 認証を有効化(OAuth、またはローカルアカウント)。
|
||||
2. `safety.bash_sandbox: always` を設定。
|
||||
3. TLS を終端(MAESTRO のネイティブ HTTPS か、前段のリバースプロキシ)。
|
||||
4. compose のポートマッピングを `127.0.0.1:9876:9876` から、公開したいインターフェースへ変更。
|
||||
|
||||
逆に**ローカル限定**に戻したい場合(SSH トンネル経由で使う等)は、compose のポートマッピングを `127.0.0.1:9876:9876` に固定します。
|
||||
|
||||
完全な堅牢化チェックリストは [SECURITY.md](../SECURITY.md) と [getting-started.md](getting-started.md) を参照。
|
||||
|
||||
|
||||
@@ -20,8 +20,11 @@ opens a setup wizard in the UI where you point MAESTRO at your LLM. To preset th
|
||||
endpoint instead, `cp .env.example .env` and set `OLLAMA_BASE_URL`/`OLLAMA_MODEL`
|
||||
(see [The LLM endpoint](#the-llm-endpoint)).
|
||||
|
||||
Compose publishes the UI on `127.0.0.1:9876` only, so a fresh instance is not
|
||||
reachable from your LAN. See [Going beyond localhost](#going-beyond-localhost).
|
||||
Compose publishes the UI on `9876` on **all host interfaces**, so a Docker host
|
||||
is reachable from your LAN out of the box. That also means a fresh instance is
|
||||
exposed and **unauthenticated** — anyone who can reach the port can run code on
|
||||
the host via the agent's Bash tool. Read [Going beyond localhost](#going-beyond-localhost)
|
||||
before leaving it up on a shared network.
|
||||
|
||||
### Windows (WSL2)
|
||||
|
||||
@@ -129,14 +132,16 @@ Rebuild after pulling new code with `docker compose up -d --build`.
|
||||
|
||||
## Going beyond localhost
|
||||
|
||||
The default binding is intentionally local-only. Before exposing MAESTRO to a
|
||||
network:
|
||||
By default Compose publishes `9876` on all interfaces, so the instance is already
|
||||
reachable from your LAN — **and unauthenticated**. Before you rely on it on any
|
||||
shared or untrusted network, harden it:
|
||||
|
||||
1. Enable authentication (OAuth, or local accounts).
|
||||
2. Set `safety.bash_sandbox: always`.
|
||||
3. Terminate TLS — either MAESTRO's native HTTPS or a reverse proxy in front.
|
||||
4. Change the compose port mapping from `127.0.0.1:9876:9876` to the interface
|
||||
you intend to serve.
|
||||
|
||||
To keep an instance **local-only** instead (e.g. reach it over an SSH tunnel),
|
||||
pin the compose port mapping back to `127.0.0.1:9876:9876`.
|
||||
|
||||
See [SECURITY.md](../SECURITY.md) and
|
||||
[getting-started.md](getting-started.md) for the full hardening checklist.
|
||||
|
||||
@@ -101,7 +101,7 @@ docker compose up -d
|
||||
# http://localhost:9876
|
||||
```
|
||||
|
||||
DB とワークスペースは named volume(`maestro-data` / `maestro-workspaces`)に永続化される。Compose は既定で `127.0.0.1:9876` のみに公開する。`config.yaml` をホストからマウントする場合は `docker-compose.yml` のコメントを参照。
|
||||
DB とワークスペースは named volume(`maestro-data` / `maestro-workspaces`)に永続化される。Compose は既定で `9876` を全インターフェースに公開するため、インスタンスは LAN から到達でき、`auth` を設定するまで認証なしのまま。共有ネットワークに置く前に認証・TLS を有効化するか、ローカル限定にしたい場合はマッピングを `127.0.0.1:9876:9876` に固定する。`config.yaml` をホストからマウントする場合は `docker-compose.yml` のコメントを参照。
|
||||
|
||||
### ブラウザ・セットアップウィザード(`config.yaml` を編集しない)
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ docker compose up -d
|
||||
# http://localhost:9876
|
||||
```
|
||||
|
||||
The DB and workspaces are persisted in named volumes (`maestro-data` / `maestro-workspaces`). By default Compose exposes only `127.0.0.1:9876`. If you want to mount `config.yaml` from the host, see the comments in `docker-compose.yml`.
|
||||
The DB and workspaces are persisted in named volumes (`maestro-data` / `maestro-workspaces`). By default Compose publishes `9876` on all interfaces, so the instance is reachable from your LAN — and unauthenticated until you configure `auth`. Enable auth/TLS before leaving it on a shared network, or pin the mapping to `127.0.0.1:9876:9876` for local-only. If you want to mount `config.yaml` from the host, see the comments in `docker-compose.yml`.
|
||||
|
||||
### Browser setup wizard (no `config.yaml` editing)
|
||||
|
||||
|
||||
@@ -620,6 +620,41 @@ grep -n "a2a_delegations\|a2a_skills" src/db/repository.ts
|
||||
|
||||
---
|
||||
|
||||
## 16. OSS リリース(`scripts/oss-sync.sh --push`)を実行するとき
|
||||
|
||||
- **スクリーンショットの目視確認(必須)**: `docs/screenshots/*.png` はバイナリのため、release gate(`oss/forbidden.txt` の正規表現スキャン)では**描画内容を検査できない**。スクリーンショットを更新した場合は、push 前に各画像を開いて以下が写り込んでいないか目視で確認する
|
||||
- 内部ホスト名・URL(Gitea / 社内サービスのアドレスバー等)
|
||||
- 実在のタスクタイトル・会話内容・ファイル名
|
||||
- ユーザー名・メールアドレス・組織名
|
||||
- 資格情報やトークンの断片(設定画面のマスク漏れ)
|
||||
- 撮り直しの手順は README スクリーンショット刷新時のレシピ(隔離 worktree + シードデータ)に従い、実データ環境のキャプチャをそのまま使わない
|
||||
|
||||
---
|
||||
|
||||
## 17. 承認パーク型 META ツール(RequestTool / RequestPackage)を追加・変更する場合
|
||||
|
||||
エージェントが「これが欲しい」と申告 → 人が承認 → ジョブ再開、という停車型ツールの配線点。RequestPackage(PR3)は RequestTool を全面ミラーしている。片方だけ直すと不整合になるので、両方を並べて確認すること。
|
||||
|
||||
**対象ファイル(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/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 を注入
|
||||
- `src/worker.ts` — recorder を task/job/space にバインド(feature enabled かつ space 解決可のときのみ)+ waiting_human 分岐で **`wait_reason='package_request'` を永続化**(忘れると resume がマッチせず永久停止)
|
||||
- **DB(三重ミラー)**: `src/db/schema.sql` / `src/db/migrate.ts` / `src/db/repositories/schema.ts`(`package_requests` テーブル)+ `src/db/repositories/tool-requests.ts`(record/get/list/decide/revert/listDeclined + 型 + rowMapper)+ `src/db/repositories/jobs.ts`(`resumePackageRequestJob` + `createJobIfNoPending` の承認ポーズ判定)。実装は `repositories/*` に置き、`src/db/repository.ts` ファサードに委譲メソッドを足す(`repository.ts` は肥大分割済みの薄いファサード)
|
||||
- `src/bridge/local-tasks-package-requests-api.ts` — GET 一覧(view 権限)+ POST decide(**task write 権限= owner/admin/space editor のみ**、エージェント自己承認不可)。承認時のインストールは `space-python-packages-service.ts` の共有パスを通す
|
||||
- `src/bridge/local-tasks-api.ts` — ルート登録 + `LocalTasksApiOptions` に config アクセサ / `server.ts` で配線
|
||||
- `ui/src/components/chat/PackageRequestApproval.tsx` + `ChatPane.tsx` + `ui/src/api/tasks.ts` + i18n `chat.json`(ja/en)
|
||||
- `docs/tools/requestpackage.md`(ReadToolDoc)+ ヘルプ + changelog
|
||||
|
||||
**なぜ必要か:**
|
||||
停車系は「ジョブ停車=ワーカー解放」で成り立つ(ツール内同期ブロックはデッドロック)。sentinel(`WAITING_HUMAN_*`)は tool → pending-states → agent-loop → piece-runner → **worker(wait_reason 永続化)** の全経路が揃って初めて機能する。承認 API は必ず write 権限ゲートを通し、インストールは共有サービス(bwrap 必須 fail-closed / wheels のみ / 固定 index / shadowing 拒否)経由に限定する。
|
||||
|
||||
---
|
||||
|
||||
## 自動検知の可能性
|
||||
|
||||
- **ツールモジュール登録漏れ**: `index.ts` と `tools-api.ts` のモジュール一覧を比較するスクリプトで CI チェック可能
|
||||
|
||||
|
Before Width: | Height: | Size: 291 KiB After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 378 KiB After Width: | Height: | Size: 326 KiB |
@@ -8,12 +8,12 @@ MAESTRO は LLM 駆動のタスクを実行し、その中でコード実行・W
|
||||
|
||||
## 脅威モデル(一段落)
|
||||
|
||||
UI/API に到達できる者はタスクを作成でき、タスクはツール(Bash・Web・ブラウザ・ファイル、そして有効なら SSH/MCP)を実行できます。認証が無ければ、ポートに到達できる者は誰でもホスト上でコードを実行できることになります。だから既定のデプロイは**ローカル限定・認証なし**です。以下はそれを安全に広げる手順です。
|
||||
UI/API に到達できる者はタスクを作成でき、タスクはツール(Bash・Web・ブラウザ・ファイル、そして有効なら SSH/MCP)を実行できます。認証が無ければ、ポートに到達できる者は誰でもホスト上でコードを実行できることになります。既定では**認証なし**で、ベアメタルでは localhost にバインドしますが、Docker の既定はポートを LAN に公開します。露出したまま放置する前に固めるのは運用者の責任です。以下がそのチェックリストです。
|
||||
|
||||
## 1. ネットワーク公開
|
||||
|
||||
- アプリは既定で `127.0.0.1` にバインドし(ベアメタル)、Docker Compose は `127.0.0.1:9876` のみを公開します。認証と TLS が整うまでこのままに。
|
||||
- 公開する際は、バインド/ポート(`server.port`、Compose のポートマッピング)を意図的に変更し、TLS を前段に置きます(MAESTRO のネイティブ HTTPS `server.tls` か、リバースプロキシ)。
|
||||
- アプリはベアメタルでは既定で `127.0.0.1` にバインドしますが、Docker Compose は `9876` を**全インターフェース**に公開するため、Docker デプロイは初期状態から LAN 到達可能・認証なしです。共有ネットワークに置いたままにする前に認証と TLS を有効化してください。ローカル限定に戻すには Compose のマッピングを `127.0.0.1:9876:9876` に固定します(SSH トンネル経由で到達)。
|
||||
- loopback を超えて到達可能な間は、常に TLS を前段に置きます(MAESTRO のネイティブ HTTPS `server.tls` か、リバースプロキシ)。
|
||||
|
||||
## 2. 認証
|
||||
|
||||
|
||||
@@ -15,18 +15,20 @@ secrets live and their file permissions, see its *Secrets and Data* section.
|
||||
|
||||
Anyone who can reach the UI/API can create tasks, and a task can run tools
|
||||
(Bash, web, browser, files, and — if enabled — SSH/MCP). Without authentication
|
||||
that means anyone who can reach the port can run code on the host. The default
|
||||
deployment is therefore **localhost-only and unauthenticated**; everything below
|
||||
is about safely widening that.
|
||||
that means anyone who can reach the port can run code on the host. It ships
|
||||
**unauthenticated**: on bare metal it binds to localhost, but the Docker default
|
||||
publishes the port to your LAN — so securing it before you leave it exposed is
|
||||
on you. Everything below is that checklist.
|
||||
|
||||
## 1. Network exposure
|
||||
|
||||
- The app binds to `127.0.0.1` by default (bare metal) and Docker Compose
|
||||
publishes only `127.0.0.1:9876`. Keep it that way until auth and TLS are in
|
||||
place.
|
||||
- When you do expose it, change the bind/port deliberately (`server.port`, the
|
||||
Compose port mapping) and front it with TLS — either MAESTRO's native HTTPS
|
||||
(`server.tls`) or a reverse proxy.
|
||||
- The app binds to `127.0.0.1` by default on bare metal, but Docker Compose
|
||||
publishes `9876` on **all interfaces** by default — so a Docker deployment is
|
||||
LAN-reachable and unauthenticated out of the box. Enable auth and TLS before
|
||||
leaving it on any shared network, or pin the Compose mapping to
|
||||
`127.0.0.1:9876:9876` (and reach it over an SSH tunnel) to keep it local-only.
|
||||
- Front it with TLS — either MAESTRO's native HTTPS (`server.tls`) or a reverse
|
||||
proxy — whenever it is reachable beyond loopback.
|
||||
|
||||
## 2. Authentication
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# RequestPackage
|
||||
|
||||
Request a Python wheel that is not already available, so an approver can install
|
||||
it into **this workspace** and let the run continue.
|
||||
|
||||
## When to use it
|
||||
|
||||
You tried to `import somelib` (in `Bash` running `python3`, or `RunUserScript`)
|
||||
and it failed with `ModuleNotFoundError`, and the library is **not** in the
|
||||
preinstalled set. Instead of retrying `pip install` (which is blocked in the
|
||||
sandbox), call:
|
||||
|
||||
```
|
||||
RequestPackage({ name: "httpx", reason: "need an async HTTP client for the API calls" })
|
||||
RequestPackage({ name: "pandas==2.2.2", reason: "pinned for reproducibility" })
|
||||
```
|
||||
|
||||
- `name` — the package. Either a bare name (`httpx`) or an exact pin
|
||||
(`httpx==0.27.0`). **Only `name==version` pins are accepted** — ranges
|
||||
(`>=`, `~=`) and URLs/extras are rejected (the overlay must be reproducible).
|
||||
- `reason` — a concrete justification. It is shown to the approver.
|
||||
|
||||
## What happens
|
||||
|
||||
- **Interactive runs (a user is watching the chat):** the movement pauses and an
|
||||
Approve / Deny card appears in the chat. When a task-write user (owner / admin /
|
||||
space editor) approves, the wheel is installed into this workspace's private
|
||||
overlay and the run resumes automatically — now `import` works. If denied, you
|
||||
continue without it.
|
||||
- **Non-interactive runs (subtasks / scheduled):** the request is recorded (the
|
||||
user finds it later) and you proceed **without** the package. Don't block on it —
|
||||
finish with what you have, or `complete({status:"needs_user_input"})` if you
|
||||
genuinely can't proceed.
|
||||
|
||||
## Rules and limits
|
||||
|
||||
- Wheels only, from a fixed package index. No source builds, no arbitrary index.
|
||||
- A package that would shadow the standard library or a preinstalled package
|
||||
(e.g. `os`, `numpy`, `pandas`) is rejected — those are already importable.
|
||||
- If the package is already installed in this workspace, the tool tells you to
|
||||
just `import` it (no approval needed).
|
||||
- The install is per-workspace. Other workspaces do not see it.
|
||||
- You cannot approve your own request — approval is a human/operator action.
|
||||
|
||||
## After approval
|
||||
|
||||
The run re-enters the same step. Re-run your Python; the import now succeeds. If
|
||||
you call `RequestPackage` again for the same package, it reports "already
|
||||
installed".
|
||||
|
||||
Related: preinstalled packages are listed in the error you get from a blocked
|
||||
`pip install`. Workspace-wide package management (for operators) lives in
|
||||
**Settings → the workspace's Python packages panel**.
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
| 名前 | 必須 | 説明 |
|
||||
|------|------|------|
|
||||
| `query` | ○ | 検索キーワード。部分一致・大文字小文字は無視 |
|
||||
| `query` | ○ | 検索キーワード。スペース区切りで複数指定すると AND 検索(すべての語を含む発言だけがヒット)。各語は部分一致・大文字小文字は無視 |
|
||||
| `source` | | `comments` / `transcript` / `both`(既定 `both`) |
|
||||
| `author` | | `user` / `agent` / `system` で発言者を絞る |
|
||||
| `kind` | | コメント種別で絞る(request/comment/interjection/result/ask/progress/handoff)。transcript には適用されない |
|
||||
|
||||
@@ -13,8 +13,8 @@ Piece の取得・編集には GetPiece / CreatePiece / UpdatePiece を使う。
|
||||
|
||||
## ツール
|
||||
|
||||
- **InstallSkill** — スキルを保存する。通常は `content` に SKILL.md 全文(YAML frontmatter + 本文)を渡す。workspace 内に `SKILL.md` と `scripts/` 等を含むディレクトリを組み立て済みなら `sourcePath`(workspace 内の絶対パス)を渡す。`scope` は `user`(個人 or 共有ワークスペース)か `system`(全ユーザー共有・admin のみ)。
|
||||
- **ReadSkill** — スキル本文を取得する。ディレクトリ型スキルは workspace の `skills/{name}/` に展開され、その相対パスでスクリプトを実行できる。
|
||||
- **InstallSkill** — スキルを保存する。通常は `content` に SKILL.md 全文(YAML frontmatter + 本文)を渡す。workspace 内に `SKILL.md` と `scripts/` 等を含むディレクトリを組み立て済みなら `sourcePath` に **workspace 相対パス**(例: `output/my-skill`)を渡す。ホスト上のフルパスは不要(パス中に workspace の実配置(`space/{id}/files` 等)が含まれていれば自動で workspace 相対に読み替える)。`scope` は `user`(個人 or 共有ワークスペース)か `system`(全ユーザー共有・admin のみ)。**`name` パラメータは SKILL.md frontmatter の `name:` と完全一致が必須**(不一致だと一覧上の表示名と保存フォルダ名がズレて UI から管理不能になるため、インストール自体を拒否する)。frontmatter に有効な `name:`(`[a-z0-9_-]`)が無い SKILL.md も拒否される。
|
||||
- **ReadSkill** — スキル本文を取得する。ディレクトリ型スキルは workspace の `skills/{name}/` に展開され、その相対パスでスクリプトを実行できる。展開済みでもスキル元が更新されていれば、次の ReadSkill でコピーを丸ごと最新版に置き換える(元が未変更なら workspace 内コピーへの手直しは維持される)。
|
||||
- **ListSkills** — インストール済みスキルの一覧を返す。
|
||||
|
||||
## スコープと可視性(重要)
|
||||
|
||||
@@ -87,13 +87,39 @@ XTimeline({
|
||||
|
||||
```js
|
||||
XPostDetail({
|
||||
url: "https://twitter.com/.../status/1234567890",
|
||||
// または status_id: "1234567890"
|
||||
tweet: "https://x.com/.../status/1234567890",
|
||||
// tweet ID だけでも、長文記事 URL (https://x.com/.../article/1234567890) でも可
|
||||
})
|
||||
```
|
||||
|
||||
返り値にはリプライツリーが含まれる。議論の流れを追いたいときに使う。
|
||||
|
||||
### X 長文記事(X Articles)
|
||||
|
||||
対象が長文記事ポストの場合、`text` は記事への t.co リンク 1 本だけになる。
|
||||
記事の中身は `article` キーに入る:
|
||||
|
||||
```yaml
|
||||
data:
|
||||
- id: '1234567890'
|
||||
text: https://t.co/xxxx # 記事ポストの本文はリンクのみ
|
||||
article:
|
||||
title: 記事タイトル
|
||||
previewText: 冒頭のプレビュー文
|
||||
plainText: 記事本文の全文(プレーンテキスト)
|
||||
publishedAtISO: '2026-07-02T00:04:51+00:00'
|
||||
coverImageUrl: https://pbs.twimg.com/media/XXXX.jpg
|
||||
```
|
||||
|
||||
- `plainText` はデフォルトで 12,000 文字で切り詰め、`plainTextTruncated: true` が付く。
|
||||
全文が必要なら `full_text: true` を渡す
|
||||
- **記事内の埋め込み画像・動画は `media[]` に合流する**(先頭 20 件まで)。通常の
|
||||
投稿メディアと同じく自動ダウンロードされ `localPath` が付くので、そのまま
|
||||
ReadImage / AnnotateImage に渡せる。動画の扱いは `tools.x_download_video` に従う
|
||||
- カバー画像(`coverImageUrl`)は自動ダウンロード対象外。必要なら DownloadFile に渡す
|
||||
- 記事ページ(`x.com/{user}/article/{id}`)を WebFetch で読むことはできない
|
||||
(ログイン壁で失敗する)。記事は必ずこのツールで取得する
|
||||
|
||||
## 出力フォーマット
|
||||
|
||||
- デフォルト: 構造化テキスト(投稿者・本文・いいね数等)
|
||||
|
||||