@geekbeer/minion 3.17.0 → 3.23.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.
@@ -40,12 +40,13 @@ async function todoRoutes(fastify) {
40
40
  return { success: false, error: 'Unauthorized' }
41
41
  }
42
42
 
43
- const { status, priority, project_id, source_type, limit } = request.query || {}
43
+ const { status, priority, project_id, source_type, session_id, limit } = request.query || {}
44
44
  const todos = todoStore.list({
45
45
  status,
46
46
  priority,
47
47
  project_id,
48
48
  source_type,
49
+ session_id,
49
50
  limit: limit ? parseInt(limit, 10) : undefined,
50
51
  })
51
52
 
@@ -75,14 +76,14 @@ async function todoRoutes(fastify) {
75
76
  return { success: false, error: 'Unauthorized' }
76
77
  }
77
78
 
78
- const { title, description, priority, source_type, source_id, project_id, due_at, data } = request.body || {}
79
+ const { title, description, priority, source_type, source_id, project_id, due_at, data, session_id } = request.body || {}
79
80
  if (!title) {
80
81
  reply.code(400)
81
82
  return { success: false, error: 'title is required' }
82
83
  }
83
84
 
84
85
  try {
85
- const todo = todoStore.add({ title, description, priority, source_type, source_id, project_id, due_at, data })
86
+ const todo = todoStore.add({ title, description, priority, source_type, source_id, project_id, due_at, data, session_id })
86
87
  console.log(`[Todos] Created: ${todo.id} "${todo.title}"`)
87
88
  reply.code(201)
88
89
  return { success: true, todo }
@@ -19,6 +19,11 @@ const VALID_SOURCE_TYPES = ['thread', 'workflow', 'directive', 'user', 'self']
19
19
  // Auto-prune completed todos older than this
20
20
  const PRUNE_DAYS = 30
21
21
 
22
+ // Max number of times an incomplete todo gets auto-injected into the chat
23
+ // prompt before we give up and stop injecting it (to avoid infinite loops
24
+ // when Claude cannot finish a task).
25
+ const MAX_INJECTION_COUNT = 5
26
+
22
27
  /**
23
28
  * Create a new TODO.
24
29
  * @param {object} todo - { title, description?, priority?, source_type?, source_id?, project_id?, due_at?, data? }
@@ -50,12 +55,14 @@ function add(todo) {
50
55
  created_at: now,
51
56
  updated_at: now,
52
57
  completed_at: null,
58
+ session_id: todo.session_id || null,
59
+ injection_count: 0,
53
60
  ...(todo.data ? { data: todo.data } : {}),
54
61
  }
55
62
 
56
63
  db.prepare(`
57
- INSERT INTO todos (id, title, description, status, priority, source_type, source_id, project_id, due_at, created_at, updated_at, completed_at, data)
58
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
64
+ INSERT INTO todos (id, title, description, status, priority, source_type, source_id, project_id, due_at, created_at, updated_at, completed_at, data, session_id, injection_count)
65
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
59
66
  `).run(
60
67
  record.id,
61
68
  record.title,
@@ -69,7 +76,9 @@ function add(todo) {
69
76
  record.created_at,
70
77
  record.updated_at,
71
78
  record.completed_at,
72
- record.data ? JSON.stringify(record.data) : null
79
+ record.data ? JSON.stringify(record.data) : null,
80
+ record.session_id,
81
+ record.injection_count
73
82
  )
74
83
 
75
84
  console.log(`[TodoStore] Added: "${record.title}" (${record.priority}, source=${record.source_type || 'none'})`)
@@ -115,7 +124,8 @@ function update(id, updates) {
115
124
  db.prepare(`
116
125
  UPDATE todos SET title = ?, description = ?, status = ?, priority = ?,
117
126
  source_type = ?, source_id = ?, project_id = ?, due_at = ?,
118
- updated_at = ?, completed_at = ?, data = ?
127
+ updated_at = ?, completed_at = ?, data = ?,
128
+ session_id = ?, injection_count = ?
119
129
  WHERE id = ?
120
130
  `).run(
121
131
  merged.title,
@@ -129,6 +139,8 @@ function update(id, updates) {
129
139
  merged.updated_at,
130
140
  merged.completed_at,
131
141
  merged.data ? JSON.stringify(merged.data) : null,
142
+ merged.session_id ?? null,
143
+ merged.injection_count ?? 0,
132
144
  id
133
145
  )
134
146
 
@@ -160,9 +172,50 @@ function getById(id) {
160
172
  return row ? parseRow(row) : null
161
173
  }
162
174
 
175
+ /**
176
+ * List incomplete (pending/in_progress) todos linked to a chat session that
177
+ * still have remaining injection budget. Used by the chat route to remind
178
+ * Claude of unfinished work even after context compaction.
179
+ * @param {string} sessionId
180
+ * @returns {Array}
181
+ */
182
+ function listActiveForSession(sessionId) {
183
+ if (!sessionId) return []
184
+ const db = getDb()
185
+ const rows = db.prepare(`
186
+ SELECT * FROM todos
187
+ WHERE session_id = ?
188
+ AND status IN ('pending', 'in_progress')
189
+ AND injection_count < ?
190
+ ORDER BY
191
+ CASE priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 WHEN 'normal' THEN 2 WHEN 'low' THEN 3 END,
192
+ created_at ASC
193
+ `).all(sessionId, MAX_INJECTION_COUNT)
194
+ return rows.map(parseRow)
195
+ }
196
+
197
+ /**
198
+ * Increment injection_count for the given todo IDs. Should be called once
199
+ * the reminder has been actually written into the outgoing prompt.
200
+ * @param {string[]} ids
201
+ */
202
+ function markInjected(ids) {
203
+ if (!Array.isArray(ids) || ids.length === 0) return
204
+ const db = getDb()
205
+ const now = new Date().toISOString()
206
+ const stmt = db.prepare(`
207
+ UPDATE todos
208
+ SET injection_count = injection_count + 1, updated_at = ?
209
+ WHERE id = ?
210
+ `)
211
+ for (const id of ids) {
212
+ stmt.run(now, id)
213
+ }
214
+ }
215
+
163
216
  /**
164
217
  * List TODOs with optional filters.
165
- * @param {object} opts - { status?, priority?, project_id?, source_type?, limit? }
218
+ * @param {object} opts - { status?, priority?, project_id?, source_type?, session_id?, limit? }
166
219
  * @returns {Array}
167
220
  */
168
221
  function list(opts = {}) {
@@ -186,6 +239,10 @@ function list(opts = {}) {
186
239
  conditions.push('source_type = ?')
187
240
  params.push(opts.source_type)
188
241
  }
242
+ if (opts.session_id) {
243
+ conditions.push('session_id = ?')
244
+ params.push(opts.session_id)
245
+ }
189
246
 
190
247
  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
191
248
  const limit = opts.limit || 100
@@ -260,6 +317,9 @@ module.exports = {
260
317
  remove,
261
318
  getById,
262
319
  list,
320
+ listActiveForSession,
321
+ markInjected,
263
322
  getSummary,
264
323
  pruneCompleted,
324
+ MAX_INJECTION_COUNT,
265
325
  }
@@ -229,6 +229,38 @@ curl -X PUT /api/config/env \
229
229
  The scheduler starts automatically on server boot if `REFLECTION_TIME` is configured.
230
230
  Changes via the config API take effect immediately (no restart required).
231
231
 
232
+ ### Todos
233
+
234
+ ミニオンローカルのToDoリスト。SQLiteに永続化され、HQにも同期される。
235
+
236
+ | Method | Endpoint | Description |
237
+ |--------|----------|-------------|
238
+ | GET | `/api/todos` | List todos. Query: `status`, `priority`, `project_id`, `source_type`, `session_id`, `limit` |
239
+ | GET | `/api/todos/summary` | Status counts |
240
+ | GET | `/api/todos/:id` | Get single todo |
241
+ | POST | `/api/todos` | Create. Body: `{title, description?, priority?, source_type?, source_id?, project_id?, due_at?, session_id?, data?}` |
242
+ | PUT | `/api/todos/:id` | Update any field including `status` |
243
+ | DELETE | `/api/todos/:id` | Delete |
244
+
245
+ **フィールド**:
246
+ - `status`: `pending` / `in_progress` / `done` / `cancelled`
247
+ - `priority`: `low` / `normal` / `high` / `urgent`
248
+ - `session_id`: チャットセッションID(任意)。**設定すると圧縮を跨いだ自動再掲の対象になる** — 次ターン以降のプロンプト冒頭で未完了Todoが自動表示される。
249
+ - `injection_count`: 自動再掲された回数(読み取り専用)。一定回数を超えたTodoは再掲が停止する。
250
+
251
+ **セッション紐づけ例**:
252
+ ```bash
253
+ # 作成時にsession_idを指定すると、このセッションのチャットに自動で再掲される
254
+ curl -X POST -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
255
+ http://localhost:8080/api/todos \
256
+ -d '{"title": "レポートを保存", "session_id": "'$SESSION_ID'", "priority": "high"}'
257
+
258
+ # 完了マーク(即座に更新すること)
259
+ curl -X PUT -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" \
260
+ http://localhost:8080/api/todos/$TODO_ID \
261
+ -d '{"status": "done"}'
262
+ ```
263
+
232
264
  ### Config
233
265
 
234
266
  | Method | Endpoint | Description |
@@ -240,6 +272,42 @@ Changes via the config API take effect immediately (no restart required).
240
272
 
241
273
  Allowed keys: `LLM_COMMAND`, `REFLECTION_TIME`
242
274
 
275
+ ### LLM Plugins (opt-in)
276
+
277
+ プラグイン方式の LLM 設定。`primary` を設定すると有効化される。未設定の場合は従来の `LLM_COMMAND` 経路で動作する。設定は `~/minion/llm/config.json` にファイルとして保存される (env var に依存しないため quote 破損バグの影響を受けない)。
278
+
279
+ | Method | Endpoint | Description |
280
+ |--------|----------|-------------|
281
+ | GET | `/api/llm/plugins` | List available plugins with capabilities & auth status |
282
+ | GET | `/api/llm/config` | Get current primary/enabled selection |
283
+ | PUT | `/api/llm/config` | Update primary/enabled. Body: `{primary?, enabled?}` |
284
+
285
+ **GET /api/llm/plugins response**:
286
+ ```json
287
+ {
288
+ "success": true,
289
+ "plugins": [
290
+ {
291
+ "name": "claude",
292
+ "displayName": "Claude Code",
293
+ "capabilities": {
294
+ "toolUse": true,
295
+ "streaming": true,
296
+ "vision": true,
297
+ "imageGeneration": false,
298
+ "sessionResume": true
299
+ },
300
+ "authenticated": true,
301
+ "builtin": true,
302
+ "enabled": true,
303
+ "primary": true
304
+ }
305
+ ]
306
+ }
307
+ ```
308
+
309
+ **PUT /api/llm/config** body: `{ "primary": "claude", "enabled": ["claude", "gemini", "codex"] }`
310
+
243
311
  ### Permissions
244
312
 
245
313
  CLIツール(Claude Code, Gemini CLI, Codex CLI)のパーミッション管理。
@@ -853,6 +921,408 @@ POST `/api/minion/execution` body:
853
921
  }
854
922
  ```
855
923
 
924
+ ### Pending Revisions (PM のみ)
925
+
926
+ レビューで `revision_requested` になったステップを検知する。ミニオンの `revision-watcher` デーモンが30秒ごとにポーリングし、LLM で差し戻し先ステップを判断した上で `/api/minion/revision-reset` を呼び出す。
927
+
928
+ | Method | Endpoint | Description |
929
+ |--------|----------|-------------|
930
+ | GET | `/api/minion/pending-revisions` | `revision_requested` 状態のステップ一覧(PMのプロジェクトのみ) |
931
+ | POST | `/api/minion/revision-reset` | 差し戻し対象ステップを `pending` に戻す |
932
+
933
+ GET Response:
934
+ ```json
935
+ {
936
+ "revisions": [
937
+ {
938
+ "execution_id": "uuid",
939
+ "workflow_name": "daily-check",
940
+ "revision_step_index": 2,
941
+ "review_comment": "string (レビュアーのフィードバック)",
942
+ "pipeline": [
943
+ {
944
+ "step_index": 0,
945
+ "skill_version_id": "uuid",
946
+ "skill_name": "string|null",
947
+ "assigned_role": "pm|engineer|accountant"
948
+ }
949
+ ]
950
+ }
951
+ ]
952
+ }
953
+ ```
954
+
955
+ POST `/api/minion/revision-reset` body:
956
+ ```json
957
+ {
958
+ "execution_id": "uuid",
959
+ "target_step_index": 0,
960
+ "revision_step_index": 2,
961
+ "revision_feedback": "string (省略可、target step の review_comment に保存)"
962
+ }
963
+ ```
964
+
965
+ - `target_step_index` は差し戻し先(やり直し起点)のインデックス
966
+ - `revision_step_index` は差し戻しを要求されたレビューステップのインデックス(`target_step_index <= revision_step_index`)
967
+ - target から revision までのステップが `pending` に戻り、`revision_feedback` が再実行時のコンテキストとして注入される
968
+
969
+ Response:
970
+ ```json
971
+ {
972
+ "success": true,
973
+ "reset_count": 3,
974
+ "target_step_index": 0,
975
+ "revision_step_index": 2
976
+ }
977
+ ```
978
+
979
+ ---
980
+
981
+ ## DAG Workflows (HQ, ノード/エッジ方式)
982
+
983
+ DAG ワークフローは従来の線形パイプラインを拡張した有向非巡回グラフ方式のワークフローです。HQダッシュボードの DAG エディタで作成・編集できるほか、ミニオンAPI経由でも JSON ベースで編集可能(PMロールのみ)。ミニオンはプルベースのポーリングで pending ノードを検出・実行します。
984
+
985
+ ### 編集エンドポイント (PMロール限定)
986
+
987
+ | Method | Endpoint | Description |
988
+ |--------|----------|-------------|
989
+ | POST | `/api/minion/dag-workflows` | 新規 DAG ワークフローを作成(ドラフトとして保存) |
990
+ | PUT | `/api/minion/dag-workflows/:id` | ドラフト graph / メタデータを更新 |
991
+ | POST | `/api/minion/dag-workflows/:id/publish` | ドラフトをバリデーションし新バージョンとして公開 |
992
+
993
+ いずれも Bearer 認証 + プロジェクトメンバーシップが `role='pm'` のミニオンに限定。非PMは 403。PUT / POST はドラフト保存時にセマンティックバリデーションを行わない(`graph` が object で `nodes` / `edges` 配列を持つことの構造チェックのみ)。パブリッシュ時には `validateDagGraph` によるフル検証(ノードID重複・エッジの参照整合性・サイクル検出・必須フィールド等)が走る。
994
+
995
+ **推奨ワークフロー**: create → put で graph を整える → publish、または既存ワークフローに対して put → publish。
996
+
997
+ POST `/api/minion/dag-workflows` body:
998
+ ```json
999
+ {
1000
+ "project_id": "uuid",
1001
+ "name": "my-dag",
1002
+ "graph": { "nodes": [], "edges": [] },
1003
+ "content": "Markdown description (optional)",
1004
+ "change_summary": "initial draft (optional)"
1005
+ }
1006
+ ```
1007
+
1008
+ - `name` は `/^[a-z0-9-]+$/`
1009
+ - `graph` は省略可。省略時はドラフト無しで作成される
1010
+ - レスポンスは作成された DAG ワークフロー全体(`current_version_id` は null のまま、ドラフトに保存)
1011
+
1012
+ PUT `/api/minion/dag-workflows/:id` body(全フィールド optional、省略したものは変更しない):
1013
+ ```json
1014
+ {
1015
+ "graph": { "nodes": [...], "edges": [...] },
1016
+ "content": "...",
1017
+ "change_summary": "Updated fan-out branch",
1018
+ "name": "renamed-dag",
1019
+ "is_active": true,
1020
+ "maturity": "preview|stable|..."
1021
+ }
1022
+ ```
1023
+
1024
+ - `graph` を渡すと draft_graph が上書きされる(構造チェックのみ、セマンティックチェック無し)
1025
+ - `content` / `change_summary` は `graph` と併せて draft スロットに保存される
1026
+
1027
+ POST `/api/minion/dag-workflows/:id/publish` (body なし):
1028
+ - 現在の draft_graph を `validateDagGraph` でフル検証
1029
+ - OK なら新バージョン行を `dag_workflow_versions` に追加し、`current_version_id` を更新、ドラフトをクリア
1030
+ - NG なら 400 で `{ error, details: [...] }` を返却
1031
+
1032
+ ### ミニオン CLI ラッパー
1033
+
1034
+ 以下の `hq` サブコマンドが上記エンドポイントを呼び出す。いずれもリクエスト送信前にローカルで JSON 構文検証を行う。
1035
+
1036
+ ```bash
1037
+ hq create dag-workflow <body.json> # POST /api/minion/dag-workflows
1038
+ hq put dag-workflow <id> <body.json> # PUT /api/minion/dag-workflows/:id
1039
+ hq publish dag-workflow <id> # POST /api/minion/dag-workflows/:id/publish
1040
+ hq fetch dag-workflow <id> # GET /api/minion/dag-workflows/:id
1041
+ hq fetch dag-execution <id> # GET /api/minion/dag-executions/:id
1042
+ ```
1043
+
1044
+ ### DAG Read Endpoints (チャットコンテキスト用)
1045
+
1046
+ ユーザーがHQダッシュボードでDAGエディタ/実行詳細を閲覧中にチャットした場合、チャットコンテキストに `context.type = 'dag-workflow'` または `'dag-execution'` が注入される。以下の `hq` CLI コマンドで詳細を取得できる(内部的に `/api/minion/dag-workflows/:id` / `/api/minion/dag-executions/:id` を叩く)。
1047
+
1048
+ | コマンド | 対応エンドポイント | 用途 |
1049
+ |---------|-------------------|------|
1050
+ | `hq fetch dag-workflow <id>` | GET `/api/minion/dag-workflows/:id` | DAG ワークフロー定義(published graph + draft)取得 |
1051
+ | `hq fetch dag-execution <id>` | GET `/api/minion/dag-executions/:id` | DAG 実行詳細(graph_snapshot + node_executions)取得 |
1052
+
1053
+ いずれもミニオンのプロジェクトメンバーシップでスコープされる。ミニオンが参加していないプロジェクトのDAGは 404。
1054
+
1055
+ GET `/api/minion/dag-workflows/:id` Response:
1056
+ ```json
1057
+ {
1058
+ "id": "uuid",
1059
+ "project_id": "uuid",
1060
+ "name": "my-dag",
1061
+ "is_active": true,
1062
+ "maturity": "draft|stable|...",
1063
+ "current_version_id": "uuid",
1064
+ "draft_graph": { "nodes": [...], "edges": [...] } ,
1065
+ "draft_content": "markdown|null",
1066
+ "draft_change_summary": "string|null",
1067
+ "draft_updated_at": "ISO|null",
1068
+ "current_version": {
1069
+ "id": "uuid",
1070
+ "version": 3,
1071
+ "graph": { "nodes": [...], "edges": [...] },
1072
+ "content": "markdown",
1073
+ "change_summary": "string",
1074
+ "created_at": "ISO"
1075
+ },
1076
+ "my_role": "pm|engineer|accountant|null"
1077
+ }
1078
+ ```
1079
+
1080
+ GET `/api/minion/dag-executions/:id` Response:
1081
+ ```json
1082
+ {
1083
+ "id": "uuid",
1084
+ "dag_workflow_id": "uuid",
1085
+ "dag_workflow_name": "my-dag",
1086
+ "dag_workflow_version_id": "uuid",
1087
+ "dag_workflow_version": 3,
1088
+ "project_id": "uuid",
1089
+ "status": "pending|running|completed|failed",
1090
+ "graph_snapshot": { "nodes": [...], "edges": [...] },
1091
+ "started_at": "ISO|null",
1092
+ "completed_at": "ISO|null",
1093
+ "created_at": "ISO",
1094
+ "node_executions": [
1095
+ {
1096
+ "id": "uuid",
1097
+ "node_id": "n2",
1098
+ "scope_path": "",
1099
+ "status": "pending|waiting|running|completed|failed|skipped",
1100
+ "outcome": "success|failure|null",
1101
+ "input_data": {},
1102
+ "output_data": {},
1103
+ "output_summary": "string|null",
1104
+ "requires_review": false,
1105
+ "review_status": "review_pending|approved|rejected|revision_requested|null",
1106
+ "revision_count": 0,
1107
+ "started_at": "ISO|null",
1108
+ "completed_at": "ISO|null"
1109
+ }
1110
+ ]
1111
+ }
1112
+ ```
1113
+
1114
+ ### DAG Runtime Endpoints (ミニオン向け)
1115
+
1116
+ | Method | Endpoint | Description |
1117
+ |--------|----------|-------------|
1118
+ | GET | `/api/dag/minion/pending-nodes` | 自分が実行すべき pending ノード一覧(ロール一致・依存解決済み) |
1119
+ | POST | `/api/dag/minion/claim-node` | ノードを楽観ロックで取得(排他実行) |
1120
+ | POST | `/api/dag/minion/node-complete` | ノード完了を報告し、下流ノードへカスケード |
1121
+
1122
+ ミニオンの `dag-step-poller` デーモンが30秒ごとに `pending-nodes` を叩き、最大2並列で claim → skill/transform 実行 → node-complete の流れを回す。`skill`・`transform` 以外のノード(start/end/review/fan_out/join/conditional)はHQ内部のカスケードエンジンが処理するため、ミニオンには返されない。
1123
+
1124
+ #### GET /api/dag/minion/pending-nodes
1125
+
1126
+ Response:
1127
+ ```json
1128
+ {
1129
+ "nodes": [
1130
+ {
1131
+ "node_execution_id": "uuid",
1132
+ "execution_id": "uuid",
1133
+ "dag_workflow_name": "my-dag",
1134
+ "node_id": "node-abc",
1135
+ "scope_path": "",
1136
+ "node_type": "skill",
1137
+ "skill_version_id": "uuid|null",
1138
+ "skill_name": "skill-1|null",
1139
+ "assigned_role": "pm|engineer|accountant",
1140
+ "input_data": { "...": "..." },
1141
+ "revision_feedback": "string|null",
1142
+ "transform_instruction": "string|null"
1143
+ }
1144
+ ]
1145
+ }
1146
+ ```
1147
+
1148
+ 返却条件:
1149
+ - `assigned_role` がミニオンのプロジェクトロールと一致
1150
+ - ノードの `status` が `pending`
1151
+ - `node_type` が `skill` または `transform`
1152
+ - スコープ内の全依存ノードが `completed`(`requires_review` の場合は `approved` も必要)
1153
+
1154
+ `scope_path` は fan-out 内のインスタンスを示す(ルートは空文字列、1段のfan-outでは `fan_out_A:0`、ネストでは `fan_out_A:2/fan_out_B:1`)。依存解決は同じ scope_path 内で閉じる。
1155
+
1156
+ #### POST /api/dag/minion/claim-node
1157
+
1158
+ Body:
1159
+ ```json
1160
+ { "node_execution_id": "uuid" }
1161
+ ```
1162
+
1163
+ Response (成功 / 201):
1164
+ ```json
1165
+ { "success": true, "node_execution_id": "uuid" }
1166
+ ```
1167
+
1168
+ Response (競合 / 409): 他ミニオンが既に claim 済み、または状態が pending でない場合。
1169
+ ```json
1170
+ { "error": "Node already claimed or not pending" }
1171
+ ```
1172
+
1173
+ 409 を受け取った場合は `pending-nodes` を取り直して次のノードに進むこと。
1174
+
1175
+ #### POST /api/dag/minion/node-complete
1176
+
1177
+ Body:
1178
+ ```json
1179
+ {
1180
+ "node_execution_id": "uuid",
1181
+ "status": "completed|failed",
1182
+ "output_data": { "...": "..." },
1183
+ "output_summary": "string (省略可、レポート/サマリー)"
1184
+ }
1185
+ ```
1186
+
1187
+ - `status: completed` で `requires_review` なノードはサーバ側で `review_status=review_pending` になりカスケードは停止(レビュー承認まで下流は生成されない)
1188
+ - `status: failed` でもカスケードは走る(fan-out join が `on_failure=ignore|collect` で集約できるため)
1189
+ - `output_data` は下流ノードの `input_data` に伝播する。**スキル実行時はスキル本文の「## Output Data」セクションの JSON コードブロックを抽出して `output_data` に載せる規約**(ミニオンの `dag-node-executor` がこの抽出を行う)
1190
+
1191
+ Response:
1192
+ ```json
1193
+ { "success": true }
1194
+ ```
1195
+
1196
+ レビュー待ち状態の場合:
1197
+ ```json
1198
+ { "success": true, "review_pending": true }
1199
+ ```
1200
+
1201
+ ### DAG Graph Structure
1202
+
1203
+ DAG ワークフローの graph は以下の構造で保存される(`dag_workflow_versions.graph` / `dag_executions.graph_snapshot`):
1204
+
1205
+ ```json
1206
+ {
1207
+ "nodes": [
1208
+ { "id": "n1", "type": "start", "label": "Start", "position": { "x": 0, "y": 0 } },
1209
+ {
1210
+ "id": "n2",
1211
+ "type": "skill",
1212
+ "label": "Fetch data",
1213
+ "skill_version_id": "uuid",
1214
+ "assigned_role": "engineer"
1215
+ },
1216
+ {
1217
+ "id": "n3",
1218
+ "type": "fan_out",
1219
+ "label": "Per item",
1220
+ "fan_out_source": ".items",
1221
+ "template": { "nodes": [ ... ], "edges": [ ... ] },
1222
+ "join_mode": "all",
1223
+ "on_failure": "collect",
1224
+ "max_concurrency": 3
1225
+ },
1226
+ {
1227
+ "id": "n4",
1228
+ "type": "join",
1229
+ "label": "Collect",
1230
+ "join_mode": "all",
1231
+ "aggregation": "array"
1232
+ },
1233
+ { "id": "n5", "type": "end", "label": "End" }
1234
+ ],
1235
+ "edges": [
1236
+ { "id": "e1", "source": "n1", "target": "n2", "kind": "normal" },
1237
+ { "id": "e2", "source": "n2", "target": "n3" },
1238
+ { "id": "e3", "source": "n3", "target": "n4" },
1239
+ { "id": "e4", "source": "n4", "target": "n5" }
1240
+ ]
1241
+ }
1242
+ ```
1243
+
1244
+ #### Node Types
1245
+
1246
+ | `type` | 役割 | ミニオン実行 |
1247
+ |--------|------|---------------|
1248
+ | `start` | エントリポイント | ❌ (内部) |
1249
+ | `end` | 終端 | ❌ (内部) |
1250
+ | `skill` | スキル実行。`skill_version_id` と `assigned_role` が必須 | ✅ |
1251
+ | `transform` | LLM によるデータ変換。`transform_instruction` が必須 | ✅ |
1252
+ | `review` | レビューゲート。`approved` / `revision_requested` で分岐 | ❌ (内部) |
1253
+ | `fan_out` | 配列入力をテンプレートsub-graphに展開して並列実行。子が全て settle すると自ノードが completed に遷移 | ❌ (内部) |
1254
+ | `join` | N本の上流エッジを待ち合わせる汎用バリア。fan_out とは独立 | ❌ (内部) |
1255
+ | `conditional` | 条件分岐(`llm` / `regex` / `jq`) | ❌ (内部) |
1256
+
1257
+ #### DagNode 主要フィールド
1258
+
1259
+ | Field | Type | 説明 |
1260
+ |-------|------|------|
1261
+ | `id` | string | グラフ内ユニークなノードID |
1262
+ | `type` | DagNodeType | 上記ノード種別 |
1263
+ | `label` | string | 表示名 |
1264
+ | `skill_version_id` | string? | skill ノードで使用するスキルバージョンUUID |
1265
+ | `assigned_role` | `pm`\|`engineer`\|`accountant` | 実行ロール |
1266
+ | `fan_out_source` | string? | fan_out: 入力から配列を取り出すドット記法(例 `.items`) |
1267
+ | `template` | DagGraph? | fan_out: 各要素ごとに展開するsub-graph |
1268
+ | `max_concurrency` | number? | fan_out: 並列インスタンス上限 |
1269
+ | `join_mode` | `all`\|`any`\|`majority` | fan_out / join: 完了判定 |
1270
+ | `on_failure` | `fail_all`\|`ignore`\|`collect` | fan_out / join: 失敗時の挙動 |
1271
+ | `aggregation` | `array`\|`merge` | join: 上流出力の束ね方(デフォルト `array`) |
1272
+ | `condition_type` | `llm`\|`regex`\|`jq` | conditional: 条件評価方式 |
1273
+ | `condition_expression` | string | conditional: 条件式 |
1274
+ | `branches` | Record<string,string> | conditional: 条件出力→遷移先ノードID |
1275
+ | `default_branch` | string? | conditional: マッチしなかった場合の遷移先 |
1276
+ | `transform_instruction` | string | transform: LLMへの変換指示 |
1277
+ | `review` | object | review: レビュー設定 |
1278
+
1279
+ #### DagEdge
1280
+
1281
+ | Field | Type | 説明 |
1282
+ |-------|------|------|
1283
+ | `id` | string | エッジID |
1284
+ | `source` | string | 起点ノードID |
1285
+ | `target` | string | 終点ノードID |
1286
+ | `kind` | `normal`\|`approved`\|`revision` | `normal`=通常、`approved`=review承認時、`revision`=review差し戻し時(サイクル検出では無視される) |
1287
+ | `condition_label` | string? | 表示ラベル |
1288
+
1289
+ ### DAG Execution API (読み取り)
1290
+
1291
+ | Method | Endpoint | Auth | 用途 |
1292
+ |--------|----------|------|------|
1293
+ | GET | `/api/dag/executions/:execId` | セッション | 実行詳細 (graph_snapshot + node_executions) 取得。UI用、ミニオンは通常使わない |
1294
+
1295
+ Response:
1296
+ ```json
1297
+ {
1298
+ "id": "uuid",
1299
+ "dag_workflow_id": "uuid",
1300
+ "dag_workflow_name": "my-dag",
1301
+ "dag_workflow_version_id": "uuid",
1302
+ "status": "pending|running|completed|failed",
1303
+ "graph_snapshot": { "nodes": [...], "edges": [...] },
1304
+ "started_at": "ISO",
1305
+ "completed_at": "ISO|null",
1306
+ "node_executions": [
1307
+ {
1308
+ "id": "uuid",
1309
+ "node_id": "n2",
1310
+ "scope_path": "",
1311
+ "status": "pending|waiting|running|completed|failed|skipped",
1312
+ "outcome": "success|failure|null",
1313
+ "input_data": {},
1314
+ "output_data": {},
1315
+ "output_summary": "string|null",
1316
+ "requires_review": false,
1317
+ "review_status": "review_pending|approved|rejected|revision_requested|null",
1318
+ "revision_count": 0,
1319
+ "started_at": "ISO|null",
1320
+ "completed_at": "ISO|null"
1321
+ }
1322
+ ]
1323
+ }
1324
+ ```
1325
+
856
1326
  ### Issue Reporting (GitHub Issue 起票)
857
1327
 
858
1328
  | Method | Endpoint | Description |