commit 7049a874f3c4419a1abdf451492fe8c45366b434 Author: clade Date: Tue Jun 2 07:19:34 2026 +0000 feat: initial public release (MAESTRO v0.1.0) Open-source release of MAESTRO, an agent orchestration platform that runs LLM-driven tasks through sandboxed tools, with a web UI. Apache-2.0. See README.md and docs/ (getting-started, configuration, architecture). diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..cf8c548 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +GITEA_API_TOKEN=your-gitea-token +GITEA_WEBHOOK_SECRET=your-webhook-secret +OLLAMA_BASE_URL=http://localhost:11434/v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..919079e --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +node_modules/ +dist/ +data/ +.env +config.yaml +.superpowers/ +*.db +*.db-wal +*.db-shm +input/ +output/ +logs/ +.worktrees/ +.claude/ +.playwright-mcp/ +.superpowers/ +orch.pid +.server.pid +src/generated/ +.worktrees/ +vendor/ +# Added by code-review-graph +.code-review-graph/ + +# Local debugging / scratch scripts (kept out of tree) +scripts/dump_payload.mjs + +# Benchmark output (run results / copied workspaces) +bench/results/ +.context/ + +data/secrets/master.key +data/browser-sessions/* +!data/browser-sessions/.gitkeep +.gstack/ + +# Core dumps from native crashes (sqlite/playwright/sharp). These contain raw +# process memory — including the decrypted master key, SSH private keys and the +# session secret — so they must never be committed. +core +core.* diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..444579b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,134 @@ +# AGENTS.md — Codebase orientation for contributors + +This is a map of the MAESTRO codebase for contributors (and AI coding agents). +For build/test/PR mechanics see [CONTRIBUTING.md](CONTRIBUTING.md); for the +request lifecycle in depth see [docs/architecture.md](docs/architecture.md). + +## Working norms + +- State conclusions and corrections directly; don't pad with reflexive agreement. +- Before editing code, understand the blast radius of a change (callers, tests). + If a change is large or risky, describe the approach before implementing. +- Investigate reported bugs by reproducing the actual behavior before concluding. + +## Execution flow + +``` +UI POST + → bridge/server.ts (Express API) + → Repository (SQLite: jobs table) + → Worker.poll() picks up a queued job + → piece-runner.ts: loads pieces/*.yaml + → agent-loop.ts: ReAct loop (LLM ↔ tool calls, up to safety.maxIterations) + → each movement completes → `transition` (intermediate) / `complete` (terminal) + → job finishes → DB update / progress comment +``` + +## Main layers (`src/`) + +- **`engine/piece-runner.ts`** — loads `pieces/*.yaml`, runs movements in order; + carries verify-movement feedback into the next execute; loop-detection aborts on + excessive repeat visits; `transition.lessons` accumulates cross-movement lessons. +- **`engine/agent-loop.ts`** — the ReAct loop for one movement. Intermediate hops + use the `transition` tool; termination (success/aborted/needs_user_input) uses the + `complete` tool. `complete.result` is the only user-visible final output. A + `ContextManager` tracks token usage from LLM `usage` responses and fires + warn/prompt/force_transition at thresholds. +- **`engine/context-manager.ts`** — threshold-based context-usage monitoring; can + auto-detect a model's context limit from the provider API. +- **`engine/piece-classifier.ts`** — LLM-based piece selection from the task text and + all piece descriptions. +- **`engine/tools/index.ts`** — dynamically loads and dispatches all tool modules. +- **`llm/openai-compat.ts`** — OpenAI-compatible (Ollama/vLLM/…) SSE streaming client; + accumulates `tool_calls` deltas into `LLMEvent`s. Retry via `provider.retry`. +- **`worker.ts` / `worker-manager.ts`** — Workers poll the DB for jobs matching their + `profiles`/`task_classes` and run them; multiple workers run concurrently and are + rebuilt on config change. +- **`config.ts` / `config-manager.ts`** — single `config.yaml`; snake_case YAML ↔ + camelCase code via `transformKeys`; runtime read/write with optimistic locking and + change events. +- **`db/repository.ts`** — SQLite (better-sqlite3). Manages `jobs`, `local_tasks`, + `local_task_comments`, `audit_log`, etc. Schema: `db/schema.sql`. +- **`bridge/server.ts`** — Express API server. Submodules: `pieces-api`, `config-api`, + `tools-api`, `scheduled-tasks-api`, `admin-api`, `share-api`, `browser-api`, + `subtask-activity-api`, and more. +- **`scheduler.ts`** — cron-style scheduled tasks (daily/weekly/monthly/cron/once). +- **`bridge/auth.ts`** — Passport OAuth2 (Google / Gitea). `requireAuth` allows active + users; `requireAdmin` allows admins. Auth is optional (unset = no auth). +- **`gateway/`** — optional LLM gateway (a proxy with virtual keys, budgets, and + Prometheus metrics). Note: its env vars use the `AAO_*` prefix and the + `aao_gateway` connection type for historical reasons (AAO = the gateway). + +## `pieces/*.yaml` — task definitions + +Each piece is an array of `movements`. Per movement: `allowed_tools`, `edit` +(Write/Edit permission), and `rules` (transition conditions). Tools not in +`allowed_tools` are not offered to the LLM. + +**Movement transition principles:** `transition` is for intermediate hops only +(`rules[].next` lists allowed targets); termination uses `complete`. `default_next` +is an engine-internal sentinel (context-overflow forced transition, ASK fallback). +Progressive pressure warns on repeat visits and aborts past a threshold. + +## Tool modules + +| Module | Tools | +|--------|-------| +| `core.ts` | Read / Write / Edit / Bash / Glob / Grep | +| `web.ts` | WebSearch / WebFetch / DownloadFile | +| `image.ts` | ReadImage / AnnotateImage | +| `office.ts` | ReadPdf / ReadExcel / ReadDocx / ReadPPTX / PdfToImages / Split… | +| `data.ts` | SQLite | +| `review.ts` | BatchReviewTextWithLLM / MergeReviewedResults | +| `browser.ts` | BrowseWeb (Playwright) | +| `knowledge.ts` | SearchKnowledge / ListDocuments / IngestDocument / … | +| `orchestration.ts` | SpawnSubTask | +| `x.ts`, `maps.ts`, `youtube.ts`, `amazon.ts`, `speech.ts`, `ms-learn.ts` | optional integrations | +| `checklist.ts` | CreateChecklist / CheckItem / GetChecklist | +| `pieces.ts` | ListPieces / GetPiece / CreatePiece / UpdatePiece | +| `skills.ts` | ReadSkill / ListSkills / InstallSkill (META_TOOL) | +| `docs.ts` | ReadToolDoc (META_TOOL) | +| `ssh.ts` | SSH execution / transfer | + +`raw-save.ts` and `structured-blocks.ts` are helper modules (not registered tools). + +Tool descriptions are kept to one sentence (they ride every LLM call); detailed +guidance lives in `docs/tools/.md` and is fetched via `ReadToolDoc`. + +## Bash sandbox + +The Bash tool runs inside a bwrap sandbox (filesystem confined to the task +workspace, env scrubbed, network unshared) when available, with a hardened +whitelist fallback otherwise. `safety.bash_sandbox` selects the mode +(`auto`/`always`/`off`). Runtime `pip`/`npm install` is rejected; Python packages +are pre-baked from `runtime/python-requirements.txt`. See +[docs/operations/bash-sandbox-provisioning.md](docs/operations/bash-sandbox-provisioning.md). + +## Workspace layout (per job) + +``` +{worktree_dir}/local/{taskId}/ + input/ uploads & DownloadFile output + output/ artifacts (the main Write/Edit-allowed area) + logs/ activity.log, history files + subtasks/ SpawnSubTask results + skills/ skill files materialized by ReadSkill +``` + +## DB migrations + +`db/schema.sql` is the initial schema. New columns are applied idempotently in +`db/migrate.ts` (`PRAGMA table_info` → existence check → `ALTER TABLE ADD COLUMN`). +Update both `schema.sql` and `migrate.ts`. + +## Tests + +Backend tests live next to their source as `*.test.ts` (vitest auto-discovers). + +## Adding a tool + +1. Export `TOOL_DEFS` and an `executeTool` from `src/engine/tools/.ts`. +2. Register the dynamic import in `tools/index.ts`. +3. Add the tool name to the using piece's `allowed_tools`. +4. Add `docs/tools/.md`. See `docs/maintenance-checklist.md` for the full + list of places that must stay in sync. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2593d6c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +All notable changes to MAESTRO are documented here. The format is loosely based +on [Keep a Changelog](https://keepachangelog.com/), and the project aims to +follow semantic versioning. + +## v0.1.0 — Initial public release (2026-06-02) + +First open-source release of MAESTRO, an agent orchestration platform: + +- Runs tasks against any OpenAI-compatible LLM endpoint (Ollama, vLLM, …). +- LLM-classified task routing into **Pieces** (YAML workflows) of **movements**. +- Sandboxed tool runtime (Read/Write/Edit/Bash/Glob/Grep, Office, Web, Browser, + Image, Data/SQLite, Knowledge/RAG, SSH, MCP, sub-tasks, and more). +- Bash tool sandbox (bwrap-based filesystem/network/env isolation with a + hardened fallback) and a declarative pre-baked Python toolchain. +- Optional LLM Gateway (virtual keys, budgets, metrics), reflection-based + learning, scheduled tasks, and a React web UI. +- Apache-2.0 licensed. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..54e007b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,78 @@ +# Contributing to MAESTRO + +Thanks for your interest in contributing! This guide covers how to build, run, +test, and submit changes. + +## Prerequisites + +- **Node.js 22+** +- An **OpenAI-compatible LLM endpoint** for running the app (e.g. [Ollama](https://ollama.com/) at `http://localhost:11434/v1`, or vLLM). Not required just to build/test. +- Optional, for the Bash tool sandbox: `bwrap` (bubblewrap) with unprivileged + user namespaces, plus `python3`/`pip` for the pre-baked tool packages. + +## Setup + +```bash +git clone maestro +cd maestro +npm ci # backend deps +npm --prefix ui ci # UI deps +cp config.yaml.example config.yaml # then edit provider/workers +``` + +## Build & run + +```bash +scripts/build-all.sh # builds backend (dist/) and UI (ui/dist/) +scripts/server.sh start # build + start with PID management +scripts/server.sh logs # tail logs +scripts/server.sh stop +# open http://localhost:9876 +``` + +`scripts/build-all.sh` also pre-bakes the Python packages the Bash sandbox uses +(`runtime/python-requirements.txt`). Pass `--skip-python` to skip that step, or +run `scripts/prebake-python.sh` separately (may need `sudo` to write to the +system Python). See `docs/operations/bash-sandbox-provisioning.md`. + +During UI development, `cd ui && npm run dev` runs Vite with HMR. + +## Tests + +```bash +npm test # all backend tests (vitest) +npx vitest run src/engine/tools/core.test.ts # a single file +``` + +- Backend tests live next to their source as `*.test.ts` (vitest auto-discovers). +- DOM-dependent UI tests need a browser-like environment and may not run in a + headless sandbox. + +## Conventions + +- **Config keys are snake_case in YAML** (`max_concurrency`) and **camelCase in + code** (`maxConcurrency`); `src/config.ts`'s `transformKeys` converts between them. +- New config options must be reflected in `config.yaml.example` **and** + `docs/configuration.md`. +- New tools: add a module under `src/engine/tools/`, register it in + `tools/index.ts`, list it in the relevant Piece's `allowed_tools`, and add a + one-line description plus `docs/tools/.md`. See `docs/maintenance-checklist.md`. +- DB schema changes: update `src/db/schema.sql` and add an idempotent migration in + `src/db/migrate.ts`. + +## Architecture + +See `AGENTS.md` for a contributor-oriented architecture overview and +`docs/architecture.md` for the execution flow in depth. + +## Submitting changes + +1. Branch from `main` (e.g. `feat/...`, `fix/...`). +2. Keep changes focused; add/adjust tests for behavior you change. +3. Ensure `npx tsc --noEmit` is clean and the relevant tests pass. +4. Open a pull request describing the change and how you verified it. + +## License + +By contributing, you agree that your contributions are licensed under the +project's [Apache-2.0](LICENSE) license. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4da9f58 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,72 @@ +FROM node:22-alpine AS builder + +WORKDIR /app + +# 依存関係のインストール +COPY package.json package-lock.json* ./ +COPY ui/package.json ui/package-lock.json* ./ui/ +RUN npm ci --ignore-scripts +RUN npm --prefix ui ci --ignore-scripts + +# noVNC スタンドアロン (vnc.html を含む Web 配布物) を取得。 +# npm の @novnc/novnc は lib のみで vnc.html を含まないため、 +# Browser タブの iframe 用に GitHub から tarball を取得する。 +ARG NOVNC_VERSION=1.6.0 +RUN apk add --no-cache --virtual .novnc-fetch curl tar \ + && mkdir -p /app/vendor/noVNC \ + && curl -fSL "https://github.com/novnc/noVNC/archive/refs/tags/v${NOVNC_VERSION}.tar.gz" \ + | tar -xz -C /app/vendor/noVNC --strip-components=1 \ + && test -f /app/vendor/noVNC/vnc.html \ + && apk del .novnc-fetch + +# TypeScript ビルド +COPY tsconfig.json ./ +COPY src ./src +COPY ui ./ui +RUN npm run build:server +RUN npm run build:ui + +# --- ランタイムステージ --- +FROM node:22-alpine AS runtime + +RUN apk add --no-cache \ + git \ + ca-certificates \ + tzdata \ + bash \ + bubblewrap \ + python3 \ + py3-pip + +# Pre-bake python packages into the system site-packages (read-only bind-mounted +# into every bash sandbox). Runtime `pip install` is intentionally unsupported. +COPY runtime/python-requirements.txt /tmp/python-requirements.txt +RUN pip3 install --no-cache-dir --break-system-packages -r /tmp/python-requirements.txt \ + && rm /tmp/python-requirements.txt + +WORKDIR /app + +# 本番依存のみインストール +COPY package.json package-lock.json* ./ +RUN npm ci --omit=dev --ignore-scripts + +# ビルド済み成果物をコピー +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/ui/dist ./ui/dist +COPY --from=builder /app/vendor ./vendor +# schema.sql は dist に含まれないため個別コピー +COPY src/db/schema.sql ./dist/db/schema.sql + +# デフォルト設定 +COPY config.yaml ./ + +# データ永続化ディレクトリ +RUN mkdir -p /data /workspaces + +ENV NODE_ENV=production \ + PORT=9876 \ + DB_PATH=/data/maestro.db + +EXPOSE 9876 + +CMD ["node", "dist/index.js"] diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..0a3e5c8 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,38 @@ + +## MCP Tools: code-review-graph + +**IMPORTANT: This project has a knowledge graph. ALWAYS use the +code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore +the codebase.** The graph is faster, cheaper (fewer tokens), and gives +you structural context (callers, dependents, test coverage) that file +scanning cannot. + +### When to use graph tools FIRST + +- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep +- **Understanding impact**: `get_impact_radius` instead of manually tracing imports +- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files +- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for +- **Architecture questions**: `get_architecture_overview` + `list_communities` + +Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need. + +### Key Tools + +| Tool | Use when | +|------|----------| +| `detect_changes` | Reviewing code changes — gives risk-scored analysis | +| `get_review_context` | Need source snippets for review — token-efficient | +| `get_impact_radius` | Understanding blast radius of a change | +| `get_affected_flows` | Finding which execution paths are impacted | +| `query_graph` | Tracing callers, callees, imports, tests, dependencies | +| `semantic_search_nodes` | Finding functions/classes by name or keyword | +| `get_architecture_overview` | Understanding high-level codebase structure | +| `refactor_tool` | Planning renames, finding dead code | + +### Workflow + +1. The graph auto-updates on file changes (via hooks). +2. Use `detect_changes` for code review. +3. Use `get_affected_flows` to understand impact. +4. Use `query_graph` pattern="tests_for" to check coverage. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cd44858 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative + Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2026 MAESTRO contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..15e1873 --- /dev/null +++ b/NOTICE @@ -0,0 +1,7 @@ +MAESTRO +Copyright 2026 MAESTRO contributors + +This product includes software developed as part of the MAESTRO project. + +Licensed under the Apache License, Version 2.0. See the LICENSE file for the +full license text. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7b12a63 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# MAESTRO + +![License](https://img.shields.io/badge/license-Apache--2.0-blue) + +**MAESTRO** — タスクを LLM 駆動で実行するエージェントオーケストレーションプラットフォーム。タスクの種類を LLM が自動判定し、適切なワークフロー(**Piece**)で処理する。ツールはサンドボックス化されたランタイムで実行され、ワークスペース・ファイル・進捗を Web UI で管理できる。 + +OpenAI 互換の LLM エンドポイント([Ollama](https://ollama.com/) / vLLM など)があれば単体で動作する。 + +## 主な機能 + +- **タスク自動ルーティング** — タスク本文を LLM が分類し、最適な Piece(YAML ワークフロー)へ振り分け。 +- **Piece × Movement** — ReAct ループで LLM とツールが対話しながら、段階的にタスクを進める。 +- **豊富なツール群** — ファイル操作(Read/Write/Edit/Bash/Glob/Grep)、Office(PDF/Excel/Docx/PPTX)、Web 取得、ブラウザ操作(Playwright)、画像、SQLite、ナレッジ検索(RAG)、SSH、サブタスク並列実行、MCP 連携、ほか。 +- **Bash サンドボックス** — bwrap によるファイルシステム/ネットワーク/環境変数の隔離(不在時は強化版 whitelist にフォールバック)。Python パッケージはプリベイク。 +- **LLM Gateway(任意)** — 仮想キー・予算・メトリクス付きの LLM プロキシ。複数 GPU/チームでの共有運用に対応。 +- **学習(Reflection)・定期タスク・タスク共有・OAuth 認証(Google/Gitea)** — いずれも任意で有効化。 +- **Web UI** — タスク作成・進捗・成果物プレビュー・設定編集・スキル/Piece 管理。 + +## クイックスタート + +### Docker(最短) + +```bash +cp .env.example .env # OLLAMA_BASE_URL などを設定 +docker compose up -d +# http://localhost:9876 を開く +``` + +LLM エンドポイントは `.env` / `config.yaml` で指定する(既定は `http://localhost:11434/v1`)。 + +### ソースから + +```bash +git clone https://gitea.example.com/your-org/maestro.git +cd maestro +npm ci && npm --prefix ui ci +cp config.yaml.example config.yaml # provider / workers を編集 +scripts/build-all.sh +scripts/server.sh start # http://localhost:9876 +``` + +詳しい手順は **[docs/getting-started.md](docs/getting-started.md)** を参照。 + +## 必要要件 + +- **Node.js 22+** +- **OpenAI 互換の LLM エンドポイント**(Ollama / vLLM など) +- 任意(Bash サンドボックス用): `bwrap`(bubblewrap, 非特権 user namespace)+ `python3`/`pip` + +## ドキュメント + +- **[docs/getting-started.md](docs/getting-started.md)** — インストール・初回起動・最初のタスク・認証/サンドボックスの有効化 +- **[docs/configuration.md](docs/configuration.md)** — `config.yaml` の全設定項目リファレンス +- **[docs/architecture.md](docs/architecture.md)** — 実行フロー・Piece/Movement・ツール・DB・サンドボックス +- **[docs/tools/](docs/tools/)** — 各ツールの詳細 +- **[docs/operations/bash-sandbox-provisioning.md](docs/operations/bash-sandbox-provisioning.md)** — 本番でのサンドボックス有効化手順 +- **[AGENTS.md](AGENTS.md)** / **[CONTRIBUTING.md](CONTRIBUTING.md)** — コントリビュータ向け + +## サーバー管理 + +```bash +scripts/server.sh start | stop | restart | status | logs +``` + +## ライセンス + +[Apache-2.0](LICENSE)。 diff --git a/bench/fixtures/notes.md b/bench/fixtures/notes.md new file mode 100644 index 0000000..3254b36 --- /dev/null +++ b/bench/fixtures/notes.md @@ -0,0 +1,7 @@ +# チーム注意事項 (2026 Q1) + +- 売上の集計単位は必ず Q1 (1-3 月) ベース。複数 Q を混在させない +- 公開資料には数値の出典 (シート名・行範囲) を必ず併記する +- レポートは 100 行以内に収める。冗長な説明より要点重視 +- 想定読者は経営層なので業界用語の濫用は避ける +- 「次アクション」は具体的な担当者・期限を含める形で書くこと diff --git a/bench/fixtures/sales.xlsx b/bench/fixtures/sales.xlsx new file mode 100644 index 0000000..af05562 Binary files /dev/null and b/bench/fixtures/sales.xlsx differ diff --git a/bench/fixtures/web/announcement.html b/bench/fixtures/web/announcement.html new file mode 100644 index 0000000..bae3473 --- /dev/null +++ b/bench/fixtures/web/announcement.html @@ -0,0 +1,22 @@ + + + + +2026年4月 社内発表 + + +

