@geekbeer/minion 3.29.1 → 3.30.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/lib/dag-step-poller.js +30 -6
- package/core/routes/skills.js +6 -0
- package/docs/api-reference.md +77 -2
- package/docs/task-guides.md +3 -0
- package/linux/workflow-runner.js +32 -1
- package/package.json +1 -1
- package/win/workflow-runner.js +32 -1
|
@@ -126,6 +126,8 @@ async function executeSkillNode(node) {
|
|
|
126
126
|
input_data,
|
|
127
127
|
revision_feedback,
|
|
128
128
|
review_history,
|
|
129
|
+
input_contracts,
|
|
130
|
+
output_contracts,
|
|
129
131
|
} = node
|
|
130
132
|
|
|
131
133
|
console.log(
|
|
@@ -174,16 +176,18 @@ async function executeSkillNode(node) {
|
|
|
174
176
|
}
|
|
175
177
|
}
|
|
176
178
|
|
|
177
|
-
// 4. Run the skill with input_data context
|
|
179
|
+
// 4. Run the skill with input_data and contract context
|
|
178
180
|
const runPayload = {
|
|
179
181
|
skill_name: skillName,
|
|
180
182
|
execution_id,
|
|
181
183
|
workflow_name: dag_workflow_name,
|
|
182
184
|
role: assigned_role,
|
|
183
|
-
// DAG-specific: inject input_data as context
|
|
185
|
+
// DAG-specific: inject input_data and contracts as context
|
|
184
186
|
dag_node_id: node_id,
|
|
185
187
|
dag_input_data: input_data,
|
|
186
188
|
dag_node_execution_id: node_execution_id,
|
|
189
|
+
dag_input_contracts: input_contracts || null,
|
|
190
|
+
dag_output_contracts: output_contracts || null,
|
|
187
191
|
}
|
|
188
192
|
if (revision_feedback) {
|
|
189
193
|
runPayload.revision_feedback = revision_feedback
|
|
@@ -251,6 +255,7 @@ async function executeTransformNode(node) {
|
|
|
251
255
|
assigned_role,
|
|
252
256
|
input_data,
|
|
253
257
|
transform_instruction,
|
|
258
|
+
output_contracts,
|
|
254
259
|
} = node
|
|
255
260
|
|
|
256
261
|
console.log(
|
|
@@ -285,7 +290,7 @@ async function executeTransformNode(node) {
|
|
|
285
290
|
|
|
286
291
|
// 2. Create ephemeral skill from transform_instruction.
|
|
287
292
|
// Write to every active plugin's skill dir so any Primary can find it.
|
|
288
|
-
const skillContent = buildTransformSkillContent(transform_instruction, input_data)
|
|
293
|
+
const skillContent = buildTransformSkillContent(transform_instruction, input_data, output_contracts)
|
|
289
294
|
for (const dir of ephemeralSkillDirs) {
|
|
290
295
|
await fs.mkdir(dir, { recursive: true })
|
|
291
296
|
await fs.writeFile(path.join(dir, 'SKILL.md'), skillContent, 'utf-8')
|
|
@@ -353,8 +358,8 @@ async function executeTransformNode(node) {
|
|
|
353
358
|
/**
|
|
354
359
|
* Build SKILL.md content for a transform node's ephemeral skill.
|
|
355
360
|
*/
|
|
356
|
-
function buildTransformSkillContent(instruction, inputData) {
|
|
357
|
-
|
|
361
|
+
function buildTransformSkillContent(instruction, inputData, outputContracts) {
|
|
362
|
+
const lines = [
|
|
358
363
|
'---',
|
|
359
364
|
'name: dag-transform',
|
|
360
365
|
'description: DAG Transform Node',
|
|
@@ -369,12 +374,31 @@ function buildTransformSkillContent(instruction, inputData) {
|
|
|
369
374
|
'',
|
|
370
375
|
'## Transform Instruction',
|
|
371
376
|
instruction,
|
|
377
|
+
]
|
|
378
|
+
|
|
379
|
+
if (outputContracts && outputContracts.length > 0) {
|
|
380
|
+
lines.push('', '## Output Contract')
|
|
381
|
+
lines.push('Your output MUST conform to the following contract(s):')
|
|
382
|
+
for (const oc of outputContracts) {
|
|
383
|
+
lines.push(`### ${oc.contract_name}`)
|
|
384
|
+
lines.push(oc.contract.description || '')
|
|
385
|
+
lines.push('| Field | Type | Required | Description |')
|
|
386
|
+
lines.push('|-------|------|----------|-------------|')
|
|
387
|
+
for (const f of oc.contract.fields || []) {
|
|
388
|
+
lines.push(`| ${f.key} | ${f.type} | ${f.required ? 'Yes' : 'No'} | ${f.description || ''} |`)
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
lines.push(
|
|
372
394
|
'',
|
|
373
395
|
'## Task',
|
|
374
396
|
'Apply the transform instruction to the input data above.',
|
|
375
397
|
'Output the result as a JSON object in an "## Output Data" section with a json code block.',
|
|
376
398
|
'Do NOT output anything other than the Output Data section.',
|
|
377
|
-
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
return lines.join('\n')
|
|
378
402
|
}
|
|
379
403
|
|
|
380
404
|
/**
|
package/core/routes/skills.js
CHANGED
|
@@ -344,6 +344,9 @@ async function skillRoutes(fastify, opts) {
|
|
|
344
344
|
revision_feedback,
|
|
345
345
|
review_history,
|
|
346
346
|
dag_node_execution_id,
|
|
347
|
+
dag_input_data,
|
|
348
|
+
dag_input_contracts,
|
|
349
|
+
dag_output_contracts,
|
|
347
350
|
} = request.body || {}
|
|
348
351
|
|
|
349
352
|
if (!skill_name) {
|
|
@@ -393,6 +396,9 @@ async function skillRoutes(fastify, opts) {
|
|
|
393
396
|
if (role) runOptions.role = role
|
|
394
397
|
if (revision_feedback) runOptions.revisionFeedback = revision_feedback
|
|
395
398
|
if (review_history && review_history.length > 0) runOptions.reviewHistory = review_history
|
|
399
|
+
if (dag_input_data) runOptions.dagInputData = dag_input_data
|
|
400
|
+
if (dag_input_contracts) runOptions.dagInputContracts = dag_input_contracts
|
|
401
|
+
if (dag_output_contracts) runOptions.dagOutputContracts = dag_output_contracts
|
|
396
402
|
|
|
397
403
|
// Run asynchronously — respond immediately
|
|
398
404
|
const executionPromise = (async () => {
|
package/docs/api-reference.md
CHANGED
|
@@ -1066,8 +1066,12 @@ POST `/api/minion/dag-workflows/:id/publish` (body なし):
|
|
|
1066
1066
|
| PATCH | `/api/minion/dag-workflows/:id/nodes/:nodeId` | ノードのプロパティを更新 |
|
|
1067
1067
|
| DELETE | `/api/minion/dag-workflows/:id/nodes/:nodeId` | ノードを削除(自動再接続対応) |
|
|
1068
1068
|
| POST | `/api/minion/dag-workflows/:id/edges` | エッジを追加 |
|
|
1069
|
-
| PATCH | `/api/minion/dag-workflows/:id/edges/:edgeId` | エッジの kind / condition_label を更新 |
|
|
1069
|
+
| PATCH | `/api/minion/dag-workflows/:id/edges/:edgeId` | エッジの kind / condition_label / contract を更新 |
|
|
1070
1070
|
| DELETE | `/api/minion/dag-workflows/:id/edges/:edgeId` | エッジを削除 |
|
|
1071
|
+
| GET | `/api/minion/dag-workflows/:id/contracts` | 全contractsを取得 |
|
|
1072
|
+
| PUT | `/api/minion/dag-workflows/:id/contracts` | 全contractsを上書き |
|
|
1073
|
+
| POST | `/api/minion/dag-workflows/:id/contracts` | 個別contractを追加/更新 |
|
|
1074
|
+
| DELETE | `/api/minion/dag-workflows/:id/contracts` | 個別contractを削除 |
|
|
1071
1075
|
| POST | `/api/minion/dag-workflows/:id/validate` | ドラフトをフル検証(公開せず) |
|
|
1072
1076
|
|
|
1073
1077
|
#### POST `/api/minion/dag-workflows/:id/nodes` — ノード追加
|
|
@@ -1166,6 +1170,7 @@ POST `/api/minion/dag-workflows/:id/publish` (body なし):
|
|
|
1166
1170
|
- `source`, `target` (必須): 既存ノードのID
|
|
1167
1171
|
- `kind` (任意): `normal` (default) | `approved` | `revision`
|
|
1168
1172
|
- `condition_label` (任意): conditional ノードの分岐ラベル
|
|
1173
|
+
- `contract` (任意): `graph.contracts` 内の型名への参照
|
|
1169
1174
|
- `id` (任意): 省略時は `edge_1`, `edge_2`, ... で自動採番
|
|
1170
1175
|
|
|
1171
1176
|
#### PATCH `/api/minion/dag-workflows/:id/edges/:edgeId` — エッジ更新
|
|
@@ -1173,18 +1178,85 @@ POST `/api/minion/dag-workflows/:id/publish` (body なし):
|
|
|
1173
1178
|
```json
|
|
1174
1179
|
{
|
|
1175
1180
|
"kind": "approved",
|
|
1176
|
-
"condition_label": "success"
|
|
1181
|
+
"condition_label": "success",
|
|
1182
|
+
"contract": "prototype"
|
|
1177
1183
|
}
|
|
1178
1184
|
```
|
|
1179
1185
|
|
|
1180
1186
|
- `kind` (任意): `normal` | `approved` | `revision`
|
|
1181
1187
|
- `condition_label` (任意): 分岐ラベル。`null` を渡すとクリア
|
|
1188
|
+
- `contract` (任意): contracts 内の型名。`null` を渡すとクリア
|
|
1182
1189
|
- 指定したフィールドのみ上書き(未指定フィールドは変更しない)
|
|
1183
1190
|
|
|
1184
1191
|
#### DELETE `/api/minion/dag-workflows/:id/edges/:edgeId` — エッジ削除
|
|
1185
1192
|
|
|
1186
1193
|
- エッジIDを指定して削除
|
|
1187
1194
|
|
|
1195
|
+
#### Contracts API
|
|
1196
|
+
|
|
1197
|
+
エッジを流れるデータの型を定義するための API。contracts は `graph.contracts` に保存される。
|
|
1198
|
+
|
|
1199
|
+
**Contract のスキーマ:**
|
|
1200
|
+
|
|
1201
|
+
```typescript
|
|
1202
|
+
{
|
|
1203
|
+
description: string // contract の説明
|
|
1204
|
+
fields: [{
|
|
1205
|
+
key: string // フィールド名
|
|
1206
|
+
type: "string" | "number" | "boolean" | "url" | "array" | "object" // フィールド型
|
|
1207
|
+
description: string // フィールドの説明
|
|
1208
|
+
required?: boolean // 必須フラグ (省略時 false)
|
|
1209
|
+
}]
|
|
1210
|
+
}
|
|
1211
|
+
```
|
|
1212
|
+
|
|
1213
|
+
**注意:**
|
|
1214
|
+
- エッジに設定する `contract` は `graph.contracts` に存在する名前でなければならない(存在しない名前を指定すると 400 エラー)
|
|
1215
|
+
- contract を削除すると、参照しているエッジの `contract` フィールドが自動でクリアされる(DELETE / PUT 共通)
|
|
1216
|
+
- `validate` エンドポイントはダングリング参照(存在しない contract への参照)をエラーとして報告する
|
|
1217
|
+
|
|
1218
|
+
##### GET `/api/minion/dag-workflows/:id/contracts` — 全contracts取得
|
|
1219
|
+
|
|
1220
|
+
レスポンス: `{ "contracts": { "name": { "description": "...", "fields": [...] }, ... } }`
|
|
1221
|
+
|
|
1222
|
+
##### PUT `/api/minion/dag-workflows/:id/contracts` — 全contracts上書き
|
|
1223
|
+
|
|
1224
|
+
```json
|
|
1225
|
+
{
|
|
1226
|
+
"contracts": {
|
|
1227
|
+
"prototype": {
|
|
1228
|
+
"description": "プロトタイプ成果物",
|
|
1229
|
+
"fields": [
|
|
1230
|
+
{ "key": "git_url", "type": "url", "description": "リポジトリURL", "required": true },
|
|
1231
|
+
{ "key": "preview_url", "type": "url", "description": "プレビューURL" }
|
|
1232
|
+
]
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
```
|
|
1237
|
+
|
|
1238
|
+
##### POST `/api/minion/dag-workflows/:id/contracts` — 個別contract追加/更新
|
|
1239
|
+
|
|
1240
|
+
```json
|
|
1241
|
+
{
|
|
1242
|
+
"name": "prototype",
|
|
1243
|
+
"contract": {
|
|
1244
|
+
"description": "プロトタイプ成果物",
|
|
1245
|
+
"fields": [
|
|
1246
|
+
{ "key": "git_url", "type": "url", "description": "リポジトリURL", "required": true }
|
|
1247
|
+
]
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
##### DELETE `/api/minion/dag-workflows/:id/contracts` — 個別contract削除
|
|
1253
|
+
|
|
1254
|
+
```json
|
|
1255
|
+
{ "name": "prototype" }
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
参照しているエッジの `contract` フィールドも自動でクリアされる。レスポンスの `cleared_edges` にクリアされたエッジIDの一覧が含まれる。
|
|
1259
|
+
|
|
1188
1260
|
#### POST `/api/minion/dag-workflows/:id/validate` — ドラフト検証
|
|
1189
1261
|
|
|
1190
1262
|
body なし。現在のドラフトを `validateDagGraph` でフル検証し結果を返す。**公開はしない**。
|
|
@@ -1245,6 +1317,9 @@ hq dag update-node <wf-id> fan_out_1 /tmp/u.json
|
|
|
1245
1317
|
|
|
1246
1318
|
テンプレート内部のノード/エッジを個別操作するAPIは現在ない。テンプレートは `template` フィールドの全体上書きで更新すること。テンプレートは start/end ノードを持たない sub-graph であり、バリデーション時に再帰的に検証される。
|
|
1247
1319
|
|
|
1320
|
+
> **⚠️ 重要: テンプレート内に `start` / `end` ノードを配置してはいけない。**
|
|
1321
|
+
> テンプレートのエントリポイントとエグジットポイントは、エッジ構造から自動検出される(incoming edge がないノード = エントリ、outgoing edge がないノード = エグジット)。`start` / `end` はトップレベル DAG 専用のノードタイプであり、テンプレート内に含めるとバリデーションエラーになる。テンプレート内では `skill`, `conditional`, `transform`, `review` 等の実行ノードのみ使用すること。
|
|
1322
|
+
|
|
1248
1323
|
### ミニオン CLI ラッパー
|
|
1249
1324
|
|
|
1250
1325
|
以下の `hq` サブコマンドが上記エンドポイントを呼び出す。いずれもリクエスト送信前にローカルで JSON 構文検証を行う。
|
package/docs/task-guides.md
CHANGED
|
@@ -345,8 +345,11 @@ hq dag remove-edge <wf-id> edge_3
|
|
|
345
345
|
|
|
346
346
|
**fan_out テンプレート編集:**
|
|
347
347
|
|
|
348
|
+
> **⚠️ テンプレート内に `start` / `end` ノードを入れないこと。** テンプレートのエントリ/エグジットはエッジ構造から自動検出される。`start` / `end` はトップレベル DAG 専用であり、テンプレート内に含めるとバリデーションエラーになる。`skill`, `conditional`, `transform`, `review` 等の実行ノードのみ使用すること。
|
|
349
|
+
|
|
348
350
|
```bash
|
|
349
351
|
# fan_out ノードの template を PATCH で上書き
|
|
352
|
+
# ※ start/end ノードは不要。実行ノードのみ配置する
|
|
350
353
|
cat > /tmp/t.json <<'EOF'
|
|
351
354
|
{
|
|
352
355
|
"template": {
|
package/linux/workflow-runner.js
CHANGED
|
@@ -92,7 +92,38 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
92
92
|
revisionContext = `## Revision Feedback\nThe reviewer requested changes to your previous output. Address the following feedback:\n${options.revisionFeedback}\n\n`
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
// Inject DAG input data context (data from upstream nodes)
|
|
96
|
+
let dagDataContext = ''
|
|
97
|
+
if (options.dagInputData && Object.keys(options.dagInputData).length > 0) {
|
|
98
|
+
dagDataContext = `## Input Data (from upstream nodes)\nThe following data was produced by upstream steps in this workflow. Use it as context for your task.\n\`\`\`json\n${JSON.stringify(options.dagInputData, null, 2)}\n\`\`\`\n\n`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Inject DAG contract context (input/output contracts from edges)
|
|
102
|
+
let contractContext = ''
|
|
103
|
+
if (options.dagInputContracts && options.dagInputContracts.length > 0) {
|
|
104
|
+
contractContext += '## Input Contracts\nThe input data above conforms to the following contract(s):\n'
|
|
105
|
+
for (const ic of options.dagInputContracts) {
|
|
106
|
+
contractContext += `### ${ic.contract_name}\n${ic.contract.description || ''}\n`
|
|
107
|
+
contractContext += '| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n'
|
|
108
|
+
for (const f of ic.contract.fields || []) {
|
|
109
|
+
contractContext += `| ${f.key} | ${f.type} | ${f.required ? 'Yes' : 'No'} | ${f.description || ''} |\n`
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
contractContext += '\n'
|
|
113
|
+
}
|
|
114
|
+
if (options.dagOutputContracts && options.dagOutputContracts.length > 0) {
|
|
115
|
+
contractContext += '## Output Contract\nYour output MUST conform to the following contract(s). Include all required fields in your execution report.\n'
|
|
116
|
+
for (const oc of options.dagOutputContracts) {
|
|
117
|
+
contractContext += `### ${oc.contract_name}\n${oc.contract.description || ''}\n`
|
|
118
|
+
contractContext += '| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n'
|
|
119
|
+
for (const f of oc.contract.fields || []) {
|
|
120
|
+
contractContext += `| ${f.key} | ${f.type} | ${f.required ? 'Yes' : 'No'} | ${f.description || ''} |\n`
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
contractContext += '\n'
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const prompt = `${rolePrefix}${revisionContext}${dagDataContext}${contractContext}Run the following skills in order: ${skillCommands}.`
|
|
96
127
|
|
|
97
128
|
// Exit code file to capture CLI result
|
|
98
129
|
const exitCodeFile = `/tmp/tmux-exit-${sessionName}`
|
package/package.json
CHANGED
package/win/workflow-runner.js
CHANGED
|
@@ -99,7 +99,38 @@ async function executeWorkflowSession(workflow, executionId, skillNames, options
|
|
|
99
99
|
revisionContext = `## Revision Feedback\nThe reviewer requested changes to your previous output. Address the following feedback:\n${options.revisionFeedback}\n\n`
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
// Inject DAG input data context (data from upstream nodes)
|
|
103
|
+
let dagDataContext = ''
|
|
104
|
+
if (options.dagInputData && Object.keys(options.dagInputData).length > 0) {
|
|
105
|
+
dagDataContext = `## Input Data (from upstream nodes)\nThe following data was produced by upstream steps in this workflow. Use it as context for your task.\n\`\`\`json\n${JSON.stringify(options.dagInputData, null, 2)}\n\`\`\`\n\n`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Inject DAG contract context (input/output contracts from edges)
|
|
109
|
+
let contractContext = ''
|
|
110
|
+
if (options.dagInputContracts && options.dagInputContracts.length > 0) {
|
|
111
|
+
contractContext += '## Input Contracts\nThe input data above conforms to the following contract(s):\n'
|
|
112
|
+
for (const ic of options.dagInputContracts) {
|
|
113
|
+
contractContext += `### ${ic.contract_name}\n${ic.contract.description || ''}\n`
|
|
114
|
+
contractContext += '| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n'
|
|
115
|
+
for (const f of ic.contract.fields || []) {
|
|
116
|
+
contractContext += `| ${f.key} | ${f.type} | ${f.required ? 'Yes' : 'No'} | ${f.description || ''} |\n`
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
contractContext += '\n'
|
|
120
|
+
}
|
|
121
|
+
if (options.dagOutputContracts && options.dagOutputContracts.length > 0) {
|
|
122
|
+
contractContext += '## Output Contract\nYour output MUST conform to the following contract(s). Include all required fields in your execution report.\n'
|
|
123
|
+
for (const oc of options.dagOutputContracts) {
|
|
124
|
+
contractContext += `### ${oc.contract_name}\n${oc.contract.description || ''}\n`
|
|
125
|
+
contractContext += '| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n'
|
|
126
|
+
for (const f of oc.contract.fields || []) {
|
|
127
|
+
contractContext += `| ${f.key} | ${f.type} | ${f.required ? 'Yes' : 'No'} | ${f.description || ''} |\n`
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
contractContext += '\n'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const prompt = `${rolePrefix}${revisionContext}${dagDataContext}${contractContext}Run the following skills in order: ${skillCommands}.`
|
|
103
134
|
|
|
104
135
|
const logFile = logManager.getLogPath(executionId)
|
|
105
136
|
|