@geekbeer/minion 3.52.0 → 3.55.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +7 -0
- package/core/db/migrations/20260508000000_page_recipes.js +33 -0
- package/core/lib/dag-step-poller.js +245 -1
- package/core/lib/web-extract/extractor.js +142 -0
- package/core/lib/web-extract/fingerprint.js +63 -0
- package/core/lib/web-extract/html-cleaner.js +72 -0
- package/core/lib/web-extract/index.js +21 -0
- package/core/lib/web-extract/playwright-runner.js +129 -0
- package/core/lib/web-extract/recipe-generator.js +247 -0
- package/core/lib/web-extract/url-normalize.js +90 -0
- package/core/routes/web.js +94 -0
- package/core/stores/page-recipe-store.js +143 -0
- package/docs/api-reference.md +106 -297
- package/docs/task-guides.md +134 -75
- package/linux/routes/chat.js +37 -20
- package/linux/server.js +2 -0
- package/mac/server.js +2 -0
- package/package.json +6 -2
- package/rules/core.md +26 -9
- package/win/routes/chat.js +38 -16
- package/win/server.js +2 -0
package/docs/task-guides.md
CHANGED
|
@@ -4,6 +4,64 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## Webページの読み取り・要約 🧪 (experimental, v3.53.0〜)
|
|
8
|
+
|
|
9
|
+
ユーザーから「このURLを要約して」「このページの情報を抽出して」「このページの一覧を取ってきて」と依頼された場合の手順。
|
|
10
|
+
|
|
11
|
+
### まずローカルAPIを試す
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
curl -X POST http://localhost:8080/api/web/extract \
|
|
15
|
+
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
16
|
+
-d '{"url": "対象URL", "hint": "何を抽出したいか短く (任意)"}' | jq
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
返ってきた JSON の `title` `content` `structured` を使って要約・回答を作成する。`structured` には初回アクセス時に LLM が選んだフィールドが入る。
|
|
20
|
+
|
|
21
|
+
このAPIは内部で Playwright + Readability を回して **メインセッションには結果 JSON だけ返す** ため、Playwright MCP を使うときに起きていたチャットコンテキストのトークン肥大化が回避できる。
|
|
22
|
+
|
|
23
|
+
### Playwright MCP を使うべき場面
|
|
24
|
+
|
|
25
|
+
`/api/web/extract` で対応できないのは以下のケース。このときだけ `mcp__playwright__*` を使う:
|
|
26
|
+
|
|
27
|
+
- ログイン必須ページ (Cookie/2FA 等の認証必要)
|
|
28
|
+
- フォーム入力・複数ページ遷移を伴う操作
|
|
29
|
+
- ボタンクリック→動的に追加されるコンテンツの取得
|
|
30
|
+
- Lancers コンペ応募など、明らかに対話的操作が必要なフロー
|
|
31
|
+
|
|
32
|
+
**単純な閲覧・抽出用途では MCP を使わない。**
|
|
33
|
+
|
|
34
|
+
### よくあるパターン
|
|
35
|
+
|
|
36
|
+
| ユーザー依頼 | 推奨手段 |
|
|
37
|
+
|--------------|----------|
|
|
38
|
+
| 「このQiita記事を要約」 | `/api/web/extract` |
|
|
39
|
+
| 「Lancersコンペの一覧を取得」 | `/api/web/extract` |
|
|
40
|
+
| 「このプロダクトページから価格を抽出」 | `/api/web/extract` |
|
|
41
|
+
| 「ログインしてダッシュボード操作」 | Playwright MCP |
|
|
42
|
+
| 「フォームを送信」 | Playwright MCP |
|
|
43
|
+
| 「複数ページ巡回して全件取得」 | `/api/web/extract` をループ呼び出し (各ページに対して) |
|
|
44
|
+
|
|
45
|
+
### キャッシュの確認・破棄 (debug)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# キャッシュ済みレシピ一覧
|
|
49
|
+
curl -H "Authorization: Bearer $API_TOKEN" http://localhost:8080/api/web/recipes | jq
|
|
50
|
+
|
|
51
|
+
# 特定のレシピを削除 (壊れたセレクタを強制再生成させたい場合)
|
|
52
|
+
curl -X DELETE -H "Authorization: Bearer $API_TOKEN" \
|
|
53
|
+
"http://localhost:8080/api/web/recipes?template=lancers.jp/work/proposal/:id&fingerprint=abc123def456"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 失敗時の対処
|
|
57
|
+
|
|
58
|
+
- `503 PLAYWRIGHT_UNAVAILABLE` → ホスト側で `npx playwright install chromium` を実行 (sudo 不要)
|
|
59
|
+
- `503 LLM_UNAVAILABLE` → primary LLM 未設定。`PUT /api/llm/config -d '{"primary":"claude"}'` で primary を指定するか、fallback として `PUT /api/secrets/ANTHROPIC_API_KEY` で API キーを投入
|
|
60
|
+
- `502 PRIMARY_LLM_BAD_JSON` → primary LLM が JSON 形式で返さなかった。プラグインの認証状態を `GET /api/llm/plugins` で確認し、必要なら別 plugin を primary にする
|
|
61
|
+
- `500 extract timeout` → ページが重すぎる/JSレンダリング待ちが長すぎる。Playwright MCP に切り替えて手動操作
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
7
65
|
## スキルの修正
|
|
8
66
|
|
|
9
67
|
### 1. ローカルのスキルを編集する
|
|
@@ -30,7 +88,7 @@ requires:
|
|
|
30
88
|
---
|
|
31
89
|
|
|
32
90
|
Skill instructions here...
|
|
33
|
-
Use {{PROJECT_VAR}} to reference project
|
|
91
|
+
Use {{PROJECT_VAR}} to reference project variables.
|
|
34
92
|
```
|
|
35
93
|
|
|
36
94
|
フロントマターのフィールド:
|
|
@@ -62,7 +120,7 @@ description: サイトをデプロイする
|
|
|
62
120
|
|
|
63
121
|
- 変数名は英数字とアンダースコアのみ(`\w+`)
|
|
64
122
|
- 未定義の変数は `{{VAR_NAME}}` のまま残る(エラーにはならない)
|
|
65
|
-
- 展開優先順位: ミニオン変数 <
|
|
123
|
+
- 展開優先順位: ミニオン変数 < プロジェクト変数(後者が上書き)
|
|
66
124
|
- ルーティン実行時もミニオン変数による `{{VAR}}` 展開が行われる
|
|
67
125
|
|
|
68
126
|
### 変数とシークレットの使い分け
|
|
@@ -72,7 +130,7 @@ description: サイトをデプロイする
|
|
|
72
130
|
| 変数 | `{{VAR_NAME}}` | 設定・パラメータ(非機密) | デプロイ先、サイトURL、プロジェクト名 |
|
|
73
131
|
| シークレット | `$SECRET_NAME`(環境変数) | 機密情報 | APIキー、パスワード、トークン |
|
|
74
132
|
|
|
75
|
-
-
|
|
133
|
+
- **変数**はスキル本文のテンプレートとして展開される。全スコープ(ミニオン・プロジェクト)で同じ `{{VAR}}` 構文を使用する
|
|
76
134
|
- **シークレット**は環境変数としてプロセスに注入される。テンプレート展開は行われない
|
|
77
135
|
- デイリーログやメモリーから変数・シークレットの値を推測して使用しないこと
|
|
78
136
|
|
|
@@ -96,63 +154,11 @@ minion-cli skill fetch <name>
|
|
|
96
154
|
|
|
97
155
|
---
|
|
98
156
|
|
|
99
|
-
## ワークフローの修正 (PM のみ)
|
|
100
|
-
|
|
101
|
-
### パイプライン構成の変更
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
# API経由で push (新バージョン自動作成)
|
|
105
|
-
curl -s -X POST "$HQ_URL/api/minion/workflows" \
|
|
106
|
-
-H "Authorization: Bearer $API_TOKEN" \
|
|
107
|
-
-H "Content-Type: application/json" \
|
|
108
|
-
-d '{
|
|
109
|
-
"name": "my-workflow",
|
|
110
|
-
"pipeline_skill_names": ["skill-1", "skill-2"],
|
|
111
|
-
"content": "Workflow description",
|
|
112
|
-
"project_id": "<project-uuid>",
|
|
113
|
-
"change_summary": "Updated pipeline"
|
|
114
|
-
}'
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### ステップごとのロール・レビュー設定
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
curl -s -X POST "$HQ_URL/api/minion/workflows" \
|
|
121
|
-
-H "Authorization: Bearer $API_TOKEN" \
|
|
122
|
-
-H "Content-Type: application/json" \
|
|
123
|
-
-d '{
|
|
124
|
-
"name": "my-workflow",
|
|
125
|
-
"pipeline_skill_names": ["skill-1", "skill-2"],
|
|
126
|
-
"pipeline_steps": [
|
|
127
|
-
{ "assigned_role": "engineer", "requires_review": false },
|
|
128
|
-
{ "assigned_role": "pm", "requires_review": true }
|
|
129
|
-
],
|
|
130
|
-
"content": "Workflow description",
|
|
131
|
-
"project_id": "<project-uuid>"
|
|
132
|
-
}'
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### ローカルワークフローの同期
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
# HQからワークフローを取得 (不足スキルも自動fetch)
|
|
139
|
-
curl -s -X POST "http://localhost:8080/api/workflows/fetch/<name>" \
|
|
140
|
-
-H "Authorization: Bearer $API_TOKEN"
|
|
141
|
-
|
|
142
|
-
# ローカルワークフローをHQにpush (スキルも自動push)
|
|
143
|
-
curl -s -X POST "http://localhost:8080/api/workflows/push/<name>" \
|
|
144
|
-
-H "Authorization: Bearer $API_TOKEN"
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
> **Note**: `/api/workflows/push|fetch` は線形パイプライン形式(`pipeline_skill_names`)の旧式ワークフロー専用です。DAGワークフロー(ノード/エッジ形式)はHQダッシュボードの DAG エディタで作成・編集します。次節を参照。
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
157
|
## DAG ワークフロー (ノード/エッジ形式)
|
|
152
158
|
|
|
153
|
-
DAG
|
|
159
|
+
DAG ワークフローは有向非巡回グラフでスキル間の依存関係を表現するワークフローです。fan-out による並列展開、join による集約、conditional による分岐、transform による LLM データ変換、script による決定的スクリプト実行 (Python/Node)、review によるゲーティングをサポートします。
|
|
154
160
|
|
|
155
|
-
**HQダッシュボードの DAG
|
|
161
|
+
**HQダッシュボードの DAG エディタ(プロジェクト画面の「DAG」タブ)で GUI 編集**できるほか、**ミニオンからは JSON ベースで編集可能**(PMロールのみ)。ミニオンは実行ランタイムも担当し、`dag-step-poller` デーモンが pending ノードを自動で処理します。
|
|
156
162
|
|
|
157
163
|
**cronスケジュール (v3.51.0〜):** DAGワークフローは cron 式による定期実行をサポート。設定はHQダッシュボードのDAGビューにある SchedulePanel から行う(最小実行間隔 5分)。発火主体はそのプロジェクトのPMロールのミニオンで、`dag-cron-poller` デーモンが60秒間隔で `/api/dag/minion/dag-cron-tick` を叩いて発火させる。PM不在のプロジェクトでは cron は発火しない。
|
|
158
164
|
|
|
@@ -347,7 +353,7 @@ hq dag remove-edge <wf-id> edge_3
|
|
|
347
353
|
|
|
348
354
|
**fan_out テンプレート編集:**
|
|
349
355
|
|
|
350
|
-
> **⚠️ テンプレート内に `start` / `end` ノードを入れないこと。** テンプレートのエントリ/エグジットはエッジ構造から自動検出される。`start` / `end` はトップレベル DAG 専用であり、テンプレート内に含めるとバリデーションエラーになる。`skill`, `conditional`, `transform`, `review` 等の実行ノードのみ使用すること。
|
|
356
|
+
> **⚠️ テンプレート内に `start` / `end` ノードを入れないこと。** テンプレートのエントリ/エグジットはエッジ構造から自動検出される。`start` / `end` はトップレベル DAG 専用であり、テンプレート内に含めるとバリデーションエラーになる。`skill`, `conditional`, `transform`, `script`, `review` 等の実行ノードのみ使用すること。
|
|
351
357
|
|
|
352
358
|
```bash
|
|
353
359
|
# fan_out ノードの template を PATCH で上書き
|
|
@@ -376,7 +382,7 @@ hq dag update-node <wf-id> fan_out_1 /tmp/t.json
|
|
|
376
382
|
- 書き込み(create / put / publish / dag 操作)は **PMロールのみ**。Engineer / accountant では 403 が返る。読み取り(fetch)は全メンバー可。
|
|
377
383
|
- ノード/エッジ操作は各ステップでドラフトに自動保存され、バリデーション結果がレスポンスに含まれる。
|
|
378
384
|
- `hq dag validate` で公開前にフル検証できる。publish 時の想定外エラーを防止できる。
|
|
379
|
-
- 全文 JSON 操作(`hq put dag-workflow`)も引き続き利用可能だが、**ノード/エッジ操作API
|
|
385
|
+
- 全文 JSON 操作(`hq put dag-workflow`)も引き続き利用可能だが、**ノード/エッジ操作APIの方が推奨**(型の取り違えで 400 になりやすい)。
|
|
380
386
|
|
|
381
387
|
### 実行フロー(ランタイム側、参考)
|
|
382
388
|
|
|
@@ -490,6 +496,63 @@ input_data: { "items": [{ "title": "Item A" }, { "title": "Item B" }, { "title":
|
|
|
490
496
|
output_data: { "items": [{ "title": "Item A" }, { "title": "Item C" }] }
|
|
491
497
|
```
|
|
492
498
|
|
|
499
|
+
### Script ノード(決定的処理、LLM不要)v3.54.0〜
|
|
500
|
+
|
|
501
|
+
Script ノードは **LLM を使わない決定的なデータ処理** を DAG 内に挟み込むためのノード。トークンコストもかからず、出力形式のブレも発生しない。ミニオン標準搭載の `python3` または `node` でインラインスクリプトを `child_process` 実行する。
|
|
502
|
+
|
|
503
|
+
**静的バリデーション要件:**
|
|
504
|
+
|
|
505
|
+
- `assigned_role` 必須(pm / engineer / accountant のいずれか)
|
|
506
|
+
- `script_runtime`: `'python'` または `'node'`
|
|
507
|
+
- `script_source`: 非空のスクリプト本文
|
|
508
|
+
- `script_timeout_seconds`: 任意(デフォルト 60、範囲 1–600)
|
|
509
|
+
|
|
510
|
+
**I/O プロトコル(skill / transform と完全に揃う):**
|
|
511
|
+
|
|
512
|
+
- 入力: `input_data` (incoming edge から渡る JSON object) → スクリプトの **stdin に JSON 1 個** として書き込まれる
|
|
513
|
+
- 出力: スクリプトが **stdout に JSON object 1 個を書く** → そのままパースされて `output_data` として `node-complete` に送られる
|
|
514
|
+
- outgoing edge に contract が貼ってあれば HQ が `output_data` を validate(skill / transform と同じ runtime validation 経路)
|
|
515
|
+
|
|
516
|
+
**failure モード(いずれも `status: failed`、stderr が `output_summary` に格納される):**
|
|
517
|
+
|
|
518
|
+
- 非 0 終了
|
|
519
|
+
- stdout が JSON parse 不能 / object でない(配列・プリミティブは不可)
|
|
520
|
+
- `script_timeout_seconds` 超過(プロセスは SIGKILL)
|
|
521
|
+
- `script_runtime` が unsupported
|
|
522
|
+
|
|
523
|
+
**典型用途:**
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
# script_runtime: "python", input edge contract: { items: array<item> }
|
|
527
|
+
# output edge contract: { items: array<item>, total: number }
|
|
528
|
+
import sys, json
|
|
529
|
+
|
|
530
|
+
data = json.load(sys.stdin)
|
|
531
|
+
items = [it for it in data["items"] if it.get("score", 0) >= 0.8]
|
|
532
|
+
json.dump({"items": items, "total": len(items)}, sys.stdout)
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
```javascript
|
|
536
|
+
// script_runtime: "node", URL 一覧から domain ごとにグルーピング
|
|
537
|
+
let raw = ''
|
|
538
|
+
process.stdin.on('data', c => raw += c)
|
|
539
|
+
process.stdin.on('end', () => {
|
|
540
|
+
const data = JSON.parse(raw)
|
|
541
|
+
const groups = {}
|
|
542
|
+
for (const url of data.urls) {
|
|
543
|
+
const host = new URL(url).host
|
|
544
|
+
;(groups[host] ||= []).push(url)
|
|
545
|
+
}
|
|
546
|
+
process.stdout.write(JSON.stringify({ groups }))
|
|
547
|
+
})
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**制約:**
|
|
551
|
+
|
|
552
|
+
- 追加ライブラリのインストール(pip / npm install)は未対応。ミニオン標準搭載のもののみ使用可能
|
|
553
|
+
- tmux セッションは作らず、ミニオンのエージェントプロセス内で `child_process.spawn` する
|
|
554
|
+
- 並列実行上限は skill / transform と共通(`concurrency-manager` の `MAX_CONCURRENT=2`)
|
|
555
|
+
|
|
493
556
|
### DAG 構築時のノード選定フロー
|
|
494
557
|
|
|
495
558
|
スキルは汎用的で再利用可能な資産であり、ワークフロー固有の contract に合わせて SKILL.md を改修することは原則行わない。代わりに以下のフローで判断する:
|
|
@@ -521,7 +584,7 @@ Review ノードはレビューゲート。`review_status=review_pending` で下
|
|
|
521
584
|
- `approved` にすると `approved` 種別のエッジで下流に進む
|
|
522
585
|
- `revision_requested` にすると `revision` 種別のエッジで差し戻し先に戻る
|
|
523
586
|
|
|
524
|
-
|
|
587
|
+
差し戻しはサーバ側のカスケードで自動処理される(`revision` 種別のエッジが指す上流ノード以降が `pending` に戻り再実行される)。
|
|
525
588
|
|
|
526
589
|
### デバッグ
|
|
527
590
|
|
|
@@ -656,17 +719,14 @@ curl -X PATCH "$HQ_URL/api/minion/projects/<project-id>/tasks/<task-id>" \
|
|
|
656
719
|
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
657
720
|
-d '{"status":"doing"}'
|
|
658
721
|
|
|
659
|
-
#
|
|
722
|
+
# レビュー依頼(acceptance_criteria を全て満たした後)
|
|
660
723
|
curl -X PATCH "$HQ_URL/api/minion/projects/<project-id>/tasks/<task-id>" \
|
|
661
724
|
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
662
725
|
-d '{"status":"review"}'
|
|
663
|
-
|
|
664
|
-
# 完了
|
|
665
|
-
curl -X PATCH "$HQ_URL/api/minion/projects/<project-id>/tasks/<task-id>" \
|
|
666
|
-
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
667
|
-
-d '{"status":"done"}'
|
|
668
726
|
```
|
|
669
727
|
|
|
728
|
+
> **重要**: ミニオン自身は `done` へ直接遷移してはいけない。`done` 遷移はレビュアー(人間または別のミニオン)の責務。受け入れ要件達成後は `review` で停止し、承認を待つこと。
|
|
729
|
+
|
|
670
730
|
### 共通の注意
|
|
671
731
|
|
|
672
732
|
- **`status_changed_at` を手動で渡してはならない**。サーバ側で自動更新される(stalled 検出に使われる)。
|
|
@@ -763,20 +823,19 @@ minion-cli skill list --local
|
|
|
763
823
|
minion-cli skill fetch <name>
|
|
764
824
|
```
|
|
765
825
|
|
|
766
|
-
### ワークフローが実行されない
|
|
826
|
+
### DAG ワークフローが実行されない
|
|
767
827
|
|
|
768
828
|
```bash
|
|
769
|
-
#
|
|
770
|
-
curl -s "
|
|
771
|
-
-H "Authorization: Bearer $API_TOKEN"
|
|
829
|
+
# pending ノードが自分に割り当てられているか確認
|
|
830
|
+
curl -s "$HQ_URL/api/dag/minion/pending-nodes" \
|
|
831
|
+
-H "Authorization: Bearer $API_TOKEN" | jq
|
|
772
832
|
|
|
773
|
-
#
|
|
774
|
-
|
|
775
|
-
-H "Authorization: Bearer $API_TOKEN" \
|
|
776
|
-
-H "Content-Type: application/json" \
|
|
777
|
-
-d '{ "workflow_id": "<workflow-uuid>" }'
|
|
833
|
+
# dag-step-poller のログを確認
|
|
834
|
+
tail -f ~/.minion/logs/agent.log | grep '\[DAG'
|
|
778
835
|
```
|
|
779
836
|
|
|
837
|
+
PMロールが不在のプロジェクトでは cron 発火が行われない。`hq list dag-workflows <project-id>` の `my_role` を確認すること。
|
|
838
|
+
|
|
780
839
|
### エージェントの状態確認
|
|
781
840
|
|
|
782
841
|
```bash
|
package/linux/routes/chat.js
CHANGED
|
@@ -362,6 +362,29 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
362
362
|
)
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
+
// Web page extraction guidance (experimental, v3.53.0)
|
|
366
|
+
if (!sessionId) {
|
|
367
|
+
const port = require('../../core/config').config.AGENT_PORT
|
|
368
|
+
parts.push(
|
|
369
|
+
'[Webページ読み取りについて 🧪 experimental]',
|
|
370
|
+
'Webページの読み取り・要約・情報抽出が必要なときは、まず以下のローカルAPIを試すこと:',
|
|
371
|
+
'',
|
|
372
|
+
'```bash',
|
|
373
|
+
`curl -X POST http://localhost:${port}/api/web/extract \\`,
|
|
374
|
+
' -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \\',
|
|
375
|
+
' -d \'{"url": "対象URL", "hint": "抽出したい内容を短く"}\'',
|
|
376
|
+
'```',
|
|
377
|
+
'',
|
|
378
|
+
'このAPIは内部で Playwright + Readability を回し、抽出済みJSONだけを返すため、',
|
|
379
|
+
'DOM全体がチャットに流れ込んでトークン肥大化することを防げる。',
|
|
380
|
+
'初回アクセスで学習したセレクタはSQLiteにキャッシュされ、2回目以降はLLM呼び出しなしで抽出される。',
|
|
381
|
+
'',
|
|
382
|
+
'Playwright MCP (`mcp__playwright__*`) は **ログイン・フォーム入力・複数画面の対話操作**が必要な場合のみ使用する。',
|
|
383
|
+
'単純な閲覧・要約・一覧取得用途ではMCPを使わない。',
|
|
384
|
+
''
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
|
|
365
388
|
// File output guidance — always inject on new sessions
|
|
366
389
|
if (!sessionId) {
|
|
367
390
|
parts.push(
|
|
@@ -441,26 +464,11 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
441
464
|
)
|
|
442
465
|
}
|
|
443
466
|
break
|
|
444
|
-
case 'workflow':
|
|
445
|
-
if (context.projectId) {
|
|
446
|
-
const label = context.workflowName
|
|
447
|
-
? `ワークフロー「${context.workflowName}」`
|
|
448
|
-
: 'ワークフロー'
|
|
449
|
-
parts.push(
|
|
450
|
-
`ユーザーはHQダッシュボードで${label}を閲覧しています。`,
|
|
451
|
-
`ワークフロー情報を取得するには以下を実行してください:`,
|
|
452
|
-
` hq fetch workflow ${context.workflowName || context.workflowId}`,
|
|
453
|
-
`プロジェクトコンテキスト:`,
|
|
454
|
-
` hq fetch project-context ${context.projectId}`,
|
|
455
|
-
`取得した内容をもとに回答してください。`
|
|
456
|
-
)
|
|
457
|
-
}
|
|
458
|
-
break
|
|
459
467
|
case 'dag-workflow':
|
|
460
468
|
if (context.projectId && context.dagWorkflowId) {
|
|
461
469
|
parts.push(
|
|
462
470
|
`ユーザーはHQダッシュボードで DAG ワークフロー (ID: ${context.dagWorkflowId}) のエディタ/詳細を閲覧しています。`,
|
|
463
|
-
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / review をサポートします。`,
|
|
471
|
+
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / script / review をサポートします。`,
|
|
464
472
|
`DAG ワークフロー情報を取得するには以下を実行してください:`,
|
|
465
473
|
` hq fetch dag-workflow ${context.dagWorkflowId}`,
|
|
466
474
|
`プロジェクトコンテキスト:`,
|
|
@@ -770,6 +778,11 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
|
|
|
770
778
|
child.on('close', async (code) => {
|
|
771
779
|
activeChatChild = null
|
|
772
780
|
|
|
781
|
+
console.log(`[Chat] child closed: code=${code}, response=${fullResponse.length}chars, turns=${turnCount}, stderr=${stderrBuffer.length}bytes, session=${resolvedSessionId}`)
|
|
782
|
+
if (stderrBuffer.trim()) {
|
|
783
|
+
console.log(`[Chat] final stderr (tail 500): ${stderrBuffer.slice(-500)}`)
|
|
784
|
+
}
|
|
785
|
+
|
|
773
786
|
// Store messages in chat-store
|
|
774
787
|
if (resolvedSessionId) {
|
|
775
788
|
// If this was a new session, also store the user message now
|
|
@@ -782,11 +795,15 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
|
|
|
782
795
|
}
|
|
783
796
|
}
|
|
784
797
|
|
|
785
|
-
|
|
786
|
-
if (code !== 0 && !fullResponse) {
|
|
798
|
+
if (code !== 0) {
|
|
787
799
|
const errorMsg = stderrBuffer.trim() || `Claude CLI exited with code ${code}`
|
|
788
|
-
console.error(`[Chat] CLI failed (exit ${code}): ${errorMsg}`)
|
|
789
|
-
const errorEvent = JSON.stringify({
|
|
800
|
+
console.error(`[Chat] CLI failed (exit ${code}, partial=${!!fullResponse}): ${errorMsg}`)
|
|
801
|
+
const errorEvent = JSON.stringify({
|
|
802
|
+
type: 'error',
|
|
803
|
+
error: errorMsg,
|
|
804
|
+
partial: !!fullResponse,
|
|
805
|
+
exit_code: code,
|
|
806
|
+
})
|
|
790
807
|
res.write(`data: ${errorEvent}\n\n`)
|
|
791
808
|
}
|
|
792
809
|
|
package/linux/server.js
CHANGED
|
@@ -85,6 +85,7 @@ const { todoRoutes } = require('../core/routes/todos')
|
|
|
85
85
|
const { emailRoutes } = require('../core/routes/emails')
|
|
86
86
|
const { daemonRoutes } = require('../core/routes/daemons')
|
|
87
87
|
const { llmRoutes } = require('../core/routes/llm')
|
|
88
|
+
const { webRoutes } = require('../core/routes/web')
|
|
88
89
|
|
|
89
90
|
// Linux-specific routes
|
|
90
91
|
const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
|
|
@@ -298,6 +299,7 @@ async function registerAllRoutes(app) {
|
|
|
298
299
|
await app.register(emailRoutes)
|
|
299
300
|
await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
|
|
300
301
|
await app.register(llmRoutes)
|
|
302
|
+
await app.register(webRoutes)
|
|
301
303
|
|
|
302
304
|
// Linux-specific routes
|
|
303
305
|
await app.register(commandRoutes)
|
package/mac/server.js
CHANGED
|
@@ -72,6 +72,7 @@ const { todoRoutes } = require('../core/routes/todos')
|
|
|
72
72
|
const { emailRoutes } = require('../core/routes/emails')
|
|
73
73
|
const { daemonRoutes } = require('../core/routes/daemons')
|
|
74
74
|
const { llmRoutes } = require('../core/routes/llm')
|
|
75
|
+
const { webRoutes } = require('../core/routes/web')
|
|
75
76
|
|
|
76
77
|
// macOS-specific routes
|
|
77
78
|
const { commandRoutes, getProcessManager, getAllowedCommands } = require('./routes/commands')
|
|
@@ -284,6 +285,7 @@ async function registerAllRoutes(app) {
|
|
|
284
285
|
await app.register(emailRoutes)
|
|
285
286
|
await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
|
|
286
287
|
await app.register(llmRoutes)
|
|
288
|
+
await app.register(webRoutes)
|
|
287
289
|
|
|
288
290
|
// macOS-specific routes
|
|
289
291
|
await app.register(commandRoutes)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekbeer/minion",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.55.1",
|
|
4
4
|
"description": "AI Agent runtime for Minion - manages status and skill deployment on VPS",
|
|
5
5
|
"main": "linux/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -33,13 +33,17 @@
|
|
|
33
33
|
"db:migration:new": "node scripts/new-migration.js"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
+
"@mozilla/readability": "^0.5.0",
|
|
36
37
|
"croner": "^9.0.0",
|
|
37
38
|
"fastify": "^5.2.2",
|
|
39
|
+
"linkedom": "^0.18.0",
|
|
40
|
+
"turndown": "^7.2.0",
|
|
38
41
|
"ws": "^8.0.0"
|
|
39
42
|
},
|
|
40
43
|
"optionalDependencies": {
|
|
41
44
|
"better-sqlite3": "^11.0.0",
|
|
42
|
-
"node-pty": "^1.0.0"
|
|
45
|
+
"node-pty": "^1.0.0",
|
|
46
|
+
"playwright": "^1.48.0"
|
|
43
47
|
},
|
|
44
48
|
"engines": {
|
|
45
49
|
"node": ">=22.0.0"
|
package/rules/core.md
CHANGED
|
@@ -15,10 +15,7 @@ You are an AI agent running on a Minion VPS, managed by @geekbeer/minion.
|
|
|
15
15
|
Project (組織・課金単位)
|
|
16
16
|
├── Context (markdown, PMが更新)
|
|
17
17
|
├── Members (minion + role: pm | engineer | accountant)
|
|
18
|
-
|
|
19
|
-
│ ├── Versions (pipeline の不変スナップショット)
|
|
20
|
-
│ └── Executions (実行履歴, ステップごとの進捗)
|
|
21
|
-
└── DAG Workflows (ノード/エッジ形式のワークフロー, beta)
|
|
18
|
+
└── DAG Workflows (ノード/エッジ形式のワークフロー)
|
|
22
19
|
├── Versions (graph の不変スナップショット)
|
|
23
20
|
└── Executions (node_executions の集合, scope-aware)
|
|
24
21
|
|
|
@@ -26,14 +23,14 @@ Minion
|
|
|
26
23
|
└── Routines (ミニオンローカルの定期タスク, cron付き)
|
|
27
24
|
```
|
|
28
25
|
|
|
29
|
-
- **Workflow**:
|
|
30
|
-
- **DAG Workflow**: プロジェクトスコープ。ノード/エッジで依存関係を表現する新方式。fan-out / join / conditional / transform / review をサポート。作成・編集はHQダッシュボードのみ、ミニオンは `dag-step-poller` で自動実行。詳細は `~/.minion/docs/api-reference.md` の「DAG Workflows」と `~/.minion/docs/task-guides.md` の「DAG ワークフロー」を参照。
|
|
26
|
+
- **DAG Workflow**: プロジェクトスコープ。ノード/エッジで依存関係を表現するワークフロー。fan-out / join / conditional / transform / script / review をサポート。作成・編集はHQダッシュボードまたはミニオンAPI、ランタイムは `dag-step-poller` が自動実行。詳細は `~/.minion/docs/api-reference.md` の「DAG Workflows」と `~/.minion/docs/task-guides.md` の「DAG ワークフロー」を参照。
|
|
31
27
|
- **PMロールで編集する場合の重要な規則**:
|
|
32
28
|
- ノード/エッジ/contract の追加・更新は**個別API** (`/nodes` `/edges` `/contracts`) を使うこと。`PUT /dag-workflows/:id` による graph 全文PUTは型の取り違えが起きやすく、バリデーションエラーで 400 が返る
|
|
33
29
|
- `edge.contract` は**単一のContract名(string)**のみ。配列は不可。複数の型構造を束ねたい場合は、それらを内包する複合Contractを1つ定義する
|
|
34
30
|
- Contract内で `List<別Contract>` を表現するには `type: 'array'` + `items: "別Contract名"` を使う(詳細は `~/.minion/docs/api-reference.md` の「Contracts API」を参照)
|
|
35
31
|
- **Contract はランタイムで強制される型定義**。`node-complete` 報告時に outgoing edge の contract で `output_data` が検証され、違反はノード `failed` 扱い。スキルが contract に沿った `## Output Data` を出せない場合は **transform ノードをスキルと下流の間に挟んで整形**すること。スキル側の SKILL.md を各ワークフロー専用に改修するのは原則 NG(スキルは汎用資産)
|
|
36
32
|
- **transform ノードの I/O 型は edge の contract から自動導出**。incoming edge と outgoing edge にそれぞれ contract を必ず貼ること。`transform_instruction` は contract だけで意図が伝わらない場合の補足ヒント(任意)
|
|
33
|
+
- **script ノード (v3.54.0〜)** は LLM を使わない決定的処理用。`script_runtime` (`'python'` or `'node'`) と `script_source` を指定。input_data を stdin で JSON 受け取り → output_data を stdout に JSON 出力する規約。outgoing edge に contract を貼れば transform と同じ runtime validation が走る。LLMトークンを節約したい・出力ブレを許容できない定型処理に使う。詳細は `~/.minion/docs/task-guides.md` の「Script ノード」を参照
|
|
37
34
|
- **fan_out の incoming edge に contract を貼る場合**、`fan_out_source` が指すフィールドが contract 内に `type='array'` として宣言されている必要がある(静的検証で弾かれる)
|
|
38
35
|
- **Routine**: ミニオンスコープ。ミニオンローカルの定期タスク。
|
|
39
36
|
- **Project Tasks / Milestones**: プロジェクトスコープ。**人間+ミニオンが共有するタスクボード**(5段階Kanban: `backlog`→`todo`→`doing`→`review`→`done`)とロードマップ(マイルストーン)。ミニオンは `/api/minion/projects/:projectId/{tasks,milestones,health}` で操作可能。
|
|
@@ -103,7 +100,27 @@ minion-cli --version # バージョン確認
|
|
|
103
100
|
|
|
104
101
|
`http://localhost:8080` — 認証: `Authorization: Bearer $API_TOKEN`
|
|
105
102
|
|
|
106
|
-
主なカテゴリ: Health, Skills,
|
|
103
|
+
主なカテゴリ: Health, Skills, Executions, Terminal, Files, Commands, Permissions, Admin (HQ-pushed freeze), Web Extraction (experimental)
|
|
104
|
+
|
|
105
|
+
#### Web Page Extraction 🧪 (experimental, v3.53.0〜)
|
|
106
|
+
|
|
107
|
+
Webページの読み取り・要約・情報抽出には、**Playwright MCP より先に** ローカルAPI `POST /api/web/extract` を試すこと。
|
|
108
|
+
|
|
109
|
+
このAPIは内部でヘッドレスブラウザ→本文クリーン (Readability) →セレクタ抽出 (LLMサブプロセスで自動学習) →JSON 返却までを完結させる。**メインセッションに巨大な DOM が流れ込まないため、トークン肥大化やセッション終了を防げる。**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
curl -X POST http://localhost:8080/api/web/extract \
|
|
113
|
+
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
114
|
+
-d '{"url": "https://example.com/article/123", "hint": "本文と著者を抽出"}'
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
レシピは初回アクセス時に LLM (Haiku) で生成・SQLite (`page_recipes` テーブル) に保存され、2回目以降の構造的に同じページでは LLM 呼び出しなしで抽出される。
|
|
118
|
+
|
|
119
|
+
Playwright MCP (`mcp__playwright__*`) は **フォーム入力・クリック・複数画面遷移など対話的な操作**が必要な場合のみ使用すること。単に「ページを読む」目的では MCP を使わない。
|
|
120
|
+
|
|
121
|
+
**実験的機能**: レスポンス形状は予告なく変わる可能性がある。要件: (1) primary LLM 設定済み (`PUT /api/llm/config` で `claude` 等を選択、`hq llm primary <name>` でも可) または `ANTHROPIC_API_KEY` シークレット設定済み、(2) ホスト上で `npx playwright install chromium` 実行済み。primary LLM が設定されていれば API キー不要 (Claude Code CLI の認証情報を再利用)。
|
|
122
|
+
|
|
123
|
+
詳細仕様は `~/.minion/docs/api-reference.md` の「Web Page Extraction」セクション、ユースケースは `~/.minion/docs/task-guides.md` の「Webページの読み取り・要約」を参照。
|
|
107
124
|
|
|
108
125
|
#### Billing-Driven Freeze (v3.52.0〜)
|
|
109
126
|
|
|
@@ -182,7 +199,7 @@ Note: Codex CLI の `.codex/` ディレクトリはLLMからの直接編集が
|
|
|
182
199
|
|
|
183
200
|
`$HQ_URL/api/minion/*` — 認証: `Authorization: Bearer $API_TOKEN`
|
|
184
201
|
|
|
185
|
-
主なカテゴリ: Projects, Context,
|
|
202
|
+
主なカテゴリ: Projects, Context, DAG Workflows, Skills, Executions, Routines, Reports
|
|
186
203
|
|
|
187
204
|
DAG ワークフローのランタイム API は `$HQ_URL/api/dag/minion/*`(pending-nodes / claim-node / node-complete / dag-cron-tick)。`dag-step-poller` と `dag-cron-poller` デーモンが自動でポーリングするため、通常ミニオンのAI側から直接叩くことは無い。
|
|
188
205
|
|
|
@@ -204,7 +221,7 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
204
221
|
- `MINION_ROUTINE_ID` — ルーティンUUID
|
|
205
222
|
- `MINION_ROUTINE_NAME` — ルーティン名
|
|
206
223
|
|
|
207
|
-
|
|
224
|
+
**変数**(ミニオン変数・プロジェクト変数)はスキル本文の `{{VAR_NAME}}` テンプレートとして実行時に展開される。スキル作成時にパラメータ化したい値は `{{変数名}}` で記述すること。展開優先順位: ミニオン変数 < プロジェクト変数(後者が優先)。
|
|
208
225
|
|
|
209
226
|
**シークレット**(ミニオンシークレット)はサーバー起動時に `process.env` にロードされ、全子プロセスで環境変数 `$SECRET_NAME` として利用可能。APIキーやパスワード等の機密情報に使用する。シークレットは `{{VAR}}` テンプレートでは展開されない。
|
|
210
227
|
|
package/win/routes/chat.js
CHANGED
|
@@ -423,6 +423,29 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
423
423
|
)
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
// Web page extraction guidance (experimental, v3.53.0)
|
|
427
|
+
if (!sessionId) {
|
|
428
|
+
const port = require('../../core/config').config.AGENT_PORT
|
|
429
|
+
parts.push(
|
|
430
|
+
'[Webページ読み取りについて 🧪 experimental]',
|
|
431
|
+
'Webページの読み取り・要約・情報抽出が必要なときは、まず以下のローカルAPIを試すこと:',
|
|
432
|
+
'',
|
|
433
|
+
'```bash',
|
|
434
|
+
`curl -X POST http://localhost:${port}/api/web/extract \\`,
|
|
435
|
+
' -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \\',
|
|
436
|
+
' -d \'{"url": "対象URL", "hint": "抽出したい内容を短く"}\'',
|
|
437
|
+
'```',
|
|
438
|
+
'',
|
|
439
|
+
'このAPIは内部で Playwright + Readability を回し、抽出済みJSONだけを返すため、',
|
|
440
|
+
'DOM全体がチャットに流れ込んでトークン肥大化することを防げる。',
|
|
441
|
+
'初回アクセスで学習したセレクタはSQLiteにキャッシュされ、2回目以降はLLM呼び出しなしで抽出される。',
|
|
442
|
+
'',
|
|
443
|
+
'Playwright MCP (`mcp__playwright__*`) は **ログイン・フォーム入力・複数画面の対話操作**が必要な場合のみ使用する。',
|
|
444
|
+
'単純な閲覧・要約・一覧取得用途ではMCPを使わない。',
|
|
445
|
+
''
|
|
446
|
+
)
|
|
447
|
+
}
|
|
448
|
+
|
|
426
449
|
// File output guidance — always inject on new sessions
|
|
427
450
|
if (!sessionId) {
|
|
428
451
|
parts.push(
|
|
@@ -502,24 +525,11 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
502
525
|
)
|
|
503
526
|
}
|
|
504
527
|
break
|
|
505
|
-
case 'workflow':
|
|
506
|
-
if (context.projectId) {
|
|
507
|
-
const label = context.workflowName ? `ワークフロー「${context.workflowName}」` : 'ワークフロー'
|
|
508
|
-
parts.push(
|
|
509
|
-
`ユーザーはHQダッシュボードで${label}を閲覧しています。`,
|
|
510
|
-
`ワークフロー情報を取得するには以下を実行してください:`,
|
|
511
|
-
` hq fetch workflow ${context.workflowName || context.workflowId}`,
|
|
512
|
-
`プロジェクトコンテキスト:`,
|
|
513
|
-
` hq fetch project-context ${context.projectId}`,
|
|
514
|
-
`取得した内容をもとに回答してください。`
|
|
515
|
-
)
|
|
516
|
-
}
|
|
517
|
-
break
|
|
518
528
|
case 'dag-workflow':
|
|
519
529
|
if (context.projectId && context.dagWorkflowId) {
|
|
520
530
|
parts.push(
|
|
521
531
|
`ユーザーはHQダッシュボードで DAG ワークフロー (ID: ${context.dagWorkflowId}) のエディタ/詳細を閲覧しています。`,
|
|
522
|
-
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / review をサポートします。`,
|
|
532
|
+
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / script / review をサポートします。`,
|
|
523
533
|
`DAG ワークフロー情報を取得するには以下を実行してください:`,
|
|
524
534
|
` hq fetch dag-workflow ${context.dagWorkflowId}`,
|
|
525
535
|
`プロジェクトコンテキスト:`,
|
|
@@ -790,6 +800,12 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
|
|
|
790
800
|
|
|
791
801
|
child.on('close', async (code) => {
|
|
792
802
|
activeChatChild = null
|
|
803
|
+
|
|
804
|
+
console.log(`[Chat] child closed: code=${code}, response=${fullResponse.length}chars, turns=${turnCount}, stderr=${stderrBuffer.length}bytes, session=${resolvedSessionId}`)
|
|
805
|
+
if (stderrBuffer.trim()) {
|
|
806
|
+
console.log(`[Chat] final stderr (tail 500): ${stderrBuffer.slice(-500)}`)
|
|
807
|
+
}
|
|
808
|
+
|
|
793
809
|
if (resolvedSessionId) {
|
|
794
810
|
if (!sessionId) {
|
|
795
811
|
await chatStore.addMessage(resolvedSessionId, { role: 'user', content: originalMessage || prompt }, undefined, workspaceId)
|
|
@@ -798,9 +814,15 @@ function streamViaLegacyLlmCommand(res, prompt, sessionId, workspaceId, original
|
|
|
798
814
|
await chatStore.addMessage(resolvedSessionId, { role: 'assistant', content: fullResponse }, turnCount, workspaceId)
|
|
799
815
|
}
|
|
800
816
|
}
|
|
801
|
-
if (code !== 0
|
|
817
|
+
if (code !== 0) {
|
|
802
818
|
const errorMsg = stderrBuffer.trim() || `Claude CLI exited with code ${code}`
|
|
803
|
-
|
|
819
|
+
console.error(`[Chat] CLI failed (exit ${code}, partial=${!!fullResponse}): ${errorMsg}`)
|
|
820
|
+
res.write(`data: ${JSON.stringify({
|
|
821
|
+
type: 'error',
|
|
822
|
+
error: errorMsg,
|
|
823
|
+
partial: !!fullResponse,
|
|
824
|
+
exit_code: code,
|
|
825
|
+
})}\n\n`)
|
|
804
826
|
}
|
|
805
827
|
|
|
806
828
|
const session = await chatStore.load(workspaceId)
|
package/win/server.js
CHANGED
|
@@ -67,6 +67,7 @@ const { todoRoutes } = require('../core/routes/todos')
|
|
|
67
67
|
const { emailRoutes } = require('../core/routes/emails')
|
|
68
68
|
const { daemonRoutes } = require('../core/routes/daemons')
|
|
69
69
|
const { llmRoutes } = require('../core/routes/llm')
|
|
70
|
+
const { webRoutes } = require('../core/routes/web')
|
|
70
71
|
|
|
71
72
|
// Validate configuration
|
|
72
73
|
validate()
|
|
@@ -232,6 +233,7 @@ async function registerRoutes(app) {
|
|
|
232
233
|
await app.register(emailRoutes)
|
|
233
234
|
await app.register(daemonRoutes, { heartbeatStatus: () => ({ running: !!heartbeatTimer, last_beat_at: lastBeatAt }) })
|
|
234
235
|
await app.register(llmRoutes)
|
|
236
|
+
await app.register(webRoutes)
|
|
235
237
|
|
|
236
238
|
// Shutdown endpoint — allows detached restart/update scripts to trigger
|
|
237
239
|
// graceful shutdown (offline heartbeat) before force-killing the process.
|