2026年4月 社内発表

+
+

新製品 ProductG 発表

+

当社は新製品 ProductG の正式販売を開始します。市場投入は 2026年Q2 を予定しています。

+
+
+

研究開発投資の増額

+

2026 会計年度の R&D 予算を前年比 15% 増額することが取締役会で承認されました。

+
+
+

新オフィス開設

+

東京・大阪・福岡の 3 拠点に加え、新たに名古屋オフィスを 2026年6月 に開設します。

+
+ + diff --git a/bench/tasks/composite-mini-report.yaml b/bench/tasks/composite-mini-report.yaml new file mode 100644 index 0000000..89c0844 --- /dev/null +++ b/bench/tasks/composite-mini-report.yaml @@ -0,0 +1,105 @@ +id: composite-mini-report +title: 3 ソース統合 + チェックリスト + 形式厳守 + +piece_hint: chat +timeout_minutes: 12 + +fixtures: + - source: fixtures/sales.xlsx + dest: input/sales.xlsx + - source: fixtures/notes.md + dest: input/notes.md + - source: fixtures/web/announcement.html + dest: web/announcement.html + +prompt: | + 以下の手順で `output/report.md` にミニレポートを作ってください。 + + ## 必須手順 (順守すること) + 1. 最初に CreateChecklist で進めるべき TODO を全部登録する (最低 4 項目) + 2. 各 TODO を進めるたびに CheckItem で完了マークを付ける + 3. 完了前に GetChecklist で進捗を確認する + + ## 情報源 + - `input/sales.xlsx` の Sheet1 から「2026年Q1 売上トップ3 商品」を抽出 + - `http://127.0.0.1:{WEB_PORT}/announcement.html` から発表内容を抽出 + - `input/notes.md` からチーム注意事項を抽出 + + ## 出力 `output/report.md` の形式 (厳守) + - 1 行目: `# サマリーレポート 2026Q1` + - セクション順: `## 売上トップ3` → `## 最新発表` → `## チーム注意事項` → `## 次アクション` + - 各セクションは 5 行以内 + - `## 次アクション` は箇条書き (- で始まる) を 3 つ、各 40 字以内 + - Markdown 画像 `![]()` や HTML タグは禁止 + + ## 注意 + - 元データに無い数値・事実をでっち上げない + - 情報が足りなければ ASK で確認する + - 出力は `output/report.md` のみ、他のファイルを作らない + +expected: + must_use_tools: [ReadExcel, WebFetch, Read, Write, CreateChecklist, CheckItem, GetChecklist] + forbidden_tool_for_ext: + Read: ['.xlsx', '.docx', '.pptx', '.xls', '.doc', '.ppt'] + must_produce_files: [output/report.md] + completion_status: [succeeded] + +checklist: + required_tools: [CreateChecklist, CheckItem, GetChecklist] + min_check_item_calls: 3 + +grading: + programmatic: + weight: 0.7 + constraints: + - type: file_first_line_equals + file: output/report.md + line: '# サマリーレポート 2026Q1' + - type: file_must_contain_in_order + file: output/report.md + sections: ['## 売上トップ3', '## 最新発表', '## チーム注意事項', '## 次アクション'] + - type: file_section_max_lines + file: output/report.md + section: 売上トップ3 + max: 5 + - type: file_section_max_lines + file: output/report.md + section: 最新発表 + max: 5 + - type: file_section_max_lines + file: output/report.md + section: チーム注意事項 + max: 5 + - type: file_line_starts_with + file: output/report.md + prefix: '-' + min_lines: 3 + section: 次アクション + - type: file_line_max_chars + file: output/report.md + max: 40 + section: 次アクション + - type: file_no_pattern + file: output/report.md + pattern: '!\[' + - type: file_no_pattern + file: output/report.md + pattern: '<[a-zA-Z][^>]*>' + + llm_judge: + weight: 0.3 + rubrics: + - name: factual_grounding + prompt: | + レポート内の売上トップ3 / 発表内容 / 注意事項 が、与えられた 3 ソース (sales.xlsx, + announcement.html, notes.md) に忠実か。捏造や混同があれば減点。 + max_score: 10 + - name: actions_quality + prompt: | + 「次アクション」3 項目が、3 ソースの内容を踏まえた具体的・行動可能なものか。 + 抽象的すぎる、ソースと無関係な内容は減点。 + max_score: 10 + - name: synthesis + prompt: | + 3 ソースの統合がレポート全体として論理的に整合しているか。 + max_score: 10 diff --git a/bench/tasks/reflection-smoke.yaml b/bench/tasks/reflection-smoke.yaml new file mode 100644 index 0000000..ed3b312 --- /dev/null +++ b/bench/tasks/reflection-smoke.yaml @@ -0,0 +1,115 @@ +# reflection-smoke.yaml +# +# Smoke test for the reflection / Hermes-mode system. +# +# DESIGN NOTE — why this is a single-step task +# ───────────────────────────────────────────── +# The ideal reflection bench is a two-run sequence: +# Run 1: submit a task + negative feedback → reflection fires → memory +# entry "feedback_user_prefers_terse_output" is written. +# Run 2: submit a second task → the reflection-produced memory entry +# appears in the system prompt → response is demonstrably terse. +# +# The current bench harness (src/bench/runner.ts) does not support multi-run +# sequences or DB assertions (reflection_metrics, memory tables). The grader +# (src/bench/grader.ts) only evaluates: +# A — tool calls from activity.log +# B — checklist tool usage +# C — file output constraints (file_first_line_equals, file_no_pattern, etc.) +# D — LLM judge rubrics against output files +# +# Therefore this YAML exercises a single task whose prompt explicitly carries +# the lesson ("one-line terse reply") that reflection would have injected into +# the system prompt on a second run. The programmatic constraints enforce the +# structural signature of a terse reply, and the LLM judge validates content +# quality. This gives a useful regression gate even without multi-step support. +# +# FULL TWO-RUN FLOW (for manual / integration testing) +# ───────────────────────────────────────────────────── +# 1. Start orchestrator with reflection.enabled: true and a reflection worker. +# 2. Submit a chat task with body: +# "Summarise the Pythagorean theorem." +# The agent will produce a verbose multi-paragraph response. +# 3. Rate that task feedback_rating='bad' via the UI. +# 4. Wait ~60 s. A task_kind='reflection' job should appear in the jobs table +# with outcome='applied' in reflection_metrics. +# Verify: SELECT outcome FROM reflection_metrics ORDER BY created_at DESC LIMIT 1; +# 5. In data/users//memory/, confirm a file like +# feedback_user_prefers_terse_output.md exists. +# 6. Submit a second task: "Summarise the Pythagorean theorem." +# The reflection memory should now be in the system prompt. +# The response should be ≤ 3 sentences with no "Certainly!" preamble. +# +# HOW THIS FILE IS DISCOVERED +# ─────────────────────────── +# The bench runner (scripts/bench-run.ts) does: +# glob("bench/tasks/*.yaml") +# No registration step is needed. Drop this file and it is automatically +# included in `npm run bench` and `npm run bench -- --task=reflection-smoke`. + +id: reflection-smoke +title: Reflection smoke — terse reply under explicit lesson +piece_hint: chat +timeout_minutes: 5 + +prompt: | + IMPORTANT USER PREFERENCE (simulating a reflection-injected memory entry): + The user prefers terse, one-line replies with no preamble phrases such as + "Certainly!", "Of course!", "Sure!", "Great question!", or "Happy to help!". + + Task: What is the Pythagorean theorem? + + Instructions: + 1. Write your answer to `output/answer.md`. + 2. The answer MUST be a single Markdown line (no headings, no bullet lists). + 3. The line MUST NOT start with a preamble phrase. + 4. The line MUST be 120 characters or fewer. + +expected: + must_use_tools: [Write] + forbidden_tools: [Bash] + must_produce_files: [output/answer.md] + completion_status: [succeeded] + +grading: + programmatic: + weight: 0.6 + constraints: + # The output must be a single non-empty line — no second non-empty line. + - type: file_no_pattern + file: output/answer.md + pattern: '(?m)^.+\n\n?.+' + # Must not contain heading markers. + - type: file_no_pattern + file: output/answer.md + pattern: '^#' + # Must not start with common preamble phrases. + - type: file_no_pattern + file: output/answer.md + pattern: '(?i)^(certainly|of course|sure[,!]|great question|happy to help|absolutely)[!,.]' + # Must not use bullet / numbered lists. + - type: file_no_pattern + file: output/answer.md + pattern: '(?m)^[-*\d]' + # Each line ≤ 120 chars (the single content line). + - type: file_line_max_chars + file: output/answer.md + max: 120 + + llm_judge: + weight: 0.4 + rubrics: + - name: terseness + prompt: | + The output should be a single terse line (no preamble, no bullet list, + no heading) that correctly states the Pythagorean theorem. + Score 10 if the answer is ≤ 2 short sentences, factually correct, and + starts directly with the mathematical content (e.g. "In a right triangle…" + or "a² + b² = c²…"). + Deduct points proportionally for verbosity, preamble phrases, or inaccuracy. + max_score: 10 + - name: factual_accuracy + prompt: | + Does the answer correctly state the Pythagorean theorem + (a² + b² = c² for a right triangle)? Score 10 for correct, 0 for wrong. + max_score: 10 diff --git a/config.yaml.example b/config.yaml.example new file mode 100644 index 0000000..aabfdb4 --- /dev/null +++ b/config.yaml.example @@ -0,0 +1,395 @@ +# MAESTRO 設定ファイル (v2 layout) +# +# このファイルを config.yaml にコピーして編集してください: +# cp config.yaml.example config.yaml +# +# ─── v2 への移行 (v1 → v2) ───────────────────────────────────── +# 旧構造 (provider.* / 平置きの worktree_dir 等) は 1 リリース分だけ +# 読み取り互換が残っています。手元の config.yaml が旧形式の場合は: +# +# scripts/migrate-config.sh --dry-run # 変換後を確認 +# scripts/migrate-config.sh # in-place で書き換え (.bak を自動保存) +# +# v3.0 で v1 形式は起動 fatal になる予定です。 + +# v2 schema バージョン (必須)。 +# - 2 : このリリースの正規形 (= 本ファイル) +# - 1 / 未指定 : v1 互換読み取り + 起動 warning +# - その他 : 起動 fatal (typo / 未来形式の混入防止) +config_version: 2 + +# ─── LLM ───────────────────────────────────────────────────── +# ジョブ実行時に LLM 呼び出し先として使う接続群と、retry / timeout / metrics。 +llm: + timeout_minutes: 10 # 1 リクエスト全体の上限 (分)。default 10 + + retry: + max_attempts: 3 + backoff_ms: [2000, 5000, 15000] # 429 / 5xx / 一時的接続失敗時の待機 ms + retryable_status: [429, 500, 502, 503, 504] + + # workers[] — この AAO がジョブ実行時に呼ぶ接続先。 + # + # connection_type: + # direct — Ollama / llama.cpp / vLLM 等の OpenAI 互換 backend に直接接続 + # aao_gateway — 別 AAO Gateway 経由で接続 (Gateway Key 必須) + # + # トップレベル `gateway.*` (この AAO 自身が gateway として動く設定) と + # 単語衝突を避けるため、worker 側は `aao_gateway` と prefix 付き。 + # + # model はワーカーごとに明示。`default_model` は廃止された。 + # roles: 用途別 (auto / fast / quality / title / reflection 等) のフィルタ。 + # max_concurrency: ワーカー単位の並列度。 + # vlm: true で画像入力に対応 (ReadImage は VLM ワーカーを優先)。 + workers: + - id: local-ollama + connection_type: direct + endpoint: http://localhost:11434/v1 + model: qwen3:32b + roles: [auto, fast, quality] + max_concurrency: 1 + enabled: true + vlm: false + + # 例: 別 AAO Gateway 越しに共有 GPU プールを使う + # - id: team-gateway + # connection_type: aao_gateway + # endpoint: http://gateway.example.com:9876/v1 + # api_key: ${TEAM_AAO_GATEWAY_KEY} # gateway 発行の sk-aao-*** virtual key + # model: qwen3:32b + # roles: [quality] + # max_concurrency: 2 + # enabled: true + + # 例: タイトル生成専用ワーカー (chat ジョブは受け付けない) + # - id: title-worker + # connection_type: direct + # endpoint: http://localhost:11434/v1 + # model: qwen3:8b + # roles: [title] + # max_concurrency: 4 + # enabled: true + + # 例: Reflection 専用ワーカー (cheap モデルで memory 更新を回す) + # - id: reflector + # connection_type: direct + # endpoint: http://localhost:11434/v1 + # model: qwen3:8b + # roles: [reflection] + # max_concurrency: 1 + # enabled: true + + # Prometheus exporter (worker side). default で enabled。 + # /metrics が bridge HTTP server (PORT, default 9876) に mount される。 + # access control: default では localhost (127.0.0.1 / ::1) のみ通る。 + # 本番では (a) bearer_token を設定するか、(b) allowed_hosts で前段 IP を許可。 + # env: AAO_WORKER_METRICS_BEARER_TOKEN / AAO_WORKER_METRICS_ALLOWED_HOSTS (CSV) でも上書き可。 + metrics: + enabled: true + prefix: aao_worker # /^[a-z][a-z0-9_]*$/ + # bearer_token: env:AAO_WORKER_METRICS_BEARER_TOKEN + # allowed_hosts: + # - 127.0.0.1 + # - ::1 + # - localhost + # # - 0.0.0.0 # 全許可。前段で firewall を必ず使う運用前提 + +# ─── AAO Gateway Server ────────────────────────────────────── +# この AAO 自身を OpenAI 互換 LLM Gateway として公開する設定。 +# 有効化すると `/v1/chat/completions` などが同 process で立ち上がる。 +# +# Virtual Keys (sk-aao-*** 形式) は **admin REST API での発行を推奨**: +# POST /api/admin/gateway/keys +# config.yaml の virtual_keys[] は bootstrap / backup 用途のみで、 +# DB に自動 import される (source='config-import')。rotation は admin API 経由でのみ。 +# +# 同 process / separate process の deploy 方法は docs/aao-gateway-overview.md を参照。 +# UI からは Settings → LLM → Gateway Server で全て編集可能。 +# gateway: +# enabled: false # true で同 process gateway が即時起動 +# # (ConfigManager hot reload 対応、再起動不要) +# listen_port: 4000 # separate-deploy 時のみ有効 (default LiteLLM 互換) +# request_timeout_sec: 600 # 1 リクエスト全体 (streaming 込み) +# upstream_timeout_sec: 30 # 各 upstream fetch の TTFB 上限 +# shutdown_graceful_sec: 30 # SIGTERM 後、in-flight SSE の drain 上限秒 +# +# backends: +# - id: gpu-a # `x-aao-backend-id` / `/v1/models` に出る ID +# endpoint: http://gpu-a:11434/v1 +# model: qwen3:32b # 厳密一致 routing +# max_slots: 2 # llama-server -np と合わせる +# api_key: ${GPU_A_API_KEY} # backend が bearer 必須な場合のみ +# - id: gpu-b +# endpoint: http://gpu-b:11434/v1 +# model: qwen3:32b +# max_slots: 2 +# +# # Bootstrap / Backup 専用 virtual_keys (新規発行は admin API 経由を推奨)。 +# # virtual_keys: +# # - key: ${TEAM_ALPHA_KEY} # 起動時に DB へ idempotent import +# # team: alpha +# # allowed_models: [qwen3:32b] +# # # tokens_budget: 1000000 # 月次 token 上限 (UTC 月初に reset) +# # # rate_limit_rpm: 60 # 1 分あたり最大リクエスト数 +# +# # Prometheus exporter (gateway side)。default enabled。 +# # team / key_prefix / backend ラベルが出るので auth 必須運用 (default localhost のみ)。 +# # env: AAO_GATEWAY_METRICS_BEARER_TOKEN / AAO_GATEWAY_METRICS_ALLOWED_HOSTS (CSV) で上書き可。 +# # metrics: +# # enabled: true +# # prefix: aao_gateway +# # bearer_token: env:AAO_GATEWAY_METRICS_BEARER_TOKEN +# # allowed_hosts: +# # - 127.0.0.1 +# # - ::1 +# # # - 0.0.0.0 # 前段 firewall 必須 + +# ─── Storage / Paths ───────────────────────────────────────── +# 旧 worktree_dir / custom_pieces_dir / user_folder_root / +# tools.task_upload_max_size_mb / tools.trash_retention_days +# は normalizer により storage.* に集約された。 +storage: + worktree_dir: ./data/workspaces # ジョブ実行時の作業ディレクトリのベース + # custom_pieces_dir: ./custom-pieces # リポジトリ内の pieces/ に加えて読みに行く Piece dir (任意) + user_folder_root: ./data/users # {root}/{userId}/ 配下に AGENTS.md/scripts/notes 等を保存 + task_upload_max_size_mb: 50 # /api/local/tasks と /comments の body 上限 (MB) + # base64 で乗るので実ファイル目安は値 × 0.75。範囲 [1, 1000] + trash_retention_days: 30 # data/users/{userId}/trash/ の自動 sweep (起動時 + 24h 周期) + # 0 で sweep 毎に全削除 + +# ─── Execution ─────────────────────────────────────────────── +# ジョブ全体の並列度・movement 上限・ジョブ retry。 +concurrency: 4 # 全 worker 合算の最大並列ジョブ数 (env: CONCURRENCY) +max_movements: 200 # 1 ジョブ内の最大 movement 数 (loop 防止) +retry: + max_attempts: 3 # ジョブ失敗時の最大再試行回数 + backoff_seconds: [60, 300, 900] # 各 attempt 間の待機秒 + +# ASK (ユーザーへの質問) 制御 +ask: + max_per_job: 2 # 1 Job あたりの ASK 上限 + +# Subtask 制御 +subtasks: + max_depth: 2 # SpawnSubTask のネスト最大深度 + max_per_parent: 10 # 1 ジョブが生成できるサブタスクの最大数 + +# ─── Context (LLM コンテキスト管理) ─────────────────────────── +# context: +# limit_tokens: 128000 # 省略時は Ollama API で自動取得、それも失敗なら 128000 +# thresholds: +# - ratio: 0.7 +# action: warn +# - ratio: 0.85 +# action: prompt +# - ratio: 0.95 +# action: force_transition + +# ─── Safety (エージェント自爆防止) ──────────────────────────── +# safety: +# max_iterations: 200 # 1 movement 内の最大イテレーション +# max_revisits: 3 # 同一 movement の最大再訪問 +# prompt_guard_ratio: 0.8 # コンテキスト上限の何 % まで prompt を許容するか (0.5–0.95) +# history_summarization: # 古い turn を構造化要約に置換して粘る (Opencode 方式) +# enabled: true # default true +# tail_turns: 2 # 末尾何 turn を必ず保護するか +# preserve_recent_budget: 8000 # 末尾保護の最大トークン数 +# bash_unrestricted: false # true: コマンドホワイトリストを撤廃し任意コマンド実行可。 +# # 代わりに bwrap サンドボックスで workspace 単位の +# # ファイルシステム隔離を強制 (タスク間の横断アクセス防止)。 +# # 前提: コンテナで user namespace が有効 (nesting=1)。 +# # 起動時に bwrap の動作確認を行い、失敗時はエラー終了。 +# # Bash サンドボックス機構: +# # auto (既定) bwrap があれば sandbox、無ければ hardened-whitelist にフォールバック +# # always sandbox を強制。bwrap 不在なら起動失敗(本番推奨) +# # off bwrap を使わない(env スクラブは維持)。デバッグ用、非推奨 +# bash_sandbox: auto + +# ─── Search Filter (WebSearch の機密情報漏洩防止) ───────────── +# search_filter: +# blocked_patterns: # カスタムブロックパターン (完全一致で除去) +# - secret-project +# - internal-codename +# auto_block: # 自動検出 (default: 全 true) +# private_ip: true # 10.* / 172.16-31.* / 192.168.* / 127.* +# internal_domain: true # .local / .internal / .lan / .intranet / .corp / .home +# email: true +# phone: true # 日本の電話番号 + +# ─── Browser Runtime (BrowseWeb / BrowserAction) ────────────── +# browser: +# page_timeout: 60000 # ms +# action_timeout: 30000 # ms +# captcha_solve: novnc # 'skip' (default) / 'novnc' +# max_captcha_pages: 5 +# channel: chrome # 'chromium' (default) / 'chrome' / 'msedge' +# executable_path: /usr/bin/google-chrome # channel と排他 + +# ─── Tools (Web & Search / Media / External / Legacy) ──────── +# UI 上は 5 カテゴリに分かれて編集可能 (Web & Search / Browser Runtime / +# Media & Documents / External Services / Legacy Knowledge)。YAML は +# 互換のため `tools` 1 ブロックで管理。 +tools: + # Web & Search + searxng_url: http://localhost:8080 # WebSearch フォールバック先 (通常は Playwright + Google) + webfetch_timeout: 30 # WebFetch / DownloadFile timeout (sec) + # websearch_timeout: 15 + # webfetch_allowed_hosts: # SSRF 例外 (private IP / .local 等を許可する場合) + # - my-internal-host.local + + # Media & Documents + # vision_model: qwen2-vl:8b-instruct # ReadImage 用 VLM (provider と別エンドポイントなら vision_base_url) + # vision_base_url: http://localhost:11434/v1 + # vision_timeout: 60 + # vision_max_tokens: 1024 + # ocr_model: glm-ocr # OCR 用モデル (vision_base_url の server に問い合わせる) + # office_excel_max_size_mb: 10 # ReadExcel 上限 (default 10) + # office_docx_max_size_mb: 10 # ReadDocx 上限 + # office_pdf_max_size_mb: 10 # ReadPdf 上限 + # office_pptx_max_size_mb: 50 # ReadPPTX 上限 + # office_pptx_max_uncompressed_mb: 200 # PPTX ZIP 展開後上限 (zip-bomb 検知) + # speech_server_url: http://localhost:8000/v1 + # speech_timeout: 300 + # speech_language: ja + + # External Services + # x_cli_command: ["twitter"] # twitter-cli 実行コマンド + # x_timeout: 90 + # x_auth_token: "..." # 任意: auth_token cookie + # x_ct0: "..." # 任意: ct0 cookie + # x_proxy: http://127.0.0.1:7890 # 任意: twitter-cli 用 proxy + # x_chrome_profile: "Profile 2" # Chrome cookie 抽出 profile + # x_download_media: auto # 'auto' (default) / 'never' + # x_download_video: thumbnail # 'thumbnail' (default) / 'full' / 'never' + # x_media_max_mb: 25 + # x_media_fetch_timeout_seconds: 15 + # google_maps_api_key: "..." # 未設定なら Nominatim / OSRM + # maps_timeout: 30 + # amazon_affiliate_tag: "your-tag-22" + # keepa_api_key: "..." + + # User scripts (RunUserScript) + # user_scripts_enabled: false # true で許可。plain runtime は Node --permission で sandbox 化 + # user_scripts_allow_userids: # 未指定 = 全ユーザー許可 (user_scripts_enabled に従う) + # - alice-id + # - bob-id + + # Legacy Knowledge (DKS) — 新規 namespace 追加は MCP 経由を推奨 + # knowledge_service_url: http://dks-server:8100 # 未設定で knowledge ツール無効 + # knowledge_namespaces: + # product-a-support: + # api_key: "sk-product-a-xxx" + # contract-review: + # api_key: "sk-contract-yyy" + +# ── Shared Knowledge Notes ─────────────────────────────────── +# data/users/{userId}/notes/ のノートをシステムプロンプトに自動注入する設定。 +# notes: +# inject: +# per_note_max_kb: 8 # 日本語コンテンツは 4 推奨 +# total_max_kb: 32 +# over_budget_strategy: skip_remaining # truncate_last / skip_remaining (default) / degrade_to_search + +# ─── 認証 (オプション) ──────────────────────────────────────── +# 未設定なら認証なしで動作 (従来互換)。 +# auth: +# session_secret: "ランダムな文字列を設定してください" +# session_max_age: 86400000 # 24h (ms) +# secure_cookie: false # HTTPS 環境では true +# admin_emails: +# - "admin@example.com" +# # primary_provider: gitea # 'google' | 'gitea'。両方有効時に明示 +# providers: +# google: +# client_id: "" +# client_secret: "" +# callback_url: "http://localhost:3000/auth/google/callback" +# gitea: +# client_id: "" +# client_secret: "" +# base_url: "https://gitea.example.com" +# callback_url: "http://localhost:3000/auth/gitea/callback" + +# ─── Branding (オプション) ──────────────────────────────────── +# config.yaml / data/branding/ は .gitignore 済みで git pull 影響なし。 +# Settings → System → Branding で GUI 編集可 (admin)。 +# branding: +# app_name: "My Team AI" +# primary_color: "#2563eb" +# login_page_title: "My Team AI" +# logo_url: "/branding/logo-abc123.svg" +# favicon_url: "/branding/favicon-def456.png" +# footer_text: "© 2026 Your Team" + +# ─── Secrets ───────────────────────────────────────────────── +# secrets: +# master_key_path: ./data/secrets/master.key # 32-byte key, auto-generated on first start (mode 0600) + +# ─── Reflection ("Hermes" mode) ────────────────────────────── +# default OFF。ON にすると毎ジョブ完了後に user memory を LLM が自動更新する。 +# snapshot は data/users/{userId}/.reflection-history/ に残り UI から revert 可。 +# reflection: +# enabled: false +# max_memory_changes_per_job: 3 +# piece_edit_cooldown_hours: 24 +# snapshot_retention_days: 90 +# per_user_daily_budget_tokens: 200000 + +# ─── MCP (Model Context Protocol) ──────────────────────────── +# Individual servers は admin UI (global) または各ユーザー (self-hosted) で管理。 +# MCP_ENCRYPTION_KEY env (64 hex chars) が必須。 +# mcp: +# call_timeout_seconds: 60 +# max_binary_size_mb: 20 +# max_output_files_per_job: 10 +# max_output_size_mb_per_job: 200 +# tool_cache_ttl_seconds: 600 +# oauth_pending_ttl_minutes: 10 +# # allow_private_addresses: false # 自前 MCP server を private 網に置く場合 true + +# ─── SSH (off by default) ──────────────────────────────────── +# 有効化手順は docs/ssh.md / config.yaml.example のコメント (旧版) を参照。 +# Operator runbook: docs/ssh.md +# Sample piece: pieces/ssh-ops.yaml +# +# ssh: +# enabled: false +# +# # allow_private_addresses: false # global default。admin は per-connection で grant 可 +# # call_timeout_seconds: 30 +# # max_output_bytes: 32768 +# # max_upload_size_mb: 100 +# # max_download_size_mb: 100 +# # audit_retention_days: 90 +# # admin_bypasses_grants: true +# # abuse_window_minutes: 10 +# # abuse_failure_threshold: 5 +# # abuse_lock_minutes: 30 +# +# # ── Interactive SSH Console (live PTY-backed shell) ── +# console: +# enabled: false # true で SshConsole* tools + UI Terminal タブを公開 +# idle_timeout_seconds: 1800 # 30min I/O-less = auto close +# max_session_duration_seconds: 14400 # 4h hard cap +# scrollback_bytes: 524288 # 512KB scrollback / session +# max_sessions_per_connection: 3 +# max_input_bytes_per_send: 16384 +# auto_inject_screen_lines: 24 +# default_cols: 120 +# default_rows: 32 + + +# ── Browser Notifications V2 (Web Push) ─────────────────────────────── +# Requires HTTPS hosting + (for iOS) PWA installation on the client side. +# notifications: +# push: +# enabled: false # true で V2 を有効化 +# vapid_subject: "https://maestro.example.com/" # RFC 8292 — operations URL preferred +# vapid_current_path: "./data/secrets/vapid.json" # 自動生成 (mode 0600) +# vapid_history_dir: "./data/secrets/vapid-history" +# payload_max_bytes: 3072 # JSON byte length cap (上限 4096) +# queue_concurrency: 8 +# per_send_timeout_ms: 10000 +# +# 起動時に vapid_current_path に鍵が無ければ自動生成、mode 0600 で保存。 +# 鍵をローテーションする場合: npm run vapid-rotate diff --git a/deploy/maestro.service b/deploy/maestro.service new file mode 100644 index 0000000..5930b1e --- /dev/null +++ b/deploy/maestro.service @@ -0,0 +1,25 @@ +[Unit] +Description=MAESTRO +After=network.target + +[Service] +Type=simple +User=agent-bot +WorkingDirectory=/opt/maestro +ExecStart=/usr/bin/node dist/index.js +Restart=on-failure +RestartSec=10 +EnvironmentFile=/opt/maestro/.env + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=maestro + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ReadWritePaths=/opt/maestro/data /tmp/agent-workspaces + +[Install] +WantedBy=multi-user.target diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..0e8e893 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +services: + maestro: + build: + context: . + dockerfile: Dockerfile + image: maestro:latest + container_name: maestro + restart: unless-stopped + ports: + - "9876:9876" + env_file: + - .env + environment: + - NODE_ENV=production + - PORT=9876 + - DB_PATH=/data/maestro.db + - WORKTREE_DIR=/workspaces + volumes: + # SQLite DB 永続化 + - maestro-data:/data + # エージェントワークスペース永続化 + - maestro-workspaces:/workspaces + # 設定ファイル (任意でホストからマウント) + # - ./config.yaml:/app/config.yaml:ro + healthcheck: + test: ["CMD", "node", "-e", "fetch('http://localhost:9876/health').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + +volumes: + maestro-data: + driver: local + maestro-workspaces: + driver: local diff --git a/docs/aao-gateway-overview.md b/docs/aao-gateway-overview.md new file mode 100644 index 0000000..42916e5 --- /dev/null +++ b/docs/aao-gateway-overview.md @@ -0,0 +1,239 @@ +# AAO Gateway モード — 機能概要 (2026-05-20 時点) + +このドキュメントは「LiteLLM Proxy 代替として AAO に追加された Gateway 機能」が今回の一連の作業でどう変わったかを日本語で解説します。技術詳細の設計 doc は末尾の関連ドキュメントセクションを参照。 + +--- + +## TL;DR (3 行) + +- **AAO 自身が OpenAI 互換 LLM Gateway として動けるようになりました。** 他の AAO や任意の OpenAI クライアントから `/v1/chat/completions` を叩けます。 +- **設定 UI のトグル 1 つで on/off** (同一プロセス内で起動・停止)。別プロセスで動かす運用も従来通り可能 (advanced)。 +- 仮想 API キーごとに **月次 token 予算 + RPM レート制限**、Prometheus でリアルタイム監視。Telemetry 外部送信ゼロ。 + +--- + +## なぜこれを作ったか + +LiteLLM Proxy には次の懸念がありました: + +1. **Telemetry**: デフォルトで匿名利用データが外部送信される (opt-out 可だが要設定)。 +2. **License 変更リスク**: MIT → BSL/SSPL への変更前例があり、組織で長期運用するには不確実性が高い。 +3. **追加依存**: Python サービス + Redis (任意) + DB を、AAO とは別に運用する必要がある。 + +これらを **「AAO 単一バイナリ + 既存 SQLite + telemetry 完全ゼロ」** で代替するのが本機能の目的です。LiteLLM 固有の高度機能 (sticky routing / canary / cost USD / OpenTelemetry / Slack アラート / multi-region federation 等) は **意図的にスコープ外**にして、シンプルな in-house ゲートウェイに振り切りました。 + +--- + +## どの機能が、どのタイミングで入ったか + +時系列に沿って、追加された機能とユーザーへの影響を解説します。 + +### Phase 1: Gateway モードの土台 (PR #326) + +| 項目 | 内容 | +|---|---| +| 起動方法 | `AAO_MODE=gateway` を環境変数指定すると、worker / scheduler / UI を起動せず、軽量に gateway サーバだけ立つ | +| エンドポイント | `/v1/chat/completions` (SSE ストリーミング) + `/v1/models` + `/health` (LiteLLM 互換 JSON) | +| 認証 | Bearer Token (config.yaml に static な virtual keys) | +| ルーティング | 複数 backend (llama-server / vLLM など) を **least-busy** (進行中リクエスト数が一番少ない backend) で振り分け | +| 安全停止 | SIGTERM 時に進行中 SSE を `shutdown_graceful_sec` (デフォルト 30s) 待って drain、`gateway_shutdown` SSE event でクライアントに retry を促す | + +### Phase 2a: 仮想キーを DB で管理 + Admin REST API (PR #330) + +- API キー形式: `sk-aao-` (32 文字、23 bytes エントロピー、SHA-256 hash で DB 保存) +- **raw key は発行直後の 1 回だけ表示**、以降は prefix しか見えない (LiteLLM 同様の安全設計) +- Admin REST API で発行 / 一覧 / rotate / soft delete (revoked_at) +- config.yaml の static key は起動時に自動で DB に移行 +- `team` フィールドでテナント分離 + +### Phase 2b: 予算とレート制限 + UI 一式 (PR #332) + +| 機能 | 実装 | +|---|---| +| 月次 token 予算 | キーごとに `tokens_budget`。UTC 月初に counter リセット。超過した次のリクエストで 429 を返す (post-hoc enforcement) | +| RPM レート制限 | キーごとに `rate_limit_rpm`。sliding 60s window、in-memory カウンタ + 30s 周期で DB flush | +| 使用量集計 | `gateway_key_usage` テーブルに (key_id, period_start) 単位で tokens_in / tokens_out / requests を記録 | +| UI | Settings → Tools → **"Gateway Keys"** タブ。発行・無効化・rotate・月次グラフ | + +### Phase 3a: Polish bundle (PR #333) + +Phase 1-2b で残った INVESTIGATE 8 件をまとめて解消: + +1. Orphan key 検出時の警告ログ +2. `Authorization: Bearer ...` の regex を RFC 6750 厳密に +3. 5 秒 LRU の key auth cache (DB 負荷削減) +4. config 由来 key の drift resync (config.yaml を書き換えた時の DB 整合) +5. PATCH で revoked key を編集しようとしたら 409 conflict +6. Dead field cleanup +7. MAX_TRACKED_KEYS LRU eviction (rate limiter) +8. **SSE error の sentinel 化**: `gateway_shutdown` / `gateway_timeout` / `budget_exhausted` / `rate_limited` の 4 種をクライアントが識別可能に + +### Phase 3b: Prometheus exporter (PR #335) + +- **Gateway 11 metrics**: requests_total / tokens_total / backend_busy_slots / key_cache_size / latency histogram など +- **Worker 6 metrics**: jobs_total / piece_runs / queue depth など +- 全 metric に **per-team label** を載せて、テナント単位の利用量を分離可視化 +- `/metrics` は **デフォルトで localhost 限定** (`127.0.0.1` / `::1` allowlist)、外部 Prometheus 用に bearer token opt-in +- cardinality 暴走を防ぐため、label を 1 桁オーダーに抑える discipline + +### cleanup (PR #337) + +- AAO の LLM クライアント (`openai-compat.ts`) で gateway sentinel SSE error を parse + +### ops (PR #340) + +- `scripts/gateway.sh start | stop | status | logs` (`.gateway.pid` 管理、`logs/gateway.log`) +- `AAO_CONFIG` env で config.yaml path を override 可能 (両モード共通) +- `GATEWAY_PORT` env で listen port を override 可能 +- DB 共有設計 (worker と gateway は同一 SQLite を WAL で共有可能) を doc に明記 + +### Phase 3c: UI 制御 + 同プロセス default ← **本セッションで完了 (PR #341)** + +ここが今回の最大の変化です。これまで「gateway 専用プロセスを別 port で起動する」モデルだったのを抜本的に変更: + +| Before (Phase 1-3b) | After (Phase 3c) | +|---|---| +| `AAO_MODE=gateway` で gateway 専用プロセスを別 port (例: 9877) に起動 | 通常の worker AAO の **同一プロセス・同一 port** で動作 | +| Worker UI と gateway を物理的に分離 | UI のトグル 1 つで gateway を mount / unmount | +| 各 AAO に gateway 用の追加デプロイが必要 | 既存 AAO を起動して UI から有効化するだけ | + +#### 新しい設定 UI (Settings → Tools → "Gateway Server") + +- **Enable Gateway** トグル: チェックで gateway 起動、外して停止 +- **Backends list** (フォーム編集): + - Endpoint URL (http/https) + - Backend ID (任意の文字列、ルーティング識別子) + - Max slots (並列処理上限) + - API key (backend 側に必要な場合) +- 編集 + Save で **hot reload**: 進行中 SSE は `gateway_shutdown` event で drain、新接続から新 config で動作 +- **リアルタイム状態 badge**: `running` / `disabled` / `misconfigured` を 3 秒周期で表示 + +#### 動作モード + +- **Gateway 有効時**: `/v1/*` paths が gateway sub-app にルーティングされ、`/health` は LiteLLM 互換 JSON を返す +- **Gateway 無効時**: `/v1/*` は 404、`/health` は bridge の `{status:'ok'}` (Docker healthcheck 等の既存利用に影響なし) + +#### Phase 3c で重要だった 8 件のバグ修正 + +レビューで critical な問題が見つかり、すべて修正済み: + +1. **prom-client メトリクス重複登録クラッシュ**: gateway 設定変更 (= bounce) 1 回ごとに `Counter` を新規登録 → 2 回目で throw して bridge プロセスごと死ぬ問題。`WeakMap>` で memoize して解決 +2. **BackendStatusRegistry が worker 用の list を見ていた**: 同プロセス mode で gateway も worker と同じ registry を共有していたため、gateway 専用の backend が status 不明 → routing blind / `/health` 空 / metrics 0。Gateway は自前 registry を `gateway.backends[]` 上に build する設計に +3. **`/health` LiteLLM 互換が壊れていた**: bridge の既存 `/health` (`{status:'ok'}`) が mountGateway より先に登録されていて、LiteLLM 互換 JSON が返らない問題。`classifyGatewayPath` を 3 値 (`gateway-only` / `gateway-when-enabled` / `false`) に拡張し、Express middleware の登録順序を修正 +4. **transition 中の config が dropped される**: `state === 'starting' | 'stopping'` 中に新規 config 適用が落ちる問題。`pendingConfig` queue + mutex chain replay で対応 (実用上は mutex serialize で到達不能だが、防御として実装) +5. **stop() が state を misconfigured のまま残す**: 公開 stop API が state を `disabled` にリセットしない問題 +6. **configsEquivalent の比較が不安定**: `JSON.stringify` の key 順依存で偽 bounce が起きる問題。stable stringify に +7. **listenPort が env 依存**: 実 listen port と表示 port が乖離する可能性。`CoreServerOptions.listenPort` で配線 +8. **`api_key` 入力時に `${VAR}` env 参照が黙って literal 保存される**: amber warning text を form に表示 + +これらは round-1 / round-2 の adversarial review で発見・修正。テストでは隠蔽されやすいバグ (例: 1 番は `beforeEach` で fresh Registry を使うと検出できない) を含むため、shared-registry pattern の test を追加で書いて検証しています。 + +--- + +## どう使うか (運用ガイド) + +### パターン A: 単一 AAO で gateway を有効化 (Phase 3c 以降の推奨) + +``` +1. scripts/server.sh start で AAO を通常起動 +2. UI に admin としてログイン +3. Settings → Tools → "Gateway Server" を開く +4. "Enable Gateway" にチェック → Backends list を入力 → Save +5. Settings → Tools → "Gateway Keys" で API キーを発行 (raw key は発行直後のみ表示) +6. 他の AAO や OpenAI クライアントから: + base_url: http://this-aao:9876/v1 + api_key: sk-aao-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx +``` + +### パターン B: Gateway 専用サーバとしてデプロイ (advanced) + +GPU サーバが多い、または worker UI を運用したくない場合: + +```bash +AAO_MODE=gateway scripts/gateway.sh start +# AAO_CONFIG=/etc/aao/gateway.yaml で別 config を指定可能 +# worker mode と同じ DB を共有可能 (キー管理が両モードで一貫) +``` + +### パターン C: 既存 LiteLLM Proxy からの乗り換え + +互換性のポイント: + +- Endpoint path: `/v1/chat/completions` ・ `/v1/models` ・ `/health` 同じ +- Response header `x-litellm-model-id` をそのまま発行 (vendor-neutral な `x-aao-backend-id` も同値で同時発行) +- Key format: `sk-aao-*` (LiteLLM の `sk-*` から prefix を変更、長さ・エントロピー同等) +- `/health` の JSON shape: `{healthy_endpoints, unhealthy_endpoints, healthy_count, unhealthy_count}` で LiteLLM 互換 + +クライアント側の `parseLiteLLMHealth` 等の既存コードは無修正で動作します。 + +--- + +## 監視 + +`/metrics` で Prometheus 形式メトリクスを取得: + +``` +# Gateway 系 +aao_gateway_requests_total{backend="llm-a",team="ops",status="ok"} 142 +aao_gateway_tokens_total{backend="llm-a",team="ops",direction="out"} 95821 +aao_gateway_backend_busy_slots{backend="llm-a"} 2 + +# Worker 系 (same-process mode 時に同じ endpoint から) +aao_worker_jobs_total{piece="chat",status="succeeded"} 38 +``` + +外部 Prometheus からスクレイプする場合は config で `provider.metrics.bearer_token` を設定し、IP allowlist を緩和。 + +--- + +## 意図的にやっていない機能 (実需確認まで保留) + +| 機能 | 保留理由 | +|---|---| +| Sticky routing (cache hit 最適化) | 1 backend 構成 + cache hit 率 実測無しでは ROI 不明 | +| Canary routing (model rollout) | 現状 model A→B 切り替え予定なし | +| Token → USD コスト換算 | tokens 数で十分か admin の要望次第 | +| Audit log テーブル | compliance 要件無し時点では不要 | +| Pre-reserve budget (並列 burst) | 実際に N×max_tokens overshoot が観測されたら検討 | +| OpenTelemetry trace | 単一 org deploy では ROI 低い | +| Slack / Email アラート | Prometheus Alertmanager で代用想定 | +| Multi-AAO federation | Prometheus federation で代用想定 | + + +--- + +## 次のステップ + + +- Backend ルーティングの動作 (least-busy の精度) +- SSE drain の挙動 (大量同時接続 + graceful shutdown) +- Budget / RPM の境界値 (オフバイワン無いか) +- Prometheus metrics の cardinality / scrape duration +- UI hot reload の race condition +- LiteLLM 互換性 (既存 client コードが無修正で動くか) + +を検証。dogfooding で観測した問題から Phase 4 のスコープを決めます。 + +--- + +## 関連ドキュメント + +- In-product help: `ui/src/content/help/11-llm-gateway.md` +- INVESTIGATE backlog (open follow-up issues): Gitea issue #338 + +--- + +## マージ済み PR 一覧 + +| Phase | PR | merge commit | 日付 | +|---|---|---|---| +| 1 | #326 | `178baa9` | 2026-05-18 | +| 2a | #330 | `78a796d` | 2026-05-18 | +| 2b | #332 | `b0569c7` | 2026-05-19 | +| 3a | #333 | `30e9d78` | 2026-05-19 | +| 3b | #335 | `9efc60f` | 2026-05-19 | +| cleanup | #337 | `bcfdd41` | 2026-05-20 | +| ops | #340 | `13bd3cd` | 2026-05-20 | +| **3c** | **#341** | **`561ff24`** | **2026-05-20 (本セッション)** | + +累計コード追加: 約 12,500 行 (テスト含む)、テスト件数 2720 (ベースライン 2707 から +13)。 diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..69b2916 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,85 @@ +# Architecture Overview + +MAESTRO は、ユーザーが投げたタスクを LLM 駆動のワークフロー(Piece)で実行する +エージェントオーケストレーターである。コントリビュータ向けのコードマップは +[../AGENTS.md](../AGENTS.md) も参照。 + +## 実行フロー + +``` +UI (POST /api/local/tasks) + → bridge/server.ts (Express API) + → Repository (SQLite: jobs テーブルに enqueue) + → Worker.poll() が queued ジョブを取得 + → piece-classifier.ts: LLM がタスクを分類し Piece を選択 + → piece-runner.ts: pieces/*.yaml を読み、movements を順に実行 + → agent-loop.ts: 1 movement の ReAct ループ (LLM ↔ tool calls) + ├─ 中間遷移: transition ツール + └─ 終了: complete ツール (success / aborted / needs_user_input) + → ジョブ完了: DB 更新 + 進捗コメント。成果物は workspace/output/ +``` + +1. **API 受付** — `bridge/server.ts` がタスクを受け、`Repository` 経由で `jobs` テーブルに `queued` で登録する。 +2. **ワーカー** — `worker.ts` が DB をポーリングし、自分の `profiles`/`task_classes` に合致するジョブを取得(複数ワーカーが並走)。 +3. **分類** — `piece-classifier.ts` がタスク本文と全 Piece の description を LLM に渡し、最適な Piece を選ぶ。 +4. **Piece 実行** — `piece-runner.ts` が Piece の movements を順に回す。verify movement のフィードバックは次の execute に引き継がれ、`transition.lessons` で movement 間の教訓が蓄積される。 +5. **ReAct ループ** — `agent-loop.ts` が 1 movement 内で LLM とツールを往復させる。`ContextManager` が LLM の `usage` からトークン使用量を追跡し、閾値(70/85/95%)で warn / prompt / force_transition を発火する。 + +## Piece と Movement + +- **Piece** = `pieces/*.yaml`。`movements` 配列で構成。 +- 各 **Movement** は `allowed_tools`(LLM に提示するツール)、`edit`(Write/Edit 可否)、`rules`(遷移条件)を持つ。`allowed_tools` 外のツールは LLM から見えない。 +- **遷移**: 中間ホップは `transition`(`rules[].next` に列挙した宛先のみ選択可)、終了は `complete`。`complete.result` がユーザーに見える唯一の最終出力。 +- **`default_next`** はエンジン内部の sentinel(コンテキスト溢れ時の強制遷移、ASK 上限時のフォールバック)。 +- **Progressive pressure**: 同一 movement への連続訪問が増えると警告を注入し、閾値超過で ABORT。 + +## ツールランタイム + +ツールは `src/engine/tools/*.ts` のモジュール群。`tools/index.ts` が動的にロードし dispatch する。各ツールは 1 行の description(毎 LLM 呼び出しに乗るため簡潔に)を持ち、詳細手順は `docs/tools/.md`(`ReadToolDoc` で取得)に置く。主なモジュールは [../AGENTS.md](../AGENTS.md#tool-modules) の一覧を参照。 + +Read 系ツールは並列実行される。Write/Edit は movement の `edit: true` のときのみ提示され、書き込みは主に `workspace/output/` に限られる。 + +## Bash サンドボックス + +エージェントの Bash 実行は、利用可能なら **bwrap サンドボックス**で隔離する: + +- **ファイルシステム**: タスクの workspace のみ rw bind、`/usr` 等は ro、他タスクの workspace やホスト `/home` は不可視。 +- **環境変数**: `--clearenv` + 最小 allowlist のみ注入(シークレット env はサンドボックス内から見えない)。 +- **ネットワーク**: `--unshare-net` で遮断(外向き通信は SSRF ガード付きの WebFetch/MCP に集約)。 +- **各 Bash コールは独立**したサンドボックス(揮発 `/tmp`・毎回新名前空間)。永続するのは workspace のみ。 + +`safety.bash_sandbox` でモードを選ぶ(`auto`/`always`/`off`)。bwrap 不在時は **hardened フォールバック**(コマンド許可リスト + パススコープ検査 + env スクラブ付き exec)になる。実行時 `pip`/`npm install` は全モードで拒否され、Python パッケージは `runtime/python-requirements.txt` からプリベイクされる。詳細は [operations/bash-sandbox-provisioning.md](operations/bash-sandbox-provisioning.md)。 + +## ワークスペース構造(ジョブ実行時) + +``` +{worktree_dir}/local/{taskId}/ + input/ アップロード・DownloadFile の保存先 + output/ 成果物(Write/Edit が許可される主な場所) + logs/ activity.log / 各種履歴 + subtasks/ SpawnSubTask の結果 + skills/ ReadSkill で materialize されたスキルファイル +``` + +## データベース + +SQLite(better-sqlite3)。`db/schema.sql` が初期スキーマ。追加カラムは `db/migrate.ts` で +`PRAGMA table_info` → 存在チェック → `ALTER TABLE ADD COLUMN` のパターンで冪等に適用する +(バージョン管理テーブルは使わない)。主なテーブル: `jobs` / `local_tasks` / +`local_task_comments` / `audit_log` ほか。 + +## ジョブのライフサイクル + +`queued` → `dispatching` → `running` → `succeeded` / `failed` / `waiting_human`(ASK 回答待ち)/ `waiting_subtasks`(並列サブタスク待ち)。失敗時は `retry` で再 `queued`(最大 `retry.max_attempts` 回)。 + +## オプションのサブシステム + +- **LLM Gateway**(`src/gateway/`) — MAESTRO 自身を OpenAI 互換 LLM プロキシとして公開(仮想キー・予算・Prometheus メトリクス)。複数 GPU/チーム共有向け。env/接続種別が `AAO_*`/`aao_gateway` の歴史的接頭辞を使う。 +- **MCP** — Model Context Protocol サーバー連携(`MCP_ENCRYPTION_KEY` 必須)。 +- **Reflection** — ジョブ完了ごとにユーザーメモリを LLM が自動更新(既定 OFF、revert 可)。 +- **認証** — Passport による Google/Gitea OAuth(任意)。`private`/`org`/`public` の可視性モデル。 +- **スケジューラ** — cron 式の定期タスク。 + +## フロントエンド + +React + Vite + TailwindCSS + @tanstack/react-query。`ui/src/App.tsx` がルート。2 カラム(list + detail)レイアウトで、タスク一覧・スケジュール・設定・スキル/Piece 管理を扱う。 diff --git a/docs/bench.md b/docs/bench.md new file mode 100644 index 0000000..07338ec --- /dev/null +++ b/docs/bench.md @@ -0,0 +1,212 @@ +# ベンチマーク (`npm run bench`) + +エージェントの **ツールコール能力 / 命令追従性 / 頭の良さ / チェックリスト使用 / 効率** を、1 つの統合タスクから多軸で計測するためのフレームワーク。 + +モデル変更・piece 改修・ツール追加などの前後で同じタスクを走らせ、品質回帰を検出する用途を想定している。 + +--- + +## 1. 前提 + +| 項目 | 必須 | +|------|------| +| `scripts/server.sh start`(またはそれ相当)でオーケストレータが起動していること | ✅ | +| `config.yaml` の `provider` が動作する LLM を指している(タスク実行 + judge の両方で使う) | ✅ | +| 初回のみ `npm run bench:fixtures` で `bench/fixtures/sales.xlsx` を生成 | ✅ | + +ベンチランナーは外部ネットに依存しない。`fixtures/web/*` はランナー内蔵の **localhost HTTP サーバ** が配信する(起動時にランダムポートを取り、`{WEB_PORT}` トークンで prompt に注入)。 + +--- + +## 2. 使い方 + +```bash +# 全タスク +npm run bench + +# 単一タスク +npm run bench -- --task=composite-mini-report + +# 別ホスト/ポートのオーケストレータ向け +npm run bench -- --server=http://127.0.0.1:9876 + +# LLM judge を skip (axis D を 1.0 固定にして programmatic だけで採点) +BENCH_JUDGE=off npm run bench + +# judge を別エンドポイント・別モデルにする +BENCH_JUDGE_ENDPOINT=https://api.example.com/v1 \ +BENCH_JUDGE_MODEL=gpt-oss:20b \ +npm run bench +``` + +実行が終わると `bench/results//` に書き出される(`run_id` は ISO タイムスタンプ)。 + +--- + +## 3. 出力の見方 + +``` +bench/results/2026-05-01T03-22-11Z/ + summary.md # ← ここを最初に見る + composite-mini-report/ + result.json # 完全な採点 + raw データ + workspace/ + logs/activity.log # エージェントが書いたログ + output/report.md # エージェントの成果物 +``` + +`summary.md` の冒頭: + +``` +# Bench run @ 2026-05-01T03:22:11.000Z + +**Overall: 73 / 100** + +| Task | Status | Total | A | B | C | D | +| ---------------------- | ---------- | ----: | --: | --: | --: | --: | +| composite-mini-report | succeeded | 73 | 90% | 100%| 70% | 60% | +``` + +各タスクの詳細セクションでは axis ごとに ✓/✗ 内訳とツールコールの全シーケンスが折りたたみで見られる。 + +`result.json` は CI から機械可読に扱える形式。 + +--- + +## 4. 採点軸(重み 100 点満点) + +| 軸 | 重み | 何を見るか | 判定 | +|----|----:|-----------|------| +| **A. Tools** | 30 | `must_use_tools` を呼んだか / `forbidden_tools` を避けたか / `forbidden_tool_for_ext` (例: Read 禁止 .xlsx) | プログラム | +| **B. Checklist** | 15 | `CreateChecklist` / `CheckItem×N` / `GetChecklist` の使用 | プログラム | +| **C. Instructions** | 30 | 出力ファイル名・1 行目固定・セクション順・行数・文字数・禁止パターンなど | プログラム | +| **D. Reasoning** | 25 | 内容の妥当性・統合の質・「次アクション」の具体性など | LLM judge ルーブリック | +| _補助: Efficiency_ | – | duration / prompt tokens(summary に数値表示のみ) | – | + +`Total = A×30 + B×15 + C×30 + D×25` を 0..100 に正規化。 + +`completion_status: [succeeded, waiting_human, failed, aborted, cancelled]` で受理する終了状態を指定。デフォルトは `[succeeded]` のみ。**failure でも grader は走り部分スコアが出る**。 + +--- + +## 5. 既存タスク + +### `composite-mini-report` + +3 ソース(Excel / Web / Markdown)を統合してミニレポートを書かせるタスク。1 本で全軸が動く。 + +- 必須ツール: `ReadExcel` / `WebFetch` / `Read` / `Write` / `CreateChecklist` / `CheckItem` (≥3 回) / `GetChecklist` +- 禁止: `.xlsx` を `Read` で開く(バイナリ混入防止 — issue #189 と同じ罠) +- 出力 `output/report.md` に厳格な形式制約(1 行目固定、セクション順、各セクション 5 行以内、「次アクション」3 件 40 字以内、画像・HTML 禁止) +- judge ルーブリック: `factual_grounding` / `actions_quality` / `synthesis` + +`bench/tasks/composite-mini-report.yaml` を参考実装としてそのまま使える。 + +--- + +## 6. タスクを追加する + +`bench/tasks/*.yaml` を作るだけで自動的に拾われる。スキーマは `src/bench/types.ts` の `BenchTask` を参照。最小例: + +```yaml +id: my-task +title: 短いタスク説明 +piece_hint: chat # piece 名 (省略時は chat) +timeout_minutes: 5 + +fixtures: # 任意 + - source: fixtures/data.txt # bench/ ルート相対 + dest: input/data.txt # input/ に置けば attachments としてアップロード + - source: fixtures/web/page.html + dest: web/page.html # web/ に置けば fixture HTTP server が配信 + +prompt_tokens: # 任意。prompt 内の {KEY} を実行時に置換 + CUSTOM_KEY: foo +prompt: | + http://127.0.0.1:{WEB_PORT}/page.html を読み、… {CUSTOM_KEY} … + +expected: + must_use_tools: [WebFetch, Write] + forbidden_tools: [Bash] + forbidden_tool_for_ext: + Read: ['.xlsx'] + must_produce_files: [output/answer.md] + completion_status: [succeeded] + +checklist: # 任意。指定すると軸 B が有効化される + required_tools: [CreateChecklist, CheckItem, GetChecklist] + min_check_item_calls: 3 + +grading: + programmatic: + constraints: + - { type: file_first_line_equals, file: output/answer.md, line: '# Title' } + - { type: file_must_contain_in_order, file: output/answer.md, sections: ['## A', '## B'] } + - { type: file_section_max_lines, file: output/answer.md, section: A, max: 5 } + - { type: file_line_starts_with, file: output/answer.md, prefix: '-', min_lines: 3, section: B } + - { type: file_line_max_chars, file: output/answer.md, max: 40, section: B } + - { type: file_no_pattern, file: output/answer.md, pattern: '!\[' } + + llm_judge: # 任意。指定しないと軸 D は 1.0 固定 + rubrics: + - name: relevance + prompt: 出力が prompt の意図と整合しているか + max_score: 10 +``` + +### プログラム制約の種類 + +| `type` | 意味 | +|--------|------| +| `file_first_line_equals` | ファイル 1 行目が完全一致するか | +| `file_must_contain_in_order` | 指定文字列が指定の順序で出現するか | +| `file_line_starts_with` | (任意セクション内で) 指定 prefix で始まる行が `min_lines` 以上あるか | +| `file_line_max_chars` | (任意セクション内で) 各行の文字数が `max` 以下か | +| `file_section_max_lines` | 指定セクションの非空行が `max` 以下か | +| `file_no_pattern` | 正規表現 (multiline) にマッチしないか | + +`section` は `## ヘッダ` の `ヘッダ` 部分(`##` は付けない)。指定無しならファイル全体が対象。 + +--- + +## 7. 内部構造 + +``` +bench/ + fixtures/ + tasks/ + results/ # gitignored +src/bench/ + types.ts # BenchTask / BenchResult / 制約スキーマ + fixture-server.ts # localhost HTTP fixture server + runner.ts # /api/local/tasks に投入 + ポーリング + ログ収集 + grader.ts # 軸 A/B/C のプログラム採点 + judge.ts # 軸 D の LLM judge 呼び出し + JSON parse + summary.ts # bench/results//summary.md 書き出し + grader.test.ts +scripts/ + bench-run.ts # CLI エントリ (`npm run bench`) + build-bench-fixtures.ts # sales.xlsx 生成 (`npm run bench:fixtures`) +``` + +ベンチランナーは既存の `/api/local/tasks` API を使うだけで、orchestrator 内部とは疎結合になっている。新しい piece やツールを追加してもベンチ側は基本変更不要。 + +--- + +## 8. トラブルシューティング + +| 症状 | 原因 / 対処 | +|------|-------------| +| `runner failed for ...: fetch failed` | `scripts/server.sh start` が立っていない、または `--server=` で指定したポートが違う | +| 全タスクで axis D が 0.0 | judge LLM のレスポンスが JSON parse 失敗。`bench/results//summary.md` の reasoning details を確認。OS の事情で短い応答しか返ってこないモデルなら `BENCH_JUDGE_MODEL` を別モデルに切り替える | +| タイムアウトで status が固まる | タスク YAML の `timeout_minutes` を伸ばす。プロバイダ側 `provider.timeoutMinutes` も併せて確認 | +| 同じタスクで毎回スコアが揺れる | LLM judge が確率的なため。programmatic 軸だけ見る・複数回平均を取る運用が無難 | +| `bench/results/` がコミットに乗ってしまった | `.gitignore` 済みだが、過去に追跡されていた場合は `git rm -r --cached bench/results` | + +--- + +## 9. 参考: 関連 issue / 機能 + +- #156 — このベンチマーク自体 +- #189 — `Read` で xlsx を開かない仕様(composite-mini-report の `forbidden_tool_for_ext` 罠と直結) +- #190 — preflight ログ表示の整理(activity.log を grader が読みやすいことの恩恵) diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..b82294d --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,229 @@ +# Configuration Reference + +MAESTRO は単一の `config.yaml`(`config.yaml.example` をコピーして作成)で設定する。 + +- **YAML キーは snake_case**(`max_concurrency`)、コード内は camelCase。`src/config.ts` の `transformKeys` が変換する。 +- 一部は**環境変数で上書き**できる([末尾参照](#environment-variable-overrides))。 +- `config_version: 2` が現行スキーマ。 + +> 値の一次ソースは `config.yaml.example`(コメント付き)と `src/config.ts`。本リファレンスは各項目の意味をまとめたもの。 + +--- + +## `llm` — ジョブ実行時の LLM 接続 + +| キー | 既定 | 意味 | +|------|------|------| +| `timeout_minutes` | 10 | 1 リクエスト全体の上限(分)。 | +| `retry.max_attempts` | 3 | 429/5xx/一時接続失敗時の再試行回数。 | +| `retry.backoff_ms` | [2000,5000,15000] | 各再試行の待機(ms)。 | +| `retry.retryable_status` | [429,500,502,503,504] | 再試行対象の HTTP ステータス。 | + +### `llm.workers[]` — ジョブ実行に使う接続先(必須) + +| キー | 意味 | +|------|------| +| `id` | ワーカー識別子。 | +| `connection_type` | `direct`(Ollama/vLLM 等の OpenAI 互換 backend に直結)/ `aao_gateway`(別 Gateway 経由、Gateway Key 必須)。 | +| `endpoint` | OpenAI 互換 API のベース URL(例 `http://localhost:11434/v1`)。 | +| `model` | 使用モデル名(ワーカーごとに明示。`default_model` は廃止)。 | +| `api_key` | `aao_gateway` 時の virtual key 等(任意)。 | +| `roles` | 用途フィルタ: `auto`/`fast`/`quality`/`title`/`reflection` 等。`[title]` のみ=タイトル生成専用。 | +| `max_concurrency` | このワーカーの並列度。 | +| `vlm` | `true` で画像入力対応(ReadImage は VLM ワーカーを優先)。 | +| `enabled` | 有効/無効。 | + +### `llm.metrics` — Prometheus exporter(worker 側) + +| キー | 既定 | 意味 | +|------|------|------| +| `enabled` | true | `/metrics`(bridge HTTP, 既定 `PORT=9876`)に mount。 | +| `prefix` | `aao_worker` | メトリクス名 prefix。 | +| `bearer_token` | — | 設定すると Bearer 認証必須(`env:NAME` 形式可)。 | +| `allowed_hosts` | localhost のみ | 許可元 IP。本番は bearer か allowlist を必ず設定。 | + +--- + +## `gateway` — LLM Gateway サーバー(任意) + +MAESTRO 自身を OpenAI 互換の LLM Gateway として公開する(仮想キー・予算・メトリクス付き、複数 GPU/チーム共有向け)。**env 変数や接続種別が `AAO_*` / `aao_gateway` という歴史的な接頭辞を使う点に注意(AAO = この Gateway 機能の旧称)。** 詳細は [aao-gateway-overview.md](aao-gateway-overview.md)。 + +| キー | 既定 | 意味 | +|------|------|------| +| `enabled` | false | true で同 process gateway が起動(hot reload 対応)。 | +| `listen_port` | 4000 | separate-deploy 時のみ。 | +| `request_timeout_sec` | 600 | 1 リクエスト全体(streaming 込み)。 | +| `upstream_timeout_sec` | 30 | 各 upstream の TTFB 上限。 | +| `shutdown_graceful_sec` | 30 | SIGTERM 後の SSE drain 上限。 | +| `backends[]` | — | `id`/`endpoint`/`model`/`max_slots`/`api_key`。model 厳密一致で routing。 | +| `virtual_keys[]` | — | bootstrap/backup 用(`key`/`team`/`allowed_models`/`tokens_budget`/`rate_limit_rpm`)。新規発行は admin API 推奨。 | +| `metrics` | enabled | `prefix: aao_gateway`、team/key_prefix/backend ラベル。auth 必須運用。 | + +Virtual Key の発行・rotation は admin REST API(`POST /api/admin/gateway/keys`)または UI(Settings → LLM → Gateway Server)で行う。 + +--- + +## `storage` — パス・容量 + +| キー | 既定 | 意味 | +|------|------|------| +| `worktree_dir` | ./data/workspaces | ジョブ作業ディレクトリのベース。 | +| `custom_pieces_dir` | — | リポジトリ内 `pieces/` に加えて読む Piece dir(任意)。 | +| `user_folder_root` | ./data/users | `{root}/{userId}/` に AGENTS.md/scripts/notes 等を保存。 | +| `task_upload_max_size_mb` | 50 | タスク/コメント body 上限(base64 込み。範囲 1–1000)。 | +| `trash_retention_days` | 30 | `trash/` の自動 sweep。0 で都度全削除。 | + +--- + +## Execution — 並列度・上限・再試行 + +| キー | 既定 | 意味 | +|------|------|------| +| `concurrency` | 4 | 全ワーカー合算の最大並列ジョブ数(env `CONCURRENCY`)。 | +| `max_movements` | 200 | 1 ジョブ内の最大 movement 数(loop 防止)。 | +| `retry.max_attempts` | 3 | ジョブ失敗時の最大再試行。 | +| `retry.backoff_seconds` | [60,300,900] | 各 attempt 間の待機秒。 | +| `ask.max_per_job` | 2 | 1 ジョブの ASK(ユーザー質問)上限。 | +| `subtasks.max_depth` | 2 | SpawnSubTask のネスト最大深度。 | +| `subtasks.max_per_parent` | 10 | 1 ジョブが生成できるサブタスク最大数。 | + +--- + +## `context` — LLM コンテキスト管理 + +| キー | 既定 | 意味 | +|------|------|------| +| `limit_tokens` | 自動取得→128000 | コンテキスト上限。省略時はプロバイダ API から自動取得。 | +| `thresholds[]` | 0.7=warn / 0.85=prompt / 0.95=force_transition | 使用率閾値ごとの動作。 | + +--- + +## `safety` — 暴走防止・Bash サンドボックス + +| キー | 既定 | 意味 | +|------|------|------| +| `max_iterations` | 200 | 1 movement 内の最大イテレーション。 | +| `max_revisits` | 3 | 同一 movement の最大再訪問。超過で ABORT。 | +| `prompt_guard_ratio` | 0.8 | プロンプトがコンテキスト上限の何%まで許容するか(0.5–0.95)。 | +| `history_summarization.enabled` | true | 古い turn を構造化要約に置換して粘る。 | +| `history_summarization.tail_turns` | 2 | 末尾何 turn を保護するか。 | +| `history_summarization.preserve_recent_budget` | 8000 | 末尾保護の最大トークン。 | +| `bash_unrestricted` | false | true で Bash のコマンド許可リストを撤廃(**サンドボックス機構は別途 `bash_sandbox` が制御**)。 | +| `bash_sandbox` | auto | Bash 隔離機構: `auto`(bwrap あれば使用、無ければ hardened-whitelist)/ `always`(bwrap 強制・不在なら起動失敗、本番推奨)/ `off`(bwrap 不使用、env スクラブは維持)。詳細 [operations/bash-sandbox-provisioning.md](operations/bash-sandbox-provisioning.md)。 | + +--- + +## `search_filter` — WebSearch の機密情報漏洩防止 + +| キー | 既定 | 意味 | +|------|------|------| +| `blocked_patterns[]` | — | 完全一致で検索クエリから除去するパターン。 | +| `auto_block.private_ip` | true | 10/172.16-31/192.168/127.* を自動ブロック。 | +| `auto_block.internal_domain` | true | `.local`/`.internal`/`.lan`/`.intranet`/`.corp`/`.home`。 | +| `auto_block.email` / `phone` | true | メール/電話番号。 | + +--- + +## `browser` — BrowseWeb ランタイム + +| キー | 既定 | 意味 | +|------|------|------| +| `page_timeout` | 60000 | ページ遷移 timeout(ms)。 | +| `action_timeout` | 30000 | アクション timeout(ms)。 | +| `captcha_solve` | skip | `skip` / `novnc`(人手 CAPTCHA 解決)。 | +| `max_captcha_pages` | 5 | CAPTCHA ページ上限。 | +| `channel` | chromium | `chromium`/`chrome`/`msedge`。 | +| `executable_path` | — | ブラウザ実行ファイル(channel と排他)。 | + +--- + +## `tools` — ツール設定 + +UI 上は Web & Search / Browser / Media & Documents / External Services / Legacy Knowledge に分かれるが YAML は `tools` 1 ブロック。主な項目: + +**Web & Search**: `searxng_url`(WebSearch フォールバック先), `webfetch_timeout`(sec), `websearch_timeout`, `webfetch_allowed_hosts[]`(SSRF 例外: private IP/.local を許可する場合)。 + +**Media & Documents**: `vision_model`/`vision_base_url`/`vision_timeout`/`vision_max_tokens`(ReadImage 用 VLM), `ocr_model`, `office_{excel,docx,pdf}_max_size_mb`(既定 10), `office_pptx_max_size_mb`(50), `office_pptx_max_uncompressed_mb`(200, zip-bomb 検知), `speech_server_url`/`speech_timeout`/`speech_language`。 + +**External Services**: `x_*`(Twitter/X CLI 連携: `x_cli_command`/`x_auth_token`/`x_ct0`/`x_proxy`/`x_download_*` 等), `google_maps_api_key`/`maps_timeout`(未設定で Nominatim/OSRM), `amazon_affiliate_tag`/`keepa_api_key`。 + +**User scripts**: `user_scripts_enabled`(RunUserScript。plain runtime は Node `--permission` で sandbox 化), `user_scripts_allow_userids[]`。 + +**Legacy Knowledge**: `knowledge_service_url`(未設定で knowledge ツール無効), `knowledge_namespaces`(namespace ごとの api_key)。新規 namespace は MCP 経由を推奨。 + +--- + +## `notes` — Shared Knowledge Notes 注入 + +`data/users/{userId}/notes/` のノートをシステムプロンプトに注入。`inject.per_note_max_kb`(日本語は 4 推奨), `inject.total_max_kb`, `inject.over_budget_strategy`(`truncate_last`/`skip_remaining`/`degrade_to_search`)。 + +--- + +## `auth` — 認証(任意) + +未設定なら認証なしで動作。 + +| キー | 意味 | +|------|------| +| `session_secret` | セッション署名鍵(ランダム文字列)。 | +| `session_max_age` | セッション有効期間(ms, 既定 86400000=24h)。 | +| `secure_cookie` | HTTPS 環境では true。 | +| `admin_emails[]` | admin ロールにするメール。 | +| `primary_provider` | `google` / `gitea`(両方有効時に明示)。 | +| `providers.google` | `client_id`/`client_secret`/`callback_url`。 | +| `providers.gitea` | `client_id`/`client_secret`/`base_url`/`callback_url`。ログイン時に Gitea org を取得し可視性に利用。 | + +--- + +## `branding`(任意) + +`app_name`/`primary_color`/`login_page_title`/`logo_url`/`favicon_url`/`footer_text`。Settings → System → Branding(admin)で GUI 編集可。`config.yaml`・`data/branding/` は gitignore 済み。 + +--- + +## `secrets` + +`master_key_path`(既定 `./data/secrets/master.key`, 32 byte, 初回起動で自動生成・mode 0600)。SSH 鍵・MCP トークン等の暗号化に使う。 + +--- + +## `reflection` — 学習(既定 OFF) + +ON で毎ジョブ完了後にユーザーメモリを LLM が自動更新(snapshot は revert 可)。`enabled`, `max_memory_changes_per_job`(3), `piece_edit_cooldown_hours`(24), `snapshot_retention_days`(90), `per_user_daily_budget_tokens`(200000)。 + +--- + +## `mcp` — Model Context Protocol + +サーバーは admin UI(global)/各ユーザー(self-hosted)で管理。**`MCP_ENCRYPTION_KEY` env(64 hex)が必須。** `call_timeout_seconds`(60), `max_binary_size_mb`(20), `max_output_files_per_job`(10), `max_output_size_mb_per_job`(200), `tool_cache_ttl_seconds`(600), `oauth_pending_ttl_minutes`(10), `allow_private_addresses`(private 網の MCP server 用、既定 false)。 + +--- + +## `ssh` — SSH ツール(既定 OFF) + +`enabled`, `allow_private_addresses`(global 既定、admin は per-connection grant 可), `call_timeout_seconds`(30), `max_output_bytes`(32768), `max_{upload,download}_size_mb`(100), `audit_retention_days`(90), `admin_bypasses_grants`(true), abuse 検知(`abuse_window_minutes`/`abuse_failure_threshold`/`abuse_lock_minutes`)。 + +**Interactive Console**(`ssh.console`): `enabled`, `idle_timeout_seconds`(1800), `max_session_duration_seconds`(14400), `scrollback_bytes`(524288), `max_sessions_per_connection`(3) ほか。手順は [ssh.md](ssh.md)。 + +--- + +## `notifications.push` — Web Push(V2, 任意) + +HTTPS ホスティング必須(iOS は PWA インストール)。`enabled`, `vapid_subject`(RFC 8292), `vapid_current_path`(自動生成・mode 0600), `vapid_history_dir`, `payload_max_bytes`(3072, 上限 4096), `queue_concurrency`(8), `per_send_timeout_ms`(10000)。鍵ローテーション: `npm run vapid-rotate`。 + +--- + +## Environment variable overrides + +一部設定は環境変数で上書きできる: + +| 環境変数 | 上書き対象 | +|----------|------------| +| `OLLAMA_BASE_URL` | LLM エンドポイント | +| `OLLAMA_MODEL` | モデル名 | +| `WORKTREE_DIR` | `storage.worktree_dir` | +| `CONCURRENCY` | `concurrency` | +| `DB_PATH` | SQLite DB パス | +| `PORT` | bridge HTTP ポート(既定 9876) | +| `LOG_LEVEL` | `debug`/`info`/`warn`/`error`(既定 info) | +| `MCP_ENCRYPTION_KEY` | MCP/SSH 秘密の暗号化鍵(MCP 利用時必須) | diff --git a/docs/context-flow.md b/docs/context-flow.md new file mode 100644 index 0000000..b94b3ad --- /dev/null +++ b/docs/context-flow.md @@ -0,0 +1,235 @@ +# コンテキスト構築と溢れ時動作 + +この資料は、新規メッセージ送信時に何が LLM へ渡るか、movement 間で何が引き継がれるか、コンテキストが逼迫した時にどう動くかをまとめたものです。 + +## 全体像 + +```mermaid +flowchart TD + U[UI / API からタスク・コメント送信] --> DB[(SQLite jobs / comments)] + DB --> W[Worker が job を取得] + W --> C[会話コンテキストを組み立て] + C --> P[piece-runner が Piece を実行] + P --> M[Movement 開始] + M --> A[agent-loop: LLM に送信] + A --> T{LLM 応答} + T -->|tool_call| X[許可されたツールを実行] + X --> H[ツール結果を movement 内履歴へ追加] + H --> A + T -->|transition| N{next_step} + N -->|次 movement| P + N -->|COMPLETE| S[Job succeeded] + N -->|ASK| Q[waiting_human] + N -->|ABORT| F[failed / aborted] + N -->|WAIT_SUBTASKS| ST[waiting_subtasks] +``` + +## 新規メッセージで送られる内容 + +ローカルタスクでは、Worker が `job.instruction` をそのまま送るのではなく、現在時刻、直近コメント、workspace 状況、タスク本文をまとめた `enrichedInstruction` を作ります。 + +```mermaid +flowchart TD + J[job.instruction] --> E[enrichedInstruction] + Time[現在日時ブロック] --> E + Comments[直近コメント最大5件] --> Trunc[各コメント最大500文字に切り詰め] + Trunc --> E + Files[input/ と output/ のファイル一覧] --> E + E --> Piece[runPiece] +``` + +含まれるもの: + +| 種別 | 内容 | +|------|------| +| 現在時刻 | `buildTimeContextBlock()` の結果 | +| これまでのやり取り | Local task comments の末尾 5 件 | +| コメント本文 | 1 コメントあたり最大 500 文字 | +| workspace 状況 | `input/` と `output/` のファイル一覧 | +| タスク本文 | 現在の `job.instruction` | + +含まれないもの: + +- すべての過去コメント全文 +- 過去 movement の LLM 会話履歴全文 +- `logs/` 配下のログ全文 +- `input/` / `output/` のファイル本文そのもの + +ファイル本文が必要な場合は、movement 内で `Read` などのツールを使って取得します。 + +## Movement 内で送られる内容 + +各 movement は、system prompt と user prompt から開始します。movement の中では、LLM がツールを呼ぶたびに assistant の tool call と tool result が同じ movement の `messages` に追加され、次の LLM 呼び出しへ再送されます。 + +```mermaid +sequenceDiagram + participant R as piece-runner + participant L as agent-loop + participant M as LLM + participant T as Tool + + R->>L: movement + enrichedInstruction + L->>M: system prompt + user prompt + M-->>L: tool_call + L->>T: executeTool + T-->>L: tool result + L->>M: これまでの messages + tool result + M-->>L: transition(next_step, summary, lessons) + L-->>R: MovementResult +``` + +movement 内で保持されるもの: + +- system prompt +- user prompt +- LLM の tool call +- ツール結果 +- `ReadImage` などが返した画像コンテキスト +- transition までのリマインド文 + +movement が終わると、この `messages` 全体は次 movement にそのまま渡されません。 + +## Movement 間で引き継がれる内容 + +movement 間では、会話履歴全文ではなく、限定された要約情報だけが引き継がれます。 + +```mermaid +flowchart LR + M1[Movement A] --> TR[transition] + TR --> Lessons[lessons / summary] + Lessons --> Log[logs/lessons.jsonl] + Lessons --> Inject[次 movement の prompt に注入] + Inject --> M2[Movement B] +``` + +主な引き継ぎ: + +| 引き継ぎ元 | 次 movement への渡り方 | +|------------|------------------------| +| transition の `lessons` | `## 前のステップで得た教訓` として注入 | +| `lessons` 未指定で COMPLETE | `summary` / output を lesson として扱う | +| verify 系の指摘 | 対象 movement へフィードバックとして追記 | +| チェックリスト | `logs/checklists/*.json` から現在状態を注入 | +| workspace の変更状況 | 一部の verify 後に git status / diff 抜粋を付加 | + +Lessons は最大 2000 文字程度に抑えられ、古いものから削られます。 + +## コンテキスト逼迫時の動作 + +`ContextManager` は LLM の usage が取れる場合は `prompt_tokens`、取れない場合は文字数推定で使用率を見ます。 + +デフォルト閾値: + +| 使用率 | action | 動作 | +|--------|--------|------| +| 70% | `warn` | progress に警告を記録 | +| 85% | `prompt` | 「作業をまとめて transition してください」という user message を追加 | +| 95% | `force_transition` | 現 movement を強制的に `defaultNext` へ進める。`defaultNext` がなければ `ABORT` | +| 99% | exhausted 判定 | 内部判定用。直接の圧縮処理ではない | + +```mermaid +flowchart TD + LLM[LLM 応答完了] --> Usage{usage あり?} + Usage -->|あり| Tokens[prompt_tokens を記録] + Usage -->|なし・3 iteration 以降| Chars[messages 文字数から推定] + Tokens --> Ratio[context 使用率を計算] + Chars --> Ratio + Ratio --> Warn{70%以上?} + Warn -->|warn 未発火| W[warn action] + Ratio --> Prompt{85%以上?} + Prompt -->|prompt 未発火| P[transition 促しを messages に追加] + Ratio --> Force{95%以上?} + Force -->|force_transition 未発火| F[defaultNext へ強制遷移] +``` + +現状の重要点: + +- コンテキスト逼迫時に、会話全体を自動要約して同じ movement を継続する処理はありません。 +- `force_transition` は「完了した」と判断するのではなく、`movement.defaultNext` へ進める機械的な退避です。 +- `defaultNext` が `COMPLETE` の movement では、結果として完了扱いになる場合があります。 +- `defaultNext` がない場合は `ABORT` になります。 + +## ツール出力の切り詰め + +大きなファイルやコマンド出力をそのまま入れると movement 内の `messages` が膨らむため、一部ツールは残コンテキストに応じて出力を自動切り詰めします。 + +```mermaid +flowchart TD + Tool[Read / Bash / Office 系ツール] --> Budget[残コンテキストから tool result 予算を計算] + Budget --> Fit{予算内?} + Fit -->|Yes| Full[全文または要求範囲を返す] + Fit -->|No| Cut[先頭側を返し、自動切り詰め注記を付ける] + Cut --> Hint[続きを読む offset / byte_offset / grep 等を案内] +``` + +予算計算: + +- `ContextManager` がある場合: `getAvailableTokens()` の 50% を 1 回の tool result 予算にする +- 予算の上限: 60,000 tokens +- 予算の下限: 最低返却 tokens を確保 +- `Read` は行指向なら行境界、改行が少ないファイルは byte/char 境界で切ります +- `Bash` は `head` / `tail` / `grep` / `awk` / `sed` などで絞る案内を付けます + +## よくあるパターン + +### 1. 通常の新規タスク + +```mermaid +flowchart TD + Create[タスク作成] --> Recent[直近コメント最大5件 + workspace 状況] + Recent --> Initial[initial_movement] + Initial --> Tools[必要なファイルだけツールで読む] + Tools --> Transition[transition] + Transition --> Next[次 movement] +``` + +この場合、過去コメント全文やファイル本文は最初から全部送られません。 + +### 2. ユーザーへの ASK 後に再開 + +```mermaid +flowchart TD + Ask[transition: ASK] --> Wait[Job waiting_human] + Wait --> Reply[ユーザーが返信] + Reply --> Queue[Job queued] + Queue --> Worker[Worker が再取得] + Worker --> Context[直近コメント最大5件を再構築] + Context --> Resume[resumeMovement から再開] +``` + +ASK 再開時も、基本は直近コメントから再構築します。前回 movement 内の LLM メッセージ全文を保存して再投入する方式ではありません。 + +### 3. 長いファイルを読む + +```mermaid +flowchart TD + Read[Read large file] --> Budget{tool result 予算} + Budget --> Truncated[自動切り詰め] + Truncated --> Notice[続きの読み方を表示] + Notice --> Targeted[必要範囲だけ再 Read / Grep] +``` + +長文を読む場合は、全文を一度に入れるより、検索や範囲指定で必要箇所を絞る前提です。 + +### 4. コンテキストが 95% を超える + +```mermaid +flowchart TD + High[Context 95%以上] --> Force[force_transition] + Force --> HasDefault{defaultNext あり?} + HasDefault -->|Yes| Next[defaultNext へ移動] + HasDefault -->|No| Abort[ABORT] + Next --> Runner[piece-runner が遷移判定を続行] +``` + +これは現状の退避動作です。圧縮サマリを作って同じ movement を継続する動作は未実装です。 + +## 参照実装 + +| 内容 | ファイル | +|------|----------| +| 新規タスクの `enrichedInstruction` 構築 | `src/worker.ts` | +| Piece / movement 遷移、Lessons 注入 | `src/engine/piece-runner.ts` | +| movement 内の LLM messages と tool result 追加 | `src/engine/agent-loop.ts` | +| コンテキスト閾値管理 | `src/engine/context-manager.ts` | +| Read / Bash 等の tool result 切り詰め | `src/engine/tools/core.ts` | diff --git a/docs/design/README.md b/docs/design/README.md new file mode 100644 index 0000000..e27b10f --- /dev/null +++ b/docs/design/README.md @@ -0,0 +1,151 @@ +# Agent Orchestrator — Design System + +A local, single-tenant agent orchestration platform. Users submit natural-language tasks via a small Japanese-language admin UI; an LLM classifier routes each task into a named **Piece** (workflow), which runs a multi-step **Movement** chain against local Ollama workers and emits progress/results back to the UI as chat-style comments. + +## Product at a glance + +- **One product, one surface:** a React + Vite + TailwindCSS admin dashboard at `/ui/` on the orchestrator server (`http://this-machine:9876/ui/`). Mobile (single-column), tablet (list + chat), desktop (list + chat + detail) layouts. +- **Language:** Japanese throughout (UI labels, empty states, toasts). Latin/mono treatment reserved for identifiers, status keywords, version tags, wordmark. +- **Primary objects:** Task → Job → Movement → Tool call. Status kanban: `queued / running / waiting_human / waiting_subtasks / retry / succeeded / failed / cancelled`. +- **Interaction model:** a threaded chat with progress cards interleaved between user requests and agent results (green = result, amber = ASK, blue bubble = user, grey pill card = progress). + +## Sources consulted +- **Codebase (read-only local mount):** `gitea-agent-orchestrator/` + - `ui/tailwind.config.js`, `ui/src/index.css` — tokens, fonts + - `ui/src/lib/utils.ts` — `statusTone()`, `relativeTime()`, activity parsing + - `ui/src/components/**` — TopBar, FilterBar, TaskListItem, ChatPane, ChatMessage, DetailHeader, OverviewTab, ProgressTab, CreateTaskDialog, EmptyState, StatChip, StatusBadge, LoadingSpinner + - `ui/public/favicon.svg` — the "hub + 3 agents" mark + - `README.md` (root) — product narrative, piece list, tool registry +- **No Figma, no slide templates, no marketing site** were provided; this design system documents the existing admin UI only. + +## Index + +| File | Purpose | +|---|---| +| `README.md` | This document | +| `SKILL.md` | Agent Skill wrapper (portable to Claude Code) | +| `colors_and_type.css` | CSS variables for color/type/spacing/radii/shadow | +| `assets/logo.svg` | 32×32 brand mark (blue rounded square, orchestrator hub + 3 agent nodes) | +| `assets/wordmark.svg` | Logo + "AGENT ORCHESTRATOR" mono wordmark lockup | +| `preview/*.html` | Atomic design-system cards (registered in review) | +| `ui_kits/admin/` | High-fidelity React recreation of the admin dashboard | + +--- + +## Content fundamentals + +**Voice.** Terse, functional, Japanese. Almost no marketing flourish. Labels are nouns or imperative verbs — "新しい依頼" (new request), "Task 作成" (create Task), "詳細" (details), "送信" (send), "共有停止" (stop sharing). + +**Person.** Neither "あなた" nor "私" — the UI talks about objects, not people. Empty state uses a numbered list of actions ("左パネルからタスクを選択する" — "select a task from the left panel") rather than second-person. + +**Case & casing.** +- Japanese sentences where there's human prose. +- English identifiers kept English, capitalized as in code: **Tasks / Schedules / Settings / Users** (TopBar navs), **Inbox / Running / Waiting / Subtasks / Retry / Done / Failed / Cancelled** (status columns). These are not translated, even in a Japanese UI. +- Meta labels are SMALL-CAPS UPPERCASE with wide tracking in the mono face: `STEP`, `TOOL`, `PREVIEW`, `FINAL`, `ASK`, `LOG`. +- "Agent Orchestrator" wordmark: uppercase, mono, letter-spacing: 0.16em, blue-600. +- Product name "agent-orchestrator" in kebab-case in docs; UI header is Title Case. + +**Example strings** (verbatim from code): +- `新しい依頼` (new request) — primary CTA +- `スレッドを選択してください` (please select a thread) — empty state title +- `左の一覧から選ぶと、会話、進捗、成果物を追えます。` — empty state description +- `メッセージを入力... (Ctrl+Enter で送信)` — composer placeholder +- `まだ進行情報がありません。` (no progress info yet) +- `(activity.log がまだありません)` — empty log fallback +- `良かった` / `改善が必要` — feedback thumbs labels + +**Numbers & units.** Counts render as ` 件` (items), ` 実行中` (running), ` 待機` (waiting). Relative time in Japanese: `たった今 / N分前 / N時間前 / N日前`. Durations mix units: `ms`, `s`, `m Ns`. + +**Tone.** Operator-facing, not consumer-facing. No exclamation marks, no emoji in UI chrome. Emoji appear only as **domain signals inside agent content**: 👍 / 👎 on feedback buttons, 📋 on checklist progress cards. Unicode symbols (☑ ✗ ⊘ ☐ ▶) are used as list markers inside agent-emitted checklists. Do not introduce new emoji outside these established spots. + +--- + +## Visual foundations + +**Palette.** A near-monochromatic slate neutral scale (Tailwind `slate-50…900`) carrying the entire surface, with **#2563eb (blue-600)** as the only brand color for primary actions, active states, focus rings, and the logo. Semantic status pills add pastel bg / deep fg pairs: green (running/success), amber (waiting/retry), indigo (subtasks), red (failed), blue (succeeded/queued edge cases), slate (queued/cancelled). All defined verbatim in `statusTone()` in `ui/src/lib/utils.ts`. + +**Type.** `IBM Plex Sans JP` for everything, `IBM Plex Mono` for identifiers, log output, version tags, wordmark, cron expressions, and the micro-label uppercase treatment. Body is **13px** — small and dense. Titles jump to 18px (detail) or 20px (dialog); chat bubbles are 14px leading-relaxed. Mobile forces input `font-size: 16px !important` to prevent iOS auto-zoom. + - *Font substitution note:* IBM Plex is already loaded from Google Fonts in the codebase; no substitution required. + +**Weight.** Heavy. 700 ("bold") is the workhorse — buttons, pill labels, status chips, even 10px/11px micro labels. 800 ("extra-bold") is reserved for titles and primary CTAs. 400/500 appear in body copy only. + +**Spacing.** Tailwind 4px scale. Gutters between panels are `8px` (`p-2 gap-2`). Cards pad `16px` (`p-4`). Buttons pad `6px 10px` (small chips), `8px 16px` (primary). The desktop layout is a 3-column grid: `clamp(240px, 22vw, 280px)` list / flexible chat / `clamp(280px, 26vw, 440px)` detail (or `clamp(360px, 30vw, 560px)` wide). + +**Backgrounds.** Flat solid colors — **never gradients**. App root is `#f3f6fb` (between slate-50 and slate-100); content cards are white. Activity log switches to an inverted surface: `bg-slate-900 text-slate-100` as a "terminal" zone. No illustrations, no patterns, no photos, no blur/glassmorphism. + +**Animation.** Minimal and purposeful. Only three motion idioms: +1. `transition-colors` on hover/active states (Tailwind default ~150ms). +2. `animate-pulse` on a 2px blue dot while a job is running. +3. `animate-spin` on the loading spinner (2px slate-200 border, blue-600 top-border). +No fades, no slides, no springs, no bounces. Expand/collapse caret rotates 90° (`rotate-90`) on click. + +**Hover states.** Buttons and list items darken one step: transparent → `bg-slate-100`, `bg-blue-600` → `bg-blue-700`, border-slate-200 → border-slate-300. Text links: `hover:underline` only on small text actions. No scale, no shadow-lift. + +**Press / active states.** Active navigation uses **filled accent**: `bg-blue-600 text-white`. Active filter pills use **tinted** style: `border-blue-600 bg-blue-50 text-blue-700`. Active list item: `border-blue-500 bg-blue-50`. No shrink, no darken-further. + +**Focus.** `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500`. Inputs on focus: `focus:border-blue-400 focus:ring-2 focus:ring-blue-100`. + +**Borders.** 1px, `#e2e8f0` (slate-200) by default. Active states promote to `slate-300` (hover) or `blue-500/600` (selected). Chat bubbles have no border on the user (blue-filled) bubble but do on ask/result bubbles to soften the pastel. + +**Shadows.** Two tiers only: +- `shadow-sm` — resting card (every panel, chip, list item). Nearly invisible, but cards in Vite have it. +- `shadow-2xl` — Dialog overlay only. +No colored shadows, no inner shadows, no glows. + +**Radii (heavy rounding).** The product reads "rounded" first, flat second. +- `rounded-lg` (8px): small selects, small buttons, rotating caret container, log surface. +- `rounded-xl` (12px): **default** — cards, buttons, inputs, list items, panels. +- `rounded-2xl` (16px): dialogs, chat bubbles (tail reduced to `rounded-br-md` / `rounded-bl-md`). +- `rounded-full` (9999px): status pills, filter tabs, avatar, pulse dot. + +**Transparency & blur.** Modal overlay uses `bg-slate-900/50` or `bg-black/40` — straight alpha, no backdrop-blur. No frosted chrome anywhere. + +**Cards.** White fill, 1px slate-200 border, `rounded-xl`, `shadow-sm`. Padded `p-3` or `p-4`. The admin list panel is itself a card; list items inside are nested mini-cards with the same recipe. + +**Chat bubbles.** +- User: `rounded-2xl rounded-br-md`, `bg-blue-600 text-white`, `shadow`. +- Agent ASK: `rounded-2xl rounded-bl-md`, `bg-amber-50 border border-amber-200`, `shadow-sm`. +- Agent RESULT: `rounded-2xl rounded-bl-md`, `bg-green-50 border border-green-200`, `shadow-sm`, renders Markdown. +- Progress card: centered, `bg-slate-50 border border-slate-200 rounded-xl`, 12px slate-500 text, click-to-expand. + +**Protection gradients vs capsules.** Never gradients. Always capsules/pills for chips and status, always rectangular cards for containers. + +**Imagery vibe.** The brand has no photography. The single branded image is the `favicon.svg`: a rounded blue square with a white central "hub" and three satellite nodes connected by thin 55%-opacity white lines — a literal orchestrator-connecting-workers glyph. Keep this as the only decorative asset unless explicitly asked. + +**Layout rules.** Fixed TopBar at top (white, slate-200 bottom border). Main content fills remaining `h-dvh` and is `overflow-hidden` at the root — panels handle their own scroll. Toasts slide in at the top-center (`mx-4 mt-2`). Dialogs center-screen, `max-width: min(860px, 92vw)`, `max-height: 88dvh`, scroll internally. + +--- + +## Iconography + +**No icon font, no icon library dependency.** Icons are inline SVG written directly in components, drawn at `w-3.5 h-3.5`, `w-4 h-4`, or `w-5 h-5`, stroke-based, `stroke-width="2"`, `strokeLinecap="round"`, `strokeLinejoin="round"`, `fill="none"`. Style is close to **Lucide / Feather** — 2px stroke, round caps, 24×24 viewBox, minimal. Example glyphs in source: magnifying-glass (search), paperclip (attach), cross (close), VNC monitor square, chevron-right caret. + +**Substitution policy.** When extending the system, prefer **Lucide** (`https://unpkg.com/lucide-static`) or hand-write a 2px-stroke, round-cap, 24×24 SVG inline. Do **not** introduce Heroicons solid, Material, or Phosphor — those break the line-weight consistency. + +**Unicode glyphs** are used functionally inside agent-generated content: +- `☑ ✗ ⊘ ☐` — checklist item states (done / failed / skipped / pending) +- `▶` — expand caret (rotates to down) +- `×` — close buttons within attachment chips +- `✕` — mobile dialog close +- `+` / `+` — add/create indicators +- `·` — meta separator (`worker: … · mode: …`) + +**Emoji.** Deliberately limited: +- 👍 / 👎 — feedback rating only +- 📋 — checklist progress card header only + +Do not introduce new emoji. When agent markdown renders emoji, the `prose` plugin styles them at 14px inline — do not restyle. + +**Logo usage.** The 32×32 favicon is the only mark. Minimum size 16×16. Clear space: `x/4` on all sides where `x` is the square's edge. Do not recolor the blue fill; if placing on blue, invert to a white square with blue contents. + +--- + +## Component notes (see `ui_kits/admin/`) + +The admin UI kit recreates: TopBar, FilterBar, TaskListItem, TaskListPanel, ChatPane, ChatMessage (user/ask/result/progress variants), DetailPanel with tab pills, StatusBadge, StatChip, EmptyState, LoadingSpinner, CreateTaskDialog, and the composite desktop 3-column layout. + +## Caveats + +- No external brand guide, marketing site, or Figma was provided — this system is derived strictly from the live admin UI source. +- No printed/decorative imagery exists in the codebase; the only "brand asset" is the favicon logo. +- Fonts load from Google Fonts CDN (same as production); no local TTFs needed. diff --git a/docs/design/colors_and_type.css b/docs/design/colors_and_type.css new file mode 100644 index 0000000..877d070 --- /dev/null +++ b/docs/design/colors_and_type.css @@ -0,0 +1,128 @@ +/* Agent Orchestrator — Colors & Type + Extracted from gitea-agent-orchestrator/ui (Tailwind config + index.css + usage). + Palette: slate neutrals + #2563eb blue accent, with pastel semantic chips. +*/ + +@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&family=IBM+Plex+Sans+JP:wght@400;500;600;700;800&display=swap'); + +:root { + /* —— Brand / Accent ———————————————————————— */ + --accent: #2563eb; /* blue-600 — primary action, active tab, focus ring */ + --accent-deep: #1d4ed8; /* blue-700 — hover on primary button */ + --accent-50: #eff6ff; + --accent-100: #dbeafe; + --accent-200: #bfdbfe; + --accent-500: #3b82f6; + --accent-700: #1d4ed8; + + /* —— Ink / Neutrals (slate scale) ————————————— */ + --ink: #0f172a; /* slate-900 — primary text */ + --muted: #64748b; /* slate-500 — secondary text */ + --slate-50: #f8fafc; /* body surface */ + --slate-100: #f1f5f9; /* scrollbar track, chip bg */ + --slate-200: #e2e8f0; /* default border */ + --slate-300: #cbd5e1; + --slate-400: #94a3b8; + --slate-500: #64748b; + --slate-600: #475569; + --slate-700: #334155; + --slate-800: #1e293b; + --slate-900: #0f172a; + --app-bg: #f3f6fb; /* root bg (index.html) */ + + /* —— Semantic status tones (from statusTone()) ———— + bg / fg pairs used in status pills and cards. */ + --status-running-bg: #dcfce7; --status-running-fg: #166534; /* green */ + --status-waiting-bg: #fef9c3; --status-waiting-fg: #854d0e; /* amber */ + --status-subtasks-bg: #e0e7ff; --status-subtasks-fg: #3730a3; /* indigo */ + --status-failed-bg: #fee2e2; --status-failed-fg: #b91c1c; /* red */ + --status-succeeded-bg: #dbeafe; --status-succeeded-fg: #1e40af; /* blue */ + --status-retry-bg: #fef3c7; --status-retry-fg: #92400e; /* amber-deep */ + --status-queued-bg: #e2e8f0; --status-queued-fg: #475569; /* slate */ + + /* Message bubbles (ChatMessage.tsx) */ + --bubble-user-bg: #2563eb; --bubble-user-fg: #ffffff; + --bubble-ask-bg: #fffbeb; --bubble-ask-border: #fde68a; /* amber-50/200 */ + --bubble-result-bg: #f0fdf4; --bubble-result-border: #bbf7d0; /* green-50/200 */ + + /* Log / terminal surface (ProgressTab) */ + --log-bg: #0f172a; + --log-fg: #f1f5f9; + + /* —— Typography ——————————————————————————— */ + --font-sans: 'IBM Plex Sans JP', 'Hiragino Sans', -apple-system, BlinkMacSystemFont, sans-serif; + --font-mono: 'IBM Plex Mono', ui-monospace, Menlo, monospace; + + /* UI base is small & dense — 13px body, mobile auto-zooms to 16. */ + --fs-10: 10px; /* micro labels, version tag */ + --fs-11: 11px; /* meta, pill labels, timestamps */ + --fs-12: 12px; /* secondary body, nav labels */ + --fs-13: 13px; /* DEFAULT body */ + --fs-14: 14px; /* chat bubble body */ + --fs-15: 15px; /* chat header */ + --fs-18: 18px; /* detail title */ + --fs-20: 20px; /* dialog title (xl) */ + + --fw-regular: 400; + --fw-medium: 500; + --fw-bold: 700; /* used aggressively — even small chips are bold */ + --fw-extra: 800; /* titles and headers */ + + /* —— Radii (very rounded) ————————————————————— */ + --radius-sm: 8px; /* rounded-lg — small buttons, selects, log surface */ + --radius-md: 12px; /* rounded-xl — DEFAULT card/button/input — everywhere */ + --radius-lg: 16px; /* rounded-2xl — dialogs, chat bubbles */ + --radius-pill: 9999px; /* status pills, filter tabs, avatar */ + + /* —— Shadow system ————————————————————————— */ + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); /* card resting */ + --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); /* modal */ + + /* —— Spacing scale (Tailwind units × 4) ————————— */ + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 20px; + --space-6: 24px; + --space-8: 32px; + + /* —— Borders ——————————————————————————————— */ + --border-default: 1px solid var(--slate-200); + --border-active: 1px solid var(--accent); + + /* —— Semantic aliases ———————————————————————— */ + --fg-1: var(--slate-900); + --fg-2: var(--slate-600); + --fg-3: var(--slate-500); + --fg-muted: var(--slate-400); + --bg-1: #ffffff; + --bg-2: var(--slate-50); + --bg-app: var(--app-bg); + --border: var(--slate-200); +} + +/* —— Base type roles ————————————————————————— */ +html, body { font-family: var(--font-sans); color: var(--fg-1); background: var(--bg-app); } +body { font-size: var(--fs-13); } + +.h1 { font-size: var(--fs-20); font-weight: var(--fw-extra); color: var(--fg-1); letter-spacing: -0.01em; } +.h2 { font-size: var(--fs-18); font-weight: var(--fw-extra); color: var(--fg-1); } +.h3 { font-size: var(--fs-15); font-weight: var(--fw-bold); color: var(--fg-1); } +.h4 { font-size: var(--fs-13); font-weight: var(--fw-bold); color: var(--fg-1); } +.p { font-size: var(--fs-13); color: var(--fg-2); line-height: 1.55; } +.meta { font-size: var(--fs-11); color: var(--fg-3); } +.micro { font-size: var(--fs-10); color: var(--fg-muted); text-transform: uppercase; letter-spacing: 0.08em; font-weight: var(--fw-bold); } +.mono { font-family: var(--font-mono); } +.code { font-family: var(--font-mono); font-size: var(--fs-12); background: var(--slate-100); padding: 2px 6px; border-radius: 6px; } + +/* The signature "Agent Orchestrator" wordmark style used in TopBar */ +.wordmark { + font-family: var(--font-mono); + font-size: var(--fs-11); + font-weight: var(--fw-bold); + color: var(--accent); + text-transform: uppercase; + letter-spacing: 0.16em; +} diff --git a/docs/design/ui_kits_reference/admin-legacy/ChatPane.jsx b/docs/design/ui_kits_reference/admin-legacy/ChatPane.jsx new file mode 100644 index 0000000..fd8cd80 --- /dev/null +++ b/docs/design/ui_kits_reference/admin-legacy/ChatPane.jsx @@ -0,0 +1,128 @@ +// ChatPane — mirrors ui/src/components/chat/* with user/ask/result/progress bubbles +function Bubble({ role, children, footer }) { + const isUser = role === 'user'; + const style = { + maxWidth: '85%', + padding: '10px 14px', + borderRadius: 16, + fontSize: 13, + lineHeight: 1.55, + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + }; + if (isUser) { + Object.assign(style, { background: '#2563eb', color: '#fff', borderBottomRightRadius: 4, alignSelf: 'flex-end' }); + } else if (role === 'ask') { + Object.assign(style, { background: '#fef9c3', color: '#854d0e', border: '1px solid #fde68a', borderBottomLeftRadius: 4 }); + } else if (role === 'result') { + Object.assign(style, { background: '#ecfdf5', color: '#065f46', border: '1px solid #a7f3d0', borderBottomLeftRadius: 4 }); + } else { + Object.assign(style, { background: '#fff', color: '#0f172a', border: '1px solid #e2e8f0', borderBottomLeftRadius: 4 }); + } + return ( +
+
{children}
+ {footer &&
{footer}
} +
+ ); +} + +function ProgressBubble({ text }) { + return ( +
+ + {text} +
+ ); +} + +function ChatHeader({ task, onOpenDetail, detailOpen }) { + return ( +
+
+
+ TASK #{task.id} +
+
+ {task.title} +
+
+
+ + +
+
+ ); +} + +function Composer({ onSend }) { + const [text, setText] = React.useState(''); + const send = () => { if (!text.trim()) return; onSend(text.trim()); setText(''); }; + return ( +
+
+ +