@geekbeer/minion 3.50.0 → 3.51.2
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-cron-poller.js +101 -0
- package/docs/api-reference.md +44 -6
- package/docs/task-guides.md +4 -2
- package/linux/bin/hq +1 -1
- package/linux/server.js +2 -0
- package/package.json +1 -1
- package/rules/core.md +3 -1
- package/win/bin/hq.ps1 +1 -1
- package/win/server.js +2 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DAG Cron Poller
|
|
3
|
+
*
|
|
4
|
+
* Polling daemon that asks HQ to fire any due cron-scheduled DAG workflows
|
|
5
|
+
* for projects where this minion is the PM. The actual claim, trigger, and
|
|
6
|
+
* next_run_at advancement are all done in HQ — the poller is just a clock
|
|
7
|
+
* source. Multiple PMs in the same project are race-safe via a conditional
|
|
8
|
+
* UPDATE on dag_workflows.next_run_at inside the HQ handler.
|
|
9
|
+
*
|
|
10
|
+
* The minimum cron interval (5 min) makes a 60s polling cadence more than
|
|
11
|
+
* precise enough; cron precision is bounded by max(poll_interval, 60s).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { config, isHqConfigured } = require('../config')
|
|
15
|
+
|
|
16
|
+
const POLL_INTERVAL_MS = 60_000
|
|
17
|
+
|
|
18
|
+
let polling = false
|
|
19
|
+
let pollTimer = null
|
|
20
|
+
let lastPollAt = null
|
|
21
|
+
let lastFiredCount = 0
|
|
22
|
+
|
|
23
|
+
async function pollOnce() {
|
|
24
|
+
if (!isHqConfigured()) return
|
|
25
|
+
if (polling) return
|
|
26
|
+
|
|
27
|
+
polling = true
|
|
28
|
+
try {
|
|
29
|
+
const url = `${config.HQ_URL}/api/dag/minion/dag-cron-tick`
|
|
30
|
+
const resp = await fetch(url, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'Authorization': `Bearer ${config.API_TOKEN}`,
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if (!resp.ok) {
|
|
39
|
+
throw new Error(`dag-cron-tick failed: ${resp.status}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = await resp.json()
|
|
43
|
+
const fired = Array.isArray(data.fired) ? data.fired : []
|
|
44
|
+
const skipped = Array.isArray(data.skipped) ? data.skipped : []
|
|
45
|
+
const errors = Array.isArray(data.errors) ? data.errors : []
|
|
46
|
+
|
|
47
|
+
lastFiredCount = fired.length
|
|
48
|
+
|
|
49
|
+
if (fired.length > 0 || errors.length > 0) {
|
|
50
|
+
console.log(
|
|
51
|
+
`[DagCronPoller] Fired ${fired.length}, skipped ${skipped.length}, errors ${errors.length}`
|
|
52
|
+
)
|
|
53
|
+
for (const f of fired) {
|
|
54
|
+
console.log(`[DagCronPoller] fired: ${f.name} (workflow ${f.workflow_id} → execution ${f.execution_id})`)
|
|
55
|
+
}
|
|
56
|
+
for (const e of errors) {
|
|
57
|
+
console.error(`[DagCronPoller] error on ${e.workflow_id}: ${e.error}`)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (err.message?.includes('fetch failed') || err.message?.includes('ECONNREFUSED')) {
|
|
62
|
+
console.log('[DagCronPoller] HQ unreachable, will retry next cycle')
|
|
63
|
+
} else {
|
|
64
|
+
console.error(`[DagCronPoller] Poll error: ${err.message}`)
|
|
65
|
+
}
|
|
66
|
+
} finally {
|
|
67
|
+
polling = false
|
|
68
|
+
lastPollAt = new Date().toISOString()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function start() {
|
|
73
|
+
if (!isHqConfigured()) {
|
|
74
|
+
console.log('[DagCronPoller] HQ not configured, cron poller disabled')
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Stagger initial poll so the three pollers (step / dag-step / cron) do
|
|
79
|
+
// not all hit HQ in the same second on minion startup.
|
|
80
|
+
setTimeout(() => pollOnce(), 12_000)
|
|
81
|
+
pollTimer = setInterval(() => pollOnce(), POLL_INTERVAL_MS)
|
|
82
|
+
console.log(`[DagCronPoller] Started (polling every ${POLL_INTERVAL_MS / 1000}s)`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function stop() {
|
|
86
|
+
if (pollTimer) {
|
|
87
|
+
clearInterval(pollTimer)
|
|
88
|
+
pollTimer = null
|
|
89
|
+
console.log('[DagCronPoller] Stopped')
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getStatus() {
|
|
94
|
+
return {
|
|
95
|
+
running: pollTimer !== null,
|
|
96
|
+
last_poll_at: lastPollAt,
|
|
97
|
+
last_fired_count: lastFiredCount,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { start, stop, pollOnce, getStatus }
|
package/docs/api-reference.md
CHANGED
|
@@ -1275,8 +1275,7 @@ PUT `/api/minion/dag-workflows/:id` body(全フィールド optional、省略
|
|
|
1275
1275
|
"content": "...",
|
|
1276
1276
|
"change_summary": "Updated fan-out branch",
|
|
1277
1277
|
"name": "renamed-dag",
|
|
1278
|
-
"is_active": true
|
|
1279
|
-
"maturity": "preview|stable|..."
|
|
1278
|
+
"is_active": true
|
|
1280
1279
|
}
|
|
1281
1280
|
```
|
|
1282
1281
|
|
|
@@ -1338,7 +1337,7 @@ POST `/api/minion/dag-workflows/:id/publish` (body なし):
|
|
|
1338
1337
|
|------|---------------|------|
|
|
1339
1338
|
| `start` / `end` | なし | |
|
|
1340
1339
|
| `skill` | `skill_id`, `skill_version_id`, `assigned_role` | |
|
|
1341
|
-
| `review` | `review: {
|
|
1340
|
+
| `review` | `assigned_role`, `review: { criteria, revision_target }` | `assigned_role: 'human'` でダッシュボードからの人間レビュー、`'pm'`/`'engineer'`/`'accountant'` で将来の minion レビュー枠(現在は未実装、`waiting` で停止)。`revision` / `approved` エッジは自動生成 |
|
|
1342
1341
|
| `fan_out` | `fan_out_source`, `template` | `template` は sub-graph `{ nodes, edges }` |
|
|
1343
1342
|
| `join` | `join_mode`, `aggregation` | |
|
|
1344
1343
|
| `conditional` | `condition_type`, `branches` or `default_branch` | |
|
|
@@ -1351,13 +1350,24 @@ POST `/api/minion/dag-workflows/:id/publish` (body なし):
|
|
|
1351
1350
|
"label": "Quality Check",
|
|
1352
1351
|
"after": "skill_1",
|
|
1353
1352
|
"before": "end",
|
|
1353
|
+
"assigned_role": "human",
|
|
1354
1354
|
"review": {
|
|
1355
|
-
"reviewer_type": "human",
|
|
1356
1355
|
"criteria": "成果物の品質を確認",
|
|
1357
|
-
"revision_target": "skill_1"
|
|
1356
|
+
"revision_target": "skill_1",
|
|
1357
|
+
"preferred_reviewer_id": "<user UUID(任意・指定なしの場合は全プロジェクトメンバーに通知)>"
|
|
1358
1358
|
}
|
|
1359
1359
|
}
|
|
1360
1360
|
```
|
|
1361
|
+
|
|
1362
|
+
`assigned_role` の値:
|
|
1363
|
+
- `human` — ダッシュボードから人間が承認 (`workflow.review_requested` 通知が送信される)
|
|
1364
|
+
- `pm` / `engineer` / `accountant` — 将来の minion レビュー枠 (現在は未実装、`waiting` で停止)
|
|
1365
|
+
|
|
1366
|
+
`preferred_reviewer_id` (任意):
|
|
1367
|
+
- `assigned_role === 'human'` の場合: `profiles.user_id` を指定すると通知をその人に絞る。脱退済みなら全メンバーにフォールバック
|
|
1368
|
+
- `assigned_role !== 'human'` の場合: `minions.id` を指定(現在は未実装)
|
|
1369
|
+
|
|
1370
|
+
> 互換性: 旧形式 `review: { reviewer_type, reviewer_id }` も読み取り可能ですが、新規作成時は `assigned_role` + `preferred_reviewer_id` を使ってください。
|
|
1361
1371
|
→ `skill_1 → end` のエッジが削除され、以下が自動生成:
|
|
1362
1372
|
- `skill_1 → review_1` (normal)
|
|
1363
1373
|
- `review_1 → end` (approved)
|
|
@@ -1634,7 +1644,6 @@ GET `/api/minion/dag-workflows/:id` Response:
|
|
|
1634
1644
|
"project_id": "uuid",
|
|
1635
1645
|
"name": "my-dag",
|
|
1636
1646
|
"is_active": true,
|
|
1637
|
-
"maturity": "draft|stable|...",
|
|
1638
1647
|
"current_version_id": "uuid",
|
|
1639
1648
|
"draft_graph": { "nodes": [...], "edges": [...] } ,
|
|
1640
1649
|
"draft_content": "markdown|null",
|
|
@@ -1693,9 +1702,12 @@ GET `/api/minion/dag-executions/:id` Response:
|
|
|
1693
1702
|
| GET | `/api/dag/minion/pending-nodes` | 自分が実行すべき pending ノード一覧(ロール一致・依存解決済み) |
|
|
1694
1703
|
| POST | `/api/dag/minion/claim-node` | ノードを楽観ロックで取得(排他実行) |
|
|
1695
1704
|
| POST | `/api/dag/minion/node-complete` | ノード完了を報告し、下流ノードへカスケード |
|
|
1705
|
+
| POST | `/api/dag/minion/dag-cron-tick` | PMロールのミニオン専用。所属プロジェクトのcron-enabled DAGワークフローを発火 |
|
|
1696
1706
|
|
|
1697
1707
|
ミニオンの `dag-step-poller` デーモンが30秒ごとに `pending-nodes` を叩き、最大2並列で claim → skill/transform 実行 → node-complete の流れを回す。`skill`・`transform` 以外のノード(start/end/review/fan_out/join/conditional)はHQ内部のカスケードエンジンが処理するため、ミニオンには返されない。
|
|
1698
1708
|
|
|
1709
|
+
`dag-cron-poller` デーモン(v3.51.0〜)は60秒ごとに `dag-cron-tick` を叩く。HQ側で「呼び出しミニオンがPMロールであるプロジェクトのDAGワークフロー」のうち `cron_enabled AND next_run_at <= now()` を atomic claim → triggerDagExecution。Push型ではないので、PMミニオンが offline ならcron発火が遅延する。
|
|
1710
|
+
|
|
1699
1711
|
#### GET /api/dag/minion/pending-nodes
|
|
1700
1712
|
|
|
1701
1713
|
Response:
|
|
@@ -1774,6 +1786,32 @@ Response:
|
|
|
1774
1786
|
{ "success": true, "review_pending": true }
|
|
1775
1787
|
```
|
|
1776
1788
|
|
|
1789
|
+
#### POST /api/dag/minion/dag-cron-tick
|
|
1790
|
+
|
|
1791
|
+
PMロールのミニオン専用。所属プロジェクトのcron-enabled DAGワークフローを発火させる。`dag-cron-poller` が60秒間隔で叩くため、AI側から直接呼ぶ必要はない。
|
|
1792
|
+
|
|
1793
|
+
Body: なし
|
|
1794
|
+
|
|
1795
|
+
Response:
|
|
1796
|
+
```json
|
|
1797
|
+
{
|
|
1798
|
+
"fired": [
|
|
1799
|
+
{ "workflow_id": "uuid", "execution_id": "uuid", "name": "Daily Report" }
|
|
1800
|
+
],
|
|
1801
|
+
"skipped": [
|
|
1802
|
+
{ "workflow_id": "uuid", "reason": "claimed_by_other|already_running" }
|
|
1803
|
+
],
|
|
1804
|
+
"errors": [
|
|
1805
|
+
{ "workflow_id": "uuid", "error": "..." }
|
|
1806
|
+
]
|
|
1807
|
+
}
|
|
1808
|
+
```
|
|
1809
|
+
|
|
1810
|
+
- 呼び出しミニオンがPMロールでないプロジェクトのワークフローは対象外(HQ側で `project_members.role='pm'` でフィルタ)
|
|
1811
|
+
- 同一プロジェクトに複数のPMミニオンが居る場合、条件付き UPDATE で先勝ちclaim。負けた側は `skipped: claimed_by_other` で返る
|
|
1812
|
+
- `cron_skip_if_running=true` のワークフローで前回実行が `pending`/`running` のままなら `skipped: already_running` で返る
|
|
1813
|
+
- 発火後の経路は手動トリガーと同じ。`dag_executions` 行作成 → start auto-complete → cascade → 各ミニオンの `dag-step-poller` が pending node を pull
|
|
1814
|
+
|
|
1777
1815
|
### DAG Graph Structure
|
|
1778
1816
|
|
|
1779
1817
|
DAG ワークフローの graph は以下の構造で保存される(`dag_workflow_versions.graph` / `dag_executions.graph_snapshot`):
|
package/docs/task-guides.md
CHANGED
|
@@ -154,6 +154,8 @@ DAG ワークフローは有向非巡回グラフでスキル間の依存関係
|
|
|
154
154
|
|
|
155
155
|
**HQダッシュボードの DAG エディタ(プロジェクト画面 → 「DAG (beta)」タブ)で GUI 編集**できるほか、**ミニオンからは JSON ベースで編集可能**(PMロールのみ)。ミニオンは実行ランタイムも担当し、`dag-step-poller` デーモンが pending ノードを自動で処理します。
|
|
156
156
|
|
|
157
|
+
**cronスケジュール (v3.51.0〜):** DAGワークフローは cron 式による定期実行をサポート。設定はHQダッシュボードのDAGビューにある SchedulePanel から行う(最小実行間隔 5分)。発火主体はそのプロジェクトのPMロールのミニオンで、`dag-cron-poller` デーモンが60秒間隔で `/api/dag/minion/dag-cron-tick` を叩いて発火させる。PM不在のプロジェクトでは cron は発火しない。
|
|
158
|
+
|
|
157
159
|
### DAG ワークフロー一覧の取得
|
|
158
160
|
|
|
159
161
|
```bash
|
|
@@ -164,7 +166,7 @@ hq list dag-workflows
|
|
|
164
166
|
hq list dag-workflows <project-uuid>
|
|
165
167
|
```
|
|
166
168
|
|
|
167
|
-
レスポンスには各ワークフローの `id`, `name`, `is_active`, `
|
|
169
|
+
レスポンスには各ワークフローの `id`, `name`, `is_active`, `my_role` 等が含まれる。個別の graph 詳細は `hq fetch dag-workflow <id>` で取得する。
|
|
168
170
|
|
|
169
171
|
### ミニオンによる DAG ワークフロー編集 (PM のみ)
|
|
170
172
|
|
|
@@ -307,8 +309,8 @@ cat > /tmp/n.json <<'EOF'
|
|
|
307
309
|
"label": "Quality Check",
|
|
308
310
|
"after": "skill_1",
|
|
309
311
|
"before": "end_1",
|
|
312
|
+
"assigned_role": "human",
|
|
310
313
|
"review": {
|
|
311
|
-
"reviewer_type": "human",
|
|
312
314
|
"criteria": "成果物の品質を確認",
|
|
313
315
|
"revision_target": "skill_1"
|
|
314
316
|
}
|
package/linux/bin/hq
CHANGED
|
@@ -275,7 +275,7 @@ case "${1:-}" in
|
|
|
275
275
|
body_file="${4:-}"
|
|
276
276
|
if [ -z "$id" ] || [ -z "$body_file" ]; then
|
|
277
277
|
echo "Usage: hq put dag-workflow <id> <body.json>" >&2
|
|
278
|
-
echo " body.json may contain { graph, content, change_summary, name, is_active
|
|
278
|
+
echo " body.json may contain { graph, content, change_summary, name, is_active }" >&2
|
|
279
279
|
exit 1
|
|
280
280
|
fi
|
|
281
281
|
validate_json_file "$body_file"
|
package/linux/server.js
CHANGED
|
@@ -60,6 +60,7 @@ const { getConfigWarnings } = require('../core/lib/config-warnings')
|
|
|
60
60
|
// Pull-model daemons (from core/)
|
|
61
61
|
const stepPoller = require('../core/lib/step-poller')
|
|
62
62
|
const dagStepPoller = require('../core/lib/dag-step-poller')
|
|
63
|
+
const dagCronPoller = require('../core/lib/dag-cron-poller')
|
|
63
64
|
const boardTaskPoller = require('../core/lib/board-task-poller')
|
|
64
65
|
const revisionWatcher = require('../core/lib/revision-watcher')
|
|
65
66
|
const reflectionScheduler = require('../core/lib/reflection-scheduler')
|
|
@@ -407,6 +408,7 @@ async function start() {
|
|
|
407
408
|
// Start Pull-model daemons
|
|
408
409
|
stepPoller.start()
|
|
409
410
|
dagStepPoller.start()
|
|
411
|
+
dagCronPoller.start()
|
|
410
412
|
boardTaskPoller.setRunner(boardTaskRunner)
|
|
411
413
|
boardTaskPoller.start()
|
|
412
414
|
revisionWatcher.start()
|
package/package.json
CHANGED
package/rules/core.md
CHANGED
|
@@ -176,7 +176,9 @@ Note: Codex CLI の `.codex/` ディレクトリはLLMからの直接編集が
|
|
|
176
176
|
|
|
177
177
|
主なカテゴリ: Projects, Context, Workflows, DAG Workflows, Skills, Executions, Routines, Reports
|
|
178
178
|
|
|
179
|
-
DAG ワークフローのランタイム API は `$HQ_URL/api/dag/minion/*`(pending-nodes / claim-node / node-complete)。`dag-step-poller` デーモンが自動でポーリングするため、通常ミニオンのAI側から直接叩くことは無い。
|
|
179
|
+
DAG ワークフローのランタイム API は `$HQ_URL/api/dag/minion/*`(pending-nodes / claim-node / node-complete / dag-cron-tick)。`dag-step-poller` と `dag-cron-poller` デーモンが自動でポーリングするため、通常ミニオンのAI側から直接叩くことは無い。
|
|
180
|
+
|
|
181
|
+
**DAG Cron Trigger (v3.51.0〜):** `dag_workflows.cron_enabled = true` が設定されたDAGワークフローは、所属プロジェクトのPMミニオンの `dag-cron-poller` が60秒間隔で `POST /api/dag/minion/dag-cron-tick` を叩いて発火する。発火主体はPMロールのミニオンに限定(HQ側で `project_members.role='pm'` でフィルタ)。最小実行間隔は5分(HQ側 `validateCronExpression` で enforce)。発火後の経路は手動トリガーと同じで、`dag_executions` 行が作成され、通常通り各ミニオンの `dag-step-poller` がpendingノードをclaimする。
|
|
180
182
|
|
|
181
183
|
## Environment Variables
|
|
182
184
|
|
package/win/bin/hq.ps1
CHANGED
|
@@ -237,7 +237,7 @@ switch ($Command) {
|
|
|
237
237
|
$BodyFile = $Arg3
|
|
238
238
|
if (-not $Id -or -not $BodyFile) {
|
|
239
239
|
Write-Error "Usage: hq put dag-workflow <id> <body.json>"
|
|
240
|
-
Write-Error " body.json may contain { graph, content, change_summary, name, is_active
|
|
240
|
+
Write-Error " body.json may contain { graph, content, change_summary, name, is_active }"
|
|
241
241
|
exit 1
|
|
242
242
|
}
|
|
243
243
|
Assert-ValidJsonFile -Path $BodyFile
|
package/win/server.js
CHANGED
|
@@ -36,6 +36,7 @@ const { getConfigWarnings } = require('../core/lib/config-warnings')
|
|
|
36
36
|
// Pull-model daemons (from core/)
|
|
37
37
|
const stepPoller = require('../core/lib/step-poller')
|
|
38
38
|
const dagStepPoller = require('../core/lib/dag-step-poller')
|
|
39
|
+
const dagCronPoller = require('../core/lib/dag-cron-poller')
|
|
39
40
|
const boardTaskPoller = require('../core/lib/board-task-poller')
|
|
40
41
|
const revisionWatcher = require('../core/lib/revision-watcher')
|
|
41
42
|
const reflectionScheduler = require('../core/lib/reflection-scheduler')
|
|
@@ -374,6 +375,7 @@ async function start() {
|
|
|
374
375
|
// Start Pull-model daemons
|
|
375
376
|
stepPoller.start()
|
|
376
377
|
dagStepPoller.start()
|
|
378
|
+
dagCronPoller.start()
|
|
377
379
|
boardTaskPoller.setRunner(boardTaskRunner)
|
|
378
380
|
boardTaskPoller.start()
|
|
379
381
|
revisionWatcher.start()
|