@geekbeer/minion 3.53.1 → 3.57.0
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/core/db/migrations/20260510100000_secrets_workspace.js +36 -0
- package/core/db/migrations/20260511100000_variables_workspace.js +37 -0
- package/core/lib/dag-step-poller.js +245 -1
- package/core/lib/session-env.js +96 -0
- package/core/lib/step-poller.js +8 -3
- package/core/lib/template-expander.js +20 -6
- package/core/routes/variables.js +67 -17
- package/core/stores/variable-store.js +278 -76
- package/docs/api-reference.md +55 -297
- package/docs/task-guides.md +76 -75
- package/linux/routes/chat.js +1 -16
- package/linux/routine-runner.js +49 -11
- package/linux/workflow-runner.js +19 -8
- package/package.json +1 -1
- package/rules/core.md +8 -10
- package/win/routes/chat.js +1 -14
- package/win/routine-runner.js +35 -8
- package/win/workflow-runner.js +12 -5
package/docs/task-guides.md
CHANGED
|
@@ -88,7 +88,7 @@ requires:
|
|
|
88
88
|
---
|
|
89
89
|
|
|
90
90
|
Skill instructions here...
|
|
91
|
-
Use {{PROJECT_VAR}} to reference project
|
|
91
|
+
Use {{PROJECT_VAR}} to reference project variables.
|
|
92
92
|
```
|
|
93
93
|
|
|
94
94
|
フロントマターのフィールド:
|
|
@@ -120,7 +120,7 @@ description: サイトをデプロイする
|
|
|
120
120
|
|
|
121
121
|
- 変数名は英数字とアンダースコアのみ(`\w+`)
|
|
122
122
|
- 未定義の変数は `{{VAR_NAME}}` のまま残る(エラーにはならない)
|
|
123
|
-
- 展開優先順位: ミニオン変数 <
|
|
123
|
+
- 展開優先順位: ミニオン変数 < プロジェクト変数(後者が上書き)
|
|
124
124
|
- ルーティン実行時もミニオン変数による `{{VAR}}` 展開が行われる
|
|
125
125
|
|
|
126
126
|
### 変数とシークレットの使い分け
|
|
@@ -130,7 +130,7 @@ description: サイトをデプロイする
|
|
|
130
130
|
| 変数 | `{{VAR_NAME}}` | 設定・パラメータ(非機密) | デプロイ先、サイトURL、プロジェクト名 |
|
|
131
131
|
| シークレット | `$SECRET_NAME`(環境変数) | 機密情報 | APIキー、パスワード、トークン |
|
|
132
132
|
|
|
133
|
-
-
|
|
133
|
+
- **変数**はスキル本文のテンプレートとして展開される。全スコープ(ミニオン・プロジェクト)で同じ `{{VAR}}` 構文を使用する
|
|
134
134
|
- **シークレット**は環境変数としてプロセスに注入される。テンプレート展開は行われない
|
|
135
135
|
- デイリーログやメモリーから変数・シークレットの値を推測して使用しないこと
|
|
136
136
|
|
|
@@ -154,63 +154,11 @@ minion-cli skill fetch <name>
|
|
|
154
154
|
|
|
155
155
|
---
|
|
156
156
|
|
|
157
|
-
## ワークフローの修正 (PM のみ)
|
|
158
|
-
|
|
159
|
-
### パイプライン構成の変更
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
# API経由で push (新バージョン自動作成)
|
|
163
|
-
curl -s -X POST "$HQ_URL/api/minion/workflows" \
|
|
164
|
-
-H "Authorization: Bearer $API_TOKEN" \
|
|
165
|
-
-H "Content-Type: application/json" \
|
|
166
|
-
-d '{
|
|
167
|
-
"name": "my-workflow",
|
|
168
|
-
"pipeline_skill_names": ["skill-1", "skill-2"],
|
|
169
|
-
"content": "Workflow description",
|
|
170
|
-
"project_id": "<project-uuid>",
|
|
171
|
-
"change_summary": "Updated pipeline"
|
|
172
|
-
}'
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### ステップごとのロール・レビュー設定
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
curl -s -X POST "$HQ_URL/api/minion/workflows" \
|
|
179
|
-
-H "Authorization: Bearer $API_TOKEN" \
|
|
180
|
-
-H "Content-Type: application/json" \
|
|
181
|
-
-d '{
|
|
182
|
-
"name": "my-workflow",
|
|
183
|
-
"pipeline_skill_names": ["skill-1", "skill-2"],
|
|
184
|
-
"pipeline_steps": [
|
|
185
|
-
{ "assigned_role": "engineer", "requires_review": false },
|
|
186
|
-
{ "assigned_role": "pm", "requires_review": true }
|
|
187
|
-
],
|
|
188
|
-
"content": "Workflow description",
|
|
189
|
-
"project_id": "<project-uuid>"
|
|
190
|
-
}'
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### ローカルワークフローの同期
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
# HQからワークフローを取得 (不足スキルも自動fetch)
|
|
197
|
-
curl -s -X POST "http://localhost:8080/api/workflows/fetch/<name>" \
|
|
198
|
-
-H "Authorization: Bearer $API_TOKEN"
|
|
199
|
-
|
|
200
|
-
# ローカルワークフローをHQにpush (スキルも自動push)
|
|
201
|
-
curl -s -X POST "http://localhost:8080/api/workflows/push/<name>" \
|
|
202
|
-
-H "Authorization: Bearer $API_TOKEN"
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
> **Note**: `/api/workflows/push|fetch` は線形パイプライン形式(`pipeline_skill_names`)の旧式ワークフロー専用です。DAGワークフロー(ノード/エッジ形式)はHQダッシュボードの DAG エディタで作成・編集します。次節を参照。
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
157
|
## DAG ワークフロー (ノード/エッジ形式)
|
|
210
158
|
|
|
211
|
-
DAG
|
|
159
|
+
DAG ワークフローは有向非巡回グラフでスキル間の依存関係を表現するワークフローです。fan-out による並列展開、join による集約、conditional による分岐、transform による LLM データ変換、script による決定的スクリプト実行 (Python/Node)、review によるゲーティングをサポートします。
|
|
212
160
|
|
|
213
|
-
**HQダッシュボードの DAG
|
|
161
|
+
**HQダッシュボードの DAG エディタ(プロジェクト画面の「DAG」タブ)で GUI 編集**できるほか、**ミニオンからは JSON ベースで編集可能**(PMロールのみ)。ミニオンは実行ランタイムも担当し、`dag-step-poller` デーモンが pending ノードを自動で処理します。
|
|
214
162
|
|
|
215
163
|
**cronスケジュール (v3.51.0〜):** DAGワークフローは cron 式による定期実行をサポート。設定はHQダッシュボードのDAGビューにある SchedulePanel から行う(最小実行間隔 5分)。発火主体はそのプロジェクトのPMロールのミニオンで、`dag-cron-poller` デーモンが60秒間隔で `/api/dag/minion/dag-cron-tick` を叩いて発火させる。PM不在のプロジェクトでは cron は発火しない。
|
|
216
164
|
|
|
@@ -405,7 +353,7 @@ hq dag remove-edge <wf-id> edge_3
|
|
|
405
353
|
|
|
406
354
|
**fan_out テンプレート編集:**
|
|
407
355
|
|
|
408
|
-
> **⚠️ テンプレート内に `start` / `end` ノードを入れないこと。** テンプレートのエントリ/エグジットはエッジ構造から自動検出される。`start` / `end` はトップレベル DAG 専用であり、テンプレート内に含めるとバリデーションエラーになる。`skill`, `conditional`, `transform`, `review` 等の実行ノードのみ使用すること。
|
|
356
|
+
> **⚠️ テンプレート内に `start` / `end` ノードを入れないこと。** テンプレートのエントリ/エグジットはエッジ構造から自動検出される。`start` / `end` はトップレベル DAG 専用であり、テンプレート内に含めるとバリデーションエラーになる。`skill`, `conditional`, `transform`, `script`, `review` 等の実行ノードのみ使用すること。
|
|
409
357
|
|
|
410
358
|
```bash
|
|
411
359
|
# fan_out ノードの template を PATCH で上書き
|
|
@@ -434,7 +382,7 @@ hq dag update-node <wf-id> fan_out_1 /tmp/t.json
|
|
|
434
382
|
- 書き込み(create / put / publish / dag 操作)は **PMロールのみ**。Engineer / accountant では 403 が返る。読み取り(fetch)は全メンバー可。
|
|
435
383
|
- ノード/エッジ操作は各ステップでドラフトに自動保存され、バリデーション結果がレスポンスに含まれる。
|
|
436
384
|
- `hq dag validate` で公開前にフル検証できる。publish 時の想定外エラーを防止できる。
|
|
437
|
-
- 全文 JSON 操作(`hq put dag-workflow`)も引き続き利用可能だが、**ノード/エッジ操作API
|
|
385
|
+
- 全文 JSON 操作(`hq put dag-workflow`)も引き続き利用可能だが、**ノード/エッジ操作APIの方が推奨**(型の取り違えで 400 になりやすい)。
|
|
438
386
|
|
|
439
387
|
### 実行フロー(ランタイム側、参考)
|
|
440
388
|
|
|
@@ -548,6 +496,63 @@ input_data: { "items": [{ "title": "Item A" }, { "title": "Item B" }, { "title":
|
|
|
548
496
|
output_data: { "items": [{ "title": "Item A" }, { "title": "Item C" }] }
|
|
549
497
|
```
|
|
550
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
|
+
|
|
551
556
|
### DAG 構築時のノード選定フロー
|
|
552
557
|
|
|
553
558
|
スキルは汎用的で再利用可能な資産であり、ワークフロー固有の contract に合わせて SKILL.md を改修することは原則行わない。代わりに以下のフローで判断する:
|
|
@@ -579,7 +584,7 @@ Review ノードはレビューゲート。`review_status=review_pending` で下
|
|
|
579
584
|
- `approved` にすると `approved` 種別のエッジで下流に進む
|
|
580
585
|
- `revision_requested` にすると `revision` 種別のエッジで差し戻し先に戻る
|
|
581
586
|
|
|
582
|
-
|
|
587
|
+
差し戻しはサーバ側のカスケードで自動処理される(`revision` 種別のエッジが指す上流ノード以降が `pending` に戻り再実行される)。
|
|
583
588
|
|
|
584
589
|
### デバッグ
|
|
585
590
|
|
|
@@ -714,17 +719,14 @@ curl -X PATCH "$HQ_URL/api/minion/projects/<project-id>/tasks/<task-id>" \
|
|
|
714
719
|
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
715
720
|
-d '{"status":"doing"}'
|
|
716
721
|
|
|
717
|
-
#
|
|
722
|
+
# レビュー依頼(acceptance_criteria を全て満たした後)
|
|
718
723
|
curl -X PATCH "$HQ_URL/api/minion/projects/<project-id>/tasks/<task-id>" \
|
|
719
724
|
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
720
725
|
-d '{"status":"review"}'
|
|
721
|
-
|
|
722
|
-
# 完了
|
|
723
|
-
curl -X PATCH "$HQ_URL/api/minion/projects/<project-id>/tasks/<task-id>" \
|
|
724
|
-
-H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
|
|
725
|
-
-d '{"status":"done"}'
|
|
726
726
|
```
|
|
727
727
|
|
|
728
|
+
> **重要**: ミニオン自身は `done` へ直接遷移してはいけない。`done` 遷移はレビュアー(人間または別のミニオン)の責務。受け入れ要件達成後は `review` で停止し、承認を待つこと。
|
|
729
|
+
|
|
728
730
|
### 共通の注意
|
|
729
731
|
|
|
730
732
|
- **`status_changed_at` を手動で渡してはならない**。サーバ側で自動更新される(stalled 検出に使われる)。
|
|
@@ -821,20 +823,19 @@ minion-cli skill list --local
|
|
|
821
823
|
minion-cli skill fetch <name>
|
|
822
824
|
```
|
|
823
825
|
|
|
824
|
-
### ワークフローが実行されない
|
|
826
|
+
### DAG ワークフローが実行されない
|
|
825
827
|
|
|
826
828
|
```bash
|
|
827
|
-
#
|
|
828
|
-
curl -s "
|
|
829
|
-
-H "Authorization: Bearer $API_TOKEN"
|
|
829
|
+
# pending ノードが自分に割り当てられているか確認
|
|
830
|
+
curl -s "$HQ_URL/api/dag/minion/pending-nodes" \
|
|
831
|
+
-H "Authorization: Bearer $API_TOKEN" | jq
|
|
830
832
|
|
|
831
|
-
#
|
|
832
|
-
|
|
833
|
-
-H "Authorization: Bearer $API_TOKEN" \
|
|
834
|
-
-H "Content-Type: application/json" \
|
|
835
|
-
-d '{ "workflow_id": "<workflow-uuid>" }'
|
|
833
|
+
# dag-step-poller のログを確認
|
|
834
|
+
tail -f ~/.minion/logs/agent.log | grep '\[DAG'
|
|
836
835
|
```
|
|
837
836
|
|
|
837
|
+
PMロールが不在のプロジェクトでは cron 発火が行われない。`hq list dag-workflows <project-id>` の `my_role` を確認すること。
|
|
838
|
+
|
|
838
839
|
### エージェントの状態確認
|
|
839
840
|
|
|
840
841
|
```bash
|
package/linux/routes/chat.js
CHANGED
|
@@ -464,26 +464,11 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
464
464
|
)
|
|
465
465
|
}
|
|
466
466
|
break
|
|
467
|
-
case 'workflow':
|
|
468
|
-
if (context.projectId) {
|
|
469
|
-
const label = context.workflowName
|
|
470
|
-
? `ワークフロー「${context.workflowName}」`
|
|
471
|
-
: 'ワークフロー'
|
|
472
|
-
parts.push(
|
|
473
|
-
`ユーザーはHQダッシュボードで${label}を閲覧しています。`,
|
|
474
|
-
`ワークフロー情報を取得するには以下を実行してください:`,
|
|
475
|
-
` hq fetch workflow ${context.workflowName || context.workflowId}`,
|
|
476
|
-
`プロジェクトコンテキスト:`,
|
|
477
|
-
` hq fetch project-context ${context.projectId}`,
|
|
478
|
-
`取得した内容をもとに回答してください。`
|
|
479
|
-
)
|
|
480
|
-
}
|
|
481
|
-
break
|
|
482
467
|
case 'dag-workflow':
|
|
483
468
|
if (context.projectId && context.dagWorkflowId) {
|
|
484
469
|
parts.push(
|
|
485
470
|
`ユーザーはHQダッシュボードで DAG ワークフロー (ID: ${context.dagWorkflowId}) のエディタ/詳細を閲覧しています。`,
|
|
486
|
-
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / review をサポートします。`,
|
|
471
|
+
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / script / review をサポートします。`,
|
|
487
472
|
`DAG ワークフロー情報を取得するには以下を実行してください:`,
|
|
488
473
|
` hq fetch dag-workflow ${context.dagWorkflowId}`,
|
|
489
474
|
`プロジェクトコンテキスト:`,
|
package/linux/routine-runner.js
CHANGED
|
@@ -16,12 +16,14 @@ const crypto = require('crypto')
|
|
|
16
16
|
const fs = require('fs').promises
|
|
17
17
|
const execAsync = promisify(exec)
|
|
18
18
|
|
|
19
|
-
const { config } = require('../core/config')
|
|
19
|
+
const { config, isHqConfigured } = require('../core/config')
|
|
20
|
+
const api = require('../core/api')
|
|
20
21
|
const executionStore = require('../core/stores/execution-store')
|
|
21
22
|
const routineStore = require('../core/stores/routine-store')
|
|
22
23
|
const logManager = require('../core/lib/log-manager')
|
|
23
24
|
const runningTasks = require('../core/lib/running-tasks')
|
|
24
25
|
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
26
|
+
const { buildTmuxNewSessionCommand } = require('../core/lib/session-env')
|
|
25
27
|
const { getActivePrimary } = require('../core/llm-plugins/lib/active')
|
|
26
28
|
const os = require('os')
|
|
27
29
|
const path = require('path')
|
|
@@ -40,6 +42,32 @@ function sleep(ms) {
|
|
|
40
42
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Fetch workspace-scoped variables for a routine from HQ.
|
|
47
|
+
* Returns an empty object when the routine isn't bound to a workspace or HQ
|
|
48
|
+
* is unreachable; the runner continues with minion-local vars only.
|
|
49
|
+
*
|
|
50
|
+
* Workspace secrets are NOT fetched — secrets remain minion-local only (the
|
|
51
|
+
* HQ DB never stores secret values, by design).
|
|
52
|
+
*
|
|
53
|
+
* @param {string} workspaceId - Routine.workspace_id (may be falsy)
|
|
54
|
+
* @returns {Promise<Record<string, string>>}
|
|
55
|
+
*/
|
|
56
|
+
async function fetchWorkspaceVars(workspaceId) {
|
|
57
|
+
if (!workspaceId || !isHqConfigured()) return {}
|
|
58
|
+
try {
|
|
59
|
+
const result = await api.request(`/workspaces/${workspaceId}/variables`)
|
|
60
|
+
const vars = {}
|
|
61
|
+
for (const v of (result?.variables || [])) {
|
|
62
|
+
if (v && typeof v.key === 'string') vars[v.key] = String(v.value ?? '')
|
|
63
|
+
}
|
|
64
|
+
return vars
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error(`[RoutineRunner] Failed to fetch workspace vars (${workspaceId}): ${err.message}`)
|
|
67
|
+
return {}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
43
71
|
/**
|
|
44
72
|
* Generate tmux session name from routine ID and execution ID
|
|
45
73
|
* Format: rt-{routineId first 8}-{executionId first 4}
|
|
@@ -90,10 +118,15 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
90
118
|
console.log(`[RoutineRunner] tmux session: ${sessionName}`)
|
|
91
119
|
console.log(`[RoutineRunner] Log file: ${logFile}`)
|
|
92
120
|
|
|
93
|
-
//
|
|
121
|
+
// Fetch HQ workspace variables when the routine is bound to a workspace.
|
|
122
|
+
// Minion variables (minion-wide ∪ WS-scoped, resolved inside the expander
|
|
123
|
+
// via workspaceId) override these as the final layer.
|
|
124
|
+
const workspaceVars = await fetchWorkspaceVars(routine.workspace_id)
|
|
125
|
+
|
|
126
|
+
// Expand {{VAR}} templates in SKILL.md files
|
|
94
127
|
let expandedOriginals = new Map()
|
|
95
128
|
try {
|
|
96
|
-
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
129
|
+
expandedOriginals = await expandSkillTemplates(skillNames, workspaceVars, routine.workspace_id || '')
|
|
97
130
|
if (expandedOriginals.size > 0) {
|
|
98
131
|
console.log(`[RoutineRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
99
132
|
}
|
|
@@ -142,14 +175,19 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
142
175
|
)
|
|
143
176
|
await execAsync(`chmod +x "${execScript}"`)
|
|
144
177
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
178
|
+
// Build tmux invocation with workspace-effective secrets + per-execution
|
|
179
|
+
// identifiers. Secrets come from variable-store (minion-wide ∪ WS-scoped,
|
|
180
|
+
// with WS values winning); identifiers always override on top.
|
|
181
|
+
const tmuxCommand = buildTmuxNewSessionCommand({
|
|
182
|
+
sessionName,
|
|
183
|
+
workspaceId: routine.workspace_id || '',
|
|
184
|
+
extraEnv: {
|
|
185
|
+
MINION_EXECUTION_ID: executionId,
|
|
186
|
+
MINION_ROUTINE_ID: routine.id,
|
|
187
|
+
MINION_ROUTINE_NAME: routine.name,
|
|
188
|
+
MINION_ROUTINE_WORKSPACE_ID: routine.workspace_id || '',
|
|
189
|
+
},
|
|
190
|
+
})
|
|
153
191
|
|
|
154
192
|
await execAsync(tmuxCommand, { cwd: homeDir })
|
|
155
193
|
|
package/linux/workflow-runner.js
CHANGED
|
@@ -22,6 +22,7 @@ const workflowStore = require('../core/stores/workflow-store')
|
|
|
22
22
|
const logManager = require('../core/lib/log-manager')
|
|
23
23
|
const runningTasks = require('../core/lib/running-tasks')
|
|
24
24
|
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
25
|
+
const { buildTmuxNewSessionCommand } = require('../core/lib/session-env')
|
|
25
26
|
const { getActivePrimary } = require('../core/llm-plugins/lib/active')
|
|
26
27
|
const os = require('os')
|
|
27
28
|
const path = require('path')
|
|
@@ -139,10 +140,13 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
139
140
|
console.log(`[WorkflowRunner] Log file: ${logFile}`)
|
|
140
141
|
console.log(`[WorkflowRunner] HOME: ${homeDir}`)
|
|
141
142
|
|
|
142
|
-
// Expand {{VAR}} templates in SKILL.md files with minion variables
|
|
143
|
+
// Expand {{VAR}} templates in SKILL.md files with minion variables effective
|
|
144
|
+
// for this workflow's workspace (minion-wide ∪ WS-scoped, WS wins). HQ has
|
|
145
|
+
// already expanded workspace/project/workflow scopes before delivering the
|
|
146
|
+
// SKILL.md, so this layer only resolves remaining placeholders.
|
|
143
147
|
let expandedOriginals = new Map()
|
|
144
148
|
try {
|
|
145
|
-
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
149
|
+
expandedOriginals = await expandSkillTemplates(skillNames, {}, workflow.workspace_id || '')
|
|
146
150
|
if (expandedOriginals.size > 0) {
|
|
147
151
|
console.log(`[WorkflowRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
148
152
|
}
|
|
@@ -200,12 +204,19 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
200
204
|
)
|
|
201
205
|
await execAsync(`chmod +x "${execScript}"`)
|
|
202
206
|
|
|
203
|
-
// PATH, HOME
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
// PATH, HOME and DISPLAY are inherited from process.env (set at startup);
|
|
208
|
+
// workspace-scoped secrets are passed explicitly via `-e` here so each
|
|
209
|
+
// session only sees secrets relevant to its workspace context.
|
|
210
|
+
const tmuxCommand = buildTmuxNewSessionCommand({
|
|
211
|
+
sessionName,
|
|
212
|
+
workspaceId: workflow.workspace_id || '',
|
|
213
|
+
extraEnv: {
|
|
214
|
+
MINION_EXECUTION_ID: executionId,
|
|
215
|
+
MINION_WORKFLOW_ID: workflow.id || '',
|
|
216
|
+
MINION_WORKFLOW_WORKSPACE_ID: workflow.workspace_id || '',
|
|
217
|
+
},
|
|
218
|
+
})
|
|
219
|
+
await execAsync(tmuxCommand, { cwd: homeDir })
|
|
209
220
|
|
|
210
221
|
// Keep session alive after command completes (for debugging via terminal mirror)
|
|
211
222
|
await execAsync(`tmux set-option -t "${sessionName}" remain-on-exit on`)
|
package/package.json
CHANGED
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,7 @@ 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)
|
|
107
104
|
|
|
108
105
|
#### Web Page Extraction 🧪 (experimental, v3.53.0〜)
|
|
109
106
|
|
|
@@ -202,7 +199,7 @@ Note: Codex CLI の `.codex/` ディレクトリはLLMからの直接編集が
|
|
|
202
199
|
|
|
203
200
|
`$HQ_URL/api/minion/*` — 認証: `Authorization: Bearer $API_TOKEN`
|
|
204
201
|
|
|
205
|
-
主なカテゴリ: Projects, Context,
|
|
202
|
+
主なカテゴリ: Projects, Context, DAG Workflows, Skills, Executions, Routines, Reports
|
|
206
203
|
|
|
207
204
|
DAG ワークフローのランタイム API は `$HQ_URL/api/dag/minion/*`(pending-nodes / claim-node / node-complete / dag-cron-tick)。`dag-step-poller` と `dag-cron-poller` デーモンが自動でポーリングするため、通常ミニオンのAI側から直接叩くことは無い。
|
|
208
205
|
|
|
@@ -223,10 +220,11 @@ Routine 実行中は以下もtmuxセッション環境で利用可能:
|
|
|
223
220
|
- `MINION_EXECUTION_ID` — 実行UUID
|
|
224
221
|
- `MINION_ROUTINE_ID` — ルーティンUUID
|
|
225
222
|
- `MINION_ROUTINE_NAME` — ルーティン名
|
|
223
|
+
- `MINION_ROUTINE_WORKSPACE_ID` — ルーティンが特定ワークスペースにバインドされている場合のワークスペースUUID(未バインドなら空文字)
|
|
226
224
|
|
|
227
|
-
|
|
225
|
+
**変数**(ワークスペース変数・プロジェクト変数・ワークフロー変数・ミニオン変数)はスキル本文の `{{VAR_NAME}}` テンプレートとして実行時に展開される。スキル作成時にパラメータ化したい値は `{{変数名}}` で記述すること。展開優先順位: ワークスペース < プロジェクト < ワークフロー < ミニオン(後者が優先)。ミニオンが最終オーバーライドとなる設計で、ミニオン運用者がHQ側のデフォルトを差し替えられる経路を保証する。ミニオン変数はさらにミニオン全体スコープとワークスペース別スコープに分かれ、当該ワークスペースのコンテキストで動く実行時はWS別がミニオン全体を上書きする。`/api/variables/*` は `?workspace_id=<uuid>` でスコープ指定(省略時はミニオン全体)。
|
|
228
226
|
|
|
229
|
-
|
|
227
|
+
**シークレット**はワークスペース別にスコープされ、ミニオンローカルのSQLite (`secrets(workspace_id, key, value)`) に保存される。ワークスペース未指定(`workspace_id=''`)はミニオン全体のスコープで、サーバー起動時に `process.env` にロードされ全子プロセスで `$SECRET_NAME` として利用可能。ワークスペース別シークレット(`workspace_id=<uuid>`)は `process.env` にはロードされず、当該ワークスペースのコンテキストで動くランナー(ワークフロー/WSバインドされたルーティン/チャットセッション)にのみ実行時注入される。同名キーが両スコープに存在する場合はワークスペース別が優先。スキル側は常に `$KEY` で参照すればよく、どちらのスコープから来た値かを意識する必要はない。シークレット値はHQ DBに保存されることはなく、HQはpass-throughとして中継するのみ。
|
|
230
228
|
|
|
231
229
|
デイリーログやメモリーから変数・シークレットの値を推測して使用してはならない。変数は `{{VAR_NAME}}` テンプレートとして定義し、シークレットは環境変数として参照すること。
|
|
232
230
|
|
package/win/routes/chat.js
CHANGED
|
@@ -525,24 +525,11 @@ async function buildContextPrefix(message, context, sessionId, workspaceId, refe
|
|
|
525
525
|
)
|
|
526
526
|
}
|
|
527
527
|
break
|
|
528
|
-
case 'workflow':
|
|
529
|
-
if (context.projectId) {
|
|
530
|
-
const label = context.workflowName ? `ワークフロー「${context.workflowName}」` : 'ワークフロー'
|
|
531
|
-
parts.push(
|
|
532
|
-
`ユーザーはHQダッシュボードで${label}を閲覧しています。`,
|
|
533
|
-
`ワークフロー情報を取得するには以下を実行してください:`,
|
|
534
|
-
` hq fetch workflow ${context.workflowName || context.workflowId}`,
|
|
535
|
-
`プロジェクトコンテキスト:`,
|
|
536
|
-
` hq fetch project-context ${context.projectId}`,
|
|
537
|
-
`取得した内容をもとに回答してください。`
|
|
538
|
-
)
|
|
539
|
-
}
|
|
540
|
-
break
|
|
541
528
|
case 'dag-workflow':
|
|
542
529
|
if (context.projectId && context.dagWorkflowId) {
|
|
543
530
|
parts.push(
|
|
544
531
|
`ユーザーはHQダッシュボードで DAG ワークフロー (ID: ${context.dagWorkflowId}) のエディタ/詳細を閲覧しています。`,
|
|
545
|
-
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / review をサポートします。`,
|
|
532
|
+
`DAG ワークフローはノード/エッジ形式でスキル間の依存関係を表現し、fan-out / join / conditional / transform / script / review をサポートします。`,
|
|
546
533
|
`DAG ワークフロー情報を取得するには以下を実行してください:`,
|
|
547
534
|
` hq fetch dag-workflow ${context.dagWorkflowId}`,
|
|
548
535
|
`プロジェクトコンテキスト:`,
|
package/win/routine-runner.js
CHANGED
|
@@ -12,12 +12,14 @@ const { stripAnsi } = require('../core/lib/strip-ansi')
|
|
|
12
12
|
const fs = require('fs').promises
|
|
13
13
|
const fsSync = require('fs')
|
|
14
14
|
|
|
15
|
-
const { config } = require('../core/config')
|
|
15
|
+
const { config, isHqConfigured } = require('../core/config')
|
|
16
|
+
const api = require('../core/api')
|
|
16
17
|
const executionStore = require('../core/stores/execution-store')
|
|
17
18
|
const routineStore = require('../core/stores/routine-store')
|
|
18
19
|
const logManager = require('../core/lib/log-manager')
|
|
19
20
|
const { activeSessions } = require('./workflow-runner')
|
|
20
21
|
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
22
|
+
const { buildSpawnEnv } = require('../core/lib/session-env')
|
|
21
23
|
const { getActivePrimary } = require('../core/llm-plugins/lib/active')
|
|
22
24
|
const os = require('os')
|
|
23
25
|
|
|
@@ -28,6 +30,25 @@ function sleep(ms) {
|
|
|
28
30
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Fetch workspace-scoped variables for a routine from HQ.
|
|
35
|
+
* Mirrors the Linux runner; see that file for full docs.
|
|
36
|
+
*/
|
|
37
|
+
async function fetchWorkspaceVars(workspaceId) {
|
|
38
|
+
if (!workspaceId || !isHqConfigured()) return {}
|
|
39
|
+
try {
|
|
40
|
+
const result = await api.request(`/workspaces/${workspaceId}/variables`)
|
|
41
|
+
const vars = {}
|
|
42
|
+
for (const v of (result?.variables || [])) {
|
|
43
|
+
if (v && typeof v.key === 'string') vars[v.key] = String(v.value ?? '')
|
|
44
|
+
}
|
|
45
|
+
return vars
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error(`[RoutineRunner] Failed to fetch workspace vars (${workspaceId}): ${err.message}`)
|
|
48
|
+
return {}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
31
52
|
function generateSessionName(routineId, executionId) {
|
|
32
53
|
const routineShort = routineId ? routineId.substring(0, 8) : 'manual'
|
|
33
54
|
const execShort = executionId ? executionId.substring(0, 4) : ''
|
|
@@ -67,10 +88,15 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
67
88
|
console.log(`[RoutineRunner] Session: ${sessionName}`)
|
|
68
89
|
console.log(`[RoutineRunner] Log file: ${logFile}`)
|
|
69
90
|
|
|
70
|
-
//
|
|
91
|
+
// Fetch HQ workspace variables when the routine is bound to a workspace.
|
|
92
|
+
// Minion variables (minion-wide ∪ WS-scoped, resolved inside the expander
|
|
93
|
+
// via workspaceId) override these as the final layer.
|
|
94
|
+
const workspaceVars = await fetchWorkspaceVars(routine.workspace_id)
|
|
95
|
+
|
|
96
|
+
// Expand {{VAR}} templates in SKILL.md files
|
|
71
97
|
let expandedOriginals = new Map()
|
|
72
98
|
try {
|
|
73
|
-
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
99
|
+
expandedOriginals = await expandSkillTemplates(skillNames, workspaceVars, routine.workspace_id || '')
|
|
74
100
|
if (expandedOriginals.size > 0) {
|
|
75
101
|
console.log(`[RoutineRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
76
102
|
}
|
|
@@ -101,14 +127,15 @@ async function executeRoutineSession(routine, executionId, skillNames) {
|
|
|
101
127
|
throw new Error('No LLM configured. Set a Primary plugin via /api/llm/config or LLM_COMMAND in minion.env')
|
|
102
128
|
}
|
|
103
129
|
|
|
104
|
-
// PATH, HOME, USERPROFILE
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
// PATH, HOME, USERPROFILE are inherited; workspace-scoped secrets are
|
|
131
|
+
// merged in via buildSpawnEnv() so a routine only sees secrets relevant
|
|
132
|
+
// to the workspace it is bound to.
|
|
133
|
+
const env = buildSpawnEnv(routine.workspace_id || '', {
|
|
108
134
|
MINION_EXECUTION_ID: executionId,
|
|
109
135
|
MINION_ROUTINE_ID: routine.id,
|
|
110
136
|
MINION_ROUTINE_NAME: routine.name,
|
|
111
|
-
|
|
137
|
+
MINION_ROUTINE_WORKSPACE_ID: routine.workspace_id || '',
|
|
138
|
+
})
|
|
112
139
|
|
|
113
140
|
const logDir = path.dirname(logFile)
|
|
114
141
|
await fs.mkdir(logDir, { recursive: true })
|
package/win/workflow-runner.js
CHANGED
|
@@ -24,6 +24,7 @@ const executionStore = require('../core/stores/execution-store')
|
|
|
24
24
|
const workflowStore = require('../core/stores/workflow-store')
|
|
25
25
|
const logManager = require('../core/lib/log-manager')
|
|
26
26
|
const { expandSkillTemplates, restoreSkillTemplates } = require('../core/lib/template-expander')
|
|
27
|
+
const { buildSpawnEnv } = require('../core/lib/session-env')
|
|
27
28
|
const { getActivePrimary } = require('../core/llm-plugins/lib/active')
|
|
28
29
|
const os = require('os')
|
|
29
30
|
|
|
@@ -142,10 +143,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
142
143
|
console.log(`[WorkflowRunner] Log file: ${logFile}`)
|
|
143
144
|
console.log(`[WorkflowRunner] HOME: ${homeDir}`)
|
|
144
145
|
|
|
145
|
-
// Expand {{VAR}} templates in SKILL.md files with minion variables
|
|
146
|
+
// Expand {{VAR}} templates in SKILL.md files with minion variables effective
|
|
147
|
+
// for this workflow's workspace (minion-wide ∪ WS-scoped, WS wins).
|
|
146
148
|
let expandedOriginals = new Map()
|
|
147
149
|
try {
|
|
148
|
-
expandedOriginals = await expandSkillTemplates(skillNames)
|
|
150
|
+
expandedOriginals = await expandSkillTemplates(skillNames, {}, workflow.workspace_id || '')
|
|
149
151
|
if (expandedOriginals.size > 0) {
|
|
150
152
|
console.log(`[WorkflowRunner] Expanded templates in ${expandedOriginals.size} skill(s)`)
|
|
151
153
|
}
|
|
@@ -178,8 +180,9 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
178
180
|
throw new Error('No LLM configured. Set a Primary plugin via /api/llm/config or LLM_COMMAND in minion.env')
|
|
179
181
|
}
|
|
180
182
|
|
|
181
|
-
// PATH, HOME, USERPROFILE
|
|
182
|
-
//
|
|
183
|
+
// PATH, HOME, USERPROFILE are inherited from process.env (set at startup);
|
|
184
|
+
// workspace-scoped secrets are merged in via buildSpawnEnv() so this
|
|
185
|
+
// session only sees secrets relevant to its workspace context.
|
|
183
186
|
|
|
184
187
|
// Open log file for streaming writes
|
|
185
188
|
const logDir = path.dirname(logFile)
|
|
@@ -195,7 +198,11 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
195
198
|
cols: 200,
|
|
196
199
|
rows: 50,
|
|
197
200
|
cwd: homeDir,
|
|
198
|
-
env:
|
|
201
|
+
env: buildSpawnEnv(workflow.workspace_id || '', {
|
|
202
|
+
MINION_EXECUTION_ID: executionId,
|
|
203
|
+
MINION_WORKFLOW_ID: workflow.id || '',
|
|
204
|
+
MINION_WORKFLOW_WORKSPACE_ID: workflow.workspace_id || '',
|
|
205
|
+
}),
|
|
199
206
|
})
|
|
200
207
|
|
|
201
208
|
// Track session
|